diff --git a/docs/rh-log.txt b/docs/rh-log.txt index 62e28cfb1..00c6404b4 100644 --- a/docs/rh-log.txt +++ b/docs/rh-log.txt @@ -1,4 +1,7 @@ January 24, 2009 +- Restored the rhythm section to fmopl.cpp and made some slight updates from + version 0.72 of MAME's fmopl.c. Also refactored CalcVoice so that the + original MAME structure is more visible. - Removed the SoundChans bitfield from AActor, since it seems there are race conditions I don't fully understand where it simply doesn't work. - Removed BaseTime initialization from sdl/i_system.cpp as per Chris's diff --git a/src/oplsynth/fmopl.cpp b/src/oplsynth/fmopl.cpp index cd3746f08..07139c523 100644 --- a/src/oplsynth/fmopl.cpp +++ b/src/oplsynth/fmopl.cpp @@ -1,20 +1,60 @@ +/* + +This file is based on fmopl.c from MAME 0.95. The non-YM3816 parts have been +ripped out in the interest of trying to make this a bit faster, since Doom +music doesn't need them. I also made it render the sound a voice at a time +instead of a sample at a time, so unused voices don't waste time being +calculated. + +Here is the appropriate section from mame.txt: + +VI. Reuse of Source Code +-------------------------- + This chapter might not apply to specific portions of MAME (e.g. CPU + emulators) which bear different copyright notices. + The source code cannot be used in a commercial product without the written + authorization of the authors. Use in non-commercial products is allowed, and + indeed encouraged. If you use portions of the MAME source code in your + program, however, you must make the full source code freely available as + well. + Usage of the _information_ contained in the source code is free for any use. + However, given the amount of time and energy it took to collect this + information, if you find new information we would appreciate if you made it + freely available as well. + +*/ + /* ** ** File: fmopl.c - software implementation of FM sound generator ** types OPL and OPL2 ** +** Copyright (C) 2002,2003 Jarek Burczynski (bujar at mame dot net) ** Copyright (C) 1999,2000 Tatsuyuki Satoh , MultiArcadeMachineEmulator development -** Copyright (C) 2002 Jarek Burczynski ** -** Version 0.60 +** Version 0.72 ** -[RH] The non-YM3816 and rhythm parts have been ripped out in the interest of trying -to make this a bit faster, since Doom music doesn't need them. And I also made it -render the sound a voice at a time instead of a sample at a time. - Revision History: +04-08-2003 Jarek Burczynski: + - removed BFRDY hack. BFRDY is busy flag, and it should be 0 only when the chip + handles memory read/write or during the adpcm synthesis when the chip + requests another byte of ADPCM data. + +24-07-2003 Jarek Burczynski: + - added a small hack for Y8950 status BFRDY flag (bit 3 should be set after + some (unknown) delay). Right now it's always set. + +14-06-2003 Jarek Burczynski: + - implemented all of the status register flags in Y8950 emulation + - renamed Y8950SetDeltaTMemory() parameters from _rom_ to _mem_ since + they can be either RAM or ROM + +08-10-2002 Jarek Burczynski (thanks to Dox for the YM3526 chip) + - corrected YM3526Read() to always set bit 2 and bit 1 + to HIGH state - identical to YM3812Read (verified on real YM3526) + 04-28-2002 Jarek Burczynski: - binary exact Envelope Generator (verified on real YM3812); compared to YM2151: the EG clock is equal to internal_clock, @@ -217,6 +257,8 @@ typedef struct fm_opl_f { UINT32 eg_timer_add; /* step of eg_timer */ UINT32 eg_timer_overflow; /* envelope generator timer overflows every 1 sample (on real chip) */ + UINT8 rhythm; /* Rhythm mode */ + UINT32 fn_tab[1024]; /* fnumber->increment counter */ /* LFO */ @@ -227,6 +269,10 @@ typedef struct fm_opl_f { UINT32 lfo_pm_cnt; UINT32 lfo_pm_inc; + UINT32 noise_rng; /* 23 bit noise shift register */ + UINT32 noise_p; /* current noise 'phase' */ + UINT32 noise_f; /* current noise peroid */ + UINT8 wavesel; /* waveform select enable flag */ int T[2]; /* timer counters */ @@ -352,7 +398,7 @@ static const unsigned char eg_inc[15*RATE_STEPS]={ /*note that there is no O(13) in this table - it's directly in the code */ static const unsigned char eg_rate_select[16+64+16]={ /* Envelope Generator rates (16 + 64 rates + 16 RKS) */ -/* 16 dummy (infinite time) rates */ +/* 16 infinite time rates */ O(14),O(14),O(14),O(14),O(14),O(14),O(14),O(14), O(14),O(14),O(14),O(14),O(14),O(14),O(14),O(14), @@ -387,9 +433,9 @@ O(12),O(12),O(12),O(12),O(12),O(12),O(12),O(12), }; #undef O -//rate 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 -//shift 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0 -//mask 4095, 2047, 1023, 511, 255, 127, 63, 31, 15, 7, 3, 1, 0, 0, 0, 0 +/*rate 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 */ +/*shift 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0 */ +/*mask 4095, 2047, 1023, 511, 255, 127, 63, 31, 15, 7, 3, 1, 0, 0, 0, 0 */ #define O(a) (a*1) static const unsigned char eg_rate_shift[16+64+16]={ /* Envelope Generator counter shifts (16 + 64 rates + 16 RKS) */ @@ -564,15 +610,14 @@ static const INT8 lfo_pm_table[8*8*2] = { static int num_lock = 0; /* work table */ -static void *cur_chip = NULL; /* current chip point */ - static signed int phase_modulation; /* phase modulation input (SLOT 2) */ -static signed int output[1]; +static signed int output; static UINT32 LFO_AM; static INT32 LFO_PM; static bool CalcVoice (FM_OPL *OPL, int voice, float *buffer, int length); +static bool CalcRhythm (FM_OPL *OPL, float *buffer, int length); @@ -640,13 +685,15 @@ INLINE void advance_lfo(FM_OPL *OPL) } /* advance to next sample */ -INLINE void advance(FM_OPL *OPL) +INLINE void advance(FM_OPL *OPL, int loch, int hich) { OPL_CH *CH; OPL_SLOT *op; int i; OPL->eg_timer += OPL->eg_timer_add; + loch *= 2; + hich *= 2; while (OPL->eg_timer >= OPL->eg_timer_overflow) { @@ -654,7 +701,7 @@ INLINE void advance(FM_OPL *OPL) OPL->eg_cnt++; - for (i=0; i<9*2; i++) + for (i = loch; i <= hich + 1; i++) { CH = &OPL->P_CH[i/2]; op = &CH->SLOT[i&1]; @@ -663,8 +710,6 @@ INLINE void advance(FM_OPL *OPL) switch(op->state) { case EG_ATT: /* attack phase */ - { - if ( !(OPL->eg_cnt & ((1<eg_sh_ar)-1) ) ) { op->volume += (~op->volume * @@ -678,8 +723,6 @@ INLINE void advance(FM_OPL *OPL) } } - - } break; case EG_DEC: /* decay phase */ @@ -734,45 +777,340 @@ INLINE void advance(FM_OPL *OPL) default: break; } - } - } - for (i=0; i<9*2; i++) - { - CH = &OPL->P_CH[i/2]; - op = &CH->SLOT[i&1]; - - /* Phase Generator */ - if(op->vib) - { - UINT8 block; - unsigned int block_fnum = CH->block_fnum; - - unsigned int fnum_lfo = (block_fnum&0x0380) >> 7; - - signed int lfo_fn_table_index_offset = lfo_pm_table[LFO_PM + 16*fnum_lfo ]; - - if (lfo_fn_table_index_offset) /* LFO phase modulation active */ + /* Phase Generator */ + if(op->vib) { - block_fnum += lfo_fn_table_index_offset; - block = (block_fnum&0x1c00) >> 10; - op->Cnt += (OPL->fn_tab[block_fnum&0x03ff] >> (7-block)) * op->mul;//ok + UINT8 block; + unsigned int block_fnum = CH->block_fnum; + + unsigned int fnum_lfo = (block_fnum&0x0380) >> 7; + + signed int lfo_fn_table_index_offset = lfo_pm_table[LFO_PM + 16*fnum_lfo ]; + + if (lfo_fn_table_index_offset) /* LFO phase modulation active */ + { + block_fnum += lfo_fn_table_index_offset; + block = (block_fnum&0x1c00) >> 10; + op->Cnt += (OPL->fn_tab[block_fnum&0x03ff] >> (7-block)) * op->mul; + } + else /* LFO phase modulation = zero */ + { + op->Cnt += op->Incr; + } } - else /* LFO phase modulation = zero */ + else /* LFO phase modulation disabled for this operator */ { op->Cnt += op->Incr; } } - else /* LFO phase modulation disabled for this operator */ - { - op->Cnt += op->Incr; - } + } +} + +INLINE void advance_noise(FM_OPL *OPL) +{ + int i; + + /* The Noise Generator of the YM3812 is 23-bit shift register. + * Period is equal to 2^23-2 samples. + * Register works at sampling frequency of the chip, so output + * can change on every sample. + * + * Output of the register and input to the bit 22 is: + * bit0 XOR bit14 XOR bit15 XOR bit22 + * + * Simply use bit 22 as the noise output. + */ + + OPL->noise_p += OPL->noise_f; + i = OPL->noise_p >> FREQ_SH; /* number of events (shifts of the shift register) */ + OPL->noise_p &= FREQ_MASK; + while (i) + { + /* + UINT32 j; + j = ( (OPL->noise_rng) ^ (OPL->noise_rng>>14) ^ (OPL->noise_rng>>15) ^ (OPL->noise_rng>>22) ) & 1; + OPL->noise_rng = (j<<22) | (OPL->noise_rng>>1); + */ + + /* + Instead of doing all the logic operations above, we + use a trick here (and use bit 0 as the noise output). + The difference is only that the noise bit changes one + step ahead. This doesn't matter since we don't know + what is real state of the noise_rng after the reset. + */ + + if (OPL->noise_rng & 1) OPL->noise_rng ^= 0x800302; + OPL->noise_rng >>= 1; + + i--; } } +INLINE signed int op_calc(UINT32 phase, unsigned int env, signed int pm, unsigned int wave_tab) +{ + UINT32 p; + + p = (env<<4) + sin_tab[wave_tab + ((((signed int)((phase & ~FREQ_MASK) + (pm<<16))) >> FREQ_SH ) & SIN_MASK) ]; + + if (p >= TL_TAB_LEN) + return 0; + return tl_tab[p]; +} + +INLINE signed int op_calc1(UINT32 phase, unsigned int env, signed int pm, unsigned int wave_tab) +{ + UINT32 p; + + p = (env<<4) + sin_tab[wave_tab + ((((signed int)((phase & ~FREQ_MASK) + pm )) >> FREQ_SH ) & SIN_MASK) ]; + + if (p >= TL_TAB_LEN) + return 0; + return tl_tab[p]; +} + + #define volume_calc(OP) ((OP)->TLL + ((UINT32)(OP)->volume) + (LFO_AM & (OP)->AMmask)) +/* calculate output */ +INLINE void OPL_CALC_CH( OPL_CH *CH, float *buffer ) +{ + OPL_SLOT *SLOT; + unsigned int env; + signed int out; + + phase_modulation = 0; + + /* SLOT 1 */ + SLOT = &CH->SLOT[SLOT1]; + env = volume_calc(SLOT); + out = SLOT->op1_out[0] + SLOT->op1_out[1]; + SLOT->op1_out[0] = SLOT->op1_out[1]; + *SLOT->connect1 += SLOT->op1_out[0]; + SLOT->op1_out[1] = 0; + if( env < ENV_QUIET ) + { + if (!SLOT->FB) + out = 0; + SLOT->op1_out[1] = op_calc1(SLOT->Cnt, env, (out<FB), SLOT->wavetable ); + } + + /* SLOT 2 */ + SLOT++; + env = volume_calc(SLOT); + if( env < ENV_QUIET ) + { + output += op_calc(SLOT->Cnt, env, phase_modulation, SLOT->wavetable); + /* [RH] Convert to floating point. */ + *buffer += float(output) / 10240; + } +} + +/* + operators used in the rhythm sounds generation process: + + Envelope Generator: + +channel operator register number Bass High Snare Tom Top +/ slot number TL ARDR SLRR Wave Drum Hat Drum Tom Cymbal + 6 / 0 12 50 70 90 f0 + + 6 / 1 15 53 73 93 f3 + + 7 / 0 13 51 71 91 f1 + + 7 / 1 16 54 74 94 f4 + + 8 / 0 14 52 72 92 f2 + + 8 / 1 17 55 75 95 f5 + + + Phase Generator: + +channel operator register number Bass High Snare Tom Top +/ slot number MULTIPLE Drum Hat Drum Tom Cymbal + 6 / 0 12 30 + + 6 / 1 15 33 + + 7 / 0 13 31 + + + + 7 / 1 16 34 ----- n o t u s e d ----- + 8 / 0 14 32 + + 8 / 1 17 35 + + + +channel operator register number Bass High Snare Tom Top +number number BLK/FNUM2 FNUM Drum Hat Drum Tom Cymbal + 6 12,15 B6 A6 + + + 7 13,16 B7 A7 + + + + + 8 14,17 B8 A8 + + + + +*/ + +/* calculate rhythm */ + +INLINE void OPL_CALC_RH( OPL_CH *CH, unsigned int noise ) +{ + OPL_SLOT *SLOT; + signed int out; + unsigned int env; + + + /* Bass Drum (verified on real YM3812): + - depends on the channel 6 'connect' register: + when connect = 0 it works the same as in normal (non-rhythm) mode (op1->op2->out) + when connect = 1 _only_ operator 2 is present on output (op2->out), operator 1 is ignored + - output sample always is multiplied by 2 + */ + + phase_modulation = 0; + /* SLOT 1 */ + SLOT = &CH[6].SLOT[SLOT1]; + env = volume_calc(SLOT); + + out = SLOT->op1_out[0] + SLOT->op1_out[1]; + SLOT->op1_out[0] = SLOT->op1_out[1]; + + if (!SLOT->CON) + phase_modulation = SLOT->op1_out[0]; + /* else ignore output of operator 1 */ + + SLOT->op1_out[1] = 0; + if( env < ENV_QUIET ) + { + if (!SLOT->FB) + out = 0; + SLOT->op1_out[1] = op_calc1(SLOT->Cnt, env, (out<FB), SLOT->wavetable ); + } + + /* SLOT 2 */ + SLOT++; + env = volume_calc(SLOT); + if( env < ENV_QUIET ) + output += op_calc(SLOT->Cnt, env, phase_modulation, SLOT->wavetable) * 2; + + + /* Phase generation is based on: */ + /* HH (13) channel 7->slot 1 combined with channel 8->slot 2 (same combination as TOP CYMBAL but different output phases) */ + /* SD (16) channel 7->slot 1 */ + /* TOM (14) channel 8->slot 1 */ + /* TOP (17) channel 7->slot 1 combined with channel 8->slot 2 (same combination as HIGH HAT but different output phases) */ + + /* Envelope generation based on: */ + /* HH channel 7->slot1 */ + /* SD channel 7->slot2 */ + /* TOM channel 8->slot1 */ + /* TOP channel 8->slot2 */ + + + /* The following formulas can be well optimized. + I leave them in direct form for now (in case I've missed something). + */ + + /* High Hat (verified on real YM3812) */ + env = volume_calc(&CH[7].SLOT[SLOT1]); + if( env < ENV_QUIET ) + { + + /* high hat phase generation: + phase = d0 or 234 (based on frequency only) + phase = 34 or 2d0 (based on noise) + */ + + /* base frequency derived from operator 1 in channel 7 */ + unsigned char bit7 = ((CH[7].SLOT[SLOT1].Cnt>>FREQ_SH)>>7)&1; + unsigned char bit3 = ((CH[7].SLOT[SLOT1].Cnt>>FREQ_SH)>>3)&1; + unsigned char bit2 = ((CH[7].SLOT[SLOT1].Cnt>>FREQ_SH)>>2)&1; + + unsigned char res1 = (bit2 ^ bit7) | bit3; + + /* when res1 = 0 phase = 0x000 | 0xd0; */ + /* when res1 = 1 phase = 0x200 | (0xd0>>2); */ + UINT32 phase = res1 ? (0x200|(0xd0>>2)) : 0xd0; + + /* enable gate based on frequency of operator 2 in channel 8 */ + unsigned char bit5e= ((CH[8].SLOT[SLOT2].Cnt>>FREQ_SH)>>5)&1; + unsigned char bit3e= ((CH[8].SLOT[SLOT2].Cnt>>FREQ_SH)>>3)&1; + + unsigned char res2 = (bit3e ^ bit5e); + + /* when res2 = 0 pass the phase from calculation above (res1); */ + /* when res2 = 1 phase = 0x200 | (0xd0>>2); */ + if (res2) + phase = (0x200|(0xd0>>2)); + + + /* when phase & 0x200 is set and noise=1 then phase = 0x200|0xd0 */ + /* when phase & 0x200 is set and noise=0 then phase = 0x200|(0xd0>>2), ie no change */ + if (phase&0x200) + { + if (noise) + phase = 0x200|0xd0; + } + else + /* when phase & 0x200 is clear and noise=1 then phase = 0xd0>>2 */ + /* when phase & 0x200 is clear and noise=0 then phase = 0xd0, ie no change */ + { + if (noise) + phase = 0xd0>>2; + } + + output += op_calc(phase<>FREQ_SH)>>8)&1; + + /* when bit8 = 0 phase = 0x100; */ + /* when bit8 = 1 phase = 0x200; */ + UINT32 phase = bit8 ? 0x200 : 0x100; + + /* Noise bit XOR'es phase by 0x100 */ + /* when noisebit = 0 pass the phase from calculation above */ + /* when noisebit = 1 phase ^= 0x100; */ + /* in other words: phase ^= (noisebit<<8); */ + if (noise) + phase ^= 0x100; + + output += op_calc(phase<>FREQ_SH)>>7)&1; + unsigned char bit3 = ((CH[7].SLOT[SLOT1].Cnt>>FREQ_SH)>>3)&1; + unsigned char bit2 = ((CH[7].SLOT[SLOT1].Cnt>>FREQ_SH)>>2)&1; + + unsigned char res1 = (bit2 ^ bit7) | bit3; + + /* when res1 = 0 phase = 0x000 | 0x100; */ + /* when res1 = 1 phase = 0x200 | 0x100; */ + UINT32 phase = res1 ? 0x300 : 0x100; + + /* enable gate based on frequency of operator 2 in channel 8 */ + unsigned char bit5e= ((CH[8].SLOT[SLOT2].Cnt>>FREQ_SH)>>5)&1; + unsigned char bit3e= ((CH[8].SLOT[SLOT2].Cnt>>FREQ_SH)>>3)&1; + + unsigned char res2 = (bit3e ^ bit5e); + /* when res2 = 0 pass the phase from calculation above (res1); */ + /* when res2 = 1 phase = 0x200 | 0x100; */ + if (res2) + phase = 0x300; + + output += op_calc(phase<freqbase = (OPL->rate) ? ((double)OPL->clock / 72.0) / OPL->rate : 0; -#else +#if 0 OPL->rate = (double)OPL->clock / 72.0; OPL->freqbase = 1.0; #endif @@ -1111,15 +1448,16 @@ static void OPLWriteReg(FM_OPL *OPL, int r, int v) case 0x04: /* IRQ clear / mask and Timer enable */ if(v&0x80) { /* IRQ flag clear */ - OPL_STATUS_RESET(OPL,0x7f); + OPL_STATUS_RESET(OPL,0x7f-0x08); /* don't reset BFRDY flag or we will have to call deltat module to set the flag */ } else { /* set IRQ mask ,timer enable*/ UINT8 st1 = v&1; UINT8 st2 = (v>>1)&1; + /* IRQRST,T1MSK,t2MSK,EOSMSK,BRMSK,x,ST2,ST1 */ - OPL_STATUS_RESET(OPL,v&0x78); - OPL_STATUSMASK_SET(OPL,((~v)&0x78)|0x01); + OPL_STATUS_RESET(OPL, v & (0x78-0x08) ); + OPL_STATUSMASK_SET(OPL, (~v) & 0x78 ); /* timer 2 */ if(OPL->st[1] != st2) { @@ -1136,6 +1474,9 @@ static void OPLWriteReg(FM_OPL *OPL, int r, int v) } } break; + case 0x08: /* MODE,DELTA-T control 2 : CSM,NOTESEL,x,x,smpl,da/ad,64k,rom */ + OPL->mode = v; + break; } break; case 0x20: /* am ON, vib ON, ksr, eg_type, mul */ @@ -1163,6 +1504,49 @@ static void OPLWriteReg(FM_OPL *OPL, int r, int v) { OPL->lfo_am_depth = v & 0x80; OPL->lfo_pm_depth_range = (v&0x40) ? 8 : 0; + + OPL->rhythm = v&0x3f; + + if(OPL->rhythm&0x20) + { + /* BD key on/off */ + if(v&0x10) + { + FM_KEYON (&OPL->P_CH[6].SLOT[SLOT1], 2); + FM_KEYON (&OPL->P_CH[6].SLOT[SLOT2], 2); + } + else + { + FM_KEYOFF(&OPL->P_CH[6].SLOT[SLOT1],~2); + FM_KEYOFF(&OPL->P_CH[6].SLOT[SLOT2],~2); + } + /* HH key on/off */ + if(v&0x01) FM_KEYON (&OPL->P_CH[7].SLOT[SLOT1], 2); + else FM_KEYOFF(&OPL->P_CH[7].SLOT[SLOT1],~2); + /* SD key on/off */ + if(v&0x08) FM_KEYON (&OPL->P_CH[7].SLOT[SLOT2], 2); + else FM_KEYOFF(&OPL->P_CH[7].SLOT[SLOT2],~2); + /* TOM key on/off */ + if(v&0x04) FM_KEYON (&OPL->P_CH[8].SLOT[SLOT1], 2); + else FM_KEYOFF(&OPL->P_CH[8].SLOT[SLOT1],~2); + /* TOP-CY key on/off */ + if(v&0x02) FM_KEYON (&OPL->P_CH[8].SLOT[SLOT2], 2); + else FM_KEYOFF(&OPL->P_CH[8].SLOT[SLOT2],~2); + } + else + { + /* BD key off */ + FM_KEYOFF(&OPL->P_CH[6].SLOT[SLOT1],~2); + FM_KEYOFF(&OPL->P_CH[6].SLOT[SLOT2],~2); + /* HH key off */ + FM_KEYOFF(&OPL->P_CH[7].SLOT[SLOT1],~2); + /* SD key off */ + FM_KEYOFF(&OPL->P_CH[7].SLOT[SLOT2],~2); + /* TOM key off */ + FM_KEYOFF(&OPL->P_CH[8].SLOT[SLOT1],~2); + /* TOP-CY off */ + FM_KEYOFF(&OPL->P_CH[8].SLOT[SLOT2],~2); + } return; } /* keyon,block,fnum */ @@ -1223,7 +1607,7 @@ static void OPLWriteReg(FM_OPL *OPL, int r, int v) CH = &OPL->P_CH[r&0x0f]; CH->SLOT[SLOT1].FB = (v>>1)&7 ? ((v>>1)&7) + 7 : 0; CH->SLOT[SLOT1].CON = v&1; - CH->SLOT[SLOT1].connect1 = CH->SLOT[SLOT1].CON ? &output[0] : &phase_modulation; + CH->SLOT[SLOT1].connect1 = CH->SLOT[SLOT1].CON ? &output : &phase_modulation; break; case 0xe0: /* waveform select */ /* simply ignore write to the waveform select register if selecting not enabled in test register */ @@ -1257,7 +1641,6 @@ static int OPL_LockTable(void) /* first time */ - cur_chip = NULL; /* allocate total level table (128kb space) */ if( !init_tables() ) { @@ -1283,7 +1666,6 @@ static void OPL_UnLockTable(void) /* last time */ - cur_chip = NULL; OPLCloseTable(); #ifdef LOG_CYM_FILE @@ -1301,7 +1683,8 @@ static void OPLResetChip(FM_OPL *OPL) OPL->eg_timer = 0; OPL->eg_cnt = 0; - OPL->mode = 0; /* normal mode */ + OPL->noise_rng = 1; /* noise shift register */ + OPL->mode = 0; /* normal mode */ OPL_STATUS_RESET(OPL,0x7f); /* reset with register write */ @@ -1359,8 +1742,6 @@ static FM_OPL *OPLCreate(int type, int clock, int rate) /* init global tables */ OPL_initalize(OPL); - /* reset chip */ - OPLResetChip(OPL); return OPL; } @@ -1371,7 +1752,7 @@ static void OPLDestroy(FM_OPL *OPL) free(OPL); } -/* Option handlers */ +/* Optional handlers */ static void OPLSetTimerHandler(FM_OPL *OPL,OPL_TIMERHANDLER TimerHandler,int channelOffset) { @@ -1409,6 +1790,7 @@ static unsigned char OPLRead(FM_OPL *OPL,int a) if( !(a&1) ) { /* status port */ + /* OPL and OPL2 */ return OPL->status & (OPL->statusmask|0x80); } @@ -1458,73 +1840,60 @@ static int OPLTimerOver(FM_OPL *OPL,int c) static FM_OPL *OPL_YM3812[MAX_OPL_CHIPS]; /* array of pointers to the YM3812's */ static int YM3812NumChips = 0; /* number of chips */ -int YM3812Init(int num, int clock, int rate) +void *YM3812Init(int clock, int rate) { - int i; - - if (YM3812NumChips) - return -1; /* duplicate init. */ - - YM3812NumChips = num; - - for (i = 0;i < YM3812NumChips; i++) - { - /* emulator create */ - OPL_YM3812[i] = OPLCreate(OPL_TYPE_YM3812,clock,rate); - if(OPL_YM3812[i] == NULL) - { - /* it's really bad - we run out of memeory */ - YM3812NumChips = 0; - return -1; - } - } - - return 0; + /* emulator create */ + FM_OPL *YM3812 = OPLCreate(OPL_TYPE_YM3812,clock,rate); + if (YM3812) + YM3812ResetChip(YM3812); + return YM3812; } -void YM3812Shutdown(void) +void YM3812Shutdown(void *chip) { - int i; + FM_OPL *YM3812 = (FM_OPL *)chip; - for (i = 0;i < YM3812NumChips; i++) - { - /* emulator shutdown */ - OPLDestroy(OPL_YM3812[i]); - OPL_YM3812[i] = NULL; - } - YM3812NumChips = 0; + /* emulator shutdown */ + OPLDestroy(YM3812); } -void YM3812ResetChip(int which) +void YM3812ResetChip(void *chip) { - OPLResetChip(OPL_YM3812[which]); + FM_OPL *YM3812 = (FM_OPL *)chip; + OPLResetChip(YM3812); } -int YM3812Write(int which, int a, int v) +int YM3812Write(void *chip, int a, int v) { - return OPLWrite(OPL_YM3812[which], a, v); + FM_OPL *YM3812 = (FM_OPL *)chip; + return OPLWrite(YM3812, a, v); } -unsigned char YM3812Read(int which, int a) +unsigned char YM3812Read(void *chip, int a) { + FM_OPL *YM3812 = (FM_OPL *)chip; /* YM3812 always returns bit2 and bit1 in HIGH state */ - return OPLRead(OPL_YM3812[which], a) | 0x06 ; + return OPLRead(YM3812, a) | 0x06 ; } -int YM3812TimerOver(int which, int c) +int YM3812TimerOver(void *chip, int c) { - return OPLTimerOver(OPL_YM3812[which], c); + FM_OPL *YM3812 = (FM_OPL *)chip; + return OPLTimerOver(YM3812, c); } -void YM3812SetTimerHandler(int which, OPL_TIMERHANDLER TimerHandler, int channelOffset) +void YM3812SetTimerHandler(void *chip, OPL_TIMERHANDLER TimerHandler, int channelOffset) { - OPLSetTimerHandler(OPL_YM3812[which], TimerHandler, channelOffset); + FM_OPL *YM3812 = (FM_OPL *)chip; + OPLSetTimerHandler(YM3812, TimerHandler, channelOffset); } -void YM3812SetIRQHandler(int which,OPL_IRQHANDLER IRQHandler,int param) +void YM3812SetIRQHandler(void *chip,OPL_IRQHANDLER IRQHandler,int param) { - OPLSetIRQHandler(OPL_YM3812[which], IRQHandler, param); + FM_OPL *YM3812 = (FM_OPL *)chip; + OPLSetIRQHandler(YM3812, IRQHandler, param); } -void YM3812SetUpdateHandler(int which,OPL_UPDATEHANDLER UpdateHandler,int param) +void YM3812SetUpdateHandler(void *chip,OPL_UPDATEHANDLER UpdateHandler,int param) { - OPLSetUpdateHandler(OPL_YM3812[which], UpdateHandler, param); + FM_OPL *YM3812 = (FM_OPL *)chip; + OPLSetUpdateHandler(YM3812, UpdateHandler, param); } @@ -1535,39 +1904,58 @@ void YM3812SetUpdateHandler(int which,OPL_UPDATEHANDLER UpdateHandler,int param) ** '*buffer' is the output buffer pointer ** 'length' is the number of samples that should be generated */ -void YM3812UpdateOne(int which, float *buffer, int length) +void YM3812UpdateOne(void *chip, float *buffer, int length) { - FM_OPL *OPL = OPL_YM3812[which]; + FM_OPL *OPL = (FM_OPL *)chip; + UINT8 rhythm = OPL->rhythm&0x20; int i; - if( (void *)OPL != cur_chip ) + if (OPL == NULL) { - cur_chip = (void *)OPL; + return; } + UINT32 lfo_am_cnt_bak = OPL->lfo_am_cnt; UINT32 eg_timer_bak = OPL->eg_timer; UINT32 eg_cnt_bak = OPL->eg_cnt; + UINT32 noise_p_bak = OPL->noise_p; + UINT32 noise_rng_bak = OPL->noise_rng; + UINT32 lfo_am_cnt_out = lfo_am_cnt_bak; UINT32 eg_timer_out = eg_timer_bak; UINT32 eg_cnt_out = eg_cnt_bak; - for (i = 0; i <= 8; ++i) + UINT32 noise_p_out = noise_p_bak; + UINT32 noise_rng_out = noise_rng_bak; + + for (i = 0; i <= (rhythm ? 5 : 8); ++i) { OPL->lfo_am_cnt = lfo_am_cnt_bak; OPL->eg_timer = eg_timer_bak; OPL->eg_cnt = eg_cnt_bak; + OPL->noise_p = noise_p_bak; + OPL->noise_rng = noise_rng_bak; if (CalcVoice (OPL, i, buffer, length)) { lfo_am_cnt_out = OPL->lfo_am_cnt; eg_timer_out = OPL->eg_timer; eg_cnt_out = OPL->eg_cnt; + noise_p_out = OPL->noise_p; + noise_rng_out = OPL->noise_rng; } } OPL->lfo_am_cnt = lfo_am_cnt_out; OPL->eg_timer = eg_timer_out; OPL->eg_cnt = eg_cnt_out; + OPL->noise_p = noise_p_out; + OPL->noise_rng = noise_rng_out; + + if (rhythm) /* Rhythm part */ + { + CalcRhythm (OPL, buffer, length); + } } // [RH] Render a whole voice at once. If nothing else, it lets us avoid @@ -1576,7 +1964,7 @@ void YM3812UpdateOne(int which, float *buffer, int length) static bool CalcVoice (FM_OPL *OPL, int voice, float *buffer, int length) { OPL_CH *const CH = &OPL->P_CH[voice]; - int i, j; + int i; if (CH->SLOT[0].state == EG_OFF && CH->SLOT[1].state == EG_OFF) { // Voice is not playing, so don't do anything for it @@ -1585,185 +1973,39 @@ static bool CalcVoice (FM_OPL *OPL, int voice, float *buffer, int length) for (i = 0; i < length; ++i) { - // advance_lfo - UINT8 tmp; + advance_lfo(OPL); - /* LFO */ - OPL->lfo_am_cnt += OPL->lfo_am_inc; - if (OPL->lfo_am_cnt >= (UINT32)(LFO_AM_TAB_ELEMENTS<lfo_am_cnt -= (LFO_AM_TAB_ELEMENTS<lfo_am_cnt >> LFO_SH ]; - - if (OPL->lfo_am_depth) - LFO_AM = tmp; - else - LFO_AM = tmp>>2; - - OPL->lfo_pm_cnt += OPL->lfo_pm_inc; - LFO_PM = ((OPL->lfo_pm_cnt>>LFO_SH) & 7) | OPL->lfo_pm_depth_range; - - // OPL_CALC_CH - OPL_SLOT *SLOT; - unsigned int env; - signed int out; - UINT32 p; - - phase_modulation = 0; - output[0] = 0; - - /* SLOT 1 */ - SLOT = &CH->SLOT[SLOT1]; - env = volume_calc(SLOT); - out = SLOT->op1_out[0] + SLOT->op1_out[1]; - SLOT->op1_out[0] = SLOT->op1_out[1]; - *SLOT->connect1 += SLOT->op1_out[0]; - SLOT->op1_out[1] = 0; - if( env < ENV_QUIET ) - { - if (!SLOT->FB) - out = 0; - p = (env<<4) + sin_tab[SLOT->wavetable + ((((SLOT->Cnt & ~FREQ_MASK) + (out<FB))>>FREQ_SH) & SIN_MASK)]; - SLOT->op1_out[1] = p >= TL_TAB_LEN ? 0 : tl_tab[p]; - } - - /* SLOT 2 */ - SLOT++; - env = volume_calc(SLOT); - if( env < ENV_QUIET ) - { - p = (env<<4) + sin_tab[SLOT->wavetable + ((((signed int)((SLOT->Cnt & ~FREQ_MASK) + (phase_modulation<<16))) >> FREQ_SH ) & SIN_MASK) ]; - if (p < TL_TAB_LEN) - { - output[0] += tl_tab[p]; - } - // [RH] Convert to floating point. - buffer[i] += float(output[0]) / 10240; - } - - // advance - OPL->eg_timer += OPL->eg_timer_add; - - while (OPL->eg_timer >= OPL->eg_timer_overflow) - { - OPL->eg_timer -= OPL->eg_timer_overflow; - - OPL->eg_cnt++; - - for (j = 0; j < 2; ++j) - { - OPL_SLOT *op = &CH->SLOT[j]; - - /* Envelope Generator */ - switch(op->state) - { - case EG_ATT: /* attack phase */ - { - - if ( !(OPL->eg_cnt & ((1<eg_sh_ar)-1) ) ) - { - op->volume += (~op->volume * - (eg_inc[op->eg_sel_ar + ((OPL->eg_cnt>>op->eg_sh_ar)&7)]) - ) >>3; - - if (op->volume <= MIN_ATT_INDEX) - { - op->volume = MIN_ATT_INDEX; - op->state = EG_DEC; - } - - } - - } - break; - - case EG_DEC: /* decay phase */ - if ( !(OPL->eg_cnt & ((1<eg_sh_dr)-1) ) ) - { - op->volume += eg_inc[op->eg_sel_dr + ((OPL->eg_cnt>>op->eg_sh_dr)&7)]; - - if ( op->volume >= (INT32)op->sl ) - op->state = EG_SUS; - - } - break; - - case EG_SUS: /* sustain phase */ - - /* this is important behaviour: - one can change percusive/non-percussive modes on the fly and - the chip will remain in sustain phase - verified on real YM3812 */ - - if(op->eg_type) /* non-percussive mode */ - { - /* do nothing */ - } - else /* percussive mode */ - { - /* during sustain phase chip adds Release Rate (in percussive mode) */ - if ( !(OPL->eg_cnt & ((1<eg_sh_rr)-1) ) ) - { - op->volume += eg_inc[op->eg_sel_rr + ((OPL->eg_cnt>>op->eg_sh_rr)&7)]; - - if ( op->volume >= MAX_ATT_INDEX ) - op->volume = MAX_ATT_INDEX; - } - /* else do nothing in sustain phase */ - } - break; - - case EG_REL: /* release phase */ - if ( !(OPL->eg_cnt & ((1<eg_sh_rr)-1) ) ) - { - op->volume += eg_inc[op->eg_sel_rr + ((OPL->eg_cnt>>op->eg_sh_rr)&7)]; - - if ( op->volume >= MAX_ATT_INDEX ) - { - op->volume = MAX_ATT_INDEX; - op->state = EG_OFF; - } - - } - break; - - default: - break; - } - - /* Phase Generator */ - if(op->vib) - { - UINT8 block; - unsigned int block_fnum = CH->block_fnum; - - unsigned int fnum_lfo = (block_fnum&0x0380) >> 7; - - signed int lfo_fn_table_index_offset = lfo_pm_table[LFO_PM + 16*fnum_lfo ]; - - if (lfo_fn_table_index_offset) /* LFO phase modulation active */ - { - block_fnum += lfo_fn_table_index_offset; - block = (block_fnum&0x1c00) >> 10; - op->Cnt += (OPL->fn_tab[block_fnum&0x03ff] >> (7-block)) * op->mul;//ok - } - else /* LFO phase modulation = zero */ - { - op->Cnt += op->Incr; - } - } - else /* LFO phase modulation disabled for this operator */ - { - op->Cnt += op->Incr; - } - } - } + advance(OPL, voice, voice); + advance_noise(OPL); } return true; } -FString YM3812GetVoiceString() +static bool CalcRhythm (FM_OPL *OPL, float *buffer, int length) { - FM_OPL *OPL = OPL_YM3812[0]; + int i; + + for (i = 0; i < length; ++i) + { + advance_lfo(OPL); + + output = 0; + OPL_CALC_RH(&OPL->P_CH[0], OPL->noise_rng & 1); + /* [RH] Convert to floating point. */ + buffer[i] += float(output) / 10240; + + advance(OPL, 6, 8); + advance_noise(OPL); + } + return true; +} + +FString YM3812GetVoiceString(void *chip) +{ + FM_OPL *OPL = (FM_OPL *)chip; char out[9*3]; for (int i = 0; i <= 8; ++i) diff --git a/src/oplsynth/fmopl.h b/src/oplsynth/fmopl.h index b79042df1..2b3eefb6e 100644 --- a/src/oplsynth/fmopl.h +++ b/src/oplsynth/fmopl.h @@ -25,18 +25,18 @@ typedef void (*OPL_PORTHANDLER_W)(int param,unsigned char data); typedef unsigned char (*OPL_PORTHANDLER_R)(int param); -int YM3812Init(int num, int clock, int rate); -void YM3812Shutdown(void); -void YM3812ResetChip(int which); -int YM3812Write(int which, int a, int v); -unsigned char YM3812Read(int which, int a); -int YM3812TimerOver(int which, int c); -void YM3812UpdateOne(int which, float *buffer, int length); +void *YM3812Init(int clock, int rate); +void YM3812Shutdown(void *chip); +void YM3812ResetChip(void *chip); +int YM3812Write(void *chip, int a, int v); +unsigned char YM3812Read(void *chip, int a); +int YM3812TimerOver(void *chip, int c); +void YM3812UpdateOne(void *chip, float *buffer, int length); -void YM3812SetTimerHandler(int which, OPL_TIMERHANDLER TimerHandler, int channelOffset); -void YM3812SetIRQHandler(int which, OPL_IRQHANDLER IRQHandler, int param); -void YM3812SetUpdateHandler(int which, OPL_UPDATEHANDLER UpdateHandler, int param); +void YM3812SetTimerHandler(void *chip, OPL_TIMERHANDLER TimerHandler, int channelOffset); +void YM3812SetIRQHandler(void *chip, OPL_IRQHANDLER IRQHandler, int param); +void YM3812SetUpdateHandler(void *chip, OPL_UPDATEHANDLER UpdateHandler, int param); -FString YM3812GetVoiceString(); +FString YM3812GetVoiceString(void *chip); #endif diff --git a/src/oplsynth/mlopl_io.cpp b/src/oplsynth/mlopl_io.cpp index 4f197a691..23dbf8e6b 100644 --- a/src/oplsynth/mlopl_io.cpp +++ b/src/oplsynth/mlopl_io.cpp @@ -58,8 +58,8 @@ void OPLio::WriteDelay(int ticks) void OPLio::OPLwriteReg(int which, uint reg, uchar data) { - YM3812Write (which, 0, reg); - YM3812Write (which, 1, data); + YM3812Write (chips[which], 0, reg); + YM3812Write (chips[which], 1, data); } /* @@ -300,16 +300,28 @@ void OPLio::OPLshutup(void) */ int OPLio::OPLinit(uint numchips) { - if (!YM3812Init (numchips, 3579545, int(OPL_SAMPLE_RATE))) + assert(numchips >= 1 && numchips <= 2); + chips[0] = YM3812Init (3579545, int(OPL_SAMPLE_RATE)); + if (chips[0] != NULL) { - OPLchannels = OPL2CHANNELS * numchips; - OPLwriteInitState(); - return 0; + if (numchips > 1) + { + chips[1] = YM3812Init (3579545, int(OPL_SAMPLE_RATE)); + if (chips[1] == NULL) + { + YM3812Shutdown(chips[0]); + chips[0] = NULL; + return -1; + } + } } else { return -1; } + OPLchannels = OPL2CHANNELS * numchips; + OPLwriteInitState(); + return 0; } void OPLio::OPLwriteInitState() @@ -328,5 +340,8 @@ void OPLio::OPLwriteInitState() */ void OPLio::OPLdeinit(void) { - YM3812Shutdown (); + YM3812Shutdown (chips[0]); + chips[0] = NULL; + YM3812Shutdown (chips[1]); + chips[1] = NULL; } diff --git a/src/oplsynth/muslib.h b/src/oplsynth/muslib.h index c3166ae37..7cc533ea5 100644 --- a/src/oplsynth/muslib.h +++ b/src/oplsynth/muslib.h @@ -184,6 +184,7 @@ struct OPLio { virtual void WriteDelay(int ticks); uint OPLchannels; + void *chips[2]; }; struct DiskWriterIO : public OPLio diff --git a/src/oplsynth/opl_mus_player.cpp b/src/oplsynth/opl_mus_player.cpp index 0139d2ff9..74063cd90 100644 --- a/src/oplsynth/opl_mus_player.cpp +++ b/src/oplsynth/opl_mus_player.cpp @@ -207,11 +207,8 @@ bool OPLmusicBlock::ServiceStream (void *buff, int numbytes) if (samplesleft > 0) { - YM3812UpdateOne (0, samples1, samplesleft); - if (TwoChips) - { - YM3812UpdateOne (1, samples1, samplesleft); - } + YM3812UpdateOne (io->chips[0], samples1, samplesleft); + YM3812UpdateOne (io->chips[1], samples1, samplesleft); OffsetSamples(samples1, samplesleft); assert(NextTickIn == ticky); NextTickIn -= samplesleft; @@ -230,11 +227,8 @@ bool OPLmusicBlock::ServiceStream (void *buff, int numbytes) { if (numsamples > 0) { - YM3812UpdateOne (0, samples1, numsamples); - if (TwoChips) - { - YM3812UpdateOne (1, samples1, numsamples); - } + YM3812UpdateOne (io->chips[0], samples1, numsamples); + YM3812UpdateOne (io->chips[1], samples1, numsamples); OffsetSamples(samples1, numsamples); } res = false; @@ -448,10 +442,12 @@ int OPLmusicFile::PlayTick () return 0; } +/* ADD_STAT (opl) { return YM3812GetVoiceString (); } +*/ OPLmusicFile::OPLmusicFile(const OPLmusicFile *source, const char *filename) {