Snake wrote: ok, so the bits I hadn't gotten to yet seem to be the bits that make the least sense. "Surprising" isn't the word. Given what the thing is supposed to do, you'd have to be on crack to implement it this way. I don't doubt that what you posted does indeed match the output of the real chip - it does seem to explain exactly what I'm seeing (all except one case which I won't be able to confirm without testing). But I do wonder if there is another way of getting the same results from some of this, just because - well - it's pretty insane I'd be interested to hear more about how you came to some of these conclusions and the tests you did to verify.
Notes on a lot of the important hardware test cases are included in the source I posted. There are a few points which could be implemented differently to get the same result, but not a lot. I simply can't think of another way to implement some of the weirdest stuff.
Consider the way output inversion is tracked for example. A change to the attack bit always causes an immediate inversion of the output, and believe me when I say I tested this under every conceivable circumstance. The YM2612 doesn't reset the inversion state to the attack state on the write, since if you had a wave where ATT=0 and ALT=1, and it had toggled the output inversion state once since the wave began (so the output was currently inverted), and you then changed the ATT bit to set, the wave output would immediately switch to a non-inverted state. With that ruled out, the observed behaviour says to me that the attack bit is being combined with the output inversion state each cycle through an XOR operation, and the internal flag tracks whether the output has been inverted from the starting value, rather than representing the true output inversion state by itself. A slightly unusual choice perhaps, but that's what the evidence indicates, and it's a fairly simple operation by itself.
That has a few flow-on effects however. It simplifies the inversion exclusion for hold mode, since we now only have to test the boolean flag to determine whether the output has been inverted yet (since hold mode only ever allows the ALT bit to toggle the inversion state once), however it now means we have to combine the ALT bit with the inversion bit in 3 places in order to calculate the true, effective inversion state.
There are more impacts as well. Now we have to consider release mode. Output inversion is always disabled during the release phase, regardless of what the current state of the output inversion bit was, and the state of the ATT bit, when the release phase was entered. Ok, so output inversion is disabled during release, but how is it disabled? Since the output inversion flag now represents whether the output inversion state has been toggled from the base value given by the ATT bit, it's not as simple as clearing the output inversion flag when entering the release phase. If you clear the output inversion flag and the ATT bit is set, you still get an inverted output. Do you then load the state of the ATT bit into the output inversion state when entering the release phase? Wrong, since toggling the ATT bit during the release phase doesn't cause output inversion to kick in. This means we now need an extra test where we check the output inversion state. If we're in the release phase, the effective output inversion state is forced to false, so our test for output inversion is now something like this:
(phase != release) && (ATT ^ inversionBit)
Some other interesting choices are having the SSG-EG state updated on the output cycle rather than the update cycle. Perhaps this made the SSG-EG mode easier to integrate with the existing design. All the voodoo with 0x200 makes sense from the point of view of least effort, since they needed some way to hook their SSG-EG update process with the existing envelope generator. Sacrificing one bit of resolution from the attenuation value and using the upper bit of the attenuation as a flag for the SSG-EG update steps in the output cycle was the easiest way to achieve it, and a 9-bit resolution for attenuation was enough for what they were emulating anyway.
Eke wrote:what happen in this case and more generally during the RELEASE phase for next samples update ? according to your algo, as attenuation level will remain higher than 0x200, this would mean the Invert flag could be swapped (even if output is not inverted during release) or the Phase counter could be reseted for each samples under some conditions...
There's no way to verify, at least that I can think of. Since the effective output inversion state is always forced to false when the release phase is active, there's no way to sample the current value of the invert flag. All we can say with certainty is that when key-on occurs, the note begins with the invert flag cleared. In my implementation, I only clear it during key-on, and simply allow it to be constantly toggled when the release phase reaches 0x200. Instead of this, you could clear it when key-off occurs, and add an exclusion to the SSG-EG update steps to prevent the invert flag being toggled during the release phase.
As for the phase counter restart, again there's no way to verify, since the internal attenuation level is always forced to 0x3FF when the release phase hits 0x200. The phase counter is theoretically restarted on the same sample as the attenuation is forced to max, but I have no way to sample the output from the phase generator when the attenuation is at max, and the only way to lower the attenuation from a release phase is to trigger a key-on event, which restarts the phase counter anyway.
Of course, seeing as there's no way to verify what the real chip does, it also doesn't matter, since there's no way any of this can have any effect on the output.
Eke wrote:isn't there a OFF state when attenuation reaches 0x3FF which turn the Enveloppe Generator off ? Or does the SSG-EG update process continue anyway ?
I'd say there isn't an "off" state, simply because there would be absolutely no point to include one. The decay phases include a limiter on the update step to clamp the maximum attenuation at 0x3FF. This prevents the decay phase from advancing once it reaches maximum attenuation anyway, and since the output during any "off" mode is just going to be 0x3FF, adding an "off" state wouldn't serve any purpose.
Eke wrote:according to your algo, as attenuation level will remain higher than 0x200, this would mean the Invert flag could be swapped
This is one of the parts I'm not yet convinced of. I can see where the thinking comes from because it solves a number of problems - then again, I've got a ton of samples that don't look like that is happening. It's possible that it is but I'd need to write some tests before I can say either way.
Do you mean in general, or during the release phase (the source of the quote)? During the release phase it doesn't matter. During the attack/decay/sustain phases, I'm quite confident the invert flag gets toggled each sample once the attenuation reaches 0x200. If you're looking at the recorded output of the chip, remember that high frequency waves (which is what a constantly toggling inversion bit gives you) get pulverized by the output circuitry.
Nemesis wrote:They didn't want the attack phase getting in the way of the SSG-EG emulation, so the mandate was to set the attack rate to 0x1F
Yes, but my point is that if they're already forcing the attenuation to zero when AR is above 62, why didn't they just force that all the time in SSG-EG mode? Or in plenty of other places where they could have done so? Leaving this up to the programmer in this way isn't something you'd usually expect to see.
In any case, the entire chip seems very well designed and logical - except for this bit, which is all a bit "WTF?
Yeah, that's true. Ok, we can agree then that SSG-EG was clearly an afterthought, done quickly, with the least effort possible, requiring the least changes to the existing core, and certainly using some questionable design approaches. In other words, it's dodgy, but they knew it was dodgy.