/* s_opl.c -- YM2413/Y8950/YM3526/YM3812 emulator by Mamiya, 2001. References: fmopl.c -- 1999,2000 written by Tatsuyuki Satoh (MAME development). fmopl.c(fixed) -- 2000 modified by mamiya (NEZplug development). fmgen.cpp -- 1999-2001 written by cisc. emu2413.c -- a YM2413 emulator : written by Mitsutaka Okazaki 2001 fmpac.ill -- 2000 created by sama. fmpac.ill -- 2000 created by NARUTO. fmpac.ill -- 2001 created by Okazaki. YM2413 application manual */ #include "kmsnddev.h" #include "divfix.h" #include "s_logtbl.h" #include "s_opltbl.h" #include "s_opl.h" #include "s_deltat.h" #include #define PG_SHIFT 10 /* fix */ #define CPS_SHIFTE 20 #define CPS_SHIFTP 14 #define LFO_SHIFT 16 #define OPLL_INST_WORK 0x40 #define OPLL_INST_WORK2 (OPLL_INST_WORK + 8 * 0x13) #define AR_BITS 6 /* fix */ #define AR_SHIFT 14 /* fix */ #define EG_SHIFT 15 /* fix */ #define AR_PHASEMAX (((1 << AR_BITS) - 1) << AR_SHIFT) #define EG_PHASEMAX (127 << EG_SHIFT) #define EG_KEYOFF (128 << EG_SHIFT) #define LOG_KEYOFF (31 << (LOG_BITS + 1)) #if 0 0-48dB AR_BITS=6 AR_SHIFT=14 x = FC000h(20.7618(sec) * 3579545 / 72)cycles 63 * 4000h x / 3 * 4 1730.15(ms) x / 3 * 256 27.03(ms) EG_BITS=7 EG_SHIFT=15 x = 3F8000h(83.7064(sec) * 3579545 / 72)cycles 127 * 8000h x / 4 = 20926.6(ms) #endif #define TESTING_OPTIMIZE_AME 1 #define USE_FBBUF 1 typedef struct { Uint32 phase; Uint32 spd; Uint32 rng; } OPL_PG; enum { EG_MODE_ATTACK, EG_MODE_DECAY, EG_MODE_SUSTINE, EG_MODE_RELEASE, EG_MODE_NUM, EG_MODE_SUSHOLD, EG_MODE_OFF = EG_MODE_NUM }; typedef struct { Uint32 phasear; Uint32 phase; Uint32 spd [EG_MODE_NUM]; Uint32 dr_phasemax; Uint8 mode; Uint8 pad4_1; Uint8 pad4_2; Uint8 pad4_3; } OPL_EG; enum { FLAG_AME = (1 << 0), FLAG_PME = (1 << 1), FLAG_EGT = (1 << 2), FLAG_KSR = (1 << 3) }; typedef struct { OPL_PG pg; OPL_EG eg; Int32 input; #if USE_FBBUF Int32 fbbuf; #endif #if TESTING_OPTIMIZE_AME Uint32* amin; #endif Uint32 tl_ofs; Uint32* sintable; Uint8 modcar; /* 1:m 0:c */ Uint8 fb; Uint8 lvl; Uint8 nst; Uint8 tll; Uint8 key; Uint8 rkey; Uint8 prevkey; Uint8 enable; Uint8 __enablehold__; Uint8 flag; Uint8 ksr; Uint8 mul; Uint8 ksl; Uint8 ar; Uint8 dr; Uint8 sl; Uint8 rr; Uint8 tl; Uint8 wf; } OPL_OP; typedef struct { OPL_OP op [2]; Uint8 con; Uint8 freql; Uint8 freqh; Uint8 blk; Uint8 kcode; Uint8 sus; Uint8 ksb; Uint8 pad4_3; } OPL_CH; enum { LFO_UNIT_AM, LFO_UNIT_PM, LFO_UNIT_NUM }; typedef struct { Uint32 output; Uint32 cnt; Uint32 sps; /* step per sample */ Uint32 adr; Uint32 adrmask; Uint32* table; } OPL_LFO; typedef struct { KMIF_SOUND_DEVICE kmif; KMIF_SOUND_DEVICE* deltatpcm; KMIF_LOGTABLE* logtbl; KMIF_OPLTABLE* opltbl; OPL_CH ch [9]; OPL_LFO lfo [LFO_UNIT_NUM]; struct OPLSOUND_COMMON_TAG { Uint32 cpsp; Uint32 cnt; Uint32* ar_table; Uint32* tll2logtbl; #if TESTING_OPTIMIZE_AME Uint32 amzero; #endif Int32 mastervolume; Uint32 sintablemask; Uint32 ratetbla [4]; Uint32 ratetbl [4]; Uint8 adr; Uint8 wfe; Uint8 rc; Uint8 rmode; Uint8 enable; } common; Uint8 regs [0x100]; Uint8 opl_type; } OPLSOUND; static Uint8 romtone [3] [16 * 19] = { { #include "i_fmpac.h" }, { #include "i_fmunit.h" }, { #include "i_vrc7.h" }, }; static void SetOpOff(OPL_OP* opp ) { opp->eg.mode = EG_MODE_OFF; opp->eg.phase = EG_KEYOFF; opp->enable = 0; } inline static void EgStep( OPLSOUND* sndp, OPL_OP* opp ) { switch ( opp->eg.mode ) { default: NEVER_REACH case EG_MODE_ATTACK: opp->eg.phase = sndp->common.ar_table [opp->eg.phasear >> (AR_SHIFT + AR_BITS - ARTBL_BITS)] >> (ARTBL_SHIFT - EG_SHIFT); opp->eg.phasear += opp->eg.spd [EG_MODE_ATTACK]; if ( opp->eg.phasear >= AR_PHASEMAX ) { opp->eg.mode = EG_MODE_DECAY; opp->eg.phase = 0; } break; case EG_MODE_DECAY: opp->eg.phase += opp->eg.spd [EG_MODE_DECAY]; if ( opp->eg.phase >= opp->eg.dr_phasemax ) { opp->eg.phase = opp->eg.dr_phasemax; opp->eg.mode = (opp->flag & FLAG_EGT) ? EG_MODE_SUSHOLD : EG_MODE_SUSTINE; } break; case EG_MODE_SUSTINE: case EG_MODE_RELEASE: opp->eg.phase += opp->eg.spd [opp->eg.mode]; if ( opp->eg.phase >= EG_PHASEMAX ) SetOpOff(opp); break; case EG_MODE_SUSHOLD: case EG_MODE_OFF: break; } } static void OpStep( OPLSOUND* sndp, OPL_OP* opp ) { int step; EgStep(sndp, opp); step = opp->pg.spd; if ( opp->flag & FLAG_PME ) step = (step * sndp->lfo [LFO_UNIT_PM].output) >> PM_SHIFT; opp->pg.phase += step; } __inline static void OpStepNG( OPLSOUND* sndp, OPL_OP* opp ) { Uint32 step; EgStep(sndp, opp); opp->pg.phase += opp->pg.spd; step = opp->pg.phase >> opp->nst/*(PG_SHIFT + 5)*/; opp->pg.phase &= (1 << opp->nst/*(PG_SHIFT + 5)*/) - 1; while ( step-- ) { opp->pg.rng ^= ((opp->pg.rng & 1) << 16) + ((opp->pg.rng & 1) << 13); opp->pg.rng >>= 1; } } #if -1 >> 1 == -1 /* RIGHT SHIFT IS SIGNED */ #define SSR(x, y) ((Int32)(x) >> (y)) #else /* RIGHT SHIFT IS UNSIGNED */ #define SSR(x, y) (((x) >= 0) ? ((x) >> (y)) : (-((-(x) - 1) >> (y)) - 1)) #endif inline static void OpSynthMod( OPLSOUND* sndp, OPL_OP* opp ) { if ( opp->enable ) { Uint32 tll; Int32 output; OpStep(sndp, opp); tll = opp->tll + (opp->eg.phase >> EG_SHIFT); tll = (tll >= (1 << TLLTBL_BITS)) ? LOG_KEYOFF : sndp->common.tll2logtbl [tll]; tll += opp->tl_ofs; #if TESTING_OPTIMIZE_AME tll += *opp->amin; #else if ( opp->flag & FLAG_AME ) tll += sndp->lfo [LFO_UNIT_AM].output; #endif tll += opp->sintable [sndp->common.sintablemask & (opp->input + (opp->pg.phase >> PG_SHIFT))]; output = LogToLin(sndp->logtbl, tll, -8 + ((LOG_LIN_BITS + 1) - (SINTBL_BITS + 2))); if ( opp->fb ) { #if USE_FBBUF Int32 fbtmp; fbtmp = opp->fbbuf + output; opp->fbbuf = output; opp->input = SSR(fbtmp, (9 - opp->fb)); #else opp->input = SSR(output, (8 - opp->fb)); #endif } opp [1].input = output; } } inline static Int32 OpSynthCarFb( OPLSOUND* sndp, OPL_OP* opp ) { if ( opp->enable ) { Uint32 tll; OpStep(sndp, opp); tll = opp->tll + (opp->eg.phase >> EG_SHIFT); tll = (tll >= (1 << TLLTBL_BITS)) ? LOG_KEYOFF : sndp->common.tll2logtbl [tll]; tll += opp->tl_ofs; #if TESTING_OPTIMIZE_AME tll +=* opp->amin; #else if ( opp->flag & FLAG_AME) tll += sndp->lfo [LFO_UNIT_AM].output; #endif tll += opp->sintable [sndp->common.sintablemask & (opp->input + (opp->pg.phase >> PG_SHIFT))]; if ( opp->fb ) { #if USE_FBBUF Int32 output, fbtmp; output = LogToLin(sndp->logtbl, tll, -8 + ((LOG_LIN_BITS + 1) - (SINTBL_BITS + 2))); fbtmp = opp->fbbuf + output; opp->fbbuf = output; opp->input = SSR(fbtmp, (9 - opp->fb)); #else Int32 output; output = LogToLin(sndp->logtbl, tll, -8 + ((LOG_LIN_BITS + 1) - (SINTBL_BITS + 2))); opp->input = SSR(output, (8 - opp->fb)); #endif } return LogToLin(sndp->logtbl, tll + sndp->common.mastervolume, -8 + LOG_LIN_BITS - 19 - opp->lvl); } return 0; } inline static Int32 OpSynthCar( OPLSOUND* sndp, OPL_OP* opp ) { if ( opp->enable ) { Uint32 tll; OpStep(sndp, opp); tll = opp->tll + (opp->eg.phase >> EG_SHIFT); tll = (tll >= (1 << TLLTBL_BITS)) ? LOG_KEYOFF : sndp->common.tll2logtbl [tll]; tll += opp->tl_ofs; #if TESTING_OPTIMIZE_AME tll += *opp->amin; #else if ( opp->flag & FLAG_AME ) tll += sndp->lfo [LFO_UNIT_AM].output; #endif tll += opp->sintable [sndp->common.sintablemask & (opp->input + (opp->pg.phase >> PG_SHIFT))]; return LogToLin(sndp->logtbl, tll + sndp->common.mastervolume, -8 + LOG_LIN_BITS - 19 - opp->lvl); } return 0; } inline static Int32 OpSynthTom( OPLSOUND* sndp, OPL_OP* opp ) { if ( opp->enable ) { Uint32 tll; OpStep(sndp, opp); tll = opp->tll + (opp->eg.phase >> EG_SHIFT); tll = (tll >= 128 - 16) ? LOG_KEYOFF : sndp->common.tll2logtbl [tll]; tll += opp->tl_ofs; tll += opp->sintable [sndp->common.sintablemask & (opp->pg.phase >> PG_SHIFT)]; return LogToLin(sndp->logtbl, tll + sndp->common.mastervolume, -8 + LOG_LIN_BITS - 19 - opp->lvl); } return 0; } static Int32 OpSynthRym( OPLSOUND* sndp, OPL_OP* opp ) { if ( opp->enable ) { Uint32 tll; OpStepNG(sndp, opp); tll = opp->tll + (opp->eg.phase >> EG_SHIFT) + 0x10/* +6dB */; tll = (tll >= (1 << TLLTBL_BITS)) ? LOG_KEYOFF : sndp->common.tll2logtbl [tll]; tll += opp->tl_ofs; tll += (opp->pg.rng & 1); return LogToLin(sndp->logtbl, tll + sndp->common.mastervolume, -8 + LOG_LIN_BITS - 19 - opp->lvl); } return 0; } inline static void LfoStep(OPL_LFO* lfop ) { lfop->cnt += lfop->sps; lfop->adr += lfop->cnt >> LFO_SHIFT; lfop->cnt &= (1 << LFO_SHIFT) - 1; lfop->output = lfop->table [lfop->adr & lfop->adrmask]; } static int sndsynth( OPLSOUND* sndp ) { Int32 accum = 0; if ( sndp->common.enable ) { Uint32 i, rch; for ( i = 0; i < LFO_UNIT_NUM; i++ ) LfoStep(&sndp->lfo [i]); rch = sndp->common.rmode ? 7 : 9; for ( i = 0; i < rch; i++ ) { if ( sndp->ch [i].op [0].modcar ) OpSynthMod(sndp, &sndp->ch [i].op [0]); else accum += OpSynthCarFb(sndp, &sndp->ch [i].op [0]); accum += OpSynthCar(sndp, &sndp->ch [i].op [1]); } if ( sndp->common.rmode ) { accum += OpSynthRym(sndp, &sndp->ch [7].op [0]); accum += OpSynthRym(sndp, &sndp->ch [7].op [1]); accum += OpSynthTom(sndp, &sndp->ch [8].op [0]); accum += OpSynthRym(sndp, &sndp->ch [8].op [1]); } } if ( sndp->deltatpcm ) { accum += sndp->deltatpcm->synth(sndp->deltatpcm->ctx); } #if 0 /* NISE DAC */ if ( accum >= 0 ) accum = (Int32)(((Uint32) accum) & (((1 << 8) - 1) << (23 - 8))); else accum = -(Int32)(((Uint32)-accum) & (((1 << 8) - 1) << (23 - 8))); #endif return accum; } static void sndvolume( OPLSOUND* sndp, Int32 volume ) { if ( sndp->deltatpcm) sndp->deltatpcm->volume(sndp->deltatpcm->ctx, volume); volume = (volume << (LOG_BITS - 8)) << 1; sndp->common.mastervolume = volume; } static Uint8 const op_table [0x20]= { 0, 2, 4, 1, 3, 5,0xFF,0xFF, 6, 8, 10, 7, 9, 11,0xFF,0xFF, 12, 14, 16, 13, 15, 17,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, }; static Uint8 const mul_table [0x10]= { 1+0, 2+0, 4+0, 2+4, 8+0, 8+2, 8+4,16-2, 16+0,16+2,16+4,16+4,16+8,16+8,32-2,32-2, }; #define DB2TLL(x) (x * 2 / 375 ) static Uint8 const ksl_table [8] [16]= { { DB2TLL( 0), DB2TLL( 0), DB2TLL( 0), DB2TLL( 0), DB2TLL( 0), DB2TLL( 0), DB2TLL( 0), DB2TLL( 0), DB2TLL( 0), DB2TLL( 0), DB2TLL( 0), DB2TLL( 0), DB2TLL( 0), DB2TLL( 0), DB2TLL( 0), DB2TLL( 0), },{ DB2TLL( 0), DB2TLL( 0), DB2TLL( 0), DB2TLL( 0), DB2TLL( 0), DB2TLL( 0), DB2TLL( 0), DB2TLL( 0), DB2TLL( 0), DB2TLL( 750), DB2TLL( 1125), DB2TLL( 1500), DB2TLL( 1875), DB2TLL( 2250), DB2TLL( 2625), DB2TLL( 3000), },{ DB2TLL( 0), DB2TLL( 0), DB2TLL( 0), DB2TLL( 0), DB2TLL( 0), DB2TLL( 1125), DB2TLL( 1875), DB2TLL( 2625), DB2TLL( 3000), DB2TLL( 3750), DB2TLL( 4125), DB2TLL( 4500), DB2TLL( 4875), DB2TLL( 5250), DB2TLL( 5625), DB2TLL( 6000), },{ DB2TLL( 0), DB2TLL( 0), DB2TLL( 0), DB2TLL( 1875), DB2TLL( 3000), DB2TLL( 4125), DB2TLL( 4875), DB2TLL( 5625), DB2TLL( 6000), DB2TLL( 6750), DB2TLL( 7125), DB2TLL( 7500), DB2TLL( 7875), DB2TLL( 8250), DB2TLL( 8625), DB2TLL( 9000), },{ DB2TLL( 0), DB2TLL( 0), DB2TLL( 3000), DB2TLL( 4875), DB2TLL( 6000), DB2TLL( 7125), DB2TLL( 7875), DB2TLL( 8625), DB2TLL( 9000), DB2TLL( 9750), DB2TLL(10125), DB2TLL(10500), DB2TLL(10875), DB2TLL(11250), DB2TLL(11625), DB2TLL(12000), },{ DB2TLL( 0), DB2TLL( 3000), DB2TLL( 6000), DB2TLL( 7875), DB2TLL( 9000), DB2TLL(10125), DB2TLL(10875), DB2TLL(11625), DB2TLL(12000), DB2TLL(12750), DB2TLL(13125), DB2TLL(13500), DB2TLL(13875), DB2TLL(14250), DB2TLL(14625), DB2TLL(15000), },{ DB2TLL( 0), DB2TLL( 6000), DB2TLL( 9000), DB2TLL(10875), DB2TLL(12000), DB2TLL(13125), DB2TLL(13875), DB2TLL(14625), DB2TLL(15000), DB2TLL(15750), DB2TLL(16125), DB2TLL(16500), DB2TLL(16875), DB2TLL(17250), DB2TLL(17625), DB2TLL(18000), },{ DB2TLL( 0), DB2TLL( 9000), DB2TLL(12000), DB2TLL(13875), DB2TLL(15000), DB2TLL(16125), DB2TLL(16875), DB2TLL(17625), DB2TLL(18000), DB2TLL(18750), DB2TLL(19125), DB2TLL(19500), DB2TLL(19875), DB2TLL(20250), DB2TLL(20625), DB2TLL(21000), } }; #undef DB2TLL static Uint32 rateconvAR( OPLSOUND* sndp, Uint32 rrr, Uint32 ksr ) { if ( !rrr ) return 0; rrr = rrr + (ksr >> 2); if ( rrr >= 15) return AR_PHASEMAX; return sndp->common.ratetbla [ksr & 3] >> (CPS_SHIFTE + 1 - rrr); } static Uint32 rateconv( OPLSOUND* sndp, Uint32 rrr, Uint32 ksr ) { if ( !rrr ) return 0; rrr = rrr + (ksr >> 2); if ( rrr > 15 ) rrr = 15; return sndp->common.ratetbl [ksr & 3] >> (CPS_SHIFTE + 1 - rrr); } static void OpUpdateWF( OPLSOUND* sndp, OPL_OP* opp ) { opp->sintable = sndp->opltbl->sin_table [opp->wf & sndp->common.wfe]; } static void OpUpdatePG( OPLSOUND* sndp, OPL_CH* chp, OPL_OP* opp ) { opp->pg.spd = (((chp->freqh << 8) + chp->freql) * opp->mul * sndp->common.cpsp) >> (CPS_SHIFTP - chp->blk); } static void OpUpdateEG( OPLSOUND* sndp, OPL_CH* chp, OPL_OP* opp ) { Uint32 sr, rr; opp->ksr = chp->kcode >> ((opp->flag & FLAG_KSR) ? 0 : 2); opp->eg.dr_phasemax = opp->sl << (1 + 2 + EG_SHIFT); /* 3dB->eg */ opp->eg.spd [EG_MODE_ATTACK] = rateconvAR(sndp, opp->ar, opp->ksr); opp->eg.spd [EG_MODE_DECAY] = rateconv(sndp, opp->dr, opp->ksr); if ( opp->flag & FLAG_EGT ) { if ( opp->eg.mode == EG_MODE_SUSTINE ) opp->eg.mode = EG_MODE_SUSHOLD; sr = 0; rr = opp->rr; } else { if ( opp->eg.mode == EG_MODE_SUSHOLD ) opp->eg.mode = EG_MODE_SUSTINE; sr = opp->rr; rr = 7; } if ( chp->sus ) { rr = 5; } opp->eg.spd [EG_MODE_SUSTINE] = rateconv(sndp, sr, opp->ksr); opp->eg.spd [EG_MODE_RELEASE] = rateconv(sndp, rr, opp->ksr); } static void OpUpdateTLL( OPLSOUND* sndp, OPL_CH* chp, OPL_OP* opp ) { opp->tll = (opp->tl + (chp->ksb >> opp->ksl)) << 1; } static void oplsetopmul( OPLSOUND* sndp, OPL_CH* chp, OPL_OP* opp, Uint32 v ) { opp->flag &= ~(FLAG_AME | FLAG_PME | FLAG_EGT | FLAG_KSR); #if TESTING_OPTIMIZE_AME if ( v & 0x80 ) opp->amin = &sndp->lfo [LFO_UNIT_AM].output; else opp->amin = &sndp->common.amzero; #else if ( v & 0x80 ) opp->flag |= FLAG_AME; #endif if ( v & 0x40 ) opp->flag |= FLAG_PME; if ( v & 0x20 ) opp->flag |= FLAG_EGT; if ( v & 0x10 ) opp->flag |= FLAG_KSR; opp->mul = mul_table [v & 0x0F]; OpUpdateEG(sndp, chp, opp); OpUpdatePG(sndp, chp, opp); } static void oplsetopkstl( OPLSOUND* sndp, OPL_CH* chp, OPL_OP* opp, Uint32 v ) { opp->ksl = (v >> 6) ? (3 - (v >> 6)) : 15; /* 0 / 1.5 / 3 / 6 db/OCT */ opp->tl = v & 0x3F; /* 0.75 db */ OpUpdateTLL(sndp, chp, opp); } static void oplsetopardr( OPLSOUND* sndp, OPL_CH* chp, OPL_OP* opp, Uint32 v ) { opp->ar = v >> 4; opp->dr = v & 0xF; OpUpdateEG(sndp, chp, opp); } static void oplsetopslrr( OPLSOUND* sndp, OPL_CH* chp, OPL_OP* opp, Uint32 v ) { opp->sl = v >> 4; opp->rr = v & 0xF; OpUpdateEG(sndp, chp, opp); } static void oplsetopwf( OPLSOUND* sndp, OPL_CH* chp, OPL_OP* opp, Uint32 v ) { opp->wf = v & 0x3; OpUpdateWF(sndp, opp); } static void oplsetopkey( OPLSOUND* sndp, OPL_OP* opp ) { Uint8 nextkey = ((sndp->common.rmode) && opp->rkey) || opp->key; if ( opp->prevkey ^ nextkey ) { opp->prevkey = nextkey; if ( nextkey ) { sndp->common.enable = 1; opp->eg.mode = EG_MODE_ATTACK; opp->eg.phase = EG_KEYOFF; opp->enable = 1; opp->eg.phasear = 0; } else if ( !opp->modcar && opp->eg.mode != EG_MODE_OFF ) opp->eg.mode = EG_MODE_RELEASE; } } static void oplsetchfreql( OPLSOUND* sndp, OPL_CH* chp, Uint32 v ) { chp->freql = v & 0xFF; chp->ksb = ksl_table [chp->blk] [(chp->freqh << 2) + (chp->freql >> 6)]; OpUpdatePG(sndp, chp, &chp->op [0]); OpUpdatePG(sndp, chp, &chp->op [1]); OpUpdateTLL(sndp, chp, &chp->op [0]); OpUpdateTLL(sndp, chp, &chp->op [1]); } static void oplsetchfreqh( OPLSOUND* sndp, OPL_CH* chp, Uint32 v ) { Uint32 key = v & 0x20; chp->kcode = (v >> 1) & 15; chp->freqh = v & 3; chp->blk = (v >> 2) & 7; chp->op [0].key = chp->op [1].key = key; oplsetopkey(sndp, &chp->op [0]); oplsetopkey(sndp, &chp->op [1]); chp->sus = 0; chp->ksb = ksl_table [chp->blk] [(chp->freqh << 2) + (chp->freql >> 6)]; OpUpdateEG(sndp, chp, &chp->op [0]); OpUpdateEG(sndp, chp, &chp->op [1]); OpUpdatePG(sndp, chp, &chp->op [0]); OpUpdatePG(sndp, chp, &chp->op [1]); OpUpdateTLL(sndp, chp, &chp->op [0]); OpUpdateTLL(sndp, chp, &chp->op [1]); } static void oplsetchfbcon( OPLSOUND* sndp, OPL_CH* chp, Uint32 v ) { chp->op [0].fb = (v >> 1) & 7; #if USE_FBBUF chp->op [0].fbbuf = 0; #endif chp->con = v & 1; chp->op [0].modcar = (chp->con) ? 0 : 1; OpUpdateEG(sndp, chp, &chp->op [0]); chp->op [1].input = 0; } static void opllsetopvolume( OPLSOUND* sndp, OPL_CH* chp, OPL_OP* opp, Uint32 v ) { opp->tl = v; OpUpdateTLL(sndp, chp, opp); } static void opllsetchfreql( OPLSOUND* sndp, OPL_CH* chp, Uint32 v ) { chp->freql = v & 0xFF; chp->ksb = ksl_table [chp->blk] [(chp->freqh << 3) + (chp->freql >> 5)]; OpUpdatePG(sndp, chp, &chp->op [0]); OpUpdatePG(sndp, chp, &chp->op [1]); OpUpdateTLL(sndp, chp, &chp->op [0]); OpUpdateTLL(sndp, chp, &chp->op [1]); } static void opllsetchfreqh( OPLSOUND* sndp, OPL_CH* chp, Uint32 v ) { Uint32 key = v & 0x10; chp->kcode = v & 15; chp->freqh = v & 1; chp->blk = (v >> 1) & 7; chp->op [0].key = chp->op [1].key = key; oplsetopkey(sndp, &chp->op [0]); oplsetopkey(sndp, &chp->op [1]); chp->sus = v & 0x20; chp->ksb = ksl_table [chp->blk] [(chp->freqh << 3) + (chp->freql >> 5)]; OpUpdateEG(sndp, chp, &chp->op [0]); OpUpdateEG(sndp, chp, &chp->op [1]); OpUpdatePG(sndp, chp, &chp->op [0]); OpUpdatePG(sndp, chp, &chp->op [1]); OpUpdateTLL(sndp, chp, &chp->op [0]); OpUpdateTLL(sndp, chp, &chp->op [1]); } static void SetOpTone( OPLSOUND* sndp, OPL_OP* opp, Uint8* tonep ) { opp->flag &= ~(FLAG_AME | FLAG_PME | FLAG_EGT | FLAG_KSR); #if TESTING_OPTIMIZE_AME if ( tonep [0] & 0x80 ) opp->amin = &sndp->lfo [LFO_UNIT_AM].output; else opp->amin = &sndp->common.amzero; #else if ( tonep [0] & 0x80 ) opp->flag |= FLAG_AME; #endif if ( tonep [0] & 0x40 ) opp->flag |= FLAG_PME; if ( tonep [0] & 0x20 ) opp->flag |= FLAG_EGT; if ( tonep [0] & 0x10 ) opp->flag |= FLAG_KSR; opp->mul = mul_table [tonep [0] & 0x0F] << 1; opp->ksl = (tonep [2] >> 6) ? (3 - (tonep [2] >> 6)) : 15; opp->ar = tonep [4] >> 4; opp->dr = tonep [4] & 0xF; opp->sl = tonep [6] >> 4; opp->rr = tonep [6] & 0xF; } static void SetChTone( OPLSOUND* sndp, OPL_CH* chp, Uint8* tonep, Uint8* tlofsp ) { Uint32 op; for ( op = 0; op < 2; op++ ) SetOpTone(sndp, &chp->op [op], &tonep [op]); chp->op [0].tl_ofs = (tlofsp [0] ^ 0x80) << (LOG_BITS - 4 + 1); chp->op [1].tl_ofs = (tlofsp [1] ^ 0x80) << (LOG_BITS - 4 + 1); chp->op [0].tl = tonep [2] & 0x3F; chp->op [0].fb = tonep [3] & 0x7; chp->op [0].wf = (tonep [3] >> 3) & 1; chp->op [1].wf = (tonep [3] >> 4) & 1; #if USE_FBBUF chp->op [0].fbbuf = 0; #endif chp->op [1].input = 0; OpUpdateWF(sndp, &chp->op [0]); OpUpdateWF(sndp, &chp->op [1]); OpUpdateEG(sndp, chp, &chp->op [0]); OpUpdateEG(sndp, chp, &chp->op [1]); OpUpdatePG(sndp, chp, &chp->op [0]); OpUpdatePG(sndp, chp, &chp->op [1]); OpUpdateTLL(sndp, chp, &chp->op [0]); OpUpdateTLL(sndp, chp, &chp->op [1]); } static void opllsetchtone( OPLSOUND* sndp, OPL_CH* chp, Uint32 tone ) { SetChTone(sndp, chp, &sndp->regs [OPLL_INST_WORK + (tone << 3)], &sndp->regs [OPLL_INST_WORK2 + (tone << 1) + 0]); } static void recovercon( OPLSOUND* sndp, OPL_CH* chp ) { chp->op [0].modcar = (chp->con) ? 0 : 1; chp->op [0].lvl = chp->con ? 1 : 0; chp->op [1].lvl = 1; OpUpdateEG(sndp, chp, &chp->op [0]); chp->op [1].input = 0; } static void initrc_common( OPLSOUND* sndp, Uint32 rmode ) { if ( rmode ) { /* BD */ sndp->ch [6].op [0].modcar = 1; sndp->ch [6].op [0].lvl = 0; OpUpdateEG(sndp, &sndp->ch [6], &sndp->ch [6].op [0]); sndp->ch [6].op [1].input = 0; sndp->ch [6].op [1].lvl = 2; /* CYM */ sndp->ch [7].op [0].modcar = 0; sndp->ch [7].op [0].lvl = 1; OpUpdateEG(sndp, &sndp->ch [7], &sndp->ch [7].op [0]); /* SD */ sndp->ch [7].op [1].input = 0; sndp->ch [7].op [1].lvl = 2; /* TOM */ sndp->ch [8].op [0].modcar = 0; sndp->ch [8].op [0].lvl = 2; OpUpdateEG(sndp, &sndp->ch [8], &sndp->ch [8].op [0]); /* HH */ sndp->ch [8].op [1].input = 0; sndp->ch [8].op [1].lvl = 1; } else { recovercon(sndp, &sndp->ch [6]); if ( !sndp->ch [6].op [0].key ) SetOpOff(&sndp->ch [6].op [0]); if ( !sndp->ch [6].op [1].key ) SetOpOff(&sndp->ch [6].op [1]); recovercon(sndp, &sndp->ch [7]); if ( !sndp->ch [7].op [0].key ) SetOpOff(&sndp->ch [7].op [0]); if ( !sndp->ch [7].op [1].key ) SetOpOff(&sndp->ch [7].op [1]); recovercon(sndp, &sndp->ch [8]); if ( !sndp->ch [8].op [0].key ) SetOpOff(&sndp->ch [8].op [0]); if ( !sndp->ch [8].op [1].key ) SetOpOff(&sndp->ch [8].op [1]); } } static void oplsetrc( OPLSOUND* sndp, Uint32 rc ) { sndp->lfo [LFO_UNIT_AM].table = (rc & 0x80) ? sndp->opltbl->am_table1 : sndp->opltbl->am_table2; sndp->lfo [LFO_UNIT_PM].table = (rc & 0x40) ? sndp->opltbl->pm_table1 : sndp->opltbl->pm_table2; if ( (sndp->common.rmode ^ rc) & 0x20 ) { if ( rc & 0x20 ) { #if 0 static Uint8 volini [2] = { 0, 0 }; static Uint8 bdtone [8] = { 0x04, 0x20, 0x28, 0x00, 0xDF, 0xF8, 0xFF, 0xF8 }; SetChTone(sndp, &sndp->ch [6], bdtone, volini); SetChTone(sndp, &sndp->ch [7], &romtone [0] [0x11 << 4], volini); SetChTone(sndp, &sndp->ch [8], &romtone [0] [0x12 << 4], volini); #endif sndp->ch [7].op [0].nst = PG_SHIFT + 4; sndp->ch [7].op [1].nst = PG_SHIFT + 6; sndp->ch [8].op [1].nst = PG_SHIFT + 5; } initrc_common(sndp, rc & 0x20); } sndp->common.rmode = rc & 0x20; sndp->common.rc = rc & 0x1F; /* BD */ sndp->ch [6].op [0].rkey = sndp->ch [6].op [1].rkey = rc & 0x10; oplsetopkey(sndp, &sndp->ch [6].op [0]); oplsetopkey(sndp, &sndp->ch [6].op [1]); /* CYM */ sndp->ch [7].op [0].rkey = rc & 0x01; oplsetopkey(sndp, &sndp->ch [7].op [0]); /* SD */ sndp->ch [7].op [1].rkey = rc & 0x08; oplsetopkey(sndp, &sndp->ch [7].op [1]); /* TOM */ sndp->ch [8].op [0].rkey = rc & 0x04; oplsetopkey(sndp, &sndp->ch [8].op [0]); /* HH */ sndp->ch [8].op [1].rkey = rc & 0x02; oplsetopkey(sndp, &sndp->ch [8].op [1]); } static void opllsetrc( OPLSOUND* sndp, Uint32 rc ) { if ( (sndp->common.rmode ^ rc) & 0x20 ) { if ( rc & 0x20 ) { opllsetchtone(sndp, &sndp->ch [6], 0x10); opllsetchtone(sndp, &sndp->ch [7], 0x11); opllsetchtone(sndp, &sndp->ch [8], 0x12); opllsetopvolume(sndp, &sndp->ch [7], &sndp->ch [7].op [0], (sndp->regs [0x37] & 0xF0) >> 2); opllsetopvolume(sndp, &sndp->ch [8], &sndp->ch [8].op [0], (sndp->regs [0x38] & 0xF0) >> 2); sndp->ch [7].op [0].nst = PG_SHIFT + 5; sndp->ch [7].op [1].nst = PG_SHIFT + 5; sndp->ch [8].op [1].nst = PG_SHIFT + 5; } else { opllsetchtone(sndp, &sndp->ch [6], sndp->regs [0x36]>>4); opllsetchtone(sndp, &sndp->ch [7], sndp->regs [0x37]>>4); opllsetchtone(sndp, &sndp->ch [8], sndp->regs [0x38]>>4); } initrc_common(sndp, rc & 0x20); } sndp->common.rmode = rc & 0x20; sndp->common.rc = rc & 0x1F; /* BD */ sndp->ch [6].op [0].rkey = sndp->ch [6].op [1].rkey = rc & 0x10; oplsetopkey(sndp, &sndp->ch [6].op [0]); oplsetopkey(sndp, &sndp->ch [6].op [1]); /* CYM */ sndp->ch [7].op [0].rkey = rc & 0x01; oplsetopkey(sndp, &sndp->ch [7].op [0]); /* SD */ sndp->ch [7].op [1].rkey = rc & 0x08; oplsetopkey(sndp, &sndp->ch [7].op [1]); /* TOM */ sndp->ch [8].op [0].rkey = rc & 0x04; oplsetopkey(sndp, &sndp->ch [8].op [0]); /* HH */ sndp->ch [8].op [1].rkey = rc & 0x02; oplsetopkey(sndp, &sndp->ch [8].op [1]); } #define OPLSETOP(func) { \ Uint32 op = op_table [a & 0x1F]; \ if ( op != 0xFF) func(sndp, &sndp->ch [op >> 1], &sndp->ch [op >> 1].op [op & 1], v); \ } __inline static void oplwritereg( OPLSOUND* sndp, Uint32 a, Uint32 v ) { switch ( a >> 5 ) { default: NEVER_REACH case 0: switch ( a & 0x1F ) { case 0x01: if ( sndp->opl_type == OPL_TYPE_OPL2 ) { Uint32 i; sndp->common.wfe = (v & 0x20) ? 3 : 0; for ( i = 0; i < 9; i++ ) { OpUpdateWF(sndp, &sndp->ch [i].op [0]); OpUpdateWF(sndp, &sndp->ch [i].op [1]); } } break; case 0x08: /* CSM mode */ case 0x07: case 0x09: case 0x0A: case 0x0B: case 0x0C: case 0x0D: case 0x0E: case 0x0F: case 0x10: case 0x11: case 0x12: if ( sndp->deltatpcm ) sndp->deltatpcm->write(sndp->deltatpcm->ctx, a - 0x07, v); break; } break; case 1: OPLSETOP(oplsetopmul); break; case 2: OPLSETOP(oplsetopkstl); break; case 3: OPLSETOP(oplsetopardr); break; case 4: OPLSETOP(oplsetopslrr); break; case 7: OPLSETOP(oplsetopwf); break; case 5: if ( (a & 0x1F) == (0xBD & 0x1F) ) oplsetrc(sndp, v); else if ( (a & 0x1F) < 9 ) oplsetchfreql(sndp, &sndp->ch [a & 0xF], v); else if ( (a & 0xF) < 9 ) oplsetchfreqh(sndp, &sndp->ch [a & 0xF], v); break; case 6: if ( (a & 0x1F) < 9) oplsetchfbcon(sndp, &sndp->ch [a & 0xF], v); break; } } static void oplwrite( OPLSOUND* sndp, Uint32 a, Uint32 v ) { if ( a & 1 ) { sndp->regs [sndp->common.adr] = v; oplwritereg(sndp, sndp->common.adr, v); } else sndp->common.adr = v; } static Uint32 oplread( OPLSOUND* sndp, Uint32 a ) { if ( a & 1 ) return sndp->regs [sndp->common.adr]; else return 0x80; } __inline static void opllwritereg( OPLSOUND* sndp, Uint32 a, Uint32 v ) { switch ( a >> 3 ) { default: NEVER_REACH case 0: sndp->regs [OPLL_INST_WORK + (a & 7)] = v; break; case 1: if ( a == 0xE) opllsetrc(sndp, v & 0x3F); break; case 2: case 3: a &= 0xF; if ( a < 9) opllsetchfreql(sndp, &sndp->ch [a], v); break; case 4: case 5: a &= 0xF; if ( a < 9) opllsetchfreqh(sndp, &sndp->ch [a], v); break; case 6: case 7: a &= 0xF; if ( a < 9 ) { if ( (sndp->common.rmode) && (a >= 6) ) { if ( a != 6) opllsetopvolume(sndp, &sndp->ch [a], &sndp->ch [a].op [0], (v & 0xF0) >> 2); } else { opllsetchtone(sndp, &sndp->ch [a], (v & 0xF0) >> 4); } opllsetopvolume(sndp, &sndp->ch [a], &sndp->ch [a].op [1], (v & 0xF) << 2); } break; } } static void opllwrite( OPLSOUND* sndp, Uint32 a, Uint32 v ) { if ( a & 1 ) { if ( sndp->common.adr < 0x40 ) { sndp->regs [sndp->common.adr] = v; opllwritereg(sndp, sndp->common.adr, v); } } else sndp->common.adr = v; } static Uint32 opllread( OPLSOUND* sndp, Uint32 a ) { return 0xFF; } static void opreset( OPLSOUND* sndp, OPL_OP* opp ) { /* XMEMSET(opp, 0, sizeof(OPL_OP)); */ SetOpOff(opp); opp->tl_ofs = 0x80 << (LOG_BITS - 4 + 1); #if TESTING_OPTIMIZE_AME opp->amin = &sndp->common.amzero; #endif opp->pg.rng = 0xFFFF; } static void chreset( OPLSOUND* sndp, OPL_CH* chp, Uint32 clock, Uint32 freq ) { Uint32 op; XMEMSET(chp, 0, sizeof(OPL_CH)); for ( op = 0; op < 2; op++ ) { opreset(sndp, &chp->op [op]); } recovercon(sndp, chp); } static void sndreset( OPLSOUND* sndp, Uint32 clock, Uint32 freq ) { Uint32 i, cpse; XMEMSET(&sndp->common, 0, sizeof(sndp->common)); XMEMSET(&sndp->lfo [LFO_UNIT_AM], 0, sizeof(OPL_LFO)); sndp->lfo [LFO_UNIT_AM].sps = DivFix(37 * (1 << AMTBL_BITS), freq * 10, LFO_SHIFT); sndp->lfo [LFO_UNIT_AM].adrmask = (1 << AMTBL_BITS) - 1; sndp->lfo [LFO_UNIT_AM].table = sndp->opltbl->am_table1; XMEMSET(&sndp->lfo [LFO_UNIT_PM], 0, sizeof(OPL_LFO)); sndp->lfo [LFO_UNIT_PM].sps = DivFix(64 * (1 << PMTBL_BITS), freq * 10, LFO_SHIFT); sndp->lfo [LFO_UNIT_PM].adrmask = (1 << PMTBL_BITS) - 1; sndp->lfo [LFO_UNIT_PM].table = sndp->opltbl->pm_table1; sndp->common.cpsp = DivFix(clock, 72 * freq, CPS_SHIFTP); cpse = DivFix(clock, 72 * freq, CPS_SHIFTE); for ( i = 0; i < 4; i++ ) { sndp->common.ratetbl [i] = (i + 4) * cpse; sndp->common.ratetbla [i] = 3 * sndp->common.ratetbl [i]; } sndp->common.tll2logtbl = sndp->opltbl->tll2log_table; sndp->common.sintablemask = (1 << SINTBL_BITS) - 1; for ( i = 0; i < 9; i++ ) chreset(sndp, &sndp->ch [i], clock, freq); if ( sndp->deltatpcm ) sndp->deltatpcm->reset(sndp->deltatpcm->ctx, clock, freq); if ( sndp->opl_type & OPL_TYPE_OPL ) { XMEMSET(&sndp->regs, 0, 0x100); sndp->common.ar_table = sndp->opltbl->ar_tablepow; sndp->common.sintablemask -= (1 << (SINTBL_BITS - 11)) - 1; for ( i = 0x0; i < 0x100; i++ ) { oplwrite(sndp, 0, i); oplwrite(sndp, 1, 0x00); } for ( i = 0xA0; i < 0xA9; i++ ) { oplwrite(sndp, 0, 0xA0 + i); oplwrite(sndp, 1, 0x40); oplwrite(sndp, 0, 0xB0 + i); oplwrite(sndp, 1, 0x0E); } } else { static Uint8 const fmbios_initdata [9] = "\x30\x10\x20\x20\xfb\xb2\xf3\xf3"; XMEMSET(&sndp->regs, 0, 0x40); sndp->common.ar_table = sndp->opltbl->ar_tablelog; sndp->common.wfe = 1; sndp->common.sintablemask -= (1 << (SINTBL_BITS - 8)) - 1; for ( i = 0; i < sizeof(fmbios_initdata)-1; i++ ) { opllwrite(sndp, 0, i); opllwrite(sndp, 1, fmbios_initdata [i]); } opllwrite(sndp, 0, 0x0E); opllwrite(sndp, 1, 0x00); opllwrite(sndp, 0, 0x0F); opllwrite(sndp, 1, 0x00); for ( i = 0; i < 9; i++ ) { opllwrite(sndp, 0, 0x10 + i); opllwrite(sndp, 1, 0x20); opllwrite(sndp, 0, 0x20 + i); opllwrite(sndp, 1, 0x07); opllwrite(sndp, 0, 0x30 + i); opllwrite(sndp, 1, 0xB3); } } } static void oplsetinst( OPLSOUND* sndp, Uint32 n, void* p, Uint32 l ) { if ( sndp->deltatpcm) sndp->deltatpcm->setinst(sndp->deltatpcm->ctx, n, p, l); } __inline static Uint32 GetDwordLE(Uint8* p ) { return p [0] | (p [1] << 8) | (p [2] << 16) | (p [3] << 24); } #define GetDwordLEM(p) (Uint32)((((Uint8* )p) [0] | (((Uint8* )p) [1] << 8) | (((Uint8* )p) [2] << 16) | (((Uint8* )p) [3] << 24)) ) static void opllsetinst( OPLSOUND* sndp, Uint32 n, Uint8* p, Uint32 l ) { Int32 i, j, sb = 9; if ( n ) return; if ( (GetDwordLE(p) & 0xF0FFFFFF) == GetDwordLEM("ILL0") ) { if ( 0 < p [4] && p [4] <= SINTBL_BITS) sb = p [4]; for ( j = 1; j < 16 + 3; j++ ) for ( i = 0; i < 8; i++ ) sndp->regs [OPLL_INST_WORK + (j << 3) + i] = p [(j << 4) + i]; for ( j = 0; j < 16 + 3; j++ ) { sndp->regs [OPLL_INST_WORK2 + (j << 1) + 0] = p [(j << 4) + 8]; sndp->regs [OPLL_INST_WORK2 + (j << 1) + 1] = p [(j << 4) + 9]; } } else { for ( j = 1; j < 16; j++ ) for ( i = 0; i < 8; i++ ) sndp->regs [OPLL_INST_WORK + (j << 3) + i] = p [((j - 1) << 3) + i]; } sndp->common.sintablemask = (1 << SINTBL_BITS) - 1; sndp->common.sintablemask -= (1 << (SINTBL_BITS - sb)) - 1; } static void sndrelease( OPLSOUND* sndp ) { if ( sndp->logtbl) sndp->logtbl->release(sndp->logtbl->ctx); if ( sndp->opltbl) sndp->opltbl->release(sndp->opltbl->ctx); if ( sndp->deltatpcm) sndp->deltatpcm->release(sndp->deltatpcm->ctx); XFREE(sndp); } KMIF_SOUND_DEVICE* OPLSoundAlloc(Uint32 opl_type ) { OPLSOUND* sndp; sndp = (OPLSOUND*) XMALLOC(sizeof(OPLSOUND)); if ( !sndp) return 0; sndp->opl_type = opl_type; sndp->kmif.ctx = sndp; sndp->kmif.release = (void (*)( void* )) sndrelease; sndp->kmif.volume = (void (*)( void*, int )) sndvolume; sndp->kmif.reset = (void (*)( void*, Uint32, Uint32 )) sndreset; sndp->kmif.synth = (int (*)( void* )) sndsynth; if ( sndp->opl_type == OPL_TYPE_MSXAUDIO ) { sndp->deltatpcm = YMDELTATPCMSoundAlloc(YMDELTATPCM_TYPE_Y8950); } else sndp->deltatpcm = 0; if ( sndp->opl_type & OPL_TYPE_OPL ) { sndp->kmif.write = (void (*)( void*, Uint32, Uint32 )) oplwrite; sndp->kmif.read = (Uint32 (*)( void*, Uint32 )) oplread; sndp->kmif.setinst = (void (*)( void*, Uint32, void*, Uint32 )) oplsetinst; } else { sndp->kmif.write = (void (*)( void*, Uint32, Uint32 )) opllwrite; sndp->kmif.read = (Uint32 (*)( void*, Uint32 )) opllread; sndp->kmif.setinst = (void (*)( void*, Uint32, void*, Uint32 )) opllsetinst; switch ( sndp->opl_type ) { case OPL_TYPE_OPLL: case OPL_TYPE_MSXMUSIC: opllsetinst(sndp, 0, romtone [0], 16 * 19); break; case OPL_TYPE_SMSFMUNIT: opllsetinst(sndp, 0, romtone [1], 16 * 19); break; case OPL_TYPE_VRC7: opllsetinst(sndp, 0, romtone [2], 16 * 19); break; } } sndp->logtbl = LogTableAddRef(); sndp->opltbl = OplTableAddRef(); if ( !sndp->logtbl || !sndp->opltbl ) { sndrelease(sndp); return 0; } return &sndp->kmif; }