Posted: Thu May 07, 2009 8:43 pm
Old chips has requirements for minimal operational frequency and don't work at lower frequencies, because their internal registers are dynamic memory-based.
Sega Megadrive/Genesis development
http://gendev.spritesmind.net/forum/
Exactly.TmEE co.(TM) wrote:what HWM said is probably the cause, aliasing+reflections of the freqs that can't be handled by the ADC at given sample rate cause it.
Did you ever post the info on LFO / phase modulation ? (I am asking because I might have overlooked something)Nemesis wrote:The next info I post will be on the LFO. The current LFO implementation in MAME is mostly correct, but there is an error in the phase modulation implementation which I'll detail. I've also figured out the cause of the different sound in Battletech track 12, which was discussed awhile back in this thread. I'll post a detailed description on what causes the difference in sound, but it's actually a fairly complex explanation, so it'll be quite a long post. Apart from that, I've got some more testing to perform on the operator unit relating to the way algorithms are implemented, the DAC needs some attention, and I'm going to publish my own notes on the YM2612 test register, but after that, I'll be pretty much done with the YM2612.
Not yet. Sorry, I've been really short on free time lately, so I've had to dial back on this stuff for a while. If you've got a specific question, I should be able to answer it. In terms of writing up a full description of the LFO, I just don't think I've got the time right now.Did you ever post the info on LFO / phase modulation ? (I am asking because I might have overlooked something)
(Never mind all the "we" references. That means I'm in agreement with myself over what I'm writing. I write all my notes as if I have some kind of multiple personality disorder, that way I can disagree or debate with myself in note form if I'm not entirely convinced of something.)-We have confirmed that MAME implements phase modulation incorrectly. Phase modulation is applied to the 11-bit fnum value before it is combined with the block data, or any other operations are carried out using it. This is done using the same process implemented in the MAME core, but without the apparent extra bit of precision which was added, which is also what breaks the precision loss when block data is 0. The incorrect calculations most likely originated from confusion over the sign extension of the phase modulation value when it was in the negative oscillation, which would create an apparent extra bit of precision, even when all significant bits had been discarded. By repeatedly disabling the LFO before it entered into the negative oscillation phase, and carefully measuring and monitoring the output from the YM2612, we were able to observe that frequency modulation values do indeed cut off after the 11th bit, not the phantom 12th bit which MAME creates.
There is no extra bit of precision with frequency modulation. Frequency modulation is applied directly to the base fnum value, which is an 11-bit number, and this is the very first step in the phase generator, before block data is even applied to fnum. I conducted a whole battery of tests using observable overflow conditions and precision limitations during the phase generator update cycle to prove this is the case, and the current implementation in MAME (and, IIRC, in Kega as well?) is measurably incorrect./* there are 2048 FNUMs that can be generated using FNUM/BLK registers
but LFO works with one more bit of a precision so we really need 4096 elements */
(Never mind all the "we" references. That means I'm in agreement with myself over what I'm writing. I write all my notes as if I have some kind of multiple personality disorder, that way I can disagree or debate with myself in note form if I'm not entirely convinced of something.)
ok, so you mean that the value from "lfo_pm_table" (in the MAME core) should be added to the fnum value (11 bits), NOT to the block+fnum value (14 bits, identified by block_fnum in the MAME core) like it's done actually (well it's actuallty done on block_fnum << 1 because of this increased precision thing) ?Frequency modulation is applied directly to the base fnum value, which is an 11-bit number
The MAME core is pretty messy, and I've forgotten most of what I knew about exactly how MAME implemented the phase generator, so I'm hesitant to try and give specific advice on exactly how to modify the MAME core to fix is up. MAME crams the block and fnum data into a single variable, it uses a large lookup table to calculate frequency modulation, it tries to cache the final phase increment value and only recalculate it when it changes, and it has multiple update functions for the phase generator which are called at different times under different circumstances. All of these decisions are designed to try and get more speed out of the core, but they also mean making changes is a lot more painful than it should be.ok, so you mean that the value from "lfo_pm_table" (in the MAME core) should be added to the fnum value (11 bits), NOT to the block+fnum value (14 bits, identified by block_fnum in the MAME core) like it's done actually (well it's actuallty done on block_fnum << 1 because of this increased precision thing) ?
I mean, the "block" value is not affected by the modulation, is it ? I don't really understand that part of the MAME implementation, because fnum AND block value are recalculated after the addition of the phase modulation
Phase modulation can't cause a negative overflow like with detune, since the "amplitude" of the phase modulation is dependent on the initial fnum data itself, specifically, the upper 6 bits of fnum. If all upper six bits of fnum are 0, phase modulation won't adjust fnum at all. Due to the way phase modulation is applied to fnum, fnum will always be larger than the amount that would be subtracted from fnum during the peak of the negative oscillation of the phase modulation.Also in that case, I would think the result should be masked as a 11 bits value (by 0x7ff) ? but what if the resulted fnum becomes negative, does it have the sign bit extension or is it simply masked like with detune overflow ?
Not really that much, just that the phase increment value is only recalculated when needed (i.e some registers changed) and that the fnum value is shifted by the block value immediately when the block/fnum register is updated, again on optimization purpose.It's a lot more complicated than this in the MAME core, but this is how the phase generator is updated each cycle in the YM2612.
Code: Select all
UINT32 fnum_lfo = ((block_fnum & 0x7f0) >> 4) * 32 * 8;
INT32 lfo_fn_table_index_offset = lfo_pm_table[ fnum_lfo + pms + LFO_PM ];
if (lfo_fn_table_index_offset) /* LFO phase modulation active */
{
/* retrieve BLOCK register value */
UINT8 blk = (block_fnum >> 11) & 7;
/* apply phase modulation to FNUM register value */
UINT32 fn = (block_fnum + (UINT32)lfo_fn_table_index_offset) & 0x7ff;
/* recalculate keyscale code */
int kc = (blk<<2) | opn_fktable[fn >> 7];
/* recalculate (frequency) phase increment counter */
int fc = (ym2612.OPN.fn_table[fn]>>(7-blk)) + SLOT->DT[kc];
/* (frequency) phase overflow (max. 17 bits) */
if (fc < 0) fc += ym2612.OPN.fn_max;
/* update phase */
SLOT->phase += (fc * SLOT->mul) >> 1;
}
else /* LFO phase modulation = zero */
{
SLOT->phase += SLOT->Incr;
}
Code: Select all
static const UINT8 lfo_pm_output[7*8][8]={
/* 7 bits meaningful (of F-NUMBER), 8 LFO output levels per one depth (out of 32), 8 LFO depths */
/* FNUM BIT 4: 000 0001xxxx */
/* DEPTH 0 */ {0, 0, 0, 0, 0, 0, 0, 0},
/* DEPTH 1 */ {0, 0, 0, 0, 0, 0, 0, 0},
/* DEPTH 2 */ {0, 0, 0, 0, 0, 0, 0, 0},
/* DEPTH 3 */ {0, 0, 0, 0, 0, 0, 0, 0},
/* DEPTH 4 */ {0, 0, 0, 0, 0, 0, 0, 0},
/* DEPTH 5 */ {0, 0, 0, 0, 0, 0, 0, 0},
/* DEPTH 6 */ {0, 0, 0, 0, 0, 0, 0, 0},
/* DEPTH 7 */ {0, 0, 0, 0, 1, 1, 1, 1},
/* FNUM BIT 5: 000 0010xxxx */
/* DEPTH 0 */ {0, 0, 0, 0, 0, 0, 0, 0},
/* DEPTH 1 */ {0, 0, 0, 0, 0, 0, 0, 0},
/* DEPTH 2 */ {0, 0, 0, 0, 0, 0, 0, 0},
/* DEPTH 3 */ {0, 0, 0, 0, 0, 0, 0, 0},
/* DEPTH 4 */ {0, 0, 0, 0, 0, 0, 0, 0},
/* DEPTH 5 */ {0, 0, 0, 0, 0, 0, 0, 0},
/* DEPTH 6 */ {0, 0, 0, 0, 1, 1, 1, 1},
/* DEPTH 7 */ {0, 0, 1, 1, 2, 2, 2, 3},
/* FNUM BIT 6: 000 0100xxxx */
/* DEPTH 0 */ {0, 0, 0, 0, 0, 0, 0, 0},
/* DEPTH 1 */ {0, 0, 0, 0, 0, 0, 0, 0},
/* DEPTH 2 */ {0, 0, 0, 0, 0, 0, 0, 0},
/* DEPTH 3 */ {0, 0, 0, 0, 0, 0, 0, 0},
/* DEPTH 4 */ {0, 0, 0, 0, 0, 0, 0, 1},
/* DEPTH 5 */ {0, 0, 0, 0, 1, 1, 1, 1},
/* DEPTH 6 */ {0, 0, 1, 1, 2, 2, 2, 3},
/* DEPTH 7 */ {0, 0, 2, 3, 4, 4, 5, 6},
/* FNUM BIT 7: 000 1000xxxx */
/* DEPTH 0 */ {0, 0, 0, 0, 0, 0, 0, 0},
/* DEPTH 1 */ {0, 0, 0, 0, 0, 0, 0, 0},
/* DEPTH 2 */ {0, 0, 0, 0, 0, 0, 1, 1},
/* DEPTH 3 */ {0, 0, 0, 0, 1, 1, 1, 1},
/* DEPTH 4 */ {0, 0, 0, 1, 1, 1, 1, 2},
/* DEPTH 5 */ {0, 0, 1, 1, 2, 2, 2, 3},
/* DEPTH 6 */ {0, 0, 2, 3, 4, 4, 5, 6},
/* DEPTH 7 */ {0, 0, 4, 6, 8, 8, 0xa, 0xc},
/* FNUM BIT 8: 001 0000xxxx */
/* DEPTH 0 */ {0, 0, 0, 0, 0, 0, 0, 0},
/* DEPTH 1 */ {0, 0, 0, 0, 1, 1, 1, 1},
/* DEPTH 2 */ {0, 0, 0, 1, 1, 1, 2, 2},
/* DEPTH 3 */ {0, 0, 1, 1, 2, 2, 3, 3},
/* DEPTH 4 */ {0, 0, 1, 2, 2, 2, 3, 4},
/* DEPTH 5 */ {0, 0, 2, 3, 4, 4, 5, 6},
/* DEPTH 6 */ {0, 0, 4, 6, 8, 8, 0xa, 0xc},
/* DEPTH 7 */ {0, 0, 8, 0xc,0x10,0x10,0x14,0x18},
/* FNUM BIT 9: 010 0000xxxx */
/* DEPTH 0 */ {0, 0, 0, 0, 0, 0, 0, 0},
/* DEPTH 1 */ {0, 0, 0, 0, 2, 2, 2, 2},
/* DEPTH 2 */ {0, 0, 0, 2, 2, 2, 4, 4},
/* DEPTH 3 */ {0, 0, 2, 2, 4, 4, 6, 6},
/* DEPTH 4 */ {0, 0, 2, 4, 4, 4, 6, 8},
/* DEPTH 5 */ {0, 0, 4, 6, 8, 8, 0xa, 0xc},
/* DEPTH 6 */ {0, 0, 8, 0xc,0x10,0x10,0x14,0x18},
/* DEPTH 7 */ {0, 0,0x10,0x18,0x20,0x20,0x28,0x30},
/* FNUM BIT10: 100 0000xxxx */
/* DEPTH 0 */ {0, 0, 0, 0, 0, 0, 0, 0},
/* DEPTH 1 */ {0, 0, 0, 0, 4, 4, 4, 4},
/* DEPTH 2 */ {0, 0, 0, 4, 4, 4, 8, 8},
/* DEPTH 3 */ {0, 0, 4, 4, 8, 8, 0xc, 0xc},
/* DEPTH 4 */ {0, 0, 4, 8, 8, 8, 0xc,0x10},
/* DEPTH 5 */ {0, 0, 8, 0xc,0x10,0x10,0x14,0x18},
/* DEPTH 6 */ {0, 0,0x10,0x18,0x20,0x20,0x28,0x30},
/* DEPTH 7 */ {0, 0,0x20,0x30,0x40,0x40,0x50,0x60},
};