
DevelopmentSega Master System / Mark III / Game Gear 
Home  Forums  Games  Scans  Maps  Cheats  Credits 
by andete. Original documents available at: https://github.com/andete/ym2413/tree/master/results
It's been a while since my last post. Partly because I took a little break and partly because it took me quite a while to figure this stuff out. Even though, in retrospect, the underlying mechanism isn't too difficult at all.
In this post I'll investigate the transitions between the attackdecaysustain release states in the ADSRenvelope in more detail. Take a look at this image:
This shows the attack and decaypart of the envelope for a waveform with AR=6 and DR=3. Notice that in both the attack and the decay phases there are segments of two different widths, following the pattern 'wide narrow narrow, wide narrow narrow, ...'. Also notice that the segment between attack and decay, encircled in green (more specifically this is the first segment of the decay phase), still has a different width than either of the repeating widths in the attack or decay phase.
The same is true for the first segment of the release phase, or for the first segment after any transition: almost always this segment has a different width than the surrounding segments.
Before going into more detail, let's first repeat what we already know about the envelope generator from previous investigations:
From the (4bit) AR,DR,RR settings, the channel frequency and the KSR setting, we calculate a 6bit effective rate. This rate determines how quickly the envelope changes.
The upper 4 bits of this rate select a bit in the global counter (this counter increases every sample, so at ~49,7kHz). Only if the selected bit toggles (in other words, after a selectable powerof2 number of samples), we execute the stuff in the next paragraph.
The lower 2 bits of the effective rate select 1 of 4 possible sequences:
0: 0,1,0,1,0,1,0,1 (4 out of 8) 1: 0,1,0,1,1,1,0,1 (5 out of 8) 2: 0,1,1,1,0,1,1,1 (6 out of 8) 3: 0,1,1,1,1,1,1,1 (7 out of 8)
From the global counter we take 3 bits, starting from the selected bit (previous paragraph) and going to the left. Or in other words: take the global counter, shift it down so that the selected bit is in position 0 and take the lower 3 bits of that number. The resulting value (between 07) selects a bit from the above sequence. Now only if that selected bit is '1' we advance to the next envelope level.
(For very high rates, the rules are more complicated, but I'll refer to the previous posts for those details. They aren't important for this post).
If we repeat the experiment from the introduction multiple times, we see that the width of the first segments varies. This section is all about measuring that phenomena.
If we zoom in on the segment encircled in green in the above image, we get this:
As I annotated in this image, I measure the width of a segment from the first peak of the segment to the first peak of the next segment. These peaks are the oscillations of the basis waveform (a sine wave in this case). So choosing a higher frequency allows to more accurately measure the segment width.
In the hope of making the analysis simpler, I started measuring with the
simplest pattern: "0,1,0,1,0,1,0,1" (4outof8)
. This gives each segment in
steadystate the same width. So all wide segments instead of narrownarrowwide
like in the above images.
Though this 4outof8 pattern is only used for notes with low frequency (first two octaves, the BLOCK channel setting). So that limits the maximum frequency, and thus also the measuring accuracy. If I combine the maximum frequency (0x3ff) with the maximum multiplication factor (car.ML=15), the distance between two peaks is about 37 samples.
Only after I already made a large number of measurements, I realized I could improve the accuracy by also measuring the corresponding segment mirrored around the xaxis (roughly speaking: this segment is shifted half a period in time and has 'opposite' amplitude). This allows to derive an upper and lower bound for the actual segment width.
In the first set of measurements I looked at the transition between attack and decay for various settings of AR and DR. See the table below for the results:
AR/DR 1  2  3  4  5  6  7 =====++================+==============+==============+==============+==============+============+============ 1  1(*)  0x800(#)      ++++++++ 2 3x0x07f20x0814  1(*)  0x3f10x413     3x0x17fb0x181d       ++++++++  0x1bec0x1c0e  0x3f10x413  1(*) 3x0x200(#)  0x100(#)  0x80(#)  0x40(#)  0x0404(#)  0xbf50xc17       0x047c(#) ? 0x3fc(#)      3  0x0bfe(#)  0x402(#)      2x 0x0c04(#)  0x404(#)       0x13fe(#) 2x 0xbfc(#)       0x1480(#) ? 0xc01(#)       0x1bfe(#) 2x 0xc03(#)      ++++++++  0x11f80x121a  0x1f00x212 3x0x1f00x212  1(*) 2x0x100(#) 2x0x80(#) 2x0x40(#) 4  0x15fb0x161d  0x5f20x614  0x5f10x613      0x19fd0x1a1f  0xdf70xe19  0x200(#)      0x0a05(#)  0x200(#)      ++++++++  0x04f20x0514  0x2f00x313  0x0f00x112 2x0x0ef0x111  1(*) 2x0x80(#) 2x0x40(#) 5  0x08f30x0915  0x4f20x514  0x2f10x313 3x0x2f10x312     0x1aeb0x1b0d  0x8f40x916  0x6f30x715 5x 0x300(#)     0x0915(#)  0x100(#)  0x2fd(#)      0x0cf5(#)  0x2ff(#) 4x 0x4fc(#)      0x0efe(#)  0x4fc(#) 4x 0x700(#)      0x14fc(#)  0x902(#)       0x16fb(#)  0x97a(#) ?      0x1902(#)  0xeff(#)       0x191d(#)        0x1cfe(#)       ++++++++ 2x0x01670x0189  0x2670x289  0x3680x38a  0x0660x088  0x0770x099  1(*) 2x0x40(#)  0x116f0x1191  0x5690x58b  0x3780x39c  0x0780x09a  0x1780x19a   6  0x13800x13a1  0xb510xb73? 0x4790x49b 2x0x1670x189 3x 0x080(#)    0x16710x1693 2x0xd7e0xda0 2x0x6680x68a  0x1780x19a 2x 0x17f(#)    0x1e740x1e97  0x386(#)  0x76a0x78c  0x3790x39b  0x199(#)    0x0285(#)  0x580(#) 2x 0x080(#) 2x 0x080(#)     0x0496(#)  0xa83(#)  0x182(#)  0x281(#)     0x0c84(#)  0xb84(#)  0x284(#) 2x 0x284(#)     0x1485(#)  0xe80(#)  0x383(#) 2x 0x380(#)     0x1882(#)  0xf80(#)  0x484(#)  0x385     0x1982(#)   0x580(#)       2x 0x680(#)        0x77e(#)     ++++++++  0x00bc0x00de  0x0ac0x0ce  0x0ab0x0cd  0x1240x146  0x0330x055  0x330x55  1(*)  0x01240x0146  0x1ad0x1cf  0x5370x559  0x1350x157  0x0ac0x0cd  0xbc0xde  7 2x0x04bf0x04e1  0x4360x458  0x6af0x6d0  0x2240x246  0x1350x157  0x44(#)   0x05360x0559  0xe2a0xe4c  0x7af0x7d1  0x3bd0x3df  0x044(#)  0x45(#)   0x0fb30x0fd5  0xfb20xfd4  0x156(#)  0x044(#)  0x045(#)  0xac(#)   0x143e0x1460   0x1be(#)  0x0ce(#)  0x0cd(#) 2x 0xce(#)     0x334(#)  0x156(#)  0x135(#)     2x 0x357(#)  0x1be(#)  0x156(#)      0x4ae(#)  0x246(#)  0x1be(#)      0x648(#)  0x2cf(#)  0x1dd(#)      2x 0x3be(#)    ++++++++     0x09a0x0bc  0x0560x078  0x450x67 4x0x110x33     0x0cd0x0ef  0x0cd0x0ef 3x0x560x78  0x560x78 8     0x0de0x100 2x0x1550x177 2x0x9a0xbc     2x0x1de0x200  0x1890x1ab 2x0xde0xff      0x2df0x301        0x3cf0x3f1   
Next I made similar measurements for the decaytorelease transition. But because taking these measurements takes a long time (3060 seconds per data point) I limited myself to DR=4,5. That should be sufficient because the pattern for this 2nd set of measurements (DR/RR) seems to be the same as for the 1st set (AR/DR).
Actually the only difference between the two sets occurs when the rowvalue is equal to the columnvalue (AR==DR or DR==RR). Before we had a segment of 0 or 1 sample, but now there's no exception when both rates are equal. It's asif there's no transition at all.
DR/RR 1  2  3  4  5  6  7 =====++================+==============+==============+==============+==============+============+============  0x05e20x0604 7x0x1ef0x211  0x1df0x201 4x0x3f10x413 6x0x0f10x112 2x0x670x89 2x0x220x44 4  0x09f50x0a17 3x0x5f30x615 2x0x1ef0x211   6x0x780x9a 6x0x330x55  0x15e80x160a 3x0xde60xe08 2x0x5f10x613     2x0x19eb0x1a0d       2x0x1dee0x1e10       ++++++++ 2x0x0cf50x0d17  0x2f00x312  0x2f10x313 3x0x0ee0x111 3x0x1ef0x211 5x0x670x89 7x0x340x56 5 2x0x12e80x130a  0x6f20x714 2x0x4f10x514 3x0x2ef0x312  4x0x780x9a   0x18fb0x191d  0x8f30x915 2x0x6f20x714       0xef70xf19     
Next I tried to pick nice 'round' numbers that fall within the error margins of
all the measurements. E.g. for the combination AR=4/DR=2 such numbers are
'0x200, 0x600 and 0xe00'. That seems to match the formula '0x200 + n * 0x400'
.
And when I made more measurements for this combination, I eventually also found
a segment with width 0xa00. This confirms that formula. So in the table below
for AR=4/DR=2 I wrote '0x200 0x600 0xa00 0xe00'.
Actually for all the combinations with DR < AR (or DR < RR) I found a similar pattern: all the observed segment widths seem to match this formula:
firstsegmentlength = (2n + 1) * L
with: L = 1 << (13  AR)
Note that L is also the required number of steps of the global counter described in the introduction.
As already mentioned before, when AR == DR, the first segment was often not visible or only 1 sample wide. We still need to investigate this further. On the other hand DR == RR is nothing special.
When 'DR > AR' (or RR > DR), the pattern is simple:
firstsegmentlength = 1 << (13  DR)
Note that this is half of all the later segments in the decay (or release) phase. Or in other words, the first segment is always a narrow segment while all later segments are wide (for these low frequencies (4outof8 pattern) we only have wide segments in steadystate).
AR/DR 1  2  3  4  5  6  7 =====++===============+=============+=============+=============+=============+======+====== 1  1(*)  0x800      ++++++++ 2  0x0800 0x1800  1(*)  0x400     ++++++++ 3  0x0400 0x1400  0x400  1(*)  0x200  0x100  0x80  0x40  0x0c00 0x1c00  0xc00      ++++++++  0x0200 0x1200  0x200  0x200  1(*)  0x100  0x80  0x40 4  0x0600 0x1600  0x600  0x600      0x0a00 0x1a00  0xa00       0x0e00 0x1e00  0xe00      ++++++++  0x0100 ...  0x100 0x900  0x100  0x100  1(*)  0x80  0x40 5  0x0300 0x1b00  0x300 0xb00  0x300  0x300     0x0500 0x1d00  0x500 0xd00  0x500      ... 0x1f00  0x700 0xf00  0x700     ++++++++  0x0080 ...  0x080 ...  0x080 0x480  0x080  0x080  1(*)  0x40 6  0x0180 0x1d80  0x180 0xd80  0x180 0x580  0x180  0x180    0x0280 0x1e80  0x280 0xe80  0x280 0x680  0x280     ... 0x1f80  ... 0xf80  0x380 0x780  0x380    ++++++++  0x0040 ...  0x040 ...  0x040 ...  0x040 0x240  0x040  0x40  1(*) 7  0x00c0 0x1ec0  0x0c0 0xec0  0x0c0 0x6c0  0x0c0 0x280  0x0c0  0xc0   0x0140 0x1f40  0x140 0xf40  0x140 0x740  0x140 0x340  0x140    ... 0x1fc0  ... 0xfc0  ... 0x7c0  0x1c0 0x3c0  0x1c0   ++++++++     0x020 ...  0x020 0x120  0x20  0x20 8     0x060 0x360  0x060 0x160  0x60  0x60     0x0a0 0x3a0  0x0a0 0x1a0  0xa0      ... 0x3e0  0x0e0 0x1e0  0xe0  DR/RR 1  2  3  4  5  6  7 =====++===============+=============+=============+=============+=============+======+======  0x0200 0x1200  0x200  0x200  0x400  0x100  0x80  0x40 4  0x0600 0x1600  0x600  0x600      0x0a00 0x1a00  0xa00       0x0e00 0x1e00  0xe00      ++++++++  0x0100 ...  0x100 0x900  0x100  0x100  0x200  0x80  0x40 5  0x0300 0x1b00  0x300 0xb00  0x300  0x300     0x0500 0x1d00  0x500 0xd00  0x500      ... 0x1f00  0x700 0xf00  0x700    
Let's again take AR=4/DR=2 as an example.
Note that, in general, when the attack phase starts, the value of the global
counter is random. In this example, when the attack phase ends we've just
processed an AR event with a '1' in the 4out8 table and that occurs only
every 0x400 samples. On the other hand, in this example, DR only effectively
advances every 0x1000 samples. Not 0x10000 starting from the end of attack,
but when the global counter reaches the next multiple of 0x10000 (this is not
100% correct, see next paragraphs). The lower bits of the global counter could
already have reached 0xc00, so the next multiple of 0x1000 is only 0x400 samples
away, But it could also be 0x800, 0xc00 or 0x1000 samples till the next multiple
of 0x10000. This already explains why, in this particular example, there are 4
possible initial segment lengths. It does not yet explain why these lengths are
actually 0x200, 0x600, 0xa00, 0xe00 rather than 0x400, 0x800, 0xc00, 0x1000.
That is '(2n+1) * L'
rather than '2n * L'
.
To explain the '2n+1' stuff we need to take a more detailed look at the 0,1,0,1,0,1,0,1 stuff. I was writing a c++ model for this phenomena and then I realized that there are two possible (evenlyspaced) 4outof8 sequences:
 0,1,0,1,0,1,0,1 > gives 0x200 0x600 0xa00 0xe00  1,0,1,0,1,0,1,0 > gives 0x400 0x800 0xc00 0x1000
One of these gives 0x200,0x600,0xa00,0xe00, the other gives 0x400,0x800,0xc00, 0x10000. We observed the first segment width, so the first sequence must be the correct one. Looking back this behavior is very logical, but it took me some time to realize this.
For reference I've included the c++ code for the model, but I won't discuss it further:
So far all measurements/models were done for frequency 0x3ff, those use the 4outof8 '0,1,0,1,0,1,0,1' pattern. Higher frequencies use 5,6,7outof8 patterns.
Above I did a lot of (time consuming) measurements for various combinations of AR/DR/RR. But after I understood the underlying model I realized it's probably enough to limit myself to a single AR/DR combination. I choose AR=5/DR=2.
I measured lengths:
0x100 0x300 0x400 0x500 0x700 0x900 0xb00 0xc00 0xd00 0xf00
Side note: I relatively quickly obtained 1,3,4,5,7,9,c,d,f. But it literally
took over 50 attempts before I finally also observed length 0xb00, which I
suspected should be there. (The c++ model confirms that some lengths are also
more likely to be observed than others, and 0xb00 is a less likely length),
Possible 5outof8 patterns are:
1,1,0,1,0,1,0,1 0,1,1,1,0,1,0,1 0,1,0,1,1,1,0,1 < only this one matches the measurements 0,1,0,1,0,1,1,1 1,1,1,0,1,0,1,0 1,0,1,1,1,0,1,0 1,0,1,0,1,1,1,0 1,0,1,0,1,0,1,1
I measured these lengths:
0x100 0x200 0x300 0x500 0x600 0x700 0xa00 0xd00 0xe00
The model predicts 0x900 0xb00 0xf00 should also be present (with lower
probability), but I didn't keep on measuring until I actually observed those
values. The current measurements were already enough to exclude the other
possible patterns (see below).
Possible 6outof8 patterns are:
1,1,0,1,1,1,0,1 0,1,1,1,0,1,1,1 < this one matches the measurements 1,1,1,0,1,1,1,0 1,0,1,1,1,0,1,1
I measured these lengths:
0x100 0x200 0x300 0x400 0x500 0x600 0x700 0xb00 0xc00
The values 0x{9,a,d,e,f}00 should also be present (only 0x800 and 0x1000 are
missing). But these measurements can already exclude the other patterns below.
Possible 7outof8 patterns are:
0,1,1,1,1,1,1,1 < this one matches the measurements 1,1,0,1,1,1,1,1 1,1,1,1,0,1,1,1 1,1,1,1,1,1,0,1 1,0,1,1,1,1,1,1 1,1,1,0,1,1,1,1 1,1,1,1,1,0,1,1 1,1,1,1,1,1,1,0
We found that the model we had for the envelope steps already could explain the initial segment lengths after a AR>DR or DR>RR transition. And although I didn't measure this, I'm confident it will also correctly model the initial segment after a RR>DP or DP>AR transition.
We also found the exact 4,5,6,7outof8 sequences, namely
4outof8: 0,1,0,1,0,1,0,1 5outof8: 0,1,0,1,1,1,0,1 6outof8: 0,1,1,1,0,1,1,1 7outof8: 0,1,1,1,1,1,1,1
Side note: seq5 OR'ed with seq6 gives seq7 OR'ing seq4 with any other sequence simply give that other sequence This is not relevant for a software implementation, but it might give a clue to how the actual hardware circuit is structured (the 4 patterns are somewhat like a 2bit binary counter).
And if we look at the various emulator cores written by Jarek Burczynski (e.g. YM2413 but also YM2151 and YMF262), they contain exactly these sequences. So kudos to Jarek!
So actually, if I hadn't done any of the investigations in this post. The emulation would (probably) have been exactly the same. But still, it's nice to better understand why this weird initial segment length stuff is happening. And even better it's nice to know that the sequences used by Jarek Burczynski are really the same sequences as used by the YM2413.