From 2a64882493c381b2ce9840ddd259aa5abb71a06a Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Wed, 4 Mar 2009 21:16:07 +0000 Subject: Add fmopl files from ScummVM. Subversion-branch: /branches/opl-branch Subversion-revision: 1445 --- opl/fmopl.c | 1191 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ opl/fmopl.h | 173 +++++++++ 2 files changed, 1364 insertions(+) create mode 100644 opl/fmopl.c create mode 100644 opl/fmopl.h (limited to 'opl') diff --git a/opl/fmopl.c b/opl/fmopl.c new file mode 100644 index 00000000..5fda6e44 --- /dev/null +++ b/opl/fmopl.c @@ -0,0 +1,1191 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL: http://scummvm.svn.sourceforge.net/svnroot/scummvm/scummvm/trunk/sound/fmopl.cpp $ + * $Id: fmopl.cpp 38211 2009-02-15 10:07:50Z sev $ + * + * LGPL licensed version of MAMEs fmopl (V0.37a modified) by + * Tatsuyuki Satoh. Included from LGPL'ed AdPlug. + */ + +#include +#include +#include +#include +#include + +#include "sound/fmopl.h" + +#if defined (_WIN32_WCE) || defined (__SYMBIAN32__) || defined(PALMOS_MODE) || defined(__GP32__) || defined(GP2X) || defined (__MAEMO__) || defined(__DS__) || defined (__MINT__) +#include "common/config-manager.h" +#endif + +/* -------------------- preliminary define section --------------------- */ +/* attack/decay rate time rate */ +#define OPL_ARRATE 141280 /* RATE 4 = 2826.24ms @ 3.6MHz */ +#define OPL_DRRATE 1956000 /* RATE 4 = 39280.64ms @ 3.6MHz */ + +#define FREQ_BITS 24 /* frequency turn */ + +/* counter bits = 20 , octerve 7 */ +#define FREQ_RATE (1<<(FREQ_BITS-20)) +#define TL_BITS (FREQ_BITS+2) + +/* final output shift , limit minimum and maximum */ +#define OPL_OUTSB (TL_BITS+3-16) /* OPL output final shift 16bit */ +#define OPL_MAXOUT (0x7fff<status |= flag; + if(!(OPL->status & 0x80)) { + if(OPL->status & OPL->statusmask) { /* IRQ on */ + OPL->status |= 0x80; + /* callback user interrupt handler (IRQ is OFF to ON) */ + if(OPL->IRQHandler) + (OPL->IRQHandler)(OPL->IRQParam,1); + } + } +} + +/* status reset and IRQ handling */ +inline void OPL_STATUS_RESET(FM_OPL *OPL, int flag) { + /* reset status flag */ + OPL->status &= ~flag; + if((OPL->status & 0x80)) { + if (!(OPL->status & OPL->statusmask)) { + OPL->status &= 0x7f; + /* callback user interrupt handler (IRQ is ON to OFF) */ + if(OPL->IRQHandler) (OPL->IRQHandler)(OPL->IRQParam,0); + } + } +} + +/* IRQ mask set */ +inline void OPL_STATUSMASK_SET(FM_OPL *OPL, int flag) { + OPL->statusmask = flag; + /* IRQ handling check */ + OPL_STATUS_SET(OPL,0); + OPL_STATUS_RESET(OPL,0); +} + +/* ----- key on ----- */ +inline void OPL_KEYON(OPL_SLOT *SLOT) { + /* sin wave restart */ + SLOT->Cnt = 0; + /* set attack */ + SLOT->evm = ENV_MOD_AR; + SLOT->evs = SLOT->evsa; + SLOT->evc = EG_AST; + SLOT->eve = EG_AED; +} + +/* ----- key off ----- */ +inline void OPL_KEYOFF(OPL_SLOT *SLOT) { + if( SLOT->evm > ENV_MOD_RR) { + /* set envelope counter from envleope output */ + + // WORKAROUND: The Kyra engine does something very strange when + // starting a new song. For each channel: + // + // * The release rate is set to "fastest". + // * Any note is keyed off. + // * A very low-frequency note is keyed on. + // + // Usually, what happens next is that the real notes is keyed + // on immediately, in which case there's no problem. + // + // However, if the note is again keyed off (because the channel + // begins on a rest rather than a note), the envelope counter + // was moved from the very lowest point on the attack curve to + // the very highest point on the release curve. + // + // Again, this might not be a problem, if the release rate is + // still set to "fastest". But in many cases, it had already + // been increased. And, possibly because of inaccuracies in the + // envelope generator, that would cause the note to "fade out" + // for quite a long time. + // + // What we really need is a way to find the correct starting + // point for the envelope counter, and that may be what the + // commented-out line below is meant to do. For now, simply + // handle the pathological case. + + if (SLOT->evm == ENV_MOD_AR && SLOT->evc == EG_AST) + SLOT->evc = EG_DED; + else if( !(SLOT->evc & EG_DST) ) + //SLOT->evc = (ENV_CURVE[SLOT->evc>>ENV_BITS]<evc = EG_DST; + SLOT->eve = EG_DED; + SLOT->evs = SLOT->evsr; + SLOT->evm = ENV_MOD_RR; + } +} + +/* ---------- calcrate Envelope Generator & Phase Generator ---------- */ + +/* return : envelope output */ +inline uint OPL_CALC_SLOT(OPL_SLOT *SLOT) { + /* calcrate envelope generator */ + if((SLOT->evc += SLOT->evs) >= SLOT->eve) { + switch( SLOT->evm ) { + case ENV_MOD_AR: /* ATTACK -> DECAY1 */ + /* next DR */ + SLOT->evm = ENV_MOD_DR; + SLOT->evc = EG_DST; + SLOT->eve = SLOT->SL; + SLOT->evs = SLOT->evsd; + break; + case ENV_MOD_DR: /* DECAY -> SL or RR */ + SLOT->evc = SLOT->SL; + SLOT->eve = EG_DED; + if(SLOT->eg_typ) { + SLOT->evs = 0; + } else { + SLOT->evm = ENV_MOD_RR; + SLOT->evs = SLOT->evsr; + } + break; + case ENV_MOD_RR: /* RR -> OFF */ + SLOT->evc = EG_OFF; + SLOT->eve = EG_OFF + 1; + SLOT->evs = 0; + break; + } + } + /* calcrate envelope */ + return SLOT->TLL + ENV_CURVE[SLOT->evc>>ENV_BITS] + (SLOT->ams ? ams : 0); +} + +/* set algorythm connection */ +static void set_algorythm(OPL_CH *CH) { + int *carrier = &outd[0]; + CH->connect1 = CH->CON ? carrier : &feedback2; + CH->connect2 = carrier; +} + +/* ---------- frequency counter for operater update ---------- */ +inline void CALC_FCSLOT(OPL_CH *CH, OPL_SLOT *SLOT) { + int ksr; + + /* frequency step counter */ + SLOT->Incr = CH->fc * SLOT->mul; + ksr = CH->kcode >> SLOT->KSR; + + if( SLOT->ksr != ksr ) { + SLOT->ksr = ksr; + /* attack , decay rate recalcration */ + SLOT->evsa = SLOT->AR[ksr]; + SLOT->evsd = SLOT->DR[ksr]; + SLOT->evsr = SLOT->RR[ksr]; + } + SLOT->TLL = SLOT->TL + (CH->ksl_base>>SLOT->ksl); +} + +/* set multi,am,vib,EG-TYP,KSR,mul */ +inline void set_mul(FM_OPL *OPL, int slot, int v) { + OPL_CH *CH = &OPL->P_CH[slot>>1]; + OPL_SLOT *SLOT = &CH->SLOT[slot & 1]; + + SLOT->mul = MUL_TABLE[v & 0x0f]; + SLOT->KSR = (v & 0x10) ? 0 : 2; + SLOT->eg_typ = (v & 0x20) >> 5; + SLOT->vib = (v & 0x40); + SLOT->ams = (v & 0x80); + CALC_FCSLOT(CH, SLOT); +} + +/* set ksl & tl */ +inline void set_ksl_tl(FM_OPL *OPL, int slot, int v) { + OPL_CH *CH = &OPL->P_CH[slot>>1]; + OPL_SLOT *SLOT = &CH->SLOT[slot & 1]; + int ksl = v >> 6; /* 0 / 1.5 / 3 / 6 db/OCT */ + + SLOT->ksl = ksl ? 3-ksl : 31; + SLOT->TL = (int)((v & 0x3f) * (0.75 / EG_STEP)); /* 0.75db step */ + + if(!(OPL->mode & 0x80)) { /* not CSM latch total level */ + SLOT->TLL = SLOT->TL + (CH->ksl_base >> SLOT->ksl); + } +} + +/* set attack rate & decay rate */ +inline void set_ar_dr(FM_OPL *OPL, int slot, int v) { + OPL_CH *CH = &OPL->P_CH[slot>>1]; + OPL_SLOT *SLOT = &CH->SLOT[slot & 1]; + int ar = v >> 4; + int dr = v & 0x0f; + + SLOT->AR = ar ? &OPL->AR_TABLE[ar << 2] : RATE_0; + SLOT->evsa = SLOT->AR[SLOT->ksr]; + if(SLOT->evm == ENV_MOD_AR) + SLOT->evs = SLOT->evsa; + + SLOT->DR = dr ? &OPL->DR_TABLE[dr<<2] : RATE_0; + SLOT->evsd = SLOT->DR[SLOT->ksr]; + if(SLOT->evm == ENV_MOD_DR) + SLOT->evs = SLOT->evsd; +} + +/* set sustain level & release rate */ +inline void set_sl_rr(FM_OPL *OPL, int slot, int v) { + OPL_CH *CH = &OPL->P_CH[slot>>1]; + OPL_SLOT *SLOT = &CH->SLOT[slot & 1]; + int sl = v >> 4; + int rr = v & 0x0f; + + SLOT->SL = SL_TABLE[sl]; + if(SLOT->evm == ENV_MOD_DR) + SLOT->eve = SLOT->SL; + SLOT->RR = &OPL->DR_TABLE[rr<<2]; + SLOT->evsr = SLOT->RR[SLOT->ksr]; + if(SLOT->evm == ENV_MOD_RR) + SLOT->evs = SLOT->evsr; +} + +/* operator output calcrator */ + +#define OP_OUT(slot,env,con) slot->wavetable[((slot->Cnt + con)>>(24-SIN_ENT_SHIFT)) & (SIN_ENT-1)][env] +/* ---------- calcrate one of channel ---------- */ +inline void OPL_CALC_CH(OPL_CH *CH) { + uint env_out; + OPL_SLOT *SLOT; + + feedback2 = 0; + /* SLOT 1 */ + SLOT = &CH->SLOT[SLOT1]; + env_out=OPL_CALC_SLOT(SLOT); + if(env_out < (uint)(EG_ENT - 1)) { + /* PG */ + if(SLOT->vib) + SLOT->Cnt += (SLOT->Incr * vib) >> VIB_RATE_SHIFT; + else + SLOT->Cnt += SLOT->Incr; + /* connection */ + if(CH->FB) { + int feedback1 = (CH->op1_out[0] + CH->op1_out[1]) >> CH->FB; + CH->op1_out[1] = CH->op1_out[0]; + *CH->connect1 += CH->op1_out[0] = OP_OUT(SLOT, env_out, feedback1); + } else { + *CH->connect1 += OP_OUT(SLOT, env_out, 0); + } + } else { + CH->op1_out[1] = CH->op1_out[0]; + CH->op1_out[0] = 0; + } + /* SLOT 2 */ + SLOT = &CH->SLOT[SLOT2]; + env_out=OPL_CALC_SLOT(SLOT); + if(env_out < (uint)(EG_ENT - 1)) { + /* PG */ + if(SLOT->vib) + SLOT->Cnt += (SLOT->Incr * vib) >> VIB_RATE_SHIFT; + else + SLOT->Cnt += SLOT->Incr; + /* connection */ + outd[0] += OP_OUT(SLOT, env_out, feedback2); + } +} + +/* ---------- calcrate rythm block ---------- */ +#define WHITE_NOISE_db 6.0 +inline void OPL_CALC_RH(FM_OPL *OPL, OPL_CH *CH) { + uint env_tam, env_sd, env_top, env_hh; + // This code used to do int(OPL->rnd.getRandomBit() * (WHITE_NOISE_db / EG_STEP)), + // but EG_STEP = 96.0/EG_ENT, and WHITE_NOISE_db=6.0. So, that's equivalent to + // int(OPL->rnd.getRandomBit() * EG_ENT/16). We know that EG_ENT is 4096, or 1024, + // or 128, so we can safely avoid any FP ops. + int whitenoise = OPL->rnd.getRandomBit() * (EG_ENT>>4); + + int tone8; + + OPL_SLOT *SLOT; + int env_out; + + /* BD : same as FM serial mode and output level is large */ + feedback2 = 0; + /* SLOT 1 */ + SLOT = &CH[6].SLOT[SLOT1]; + env_out = OPL_CALC_SLOT(SLOT); + if(env_out < EG_ENT-1) { + /* PG */ + if(SLOT->vib) + SLOT->Cnt += (SLOT->Incr * vib) >> VIB_RATE_SHIFT; + else + SLOT->Cnt += SLOT->Incr; + /* connection */ + if(CH[6].FB) { + int feedback1 = (CH[6].op1_out[0] + CH[6].op1_out[1]) >> CH[6].FB; + CH[6].op1_out[1] = CH[6].op1_out[0]; + feedback2 = CH[6].op1_out[0] = OP_OUT(SLOT, env_out, feedback1); + } + else { + feedback2 = OP_OUT(SLOT, env_out, 0); + } + } else { + feedback2 = 0; + CH[6].op1_out[1] = CH[6].op1_out[0]; + CH[6].op1_out[0] = 0; + } + /* SLOT 2 */ + SLOT = &CH[6].SLOT[SLOT2]; + env_out = OPL_CALC_SLOT(SLOT); + if(env_out < EG_ENT-1) { + /* PG */ + if(SLOT->vib) + SLOT->Cnt += (SLOT->Incr * vib) >> VIB_RATE_SHIFT; + else + SLOT->Cnt += SLOT->Incr; + /* connection */ + outd[0] += OP_OUT(SLOT, env_out, feedback2) * 2; + } + + // SD (17) = mul14[fnum7] + white noise + // TAM (15) = mul15[fnum8] + // TOP (18) = fnum6(mul18[fnum8]+whitenoise) + // HH (14) = fnum7(mul18[fnum8]+whitenoise) + white noise + env_sd = OPL_CALC_SLOT(SLOT7_2) + whitenoise; + env_tam =OPL_CALC_SLOT(SLOT8_1); + env_top = OPL_CALC_SLOT(SLOT8_2); + env_hh = OPL_CALC_SLOT(SLOT7_1) + whitenoise; + + /* PG */ + if(SLOT7_1->vib) + SLOT7_1->Cnt += (SLOT7_1->Incr * vib) >> (VIB_RATE_SHIFT-1); + else + SLOT7_1->Cnt += 2 * SLOT7_1->Incr; + if(SLOT7_2->vib) + SLOT7_2->Cnt += (CH[7].fc * vib) >> (VIB_RATE_SHIFT-3); + else + SLOT7_2->Cnt += (CH[7].fc * 8); + if(SLOT8_1->vib) + SLOT8_1->Cnt += (SLOT8_1->Incr * vib) >> VIB_RATE_SHIFT; + else + SLOT8_1->Cnt += SLOT8_1->Incr; + if(SLOT8_2->vib) + SLOT8_2->Cnt += ((CH[8].fc * 3) * vib) >> (VIB_RATE_SHIFT-4); + else + SLOT8_2->Cnt += (CH[8].fc * 48); + + tone8 = OP_OUT(SLOT8_2,whitenoise,0 ); + + /* SD */ + if(env_sd < (uint)(EG_ENT - 1)) + outd[0] += OP_OUT(SLOT7_1, env_sd, 0) * 8; + /* TAM */ + if(env_tam < (uint)(EG_ENT - 1)) + outd[0] += OP_OUT(SLOT8_1, env_tam, 0) * 2; + /* TOP-CY */ + if(env_top < (uint)(EG_ENT - 1)) + outd[0] += OP_OUT(SLOT7_2, env_top, tone8) * 2; + /* HH */ + if(env_hh < (uint)(EG_ENT-1)) + outd[0] += OP_OUT(SLOT7_2, env_hh, tone8) * 2; +} + +/* ----------- initialize time tabls ----------- */ +static void init_timetables(FM_OPL *OPL, int ARRATE, int DRRATE) { + int i; + double rate; + + /* make attack rate & decay rate tables */ + for (i = 0; i < 4; i++) + OPL->AR_TABLE[i] = OPL->DR_TABLE[i] = 0; + for (i = 4; i <= 60; i++) { + rate = OPL->freqbase; /* frequency rate */ + if(i < 60) + rate *= 1.0 + (i & 3) * 0.25; /* b0-1 : x1 , x1.25 , x1.5 , x1.75 */ + rate *= 1 << ((i >> 2) - 1); /* b2-5 : shift bit */ + rate *= (double)(EG_ENT << ENV_BITS); + OPL->AR_TABLE[i] = (int)(rate / ARRATE); + OPL->DR_TABLE[i] = (int)(rate / DRRATE); + } + for (i = 60; i < 76; i++) { + OPL->AR_TABLE[i] = EG_AED-1; + OPL->DR_TABLE[i] = OPL->DR_TABLE[60]; + } +} + +/* ---------- generic table initialize ---------- */ +static int OPLOpenTable(void) { + int s,t; + double rate; + int i,j; + double pom; + +#ifdef __DS__ + DS::fastRamReset(); + + TL_TABLE = (int *) DS::fastRamAlloc(TL_MAX * 2 * sizeof(int *)); + SIN_TABLE = (int **) DS::fastRamAlloc(SIN_ENT * 4 * sizeof(int *)); +#else + + /* allocate dynamic tables */ + if((TL_TABLE = (int *)malloc(TL_MAX * 2 * sizeof(int))) == NULL) + return 0; + + if((SIN_TABLE = (int **)malloc(SIN_ENT * 4 * sizeof(int *))) == NULL) { + free(TL_TABLE); + return 0; + } +#endif + + if((AMS_TABLE = (int *)malloc(AMS_ENT * 2 * sizeof(int))) == NULL) { + free(TL_TABLE); + free(SIN_TABLE); + return 0; + } + + if((VIB_TABLE = (int *)malloc(VIB_ENT * 2 * sizeof(int))) == NULL) { + free(TL_TABLE); + free(SIN_TABLE); + free(AMS_TABLE); + return 0; + } + /* make total level table */ + for (t = 0; t < EG_ENT - 1 ; t++) { + rate = ((1 << TL_BITS) - 1) / pow(10.0, EG_STEP * t / 20); /* dB -> voltage */ + TL_TABLE[ t] = (int)rate; + TL_TABLE[TL_MAX + t] = -TL_TABLE[t]; + } + /* fill volume off area */ + for (t = EG_ENT - 1; t < TL_MAX; t++) { + TL_TABLE[t] = TL_TABLE[TL_MAX + t] = 0; + } + + /* make sinwave table (total level offet) */ + /* degree 0 = degree 180 = off */ + SIN_TABLE[0] = SIN_TABLE[SIN_ENT /2 ] = &TL_TABLE[EG_ENT - 1]; + for (s = 1;s <= SIN_ENT / 4; s++) { + pom = sin(2 * PI * s / SIN_ENT); /* sin */ + pom = 20 * log10(1 / pom); /* decibel */ + j = int(pom / EG_STEP); /* TL_TABLE steps */ + + /* degree 0 - 90 , degree 180 - 90 : plus section */ + SIN_TABLE[ s] = SIN_TABLE[SIN_ENT / 2 - s] = &TL_TABLE[j]; + /* degree 180 - 270 , degree 360 - 270 : minus section */ + SIN_TABLE[SIN_ENT / 2 + s] = SIN_TABLE[SIN_ENT - s] = &TL_TABLE[TL_MAX + j]; + } + for (s = 0;s < SIN_ENT; s++) { + SIN_TABLE[SIN_ENT * 1 + s] = s < (SIN_ENT / 2) ? SIN_TABLE[s] : &TL_TABLE[EG_ENT]; + SIN_TABLE[SIN_ENT * 2 + s] = SIN_TABLE[s % (SIN_ENT / 2)]; + SIN_TABLE[SIN_ENT * 3 + s] = (s / (SIN_ENT / 4)) & 1 ? &TL_TABLE[EG_ENT] : SIN_TABLE[SIN_ENT * 2 + s]; + } + + + ENV_CURVE = (int *)malloc(sizeof(int) * (2*EG_ENT+1)); + + /* envelope counter -> envelope output table */ + for (i=0; i < EG_ENT; i++) { + /* ATTACK curve */ + pom = pow(((double)(EG_ENT - 1 - i) / EG_ENT), 8) * EG_ENT; + /* if( pom >= EG_ENT ) pom = EG_ENT-1; */ + ENV_CURVE[i] = (int)pom; + /* DECAY ,RELEASE curve */ + ENV_CURVE[(EG_DST >> ENV_BITS) + i]= i; + } + /* off */ + ENV_CURVE[EG_OFF >> ENV_BITS]= EG_ENT - 1; + /* make LFO ams table */ + for (i=0; i < AMS_ENT; i++) { + pom = (1.0 + sin(2 * PI * i / AMS_ENT)) / 2; /* sin */ + AMS_TABLE[i] = (int)((1.0 / EG_STEP) * pom); /* 1dB */ + AMS_TABLE[AMS_ENT + i] = (int)((4.8 / EG_STEP) * pom); /* 4.8dB */ + } + /* make LFO vibrate table */ + for (i=0; i < VIB_ENT; i++) { + /* 100cent = 1seminote = 6% ?? */ + pom = (double)VIB_RATE * 0.06 * sin(2 * PI * i / VIB_ENT); /* +-100sect step */ + VIB_TABLE[i] = (int)(VIB_RATE + (pom * 0.07)); /* +- 7cent */ + VIB_TABLE[VIB_ENT + i] = (int)(VIB_RATE + (pom * 0.14)); /* +-14cent */ + } + return 1; +} + +static void OPLCloseTable(void) { + free(TL_TABLE); + free(SIN_TABLE); + free(AMS_TABLE); + free(VIB_TABLE); + free(ENV_CURVE); +} + +/* CSM Key Controll */ +inline void CSMKeyControll(OPL_CH *CH) { + OPL_SLOT *slot1 = &CH->SLOT[SLOT1]; + OPL_SLOT *slot2 = &CH->SLOT[SLOT2]; + /* all key off */ + OPL_KEYOFF(slot1); + OPL_KEYOFF(slot2); + /* total level latch */ + slot1->TLL = slot1->TL + (CH->ksl_base>>slot1->ksl); + slot1->TLL = slot1->TL + (CH->ksl_base>>slot1->ksl); + /* key on */ + CH->op1_out[0] = CH->op1_out[1] = 0; + OPL_KEYON(slot1); + OPL_KEYON(slot2); +} + +/* ---------- opl initialize ---------- */ +static void OPL_initalize(FM_OPL *OPL) { + int fn; + + /* frequency base */ + OPL->freqbase = (OPL->rate) ? ((double)OPL->clock / OPL->rate) / 72 : 0; + /* Timer base time */ + OPL->TimerBase = 1.0/((double)OPL->clock / 72.0 ); + /* make time tables */ + init_timetables(OPL, OPL_ARRATE, OPL_DRRATE); + /* make fnumber -> increment counter table */ + for( fn=0; fn < 1024; fn++) { + OPL->FN_TABLE[fn] = (uint)(OPL->freqbase * fn * FREQ_RATE * (1<<7) / 2); + } + /* LFO freq.table */ + OPL->amsIncr = (int)(OPL->rate ? (double)AMS_ENT * (1 << AMS_SHIFT) / OPL->rate * 3.7 * ((double)OPL->clock/3600000) : 0); + OPL->vibIncr = (int)(OPL->rate ? (double)VIB_ENT * (1 << VIB_SHIFT) / OPL->rate * 6.4 * ((double)OPL->clock/3600000) : 0); +} + +/* ---------- write a OPL registers ---------- */ +void OPLWriteReg(FM_OPL *OPL, int r, int v) { + OPL_CH *CH; + int slot; + uint block_fnum; + + switch(r & 0xe0) { + case 0x00: /* 00-1f:controll */ + switch(r & 0x1f) { + case 0x01: + /* wave selector enable */ + if(OPL->type&OPL_TYPE_WAVESEL) { + OPL->wavesel = v & 0x20; + if(!OPL->wavesel) { + /* preset compatible mode */ + int c; + for(c=0; cmax_ch; c++) { + OPL->P_CH[c].SLOT[SLOT1].wavetable = &SIN_TABLE[0]; + OPL->P_CH[c].SLOT[SLOT2].wavetable = &SIN_TABLE[0]; + } + } + } + return; + case 0x02: /* Timer 1 */ + OPL->T[0] = (256-v) * 4; + break; + case 0x03: /* Timer 2 */ + OPL->T[1] = (256-v) * 16; + return; + case 0x04: /* IRQ clear / mask and Timer enable */ + if(v & 0x80) { /* IRQ flag clear */ + OPL_STATUS_RESET(OPL, 0x7f); + } 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); + /* timer 2 */ + if(OPL->st[1] != st2) { + double interval = st2 ? (double)OPL->T[1] * OPL->TimerBase : 0.0; + OPL->st[1] = st2; + if (OPL->TimerHandler) (OPL->TimerHandler)(OPL->TimerParam + 1, interval); + } + /* timer 1 */ + if(OPL->st[0] != st1) { + double interval = st1 ? (double)OPL->T[0] * OPL->TimerBase : 0.0; + OPL->st[0] = st1; + if (OPL->TimerHandler) (OPL->TimerHandler)(OPL->TimerParam + 0, interval); + } + } + return; + } + break; + case 0x20: /* am,vib,ksr,eg type,mul */ + slot = slot_array[r&0x1f]; + if(slot == -1) + return; + set_mul(OPL,slot,v); + return; + case 0x40: + slot = slot_array[r&0x1f]; + if(slot == -1) + return; + set_ksl_tl(OPL,slot,v); + return; + case 0x60: + slot = slot_array[r&0x1f]; + if(slot == -1) + return; + set_ar_dr(OPL,slot,v); + return; + case 0x80: + slot = slot_array[r&0x1f]; + if(slot == -1) + return; + set_sl_rr(OPL,slot,v); + return; + case 0xa0: + switch(r) { + case 0xbd: + /* amsep,vibdep,r,bd,sd,tom,tc,hh */ + { + uint8 rkey = OPL->rythm ^ v; + OPL->ams_table = &AMS_TABLE[v & 0x80 ? AMS_ENT : 0]; + OPL->vib_table = &VIB_TABLE[v & 0x40 ? VIB_ENT : 0]; + OPL->rythm = v & 0x3f; + if(OPL->rythm & 0x20) { + /* BD key on/off */ + if(rkey & 0x10) { + if(v & 0x10) { + OPL->P_CH[6].op1_out[0] = OPL->P_CH[6].op1_out[1] = 0; + OPL_KEYON(&OPL->P_CH[6].SLOT[SLOT1]); + OPL_KEYON(&OPL->P_CH[6].SLOT[SLOT2]); + } else { + OPL_KEYOFF(&OPL->P_CH[6].SLOT[SLOT1]); + OPL_KEYOFF(&OPL->P_CH[6].SLOT[SLOT2]); + } + } + /* SD key on/off */ + if(rkey & 0x08) { + if(v & 0x08) + OPL_KEYON(&OPL->P_CH[7].SLOT[SLOT2]); + else + OPL_KEYOFF(&OPL->P_CH[7].SLOT[SLOT2]); + }/* TAM key on/off */ + if(rkey & 0x04) { + if(v & 0x04) + OPL_KEYON(&OPL->P_CH[8].SLOT[SLOT1]); + else + OPL_KEYOFF(&OPL->P_CH[8].SLOT[SLOT1]); + } + /* TOP-CY key on/off */ + if(rkey & 0x02) { + if(v & 0x02) + OPL_KEYON(&OPL->P_CH[8].SLOT[SLOT2]); + else + OPL_KEYOFF(&OPL->P_CH[8].SLOT[SLOT2]); + } + /* HH key on/off */ + if(rkey & 0x01) { + if(v & 0x01) + OPL_KEYON(&OPL->P_CH[7].SLOT[SLOT1]); + else + OPL_KEYOFF(&OPL->P_CH[7].SLOT[SLOT1]); + } + } + } + return; + + default: + break; + } + /* keyon,block,fnum */ + if((r & 0x0f) > 8) + return; + CH = &OPL->P_CH[r & 0x0f]; + if(!(r&0x10)) { /* a0-a8 */ + block_fnum = (CH->block_fnum & 0x1f00) | v; + } else { /* b0-b8 */ + int keyon = (v >> 5) & 1; + block_fnum = ((v & 0x1f) << 8) | (CH->block_fnum & 0xff); + if(CH->keyon != keyon) { + if((CH->keyon=keyon)) { + CH->op1_out[0] = CH->op1_out[1] = 0; + OPL_KEYON(&CH->SLOT[SLOT1]); + OPL_KEYON(&CH->SLOT[SLOT2]); + } else { + OPL_KEYOFF(&CH->SLOT[SLOT1]); + OPL_KEYOFF(&CH->SLOT[SLOT2]); + } + } + } + /* update */ + if(CH->block_fnum != block_fnum) { + int blockRv = 7 - (block_fnum >> 10); + int fnum = block_fnum & 0x3ff; + CH->block_fnum = block_fnum; + CH->ksl_base = KSL_TABLE[block_fnum >> 6]; + CH->fc = OPL->FN_TABLE[fnum] >> blockRv; + CH->kcode = CH->block_fnum >> 9; + if((OPL->mode & 0x40) && CH->block_fnum & 0x100) + CH->kcode |=1; + CALC_FCSLOT(CH,&CH->SLOT[SLOT1]); + CALC_FCSLOT(CH,&CH->SLOT[SLOT2]); + } + return; + case 0xc0: + /* FB,C */ + if((r & 0x0f) > 8) + return; + CH = &OPL->P_CH[r&0x0f]; + { + int feedback = (v >> 1) & 7; + CH->FB = feedback ? (8 + 1) - feedback : 0; + CH->CON = v & 1; + set_algorythm(CH); + } + return; + case 0xe0: /* wave type */ + slot = slot_array[r & 0x1f]; + if(slot == -1) + return; + CH = &OPL->P_CH[slot>>1]; + if(OPL->wavesel) { + CH->SLOT[slot&1].wavetable = &SIN_TABLE[(v & 0x03) * SIN_ENT]; + } + return; + } +} + +/* lock/unlock for common table */ +static int OPL_LockTable(void) { + num_lock++; + if(num_lock>1) + return 0; + /* first time */ + cur_chip = NULL; + /* allocate total level table (128kb space) */ + if(!OPLOpenTable()) { + num_lock--; + return -1; + } + return 0; +} + +static void OPL_UnLockTable(void) { + if(num_lock) + num_lock--; + if(num_lock) + return; + /* last time */ + cur_chip = NULL; + OPLCloseTable(); +} + +/*******************************************************************************/ +/* YM3812 local section */ +/*******************************************************************************/ + +/* ---------- update one of chip ----------- */ +void YM3812UpdateOne(FM_OPL *OPL, int16 *buffer, int length, int interleave) { + int i; + int data; + int16 *buf = buffer; + uint amsCnt = OPL->amsCnt; + uint vibCnt = OPL->vibCnt; + uint8 rythm = OPL->rythm & 0x20; + OPL_CH *CH, *R_CH; + + + if((void *)OPL != cur_chip) { + cur_chip = (void *)OPL; + /* channel pointers */ + S_CH = OPL->P_CH; + E_CH = &S_CH[9]; + /* rythm slot */ + SLOT7_1 = &S_CH[7].SLOT[SLOT1]; + SLOT7_2 = &S_CH[7].SLOT[SLOT2]; + SLOT8_1 = &S_CH[8].SLOT[SLOT1]; + SLOT8_2 = &S_CH[8].SLOT[SLOT2]; + /* LFO state */ + amsIncr = OPL->amsIncr; + vibIncr = OPL->vibIncr; + ams_table = OPL->ams_table; + vib_table = OPL->vib_table; + } + R_CH = rythm ? &S_CH[6] : E_CH; + for(i = 0; i < length; i++) { + /* channel A channel B channel C */ + /* LFO */ + ams = ams_table[(amsCnt += amsIncr) >> AMS_SHIFT]; + vib = vib_table[(vibCnt += vibIncr) >> VIB_SHIFT]; + outd[0] = 0; + /* FM part */ + for(CH=S_CH; CH < R_CH; CH++) + OPL_CALC_CH(CH); + /* Rythn part */ + if(rythm) + OPL_CALC_RH(OPL, S_CH); + /* limit check */ + data = CLIP(outd[0], OPL_MINOUT, OPL_MAXOUT); + /* store to sound buffer */ + buf[i << interleave] = data >> OPL_OUTSB; + } + + OPL->amsCnt = amsCnt; + OPL->vibCnt = vibCnt; +} + +/* ---------- reset a chip ---------- */ +void OPLResetChip(FM_OPL *OPL) { + int c,s; + int i; + + /* reset chip */ + OPL->mode = 0; /* normal mode */ + OPL_STATUS_RESET(OPL, 0x7f); + /* reset with register write */ + OPLWriteReg(OPL, 0x01,0); /* wabesel disable */ + OPLWriteReg(OPL, 0x02,0); /* Timer1 */ + OPLWriteReg(OPL, 0x03,0); /* Timer2 */ + OPLWriteReg(OPL, 0x04,0); /* IRQ mask clear */ + for(i = 0xff; i >= 0x20; i--) + OPLWriteReg(OPL,i,0); + /* reset OPerator parameter */ + for(c = 0; c < OPL->max_ch ;c++ ) { + OPL_CH *CH = &OPL->P_CH[c]; + /* OPL->P_CH[c].PAN = OPN_CENTER; */ + for(s = 0; s < 2; s++ ) { + /* wave table */ + CH->SLOT[s].wavetable = &SIN_TABLE[0]; + /* CH->SLOT[s].evm = ENV_MOD_RR; */ + CH->SLOT[s].evc = EG_OFF; + CH->SLOT[s].eve = EG_OFF + 1; + CH->SLOT[s].evs = 0; + } + } +} + +/* ---------- Create a virtual YM3812 ---------- */ +/* 'rate' is sampling rate and 'bufsiz' is the size of the */ +FM_OPL *OPLCreate(int type, int clock, int rate) { + char *ptr; + FM_OPL *OPL; + int state_size; + int max_ch = 9; /* normaly 9 channels */ + + if( OPL_LockTable() == -1) + return NULL; + /* allocate OPL state space */ + state_size = sizeof(FM_OPL); + state_size += sizeof(OPL_CH) * max_ch; + + /* allocate memory block */ + ptr = (char *)calloc(state_size, 1); + if(ptr == NULL) + return NULL; + + /* clear */ + memset(ptr, 0, state_size); + OPL = (FM_OPL *)ptr; ptr += sizeof(FM_OPL); + OPL->P_CH = (OPL_CH *)ptr; ptr += sizeof(OPL_CH) * max_ch; + + /* set channel state pointer */ + OPL->type = type; + OPL->clock = clock; + OPL->rate = rate; + OPL->max_ch = max_ch; + + /* init grobal tables */ + OPL_initalize(OPL); + + /* reset chip */ + OPLResetChip(OPL); + return OPL; +} + +/* ---------- Destroy one of vietual YM3812 ---------- */ +void OPLDestroy(FM_OPL *OPL) { + OPL_UnLockTable(); + free(OPL); +} + +/* ---------- Option handlers ---------- */ +void OPLSetTimerHandler(FM_OPL *OPL, OPL_TIMERHANDLER TimerHandler,int channelOffset) { + OPL->TimerHandler = TimerHandler; + OPL->TimerParam = channelOffset; +} + +void OPLSetIRQHandler(FM_OPL *OPL, OPL_IRQHANDLER IRQHandler, int param) { + OPL->IRQHandler = IRQHandler; + OPL->IRQParam = param; +} + +void OPLSetUpdateHandler(FM_OPL *OPL, OPL_UPDATEHANDLER UpdateHandler,int param) { + OPL->UpdateHandler = UpdateHandler; + OPL->UpdateParam = param; +} + +/* ---------- YM3812 I/O interface ---------- */ +int OPLWrite(FM_OPL *OPL,int a,int v) { + if(!(a & 1)) { /* address port */ + OPL->address = v & 0xff; + } else { /* data port */ + if(OPL->UpdateHandler) + OPL->UpdateHandler(OPL->UpdateParam,0); + OPLWriteReg(OPL, OPL->address,v); + } + return OPL->status >> 7; +} + +unsigned char OPLRead(FM_OPL *OPL,int a) { + if(!(a & 1)) { /* status port */ + return OPL->status & (OPL->statusmask | 0x80); + } + /* data port */ + switch(OPL->address) { + case 0x05: /* KeyBoard IN */ + warning("OPL:read unmapped KEYBOARD port\n"); + return 0; + case 0x19: /* I/O DATA */ + warning("OPL:read unmapped I/O port\n"); + return 0; + case 0x1a: /* PCM-DATA */ + return 0; + default: + break; + } + return 0; +} + +int OPLTimerOver(FM_OPL *OPL, int c) { + if(c) { /* Timer B */ + OPL_STATUS_SET(OPL, 0x20); + } else { /* Timer A */ + OPL_STATUS_SET(OPL, 0x40); + /* CSM mode key,TL controll */ + if(OPL->mode & 0x80) { /* CSM mode total level latch and auto key on */ + int ch; + if(OPL->UpdateHandler) + OPL->UpdateHandler(OPL->UpdateParam,0); + for(ch = 0; ch < 9; ch++) + CSMKeyControll(&OPL->P_CH[ch]); + } + } + /* reload timer */ + if (OPL->TimerHandler) + (OPL->TimerHandler)(OPL->TimerParam + c, (double)OPL->T[c] * OPL->TimerBase); + return OPL->status >> 7; +} + +FM_OPL *makeAdlibOPL(int rate) { + // We need to emulate one YM3812 chip + int env_bits = FMOPL_ENV_BITS_HQ; + int eg_ent = FMOPL_EG_ENT_HQ; +#if defined (_WIN32_WCE) || defined(__SYMBIAN32__) || defined(PALMOS_MODE) || defined(__GP32__) || defined (GP2X) || defined(__MAEMO__) || defined(__DS__) || defined (__MINT__) + if (ConfMan.hasKey("FM_high_quality") && ConfMan.getBool("FM_high_quality")) { + env_bits = FMOPL_ENV_BITS_HQ; + eg_ent = FMOPL_EG_ENT_HQ; + } else if (ConfMan.hasKey("FM_medium_quality") && ConfMan.getBool("FM_medium_quality")) { + env_bits = FMOPL_ENV_BITS_MQ; + eg_ent = FMOPL_EG_ENT_MQ; + } else { + env_bits = FMOPL_ENV_BITS_LQ; + eg_ent = FMOPL_EG_ENT_LQ; + } +#endif + + OPLBuildTables(env_bits, eg_ent); + return OPLCreate(OPL_TYPE_YM3812, 3579545, rate); +} diff --git a/opl/fmopl.h b/opl/fmopl.h new file mode 100644 index 00000000..1c5e5e9f --- /dev/null +++ b/opl/fmopl.h @@ -0,0 +1,173 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL: http://scummvm.svn.sourceforge.net/svnroot/scummvm/scummvm/trunk/sound/fmopl.h $ + * $Id: fmopl.h 38211 2009-02-15 10:07:50Z sev $ + * + * LGPL licensed version of MAMEs fmopl (V0.37a modified) by + * Tatsuyuki Satoh. Included from LGPL'ed AdPlug. + */ + + +#ifndef SOUND_FMOPL_H +#define SOUND_FMOPL_H + +#include "common/scummsys.h" +#include "common/util.h" + +enum { + FMOPL_ENV_BITS_HQ = 16, + FMOPL_ENV_BITS_MQ = 8, + FMOPL_ENV_BITS_LQ = 8, + FMOPL_EG_ENT_HQ = 4096, + FMOPL_EG_ENT_MQ = 1024, + FMOPL_EG_ENT_LQ = 128 +}; + + +typedef void (*OPL_TIMERHANDLER)(int channel,double interval_Sec); +typedef void (*OPL_IRQHANDLER)(int param,int irq); +typedef void (*OPL_UPDATEHANDLER)(int param,int min_interval_us); + +#define OPL_TYPE_WAVESEL 0x01 /* waveform select */ + +/* Saving is necessary for member of the 'R' mark for suspend/resume */ +/* ---------- OPL one of slot ---------- */ +typedef struct fm_opl_slot { + int TL; /* total level :TL << 8 */ + int TLL; /* adjusted now TL */ + uint8 KSR; /* key scale rate :(shift down bit) */ + int *AR; /* attack rate :&AR_TABLE[AR<<2] */ + int *DR; /* decay rate :&DR_TABLE[DR<<2] */ + int SL; /* sustain level :SL_TABLE[SL] */ + int *RR; /* release rate :&DR_TABLE[RR<<2] */ + uint8 ksl; /* keyscale level :(shift down bits) */ + uint8 ksr; /* key scale rate :kcode>>KSR */ + uint mul; /* multiple :ML_TABLE[ML] */ + uint Cnt; /* frequency count */ + uint Incr; /* frequency step */ + + /* envelope generator state */ + uint8 eg_typ;/* envelope type flag */ + uint8 evm; /* envelope phase */ + int evc; /* envelope counter */ + int eve; /* envelope counter end point */ + int evs; /* envelope counter step */ + int evsa; /* envelope step for AR :AR[ksr] */ + int evsd; /* envelope step for DR :DR[ksr] */ + int evsr; /* envelope step for RR :RR[ksr] */ + + /* LFO */ + uint8 ams; /* ams flag */ + uint8 vib; /* vibrate flag */ + /* wave selector */ + int **wavetable; +} OPL_SLOT; + +/* ---------- OPL one of channel ---------- */ +typedef struct fm_opl_channel { + OPL_SLOT SLOT[2]; + uint8 CON; /* connection type */ + uint8 FB; /* feed back :(shift down bit)*/ + int *connect1; /* slot1 output pointer */ + int *connect2; /* slot2 output pointer */ + int op1_out[2]; /* slot1 output for selfeedback */ + + /* phase generator state */ + uint block_fnum; /* block+fnum */ + uint8 kcode; /* key code : KeyScaleCode */ + uint fc; /* Freq. Increment base */ + uint ksl_base; /* KeyScaleLevel Base step */ + uint8 keyon; /* key on/off flag */ +} OPL_CH; + +/* OPL state */ +typedef struct fm_opl_f { + uint8 type; /* chip type */ + int clock; /* master clock (Hz) */ + int rate; /* sampling rate (Hz) */ + double freqbase; /* frequency base */ + double TimerBase; /* Timer base time (==sampling time) */ + uint8 address; /* address register */ + uint8 status; /* status flag */ + uint8 statusmask; /* status mask */ + uint mode; /* Reg.08 : CSM , notesel,etc. */ + + /* Timer */ + int T[2]; /* timer counter */ + uint8 st[2]; /* timer enable */ + + /* FM channel slots */ + OPL_CH *P_CH; /* pointer of CH */ + int max_ch; /* maximum channel */ + + /* Rythm sention */ + uint8 rythm; /* Rythm mode , key flag */ + + /* time tables */ + int AR_TABLE[76]; /* atttack rate tables */ + int DR_TABLE[76]; /* decay rate tables */ + uint FN_TABLE[1024];/* fnumber -> increment counter */ + + /* LFO */ + int *ams_table; + int *vib_table; + int amsCnt; + int amsIncr; + int vibCnt; + int vibIncr; + + /* wave selector enable flag */ + uint8 wavesel; + + /* external event callback handler */ + OPL_TIMERHANDLER TimerHandler; /* TIMER handler */ + int TimerParam; /* TIMER parameter */ + OPL_IRQHANDLER IRQHandler; /* IRQ handler */ + int IRQParam; /* IRQ parameter */ + OPL_UPDATEHANDLER UpdateHandler; /* stream update handler */ + int UpdateParam; /* stream update parameter */ + + Common::RandomSource rnd; +} FM_OPL; + +/* ---------- Generic interface section ---------- */ +#define OPL_TYPE_YM3526 (0) +#define OPL_TYPE_YM3812 (OPL_TYPE_WAVESEL) + +void OPLBuildTables(int ENV_BITS_PARAM, int EG_ENT_PARAM); + +FM_OPL *OPLCreate(int type, int clock, int rate); +void OPLDestroy(FM_OPL *OPL); +void OPLSetTimerHandler(FM_OPL *OPL, OPL_TIMERHANDLER TimerHandler, int channelOffset); +void OPLSetIRQHandler(FM_OPL *OPL, OPL_IRQHANDLER IRQHandler, int param); +void OPLSetUpdateHandler(FM_OPL *OPL, OPL_UPDATEHANDLER UpdateHandler, int param); + +void OPLResetChip(FM_OPL *OPL); +int OPLWrite(FM_OPL *OPL, int a, int v); +unsigned char OPLRead(FM_OPL *OPL, int a); +int OPLTimerOver(FM_OPL *OPL, int c); +void OPLWriteReg(FM_OPL *OPL, int r, int v); +void YM3812UpdateOne(FM_OPL *OPL, int16 *buffer, int length, int interleave = 0); + +// Factory method +FM_OPL *makeAdlibOPL(int rate); + +#endif -- cgit v1.2.3 From 4422bbf9815ec42402d0235845a8fd33afd792b2 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Wed, 4 Mar 2009 21:18:18 +0000 Subject: Fix up fmopl code to remove ScummVM dependencies and compile as C. Add to build. Subversion-branch: /branches/opl-branch Subversion-revision: 1446 --- opl/Makefile.am | 8 +++++ opl/fmopl.c | 106 +++++++++++++++++++------------------------------------- opl/fmopl.h | 70 +++++++++++++++++-------------------- 3 files changed, 75 insertions(+), 109 deletions(-) create mode 100644 opl/Makefile.am (limited to 'opl') diff --git a/opl/Makefile.am b/opl/Makefile.am new file mode 100644 index 00000000..8fd2303e --- /dev/null +++ b/opl/Makefile.am @@ -0,0 +1,8 @@ + +AM_CFLAGS=@SDLMIXER_CFLAGS@ + +noinst_LIBRARIES=libopl.a + +libopl_a_SOURCES = \ + fmopl.c + diff --git a/opl/fmopl.c b/opl/fmopl.c index 5fda6e44..6713ecc1 100644 --- a/opl/fmopl.c +++ b/opl/fmopl.c @@ -1,4 +1,4 @@ -/* ScummVM - Graphic Adventure Engine +/* This file is derived from fmopl.cpp from ScummVM. * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT @@ -18,9 +18,6 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * - * $URL: http://scummvm.svn.sourceforge.net/svnroot/scummvm/scummvm/trunk/sound/fmopl.cpp $ - * $Id: fmopl.cpp 38211 2009-02-15 10:07:50Z sev $ - * * LGPL licensed version of MAMEs fmopl (V0.37a modified) by * Tatsuyuki Satoh. Included from LGPL'ed AdPlug. */ @@ -31,11 +28,13 @@ #include #include -#include "sound/fmopl.h" +#include "fmopl.h" + +#define PI 3.1415926539 -#if defined (_WIN32_WCE) || defined (__SYMBIAN32__) || defined(PALMOS_MODE) || defined(__GP32__) || defined(GP2X) || defined (__MAEMO__) || defined(__DS__) || defined (__MINT__) -#include "common/config-manager.h" -#endif +#define CLIP(value, min, max) \ + ( (value) < (min) ? (min) : \ + (value) > (max) ? (max) : (value) ) /* -------------------- preliminary define section --------------------- */ /* attack/decay rate time rate */ @@ -57,12 +56,7 @@ /* sinwave entries */ /* used static memory = SIN_ENT * 4 (byte) */ -#ifdef __DS__ -#include "dsmain.h" -#define SIN_ENT_SHIFT 8 -#else #define SIN_ENT_SHIFT 11 -#endif #define SIN_ENT (1<status |= flag; if(!(OPL->status & 0x80)) { @@ -261,7 +256,7 @@ inline void OPL_STATUS_SET(FM_OPL *OPL, int flag) { } /* status reset and IRQ handling */ -inline void OPL_STATUS_RESET(FM_OPL *OPL, int flag) { +static inline void OPL_STATUS_RESET(FM_OPL *OPL, int flag) { /* reset status flag */ OPL->status &= ~flag; if((OPL->status & 0x80)) { @@ -274,7 +269,7 @@ inline void OPL_STATUS_RESET(FM_OPL *OPL, int flag) { } /* IRQ mask set */ -inline void OPL_STATUSMASK_SET(FM_OPL *OPL, int flag) { +static inline void OPL_STATUSMASK_SET(FM_OPL *OPL, int flag) { OPL->statusmask = flag; /* IRQ handling check */ OPL_STATUS_SET(OPL,0); @@ -282,7 +277,7 @@ inline void OPL_STATUSMASK_SET(FM_OPL *OPL, int flag) { } /* ----- key on ----- */ -inline void OPL_KEYON(OPL_SLOT *SLOT) { +static inline void OPL_KEYON(OPL_SLOT *SLOT) { /* sin wave restart */ SLOT->Cnt = 0; /* set attack */ @@ -293,7 +288,7 @@ inline void OPL_KEYON(OPL_SLOT *SLOT) { } /* ----- key off ----- */ -inline void OPL_KEYOFF(OPL_SLOT *SLOT) { +static inline void OPL_KEYOFF(OPL_SLOT *SLOT) { if( SLOT->evm > ENV_MOD_RR) { /* set envelope counter from envleope output */ @@ -337,7 +332,7 @@ inline void OPL_KEYOFF(OPL_SLOT *SLOT) { /* ---------- calcrate Envelope Generator & Phase Generator ---------- */ /* return : envelope output */ -inline uint OPL_CALC_SLOT(OPL_SLOT *SLOT) { +static inline uint OPL_CALC_SLOT(OPL_SLOT *SLOT) { /* calcrate envelope generator */ if((SLOT->evc += SLOT->evs) >= SLOT->eve) { switch( SLOT->evm ) { @@ -377,7 +372,7 @@ static void set_algorythm(OPL_CH *CH) { } /* ---------- frequency counter for operater update ---------- */ -inline void CALC_FCSLOT(OPL_CH *CH, OPL_SLOT *SLOT) { +static inline void CALC_FCSLOT(OPL_CH *CH, OPL_SLOT *SLOT) { int ksr; /* frequency step counter */ @@ -395,7 +390,7 @@ inline void CALC_FCSLOT(OPL_CH *CH, OPL_SLOT *SLOT) { } /* set multi,am,vib,EG-TYP,KSR,mul */ -inline void set_mul(FM_OPL *OPL, int slot, int v) { +static inline void set_mul(FM_OPL *OPL, int slot, int v) { OPL_CH *CH = &OPL->P_CH[slot>>1]; OPL_SLOT *SLOT = &CH->SLOT[slot & 1]; @@ -408,7 +403,7 @@ inline void set_mul(FM_OPL *OPL, int slot, int v) { } /* set ksl & tl */ -inline void set_ksl_tl(FM_OPL *OPL, int slot, int v) { +static inline void set_ksl_tl(FM_OPL *OPL, int slot, int v) { OPL_CH *CH = &OPL->P_CH[slot>>1]; OPL_SLOT *SLOT = &CH->SLOT[slot & 1]; int ksl = v >> 6; /* 0 / 1.5 / 3 / 6 db/OCT */ @@ -422,7 +417,7 @@ inline void set_ksl_tl(FM_OPL *OPL, int slot, int v) { } /* set attack rate & decay rate */ -inline void set_ar_dr(FM_OPL *OPL, int slot, int v) { +static inline void set_ar_dr(FM_OPL *OPL, int slot, int v) { OPL_CH *CH = &OPL->P_CH[slot>>1]; OPL_SLOT *SLOT = &CH->SLOT[slot & 1]; int ar = v >> 4; @@ -440,7 +435,7 @@ inline void set_ar_dr(FM_OPL *OPL, int slot, int v) { } /* set sustain level & release rate */ -inline void set_sl_rr(FM_OPL *OPL, int slot, int v) { +static inline void set_sl_rr(FM_OPL *OPL, int slot, int v) { OPL_CH *CH = &OPL->P_CH[slot>>1]; OPL_SLOT *SLOT = &CH->SLOT[slot & 1]; int sl = v >> 4; @@ -459,7 +454,7 @@ inline void set_sl_rr(FM_OPL *OPL, int slot, int v) { #define OP_OUT(slot,env,con) slot->wavetable[((slot->Cnt + con)>>(24-SIN_ENT_SHIFT)) & (SIN_ENT-1)][env] /* ---------- calcrate one of channel ---------- */ -inline void OPL_CALC_CH(OPL_CH *CH) { +static inline void OPL_CALC_CH(OPL_CH *CH) { uint env_out; OPL_SLOT *SLOT; @@ -501,13 +496,13 @@ inline void OPL_CALC_CH(OPL_CH *CH) { /* ---------- calcrate rythm block ---------- */ #define WHITE_NOISE_db 6.0 -inline void OPL_CALC_RH(FM_OPL *OPL, OPL_CH *CH) { +static inline void OPL_CALC_RH(FM_OPL *OPL, OPL_CH *CH) { uint env_tam, env_sd, env_top, env_hh; // This code used to do int(OPL->rnd.getRandomBit() * (WHITE_NOISE_db / EG_STEP)), // but EG_STEP = 96.0/EG_ENT, and WHITE_NOISE_db=6.0. So, that's equivalent to // int(OPL->rnd.getRandomBit() * EG_ENT/16). We know that EG_ENT is 4096, or 1024, // or 128, so we can safely avoid any FP ops. - int whitenoise = OPL->rnd.getRandomBit() * (EG_ENT>>4); + int whitenoise = (rand() & 1) * (EG_ENT>>4); int tone8; @@ -625,13 +620,6 @@ static int OPLOpenTable(void) { int i,j; double pom; -#ifdef __DS__ - DS::fastRamReset(); - - TL_TABLE = (int *) DS::fastRamAlloc(TL_MAX * 2 * sizeof(int *)); - SIN_TABLE = (int **) DS::fastRamAlloc(SIN_ENT * 4 * sizeof(int *)); -#else - /* allocate dynamic tables */ if((TL_TABLE = (int *)malloc(TL_MAX * 2 * sizeof(int))) == NULL) return 0; @@ -640,7 +628,6 @@ static int OPLOpenTable(void) { free(TL_TABLE); return 0; } -#endif if((AMS_TABLE = (int *)malloc(AMS_ENT * 2 * sizeof(int))) == NULL) { free(TL_TABLE); @@ -671,7 +658,7 @@ static int OPLOpenTable(void) { for (s = 1;s <= SIN_ENT / 4; s++) { pom = sin(2 * PI * s / SIN_ENT); /* sin */ pom = 20 * log10(1 / pom); /* decibel */ - j = int(pom / EG_STEP); /* TL_TABLE steps */ + j = (int) (pom / EG_STEP); /* TL_TABLE steps */ /* degree 0 - 90 , degree 180 - 90 : plus section */ SIN_TABLE[ s] = SIN_TABLE[SIN_ENT / 2 - s] = &TL_TABLE[j]; @@ -723,7 +710,7 @@ static void OPLCloseTable(void) { } /* CSM Key Controll */ -inline void CSMKeyControll(OPL_CH *CH) { +static inline void CSMKeyControll(OPL_CH *CH) { OPL_SLOT *slot1 = &CH->SLOT[SLOT1]; OPL_SLOT *slot2 = &CH->SLOT[SLOT2]; /* all key off */ @@ -790,8 +777,8 @@ void OPLWriteReg(FM_OPL *OPL, int r, int v) { if(v & 0x80) { /* IRQ flag clear */ OPL_STATUS_RESET(OPL, 0x7f); } else { /* set IRQ mask ,timer enable*/ - uint8 st1 = v & 1; - uint8 st2 = (v >> 1) & 1; + uint8_t st1 = v & 1; + uint8_t 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); @@ -840,7 +827,7 @@ void OPLWriteReg(FM_OPL *OPL, int r, int v) { case 0xbd: /* amsep,vibdep,r,bd,sd,tom,tc,hh */ { - uint8 rkey = OPL->rythm ^ v; + uint8_t rkey = OPL->rythm ^ v; OPL->ams_table = &AMS_TABLE[v & 0x80 ? AMS_ENT : 0]; OPL->vib_table = &VIB_TABLE[v & 0x40 ? VIB_ENT : 0]; OPL->rythm = v & 0x3f; @@ -978,13 +965,13 @@ static void OPL_UnLockTable(void) { /*******************************************************************************/ /* ---------- update one of chip ----------- */ -void YM3812UpdateOne(FM_OPL *OPL, int16 *buffer, int length, int interleave) { +void YM3812UpdateOne(FM_OPL *OPL, int16_t *buffer, int length, int interleave) { int i; int data; - int16 *buf = buffer; + int16_t *buf = buffer; uint amsCnt = OPL->amsCnt; uint vibCnt = OPL->vibCnt; - uint8 rythm = OPL->rythm & 0x20; + uint8_t rythm = OPL->rythm & 0x20; OPL_CH *CH, *R_CH; @@ -1133,19 +1120,7 @@ unsigned char OPLRead(FM_OPL *OPL,int a) { if(!(a & 1)) { /* status port */ return OPL->status & (OPL->statusmask | 0x80); } - /* data port */ - switch(OPL->address) { - case 0x05: /* KeyBoard IN */ - warning("OPL:read unmapped KEYBOARD port\n"); - return 0; - case 0x19: /* I/O DATA */ - warning("OPL:read unmapped I/O port\n"); - return 0; - case 0x1a: /* PCM-DATA */ - return 0; - default: - break; - } + return 0; } @@ -1173,19 +1148,8 @@ FM_OPL *makeAdlibOPL(int rate) { // We need to emulate one YM3812 chip int env_bits = FMOPL_ENV_BITS_HQ; int eg_ent = FMOPL_EG_ENT_HQ; -#if defined (_WIN32_WCE) || defined(__SYMBIAN32__) || defined(PALMOS_MODE) || defined(__GP32__) || defined (GP2X) || defined(__MAEMO__) || defined(__DS__) || defined (__MINT__) - if (ConfMan.hasKey("FM_high_quality") && ConfMan.getBool("FM_high_quality")) { - env_bits = FMOPL_ENV_BITS_HQ; - eg_ent = FMOPL_EG_ENT_HQ; - } else if (ConfMan.hasKey("FM_medium_quality") && ConfMan.getBool("FM_medium_quality")) { - env_bits = FMOPL_ENV_BITS_MQ; - eg_ent = FMOPL_EG_ENT_MQ; - } else { - env_bits = FMOPL_ENV_BITS_LQ; - eg_ent = FMOPL_EG_ENT_LQ; - } -#endif OPLBuildTables(env_bits, eg_ent); return OPLCreate(OPL_TYPE_YM3812, 3579545, rate); } + diff --git a/opl/fmopl.h b/opl/fmopl.h index 1c5e5e9f..2bbe8363 100644 --- a/opl/fmopl.h +++ b/opl/fmopl.h @@ -1,4 +1,4 @@ -/* ScummVM - Graphic Adventure Engine +/* This file is derived from fmopl.h from ScummVM. * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT @@ -18,19 +18,15 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * - * $URL: http://scummvm.svn.sourceforge.net/svnroot/scummvm/scummvm/trunk/sound/fmopl.h $ - * $Id: fmopl.h 38211 2009-02-15 10:07:50Z sev $ - * * LGPL licensed version of MAMEs fmopl (V0.37a modified) by * Tatsuyuki Satoh. Included from LGPL'ed AdPlug. */ -#ifndef SOUND_FMOPL_H -#define SOUND_FMOPL_H +#ifndef OPL_FMOPL_H +#define OPL_FMOPL_H -#include "common/scummsys.h" -#include "common/util.h" +#include "inttypes.h" enum { FMOPL_ENV_BITS_HQ = 16, @@ -41,7 +37,6 @@ enum { FMOPL_EG_ENT_LQ = 128 }; - typedef void (*OPL_TIMERHANDLER)(int channel,double interval_Sec); typedef void (*OPL_IRQHANDLER)(int param,int irq); typedef void (*OPL_UPDATEHANDLER)(int param,int min_interval_us); @@ -53,20 +48,20 @@ typedef void (*OPL_UPDATEHANDLER)(int param,int min_interval_us); typedef struct fm_opl_slot { int TL; /* total level :TL << 8 */ int TLL; /* adjusted now TL */ - uint8 KSR; /* key scale rate :(shift down bit) */ + uint8_t KSR; /* key scale rate :(shift down bit) */ int *AR; /* attack rate :&AR_TABLE[AR<<2] */ int *DR; /* decay rate :&DR_TABLE[DR<<2] */ int SL; /* sustain level :SL_TABLE[SL] */ int *RR; /* release rate :&DR_TABLE[RR<<2] */ - uint8 ksl; /* keyscale level :(shift down bits) */ - uint8 ksr; /* key scale rate :kcode>>KSR */ - uint mul; /* multiple :ML_TABLE[ML] */ - uint Cnt; /* frequency count */ - uint Incr; /* frequency step */ + uint8_t ksl; /* keyscale level :(shift down bits) */ + uint8_t ksr; /* key scale rate :kcode>>KSR */ + unsigned int mul; /* multiple :ML_TABLE[ML] */ + unsigned int Cnt; /* frequency count */ + unsigned int Incr; /* frequency step */ /* envelope generator state */ - uint8 eg_typ;/* envelope type flag */ - uint8 evm; /* envelope phase */ + uint8_t eg_typ;/* envelope type flag */ + uint8_t evm; /* envelope phase */ int evc; /* envelope counter */ int eve; /* envelope counter end point */ int evs; /* envelope counter step */ @@ -75,8 +70,8 @@ typedef struct fm_opl_slot { int evsr; /* envelope step for RR :RR[ksr] */ /* LFO */ - uint8 ams; /* ams flag */ - uint8 vib; /* vibrate flag */ + uint8_t ams; /* ams flag */ + uint8_t vib; /* vibrate flag */ /* wave selector */ int **wavetable; } OPL_SLOT; @@ -84,47 +79,47 @@ typedef struct fm_opl_slot { /* ---------- OPL one of channel ---------- */ typedef struct fm_opl_channel { OPL_SLOT SLOT[2]; - uint8 CON; /* connection type */ - uint8 FB; /* feed back :(shift down bit)*/ + uint8_t CON; /* connection type */ + uint8_t FB; /* feed back :(shift down bit)*/ int *connect1; /* slot1 output pointer */ int *connect2; /* slot2 output pointer */ int op1_out[2]; /* slot1 output for selfeedback */ /* phase generator state */ - uint block_fnum; /* block+fnum */ - uint8 kcode; /* key code : KeyScaleCode */ - uint fc; /* Freq. Increment base */ - uint ksl_base; /* KeyScaleLevel Base step */ - uint8 keyon; /* key on/off flag */ + unsigned int block_fnum; /* block+fnum */ + uint8_t kcode; /* key code : KeyScaleCode */ + unsigned int fc; /* Freq. Increment base */ + unsigned int ksl_base; /* KeyScaleLevel Base step */ + uint8_t keyon; /* key on/off flag */ } OPL_CH; /* OPL state */ typedef struct fm_opl_f { - uint8 type; /* chip type */ + uint8_t type; /* chip type */ int clock; /* master clock (Hz) */ int rate; /* sampling rate (Hz) */ double freqbase; /* frequency base */ double TimerBase; /* Timer base time (==sampling time) */ - uint8 address; /* address register */ - uint8 status; /* status flag */ - uint8 statusmask; /* status mask */ - uint mode; /* Reg.08 : CSM , notesel,etc. */ + uint8_t address; /* address register */ + uint8_t status; /* status flag */ + uint8_t statusmask; /* status mask */ + unsigned int mode; /* Reg.08 : CSM , notesel,etc. */ /* Timer */ int T[2]; /* timer counter */ - uint8 st[2]; /* timer enable */ + uint8_t st[2]; /* timer enable */ /* FM channel slots */ OPL_CH *P_CH; /* pointer of CH */ int max_ch; /* maximum channel */ /* Rythm sention */ - uint8 rythm; /* Rythm mode , key flag */ + uint8_t rythm; /* Rythm mode , key flag */ /* time tables */ int AR_TABLE[76]; /* atttack rate tables */ int DR_TABLE[76]; /* decay rate tables */ - uint FN_TABLE[1024];/* fnumber -> increment counter */ + unsigned int FN_TABLE[1024];/* fnumber -> increment counter */ /* LFO */ int *ams_table; @@ -135,7 +130,7 @@ typedef struct fm_opl_f { int vibIncr; /* wave selector enable flag */ - uint8 wavesel; + uint8_t wavesel; /* external event callback handler */ OPL_TIMERHANDLER TimerHandler; /* TIMER handler */ @@ -144,8 +139,6 @@ typedef struct fm_opl_f { int IRQParam; /* IRQ parameter */ OPL_UPDATEHANDLER UpdateHandler; /* stream update handler */ int UpdateParam; /* stream update parameter */ - - Common::RandomSource rnd; } FM_OPL; /* ---------- Generic interface section ---------- */ @@ -165,9 +158,10 @@ int OPLWrite(FM_OPL *OPL, int a, int v); unsigned char OPLRead(FM_OPL *OPL, int a); int OPLTimerOver(FM_OPL *OPL, int c); void OPLWriteReg(FM_OPL *OPL, int r, int v); -void YM3812UpdateOne(FM_OPL *OPL, int16 *buffer, int length, int interleave = 0); +void YM3812UpdateOne(FM_OPL *OPL, int16_t *buffer, int length, int interleave); // Factory method FM_OPL *makeAdlibOPL(int rate); #endif + -- cgit v1.2.3 From 6f41d1d24f4fccba4aa77be15c60a0b173b2dfe6 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Mon, 9 Mar 2009 23:59:43 +0000 Subject: Make global variables static. Replace uint with uint32_t. Subversion-branch: /branches/opl-branch Subversion-revision: 1454 --- opl/fmopl.c | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) (limited to 'opl') diff --git a/opl/fmopl.c b/opl/fmopl.c index 6713ecc1..1671244e 100644 --- a/opl/fmopl.c +++ b/opl/fmopl.c @@ -61,16 +61,16 @@ /* output level entries (envelope,sinwave) */ /* envelope counter lower bits */ -int ENV_BITS; +static int ENV_BITS; /* envelope output entries */ -int EG_ENT; +static int EG_ENT; /* used dynamic memory = EG_ENT*4*4(byte)or EG_ENT*6*4(byte) */ /* used static memory = EG_ENT*4 (byte) */ -int EG_OFF; /* OFF */ -int EG_DED; -int EG_DST; /* DECAY START */ -int EG_AED; +static int EG_OFF; /* OFF */ +static int EG_DED; +static int EG_DST; /* DECAY START */ +static int EG_AED; #define EG_AST 0 /* ATTACK START */ #define EG_STEP (96.0/EG_ENT) /* OPL is 0.1875 dB step */ @@ -103,7 +103,7 @@ static const int slot_array[32] = { -1,-1,-1,-1,-1,-1,-1,-1 }; -static uint KSL_TABLE[8 * 16]; +static uint32_t KSL_TABLE[8 * 16]; static const double KSL_TABLE_SEED[8 * 16] = { /* OCT 0 */ @@ -153,7 +153,7 @@ static const double KSL_TABLE_SEED[8 * 16] = { static int SL_TABLE[16]; -static const uint SL_TABLE_SEED[16] = { +static const uint32_t SL_TABLE_SEED[16] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 31 }; @@ -179,7 +179,7 @@ static int *ENV_CURVE; /* multiple table */ #define ML(a) (int)(a * 2) -static const uint MUL_TABLE[16]= { +static const uint32_t MUL_TABLE[16]= { /* 1/2, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15 */ ML(0.50), ML(1.00), ML(2.00), ML(3.00), ML(4.00), ML(5.00), ML(6.00), ML(7.00), ML(8.00), ML(9.00), ML(10.00), ML(10.00),ML(12.00),ML(12.00),ML(15.00),ML(15.00) @@ -215,7 +215,7 @@ static int feedback2; /* connect for SLOT 2 */ /* --------------------- rebuild tables ------------------- */ #define ARRAYSIZE(x) (sizeof(x) / sizeof(*x)) -#define SC_KSL(mydb) ((uint) (mydb / (EG_STEP / 2))) +#define SC_KSL(mydb) ((uint32_t) (mydb / (EG_STEP / 2))) #define SC_SL(db) (int)(db * ((3 / EG_STEP) * (1 << ENV_BITS))) + EG_DST void OPLBuildTables(int ENV_BITS_PARAM, int EG_ENT_PARAM) { @@ -332,7 +332,7 @@ static inline void OPL_KEYOFF(OPL_SLOT *SLOT) { /* ---------- calcrate Envelope Generator & Phase Generator ---------- */ /* return : envelope output */ -static inline uint OPL_CALC_SLOT(OPL_SLOT *SLOT) { +static inline uint32_t OPL_CALC_SLOT(OPL_SLOT *SLOT) { /* calcrate envelope generator */ if((SLOT->evc += SLOT->evs) >= SLOT->eve) { switch( SLOT->evm ) { @@ -455,14 +455,14 @@ static inline void set_sl_rr(FM_OPL *OPL, int slot, int v) { #define OP_OUT(slot,env,con) slot->wavetable[((slot->Cnt + con)>>(24-SIN_ENT_SHIFT)) & (SIN_ENT-1)][env] /* ---------- calcrate one of channel ---------- */ static inline void OPL_CALC_CH(OPL_CH *CH) { - uint env_out; + uint32_t env_out; OPL_SLOT *SLOT; feedback2 = 0; /* SLOT 1 */ SLOT = &CH->SLOT[SLOT1]; env_out=OPL_CALC_SLOT(SLOT); - if(env_out < (uint)(EG_ENT - 1)) { + if(env_out < (uint32_t)(EG_ENT - 1)) { /* PG */ if(SLOT->vib) SLOT->Cnt += (SLOT->Incr * vib) >> VIB_RATE_SHIFT; @@ -483,7 +483,7 @@ static inline void OPL_CALC_CH(OPL_CH *CH) { /* SLOT 2 */ SLOT = &CH->SLOT[SLOT2]; env_out=OPL_CALC_SLOT(SLOT); - if(env_out < (uint)(EG_ENT - 1)) { + if(env_out < (uint32_t)(EG_ENT - 1)) { /* PG */ if(SLOT->vib) SLOT->Cnt += (SLOT->Incr * vib) >> VIB_RATE_SHIFT; @@ -497,7 +497,7 @@ static inline void OPL_CALC_CH(OPL_CH *CH) { /* ---------- calcrate rythm block ---------- */ #define WHITE_NOISE_db 6.0 static inline void OPL_CALC_RH(FM_OPL *OPL, OPL_CH *CH) { - uint env_tam, env_sd, env_top, env_hh; + uint32_t env_tam, env_sd, env_top, env_hh; // This code used to do int(OPL->rnd.getRandomBit() * (WHITE_NOISE_db / EG_STEP)), // but EG_STEP = 96.0/EG_ENT, and WHITE_NOISE_db=6.0. So, that's equivalent to // int(OPL->rnd.getRandomBit() * EG_ENT/16). We know that EG_ENT is 4096, or 1024, @@ -577,16 +577,16 @@ static inline void OPL_CALC_RH(FM_OPL *OPL, OPL_CH *CH) { tone8 = OP_OUT(SLOT8_2,whitenoise,0 ); /* SD */ - if(env_sd < (uint)(EG_ENT - 1)) + if(env_sd < (uint32_t)(EG_ENT - 1)) outd[0] += OP_OUT(SLOT7_1, env_sd, 0) * 8; /* TAM */ - if(env_tam < (uint)(EG_ENT - 1)) + if(env_tam < (uint32_t)(EG_ENT - 1)) outd[0] += OP_OUT(SLOT8_1, env_tam, 0) * 2; /* TOP-CY */ - if(env_top < (uint)(EG_ENT - 1)) + if(env_top < (uint32_t)(EG_ENT - 1)) outd[0] += OP_OUT(SLOT7_2, env_top, tone8) * 2; /* HH */ - if(env_hh < (uint)(EG_ENT-1)) + if(env_hh < (uint32_t)(EG_ENT-1)) outd[0] += OP_OUT(SLOT7_2, env_hh, tone8) * 2; } @@ -737,7 +737,7 @@ static void OPL_initalize(FM_OPL *OPL) { init_timetables(OPL, OPL_ARRATE, OPL_DRRATE); /* make fnumber -> increment counter table */ for( fn=0; fn < 1024; fn++) { - OPL->FN_TABLE[fn] = (uint)(OPL->freqbase * fn * FREQ_RATE * (1<<7) / 2); + OPL->FN_TABLE[fn] = (uint32_t)(OPL->freqbase * fn * FREQ_RATE * (1<<7) / 2); } /* LFO freq.table */ OPL->amsIncr = (int)(OPL->rate ? (double)AMS_ENT * (1 << AMS_SHIFT) / OPL->rate * 3.7 * ((double)OPL->clock/3600000) : 0); @@ -748,7 +748,7 @@ static void OPL_initalize(FM_OPL *OPL) { void OPLWriteReg(FM_OPL *OPL, int r, int v) { OPL_CH *CH; int slot; - uint block_fnum; + uint32_t block_fnum; switch(r & 0xe0) { case 0x00: /* 00-1f:controll */ @@ -969,8 +969,8 @@ void YM3812UpdateOne(FM_OPL *OPL, int16_t *buffer, int length, int interleave) { int i; int data; int16_t *buf = buffer; - uint amsCnt = OPL->amsCnt; - uint vibCnt = OPL->vibCnt; + uint32_t amsCnt = OPL->amsCnt; + uint32_t vibCnt = OPL->vibCnt; uint8_t rythm = OPL->rythm & 0x20; OPL_CH *CH, *R_CH; -- cgit v1.2.3 From 9f8ab35852dfd68eb6cc92ab84b79a90995dc027 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Tue, 10 Mar 2009 00:02:41 +0000 Subject: Add opl library main header and stub functions. Subversion-branch: /branches/opl-branch Subversion-revision: 1455 --- opl/Makefile.am | 1 + opl/opl.c | 49 ++++++++++++++++++++++++++++++++++++++ opl/opl.h | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 123 insertions(+) create mode 100644 opl/opl.c create mode 100644 opl/opl.h (limited to 'opl') diff --git a/opl/Makefile.am b/opl/Makefile.am index 8fd2303e..65f8b2de 100644 --- a/opl/Makefile.am +++ b/opl/Makefile.am @@ -4,5 +4,6 @@ AM_CFLAGS=@SDLMIXER_CFLAGS@ noinst_LIBRARIES=libopl.a libopl_a_SOURCES = \ + opl.c opl.h \ fmopl.c diff --git a/opl/opl.c b/opl/opl.c new file mode 100644 index 00000000..d3dfc23b --- /dev/null +++ b/opl/opl.c @@ -0,0 +1,49 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 2009 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +// DESCRIPTION: +// OPL interface. +// +//----------------------------------------------------------------------------- + +#include "opl.h" + +int OPL_Init(unsigned int port_base) +{ + // TODO + return 1; +} + +void OPL_Shutdown(void) +{ + // TODO +} + +void OPL_WritePort(opl_port_t port, unsigned int value) +{ + // TODO +} + +unsigned int OPL_ReadPort(opl_port_t port) +{ + // TODO + return 0; +} + diff --git a/opl/opl.h b/opl/opl.h new file mode 100644 index 00000000..62b507f7 --- /dev/null +++ b/opl/opl.h @@ -0,0 +1,73 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 2009 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +// DESCRIPTION: +// OPL interface. +// +//----------------------------------------------------------------------------- + + +#ifndef OPL_OPL_H +#define OPL_OPL_H + +typedef enum +{ + OPL_REGISTER_PORT = 0, + OPL_DATA_PORT = 1 +} opl_port_t; + +#define OPL_NUM_OPERATORS 21 +#define OPL_NUM_VOICES 9 + +#define OPL_REG_TIMER1 0x02 +#define OPL_REG_TIMER2 0x03 +#define OPL_REG_TIMER_CTRL 0x04 +#define OPL_REG_FM_MODE 0x08 + +// Operator registers (21 of each): + +#define OPL_REGS_TREMOLO 0x20 +#define OPL_REGS_LEVEL 0x40 +#define OPL_REGS_ATTACK 0x60 +#define OPL_REGS_SUSTAIN 0x80 + +// Voice registers (9 of each): + +#define OPL_REGS_FREQ_1 0xA0 +#define OPL_REGS_FREQ_2 0xB0 + +// Initialise the OPL subsystem. + +int OPL_Init(unsigned int port_base); + +// Shut down the OPL subsystem. + +void OPL_Shutdown(void); + +// Write to one of the OPL I/O ports: + +void OPL_WritePort(opl_port_t port, unsigned int value); + +// Read from one of the OPL I/O ports: + +unsigned int OPL_ReadPort(opl_port_t port); + +#endif + -- cgit v1.2.3 From ca7f823d48bb78eda9048750909cdb315836125c Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Tue, 10 Mar 2009 21:21:16 +0000 Subject: Initialise OPL registers on startup, initialise voices. Subversion-branch: /branches/opl-branch Subversion-revision: 1457 --- opl/opl.h | 3 +++ 1 file changed, 3 insertions(+) (limited to 'opl') diff --git a/opl/opl.h b/opl/opl.h index 62b507f7..352e6696 100644 --- a/opl/opl.h +++ b/opl/opl.h @@ -36,6 +36,7 @@ typedef enum #define OPL_NUM_OPERATORS 21 #define OPL_NUM_VOICES 9 +#define OPL_REG_WAVEFORM_ENABLE 0x01 #define OPL_REG_TIMER1 0x02 #define OPL_REG_TIMER2 0x03 #define OPL_REG_TIMER_CTRL 0x04 @@ -47,11 +48,13 @@ typedef enum #define OPL_REGS_LEVEL 0x40 #define OPL_REGS_ATTACK 0x60 #define OPL_REGS_SUSTAIN 0x80 +#define OPL_REGS_WAVEFORM 0xE0 // Voice registers (9 of each): #define OPL_REGS_FREQ_1 0xA0 #define OPL_REGS_FREQ_2 0xB0 +#define OPL_REGS_FEEDBACK 0xC0 // Initialise the OPL subsystem. -- cgit v1.2.3 From 84a05be9dbc9d1b181670f8fd7e036bbe691d856 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Tue, 10 Mar 2009 21:55:55 +0000 Subject: Add OPL lib plumbing and Linux native driver. Subversion-branch: /branches/opl-branch Subversion-revision: 1458 --- opl/Makefile.am | 4 ++- opl/opl.c | 56 +++++++++++++++++++++++++++---- opl/opl_internal.h | 96 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ opl/opl_linux.c | 79 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 228 insertions(+), 7 deletions(-) create mode 100644 opl/opl_internal.h create mode 100644 opl/opl_linux.c (limited to 'opl') diff --git a/opl/Makefile.am b/opl/Makefile.am index 65f8b2de..e1dbe42c 100644 --- a/opl/Makefile.am +++ b/opl/Makefile.am @@ -4,6 +4,8 @@ AM_CFLAGS=@SDLMIXER_CFLAGS@ noinst_LIBRARIES=libopl.a libopl_a_SOURCES = \ + opl_internal.h \ opl.c opl.h \ - fmopl.c + opl_linux.c \ + fmopl.c fmopl.h diff --git a/opl/opl.c b/opl/opl.c index d3dfc23b..28609223 100644 --- a/opl/opl.c +++ b/opl/opl.c @@ -23,27 +23,71 @@ // //----------------------------------------------------------------------------- +#include "config.h" + +#include + #include "opl.h" +#include "opl_internal.h" + +#ifdef HAVE_IOPERM +extern opl_driver_t opl_linux_driver; +#endif + +static opl_driver_t *drivers[] = +{ +#ifdef HAVE_IOPERM + &opl_linux_driver, +#endif + NULL +}; + +static opl_driver_t *driver = NULL; int OPL_Init(unsigned int port_base) { - // TODO - return 1; + int i; + + // Try drivers until we find a working one: + + for (i=0; drivers[i] != NULL; ++i) + { + if (drivers[i]->init_func(port_base)) + { + driver = drivers[i]; + return 1; + } + } + + return 0; } void OPL_Shutdown(void) { - // TODO + if (driver != NULL) + { + driver->shutdown_func(); + driver = NULL; + } } void OPL_WritePort(opl_port_t port, unsigned int value) { - // TODO + if (driver != NULL) + { + driver->write_port_func(port, value); + } } unsigned int OPL_ReadPort(opl_port_t port) { - // TODO - return 0; + if (driver != NULL) + { + return driver->read_port_func(port); + } + else + { + return 0; + } } diff --git a/opl/opl_internal.h b/opl/opl_internal.h new file mode 100644 index 00000000..964f55cc --- /dev/null +++ b/opl/opl_internal.h @@ -0,0 +1,96 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 2009 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +// DESCRIPTION: +// OPL internal interface. +// +//----------------------------------------------------------------------------- + + +#ifndef OPL_INTERNAL_H +#define OPL_INTERNAL_H + +#include "opl.h" + +typedef int (*opl_init_func)(unsigned int port_base); +typedef void (*opl_shutdown_func)(void); +typedef unsigned int (*opl_read_port_func)(opl_port_t port); +typedef void (*opl_write_port_func)(opl_port_t port, unsigned int value); + +typedef struct +{ + char *name; + + opl_init_func init_func; + opl_shutdown_func shutdown_func; + opl_read_port_func read_port_func; + opl_write_port_func write_port_func; +} opl_driver_t; + +#endif /* #ifndef OPL_INTERNAL_H */ + +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 2009 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +// DESCRIPTION: +// OPL internal interface. +// +//----------------------------------------------------------------------------- + + +#ifndef OPL_INTERNAL_H +#define OPL_INTERNAL_H + +#include "opl.h" + +typedef int (*opl_init_func)(unsigned int port_base); +typedef void (*opl_shutdown_func)(void); +typedef unsigned int (*opl_read_port_func)(opl_port_t port); +typedef void (*opl_write_port_func)(opl_port_t port, unsigned int value); + +typedef struct +{ + char *name; + + opl_init_func init_func; + opl_shutdown_func shutdown_func; + opl_read_port_func read_port_func; + opl_write_port_func write_port_func; +} opl_driver_t; + +#endif /* #ifndef OPL_INTERNAL_H */ + diff --git a/opl/opl_linux.c b/opl/opl_linux.c new file mode 100644 index 00000000..438b091a --- /dev/null +++ b/opl/opl_linux.c @@ -0,0 +1,79 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 2009 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +// DESCRIPTION: +// OPL Linux interface. +// +//----------------------------------------------------------------------------- + +#include "config.h" + +#ifdef HAVE_IOPERM + +#include +#include + +#include "opl.h" +#include "opl_internal.h" + +static unsigned int opl_port_base; + +static void OPL_Linux_Init(unsigned int port_base) +{ + // Try to get permissions: + + if (ioperm(port_base, 2, 1) < 0) + { + return 0; + } + + opl_port_base = port_base; + + return 1; +} + +static void OPL_Linux_Shutdown(void) +{ + // Release permissions + + ioperm(opl_port_base, 2, 0); +} + +static unsigned int OPL_Linux_PortRead(opl_port_t port) +{ + return inb(opl_port_base + port); +} + +static void OPL_Linux_PortWrite(opl_port_t port, unsigned int value) +{ + outb(opl_port_base + port, value); +} + +opl_driver_t opl_linux_driver = +{ + "Linux", + OPL_Linux_Init, + OPL_Linux_Shutdown, + OPL_Linux_PortRead, + OPL_Linux_PortWrite +}; + +#endif /* #ifdef HAVE_IOPERM */ + -- cgit v1.2.3 From 02e3e618472e4afa9a58950a9853aa0356b59504 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Tue, 10 Mar 2009 21:57:06 +0000 Subject: Set svn:ignore property. Subversion-branch: /branches/opl-branch Subversion-revision: 1459 --- opl/.gitignore | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 opl/.gitignore (limited to 'opl') diff --git a/opl/.gitignore b/opl/.gitignore new file mode 100644 index 00000000..e64379dc --- /dev/null +++ b/opl/.gitignore @@ -0,0 +1,5 @@ +Makefile.in +Makefile +.deps +libopl.a +*.rc -- cgit v1.2.3 From c423d770e9a05e762a2382e342ddd0bb7e6f3996 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Tue, 10 Mar 2009 22:19:29 +0000 Subject: Fix outb() call, display error message if unable to gain port permissions. Subversion-branch: /branches/opl-branch Subversion-revision: 1460 --- opl/opl_linux.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'opl') diff --git a/opl/opl_linux.c b/opl/opl_linux.c index 438b091a..d1d1e466 100644 --- a/opl/opl_linux.c +++ b/opl/opl_linux.c @@ -27,6 +27,9 @@ #ifdef HAVE_IOPERM +#include +#include +#include #include #include @@ -35,12 +38,14 @@ static unsigned int opl_port_base; -static void OPL_Linux_Init(unsigned int port_base) +static int OPL_Linux_Init(unsigned int port_base) { // Try to get permissions: if (ioperm(port_base, 2, 1) < 0) { + fprintf(stderr, "Failed to get I/O port permissions for 0x%x: %s\n", + port_base, strerror(errno)); return 0; } @@ -63,7 +68,7 @@ static unsigned int OPL_Linux_PortRead(opl_port_t port) static void OPL_Linux_PortWrite(opl_port_t port, unsigned int value) { - outb(opl_port_base + port, value); + outb(value, opl_port_base + port); } opl_driver_t opl_linux_driver = -- cgit v1.2.3 From 962d6dcf12e740c26c3be035884b310e74947d97 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Wed, 11 Mar 2009 20:20:05 +0000 Subject: Debug trace code for register writes. Subversion-branch: /branches/opl-branch Subversion-revision: 1462 --- opl/opl.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) (limited to 'opl') diff --git a/opl/opl.c b/opl/opl.c index 28609223..9afdd980 100644 --- a/opl/opl.c +++ b/opl/opl.c @@ -30,6 +30,8 @@ #include "opl.h" #include "opl_internal.h" +#define OPL_DEBUG_TRACE + #ifdef HAVE_IOPERM extern opl_driver_t opl_linux_driver; #endif @@ -75,6 +77,9 @@ void OPL_WritePort(opl_port_t port, unsigned int value) { if (driver != NULL) { +#ifdef OPL_DEBUG_TRACE + printf("OPL_write: %i, %x\n", port, value); +#endif driver->write_port_func(port, value); } } @@ -83,7 +88,15 @@ unsigned int OPL_ReadPort(opl_port_t port) { if (driver != NULL) { - return driver->read_port_func(port); + unsigned int result; + + result = driver->read_port_func(port); + +#ifdef OPL_DEBUG_TRACE + printf("OPL_read: %i -> %x\n", port, result); +#endif + + return result; } else { -- cgit v1.2.3 From d9f55dd56cc2a21dc6be97e9a2512d9bd8f05286 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Sun, 29 Mar 2009 22:40:44 +0000 Subject: Fix compile warning. Subversion-branch: /branches/opl-branch Subversion-revision: 1491 --- opl/opl.c | 1 + 1 file changed, 1 insertion(+) (limited to 'opl') diff --git a/opl/opl.c b/opl/opl.c index 9afdd980..8f75241b 100644 --- a/opl/opl.c +++ b/opl/opl.c @@ -25,6 +25,7 @@ #include "config.h" +#include #include #include "opl.h" -- cgit v1.2.3 From 1d92cd1477d44245c151c1f39b15c356019d8359 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Wed, 27 May 2009 18:00:35 +0000 Subject: Oops. Subversion-branch: /branches/opl-branch Subversion-revision: 1533 --- opl/opl_internal.h | 48 ------------------------------------------------ 1 file changed, 48 deletions(-) (limited to 'opl') diff --git a/opl/opl_internal.h b/opl/opl_internal.h index 964f55cc..7c1e8854 100644 --- a/opl/opl_internal.h +++ b/opl/opl_internal.h @@ -46,51 +46,3 @@ typedef struct #endif /* #ifndef OPL_INTERNAL_H */ -// Emacs style mode select -*- C++ -*- -//----------------------------------------------------------------------------- -// -// Copyright(C) 2009 Simon Howard -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA -// 02111-1307, USA. -// -// DESCRIPTION: -// OPL internal interface. -// -//----------------------------------------------------------------------------- - - -#ifndef OPL_INTERNAL_H -#define OPL_INTERNAL_H - -#include "opl.h" - -typedef int (*opl_init_func)(unsigned int port_base); -typedef void (*opl_shutdown_func)(void); -typedef unsigned int (*opl_read_port_func)(opl_port_t port); -typedef void (*opl_write_port_func)(opl_port_t port, unsigned int value); - -typedef struct -{ - char *name; - - opl_init_func init_func; - opl_shutdown_func shutdown_func; - opl_read_port_func read_port_func; - opl_write_port_func write_port_func; -} opl_driver_t; - -#endif /* #ifndef OPL_INTERNAL_H */ - -- cgit v1.2.3 From a4695f20fbaba6d4fc351edb78825328cd95f49f Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Wed, 27 May 2009 18:01:00 +0000 Subject: Add initial SDL driver. Subversion-branch: /branches/opl-branch Subversion-revision: 1534 --- opl/Makefile.am | 1 + opl/opl_sdl.c | 172 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 173 insertions(+) create mode 100644 opl/opl_sdl.c (limited to 'opl') diff --git a/opl/Makefile.am b/opl/Makefile.am index e1dbe42c..d2870fc1 100644 --- a/opl/Makefile.am +++ b/opl/Makefile.am @@ -7,5 +7,6 @@ libopl_a_SOURCES = \ opl_internal.h \ opl.c opl.h \ opl_linux.c \ + opl_sdl.c \ fmopl.c fmopl.h diff --git a/opl/opl_sdl.c b/opl/opl_sdl.c new file mode 100644 index 00000000..a0d9d74e --- /dev/null +++ b/opl/opl_sdl.c @@ -0,0 +1,172 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 2009 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +// DESCRIPTION: +// OPL SDL interface. +// +//----------------------------------------------------------------------------- + +#include "config.h" + +#include +#include +#include + +#include "SDL.h" +#include "SDL_mixer.h" + +#include "fmopl.h" + +#include "opl.h" +#include "opl_internal.h" + +// TODO: +#define opl_sample_rate 22050 + +static FM_OPL *opl_emulator = NULL; +static int sdl_was_initialised = 0; +static int mixing_freq, mixing_channels; +static Uint16 mixing_format; + +static int SDLIsInitialised(void) +{ + int freq, channels; + Uint16 format; + + return Mix_QuerySpec(&freq, &format, &channels); +} + +// Callback function to fill a new sound buffer: + +static void OPL_Mix_Callback(void *udata, Uint8 *stream, int len) +{ +} + +static void OPL_SDL_Shutdown(void) +{ + if (sdl_was_initialised) + { + Mix_CloseAudio(); + SDL_QuitSubSystem(SDL_INIT_AUDIO); + sdl_was_initialised = 0; + } + + if (opl_emulator != NULL) + { + OPLDestroy(opl_emulator); + opl_emulator = NULL; + } +} + +static int OPL_SDL_Init(unsigned int port_base) +{ + // Check if SDL_mixer has been opened already + // If not, we must initialise it now + + if (!SDLIsInitialised()) + { + if (SDL_Init(SDL_INIT_AUDIO) < 0) + { + fprintf(stderr, "Unable to set up sound.\n"); + return 0; + } + + if (Mix_OpenAudio(opl_sample_rate, AUDIO_S16SYS, 2, 1024) < 0) + { + fprintf(stderr, "Error initialising SDL_mixer: %s\n", Mix_GetError()); + + SDL_QuitSubSystem(SDL_INIT_AUDIO); + return 0; + } + + SDL_PauseAudio(0); + + // When this module shuts down, it has the responsibility to + // shut down SDL. + + sdl_was_initialised = 1; + } + else + { + sdl_was_initialised = 0; + } + + // Get the mixer frequency, format and number of channels. + + Mix_QuerySpec(&mixing_freq, &mixing_format, &mixing_channels); + + // Only supports AUDIO_S16SYS + + if (mixing_format != AUDIO_S16SYS || mixing_channels != 2) + { + fprintf(stderr, + "OPL_SDL only supports native signed 16-bit LSB, " + "stereo format!\n"); + + OPL_SDL_Shutdown(); + return 0; + } + + // Create the emulator structure: + + opl_emulator = makeAdlibOPL(mixing_freq); + + if (opl_emulator == NULL) + { + fprintf(stderr, "Failed to initialise software OPL emulator!\n"); + OPL_SDL_Shutdown(); + return 0; + } + + // TODO: This should be music callback? or-? + Mix_SetPostMix(OPL_Mix_Callback, NULL); + + return 1; +} + +static unsigned int OPL_SDL_PortRead(opl_port_t port) +{ + if (opl_emulator != NULL) + { + return OPLRead(opl_emulator, port); + } + else + { + return 0; + } +} + +static void OPL_SDL_PortWrite(opl_port_t port, unsigned int value) +{ + if (opl_emulator != NULL) + { + OPLWrite(opl_emulator, port, value); + } +} + +opl_driver_t opl_sdl_driver = +{ + "SDL", + OPL_SDL_Init, + OPL_SDL_Shutdown, + OPL_SDL_PortRead, + OPL_SDL_PortWrite +}; + -- cgit v1.2.3 From 223879d264009f4678803651f885214f84606cb7 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Thu, 28 May 2009 18:26:13 +0000 Subject: Add droplay example program from /research, adapted to work with OPL library. Subversion-branch: /branches/opl-branch Subversion-revision: 1535 --- opl/Makefile.am | 2 + opl/examples/Makefile.am | 8 +++ opl/examples/droplay.c | 177 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 187 insertions(+) create mode 100644 opl/examples/Makefile.am create mode 100644 opl/examples/droplay.c (limited to 'opl') diff --git a/opl/Makefile.am b/opl/Makefile.am index d2870fc1..d4149773 100644 --- a/opl/Makefile.am +++ b/opl/Makefile.am @@ -1,6 +1,8 @@ AM_CFLAGS=@SDLMIXER_CFLAGS@ +SUBDIRS = . examples + noinst_LIBRARIES=libopl.a libopl_a_SOURCES = \ diff --git a/opl/examples/Makefile.am b/opl/examples/Makefile.am new file mode 100644 index 00000000..3dc07d46 --- /dev/null +++ b/opl/examples/Makefile.am @@ -0,0 +1,8 @@ + +AM_CFLAGS = -I.. + +noinst_PROGRAMS=droplay + +droplay_LDADD = ../libopl.a @LDFLAGS@ @SDL_LIBS@ +droplay_SOURCES = droplay.c + diff --git a/opl/examples/droplay.c b/opl/examples/droplay.c new file mode 100644 index 00000000..7cd595e9 --- /dev/null +++ b/opl/examples/droplay.c @@ -0,0 +1,177 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 2009 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +// DESCRIPTION: +// Demonstration program for OPL library to play back DRO +// format files. +// +//----------------------------------------------------------------------------- + + +#include +#include + +#include "SDL.h" + +#include "opl.h" + +#define HEADER_STRING "DBRAWOPL" +#define ADLIB_PORT 0x388 + +void WriteReg(unsigned int reg, unsigned int val) +{ + int i; + + OPL_WritePort(OPL_REGISTER_PORT, reg); + + for (i=0; i<6; ++i) + { + OPL_ReadPort(OPL_REGISTER_PORT); + } + + OPL_WritePort(OPL_DATA_PORT, val); + + for (i=0; i<35; ++i) + { + OPL_ReadPort(OPL_REGISTER_PORT); + } +} + +void ClearAllRegs(void) +{ + int i; + + for (i=0; i<=0xff; ++i) + { + WriteReg(i, 0x00); + } +} + +// Detect an OPL chip. + +int DetectOPL(void) +{ + WriteReg(OPL_REG_TIMER_CTRL, 0x60); + WriteReg(OPL_REG_TIMER_CTRL, 0x80); + int val1 = OPL_ReadPort(OPL_REGISTER_PORT) & 0xe0; + WriteReg(OPL_REG_TIMER1, 0xff); + WriteReg(OPL_REG_TIMER_CTRL, 0x21); + SDL_Delay(50); + int val2 = OPL_ReadPort(OPL_REGISTER_PORT) & 0xe0; + WriteReg(OPL_REG_TIMER_CTRL, 0x60); + WriteReg(OPL_REG_TIMER_CTRL, 0x80); + + return val1 != 0 || val2 != 0xc0; +} + +void Init(void) +{ + if (SDL_Init(SDL_INIT_TIMER) < 0) + { + fprintf(stderr, "Unable to initialise SDL timer\n"); + exit(-1); + } + + if (!OPL_Init(ADLIB_PORT)) + { + fprintf(stderr, "Unable to initialise OPL layer\n"); + exit(-1); + } + + if (!DetectOPL()) + { + fprintf(stderr, "Adlib not detected\n"); + exit(-1); + } +} + +void Shutdown(void) +{ + OPL_Shutdown(); +} + +void PlayFile(char *filename) +{ + FILE *stream; + char buf[8]; + + stream = fopen(filename, "rb"); + + if (fread(buf, 1, 8, stream) < 8) + { + fprintf(stderr, "failed to read raw OPL header\n"); + exit(-1); + } + + if (strncmp(buf, HEADER_STRING, 8) != 0) + { + fprintf(stderr, "Raw OPL header not found\n"); + exit(-1); + } + + fseek(stream, 28, SEEK_SET); + + while (!feof(stream)) + { + int reg, val; + + reg = fgetc(stream); + val = fgetc(stream); + + // Delay? + + if (reg == 0x00) + { + SDL_Delay(val); + } + else if (reg == 0x01) + { + val |= (fgetc(stream) << 8); + SDL_Delay(val); + } + else + { + WriteReg(reg, val); + } + } + + fclose(stream); +} + +int main(int argc, char *argv[]) +{ + if (argc < 2) + { + printf("Usage: %s \n", argv[0]); + exit(-1); + } + + Init(); + ClearAllRegs(); + SDL_Delay(1000); + + PlayFile(argv[1]); + + ClearAllRegs(); + Shutdown(); + + return 0; +} + -- cgit v1.2.3 From b695843cc549e3de4845e340c611efb0ae98866c Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Thu, 28 May 2009 18:34:05 +0000 Subject: Fix OPL detect. Subversion-branch: /branches/opl-branch Subversion-revision: 1536 --- opl/examples/droplay.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'opl') diff --git a/opl/examples/droplay.c b/opl/examples/droplay.c index 7cd595e9..af1a59d9 100644 --- a/opl/examples/droplay.c +++ b/opl/examples/droplay.c @@ -78,7 +78,7 @@ int DetectOPL(void) WriteReg(OPL_REG_TIMER_CTRL, 0x60); WriteReg(OPL_REG_TIMER_CTRL, 0x80); - return val1 != 0 || val2 != 0xc0; + return val1 == 0 && val2 == 0xc0; } void Init(void) -- cgit v1.2.3 From 6e4f6ab9626d81e4106d3ccc974a76d832fdff13 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Thu, 28 May 2009 18:37:31 +0000 Subject: Set channel bits for OPL3 so that OPL2 traces will play back properly. Subversion-branch: /branches/opl-branch Subversion-revision: 1537 --- opl/examples/droplay.c | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'opl') diff --git a/opl/examples/droplay.c b/opl/examples/droplay.c index af1a59d9..5f09fe11 100644 --- a/opl/examples/droplay.c +++ b/opl/examples/droplay.c @@ -39,6 +39,15 @@ void WriteReg(unsigned int reg, unsigned int val) { int i; + // This was recorded from an OPL2, but we are probably playing + // back on an OPL3, so we need to enable the original OPL2 + // channels. Doom does this already, but other games don't. + + if ((reg & 0xf0) == OPL_REGS_FEEDBACK) + { + val |= 0x30; + } + OPL_WritePort(OPL_REGISTER_PORT, reg); for (i=0; i<6; ++i) -- cgit v1.2.3 From 98ee23f4268dbb1395aa0b2cbfad9f53d1092b33 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Sat, 30 May 2009 23:24:11 +0000 Subject: Add initial callback/timer API. Subversion-branch: /branches/opl-branch Subversion-revision: 1538 --- opl/Makefile.am | 2 + opl/examples/.gitignore | 5 ++ opl/examples/droplay.c | 94 +++++++++++++++++------ opl/opl.c | 24 ++++++ opl/opl.h | 16 ++++ opl/opl_internal.h | 8 ++ opl/opl_linux.c | 18 ++++- opl/opl_queue.c | 192 ++++++++++++++++++++++++++++++++++++++++++++++ opl/opl_queue.h | 44 +++++++++++ opl/opl_sdl.c | 25 +++++- opl/opl_timer.c | 197 ++++++++++++++++++++++++++++++++++++++++++++++++ opl/opl_timer.h | 40 ++++++++++ 12 files changed, 641 insertions(+), 24 deletions(-) create mode 100644 opl/examples/.gitignore create mode 100644 opl/opl_queue.c create mode 100644 opl/opl_queue.h create mode 100644 opl/opl_timer.c create mode 100644 opl/opl_timer.h (limited to 'opl') diff --git a/opl/Makefile.am b/opl/Makefile.am index d4149773..d48b491b 100644 --- a/opl/Makefile.am +++ b/opl/Makefile.am @@ -10,5 +10,7 @@ libopl_a_SOURCES = \ opl.c opl.h \ opl_linux.c \ opl_sdl.c \ + opl_queue.c opl_queue.h \ + opl_timer.c opl_timer.h \ fmopl.c fmopl.h diff --git a/opl/examples/.gitignore b/opl/examples/.gitignore new file mode 100644 index 00000000..49bb1af8 --- /dev/null +++ b/opl/examples/.gitignore @@ -0,0 +1,5 @@ +Makefile.in +Makefile +.deps +droplay + diff --git a/opl/examples/droplay.c b/opl/examples/droplay.c index 5f09fe11..89cf6862 100644 --- a/opl/examples/droplay.c +++ b/opl/examples/droplay.c @@ -116,44 +116,51 @@ void Shutdown(void) OPL_Shutdown(); } -void PlayFile(char *filename) +struct timer_data { - FILE *stream; - char buf[8]; - - stream = fopen(filename, "rb"); + int running; + FILE *fstream; +}; - if (fread(buf, 1, 8, stream) < 8) - { - fprintf(stderr, "failed to read raw OPL header\n"); - exit(-1); - } +void TimerCallback(void *data) +{ + struct timer_data *timer_data = data; + int delay; - if (strncmp(buf, HEADER_STRING, 8) != 0) + if (!timer_data->running) { - fprintf(stderr, "Raw OPL header not found\n"); - exit(-1); + return; } - fseek(stream, 28, SEEK_SET); + // Read data until we must make a delay. - while (!feof(stream)) + for (;;) { int reg, val; - reg = fgetc(stream); - val = fgetc(stream); + // End of file? - // Delay? + if (feof(timer_data->fstream)) + { + timer_data->running = 0; + return; + } + + reg = fgetc(timer_data->fstream); + val = fgetc(timer_data->fstream); + + // Register value of 0 or 1 indicates a delay. if (reg == 0x00) { - SDL_Delay(val); + delay = val; + break; } else if (reg == 0x01) { - val |= (fgetc(stream) << 8); - SDL_Delay(val); + val |= (fgetc(timer_data->fstream) << 8); + delay = val; + break; } else { @@ -161,7 +168,50 @@ void PlayFile(char *filename) } } - fclose(stream); + // Schedule the next timer callback. + + OPL_SetCallback(delay, TimerCallback, timer_data); +} + +void PlayFile(char *filename) +{ + struct timer_data timer_data; + int running; + char buf[8]; + + timer_data.fstream = fopen(filename, "rb"); + + if (fread(buf, 1, 8, timer_data.fstream) < 8) + { + fprintf(stderr, "failed to read raw OPL header\n"); + exit(-1); + } + + if (strncmp(buf, HEADER_STRING, 8) != 0) + { + fprintf(stderr, "Raw OPL header not found\n"); + exit(-1); + } + + fseek(timer_data.fstream, 28, SEEK_SET); + timer_data.running = 1; + + // Start callback loop sequence. + + OPL_SetCallback(0, TimerCallback, &timer_data); + + // Sleep until the playback finishes. + + do + { + OPL_Lock(); + running = timer_data.running; + OPL_Unlock(); + + SDL_Delay(100); + } while (running); + + fclose(timer_data.fstream); } int main(int argc, char *argv[]) diff --git a/opl/opl.c b/opl/opl.c index 8f75241b..6c2d9c4f 100644 --- a/opl/opl.c +++ b/opl/opl.c @@ -105,3 +105,27 @@ unsigned int OPL_ReadPort(opl_port_t port) } } +void OPL_SetCallback(unsigned int ms, opl_callback_t callback, void *data) +{ + if (driver != NULL) + { + driver->set_callback_func(ms, callback, data); + } +} + +void OPL_Lock(void) +{ + if (driver != NULL) + { + driver->lock_func(); + } +} + +void OPL_Unlock(void) +{ + if (driver != NULL) + { + driver->unlock_func(); + } +} + diff --git a/opl/opl.h b/opl/opl.h index 352e6696..515950b1 100644 --- a/opl/opl.h +++ b/opl/opl.h @@ -27,6 +27,8 @@ #ifndef OPL_OPL_H #define OPL_OPL_H +typedef void (*opl_callback_t)(void *data); + typedef enum { OPL_REGISTER_PORT = 0, @@ -72,5 +74,19 @@ void OPL_WritePort(opl_port_t port, unsigned int value); unsigned int OPL_ReadPort(opl_port_t port); +// Set a timer callback. After the specified number of milliseconds +// have elapsed, the callback will be invoked. + +void OPL_SetCallback(unsigned int ms, opl_callback_t callback, void *data); + +// Begin critical section, during which, OPL callbacks will not be +// invoked. + +void OPL_Lock(void); + +// End critical section. + +void OPL_Unlock(void); + #endif diff --git a/opl/opl_internal.h b/opl/opl_internal.h index 7c1e8854..cd125122 100644 --- a/opl/opl_internal.h +++ b/opl/opl_internal.h @@ -33,6 +33,11 @@ typedef int (*opl_init_func)(unsigned int port_base); typedef void (*opl_shutdown_func)(void); typedef unsigned int (*opl_read_port_func)(opl_port_t port); typedef void (*opl_write_port_func)(opl_port_t port, unsigned int value); +typedef void (*opl_set_callback_func)(unsigned int ms, + opl_callback_t callback, + void *data); +typedef void (*opl_lock_func)(void); +typedef void (*opl_unlock_func)(void); typedef struct { @@ -42,6 +47,9 @@ typedef struct opl_shutdown_func shutdown_func; opl_read_port_func read_port_func; opl_write_port_func write_port_func; + opl_set_callback_func set_callback_func; + opl_lock_func lock_func; + opl_unlock_func unlock_func; } opl_driver_t; #endif /* #ifndef OPL_INTERNAL_H */ diff --git a/opl/opl_linux.c b/opl/opl_linux.c index d1d1e466..4a10337f 100644 --- a/opl/opl_linux.c +++ b/opl/opl_linux.c @@ -35,6 +35,7 @@ #include "opl.h" #include "opl_internal.h" +#include "opl_timer.h" static unsigned int opl_port_base; @@ -51,11 +52,23 @@ static int OPL_Linux_Init(unsigned int port_base) opl_port_base = port_base; + // Start callback thread + + if (!OPL_Timer_StartThread()) + { + ioperm(port_base, 2, 0); + return 0; + } + return 1; } static void OPL_Linux_Shutdown(void) { + // Stop callback thread + + OPL_Timer_StopThread(); + // Release permissions ioperm(opl_port_base, 2, 0); @@ -77,7 +90,10 @@ opl_driver_t opl_linux_driver = OPL_Linux_Init, OPL_Linux_Shutdown, OPL_Linux_PortRead, - OPL_Linux_PortWrite + OPL_Linux_PortWrite, + OPL_Timer_SetCallback, + OPL_Timer_Lock, + OPL_Timer_Unlock }; #endif /* #ifdef HAVE_IOPERM */ diff --git a/opl/opl_queue.c b/opl/opl_queue.c new file mode 100644 index 00000000..6639eee5 --- /dev/null +++ b/opl/opl_queue.c @@ -0,0 +1,192 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 2009 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +// DESCRIPTION: +// Queue of waiting callbacks, stored in a binary min heap, so that we +// can always get the first callback. +// +//----------------------------------------------------------------------------- + +#include +#include +#include + +#include "opl_queue.h" + +#define MAX_OPL_QUEUE 64 + +typedef struct +{ + opl_callback_t callback; + void *data; + unsigned int time; +} opl_queue_entry_t; + +struct opl_callback_queue_s +{ + opl_queue_entry_t entries[MAX_OPL_QUEUE]; + unsigned int num_entries; +}; + +opl_callback_queue_t *OPL_Queue_Create(void) +{ + opl_callback_queue_t *queue; + + queue = malloc(sizeof(opl_callback_queue_t)); + queue->num_entries = 0; + + return queue; +} + +void OPL_Queue_Destroy(opl_callback_queue_t *queue) +{ + free(queue); +} + +int OPL_Queue_IsEmpty(opl_callback_queue_t *queue) +{ + return queue->num_entries == 0; +} + +void OPL_Queue_Push(opl_callback_queue_t *queue, + opl_callback_t callback, void *data, + unsigned int time) +{ + int entry_id; + + if (queue->num_entries >= MAX_OPL_QUEUE) + { + fprintf(stderr, "OPL_Queue_Push: Exceeded maximum callbacks\n"); + return; + } + + // Add to last queue entry. + + entry_id = queue->num_entries; + ++queue->num_entries; + + // Shift existing entries down in the heap. + + while (entry_id > 0 && time < queue->entries[entry_id / 2].time) + { + memcpy(&queue->entries[entry_id], + &queue->entries[entry_id / 2], + sizeof(opl_queue_entry_t)); + + entry_id /= 2; + } + + // Insert new callback data. + + queue->entries[entry_id].callback = callback; + queue->entries[entry_id].data = data; + queue->entries[entry_id].time = time; +} + +int OPL_Queue_Pop(opl_callback_queue_t *queue, + opl_callback_t *callback, void **data) +{ + opl_queue_entry_t *entry; + int child1, child2; + int i, next_i; + + // Empty? + + if (queue->num_entries <= 0) + { + return 0; + } + + // Store the result: + + *callback = queue->entries[0].callback; + *data = queue->entries[0].data; + + // Decrease the heap size, and keep pointer to the last entry in + // the heap, which must now be percolated down from the top. + + --queue->num_entries; + entry = &queue->entries[queue->num_entries]; + + // Percolate down. + + i = 0; + + for (;;) + { + child1 = i * 2 + 1; + child2 = i * 2 + 2; + + if (child1 < queue->num_entries + && queue->entries[child1].time < entry->time) + { + // Left child is less than entry. + // Use the minimum of left and right children. + + if (child2 < queue->num_entries + && queue->entries[child2].time < queue->entries[child1].time) + { + next_i = child2; + } + else + { + next_i = child1; + } + } + else if (child2 < queue->num_entries + && queue->entries[child2].time < entry->time) + { + // Right child is less than entry. Go down the right side. + + next_i = child2; + } + else + { + // Finished percolating. + break; + } + + // Percolate the next value up and advance. + + memcpy(&queue->entries[i], + &queue->entries[next_i], + sizeof(opl_queue_entry_t)); + i = next_i; + } + + // Store the old last-entry at its new position. + + memcpy(&queue->entries[i], entry, sizeof(opl_queue_entry_t)); + + return 1; +} + +unsigned int OPL_Queue_Peek(opl_callback_queue_t *queue) +{ + if (queue->num_entries > 0) + { + return queue->entries[0].time; + } + else + { + return 0; + } +} + diff --git a/opl/opl_queue.h b/opl/opl_queue.h new file mode 100644 index 00000000..6ead0010 --- /dev/null +++ b/opl/opl_queue.h @@ -0,0 +1,44 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 2009 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +// DESCRIPTION: +// OPL callback queue. +// +//----------------------------------------------------------------------------- + +#ifndef OPL_QUEUE_H +#define OPL_QUEUE_H + +#include "opl.h" + +typedef struct opl_callback_queue_s opl_callback_queue_t; + +opl_callback_queue_t *OPL_Queue_Create(void); +int OPL_Queue_IsEmpty(opl_callback_queue_t *queue); +void OPL_Queue_Destroy(opl_callback_queue_t *queue); +void OPL_Queue_Push(opl_callback_queue_t *queue, + opl_callback_t callback, void *data, + unsigned int time); +int OPL_Queue_Pop(opl_callback_queue_t *queue, + opl_callback_t *callback, void **data); +unsigned int OPL_Queue_Peek(opl_callback_queue_t *queue); + +#endif /* #ifndef OPL_QUEUE_H */ + diff --git a/opl/opl_sdl.c b/opl/opl_sdl.c index a0d9d74e..2b8f5174 100644 --- a/opl/opl_sdl.c +++ b/opl/opl_sdl.c @@ -37,9 +37,12 @@ #include "opl.h" #include "opl_internal.h" +#include "opl_queue.h" + // TODO: #define opl_sample_rate 22050 +static opl_callback_queue_t *callback_queue; static FM_OPL *opl_emulator = NULL; static int sdl_was_initialised = 0; static int mixing_freq, mixing_channels; @@ -65,6 +68,7 @@ static void OPL_SDL_Shutdown(void) { Mix_CloseAudio(); SDL_QuitSubSystem(SDL_INIT_AUDIO); + OPL_Queue_Destroy(callback_queue); sdl_was_initialised = 0; } @@ -82,6 +86,8 @@ static int OPL_SDL_Init(unsigned int port_base) if (!SDLIsInitialised()) { + callback_queue = OPL_Queue_Create(); + if (SDL_Init(SDL_INIT_AUDIO) < 0) { fprintf(stderr, "Unable to set up sound.\n"); @@ -161,12 +167,29 @@ static void OPL_SDL_PortWrite(opl_port_t port, unsigned int value) } } +static void OPL_SDL_SetCallback(unsigned int ms, + opl_callback_t callback, + void *data) +{ +} + +static void OPL_SDL_Lock(void) +{ +} + +static void OPL_SDL_Unlock(void) +{ +} + opl_driver_t opl_sdl_driver = { "SDL", OPL_SDL_Init, OPL_SDL_Shutdown, OPL_SDL_PortRead, - OPL_SDL_PortWrite + OPL_SDL_PortWrite, + OPL_SDL_SetCallback, + OPL_SDL_Lock, + OPL_SDL_Unlock }; diff --git a/opl/opl_timer.c b/opl/opl_timer.c new file mode 100644 index 00000000..519dbbd9 --- /dev/null +++ b/opl/opl_timer.c @@ -0,0 +1,197 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 2009 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +// DESCRIPTION: +// OPL timer thread. +// Once started using OPL_Timer_StartThread, the thread sleeps, +// waking up to invoke callbacks set using OPL_Timer_SetCallback. +// +//----------------------------------------------------------------------------- + +#include "SDL.h" + +#include "opl_timer.h" +#include "opl_queue.h" + +typedef enum +{ + THREAD_STATE_STOPPED, + THREAD_STATE_RUNNING, + THREAD_STATE_STOPPING, +} thread_state_t; + +static SDL_Thread *timer_thread = NULL; +static thread_state_t timer_thread_state; +static int current_time; + +// Queue of callbacks waiting to be invoked. +// The callback queue mutex is held while the callback queue structure +// or current_time is being accessed. + +static opl_callback_queue_t *callback_queue; +static SDL_mutex *callback_queue_mutex; + +// The timer mutex is held while timer callback functions are being +// invoked, so that the calling code can prevent clashes. + +static SDL_mutex *timer_mutex; + +// Returns true if there is a callback at the head of the queue ready +// to be invoked. Otherwise, next_time is set to the time when the +// timer thread must wake up again to check. + +static int CallbackWaiting(unsigned int *next_time) +{ + // If there are no queued callbacks, sleep for 50ms at a time + // until a callback is added. + + if (OPL_Queue_IsEmpty(callback_queue)) + { + *next_time = current_time + 50; + return 0; + } + + // Read the time of the first callback in the queue. + // If the time for the callback has not yet arrived, + // we must sleep until the callback time. + + *next_time = OPL_Queue_Peek(callback_queue); + + return *next_time <= current_time; +} + +static unsigned int GetNextTime(void) +{ + opl_callback_t callback; + void *callback_data; + unsigned int next_time; + int have_callback; + + // Keep running through callbacks until there are none ready to + // run. When we run out of callbacks, next_time will be set. + + do + { + SDL_LockMutex(callback_queue_mutex); + + // Check if the callback at the head of the list is ready to + // be invoked. If so, pop from the head of the queue. + + have_callback = CallbackWaiting(&next_time); + + if (have_callback) + { + OPL_Queue_Pop(callback_queue, &callback, &callback_data); + } + + SDL_UnlockMutex(callback_queue_mutex); + + // Now invoke the callback, if we have one. + // The timer mutex is held while the callback is invoked. + + if (have_callback) + { + SDL_LockMutex(timer_mutex); + callback(callback_data); + SDL_UnlockMutex(timer_mutex); + } + } while (have_callback); + + return next_time; +} + +static int ThreadFunction(void *unused) +{ + unsigned int next_time; + unsigned int now; + + // Keep running until OPL_Timer_StopThread is called. + + while (timer_thread_state == THREAD_STATE_RUNNING) + { + // Get the next time that we must sleep until, and + // wait until that time. + + next_time = GetNextTime(); + now = SDL_GetTicks(); + + if (next_time > now) + { + SDL_Delay(next_time - now); + } + + // Update the current time. + + SDL_LockMutex(callback_queue_mutex); + current_time = next_time; + SDL_UnlockMutex(callback_queue_mutex); + } + + timer_thread_state = THREAD_STATE_STOPPED; + + return 0; +} + +int OPL_Timer_StartThread(void) +{ + timer_thread_state = THREAD_STATE_RUNNING; + timer_thread = SDL_CreateThread(ThreadFunction, NULL); + timer_mutex = SDL_CreateMutex(); + + callback_queue = OPL_Queue_Create(); + callback_queue_mutex = SDL_CreateMutex(); + current_time = SDL_GetTicks(); + + if (timer_thread == NULL) + { + timer_thread_state = THREAD_STATE_STOPPED; + return 0; + } + + return 1; +} + +void OPL_Timer_StopThread(void) +{ + timer_thread_state = THREAD_STATE_STOPPING; + + while (timer_thread_state != THREAD_STATE_STOPPED) + { + SDL_Delay(1); + } +} + +void OPL_Timer_SetCallback(unsigned int ms, opl_callback_t callback, void *data) +{ + SDL_LockMutex(callback_queue_mutex); + OPL_Queue_Push(callback_queue, callback, data, current_time + ms); + SDL_UnlockMutex(callback_queue_mutex); +} + +void OPL_Timer_Lock(void) +{ + SDL_LockMutex(timer_mutex); +} + +void OPL_Timer_Unlock(void) +{ + SDL_UnlockMutex(timer_mutex); +} + diff --git a/opl/opl_timer.h b/opl/opl_timer.h new file mode 100644 index 00000000..26255df5 --- /dev/null +++ b/opl/opl_timer.h @@ -0,0 +1,40 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 2009 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +// DESCRIPTION: +// OPL timer thread. +// +//----------------------------------------------------------------------------- + +#ifndef OPL_TIMER_H +#define OPL_TIMER_H + +#include "opl.h" + +int OPL_Timer_StartThread(void); +void OPL_Timer_StopThread(void); +void OPL_Timer_SetCallback(unsigned int ms, + opl_callback_t callback, + void *data); +void OPL_Timer_Lock(void); +void OPL_Timer_Unlock(void); + +#endif /* #ifndef OPL_TIMER_H */ + -- cgit v1.2.3 From a09487cb98273aee3ee950965b49f59d3fc3554f Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Sun, 31 May 2009 23:20:17 +0000 Subject: Fix OPL callback queue. Subversion-branch: /branches/opl-branch Subversion-revision: 1539 --- opl/opl_queue.c | 89 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 86 insertions(+), 3 deletions(-) (limited to 'opl') diff --git a/opl/opl_queue.c b/opl/opl_queue.c index 6639eee5..fe5f1ef8 100644 --- a/opl/opl_queue.c +++ b/opl/opl_queue.c @@ -70,6 +70,7 @@ void OPL_Queue_Push(opl_callback_queue_t *queue, unsigned int time) { int entry_id; + int parent_id; if (queue->num_entries >= MAX_OPL_QUEUE) { @@ -84,13 +85,26 @@ void OPL_Queue_Push(opl_callback_queue_t *queue, // Shift existing entries down in the heap. - while (entry_id > 0 && time < queue->entries[entry_id / 2].time) + while (entry_id > 0) { + parent_id = (entry_id - 1) / 2; + + // Is the heap condition satisfied? + + if (time >= queue->entries[parent_id].time) + { + break; + } + + // Move the existing entry down in the heap. + memcpy(&queue->entries[entry_id], - &queue->entries[entry_id / 2], + &queue->entries[parent_id], sizeof(opl_queue_entry_t)); - entry_id /= 2; + // Advance to the parent. + + entry_id = parent_id; } // Insert new callback data. @@ -190,3 +204,72 @@ unsigned int OPL_Queue_Peek(opl_callback_queue_t *queue) } } +#ifdef TEST + +#include + +static void PrintQueueNode(opl_callback_queue_t *queue, int node, int depth) +{ + int i; + + if (node >= queue->num_entries) + { + return; + } + + for (i=0; ientries[node].time); + + PrintQueueNode(queue, node * 2 + 1, depth + 1); + PrintQueueNode(queue, node * 2 + 2, depth + 1); +} + +static void PrintQueue(opl_callback_queue_t *queue) +{ + PrintQueueNode(queue, 0, 0); +} + +int main() +{ + opl_callback_queue_t *queue; + int iteration; + + queue = OPL_Queue_Create(); + + for (iteration=0; iteration<5000; ++iteration) + { + opl_callback_t callback; + void *data; + unsigned int time; + unsigned int newtime; + int i; + + for (i=0; i= time); + time = newtime; + } + + assert(OPL_Queue_IsEmpty(queue)); + assert(!OPL_Queue_Pop(queue, &callback, &data)); + } +} + +#endif + -- cgit v1.2.3 From 40ca9e8297ae8638c638f33b6ef5393ee88c7056 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Tue, 2 Jun 2009 00:36:52 +0000 Subject: Fix crash due to timer thread starting before resources allocated. Subversion-branch: /branches/opl-branch Subversion-revision: 1540 --- opl/opl_timer.c | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) (limited to 'opl') diff --git a/opl/opl_timer.c b/opl/opl_timer.c index 519dbbd9..62ffbd37 100644 --- a/opl/opl_timer.c +++ b/opl/opl_timer.c @@ -149,19 +149,34 @@ static int ThreadFunction(void *unused) return 0; } -int OPL_Timer_StartThread(void) +static void InitResources(void) { - timer_thread_state = THREAD_STATE_RUNNING; - timer_thread = SDL_CreateThread(ThreadFunction, NULL); - timer_mutex = SDL_CreateMutex(); - callback_queue = OPL_Queue_Create(); + timer_mutex = SDL_CreateMutex(); callback_queue_mutex = SDL_CreateMutex(); +} + +static void FreeResources(void) +{ + OPL_Queue_Destroy(callback_queue); + SDL_DestroyMutex(callback_queue_mutex); + SDL_DestroyMutex(timer_mutex); +} + +int OPL_Timer_StartThread(void) +{ + InitResources(); + + timer_thread_state = THREAD_STATE_RUNNING; current_time = SDL_GetTicks(); + timer_thread = SDL_CreateThread(ThreadFunction, NULL); + if (timer_thread == NULL) { timer_thread_state = THREAD_STATE_STOPPED; + FreeResources(); + return 0; } @@ -176,6 +191,8 @@ void OPL_Timer_StopThread(void) { SDL_Delay(1); } + + FreeResources(); } void OPL_Timer_SetCallback(unsigned int ms, opl_callback_t callback, void *data) -- cgit v1.2.3 From b09cb29b9f573501206bd31e51f3014ec79d5f95 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Tue, 2 Jun 2009 01:08:20 +0000 Subject: Disable debug output. Subversion-branch: /branches/opl-branch Subversion-revision: 1541 --- opl/opl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'opl') diff --git a/opl/opl.c b/opl/opl.c index 6c2d9c4f..5fddf205 100644 --- a/opl/opl.c +++ b/opl/opl.c @@ -31,7 +31,7 @@ #include "opl.h" #include "opl_internal.h" -#define OPL_DEBUG_TRACE +//#define OPL_DEBUG_TRACE #ifdef HAVE_IOPERM extern opl_driver_t opl_linux_driver; -- cgit v1.2.3 From e33a4961331301b1e3a5c65d148050fa33c4c594 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Fri, 28 Aug 2009 18:04:04 +0000 Subject: Working SDL OPL driver. Subversion-branch: /branches/opl-branch Subversion-revision: 1632 --- opl/examples/Makefile.am | 2 +- opl/examples/droplay.c | 10 ++- opl/opl.c | 58 +++++++++++++++ opl/opl.h | 4 ++ opl/opl_sdl.c | 182 ++++++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 249 insertions(+), 7 deletions(-) (limited to 'opl') diff --git a/opl/examples/Makefile.am b/opl/examples/Makefile.am index 3dc07d46..7c2c7c8a 100644 --- a/opl/examples/Makefile.am +++ b/opl/examples/Makefile.am @@ -3,6 +3,6 @@ AM_CFLAGS = -I.. noinst_PROGRAMS=droplay -droplay_LDADD = ../libopl.a @LDFLAGS@ @SDL_LIBS@ +droplay_LDADD = ../libopl.a @LDFLAGS@ @SDL_LIBS@ @SDLMIXER_LIBS@ droplay_SOURCES = droplay.c diff --git a/opl/examples/droplay.c b/opl/examples/droplay.c index 89cf6862..5158fbcd 100644 --- a/opl/examples/droplay.c +++ b/opl/examples/droplay.c @@ -77,16 +77,20 @@ void ClearAllRegs(void) int DetectOPL(void) { + int val1, val2; + WriteReg(OPL_REG_TIMER_CTRL, 0x60); WriteReg(OPL_REG_TIMER_CTRL, 0x80); - int val1 = OPL_ReadPort(OPL_REGISTER_PORT) & 0xe0; + val1 = OPL_ReadPort(OPL_REGISTER_PORT) & 0xe0; WriteReg(OPL_REG_TIMER1, 0xff); WriteReg(OPL_REG_TIMER_CTRL, 0x21); - SDL_Delay(50); - int val2 = OPL_ReadPort(OPL_REGISTER_PORT) & 0xe0; + OPL_Delay(50); + val2 = OPL_ReadPort(OPL_REGISTER_PORT) & 0xe0; WriteReg(OPL_REG_TIMER_CTRL, 0x60); WriteReg(OPL_REG_TIMER_CTRL, 0x80); +// Temporary hack for SDL driver. +return 1; return val1 == 0 && val2 == 0xc0; } diff --git a/opl/opl.c b/opl/opl.c index 5fddf205..e53b5d6e 100644 --- a/opl/opl.c +++ b/opl/opl.c @@ -28,6 +28,8 @@ #include #include +#include "SDL.h" + #include "opl.h" #include "opl_internal.h" @@ -36,12 +38,14 @@ #ifdef HAVE_IOPERM extern opl_driver_t opl_linux_driver; #endif +extern opl_driver_t opl_sdl_driver; static opl_driver_t *drivers[] = { #ifdef HAVE_IOPERM &opl_linux_driver, #endif + &opl_sdl_driver, NULL }; @@ -129,3 +133,57 @@ void OPL_Unlock(void) } } +typedef struct +{ + int finished; + + SDL_mutex *mutex; + SDL_cond *cond; +} delay_data_t; + +static void DelayCallback(void *_delay_data) +{ + delay_data_t *delay_data = _delay_data; + + SDL_LockMutex(delay_data->mutex); + delay_data->finished = 1; + SDL_UnlockMutex(delay_data->mutex); + + SDL_CondSignal(delay_data->cond); +} + +void OPL_Delay(unsigned int ms) +{ + delay_data_t delay_data; + + if (driver == NULL) + { + return; + } + + // Create a callback that will signal this thread after the + // specified time. + + delay_data.finished = 0; + delay_data.mutex = SDL_CreateMutex(); + delay_data.cond = SDL_CreateCond(); + + OPL_SetCallback(ms, DelayCallback, &delay_data); + + // Wait until the callback is invoked. + + SDL_LockMutex(delay_data.mutex); + + while (!delay_data.finished) + { + SDL_CondWait(delay_data.cond, delay_data.mutex); + } + + SDL_UnlockMutex(delay_data.mutex); + + // Clean up. + + SDL_DestroyMutex(delay_data.mutex); + SDL_DestroyCond(delay_data.cond); +} + diff --git a/opl/opl.h b/opl/opl.h index 515950b1..3413e3ab 100644 --- a/opl/opl.h +++ b/opl/opl.h @@ -88,5 +88,9 @@ void OPL_Lock(void); void OPL_Unlock(void); +// Block until the specified number of milliseconds have elapsed. + +void OPL_Delay(unsigned int ms); + #endif diff --git a/opl/opl_sdl.c b/opl/opl_sdl.c index 2b8f5174..849a10b0 100644 --- a/opl/opl_sdl.c +++ b/opl/opl_sdl.c @@ -28,6 +28,7 @@ #include #include #include +#include #include "SDL.h" #include "SDL_mixer.h" @@ -42,8 +43,33 @@ // TODO: #define opl_sample_rate 22050 +// When the callback mutex is locked using OPL_Lock, callback functions +// are not invoked. + +static SDL_mutex *callback_mutex = NULL; + +// Queue of callbacks waiting to be invoked. + static opl_callback_queue_t *callback_queue; + +// Mutex used to control access to the callback queue. + +static SDL_mutex *callback_queue_mutex = NULL; + +// Current time, in number of samples since startup: + +static int current_time; + +// OPL software emulator structure. + static FM_OPL *opl_emulator = NULL; + +// Temporary mixing buffer used by the mixing callback. + +static int16_t *mix_buffer = NULL; + +// SDL parameters. + static int sdl_was_initialised = 0; static int mixing_freq, mixing_channels; static Uint16 mixing_format; @@ -56,10 +82,131 @@ static int SDLIsInitialised(void) return Mix_QuerySpec(&freq, &format, &channels); } +// Advance time by the specified number of samples, invoking any +// callback functions as appropriate. + +static void AdvanceTime(unsigned int nsamples) +{ + opl_callback_t callback; + void *callback_data; + + SDL_LockMutex(callback_queue_mutex); + + // Advance time. + + current_time += nsamples; + + // Are there callbacks to invoke now? Keep invoking them + // until there are none more left. + + while (!OPL_Queue_IsEmpty(callback_queue) + && current_time >= OPL_Queue_Peek(callback_queue)) + { + // Pop the callback from the queue to invoke it. + + if (!OPL_Queue_Pop(callback_queue, &callback, &callback_data)) + { + break; + } + + // The mutex stuff here is a bit complicated. We must + // hold callback_mutex when we invoke the callback (so that + // the control thread can use OPL_Lock() to prevent callbacks + // from being invoked), but we must not be holding + // callback_queue_mutex, as the callback must be able to + // call OPL_SetCallback to schedule new callbacks. + + SDL_UnlockMutex(callback_queue_mutex); + + SDL_LockMutex(callback_mutex); + callback(callback_data); + SDL_UnlockMutex(callback_mutex); + + SDL_LockMutex(callback_queue_mutex); + } + + SDL_UnlockMutex(callback_queue_mutex); +} + +// Call the OPL emulator code to fill the specified buffer. + +static void FillBuffer(int16_t *buffer, unsigned int nsamples) +{ + unsigned int i; + + // This seems like a reasonable assumption. mix_buffer is + // 1 second long, which should always be much longer than the + // SDL mix buffer. + + assert(nsamples < mixing_freq); + + YM3812UpdateOne(opl_emulator, mix_buffer, nsamples, 0); + + // Mix into the destination buffer, doubling up into stereo. + + for (i=0; i buffer_len - filled) + { + nsamples = buffer_len - filled; + } + } + + SDL_UnlockMutex(callback_queue_mutex); + + // Add emulator output to buffer. + + FillBuffer(buffer + filled * 2, nsamples); + filled += nsamples; + + // Invoke callbacks for this point in time. + + AdvanceTime(nsamples); + } } static void OPL_SDL_Shutdown(void) @@ -69,6 +216,7 @@ static void OPL_SDL_Shutdown(void) Mix_CloseAudio(); SDL_QuitSubSystem(SDL_INIT_AUDIO); OPL_Queue_Destroy(callback_queue); + free(mix_buffer); sdl_was_initialised = 0; } @@ -77,6 +225,18 @@ static void OPL_SDL_Shutdown(void) OPLDestroy(opl_emulator); opl_emulator = NULL; } + + if (callback_mutex != NULL) + { + SDL_DestroyMutex(callback_mutex); + callback_mutex = NULL; + } + + if (callback_queue_mutex != NULL) + { + SDL_DestroyMutex(callback_queue_mutex); + callback_queue_mutex = NULL; + } } static int OPL_SDL_Init(unsigned int port_base) @@ -86,8 +246,6 @@ static int OPL_SDL_Init(unsigned int port_base) if (!SDLIsInitialised()) { - callback_queue = OPL_Queue_Create(); - if (SDL_Init(SDL_INIT_AUDIO) < 0) { fprintf(stderr, "Unable to set up sound.\n"); @@ -114,6 +272,11 @@ static int OPL_SDL_Init(unsigned int port_base) sdl_was_initialised = 0; } + // Queue structure of callbacks to invoke. + + callback_queue = OPL_Queue_Create(); + current_time = 0; + // Get the mixer frequency, format and number of channels. Mix_QuerySpec(&mixing_freq, &mixing_format, &mixing_channels); @@ -130,6 +293,10 @@ static int OPL_SDL_Init(unsigned int port_base) return 0; } + // Mix buffer: + + mix_buffer = malloc(mixing_freq * 2); + // Create the emulator structure: opl_emulator = makeAdlibOPL(mixing_freq); @@ -141,6 +308,9 @@ static int OPL_SDL_Init(unsigned int port_base) return 0; } + callback_mutex = SDL_CreateMutex(); + callback_queue_mutex = SDL_CreateMutex(); + // TODO: This should be music callback? or-? Mix_SetPostMix(OPL_Mix_Callback, NULL); @@ -171,14 +341,20 @@ static void OPL_SDL_SetCallback(unsigned int ms, opl_callback_t callback, void *data) { + SDL_LockMutex(callback_queue_mutex); + OPL_Queue_Push(callback_queue, callback, data, + current_time + (ms * mixing_freq) / 1000); + SDL_UnlockMutex(callback_queue_mutex); } static void OPL_SDL_Lock(void) { + SDL_LockMutex(callback_mutex); } static void OPL_SDL_Unlock(void) { + SDL_UnlockMutex(callback_mutex); } opl_driver_t opl_sdl_driver = -- cgit v1.2.3 From ca065a06caac9ba5fab3eb8b1f49d529755506db Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Sat, 29 Aug 2009 20:08:21 +0000 Subject: Set timer callback for OPL emulator so that the adlib detection routine works. Subversion-branch: /branches/opl-branch Subversion-revision: 1633 --- opl/examples/droplay.c | 2 -- opl/opl_sdl.c | 25 +++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) (limited to 'opl') diff --git a/opl/examples/droplay.c b/opl/examples/droplay.c index 5158fbcd..b1656815 100644 --- a/opl/examples/droplay.c +++ b/opl/examples/droplay.c @@ -89,8 +89,6 @@ int DetectOPL(void) WriteReg(OPL_REG_TIMER_CTRL, 0x60); WriteReg(OPL_REG_TIMER_CTRL, 0x80); -// Temporary hack for SDL driver. -return 1; return val1 == 0 && val2 == 0xc0; } diff --git a/opl/opl_sdl.c b/opl/opl_sdl.c index 849a10b0..42fe3347 100644 --- a/opl/opl_sdl.c +++ b/opl/opl_sdl.c @@ -239,6 +239,29 @@ static void OPL_SDL_Shutdown(void) } } +// Callback when a timer expires. + +static void TimerOver(void *data) +{ + int channel = (int) data; + + OPLTimerOver(opl_emulator, channel); +} + +// Callback invoked when the emulator code wants to set a timer. + +static void TimerHandler(int channel, double interval_seconds) +{ + unsigned int interval_samples; + + interval_samples = (int) (interval_seconds * mixing_freq); + + SDL_LockMutex(callback_queue_mutex); + OPL_Queue_Push(callback_queue, TimerOver, (void *) channel, + current_time + interval_samples); + SDL_UnlockMutex(callback_queue_mutex); +} + static int OPL_SDL_Init(unsigned int port_base) { // Check if SDL_mixer has been opened already @@ -308,6 +331,8 @@ static int OPL_SDL_Init(unsigned int port_base) return 0; } + OPLSetTimerHandler(opl_emulator, TimerHandler, 0); + callback_mutex = SDL_CreateMutex(); callback_queue_mutex = SDL_CreateMutex(); -- cgit v1.2.3 From e7262d2a8859501152ef22bc7cb9dcc26b05c402 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Sat, 29 Aug 2009 20:12:49 +0000 Subject: Fix crash when specifying an invalid filename. Subversion-branch: /branches/opl-branch Subversion-revision: 1634 --- opl/examples/droplay.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'opl') diff --git a/opl/examples/droplay.c b/opl/examples/droplay.c index b1656815..4c2fc2f8 100644 --- a/opl/examples/droplay.c +++ b/opl/examples/droplay.c @@ -183,6 +183,12 @@ void PlayFile(char *filename) timer_data.fstream = fopen(filename, "rb"); + if (timer_data.fstream == NULL) + { + fprintf(stderr, "Failed to open %s\n", filename); + exit(-1); + } + if (fread(buf, 1, 8, timer_data.fstream) < 8) { fprintf(stderr, "failed to read raw OPL header\n"); @@ -226,7 +232,6 @@ int main(int argc, char *argv[]) Init(); ClearAllRegs(); - SDL_Delay(1000); PlayFile(argv[1]); -- cgit v1.2.3 From 6cae0f05cfedce28038f9411f03e6b1415250c33 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Sat, 29 Aug 2009 21:03:24 +0000 Subject: Don't crash if OPL is shutdown after SDL was initialised. Subversion-branch: /branches/opl-branch Subversion-revision: 1635 --- opl/opl_sdl.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'opl') diff --git a/opl/opl_sdl.c b/opl/opl_sdl.c index 42fe3347..b9d83385 100644 --- a/opl/opl_sdl.c +++ b/opl/opl_sdl.c @@ -211,6 +211,8 @@ static void OPL_Mix_Callback(void *udata, static void OPL_SDL_Shutdown(void) { + Mix_SetPostMix(NULL, NULL); + if (sdl_was_initialised) { Mix_CloseAudio(); -- cgit v1.2.3 From a10180a460f6425cd308719584aa58ab4fcb63fb Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Sat, 29 Aug 2009 21:26:43 +0000 Subject: Use OPL_Delay to wait 1ms for timer to expire when doing OPL detect. Subversion-branch: /branches/opl-branch Subversion-revision: 1638 --- opl/examples/droplay.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'opl') diff --git a/opl/examples/droplay.c b/opl/examples/droplay.c index 4c2fc2f8..d53a427b 100644 --- a/opl/examples/droplay.c +++ b/opl/examples/droplay.c @@ -81,11 +81,16 @@ int DetectOPL(void) WriteReg(OPL_REG_TIMER_CTRL, 0x60); WriteReg(OPL_REG_TIMER_CTRL, 0x80); + val1 = OPL_ReadPort(OPL_REGISTER_PORT) & 0xe0; + WriteReg(OPL_REG_TIMER1, 0xff); WriteReg(OPL_REG_TIMER_CTRL, 0x21); - OPL_Delay(50); + + OPL_Delay(1); + val2 = OPL_ReadPort(OPL_REGISTER_PORT) & 0xe0; + WriteReg(OPL_REG_TIMER_CTRL, 0x60); WriteReg(OPL_REG_TIMER_CTRL, 0x80); -- cgit v1.2.3 From 076adeb0aa24e9bdc677435ddb6e444db58d5436 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Sun, 30 Aug 2009 12:07:04 +0000 Subject: Add method to clear all existing callbacks. Subversion-branch: /branches/opl-branch Subversion-revision: 1642 --- opl/opl.c | 8 ++++++++ opl/opl.h | 4 ++++ opl/opl_internal.h | 2 ++ opl/opl_linux.c | 1 + opl/opl_queue.c | 5 +++++ opl/opl_queue.h | 1 + opl/opl_sdl.c | 8 ++++++++ opl/opl_timer.c | 7 +++++++ opl/opl_timer.h | 1 + 9 files changed, 37 insertions(+) (limited to 'opl') diff --git a/opl/opl.c b/opl/opl.c index e53b5d6e..6e75c951 100644 --- a/opl/opl.c +++ b/opl/opl.c @@ -117,6 +117,14 @@ void OPL_SetCallback(unsigned int ms, opl_callback_t callback, void *data) } } +void OPL_ClearCallbacks(void) +{ + if (driver != NULL) + { + driver->clear_callbacks_func(); + } +} + void OPL_Lock(void) { if (driver != NULL) diff --git a/opl/opl.h b/opl/opl.h index 3413e3ab..a0998404 100644 --- a/opl/opl.h +++ b/opl/opl.h @@ -79,6 +79,10 @@ unsigned int OPL_ReadPort(opl_port_t port); void OPL_SetCallback(unsigned int ms, opl_callback_t callback, void *data); +// Clear all OPL callbacks that have been set. + +void OPL_ClearCallbacks(void); + // Begin critical section, during which, OPL callbacks will not be // invoked. diff --git a/opl/opl_internal.h b/opl/opl_internal.h index cd125122..384b96f8 100644 --- a/opl/opl_internal.h +++ b/opl/opl_internal.h @@ -36,6 +36,7 @@ typedef void (*opl_write_port_func)(opl_port_t port, unsigned int value); typedef void (*opl_set_callback_func)(unsigned int ms, opl_callback_t callback, void *data); +typedef void (*opl_clear_callbacks_func)(void); typedef void (*opl_lock_func)(void); typedef void (*opl_unlock_func)(void); @@ -48,6 +49,7 @@ typedef struct opl_read_port_func read_port_func; opl_write_port_func write_port_func; opl_set_callback_func set_callback_func; + opl_clear_callbacks_func clear_callbacks_func; opl_lock_func lock_func; opl_unlock_func unlock_func; } opl_driver_t; diff --git a/opl/opl_linux.c b/opl/opl_linux.c index 4a10337f..8a61dbf7 100644 --- a/opl/opl_linux.c +++ b/opl/opl_linux.c @@ -92,6 +92,7 @@ opl_driver_t opl_linux_driver = OPL_Linux_PortRead, OPL_Linux_PortWrite, OPL_Timer_SetCallback, + OPL_Timer_ClearCallbacks, OPL_Timer_Lock, OPL_Timer_Unlock }; diff --git a/opl/opl_queue.c b/opl/opl_queue.c index fe5f1ef8..f9d4c377 100644 --- a/opl/opl_queue.c +++ b/opl/opl_queue.c @@ -65,6 +65,11 @@ int OPL_Queue_IsEmpty(opl_callback_queue_t *queue) return queue->num_entries == 0; } +void OPL_Queue_Clear(opl_callback_queue_t *queue) +{ + queue->num_entries = 0; +} + void OPL_Queue_Push(opl_callback_queue_t *queue, opl_callback_t callback, void *data, unsigned int time) diff --git a/opl/opl_queue.h b/opl/opl_queue.h index 6ead0010..2447702b 100644 --- a/opl/opl_queue.h +++ b/opl/opl_queue.h @@ -32,6 +32,7 @@ typedef struct opl_callback_queue_s opl_callback_queue_t; opl_callback_queue_t *OPL_Queue_Create(void); int OPL_Queue_IsEmpty(opl_callback_queue_t *queue); +void OPL_Queue_Clear(opl_callback_queue_t *queue); void OPL_Queue_Destroy(opl_callback_queue_t *queue); void OPL_Queue_Push(opl_callback_queue_t *queue, opl_callback_t callback, void *data, diff --git a/opl/opl_sdl.c b/opl/opl_sdl.c index b9d83385..ffbe6820 100644 --- a/opl/opl_sdl.c +++ b/opl/opl_sdl.c @@ -374,6 +374,13 @@ static void OPL_SDL_SetCallback(unsigned int ms, SDL_UnlockMutex(callback_queue_mutex); } +static void OPL_SDL_ClearCallbacks(void) +{ + SDL_LockMutex(callback_queue_mutex); + OPL_Queue_Clear(callback_queue); + SDL_UnlockMutex(callback_queue_mutex); +} + static void OPL_SDL_Lock(void) { SDL_LockMutex(callback_mutex); @@ -392,6 +399,7 @@ opl_driver_t opl_sdl_driver = OPL_SDL_PortRead, OPL_SDL_PortWrite, OPL_SDL_SetCallback, + OPL_SDL_ClearCallbacks, OPL_SDL_Lock, OPL_SDL_Unlock }; diff --git a/opl/opl_timer.c b/opl/opl_timer.c index 62ffbd37..e254a5e2 100644 --- a/opl/opl_timer.c +++ b/opl/opl_timer.c @@ -202,6 +202,13 @@ void OPL_Timer_SetCallback(unsigned int ms, opl_callback_t callback, void *data) SDL_UnlockMutex(callback_queue_mutex); } +void OPL_Timer_ClearCallbacks(void) +{ + SDL_LockMutex(callback_queue_mutex); + OPL_Queue_Clear(callback_queue); + SDL_UnlockMutex(callback_queue_mutex); +} + void OPL_Timer_Lock(void) { SDL_LockMutex(timer_mutex); diff --git a/opl/opl_timer.h b/opl/opl_timer.h index 26255df5..e8657a90 100644 --- a/opl/opl_timer.h +++ b/opl/opl_timer.h @@ -33,6 +33,7 @@ void OPL_Timer_StopThread(void); void OPL_Timer_SetCallback(unsigned int ms, opl_callback_t callback, void *data); +void OPL_Timer_ClearCallbacks(void); void OPL_Timer_Lock(void); void OPL_Timer_Unlock(void); -- cgit v1.2.3 From 79698ecfd9e025f28350566d2b89b223688a1b45 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Mon, 21 Sep 2009 22:20:33 +0000 Subject: Implement pausing of music. Subversion-branch: /branches/opl-branch Subversion-revision: 1688 --- opl/opl.c | 10 +++++++++- opl/opl.h | 4 ++++ opl/opl_internal.h | 2 ++ opl/opl_linux.c | 3 ++- opl/opl_sdl.c | 35 +++++++++++++++++++++++++++++------ opl/opl_timer.c | 34 ++++++++++++++++++++++++++++++++-- opl/opl_timer.h | 1 + 7 files changed, 79 insertions(+), 10 deletions(-) (limited to 'opl') diff --git a/opl/opl.c b/opl/opl.c index 6e75c951..9d73fe0e 100644 --- a/opl/opl.c +++ b/opl/opl.c @@ -33,7 +33,7 @@ #include "opl.h" #include "opl_internal.h" -//#define OPL_DEBUG_TRACE +#define OPL_DEBUG_TRACE #ifdef HAVE_IOPERM extern opl_driver_t opl_linux_driver; @@ -195,3 +195,11 @@ void OPL_Delay(unsigned int ms) SDL_DestroyCond(delay_data.cond); } +void OPL_SetPaused(int paused) +{ + if (driver != NULL) + { + driver->set_paused_func(paused); + } +} + diff --git a/opl/opl.h b/opl/opl.h index a0998404..f5b93a64 100644 --- a/opl/opl.h +++ b/opl/opl.h @@ -96,5 +96,9 @@ void OPL_Unlock(void); void OPL_Delay(unsigned int ms); +// Pause the OPL callbacks. + +void OPL_SetPaused(int paused); + #endif diff --git a/opl/opl_internal.h b/opl/opl_internal.h index 384b96f8..78cbe7b2 100644 --- a/opl/opl_internal.h +++ b/opl/opl_internal.h @@ -39,6 +39,7 @@ typedef void (*opl_set_callback_func)(unsigned int ms, typedef void (*opl_clear_callbacks_func)(void); typedef void (*opl_lock_func)(void); typedef void (*opl_unlock_func)(void); +typedef void (*opl_set_paused_func)(int paused); typedef struct { @@ -52,6 +53,7 @@ typedef struct opl_clear_callbacks_func clear_callbacks_func; opl_lock_func lock_func; opl_unlock_func unlock_func; + opl_set_paused_func set_paused_func; } opl_driver_t; #endif /* #ifndef OPL_INTERNAL_H */ diff --git a/opl/opl_linux.c b/opl/opl_linux.c index 8a61dbf7..089192c9 100644 --- a/opl/opl_linux.c +++ b/opl/opl_linux.c @@ -94,7 +94,8 @@ opl_driver_t opl_linux_driver = OPL_Timer_SetCallback, OPL_Timer_ClearCallbacks, OPL_Timer_Lock, - OPL_Timer_Unlock + OPL_Timer_Unlock, + OPL_Timer_SetPaused }; #endif /* #ifdef HAVE_IOPERM */ diff --git a/opl/opl_sdl.c b/opl/opl_sdl.c index ffbe6820..e38f9f6e 100644 --- a/opl/opl_sdl.c +++ b/opl/opl_sdl.c @@ -60,6 +60,15 @@ static SDL_mutex *callback_queue_mutex = NULL; static int current_time; +// If non-zero, playback is currently paused. + +static int opl_sdl_paused; + +// Time offset (in samples) due to the fact that callbacks +// were previously paused. + +static unsigned int pause_offset; + // OPL software emulator structure. static FM_OPL *opl_emulator = NULL; @@ -96,11 +105,16 @@ static void AdvanceTime(unsigned int nsamples) current_time += nsamples; + if (opl_sdl_paused) + { + pause_offset += nsamples; + } + // Are there callbacks to invoke now? Keep invoking them // until there are none more left. while (!OPL_Queue_IsEmpty(callback_queue) - && current_time >= OPL_Queue_Peek(callback_queue)) + && current_time >= OPL_Queue_Peek(callback_queue) + pause_offset) { // Pop the callback from the queue to invoke it. @@ -180,13 +194,13 @@ static void OPL_Mix_Callback(void *udata, // the callback queue must be invoked. We can then fill the // buffer with this many samples. - if (OPL_Queue_IsEmpty(callback_queue)) + if (opl_sdl_paused || OPL_Queue_IsEmpty(callback_queue)) { nsamples = buffer_len - filled; } else { - next_callback_time = OPL_Queue_Peek(callback_queue); + next_callback_time = OPL_Queue_Peek(callback_queue) + pause_offset; nsamples = next_callback_time - current_time; @@ -260,7 +274,7 @@ static void TimerHandler(int channel, double interval_seconds) SDL_LockMutex(callback_queue_mutex); OPL_Queue_Push(callback_queue, TimerOver, (void *) channel, - current_time + interval_samples); + current_time - pause_offset + interval_samples); SDL_UnlockMutex(callback_queue_mutex); } @@ -297,6 +311,9 @@ static int OPL_SDL_Init(unsigned int port_base) sdl_was_initialised = 0; } + opl_sdl_paused = 0; + pause_offset = 0; + // Queue structure of callbacks to invoke. callback_queue = OPL_Queue_Create(); @@ -370,7 +387,7 @@ static void OPL_SDL_SetCallback(unsigned int ms, { SDL_LockMutex(callback_queue_mutex); OPL_Queue_Push(callback_queue, callback, data, - current_time + (ms * mixing_freq) / 1000); + current_time - pause_offset + (ms * mixing_freq) / 1000); SDL_UnlockMutex(callback_queue_mutex); } @@ -391,6 +408,11 @@ static void OPL_SDL_Unlock(void) SDL_UnlockMutex(callback_mutex); } +static void OPL_SDL_SetPaused(int paused) +{ + opl_sdl_paused = paused; +} + opl_driver_t opl_sdl_driver = { "SDL", @@ -401,6 +423,7 @@ opl_driver_t opl_sdl_driver = OPL_SDL_SetCallback, OPL_SDL_ClearCallbacks, OPL_SDL_Lock, - OPL_SDL_Unlock + OPL_SDL_Unlock, + OPL_SDL_SetPaused }; diff --git a/opl/opl_timer.c b/opl/opl_timer.c index e254a5e2..35b2092f 100644 --- a/opl/opl_timer.c +++ b/opl/opl_timer.c @@ -41,6 +41,15 @@ static SDL_Thread *timer_thread = NULL; static thread_state_t timer_thread_state; static int current_time; +// If non-zero, callbacks are currently paused. + +static int opl_timer_paused; + +// Offset in milliseconds to adjust time due to the fact that playback +// was paused. + +static unsigned int pause_offset = 0; + // Queue of callbacks waiting to be invoked. // The callback queue mutex is held while the callback queue structure // or current_time is being accessed. @@ -59,6 +68,17 @@ static SDL_mutex *timer_mutex; static int CallbackWaiting(unsigned int *next_time) { + // If paused, just wait in 50ms increments until unpaused. + // Update pause_offset so after we unpause, the callback + // times will be right. + + if (opl_timer_paused) + { + *next_time = current_time + 50; + pause_offset += 50; + return 0; + } + // If there are no queued callbacks, sleep for 50ms at a time // until a callback is added. @@ -72,7 +92,7 @@ static int CallbackWaiting(unsigned int *next_time) // If the time for the callback has not yet arrived, // we must sleep until the callback time. - *next_time = OPL_Queue_Peek(callback_queue); + *next_time = OPL_Queue_Peek(callback_queue) + pause_offset; return *next_time <= current_time; } @@ -169,6 +189,8 @@ int OPL_Timer_StartThread(void) timer_thread_state = THREAD_STATE_RUNNING; current_time = SDL_GetTicks(); + opl_timer_paused = 0; + pause_offset = 0; timer_thread = SDL_CreateThread(ThreadFunction, NULL); @@ -198,7 +220,8 @@ void OPL_Timer_StopThread(void) void OPL_Timer_SetCallback(unsigned int ms, opl_callback_t callback, void *data) { SDL_LockMutex(callback_queue_mutex); - OPL_Queue_Push(callback_queue, callback, data, current_time + ms); + OPL_Queue_Push(callback_queue, callback, data, + current_time + ms - pause_offset); SDL_UnlockMutex(callback_queue_mutex); } @@ -219,3 +242,10 @@ void OPL_Timer_Unlock(void) SDL_UnlockMutex(timer_mutex); } +void OPL_Timer_SetPaused(int paused) +{ + SDL_LockMutex(callback_queue_mutex); + opl_timer_paused = paused; + SDL_UnlockMutex(callback_queue_mutex); +} + diff --git a/opl/opl_timer.h b/opl/opl_timer.h index e8657a90..f03fc499 100644 --- a/opl/opl_timer.h +++ b/opl/opl_timer.h @@ -36,6 +36,7 @@ void OPL_Timer_SetCallback(unsigned int ms, void OPL_Timer_ClearCallbacks(void); void OPL_Timer_Lock(void); void OPL_Timer_Unlock(void); +void OPL_Timer_SetPaused(int paused); #endif /* #ifndef OPL_TIMER_H */ -- cgit v1.2.3 From 07f50245788a5a5b259864c1631d4e97eaec92fb Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Thu, 24 Sep 2009 21:57:21 +0000 Subject: Disable OPL debug output. Subversion-branch: /branches/opl-branch Subversion-revision: 1689 --- opl/opl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'opl') diff --git a/opl/opl.c b/opl/opl.c index 9d73fe0e..9ec19fd4 100644 --- a/opl/opl.c +++ b/opl/opl.c @@ -33,7 +33,7 @@ #include "opl.h" #include "opl_internal.h" -#define OPL_DEBUG_TRACE +//#define OPL_DEBUG_TRACE #ifdef HAVE_IOPERM extern opl_driver_t opl_linux_driver; -- cgit v1.2.3 From 6f3d0abb12bcc05336e780414fe84a8f9f25ec26 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Sat, 26 Sep 2009 19:56:24 +0000 Subject: Add OpenBSD/NetBSD native OPL backend. Subversion-branch: /branches/opl-branch Subversion-revision: 1690 --- opl/Makefile.am | 1 + opl/opl.c | 6 ++++ opl/opl_obsd.c | 105 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 112 insertions(+) create mode 100644 opl/opl_obsd.c (limited to 'opl') diff --git a/opl/Makefile.am b/opl/Makefile.am index d48b491b..9a757fdb 100644 --- a/opl/Makefile.am +++ b/opl/Makefile.am @@ -8,6 +8,7 @@ noinst_LIBRARIES=libopl.a libopl_a_SOURCES = \ opl_internal.h \ opl.c opl.h \ + opl_obsd.c opl_obsd.h \ opl_linux.c \ opl_sdl.c \ opl_queue.c opl_queue.h \ diff --git a/opl/opl.c b/opl/opl.c index 9ec19fd4..f28cd63f 100644 --- a/opl/opl.c +++ b/opl/opl.c @@ -38,12 +38,18 @@ #ifdef HAVE_IOPERM extern opl_driver_t opl_linux_driver; #endif +#ifdef HAVE_LIBI386 +extern opl_driver_t opl_openbsd_driver; +#endif extern opl_driver_t opl_sdl_driver; static opl_driver_t *drivers[] = { #ifdef HAVE_IOPERM &opl_linux_driver, +#endif +#ifdef HAVE_LIBI386 + &opl_openbsd_driver, #endif &opl_sdl_driver, NULL diff --git a/opl/opl_obsd.c b/opl/opl_obsd.c new file mode 100644 index 00000000..a3a22ab8 --- /dev/null +++ b/opl/opl_obsd.c @@ -0,0 +1,105 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 2009 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +// DESCRIPTION: +// OPL OpenBSD interface (also NetBSD) +// +//----------------------------------------------------------------------------- + +#include "config.h" + +#ifdef HAVE_LIBI386 + +#include +#include +#include +#include + +#include +#include +#include + +#include "opl.h" +#include "opl_internal.h" +#include "opl_timer.h" + +static unsigned int opl_port_base; + +static int OPL_OpenBSD_Init(unsigned int port_base) +{ + // Try to get permissions: + + if (i386_iopl(1) < 0) + { + fprintf(stderr, "Failed to get raise I/O privilege level: " + "check that you are running as root.\n"); + return 0; + } + + opl_port_base = port_base; + + // Start callback thread + + if (!OPL_Timer_StartThread()) + { + i386_iopl(0); + return 0; + } + + return 1; +} + +static void OPL_OpenBSD_Shutdown(void) +{ + // Stop callback thread + + OPL_Timer_StopThread(); + + // Release I/O port permissions: + + i386_iopl(0); +} + +static unsigned int OPL_OpenBSD_PortRead(opl_port_t port) +{ + return inb(opl_port_base + port); +} + +static void OPL_OpenBSD_PortWrite(opl_port_t port, unsigned int value) +{ + outb(opl_port_base + port, value); +} + +opl_driver_t opl_openbsd_driver = +{ + "OpenBSD", + OPL_OpenBSD_Init, + OPL_OpenBSD_Shutdown, + OPL_OpenBSD_PortRead, + OPL_OpenBSD_PortWrite, + OPL_Timer_SetCallback, + OPL_Timer_ClearCallbacks, + OPL_Timer_Lock, + OPL_Timer_Unlock, + OPL_Timer_SetPaused +}; + +#endif /* #ifdef HAVE_IOPERM */ + -- cgit v1.2.3 From dce2c95f05b8f5ed734d1a1b75ccd7bfb2260557 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Sat, 26 Sep 2009 23:52:41 +0000 Subject: Move register read/write code into OPL library. Detect OPL in the library code, so that we fall back to software emulation if we have port access but an OPL is not detected. Fix detection of ioperm in configure. Subversion-branch: /branches/opl-branch Subversion-revision: 1692 --- opl/examples/droplay.c | 31 ------- opl/opl.c | 244 +++++++++++++++++++++++++++++++++++++++++++++++-- opl/opl.h | 31 ++++++- opl/opl_obsd.c | 2 +- 4 files changed, 269 insertions(+), 39 deletions(-) (limited to 'opl') diff --git a/opl/examples/droplay.c b/opl/examples/droplay.c index d53a427b..36f5c3c0 100644 --- a/opl/examples/droplay.c +++ b/opl/examples/droplay.c @@ -73,30 +73,6 @@ void ClearAllRegs(void) } } -// Detect an OPL chip. - -int DetectOPL(void) -{ - int val1, val2; - - WriteReg(OPL_REG_TIMER_CTRL, 0x60); - WriteReg(OPL_REG_TIMER_CTRL, 0x80); - - val1 = OPL_ReadPort(OPL_REGISTER_PORT) & 0xe0; - - WriteReg(OPL_REG_TIMER1, 0xff); - WriteReg(OPL_REG_TIMER_CTRL, 0x21); - - OPL_Delay(1); - - val2 = OPL_ReadPort(OPL_REGISTER_PORT) & 0xe0; - - WriteReg(OPL_REG_TIMER_CTRL, 0x60); - WriteReg(OPL_REG_TIMER_CTRL, 0x80); - - return val1 == 0 && val2 == 0xc0; -} - void Init(void) { if (SDL_Init(SDL_INIT_TIMER) < 0) @@ -110,12 +86,6 @@ void Init(void) fprintf(stderr, "Unable to initialise OPL layer\n"); exit(-1); } - - if (!DetectOPL()) - { - fprintf(stderr, "Adlib not detected\n"); - exit(-1); - } } void Shutdown(void) @@ -236,7 +206,6 @@ int main(int argc, char *argv[]) } Init(); - ClearAllRegs(); PlayFile(argv[1]); diff --git a/opl/opl.c b/opl/opl.c index f28cd63f..00c9b04a 100644 --- a/opl/opl.c +++ b/opl/opl.c @@ -1,4 +1,4 @@ -// Emacs style mode select -*- C++ -*- +// Emacs style mode select -*- C++ -*- //----------------------------------------------------------------------------- // // Copyright(C) 2009 Simon Howard @@ -28,6 +28,10 @@ #include #include +#ifdef _WIN32_WCE +#include "libc_wince.h" +#endif + #include "SDL.h" #include "opl.h" @@ -56,25 +60,113 @@ static opl_driver_t *drivers[] = }; static opl_driver_t *driver = NULL; +static int init_stage_reg_writes = 1; -int OPL_Init(unsigned int port_base) +// +// Init/shutdown code. +// + +// Initialize the specified driver and detect an OPL chip. Returns +// true if an OPL is detected. + +static int InitDriver(opl_driver_t *_driver, unsigned int port_base) { - int i; + // Initialize the driver. + + if (!_driver->init_func(port_base)) + { + return 0; + } + + // The driver was initialized okay, so we now have somewhere + // to write to. It doesn't mean there's an OPL chip there, + // though. Perform the detection sequence to make sure. + // (it's done twice, like how Doom does it). + + driver = _driver; + init_stage_reg_writes = 1; + + if (!OPL_Detect() || !OPL_Detect()) + { + printf("OPL_Init: No OPL detected using '%s' driver.\n", _driver->name); + _driver->shutdown_func(); + driver = NULL; + return 0; + } - // Try drivers until we find a working one: + // Initialize all registers. + + OPL_InitRegisters(); + + init_stage_reg_writes = 0; + + printf("OPL_Init: Using driver '%s'.\n", driver->name); + + return 1; +} + +// Find a driver automatically by trying each in the list. + +static int AutoSelectDriver(unsigned int port_base) +{ + int i; for (i=0; drivers[i] != NULL; ++i) { - if (drivers[i]->init_func(port_base)) + if (InitDriver(drivers[i], port_base)) { - driver = drivers[i]; return 1; } } + printf("OPL_Init: Failed to find a working driver.\n"); + return 0; } +// Initialize the OPL library. Returns true if initialized +// successfully. + +int OPL_Init(unsigned int port_base) +{ + char *driver_name; + int i; + + driver_name = getenv("OPL_DRIVER"); + + if (driver_name != NULL) + { + // Search the list until we find the driver with this name. + + for (i=0; drivers[i] != NULL; ++i) + { + if (!strcmp(driver_name, drivers[i]->name)) + { + if (InitDriver(drivers[i], port_base)) + { + return 1; + } + else + { + printf("OPL_Init: Failed to initialize " + "driver: '%s'.\n", driver_name); + return 0; + } + } + } + + printf("OPL_Init: unknown driver: '%s'.\n", driver_name); + + return 0; + } + else + { + return AutoSelectDriver(port_base); + } +} + +// Shut down the OPL library. + void OPL_Shutdown(void) { if (driver != NULL) @@ -115,6 +207,146 @@ unsigned int OPL_ReadPort(opl_port_t port) } } +// +// Higher-level functions, based on the lower-level functions above +// (register write, etc). +// + +unsigned int OPL_ReadStatus(void) +{ + return OPL_ReadPort(OPL_REGISTER_PORT); +} + +// Write an OPL register value + +void OPL_WriteRegister(int reg, int value) +{ + int i; + + OPL_WritePort(OPL_REGISTER_PORT, reg); + + // For timing, read the register port six times after writing the + // register number to cause the appropriate delay + + for (i=0; i<6; ++i) + { + // An oddity of the Doom OPL code: at startup initialisation, + // the spacing here is performed by reading from the register + // port; after initialisation, the data port is read, instead. + + if (init_stage_reg_writes) + { + OPL_ReadPort(OPL_REGISTER_PORT); + } + else + { + OPL_ReadPort(OPL_DATA_PORT); + } + } + + OPL_WritePort(OPL_DATA_PORT, value); + + // Read the register port 24 times after writing the value to + // cause the appropriate delay + + for (i=0; i<24; ++i) + { + OPL_ReadStatus(); + } +} + +// Detect the presence of an OPL chip + +int OPL_Detect(void) +{ + int result1, result2; + int i; + + // Reset both timers: + OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x60); + + // Enable interrupts: + OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x80); + + // Read status + result1 = OPL_ReadStatus(); + + // Set timer: + OPL_WriteRegister(OPL_REG_TIMER1, 0xff); + + // Start timer 1: + OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x21); + + // Wait for 80 microseconds + // This is how Doom does it: + + for (i=0; i<200; ++i) + { + OPL_ReadStatus(); + } + + OPL_Delay(1); + + // Read status + result2 = OPL_ReadStatus(); + + // Reset both timers: + OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x60); + + // Enable interrupts: + OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x80); + + return (result1 & 0xe0) == 0x00 + && (result2 & 0xe0) == 0xc0; +} + +// Initialize registers on startup + +void OPL_InitRegisters(void) +{ + int r; + + // Initialize level registers + + for (r=OPL_REGS_LEVEL; r <= OPL_REGS_LEVEL + OPL_NUM_OPERATORS; ++r) + { + OPL_WriteRegister(r, 0x3f); + } + + // Initialize other registers + // These two loops write to registers that actually don't exist, + // but this is what Doom does ... + // Similarly, the <= is also intenational. + + for (r=OPL_REGS_ATTACK; r <= OPL_REGS_WAVEFORM + OPL_NUM_OPERATORS; ++r) + { + OPL_WriteRegister(r, 0x00); + } + + // More registers ... + + for (r=1; r < OPL_REGS_LEVEL; ++r) + { + OPL_WriteRegister(r, 0x00); + } + + // Re-initialize the low registers: + + // Reset both timers and enable interrupts: + OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x60); + OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x80); + + // "Allow FM chips to control the waveform of each operator": + OPL_WriteRegister(OPL_REG_WAVEFORM_ENABLE, 0x20); + + // Keyboard split point on (?) + OPL_WriteRegister(OPL_REG_FM_MODE, 0x40); +} + +// +// Timer functions. +// + void OPL_SetCallback(unsigned int ms, opl_callback_t callback, void *data) { if (driver != NULL) diff --git a/opl/opl.h b/opl/opl.h index f5b93a64..9f5d0a9f 100644 --- a/opl/opl.h +++ b/opl/opl.h @@ -58,7 +58,11 @@ typedef enum #define OPL_REGS_FREQ_2 0xB0 #define OPL_REGS_FEEDBACK 0xC0 -// Initialise the OPL subsystem. +// +// Low-level functions. +// + +// Initialize the OPL subsystem. int OPL_Init(unsigned int port_base); @@ -74,6 +78,31 @@ void OPL_WritePort(opl_port_t port, unsigned int value); unsigned int OPL_ReadPort(opl_port_t port); +// +// Higher-level functions. +// + +// Read the cuurrent status byte of the OPL chip. + +unsigned int OPL_ReadStatus(void); + +// Write to an OPL register. + +void OPL_WriteRegister(int reg, int value); + +// Perform a detection sequence to determine that an +// OPL chip is present. + +int OPL_Detect(void); + +// Initialize all registers, performed on startup. + +void OPL_InitRegisters(void); + +// +// Timer callback functions. +// + // Set a timer callback. After the specified number of milliseconds // have elapsed, the callback will be invoked. diff --git a/opl/opl_obsd.c b/opl/opl_obsd.c index a3a22ab8..82d9186d 100644 --- a/opl/opl_obsd.c +++ b/opl/opl_obsd.c @@ -101,5 +101,5 @@ opl_driver_t opl_openbsd_driver = OPL_Timer_SetPaused }; -#endif /* #ifdef HAVE_IOPERM */ +#endif /* #ifdef HAVE_LIBI386 */ -- cgit v1.2.3 From de66c6b9672e4a97af96938517a1d954f32966ec Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Sun, 27 Sep 2009 00:40:55 +0000 Subject: Set privilege level to 3, not 1. Subversion-branch: /branches/opl-branch Subversion-revision: 1694 --- opl/opl_obsd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'opl') diff --git a/opl/opl_obsd.c b/opl/opl_obsd.c index 82d9186d..05574333 100644 --- a/opl/opl_obsd.c +++ b/opl/opl_obsd.c @@ -46,7 +46,7 @@ static int OPL_OpenBSD_Init(unsigned int port_base) { // Try to get permissions: - if (i386_iopl(1) < 0) + if (i386_iopl(3) < 0) { fprintf(stderr, "Failed to get raise I/O privilege level: " "check that you are running as root.\n"); -- cgit v1.2.3 From d484bfaf001faeb48fee28d046149108ab52a394 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Wed, 30 Sep 2009 00:11:44 +0000 Subject: Add Win9x native OPL driver (untested). Subversion-branch: /branches/opl-branch Subversion-revision: 1696 --- opl/Makefile.am | 5 ++- opl/opl.c | 6 +++ opl/opl_win9x.c | 137 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 146 insertions(+), 2 deletions(-) create mode 100644 opl/opl_win9x.c (limited to 'opl') diff --git a/opl/Makefile.am b/opl/Makefile.am index 9a757fdb..8bbed9f0 100644 --- a/opl/Makefile.am +++ b/opl/Makefile.am @@ -8,10 +8,11 @@ noinst_LIBRARIES=libopl.a libopl_a_SOURCES = \ opl_internal.h \ opl.c opl.h \ - opl_obsd.c opl_obsd.h \ opl_linux.c \ - opl_sdl.c \ + opl_obsd.c \ opl_queue.c opl_queue.h \ + opl_sdl.c \ opl_timer.c opl_timer.h \ + opl_win9x.c \ fmopl.c fmopl.h diff --git a/opl/opl.c b/opl/opl.c index 00c9b04a..749c19d1 100644 --- a/opl/opl.c +++ b/opl/opl.c @@ -45,6 +45,9 @@ extern opl_driver_t opl_linux_driver; #ifdef HAVE_LIBI386 extern opl_driver_t opl_openbsd_driver; #endif +#ifdef _WIN32 +extern opl_driver_t opl_win9x_driver; +#endif extern opl_driver_t opl_sdl_driver; static opl_driver_t *drivers[] = @@ -54,6 +57,9 @@ static opl_driver_t *drivers[] = #endif #ifdef HAVE_LIBI386 &opl_openbsd_driver, +#endif +#ifdef _WIN32 + &opl_win9x_driver, #endif &opl_sdl_driver, NULL diff --git a/opl/opl_win9x.c b/opl/opl_win9x.c new file mode 100644 index 00000000..04555760 --- /dev/null +++ b/opl/opl_win9x.c @@ -0,0 +1,137 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 2009 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +// DESCRIPTION: +// OPL Win9x native interface. +// +//----------------------------------------------------------------------------- + +#include "config.h" + +#ifdef _WIN32 + +#define WIN32_LEAN_AND_MEAN +#include + +#include "opl.h" +#include "opl_internal.h" +#include "opl_timer.h" + +static unsigned int opl_port_base; + +// MingW? + +#if defined(__GNUC__) && defined(__i386__) + +static unsigned int OPL_Win9x_PortRead(opl_port_t port) +{ + unsigned char result; + + __asm__ volatile ( + "movl %1, %%edx\n" + "inb %%dx, %%al\n" + "movb %%al, %0" + : "=m" (result) + : "r" (opl_port_base + port) + : "edx", "al", "memory" + ); + + return result; +} + +static void OPL_Win9x_PortWrite(opl_port_t port, unsigned int value) +{ + __asm__ volatile ( + "movl %0, %%edx\n" + "movb %1, %%al\n" + "outb %%al, %%dx" + : + : "r" (opl_port_base + port), "r" ((unsigned char) value) + : "edx", "al" + ); +} + +// TODO: MSVC version +// #elif defined(_MSC_VER) && defined(_M_IX6) ... + +#else + +// Not x86, or don't know how to do port R/W on this compiler. + +#define NO_PORT_RW + +static unsigned int OPL_Win9x_PortRead(opl_port_t port) +{ + return 0; +} + +static void OPL_Win9x_PortWrite(opl_port_t port, unsigned int value) +{ +} + +#endif + +static int OPL_Win9x_Init(unsigned int port_base) +{ +#ifndef NO_PORT_RW + + OSVERSIONINFO version_info; + + // Check that this is a Windows 9x series OS: + + GetVersionEx(&version_info); + + if (version_info.dwPlatformId == 1) + { + opl_port_base = port_base; + + // Start callback thread + + return OPL_Timer_StartThread(); + } + +#endif + + return 0; +} + +static void OPL_Win9x_Shutdown(void) +{ + // Stop callback thread + + OPL_Timer_StopThread(); +} + +opl_driver_t opl_win9x_driver = +{ + "Win9x", + OPL_Win9x_Init, + OPL_Win9x_Shutdown, + OPL_Win9x_PortRead, + OPL_Win9x_PortWrite, + OPL_Timer_SetCallback, + OPL_Timer_ClearCallbacks, + OPL_Timer_Lock, + OPL_Timer_Unlock, + OPL_Timer_SetPaused +}; + +#endif /* #ifdef _WIN32 */ + -- cgit v1.2.3 From b8b755758cfa1e297332e5edf8e9e166ddf327ac Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Wed, 30 Sep 2009 01:13:18 +0000 Subject: Initialize dwOSVersionInfoSize before calling GetVersionEx(). Subversion-branch: /branches/opl-branch Subversion-revision: 1697 --- opl/opl_win9x.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'opl') diff --git a/opl/opl_win9x.c b/opl/opl_win9x.c index 04555760..ff527b3e 100644 --- a/opl/opl_win9x.c +++ b/opl/opl_win9x.c @@ -96,6 +96,9 @@ static int OPL_Win9x_Init(unsigned int port_base) // Check that this is a Windows 9x series OS: + memset(&version_info, 0, sizeof(version_info)); + version_info.dwOSVersionInfoSize = sizeof(version_info); + GetVersionEx(&version_info); if (version_info.dwPlatformId == 1) -- cgit v1.2.3 From 4a70f989d2aacabffc2f02017704de042be7418a Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Thu, 1 Oct 2009 00:08:48 +0000 Subject: Convert to American English spellings. Subversion-branch: /branches/opl-branch Subversion-revision: 1700 --- opl/opl.c | 4 ++-- opl/opl_sdl.c | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) (limited to 'opl') diff --git a/opl/opl.c b/opl/opl.c index 749c19d1..bf999d47 100644 --- a/opl/opl.c +++ b/opl/opl.c @@ -236,9 +236,9 @@ void OPL_WriteRegister(int reg, int value) for (i=0; i<6; ++i) { - // An oddity of the Doom OPL code: at startup initialisation, + // An oddity of the Doom OPL code: at startup initialization, // the spacing here is performed by reading from the register - // port; after initialisation, the data port is read, instead. + // port; after initialization, the data port is read, instead. if (init_stage_reg_writes) { diff --git a/opl/opl_sdl.c b/opl/opl_sdl.c index e38f9f6e..40430546 100644 --- a/opl/opl_sdl.c +++ b/opl/opl_sdl.c @@ -79,11 +79,11 @@ static int16_t *mix_buffer = NULL; // SDL parameters. -static int sdl_was_initialised = 0; +static int sdl_was_initialized = 0; static int mixing_freq, mixing_channels; static Uint16 mixing_format; -static int SDLIsInitialised(void) +static int SDLIsInitialized(void) { int freq, channels; Uint16 format; @@ -227,13 +227,13 @@ static void OPL_SDL_Shutdown(void) { Mix_SetPostMix(NULL, NULL); - if (sdl_was_initialised) + if (sdl_was_initialized) { Mix_CloseAudio(); SDL_QuitSubSystem(SDL_INIT_AUDIO); OPL_Queue_Destroy(callback_queue); free(mix_buffer); - sdl_was_initialised = 0; + sdl_was_initialized = 0; } if (opl_emulator != NULL) @@ -281,9 +281,9 @@ static void TimerHandler(int channel, double interval_seconds) static int OPL_SDL_Init(unsigned int port_base) { // Check if SDL_mixer has been opened already - // If not, we must initialise it now + // If not, we must initialize it now - if (!SDLIsInitialised()) + if (!SDLIsInitialized()) { if (SDL_Init(SDL_INIT_AUDIO) < 0) { @@ -304,11 +304,11 @@ static int OPL_SDL_Init(unsigned int port_base) // When this module shuts down, it has the responsibility to // shut down SDL. - sdl_was_initialised = 1; + sdl_was_initialized = 1; } else { - sdl_was_initialised = 0; + sdl_was_initialized = 0; } opl_sdl_paused = 0; @@ -345,7 +345,7 @@ static int OPL_SDL_Init(unsigned int port_base) if (opl_emulator == NULL) { - fprintf(stderr, "Failed to initialise software OPL emulator!\n"); + fprintf(stderr, "Failed to initialize software OPL emulator!\n"); OPL_SDL_Shutdown(); return 0; } -- cgit v1.2.3 From 073e710f685a10c2084f6c04fbc4fe7c87530d93 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Sat, 3 Oct 2009 22:43:07 +0000 Subject: Use Mix_HookMusic rather than Mix_SetPostMix for OPL emulation, to avoid conflict with PC speaker emulation. Subversion-branch: /branches/opl-branch Subversion-revision: 1706 --- opl/opl_sdl.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'opl') diff --git a/opl/opl_sdl.c b/opl/opl_sdl.c index 40430546..4a2e1ff8 100644 --- a/opl/opl_sdl.c +++ b/opl/opl_sdl.c @@ -160,8 +160,8 @@ static void FillBuffer(int16_t *buffer, unsigned int nsamples) for (i=0; i -#include -#include -#include +#if defined(HAVE_LIBI386) #include #include #include +#define set_iopl i386_iopl + +#elif defined(HAVE_LIBAMD64) + +#include +#include +#include +#define set_iopl amd64_iopl + +#else +#define NO_OBSD_DRIVER +#endif + +// If the above succeeded, proceed with the rest. + +#ifndef NO_OBSD_DRIVER + +#include +#include +#include +#include #include "opl.h" #include "opl_internal.h" @@ -46,7 +66,7 @@ static int OPL_OpenBSD_Init(unsigned int port_base) { // Try to get permissions: - if (i386_iopl(3) < 0) + if (set_iopl(3) < 0) { fprintf(stderr, "Failed to get raise I/O privilege level: " "check that you are running as root.\n"); @@ -59,7 +79,7 @@ static int OPL_OpenBSD_Init(unsigned int port_base) if (!OPL_Timer_StartThread()) { - i386_iopl(0); + set_iopl(0); return 0; } @@ -74,7 +94,7 @@ static void OPL_OpenBSD_Shutdown(void) // Release I/O port permissions: - i386_iopl(0); + set_iopl(0); } static unsigned int OPL_OpenBSD_PortRead(opl_port_t port) @@ -101,5 +121,5 @@ opl_driver_t opl_openbsd_driver = OPL_Timer_SetPaused }; -#endif /* #ifdef HAVE_LIBI386 */ +#endif /* #ifndef NO_OBSD_DRIVER */ -- cgit v1.2.3 From e30325c40f6ea482862745db0f4555e513f2952e Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Sat, 17 Oct 2009 22:36:15 +0000 Subject: Add OPL library API function to set software emulation sample rate, and set from snd_samplerate in the configuration file. Subversion-branch: /branches/opl-branch Subversion-revision: 1723 --- opl/opl.c | 9 +++++++++ opl/opl.h | 4 ++++ opl/opl_internal.h | 4 ++++ opl/opl_sdl.c | 19 +++++++++++++++---- 4 files changed, 32 insertions(+), 4 deletions(-) (limited to 'opl') diff --git a/opl/opl.c b/opl/opl.c index 8e57647e..2c8fd692 100644 --- a/opl/opl.c +++ b/opl/opl.c @@ -68,6 +68,8 @@ static opl_driver_t *drivers[] = static opl_driver_t *driver = NULL; static int init_stage_reg_writes = 1; +unsigned int opl_sample_rate = 22050; + // // Init/shutdown code. // @@ -182,6 +184,13 @@ void OPL_Shutdown(void) } } +// Set the sample rate used for software OPL emulation. + +void OPL_SetSampleRate(unsigned int rate) +{ + opl_sample_rate = rate; +} + void OPL_WritePort(opl_port_t port, unsigned int value) { if (driver != NULL) diff --git a/opl/opl.h b/opl/opl.h index 9f5d0a9f..04d3cf27 100644 --- a/opl/opl.h +++ b/opl/opl.h @@ -70,6 +70,10 @@ int OPL_Init(unsigned int port_base); void OPL_Shutdown(void); +// Set the sample rate used for software emulation. + +void OPL_SetSampleRate(unsigned int rate); + // Write to one of the OPL I/O ports: void OPL_WritePort(opl_port_t port, unsigned int value); diff --git a/opl/opl_internal.h b/opl/opl_internal.h index 78cbe7b2..4a46b060 100644 --- a/opl/opl_internal.h +++ b/opl/opl_internal.h @@ -56,5 +56,9 @@ typedef struct opl_set_paused_func set_paused_func; } opl_driver_t; +// Sample rate to use when doing software emulation. + +extern unsigned int opl_sample_rate; + #endif /* #ifndef OPL_INTERNAL_H */ diff --git a/opl/opl_sdl.c b/opl/opl_sdl.c index 4a2e1ff8..2eb8288f 100644 --- a/opl/opl_sdl.c +++ b/opl/opl_sdl.c @@ -40,9 +40,6 @@ #include "opl_queue.h" -// TODO: -#define opl_sample_rate 22050 - // When the callback mutex is locked using OPL_Lock, callback functions // are not invoked. @@ -278,6 +275,20 @@ static void TimerHandler(int channel, double interval_seconds) SDL_UnlockMutex(callback_queue_mutex); } +static unsigned int GetSliceSize(void) +{ + unsigned int slicesize; + + slicesize = 1024 * (opl_sample_rate / 11025); + + if (slicesize <= 1024) + { + slicesize = 1024; + } + + return slicesize; +} + static int OPL_SDL_Init(unsigned int port_base) { // Check if SDL_mixer has been opened already @@ -291,7 +302,7 @@ static int OPL_SDL_Init(unsigned int port_base) return 0; } - if (Mix_OpenAudio(opl_sample_rate, AUDIO_S16SYS, 2, 1024) < 0) + if (Mix_OpenAudio(opl_sample_rate, AUDIO_S16SYS, 2, GetSliceSize()) < 0) { fprintf(stderr, "Error initialising SDL_mixer: %s\n", Mix_GetError()); -- cgit v1.2.3 From a8e79308562fbcea7a39ed1846329959cbf343b3 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Sat, 17 Oct 2009 22:45:22 +0000 Subject: Change GetSliceSize() to always return a power of two. Subversion-branch: /branches/opl-branch Subversion-revision: 1724 --- opl/opl_sdl.c | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) (limited to 'opl') diff --git a/opl/opl_sdl.c b/opl/opl_sdl.c index 2eb8288f..1963d5cd 100644 --- a/opl/opl_sdl.c +++ b/opl/opl_sdl.c @@ -40,6 +40,8 @@ #include "opl_queue.h" +#define MAX_SOUND_SLICE_TIME 100 /* ms */ + // When the callback mutex is locked using OPL_Lock, callback functions // are not invoked. @@ -277,16 +279,26 @@ static void TimerHandler(int channel, double interval_seconds) static unsigned int GetSliceSize(void) { - unsigned int slicesize; + int limit; + int n; + + limit = (opl_sample_rate * MAX_SOUND_SLICE_TIME) / 1000; - slicesize = 1024 * (opl_sample_rate / 11025); + // Try all powers of two, not exceeding the limit. - if (slicesize <= 1024) + for (n=0;; ++n) { - slicesize = 1024; + // 2^n <= limit < 2^n+1 ? + + if ((1 << (n + 1)) > limit) + { + return (1 << n); + } } - return slicesize; + // Should never happen? + + return 1024; } static int OPL_SDL_Init(unsigned int port_base) -- cgit v1.2.3 From cdacf59acecd944f4a573b3e112c0c43b052f975 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Fri, 5 Feb 2010 03:27:58 +0000 Subject: Add a hint message about permissions if unable to get I/O permissions for hardware OPL access. Subversion-branch: /branches/opl-branch Subversion-revision: 1844 --- opl/opl_linux.c | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'opl') diff --git a/opl/opl_linux.c b/opl/opl_linux.c index 089192c9..319686b8 100644 --- a/opl/opl_linux.c +++ b/opl/opl_linux.c @@ -47,6 +47,14 @@ static int OPL_Linux_Init(unsigned int port_base) { fprintf(stderr, "Failed to get I/O port permissions for 0x%x: %s\n", port_base, strerror(errno)); + + if (errno == EPERM) + { + fprintf(stderr, + "\tYou may need to run the program as root in order\n" + "\tto acquire I/O port permissions for OPL MIDI playback.\n"); + } + return 0; } -- cgit v1.2.3 From 06b97d2d116b622bc067b245f81b2857767d598e Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Fri, 26 Feb 2010 21:07:59 +0000 Subject: Add OPL hardware playback support for Windows NT-based systems. Subversion-branch: /branches/opl-branch Subversion-revision: 1871 --- opl/Makefile.am | 3 +- opl/ioperm_sys.c | 255 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ opl/ioperm_sys.h | 36 ++++++++ opl/opl.c | 11 ++- opl/opl_win32.c | 172 +++++++++++++++++++++++++++++++++++++ opl/opl_win9x.c | 140 ------------------------------ 6 files changed, 474 insertions(+), 143 deletions(-) create mode 100644 opl/ioperm_sys.c create mode 100644 opl/ioperm_sys.h create mode 100644 opl/opl_win32.c delete mode 100644 opl/opl_win9x.c (limited to 'opl') diff --git a/opl/Makefile.am b/opl/Makefile.am index 8bbed9f0..d099b875 100644 --- a/opl/Makefile.am +++ b/opl/Makefile.am @@ -13,6 +13,7 @@ libopl_a_SOURCES = \ opl_queue.c opl_queue.h \ opl_sdl.c \ opl_timer.c opl_timer.h \ - opl_win9x.c \ + opl_win32.c \ + ioperm_sys.c ioperm_sys.h \ fmopl.c fmopl.h diff --git a/opl/ioperm_sys.c b/opl/ioperm_sys.c new file mode 100644 index 00000000..37512b63 --- /dev/null +++ b/opl/ioperm_sys.c @@ -0,0 +1,255 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 2002, 2003 Marcel Telka +// Copyright(C) 2009 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +// DESCRIPTION: +// Interface to the ioperm.sys driver, based on code from the +// Cygwin ioperm library. +// +//----------------------------------------------------------------------------- + +#ifdef _WIN32 + +#include + +#define WIN32_LEAN_AND_MEAN +#include +#include + +#include + +#define IOPERM_FILE "\\\\.\\ioperm" + +#define IOCTL_IOPERM \ + CTL_CODE(FILE_DEVICE_UNKNOWN, 0xA00, METHOD_BUFFERED, FILE_ANY_ACCESS) + +struct ioperm_data +{ + unsigned long from; + unsigned long num; + int turn_on; +}; + +static SC_HANDLE scm = NULL; +static SC_HANDLE svc = NULL; +static int service_was_created = 0; +static int service_was_started = 0; + +int IOperm_EnablePortRange(unsigned int from, unsigned int num, int turn_on) +{ + HANDLE h; + struct ioperm_data ioperm_data; + DWORD BytesReturned; + BOOL r; + + h = CreateFile(IOPERM_FILE, GENERIC_READ, 0, NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + + if (h == INVALID_HANDLE_VALUE) + { + errno = ENODEV; + return -1; + } + + ioperm_data.from = from; + ioperm_data.num = num; + ioperm_data.turn_on = turn_on; + + r = DeviceIoControl(h, IOCTL_IOPERM, + &ioperm_data, sizeof ioperm_data, + NULL, 0, + &BytesReturned, NULL); + + if (!r) + { + errno = EPERM; + } + + CloseHandle(h); + + return r != 0; +} + +// Load ioperm.sys driver. +// Returns 1 for success, 0 for failure. +// Remember to call IOperm_UninstallDriver to uninstall the driver later. + +int IOperm_InstallDriver(void) +{ + int error; + int result = 1; + + scm = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); + + if (scm == NULL) + { + error = GetLastError(); + fprintf(stderr, "IOperm_InstallDriver: OpenSCManager failed (%i)\n", + error); + return 0; + } + + svc = CreateService(scm, + TEXT("ioperm"), + TEXT("I/O port access driver"), + SERVICE_ALL_ACCESS, + SERVICE_KERNEL_DRIVER, + SERVICE_AUTO_START, + SERVICE_ERROR_NORMAL, + "ioperm.sys", + NULL, + NULL, + NULL, + NULL, + NULL); + + if (svc == NULL) + { + error = GetLastError(); + + if (error != ERROR_SERVICE_EXISTS) + { + fprintf(stderr, + "IOperm_InstallDriver: Failed to create service (%i)\n", + error); + } + else + { + svc = OpenService(scm, TEXT("ioperm"), SERVICE_ALL_ACCESS); + + if (svc == NULL) + { + error = GetLastError(); + + fprintf(stderr, + "IOperm_InstallDriver: Failed to open service (%i)\n", + error); + } + } + + if (svc == NULL) + { + CloseServiceHandle(scm); + return 0; + } + } + else + { + service_was_created = 1; + } + + if (!StartService(svc, 0, NULL)) + { + error = GetLastError(); + + if (error != ERROR_SERVICE_ALREADY_RUNNING) + { + fprintf(stderr, "IOperm_InstallDriver: Failed to start service (%i)\n", + error); + result = 0; + } + else + { + printf("IOperm_InstallDriver: ioperm driver already running\n"); + } + } + else + { + printf("IOperm_InstallDriver: ioperm driver installed\n"); + service_was_started = 1; + } + + if (result == 0) + { + CloseServiceHandle(svc); + CloseServiceHandle(scm); + } + + return result; +} + +int IOperm_UninstallDriver(void) +{ + SERVICE_STATUS stat; + int result = 1; + int error; + + // If we started the service, stop it. + + if (service_was_started) + { + if (!ControlService(svc, SERVICE_CONTROL_STOP, &stat)) + { + error = GetLastError(); + + if (error == ERROR_SERVICE_NOT_ACTIVE) + { + fprintf(stderr, + "IOperm_UninstallDriver: Service not active? (%i)\n", + error); + } + else + { + fprintf(stderr, + "IOperm_UninstallDriver: Failed to stop service (%i)\n", + error); + result = 0; + } + } + } + + // If we created the service, delete it. + + if (service_was_created) + { + if (!DeleteService(svc)) + { + error = GetLastError(); + + fprintf(stderr, + "IOperm_UninstallDriver: DeleteService failed (%i)\n", + error); + + result = 0; + } + } + + // Close handles. + + if (svc != NULL) + { + CloseServiceHandle(svc); + svc = NULL; + } + + if (scm != NULL) + { + CloseServiceHandle(scm); + scm = NULL; + } + + service_was_created = 0; + service_was_started = 0; + + return result; +} + +#endif /* #ifndef _WIN32 */ + diff --git a/opl/ioperm_sys.h b/opl/ioperm_sys.h new file mode 100644 index 00000000..faf17bf3 --- /dev/null +++ b/opl/ioperm_sys.h @@ -0,0 +1,36 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 2002, 2003 Marcel Telka +// Copyright(C) 2009 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +// DESCRIPTION: +// Interface to the ioperm.sys driver, based on code from the +// Cygwin ioperm library. +// +//----------------------------------------------------------------------------- + +#ifndef IOPERM_SYS_H +#define IOPERM_SYS_H + +int IOperm_EnablePortRange(unsigned int from, unsigned int num, int turn_on); +int IOperm_InstallDriver(void); +int IOperm_UninstallDriver(void); + +#endif /* #ifndef IOPERM_SYS_H */ + diff --git a/opl/opl.c b/opl/opl.c index 2c8fd692..9e674530 100644 --- a/opl/opl.c +++ b/opl/opl.c @@ -46,7 +46,7 @@ extern opl_driver_t opl_linux_driver; extern opl_driver_t opl_openbsd_driver; #endif #ifdef _WIN32 -extern opl_driver_t opl_win9x_driver; +extern opl_driver_t opl_win32_driver; #endif extern opl_driver_t opl_sdl_driver; @@ -59,7 +59,7 @@ static opl_driver_t *drivers[] = &opl_openbsd_driver, #endif #ifdef _WIN32 - &opl_win9x_driver, + &opl_win32_driver, #endif &opl_sdl_driver, NULL @@ -197,6 +197,7 @@ void OPL_WritePort(opl_port_t port, unsigned int value) { #ifdef OPL_DEBUG_TRACE printf("OPL_write: %i, %x\n", port, value); + fflush(stdout); #endif driver->write_port_func(port, value); } @@ -208,10 +209,16 @@ unsigned int OPL_ReadPort(opl_port_t port) { unsigned int result; +#ifdef OPL_DEBUG_TRACE + printf("OPL_read: %i...\n", port); + fflush(stdout); +#endif + result = driver->read_port_func(port); #ifdef OPL_DEBUG_TRACE printf("OPL_read: %i -> %x\n", port, result); + fflush(stdout); #endif return result; diff --git a/opl/opl_win32.c b/opl/opl_win32.c new file mode 100644 index 00000000..29df3643 --- /dev/null +++ b/opl/opl_win32.c @@ -0,0 +1,172 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 2009 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +// DESCRIPTION: +// OPL Win32 native interface. +// +//----------------------------------------------------------------------------- + +#include "config.h" + +#ifdef _WIN32 + +#include + +#define WIN32_LEAN_AND_MEAN +#include + +#include "opl.h" +#include "opl_internal.h" +#include "opl_timer.h" + +#include "ioperm_sys.h" + +static unsigned int opl_port_base; + +// MingW? + +#if defined(__GNUC__) && defined(__i386__) + +static unsigned int OPL_Win32_PortRead(opl_port_t port) +{ + unsigned char result; + + __asm__ volatile ( + "movl %1, %%edx\n" + "inb %%dx, %%al\n" + "movb %%al, %0" + : "=m" (result) + : "r" (opl_port_base + port) + : "edx", "al", "memory" + ); + + return result; +} + +static void OPL_Win32_PortWrite(opl_port_t port, unsigned int value) +{ + __asm__ volatile ( + "movl %0, %%edx\n" + "movb %1, %%al\n" + "outb %%al, %%dx" + : + : "r" (opl_port_base + port), "r" ((unsigned char) value) + : "edx", "al" + ); +} + +// TODO: MSVC version +// #elif defined(_MSC_VER) && defined(_M_IX6) ... + +#else + +// Not x86, or don't know how to do port R/W on this compiler. + +#define NO_PORT_RW + +static unsigned int OPL_Win32_PortRead(opl_port_t port) +{ + return 0; +} + +static void OPL_Win32_PortWrite(opl_port_t port, unsigned int value) +{ +} + +#endif + +static int OPL_Win32_Init(unsigned int port_base) +{ +#ifndef NO_PORT_RW + + OSVERSIONINFO version_info; + + opl_port_base = port_base; + + // Check the OS version. + + memset(&version_info, 0, sizeof(version_info)); + version_info.dwOSVersionInfoSize = sizeof(version_info); + + GetVersionEx(&version_info); + + // On NT-based systems, we must acquire I/O port permissions + // using the ioperm.sys driver. + + if (version_info.dwPlatformId == VER_PLATFORM_WIN32_NT) + { + // Install driver. + + if (!IOperm_InstallDriver()) + { + return 0; + } + + // Open port range. + + if (!IOperm_EnablePortRange(opl_port_base, 2, 1)) + { + IOperm_UninstallDriver(); + return 0; + } + } + + // Start callback thread + + if (!OPL_Timer_StartThread()) + { + IOperm_UninstallDriver(); + return 0; + } + + return 1; + +#endif + + return 0; +} + +static void OPL_Win32_Shutdown(void) +{ + // Stop callback thread + + OPL_Timer_StopThread(); + + // Unload IOperm library. + + IOperm_UninstallDriver(); +} + +opl_driver_t opl_win32_driver = +{ + "Win32", + OPL_Win32_Init, + OPL_Win32_Shutdown, + OPL_Win32_PortRead, + OPL_Win32_PortWrite, + OPL_Timer_SetCallback, + OPL_Timer_ClearCallbacks, + OPL_Timer_Lock, + OPL_Timer_Unlock, + OPL_Timer_SetPaused +}; + +#endif /* #ifdef _WIN32 */ + diff --git a/opl/opl_win9x.c b/opl/opl_win9x.c deleted file mode 100644 index ff527b3e..00000000 --- a/opl/opl_win9x.c +++ /dev/null @@ -1,140 +0,0 @@ -// Emacs style mode select -*- C++ -*- -//----------------------------------------------------------------------------- -// -// Copyright(C) 2009 Simon Howard -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA -// 02111-1307, USA. -// -// DESCRIPTION: -// OPL Win9x native interface. -// -//----------------------------------------------------------------------------- - -#include "config.h" - -#ifdef _WIN32 - -#define WIN32_LEAN_AND_MEAN -#include - -#include "opl.h" -#include "opl_internal.h" -#include "opl_timer.h" - -static unsigned int opl_port_base; - -// MingW? - -#if defined(__GNUC__) && defined(__i386__) - -static unsigned int OPL_Win9x_PortRead(opl_port_t port) -{ - unsigned char result; - - __asm__ volatile ( - "movl %1, %%edx\n" - "inb %%dx, %%al\n" - "movb %%al, %0" - : "=m" (result) - : "r" (opl_port_base + port) - : "edx", "al", "memory" - ); - - return result; -} - -static void OPL_Win9x_PortWrite(opl_port_t port, unsigned int value) -{ - __asm__ volatile ( - "movl %0, %%edx\n" - "movb %1, %%al\n" - "outb %%al, %%dx" - : - : "r" (opl_port_base + port), "r" ((unsigned char) value) - : "edx", "al" - ); -} - -// TODO: MSVC version -// #elif defined(_MSC_VER) && defined(_M_IX6) ... - -#else - -// Not x86, or don't know how to do port R/W on this compiler. - -#define NO_PORT_RW - -static unsigned int OPL_Win9x_PortRead(opl_port_t port) -{ - return 0; -} - -static void OPL_Win9x_PortWrite(opl_port_t port, unsigned int value) -{ -} - -#endif - -static int OPL_Win9x_Init(unsigned int port_base) -{ -#ifndef NO_PORT_RW - - OSVERSIONINFO version_info; - - // Check that this is a Windows 9x series OS: - - memset(&version_info, 0, sizeof(version_info)); - version_info.dwOSVersionInfoSize = sizeof(version_info); - - GetVersionEx(&version_info); - - if (version_info.dwPlatformId == 1) - { - opl_port_base = port_base; - - // Start callback thread - - return OPL_Timer_StartThread(); - } - -#endif - - return 0; -} - -static void OPL_Win9x_Shutdown(void) -{ - // Stop callback thread - - OPL_Timer_StopThread(); -} - -opl_driver_t opl_win9x_driver = -{ - "Win9x", - OPL_Win9x_Init, - OPL_Win9x_Shutdown, - OPL_Win9x_PortRead, - OPL_Win9x_PortWrite, - OPL_Timer_SetCallback, - OPL_Timer_ClearCallbacks, - OPL_Timer_Lock, - OPL_Timer_Unlock, - OPL_Timer_SetPaused -}; - -#endif /* #ifdef _WIN32 */ - -- cgit v1.2.3 From 1398c8aed38b0d42e7081410ae1710858d335f7e Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Sat, 27 Feb 2010 17:48:25 +0000 Subject: Fix race condition with condition variable freed before it is signaled. Subversion-branch: /branches/opl-branch Subversion-revision: 1873 --- opl/opl.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'opl') diff --git a/opl/opl.c b/opl/opl.c index 9e674530..6d0e16db 100644 --- a/opl/opl.c +++ b/opl/opl.c @@ -415,9 +415,10 @@ static void DelayCallback(void *_delay_data) SDL_LockMutex(delay_data->mutex); delay_data->finished = 1; - SDL_UnlockMutex(delay_data->mutex); SDL_CondSignal(delay_data->cond); + + SDL_UnlockMutex(delay_data->mutex); } void OPL_Delay(unsigned int ms) -- cgit v1.2.3 From 77ea97c6277cc7261e332deec5d48fbddd88821a Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Sat, 27 Feb 2010 18:57:46 +0000 Subject: When loading driver, pass the full path to the ioperm.sys file. Subversion-branch: /branches/opl-branch Subversion-revision: 1874 --- opl/ioperm_sys.c | 37 ++++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 13 deletions(-) (limited to 'opl') diff --git a/opl/ioperm_sys.c b/opl/ioperm_sys.c index 37512b63..4c06d97a 100644 --- a/opl/ioperm_sys.c +++ b/opl/ioperm_sys.c @@ -93,6 +93,7 @@ int IOperm_EnablePortRange(unsigned int from, unsigned int num, int turn_on) int IOperm_InstallDriver(void) { + wchar_t driver_path[MAX_PATH]; int error; int result = 1; @@ -106,19 +107,25 @@ int IOperm_InstallDriver(void) return 0; } - svc = CreateService(scm, - TEXT("ioperm"), - TEXT("I/O port access driver"), - SERVICE_ALL_ACCESS, - SERVICE_KERNEL_DRIVER, - SERVICE_AUTO_START, - SERVICE_ERROR_NORMAL, - "ioperm.sys", - NULL, - NULL, - NULL, - NULL, - NULL); + // Get the full path to the driver file. + + GetFullPathNameW(L"ioperm.sys", MAX_PATH, driver_path, NULL); + + // Create the service. + + svc = CreateServiceW(scm, + L"ioperm", + L"ioperm support for Cygwin driver", + SERVICE_ALL_ACCESS, + SERVICE_KERNEL_DRIVER, + SERVICE_AUTO_START, + SERVICE_ERROR_NORMAL, + driver_path, + NULL, + NULL, + NULL, + NULL, + NULL); if (svc == NULL) { @@ -229,6 +236,10 @@ int IOperm_UninstallDriver(void) result = 0; } + else + { + printf("IOperm_InstallDriver: ioperm driver uninstalled.\n"); + } } // Close handles. -- cgit v1.2.3 From 92c9d5c388ab91ade416539438fb0b8da9cd50bb Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Sat, 27 Feb 2010 19:11:24 +0000 Subject: Use wide-character versions of Win32 API functions. Clean up properly if it was not possible to start the ioperm service. Subversion-branch: /branches/opl-branch Subversion-revision: 1875 --- opl/ioperm_sys.c | 44 ++++++++++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 18 deletions(-) (limited to 'opl') diff --git a/opl/ioperm_sys.c b/opl/ioperm_sys.c index 4c06d97a..0e1ecfd5 100644 --- a/opl/ioperm_sys.c +++ b/opl/ioperm_sys.c @@ -35,7 +35,9 @@ #include -#define IOPERM_FILE "\\\\.\\ioperm" +#include "ioperm_sys.h" + +#define IOPERM_FILE L"\\\\.\\ioperm" #define IOCTL_IOPERM \ CTL_CODE(FILE_DEVICE_UNKNOWN, 0xA00, METHOD_BUFFERED, FILE_ANY_ACCESS) @@ -59,8 +61,8 @@ int IOperm_EnablePortRange(unsigned int from, unsigned int num, int turn_on) DWORD BytesReturned; BOOL r; - h = CreateFile(IOPERM_FILE, GENERIC_READ, 0, NULL, - OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + h = CreateFileW(IOPERM_FILE, GENERIC_READ, 0, NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (h == INVALID_HANDLE_VALUE) { @@ -97,12 +99,12 @@ int IOperm_InstallDriver(void) int error; int result = 1; - scm = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); + scm = OpenSCManagerW(NULL, NULL, SC_MANAGER_ALL_ACCESS); if (scm == NULL) { error = GetLastError(); - fprintf(stderr, "IOperm_InstallDriver: OpenSCManager failed (%i)\n", + fprintf(stderr, "IOperm_InstallDriver: OpenSCManager failed (%i).\n", error); return 0; } @@ -134,19 +136,19 @@ int IOperm_InstallDriver(void) if (error != ERROR_SERVICE_EXISTS) { fprintf(stderr, - "IOperm_InstallDriver: Failed to create service (%i)\n", + "IOperm_InstallDriver: Failed to create service (%i).\n", error); } else { - svc = OpenService(scm, TEXT("ioperm"), SERVICE_ALL_ACCESS); + svc = OpenServiceW(scm, L"ioperm", SERVICE_ALL_ACCESS); if (svc == NULL) { error = GetLastError(); fprintf(stderr, - "IOperm_InstallDriver: Failed to open service (%i)\n", + "IOperm_InstallDriver: Failed to open service (%i).\n", error); } } @@ -162,31 +164,37 @@ int IOperm_InstallDriver(void) service_was_created = 1; } - if (!StartService(svc, 0, NULL)) + // Start the service. If the service already existed, it might have + // already been running as well. + + if (!StartServiceW(svc, 0, NULL)) { error = GetLastError(); if (error != ERROR_SERVICE_ALREADY_RUNNING) { - fprintf(stderr, "IOperm_InstallDriver: Failed to start service (%i)\n", + fprintf(stderr, "IOperm_InstallDriver: Failed to start service (%i).\n", error); + result = 0; } else { - printf("IOperm_InstallDriver: ioperm driver already running\n"); + printf("IOperm_InstallDriver: ioperm driver already running.\n"); } } else { - printf("IOperm_InstallDriver: ioperm driver installed\n"); + printf("IOperm_InstallDriver: ioperm driver installed.\n"); service_was_started = 1; } + // If we failed to start the driver running, we need to clean up + // before finishing. + if (result == 0) { - CloseServiceHandle(svc); - CloseServiceHandle(scm); + IOperm_UninstallDriver(); } return result; @@ -215,7 +223,7 @@ int IOperm_UninstallDriver(void) else { fprintf(stderr, - "IOperm_UninstallDriver: Failed to stop service (%i)\n", + "IOperm_UninstallDriver: Failed to stop service (%i).\n", error); result = 0; } @@ -231,14 +239,14 @@ int IOperm_UninstallDriver(void) error = GetLastError(); fprintf(stderr, - "IOperm_UninstallDriver: DeleteService failed (%i)\n", + "IOperm_UninstallDriver: DeleteService failed (%i).\n", error); result = 0; } - else + else if (service_was_started) { - printf("IOperm_InstallDriver: ioperm driver uninstalled.\n"); + printf("IOperm_UnInstallDriver: ioperm driver uninstalled.\n"); } } -- cgit v1.2.3 From b9e18229624500d6d2a6112a5c00882d7b7051de Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Mon, 8 Mar 2010 00:51:00 +0000 Subject: Load advapi32.dll pointers dynamically at runtime. This should fix any potential problems with that library not existing on Win9x. Subversion-branch: /branches/opl-branch Subversion-revision: 1877 --- opl/ioperm_sys.c | 129 ++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 108 insertions(+), 21 deletions(-) (limited to 'opl') diff --git a/opl/ioperm_sys.c b/opl/ioperm_sys.c index 0e1ecfd5..8f50bcd3 100644 --- a/opl/ioperm_sys.c +++ b/opl/ioperm_sys.c @@ -49,11 +49,93 @@ struct ioperm_data int turn_on; }; +// Function pointers for advapi32.dll. This DLL does not exist on +// Windows 9x, so they are dynamically loaded from the DLL at runtime. + +static SC_HANDLE WINAPI (*MyOpenSCManagerW)(wchar_t *lpMachineName, + wchar_t *lpDatabaseName, + DWORD dwDesiredAccess) = NULL; +static SC_HANDLE WINAPI (*MyCreateServiceW)(SC_HANDLE hSCManager, + wchar_t *lpServiceName, + wchar_t *lpDisplayName, + DWORD dwDesiredAccess, + DWORD dwServiceType, + DWORD dwStartType, + DWORD dwErrorControl, + wchar_t *lpBinaryPathName, + wchar_t *lpLoadOrderGroup, + LPDWORD lpdwTagId, + wchar_t *lpDependencies, + wchar_t *lpServiceStartName, + wchar_t *lpPassword); +static SC_HANDLE WINAPI (*MyOpenServiceW)(SC_HANDLE hSCManager, + wchar_t *lpServiceName, + DWORD dwDesiredAccess); +static BOOL WINAPI (*MyStartServiceW)(SC_HANDLE hService, + DWORD dwNumServiceArgs, + wchar_t **lpServiceArgVectors); +static BOOL WINAPI (*MyControlService)(SC_HANDLE hService, + DWORD dwControl, + LPSERVICE_STATUS lpServiceStatus); +static BOOL WINAPI (*MyCloseServiceHandle)(SC_HANDLE hSCObject); +static BOOL WINAPI (*MyDeleteService)(SC_HANDLE hService); + +static struct +{ + char *name; + void **fn; +} dll_functions[] = { + { "OpenSCManagerW", (void **) &MyOpenSCManagerW }, + { "CreateServiceW", (void **) &MyCreateServiceW }, + { "OpenServiceW", (void **) &MyOpenServiceW }, + { "StartServiceW", (void **) &MyStartServiceW }, + { "ControlService", (void **) &MyControlService }, + { "CloseServiceHandle", (void **) &MyCloseServiceHandle }, + { "DeleteService", (void **) &MyDeleteService }, +}; + +// Globals + static SC_HANDLE scm = NULL; static SC_HANDLE svc = NULL; static int service_was_created = 0; static int service_was_started = 0; +static int LoadLibraryPointers(void) +{ + HMODULE dll; + int i; + + // Already loaded? + + if (MyOpenSCManagerW != NULL) + { + return 1; + } + + dll = LoadLibraryW(L"advapi32.dll"); + + if (dll == NULL) + { + fprintf(stderr, "LoadLibraryPointers: Failed to open advapi32.dll\n"); + return 0; + } + + for (i = 0; i < sizeof(dll_functions) / sizeof(*dll_functions); ++i) + { + *dll_functions[i].fn = GetProcAddress(dll, dll_functions[i].name); + + if (*dll_functions[i].fn == NULL) + { + fprintf(stderr, "LoadLibraryPointers: Failed to get address " + "for '%s'\n", dll_functions[i].name); + return 0; + } + } + + return 1; +} + int IOperm_EnablePortRange(unsigned int from, unsigned int num, int turn_on) { HANDLE h; @@ -99,7 +181,12 @@ int IOperm_InstallDriver(void) int error; int result = 1; - scm = OpenSCManagerW(NULL, NULL, SC_MANAGER_ALL_ACCESS); + if (!LoadLibraryPointers()) + { + return 0; + } + + scm = MyOpenSCManagerW(NULL, NULL, SC_MANAGER_ALL_ACCESS); if (scm == NULL) { @@ -115,19 +202,19 @@ int IOperm_InstallDriver(void) // Create the service. - svc = CreateServiceW(scm, - L"ioperm", - L"ioperm support for Cygwin driver", - SERVICE_ALL_ACCESS, - SERVICE_KERNEL_DRIVER, - SERVICE_AUTO_START, - SERVICE_ERROR_NORMAL, - driver_path, - NULL, - NULL, - NULL, - NULL, - NULL); + svc = MyCreateServiceW(scm, + L"ioperm", + L"ioperm support for Cygwin driver", + SERVICE_ALL_ACCESS, + SERVICE_KERNEL_DRIVER, + SERVICE_AUTO_START, + SERVICE_ERROR_NORMAL, + driver_path, + NULL, + NULL, + NULL, + NULL, + NULL); if (svc == NULL) { @@ -141,7 +228,7 @@ int IOperm_InstallDriver(void) } else { - svc = OpenServiceW(scm, L"ioperm", SERVICE_ALL_ACCESS); + svc = MyOpenServiceW(scm, L"ioperm", SERVICE_ALL_ACCESS); if (svc == NULL) { @@ -155,7 +242,7 @@ int IOperm_InstallDriver(void) if (svc == NULL) { - CloseServiceHandle(scm); + MyCloseServiceHandle(scm); return 0; } } @@ -167,7 +254,7 @@ int IOperm_InstallDriver(void) // Start the service. If the service already existed, it might have // already been running as well. - if (!StartServiceW(svc, 0, NULL)) + if (!MyStartServiceW(svc, 0, NULL)) { error = GetLastError(); @@ -210,7 +297,7 @@ int IOperm_UninstallDriver(void) if (service_was_started) { - if (!ControlService(svc, SERVICE_CONTROL_STOP, &stat)) + if (!MyControlService(svc, SERVICE_CONTROL_STOP, &stat)) { error = GetLastError(); @@ -234,7 +321,7 @@ int IOperm_UninstallDriver(void) if (service_was_created) { - if (!DeleteService(svc)) + if (!MyDeleteService(svc)) { error = GetLastError(); @@ -254,13 +341,13 @@ int IOperm_UninstallDriver(void) if (svc != NULL) { - CloseServiceHandle(svc); + MyCloseServiceHandle(svc); svc = NULL; } if (scm != NULL) { - CloseServiceHandle(scm); + MyCloseServiceHandle(scm); scm = NULL; } -- cgit v1.2.3 From 695b64ce292e94a808797bfcaf2ed3f9823ad577 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Mon, 8 Mar 2010 18:52:59 +0000 Subject: Add OPL-TODO to dist, set svn:ignore properties. Subversion-branch: /trunk/chocolate-doom Subversion-revision: 1880 --- opl/.gitignore | 2 ++ opl/examples/.gitignore | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) (limited to 'opl') diff --git a/opl/.gitignore b/opl/.gitignore index e64379dc..8aac4e77 100644 --- a/opl/.gitignore +++ b/opl/.gitignore @@ -3,3 +3,5 @@ Makefile .deps libopl.a *.rc +tags +TAGS diff --git a/opl/examples/.gitignore b/opl/examples/.gitignore index 49bb1af8..4d589c73 100644 --- a/opl/examples/.gitignore +++ b/opl/examples/.gitignore @@ -2,4 +2,6 @@ Makefile.in Makefile .deps droplay - +*.exe +tags +TAGS -- cgit v1.2.3 From 6ebf557c44b11a16ff15e5f560a81753be33bad3 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Fri, 13 Aug 2010 18:42:52 +0000 Subject: Add C-converted version of DOSbox OPL emulator. Subversion-branch: /trunk/chocolate-doom Subversion-revision: 1955 --- opl/dbopl.c | 1603 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ opl/dbopl.h | 196 ++++++++ 2 files changed, 1799 insertions(+) create mode 100644 opl/dbopl.c create mode 100644 opl/dbopl.h (limited to 'opl') diff --git a/opl/dbopl.c b/opl/dbopl.c new file mode 100644 index 00000000..556c570d --- /dev/null +++ b/opl/dbopl.c @@ -0,0 +1,1603 @@ +/* + * Copyright (C) 2002-2010 The DOSBox Team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +// +// Chocolate Doom-related discussion: +// +// This is the DosBox OPL emulator code (src/hardware/dbopl.cpp) r3635, +// converted to C. The bulk of the work was done using the minus-minus +// script in the Chocolate Doom SVN repository, then the result tweaked +// by hand until working. +// + + +/* + DOSBox implementation of a combined Yamaha YMF262 and Yamaha YM3812 emulator. + Enabling the opl3 bit will switch the emulator to stereo opl3 output instead of regular mono opl2 + Except for the table generation it's all integer math + Can choose different types of generators, using muls and bigger tables, try different ones for slower platforms + The generation was based on the MAME implementation but tried to have it use less memory and be faster in general + MAME uses much bigger envelope tables and this will be the biggest cause of it sounding different at times + + //TODO Don't delay first operator 1 sample in opl3 mode + //TODO Maybe not use class method pointers but a regular function pointers with operator as first parameter + //TODO Fix panning for the Percussion channels, would any opl3 player use it and actually really change it though? + //TODO Check if having the same accuracy in all frequency multipliers sounds better or not + + //DUNNO Keyon in 4op, switch to 2op without keyoff. +*/ + +/* $Id: dbopl.cpp,v 1.10 2009-06-10 19:54:51 harekiet Exp $ */ + + +#include +#include +#include +//#include "dosbox.h" +#include "dbopl.h" + + +#define GCC_UNLIKELY(x) x + +#define TRUE 1 +#define FALSE 0 + +#ifndef PI +#define PI 3.14159265358979323846 +#endif + +#define OPLRATE ((double)(14318180.0 / 288.0)) +#define TREMOLO_TABLE 52 + +//Try to use most precision for frequencies +//Else try to keep different waves in synch +//#define WAVE_PRECISION 1 +#ifndef WAVE_PRECISION +//Wave bits available in the top of the 32bit range +//Original adlib uses 10.10, we use 10.22 +#define WAVE_BITS 10 +#else +//Need some extra bits at the top to have room for octaves and frequency multiplier +//We support to 8 times lower rate +//128 * 15 * 8 = 15350, 2^13.9, so need 14 bits +#define WAVE_BITS 14 +#endif +#define WAVE_SH ( 32 - WAVE_BITS ) +#define WAVE_MASK ( ( 1 << WAVE_SH ) - 1 ) + +//Use the same accuracy as the waves +#define LFO_SH ( WAVE_SH - 10 ) +//LFO is controlled by our tremolo 256 sample limit +#define LFO_MAX ( 256 << ( LFO_SH ) ) + + +//Maximum amount of attenuation bits +//Envelope goes to 511, 9 bits +#if (DBOPL_WAVE == WAVE_TABLEMUL ) +//Uses the value directly +#define ENV_BITS ( 9 ) +#else +//Add 3 bits here for more accuracy and would have to be shifted up either way +#define ENV_BITS ( 9 ) +#endif +//Limits of the envelope with those bits and when the envelope goes silent +#define ENV_MIN 0 +#define ENV_EXTRA ( ENV_BITS - 9 ) +#define ENV_MAX ( 511 << ENV_EXTRA ) +#define ENV_LIMIT ( ( 12 * 256) >> ( 3 - ENV_EXTRA ) ) +#define ENV_SILENT( _X_ ) ( (_X_) >= ENV_LIMIT ) + +//Attack/decay/release rate counter shift +#define RATE_SH 24 +#define RATE_MASK ( ( 1 << RATE_SH ) - 1 ) +//Has to fit within 16bit lookuptable +#define MUL_SH 16 + +//Check some ranges +#if ENV_EXTRA > 3 +#error Too many envelope bits +#endif + +static inline void Operator__SetState(Operator *self, Bit8u s ); +static inline Bit32u Chip__ForwardNoise(Chip *self); + +// C++'s template<> sure is useful sometimes. + +static Channel* Channel__BlockTemplate(Channel *self, Chip* chip, + Bit32u samples, Bit32s* output, + SynthMode mode ); +#define BLOCK_TEMPLATE(mode) \ + static Channel* Channel__BlockTemplate_ ## mode(Channel *self, Chip* chip, \ + Bit32u samples, Bit32s* output) \ + { \ + return Channel__BlockTemplate(self, chip, samples, output, mode); \ + } + +BLOCK_TEMPLATE(sm2AM) +BLOCK_TEMPLATE(sm2FM) +BLOCK_TEMPLATE(sm3AM) +BLOCK_TEMPLATE(sm3FM) +BLOCK_TEMPLATE(sm3FMFM) +BLOCK_TEMPLATE(sm3AMFM) +BLOCK_TEMPLATE(sm3FMAM) +BLOCK_TEMPLATE(sm3AMAM) +BLOCK_TEMPLATE(sm2Percussion) +BLOCK_TEMPLATE(sm3Percussion) + +//How much to substract from the base value for the final attenuation +static const Bit8u KslCreateTable[16] = { + //0 will always be be lower than 7 * 8 + 64, 32, 24, 19, + 16, 12, 11, 10, + 8, 6, 5, 4, + 3, 2, 1, 0, +}; + +#define M(_X_) ((Bit8u)( (_X_) * 2)) +static const Bit8u FreqCreateTable[16] = { + M(0.5), M(1 ), M(2 ), M(3 ), M(4 ), M(5 ), M(6 ), M(7 ), + M(8 ), M(9 ), M(10), M(10), M(12), M(12), M(15), M(15) +}; +#undef M + +//We're not including the highest attack rate, that gets a special value +static const Bit8u AttackSamplesTable[13] = { + 69, 55, 46, 40, + 35, 29, 23, 20, + 19, 15, 11, 10, + 9 +}; +//On a real opl these values take 8 samples to reach and are based upon larger tables +static const Bit8u EnvelopeIncreaseTable[13] = { + 4, 5, 6, 7, + 8, 10, 12, 14, + 16, 20, 24, 28, + 32, +}; + +#if ( DBOPL_WAVE == WAVE_HANDLER ) || ( DBOPL_WAVE == WAVE_TABLELOG ) +static Bit16u ExpTable[ 256 ]; +#endif + +#if ( DBOPL_WAVE == WAVE_HANDLER ) +//PI table used by WAVEHANDLER +static Bit16u SinTable[ 512 ]; +#endif + +#if ( DBOPL_WAVE > WAVE_HANDLER ) +//Layout of the waveform table in 512 entry intervals +//With overlapping waves we reduce the table to half it's size + +// | |//\\|____|WAV7|//__|/\ |____|/\/\| +// |\\//| | |WAV7| | \/| | | +// |06 |0126|17 |7 |3 |4 |4 5 |5 | + +//6 is just 0 shifted and masked + +static Bit16s WaveTable[ 8 * 512 ]; +//Distance into WaveTable the wave starts +static const Bit16u WaveBaseTable[8] = { + 0x000, 0x200, 0x200, 0x800, + 0xa00, 0xc00, 0x100, 0x400, + +}; +//Mask the counter with this +static const Bit16u WaveMaskTable[8] = { + 1023, 1023, 511, 511, + 1023, 1023, 512, 1023, +}; + +//Where to start the counter on at keyon +static const Bit16u WaveStartTable[8] = { + 512, 0, 0, 0, + 0, 512, 512, 256, +}; +#endif + +#if ( DBOPL_WAVE == WAVE_TABLEMUL ) +static Bit16u MulTable[ 384 ]; +#endif + +static Bit8u KslTable[ 8 * 16 ]; +static Bit8u TremoloTable[ TREMOLO_TABLE ]; +//Start of a channel behind the chip struct start +static Bit16u ChanOffsetTable[32]; +//Start of an operator behind the chip struct start +static Bit16u OpOffsetTable[64]; + +//The lower bits are the shift of the operator vibrato value +//The highest bit is right shifted to generate -1 or 0 for negation +//So taking the highest input value of 7 this gives 3, 7, 3, 0, -3, -7, -3, 0 +static const Bit8s VibratoTable[ 8 ] = { + 1 - 0x00, 0 - 0x00, 1 - 0x00, 30 - 0x00, + 1 - 0x80, 0 - 0x80, 1 - 0x80, 30 - 0x80 +}; + +//Shift strength for the ksl value determined by ksl strength +static const Bit8u KslShiftTable[4] = { + 31,1,2,0 +}; + +//Generate a table index and table shift value using input value from a selected rate +static void EnvelopeSelect( Bit8u val, Bit8u *index, Bit8u *shift ) { + if ( val < 13 * 4 ) { //Rate 0 - 12 + *shift = 12 - ( val >> 2 ); + *index = val & 3; + } else if ( val < 15 * 4 ) { //rate 13 - 14 + *shift = 0; + *index = val - 12 * 4; + } else { //rate 15 and up + *shift = 0; + *index = 12; + } +} + +#if ( DBOPL_WAVE == WAVE_HANDLER ) +/* + Generate the different waveforms out of the sine/exponetial table using handlers +*/ +static inline Bits MakeVolume( Bitu wave, Bitu volume ) { + Bitu total = wave + volume; + Bitu index = total & 0xff; + Bitu sig = ExpTable[ index ]; + Bitu exp = total >> 8; +#if 0 + //Check if we overflow the 31 shift limit + if ( exp >= 32 ) { + LOG_MSG( "WTF %d %d", total, exp ); + } +#endif + return (sig >> exp); +}; + +static Bits DB_FASTCALL WaveForm0( Bitu i, Bitu volume ) { + Bits neg = 0 - (( i >> 9) & 1);//Create ~0 or 0 + Bitu wave = SinTable[i & 511]; + return (MakeVolume( wave, volume ) ^ neg) - neg; +} +static Bits DB_FASTCALL WaveForm1( Bitu i, Bitu volume ) { + Bit32u wave = SinTable[i & 511]; + wave |= ( ( (i ^ 512 ) & 512) - 1) >> ( 32 - 12 ); + return MakeVolume( wave, volume ); +} +static Bits DB_FASTCALL WaveForm2( Bitu i, Bitu volume ) { + Bitu wave = SinTable[i & 511]; + return MakeVolume( wave, volume ); +} +static Bits DB_FASTCALL WaveForm3( Bitu i, Bitu volume ) { + Bitu wave = SinTable[i & 255]; + wave |= ( ( (i ^ 256 ) & 256) - 1) >> ( 32 - 12 ); + return MakeVolume( wave, volume ); +} +static Bits DB_FASTCALL WaveForm4( Bitu i, Bitu volume ) { + //Twice as fast + i <<= 1; + Bits neg = 0 - (( i >> 9) & 1);//Create ~0 or 0 + Bitu wave = SinTable[i & 511]; + wave |= ( ( (i ^ 512 ) & 512) - 1) >> ( 32 - 12 ); + return (MakeVolume( wave, volume ) ^ neg) - neg; +} +static Bits DB_FASTCALL WaveForm5( Bitu i, Bitu volume ) { + //Twice as fast + i <<= 1; + Bitu wave = SinTable[i & 511]; + wave |= ( ( (i ^ 512 ) & 512) - 1) >> ( 32 - 12 ); + return MakeVolume( wave, volume ); +} +static Bits DB_FASTCALL WaveForm6( Bitu i, Bitu volume ) { + Bits neg = 0 - (( i >> 9) & 1);//Create ~0 or 0 + return (MakeVolume( 0, volume ) ^ neg) - neg; +} +static Bits DB_FASTCALL WaveForm7( Bitu i, Bitu volume ) { + //Negative is reversed here + Bits neg = (( i >> 9) & 1) - 1; + Bitu wave = (i << 3); + //When negative the volume also runs backwards + wave = ((wave ^ neg) - neg) & 4095; + return (MakeVolume( wave, volume ) ^ neg) - neg; +} + +static const WaveHandler WaveHandlerTable[8] = { + WaveForm0, WaveForm1, WaveForm2, WaveForm3, + WaveForm4, WaveForm5, WaveForm6, WaveForm7 +}; + +#endif + +/* + Operator +*/ + +//We zero out when rate == 0 +inline void Operator__UpdateAttack(Operator *self, const Chip* chip ) { + Bit8u rate = self->reg60 >> 4; + if ( rate ) { + Bit8u val = (rate << 2) + self->ksr; + self->attackAdd = chip->attackRates[ val ]; + self->rateZero &= ~(1 << ATTACK); + } else { + self->attackAdd = 0; + self->rateZero |= (1 << ATTACK); + } +} +inline void Operator__UpdateDecay(Operator *self, const Chip* chip ) { + Bit8u rate = self->reg60 & 0xf; + if ( rate ) { + Bit8u val = (rate << 2) + self->ksr; + self->decayAdd = chip->linearRates[ val ]; + self->rateZero &= ~(1 << DECAY); + } else { + self->decayAdd = 0; + self->rateZero |= (1 << DECAY); + } +} +inline void Operator__UpdateRelease(Operator *self, const Chip* chip ) { + Bit8u rate = self->reg80 & 0xf; + if ( rate ) { + Bit8u val = (rate << 2) + self->ksr; + self->releaseAdd = chip->linearRates[ val ]; + self->rateZero &= ~(1 << RELEASE); + if ( !(self->reg20 & MASK_SUSTAIN ) ) { + self->rateZero &= ~( 1 << SUSTAIN ); + } + } else { + self->rateZero |= (1 << RELEASE); + self->releaseAdd = 0; + if ( !(self->reg20 & MASK_SUSTAIN ) ) { + self->rateZero |= ( 1 << SUSTAIN ); + } + } +} + +inline void Operator__UpdateAttenuation(Operator *self) { + Bit8u kslBase = (Bit8u)((self->chanData >> SHIFT_KSLBASE) & 0xff); + Bit32u tl = self->reg40 & 0x3f; + Bit8u kslShift = KslShiftTable[ self->reg40 >> 6 ]; + //Make sure the attenuation goes to the right bits + self->totalLevel = tl << ( ENV_BITS - 7 ); //Total level goes 2 bits below max + self->totalLevel += ( kslBase << ENV_EXTRA ) >> kslShift; +} + +void Operator__UpdateFrequency(Operator *self) { + Bit32u freq = self->chanData & (( 1 << 10 ) - 1); + Bit32u block = (self->chanData >> 10) & 0xff; +#ifdef WAVE_PRECISION + block = 7 - block; + self->waveAdd = ( freq * self->freqMul ) >> block; +#else + self->waveAdd = ( freq << block ) * self->freqMul; +#endif + if ( self->reg20 & MASK_VIBRATO ) { + self->vibStrength = (Bit8u)(freq >> 7); + +#ifdef WAVE_PRECISION + self->vibrato = ( self->vibStrength * self->freqMul ) >> block; +#else + self->vibrato = ( self->vibStrength << block ) * self->freqMul; +#endif + } else { + self->vibStrength = 0; + self->vibrato = 0; + } +} + +void Operator__UpdateRates(Operator *self, const Chip* chip ) { + //Mame seems to reverse this where enabling ksr actually lowers + //the rate, but pdf manuals says otherwise? + Bit8u newKsr = (Bit8u)((self->chanData >> SHIFT_KEYCODE) & 0xff); + if ( !( self->reg20 & MASK_KSR ) ) { + newKsr >>= 2; + } + if ( self->ksr == newKsr ) + return; + self->ksr = newKsr; + Operator__UpdateAttack( self, chip ); + Operator__UpdateDecay( self, chip ); + Operator__UpdateRelease( self, chip ); +} + +static inline Bit32s Operator__RateForward(Operator *self, Bit32u add ) { + self->rateIndex += add; + Bit32s ret = self->rateIndex >> RATE_SH; + self->rateIndex = self->rateIndex & RATE_MASK; + return ret; +} + +static Bits Operator__TemplateVolume(Operator *self, OperatorState yes) { + Bit32s vol = self->volume; + Bit32s change; + switch ( yes ) { + case OFF: + return ENV_MAX; + case ATTACK: + change = Operator__RateForward( self, self->attackAdd ); + if ( !change ) + return vol; + vol += ( (~vol) * change ) >> 3; + if ( vol < ENV_MIN ) { + self->volume = ENV_MIN; + self->rateIndex = 0; + Operator__SetState( self, DECAY ); + return ENV_MIN; + } + break; + case DECAY: + vol += Operator__RateForward( self, self->decayAdd ); + if ( GCC_UNLIKELY(vol >= self->sustainLevel) ) { + //Check if we didn't overshoot max attenuation, then just go off + if ( GCC_UNLIKELY(vol >= ENV_MAX) ) { + self->volume = ENV_MAX; + Operator__SetState( self, OFF ); + return ENV_MAX; + } + //Continue as sustain + self->rateIndex = 0; + Operator__SetState( self, SUSTAIN ); + } + break; + case SUSTAIN: + if ( self->reg20 & MASK_SUSTAIN ) { + return vol; + } + //In sustain phase, but not sustaining, do regular release + case RELEASE: + vol += Operator__RateForward( self, self->releaseAdd );; + if ( GCC_UNLIKELY(vol >= ENV_MAX) ) { + self->volume = ENV_MAX; + Operator__SetState( self, OFF ); + return ENV_MAX; + } + break; + } + self->volume = vol; + return vol; +} + +#define TEMPLATE_VOLUME(mode) \ + static Bits Operator__TemplateVolume ## mode(Operator *self) \ + { \ + return Operator__TemplateVolume(self, mode); \ + } + +TEMPLATE_VOLUME(OFF) +TEMPLATE_VOLUME(RELEASE) +TEMPLATE_VOLUME(SUSTAIN) +TEMPLATE_VOLUME(ATTACK) +TEMPLATE_VOLUME(DECAY) + +static const VolumeHandler VolumeHandlerTable[5] = { + &Operator__TemplateVolumeOFF, + &Operator__TemplateVolumeRELEASE, + &Operator__TemplateVolumeSUSTAIN, + &Operator__TemplateVolumeDECAY, + &Operator__TemplateVolumeATTACK, +}; + +static inline Bitu Operator__ForwardVolume(Operator *self) { + return self->currentLevel + (self->volHandler)(); +} + + +static inline Bitu Operator__ForwardWave(Operator *self) { + self->waveIndex += self->waveCurrent; + return self->waveIndex >> WAVE_SH; +} + +void Operator__Write20(Operator *self, const Chip* chip, Bit8u val ) { + Bit8u change = (self->reg20 ^ val ); + if ( !change ) + return; + self->reg20 = val; + //Shift the tremolo bit over the entire register, saved a branch, YES! + self->tremoloMask = (Bit8s)(val) >> 7; + self->tremoloMask &= ~(( 1 << ENV_EXTRA ) -1); + //Update specific features based on changes + if ( change & MASK_KSR ) { + Operator__UpdateRates( self, chip ); + } + //With sustain enable the volume doesn't change + if ( self->reg20 & MASK_SUSTAIN || ( !self->releaseAdd ) ) { + self->rateZero |= ( 1 << SUSTAIN ); + } else { + self->rateZero &= ~( 1 << SUSTAIN ); + } + //Frequency multiplier or vibrato changed + if ( change & (0xf | MASK_VIBRATO) ) { + self->freqMul = chip->freqMul[ val & 0xf ]; + Operator__UpdateFrequency(self); + } +} + +void Operator__Write40(Operator *self, const Chip *chip, Bit8u val ) { + if (!(self->reg40 ^ val )) + return; + self->reg40 = val; + Operator__UpdateAttenuation( self ); +} + +void Operator__Write60(Operator *self, const Chip* chip, Bit8u val ) { + Bit8u change = self->reg60 ^ val; + self->reg60 = val; + if ( change & 0x0f ) { + Operator__UpdateDecay( self, chip ); + } + if ( change & 0xf0 ) { + Operator__UpdateAttack( self, chip ); + } +} + +void Operator__Write80(Operator *self, const Chip* chip, Bit8u val ) { + Bit8u change = (self->reg80 ^ val ); + if ( !change ) + return; + self->reg80 = val; + Bit8u sustain = val >> 4; + //Turn 0xf into 0x1f + sustain |= ( sustain + 1) & 0x10; + self->sustainLevel = sustain << ( ENV_BITS - 5 ); + if ( change & 0x0f ) { + Operator__UpdateRelease( self, chip ); + } +} + +void Operator__WriteE0(Operator *self, const Chip* chip, Bit8u val ) { + if ( !(self->regE0 ^ val) ) + return; + //in opl3 mode you can always selet 7 waveforms regardless of waveformselect + Bit8u waveForm = val & ( ( 0x3 & chip->waveFormMask ) | (0x7 & chip->opl3Active ) ); + self->regE0 = val; +#if( DBOPL_WAVE == WAVE_HANDLER ) + self->waveHandler = WaveHandlerTable[ waveForm ]; +#else + self->waveBase = WaveTable + WaveBaseTable[ waveForm ]; + self->waveStart = WaveStartTable[ waveForm ] << WAVE_SH; + self->waveMask = WaveMaskTable[ waveForm ]; +#endif +} + +static inline void Operator__SetState(Operator *self, Bit8u s ) { + self->state = s; + self->volHandler = VolumeHandlerTable[ s ]; +} + +static inline int Operator__Silent(Operator *self) { + if ( !ENV_SILENT( self->totalLevel + self->volume ) ) + return FALSE; + if ( !(self->rateZero & ( 1 << self->state ) ) ) + return FALSE; + return TRUE; +} + +static inline void Operator__Prepare(Operator *self, const Chip* chip ) { + self->currentLevel = self->totalLevel + (chip->tremoloValue & self->tremoloMask); + self->waveCurrent = self->waveAdd; + if ( self->vibStrength >> chip->vibratoShift ) { + Bit32s add = self->vibrato >> chip->vibratoShift; + //Sign extend over the shift value + Bit32s neg = chip->vibratoSign; + //Negate the add with -1 or 0 + add = ( add ^ neg ) - neg; + self->waveCurrent += add; + } +} + +void Operator__KeyOn(Operator *self, Bit8u mask ) { + if ( !self->keyOn ) { + //Restart the frequency generator +#if( DBOPL_WAVE > WAVE_HANDLER ) + self->waveIndex = self->waveStart; +#else + self->waveIndex = 0; +#endif + self->rateIndex = 0; + Operator__SetState( self, ATTACK ); + } + self->keyOn |= mask; +} + +void Operator__KeyOff(Operator *self, Bit8u mask ) { + self->keyOn &= ~mask; + if ( !self->keyOn ) { + if ( self->state != OFF ) { + Operator__SetState( self, RELEASE ); + } + } +} + +static inline Bits Operator__GetWave(Operator *self, Bitu index, Bitu vol ) { +#if( DBOPL_WAVE == WAVE_HANDLER ) + return self->waveHandler( index, vol << ( 3 - ENV_EXTRA ) ); +#elif( DBOPL_WAVE == WAVE_TABLEMUL ) + return(self->waveBase[ index & self->waveMask ] * MulTable[ vol >> ENV_EXTRA ]) >> MUL_SH; +#elif( DBOPL_WAVE == WAVE_TABLELOG ) + Bit32s wave = self->waveBase[ index & self->waveMask ]; + Bit32u total = ( wave & 0x7fff ) + vol << ( 3 - ENV_EXTRA ); + Bit32s sig = ExpTable[ total & 0xff ]; + Bit32u exp = total >> 8; + Bit32s neg = wave >> 16; + return((sig ^ neg) - neg) >> exp; +#else +#error "No valid wave routine" +#endif +} + +static inline Bits Operator__GetSample(Operator *self, Bits modulation ) { + Bitu vol = Operator__ForwardVolume(self); + if ( ENV_SILENT( vol ) ) { + //Simply forward the wave + self->waveIndex += self->waveCurrent; + return 0; + } else { + Bitu index = Operator__ForwardWave(self); + index += modulation; + return Operator__GetWave( self, index, vol ); + } +} + +void Operator__Operator(Operator *self) { + self->chanData = 0; + self->freqMul = 0; + self->waveIndex = 0; + self->waveAdd = 0; + self->waveCurrent = 0; + self->keyOn = 0; + self->ksr = 0; + self->reg20 = 0; + self->reg40 = 0; + self->reg60 = 0; + self->reg80 = 0; + self->regE0 = 0; + Operator__SetState( self, OFF ); + self->rateZero = (1 << OFF); + self->sustainLevel = ENV_MAX; + self->currentLevel = ENV_MAX; + self->totalLevel = ENV_MAX; + self->volume = ENV_MAX; + self->releaseAdd = 0; +} + +/* + Channel +*/ + +void Channel__Channel(Channel *self) { + Operator__Operator(&self->op[0]); + Operator__Operator(&self->op[1]); + self->old[0] = self->old[1] = 0; + self->chanData = 0; + self->regB0 = 0; + self->regC0 = 0; + self->maskLeft = -1; + self->maskRight = -1; + self->feedback = 31; + self->fourMask = 0; + self->synthHandler = Channel__BlockTemplate_sm2FM; +}; + +static inline Operator* Channel__Op( Channel *self, Bitu index ) { + return &( ( self + (index >> 1) )->op[ index & 1 ]); +} + +void Channel__SetChanData(Channel *self, const Chip* chip, Bit32u data ) { + Bit32u change = self->chanData ^ data; + self->chanData = data; + Channel__Op( self, 0 )->chanData = data; + Channel__Op( self, 1 )->chanData = data; + //Since a frequency update triggered this, always update frequency + Operator__UpdateFrequency(Channel__Op( self, 0 )); + Operator__UpdateFrequency(Channel__Op( self, 1 )); + if ( change & ( 0xff << SHIFT_KSLBASE ) ) { + Operator__UpdateAttenuation(Channel__Op( self, 0 )); + Operator__UpdateAttenuation(Channel__Op( self, 1 )); + } + if ( change & ( 0xff << SHIFT_KEYCODE ) ) { + Operator__UpdateRates(Channel__Op( self, 0 ), chip); + Operator__UpdateRates(Channel__Op( self, 1 ), chip); + } +} + +void Channel__UpdateFrequency(Channel *self, const Chip* chip, Bit8u fourOp ) { + //Extrace the frequency bits + Bit32u data = self->chanData & 0xffff; + Bit32u kslBase = KslTable[ data >> 6 ]; + Bit32u keyCode = ( data & 0x1c00) >> 9; + if ( chip->reg08 & 0x40 ) { + keyCode |= ( data & 0x100)>>8; /* notesel == 1 */ + } else { + keyCode |= ( data & 0x200)>>9; /* notesel == 0 */ + } + //Add the keycode and ksl into the highest bits of chanData + data |= (keyCode << SHIFT_KEYCODE) | ( kslBase << SHIFT_KSLBASE ); + Channel__SetChanData( self + 0, chip, data ); + if ( fourOp & 0x3f ) { + Channel__SetChanData( self + 1, chip, data ); + } +} + +void Channel__WriteA0(Channel *self, const Chip* chip, Bit8u val ) { + Bit8u fourOp = chip->reg104 & chip->opl3Active & self->fourMask; + //Don't handle writes to silent fourop channels + if ( fourOp > 0x80 ) + return; + Bit32u change = (self->chanData ^ val ) & 0xff; + if ( change ) { + self->chanData ^= change; + Channel__UpdateFrequency( self, chip, fourOp ); + } +} + +void Channel__WriteB0(Channel *self, const Chip* chip, Bit8u val ) { + Bit8u fourOp = chip->reg104 & chip->opl3Active & self->fourMask; + //Don't handle writes to silent fourop channels + if ( fourOp > 0x80 ) + return; + Bitu change = (self->chanData ^ ( val << 8 ) ) & 0x1f00; + if ( change ) { + self->chanData ^= change; + Channel__UpdateFrequency( self, chip, fourOp ); + } + //Check for a change in the keyon/off state + if ( !(( val ^ self->regB0) & 0x20)) + return; + self->regB0 = val; + if ( val & 0x20 ) { + Operator__KeyOn( Channel__Op(self, 0), 0x1 ); + Operator__KeyOn( Channel__Op(self, 1), 0x1 ); + if ( fourOp & 0x3f ) { + Operator__KeyOn( Channel__Op(self + 1, 0), 1 ); + Operator__KeyOn( Channel__Op(self + 1, 1), 1 ); + } + } else { + Operator__KeyOff( Channel__Op(self, 0), 0x1 ); + Operator__KeyOff( Channel__Op(self, 1), 0x1 ); + if ( fourOp & 0x3f ) { + Operator__KeyOff( Channel__Op(self + 1, 0), 1 ); + Operator__KeyOff( Channel__Op(self + 1, 1), 1 ); + } + } +} + +void Channel__WriteC0(Channel *self, const Chip* chip, Bit8u val ) { + Bit8u change = val ^ self->regC0; + if ( !change ) + return; + self->regC0 = val; + self->feedback = ( val >> 1 ) & 7; + if ( self->feedback ) { + //We shift the input to the right 10 bit wave index value + self->feedback = 9 - self->feedback; + } else { + self->feedback = 31; + } + //Select the new synth mode + if ( chip->opl3Active ) { + //4-op mode enabled for this channel + if ( (chip->reg104 & self->fourMask) & 0x3f ) { + Channel* chan0, *chan1; + //Check if it's the 2nd channel in a 4-op + if ( !(self->fourMask & 0x80 ) ) { + chan0 = self; + chan1 = self + 1; + } else { + chan0 = self - 1; + chan1 = self; + } + + Bit8u synth = ( (chan0->regC0 & 1) << 0 )| (( chan1->regC0 & 1) << 1 ); + switch ( synth ) { + case 0: + chan0->synthHandler = Channel__BlockTemplate_sm3FMFM; + break; + case 1: + chan0->synthHandler = Channel__BlockTemplate_sm3AMFM; + break; + case 2: + chan0->synthHandler = Channel__BlockTemplate_sm3FMAM ; + break; + case 3: + chan0->synthHandler = Channel__BlockTemplate_sm3AMAM ; + break; + } + //Disable updating percussion channels + } else if ((self->fourMask & 0x40) && ( chip->regBD & 0x20) ) { + + //Regular dual op, am or fm + } else if ( val & 1 ) { + self->synthHandler = Channel__BlockTemplate_sm3AM; + } else { + self->synthHandler = Channel__BlockTemplate_sm3FM; + } + self->maskLeft = ( val & 0x10 ) ? -1 : 0; + self->maskRight = ( val & 0x20 ) ? -1 : 0; + //opl2 active + } else { + //Disable updating percussion channels + if ( (self->fourMask & 0x40) && ( chip->regBD & 0x20 ) ) { + + //Regular dual op, am or fm + } else if ( val & 1 ) { + self->synthHandler = Channel__BlockTemplate_sm2AM; + } else { + self->synthHandler = Channel__BlockTemplate_sm2FM; + } + } +} + +void Channel__ResetC0(Channel *self, const Chip* chip ) { + Bit8u val = self->regC0; + self->regC0 ^= 0xff; + Channel__WriteC0( self, chip, val ); +}; + +static inline void Channel__GeneratePercussion(Channel *self, Chip* chip, + Bit32s* output, int opl3Mode ) { + Channel* chan = self; + + //BassDrum + Bit32s mod = (Bit32u)((self->old[0] + self->old[1])) >> self->feedback; + self->old[0] = self->old[1]; + self->old[1] = Operator__GetSample( Channel__Op(self, 0), mod ); + + //When bassdrum is in AM mode first operator is ignoed + if ( chan->regC0 & 1 ) { + mod = 0; + } else { + mod = self->old[0]; + } + Bit32s sample = Operator__GetSample( Channel__Op(self, 1), mod ); + + //Precalculate stuff used by other outputs + Bit32u noiseBit = Chip__ForwardNoise(chip) & 0x1; + Bit32u c2 = Operator__ForwardWave(Channel__Op(self, 2)); + Bit32u c5 = Operator__ForwardWave(Channel__Op(self, 5)); + Bit32u phaseBit = (((c2 & 0x88) ^ ((c2<<5) & 0x80)) | ((c5 ^ (c5<<2)) & 0x20)) ? 0x02 : 0x00; + + //Hi-Hat + Bit32u hhVol = Operator__ForwardVolume(Channel__Op(self, 2)); + if ( !ENV_SILENT( hhVol ) ) { + Bit32u hhIndex = (phaseBit<<8) | (0x34 << ( phaseBit ^ (noiseBit << 1 ))); + sample += Operator__GetWave( Channel__Op(self, 2), hhIndex, hhVol ); + } + //Snare Drum + Bit32u sdVol = Operator__ForwardVolume( Channel__Op(self, 3) ); + if ( !ENV_SILENT( sdVol ) ) { + Bit32u sdIndex = ( 0x100 + (c2 & 0x100) ) ^ ( noiseBit << 8 ); + sample += Operator__GetWave( Channel__Op(self, 3), sdIndex, sdVol ); + } + //Tom-tom + sample += Operator__GetSample( Channel__Op(self, 4), 0 ); + + //Top-Cymbal + Bit32u tcVol = Operator__ForwardVolume(Channel__Op(self, 5)); + if ( !ENV_SILENT( tcVol ) ) { + Bit32u tcIndex = (1 + phaseBit) << 8; + sample += Operator__GetWave( Channel__Op(self, 5), tcIndex, tcVol ); + } + sample <<= 1; + if ( opl3Mode ) { + output[0] += sample; + output[1] += sample; + } else { + output[0] += sample; + } +} + +Channel* Channel__BlockTemplate(Channel *self, Chip* chip, + Bit32u samples, Bit32s* output, + SynthMode mode ) { + Bitu i; + + switch( mode ) { + case sm2AM: + case sm3AM: + if ( Operator__Silent(Channel__Op(self, 0)) + && Operator__Silent(Channel__Op(self, 1))) { + self->old[0] = self->old[1] = 0; + return(self + 1); + } + break; + case sm2FM: + case sm3FM: + if ( Operator__Silent(Channel__Op(self, 1))) { + self->old[0] = self->old[1] = 0; + return (self + 1); + } + break; + case sm3FMFM: + if ( Operator__Silent(Channel__Op(self, 3))) { + self->old[0] = self->old[1] = 0; + return (self + 2); + } + break; + case sm3AMFM: + if ( Operator__Silent( Channel__Op(self, 0) ) + && Operator__Silent( Channel__Op(self, 3) )) { + self->old[0] = self->old[1] = 0; + return (self + 2); + } + break; + case sm3FMAM: + if ( Operator__Silent( Channel__Op(self, 1)) + && Operator__Silent( Channel__Op(self, 3))) { + self->old[0] = self->old[1] = 0; + return (self + 2); + } + break; + case sm3AMAM: + if ( Operator__Silent( Channel__Op(self, 0) ) + && Operator__Silent( Channel__Op(self, 2) ) + && Operator__Silent( Channel__Op(self, 3) )) { + self->old[0] = self->old[1] = 0; + return (self + 2); + } + break; + + default: + abort(); + } + //Init the operators with the the current vibrato and tremolo values + Operator__Prepare( Channel__Op( self, 0 ), chip ); + Operator__Prepare( Channel__Op( self, 1 ), chip ); + if ( mode > sm4Start ) { + Operator__Prepare( Channel__Op( self, 2 ), chip ); + Operator__Prepare( Channel__Op( self, 3 ), chip ); + } + if ( mode > sm6Start ) { + Operator__Prepare( Channel__Op( self, 4 ), chip ); + Operator__Prepare( Channel__Op( self, 5 ), chip ); + } + for ( i = 0; i < samples; i++ ) { + //Early out for percussion handlers + if ( mode == sm2Percussion ) { + Channel__GeneratePercussion( self, chip, output + i, FALSE ); + continue; //Prevent some unitialized value bitching + } else if ( mode == sm3Percussion ) { + Channel__GeneratePercussion( self, chip, output + i * 2, TRUE ); + continue; //Prevent some unitialized value bitching + } + + //Do unsigned shift so we can shift out all bits but still stay in 10 bit range otherwise + Bit32s mod = (Bit32u)((self->old[0] + self->old[1])) >> self->feedback; + self->old[0] = self->old[1]; + self->old[1] = Operator__GetSample( Channel__Op(self, 0), mod ); + Bit32s sample; + Bit32s out0 = self->old[0]; + if ( mode == sm2AM || mode == sm3AM ) { + sample = out0 + Operator__GetSample( Channel__Op(self, 1), 0 ); + } else if ( mode == sm2FM || mode == sm3FM ) { + sample = Operator__GetSample( Channel__Op(self, 1), out0 ); + } else if ( mode == sm3FMFM ) { + Bits next = Operator__GetSample( Channel__Op(self, 1), out0 ); + next = Operator__GetSample( Channel__Op(self, 2), next ); + sample = Operator__GetSample( Channel__Op(self, 3), next ); + } else if ( mode == sm3AMFM ) { + sample = out0; + Bits next = Operator__GetSample( Channel__Op(self, 1), 0 ); + next = Operator__GetSample( Channel__Op(self, 2), next ); + sample += Operator__GetSample( Channel__Op(self, 3), next ); + } else if ( mode == sm3FMAM ) { + sample = Operator__GetSample( Channel__Op(self, 1), out0 ); + Bits next = Operator__GetSample( Channel__Op(self, 2), 0 ); + sample += Operator__GetSample( Channel__Op(self, 3), next ); + } else if ( mode == sm3AMAM ) { + sample = out0; + Bits next = Operator__GetSample( Channel__Op(self, 1), 0 ); + sample += Operator__GetSample( Channel__Op(self, 2), next ); + sample += Operator__GetSample( Channel__Op(self, 3), 0 ); + } + switch( mode ) { + case sm2AM: + case sm2FM: + output[ i ] += sample; + break; + case sm3AM: + case sm3FM: + case sm3FMFM: + case sm3AMFM: + case sm3FMAM: + case sm3AMAM: + output[ i * 2 + 0 ] += sample & self->maskLeft; + output[ i * 2 + 1 ] += sample & self->maskRight; + break; + default: + abort(); + } + } + switch( mode ) { + case sm2AM: + case sm2FM: + case sm3AM: + case sm3FM: + return ( self + 1 ); + case sm3FMFM: + case sm3AMFM: + case sm3FMAM: + case sm3AMAM: + return ( self + 2 ); + case sm2Percussion: + case sm3Percussion: + return( self + 3 ); + default: + abort(); + } + return 0; +} + +/* + Chip +*/ + +void Chip__Chip(Chip *self) { + int i; + + for (i=0; i<18; ++i) { + Channel__Channel(&self->chan[i]); + } + + self->reg08 = 0; + self->reg04 = 0; + self->regBD = 0; + self->reg104 = 0; + self->opl3Active = 0; +} + +static inline Bit32u Chip__ForwardNoise(Chip *self) { + self->noiseCounter += self->noiseAdd; + Bitu count = self->noiseCounter >> LFO_SH; + self->noiseCounter &= WAVE_MASK; + for ( ; count > 0; --count ) { + //Noise calculation from mame + self->noiseValue ^= ( 0x800302 ) & ( 0 - (self->noiseValue & 1 ) ); + self->noiseValue >>= 1; + } + return self->noiseValue; +} + +static inline Bit32u Chip__ForwardLFO(Chip *self, Bit32u samples ) { + //Current vibrato value, runs 4x slower than tremolo + self->vibratoSign = ( VibratoTable[ self->vibratoIndex >> 2] ) >> 7; + self->vibratoShift = ( VibratoTable[ self->vibratoIndex >> 2] & 7) + self->vibratoStrength; + self->tremoloValue = TremoloTable[ self->tremoloIndex ] >> self->tremoloStrength; + + //Check hom many samples there can be done before the value changes + Bit32u todo = LFO_MAX - self->lfoCounter; + Bit32u count = (todo + self->lfoAdd - 1) / self->lfoAdd; + if ( count > samples ) { + count = samples; + self->lfoCounter += count * self->lfoAdd; + } else { + self->lfoCounter += count * self->lfoAdd; + self->lfoCounter &= (LFO_MAX - 1); + //Maximum of 7 vibrato value * 4 + self->vibratoIndex = ( self->vibratoIndex + 1 ) & 31; + //Clip tremolo to the the table size + if ( self->tremoloIndex + 1 < TREMOLO_TABLE ) + ++self->tremoloIndex; + else + self->tremoloIndex = 0; + } + return count; +} + + +void Chip__WriteBD(Chip *self, Bit8u val ) { + Bit8u change = self->regBD ^ val; + if ( !change ) + return; + self->regBD = val; + //TODO could do this with shift and xor? + self->vibratoStrength = (val & 0x40) ? 0x00 : 0x01; + self->tremoloStrength = (val & 0x80) ? 0x00 : 0x02; + if ( val & 0x20 ) { + //Drum was just enabled, make sure channel 6 has the right synth + if ( change & 0x20 ) { + if ( self->opl3Active ) { + self->chan[6].synthHandler + = Channel__BlockTemplate_sm3Percussion; + } else { + self->chan[6].synthHandler + = Channel__BlockTemplate_sm2Percussion; + } + } + //Bass Drum + if ( val & 0x10 ) { + Operator__KeyOn( &self->chan[6].op[0], 0x2 ); + Operator__KeyOn( &self->chan[6].op[1], 0x2 ); + } else { + Operator__KeyOff( &self->chan[6].op[0], 0x2 ); + Operator__KeyOff( &self->chan[6].op[1], 0x2 ); + } + //Hi-Hat + if ( val & 0x1 ) { + Operator__KeyOn( &self->chan[7].op[0], 0x2 ); + } else { + Operator__KeyOff( &self->chan[7].op[0], 0x2 ); + } + //Snare + if ( val & 0x8 ) { + Operator__KeyOn( &self->chan[7].op[1], 0x2 ); + } else { + Operator__KeyOff( &self->chan[7].op[1], 0x2 ); + } + //Tom-Tom + if ( val & 0x4 ) { + Operator__KeyOn( &self->chan[8].op[0], 0x2 ); + } else { + Operator__KeyOff( &self->chan[8].op[0], 0x2 ); + } + //Top Cymbal + if ( val & 0x2 ) { + Operator__KeyOn( &self->chan[8].op[1], 0x2 ); + } else { + Operator__KeyOff( &self->chan[8].op[1], 0x2 ); + } + //Toggle keyoffs when we turn off the percussion + } else if ( change & 0x20 ) { + //Trigger a reset to setup the original synth handler + Channel__ResetC0( &self->chan[6], self ); + Operator__KeyOff( &self->chan[6].op[0], 0x2 ); + Operator__KeyOff( &self->chan[6].op[1], 0x2 ); + Operator__KeyOff( &self->chan[7].op[0], 0x2 ); + Operator__KeyOff( &self->chan[7].op[1], 0x2 ); + Operator__KeyOff( &self->chan[8].op[0], 0x2 ); + Operator__KeyOff( &self->chan[8].op[1], 0x2 ); + } +} + + +#define REGOP( _FUNC_ ) \ + index = ( ( reg >> 3) & 0x20 ) | ( reg & 0x1f ); \ + if ( OpOffsetTable[ index ] ) { \ + Operator* regOp = (Operator*)( ((char *)self ) + OpOffsetTable[ index ] ); \ + Operator__ ## _FUNC_ (regOp, self, val); \ + } + +#define REGCHAN( _FUNC_ ) \ + index = ( ( reg >> 4) & 0x10 ) | ( reg & 0xf ); \ + if ( ChanOffsetTable[ index ] ) { \ + Channel* regChan = (Channel*)( ((char *)self ) + ChanOffsetTable[ index ] ); \ + Channel__ ## _FUNC_ (regChan, self, val); \ + } + +void Chip__WriteReg(Chip *self, Bit32u reg, Bit8u val ) { + Bitu index; + switch ( (reg & 0xf0) >> 4 ) { + case 0x00 >> 4: + if ( reg == 0x01 ) { + self->waveFormMask = ( val & 0x20 ) ? 0x7 : 0x0; + } else if ( reg == 0x104 ) { + //Only detect changes in lowest 6 bits + if ( !((self->reg104 ^ val) & 0x3f) ) + return; + //Always keep the highest bit enabled, for checking > 0x80 + self->reg104 = 0x80 | ( val & 0x3f ); + } else if ( reg == 0x105 ) { + int i; + + //MAME says the real opl3 doesn't reset anything on opl3 disable/enable till the next write in another register + if ( !((self->opl3Active ^ val) & 1 ) ) + return; + self->opl3Active = ( val & 1 ) ? 0xff : 0; + //Update the 0xc0 register for all channels to signal the switch to mono/stereo handlers + for ( i = 0; i < 18;i++ ) { + Channel__ResetC0( &self->chan[i], self ); + } + } else if ( reg == 0x08 ) { + self->reg08 = val; + } + case 0x10 >> 4: + break; + case 0x20 >> 4: + case 0x30 >> 4: + REGOP( Write20 ); + break; + case 0x40 >> 4: + case 0x50 >> 4: + REGOP( Write40 ); + break; + case 0x60 >> 4: + case 0x70 >> 4: + REGOP( Write60 ); + break; + case 0x80 >> 4: + case 0x90 >> 4: + REGOP( Write80 ); + break; + case 0xa0 >> 4: + REGCHAN( WriteA0 ); + break; + case 0xb0 >> 4: + if ( reg == 0xbd ) { + Chip__WriteBD( self, val ); + } else { + REGCHAN( WriteB0 ); + } + break; + case 0xc0 >> 4: + REGCHAN( WriteC0 ); + case 0xd0 >> 4: + break; + case 0xe0 >> 4: + case 0xf0 >> 4: + REGOP( WriteE0 ); + break; + } +} + + +Bit32u Chip__WriteAddr(Chip *self, Bit32u port, Bit8u val ) { + switch ( port & 3 ) { + case 0: + return val; + case 2: + if ( self->opl3Active || (val == 0x05) ) + return 0x100 | val; + else + return val; + } + return 0; +} + +void Chip__GenerateBlock2(Chip *self, Bitu total, Bit32s* output ) { + while ( total > 0 ) { + Channel *ch; + int count; + + Bit32u samples = Chip__ForwardLFO( self, total ); + memset(output, 0, sizeof(Bit32s) * samples); + count = 0; + for ( ch = self->chan; ch < self->chan + 9; ) { + count++; + ch = (ch->synthHandler)( ch, self, samples, output ); + } + total -= samples; + output += samples; + } +} + +void Chip__GenerateBlock3(Chip *self, Bitu total, Bit32s* output ) { + while ( total > 0 ) { + int count; + Channel *ch; + + Bit32u samples = Chip__ForwardLFO( self, total ); + memset(output, 0, sizeof(Bit32s) * samples *2); + count = 0; + for ( ch = self->chan; ch < self->chan + 18; ) { + count++; + ch = (ch->synthHandler)( ch, self, samples, output ); + } + total -= samples; + output += samples * 2; + } +} + +void Chip__Setup(Chip *self, Bit32u rate ) { + double original = OPLRATE; + Bit32u i; +// double original = rate; + double scale = original / (double)rate; + + //Noise counter is run at the same precision as general waves + self->noiseAdd = (Bit32u)( 0.5 + scale * ( 1 << LFO_SH ) ); + self->noiseCounter = 0; + self->noiseValue = 1; //Make sure it triggers the noise xor the first time + //The low frequency oscillation counter + //Every time his overflows vibrato and tremoloindex are increased + self->lfoAdd = (Bit32u)( 0.5 + scale * ( 1 << LFO_SH ) ); + self->lfoCounter = 0; + self->vibratoIndex = 0; + self->tremoloIndex = 0; + + //With higher octave this gets shifted up + //-1 since the freqCreateTable = *2 +#ifdef WAVE_PRECISION + double freqScale = ( 1 << 7 ) * scale * ( 1 << ( WAVE_SH - 1 - 10)); + for ( i = 0; i < 16; i++ ) { + self->freqMul[i] = (Bit32u)( 0.5 + freqScale * FreqCreateTable[ i ] ); + } +#else + Bit32u freqScale = (Bit32u)( 0.5 + scale * ( 1 << ( WAVE_SH - 1 - 10))); + for ( i = 0; i < 16; i++ ) { + self->freqMul[i] = freqScale * FreqCreateTable[ i ]; + } +#endif + + //-3 since the real envelope takes 8 steps to reach the single value we supply + for ( i = 0; i < 76; i++ ) { + Bit8u index, shift; + EnvelopeSelect( i, &index, &shift ); + self->linearRates[i] = (Bit32u)( scale * (EnvelopeIncreaseTable[ index ] << ( RATE_SH + ENV_EXTRA - shift - 3 ))); + } + //Generate the best matching attack rate + for ( i = 0; i < 62; i++ ) { + Bit8u index, shift; + EnvelopeSelect( i, &index, &shift ); + //Original amount of samples the attack would take + Bit32s original = (Bit32u)( (AttackSamplesTable[ index ] << shift) / scale); + + Bit32s guessAdd = (Bit32u)( scale * (EnvelopeIncreaseTable[ index ] << ( RATE_SH - shift - 3 ))); + Bit32s bestAdd = guessAdd; + Bit32u bestDiff = 1 << 30; + Bit32u passes; + + for ( passes = 0; passes < 16; passes ++ ) { + Bit32s volume = ENV_MAX; + Bit32s samples = 0; + Bit32u count = 0; + while ( volume > 0 && samples < original * 2 ) { + count += guessAdd; + Bit32s change = count >> RATE_SH; + count &= RATE_MASK; + if ( GCC_UNLIKELY(change) ) { // less than 1 % + volume += ( ~volume * change ) >> 3; + } + samples++; + + } + Bit32s diff = original - samples; + Bit32u lDiff = labs( diff ); + //Init last on first pass + if ( lDiff < bestDiff ) { + bestDiff = lDiff; + bestAdd = guessAdd; + if ( !bestDiff ) + break; + } + //Below our target + if ( diff < 0 ) { + //Better than the last time + Bit32s mul = ((original - diff) << 12) / original; + guessAdd = ((guessAdd * mul) >> 12); + guessAdd++; + } else if ( diff > 0 ) { + Bit32s mul = ((original - diff) << 12) / original; + guessAdd = (guessAdd * mul) >> 12; + guessAdd--; + } + } + self->attackRates[i] = bestAdd; + } + for ( i = 62; i < 76; i++ ) { + //This should provide instant volume maximizing + self->attackRates[i] = 8 << RATE_SH; + } + //Setup the channels with the correct four op flags + //Channels are accessed through a table so they appear linear here + self->chan[ 0].fourMask = 0x00 | ( 1 << 0 ); + self->chan[ 1].fourMask = 0x80 | ( 1 << 0 ); + self->chan[ 2].fourMask = 0x00 | ( 1 << 1 ); + self->chan[ 3].fourMask = 0x80 | ( 1 << 1 ); + self->chan[ 4].fourMask = 0x00 | ( 1 << 2 ); + self->chan[ 5].fourMask = 0x80 | ( 1 << 2 ); + + self->chan[ 9].fourMask = 0x00 | ( 1 << 3 ); + self->chan[10].fourMask = 0x80 | ( 1 << 3 ); + self->chan[11].fourMask = 0x00 | ( 1 << 4 ); + self->chan[12].fourMask = 0x80 | ( 1 << 4 ); + self->chan[13].fourMask = 0x00 | ( 1 << 5 ); + self->chan[14].fourMask = 0x80 | ( 1 << 5 ); + + //mark the percussion channels + self->chan[ 6].fourMask = 0x40; + self->chan[ 7].fourMask = 0x40; + self->chan[ 8].fourMask = 0x40; + + //Clear Everything in opl3 mode + Chip__WriteReg( self, 0x105, 0x1 ); + for ( i = 0; i < 512; i++ ) { + if ( i == 0x105 ) + continue; + Chip__WriteReg( self, i, 0xff ); + Chip__WriteReg( self, i, 0x0 ); + } + Chip__WriteReg( self, 0x105, 0x0 ); + //Clear everything in opl2 mode + for ( i = 0; i < 255; i++ ) { + Chip__WriteReg( self, i, 0xff ); + Chip__WriteReg( self, i, 0x0 ); + } +} + +static int doneTables = FALSE; +void InitTables( void ) { + int i, oct; + + if ( doneTables ) + return; + doneTables = TRUE; +#if ( DBOPL_WAVE == WAVE_HANDLER ) || ( DBOPL_WAVE == WAVE_TABLELOG ) + //Exponential volume table, same as the real adlib + for ( i = 0; i < 256; i++ ) { + //Save them in reverse + ExpTable[i] = (int)( 0.5 + ( pow(2.0, ( 255 - i) * ( 1.0 /256 ) )-1) * 1024 ); + ExpTable[i] += 1024; //or remove the -1 oh well :) + //Preshift to the left once so the final volume can shift to the right + ExpTable[i] *= 2; + } +#endif +#if ( DBOPL_WAVE == WAVE_HANDLER ) + //Add 0.5 for the trunc rounding of the integer cast + //Do a PI sinetable instead of the original 0.5 PI + for ( i = 0; i < 512; i++ ) { + SinTable[i] = (Bit16s)( 0.5 - log10( sin( (i + 0.5) * (PI / 512.0) ) ) / log10(2.0)*256 ); + } +#endif +#if ( DBOPL_WAVE == WAVE_TABLEMUL ) + //Multiplication based tables + for ( i = 0; i < 384; i++ ) { + int s = i * 8; + //TODO maybe keep some of the precision errors of the original table? + double val = ( 0.5 + ( pow(2.0, -1.0 + ( 255 - s) * ( 1.0 /256 ) )) * ( 1 << MUL_SH )); + MulTable[i] = (Bit16u)(val); + } + + //Sine Wave Base + for ( i = 0; i < 512; i++ ) { + WaveTable[ 0x0200 + i ] = (Bit16s)(sin( (i + 0.5) * (PI / 512.0) ) * 4084); + WaveTable[ 0x0000 + i ] = -WaveTable[ 0x200 + i ]; + } + //Exponential wave + for ( i = 0; i < 256; i++ ) { + WaveTable[ 0x700 + i ] = (Bit16s)( 0.5 + ( pow(2.0, -1.0 + ( 255 - i * 8) * ( 1.0 /256 ) ) ) * 4085 ); + WaveTable[ 0x6ff - i ] = -WaveTable[ 0x700 + i ]; + } +#endif +#if ( DBOPL_WAVE == WAVE_TABLELOG ) + //Sine Wave Base + for ( i = 0; i < 512; i++ ) { + WaveTable[ 0x0200 + i ] = (Bit16s)( 0.5 - log10( sin( (i + 0.5) * (PI / 512.0) ) ) / log10(2.0)*256 ); + WaveTable[ 0x0000 + i ] = ((Bit16s)0x8000) | WaveTable[ 0x200 + i]; + } + //Exponential wave + for ( i = 0; i < 256; i++ ) { + WaveTable[ 0x700 + i ] = i * 8; + WaveTable[ 0x6ff - i ] = ((Bit16s)0x8000) | i * 8; + } +#endif + + // | |//\\|____|WAV7|//__|/\ |____|/\/\| + // |\\//| | |WAV7| | \/| | | + // |06 |0126|27 |7 |3 |4 |4 5 |5 | + +#if (( DBOPL_WAVE == WAVE_TABLELOG ) || ( DBOPL_WAVE == WAVE_TABLEMUL )) + for ( i = 0; i < 256; i++ ) { + //Fill silence gaps + WaveTable[ 0x400 + i ] = WaveTable[0]; + WaveTable[ 0x500 + i ] = WaveTable[0]; + WaveTable[ 0x900 + i ] = WaveTable[0]; + WaveTable[ 0xc00 + i ] = WaveTable[0]; + WaveTable[ 0xd00 + i ] = WaveTable[0]; + //Replicate sines in other pieces + WaveTable[ 0x800 + i ] = WaveTable[ 0x200 + i ]; + //double speed sines + WaveTable[ 0xa00 + i ] = WaveTable[ 0x200 + i * 2 ]; + WaveTable[ 0xb00 + i ] = WaveTable[ 0x000 + i * 2 ]; + WaveTable[ 0xe00 + i ] = WaveTable[ 0x200 + i * 2 ]; + WaveTable[ 0xf00 + i ] = WaveTable[ 0x200 + i * 2 ]; + } +#endif + + //Create the ksl table + for ( oct = 0; oct < 8; oct++ ) { + int base = oct * 8; + for ( i = 0; i < 16; i++ ) { + int val = base - KslCreateTable[i]; + if ( val < 0 ) + val = 0; + //*4 for the final range to match attenuation range + KslTable[ oct * 16 + i ] = val * 4; + } + } + //Create the Tremolo table, just increase and decrease a triangle wave + for ( i = 0; i < TREMOLO_TABLE / 2; i++ ) { + Bit8u val = i << ENV_EXTRA; + TremoloTable[i] = val; + TremoloTable[TREMOLO_TABLE - 1 - i] = val; + } + //Create a table with offsets of the channels from the start of the chip + Chip *chip = NULL; + for ( i = 0; i < 32; i++ ) { + Bitu index = i & 0xf; + if ( index >= 9 ) { + ChanOffsetTable[i] = 0; + continue; + } + //Make sure the four op channels follow eachother + if ( index < 6 ) { + index = (index % 3) * 2 + ( index / 3 ); + } + //Add back the bits for highest ones + if ( i >= 16 ) + index += 9; + Bitu blah = (Bitu) ( &(chip->chan[ index ]) ); + ChanOffsetTable[i] = blah; + } + //Same for operators + for ( i = 0; i < 64; i++ ) { + if ( i % 8 >= 6 || ( (i / 8) % 4 == 3 ) ) { + OpOffsetTable[i] = 0; + continue; + } + Bitu chNum = (i / 8) * 3 + (i % 8) % 3; + //Make sure we use 16 and up for the 2nd range to match the chanoffset gap + if ( chNum >= 12 ) + chNum += 16 - 12; + Bitu opNum = ( i % 8 ) / 3; + Channel* chan = NULL; + Bitu blah = (Bitu) ( &(chan->op[opNum]) ); + OpOffsetTable[i] = ChanOffsetTable[ chNum ] + blah; + } +#if 0 + //Stupid checks if table's are correct + for ( Bitu i = 0; i < 18; i++ ) { + Bit32u find = (Bit16u)( &(chip->chan[ i ]) ); + for ( Bitu c = 0; c < 32; c++ ) { + if ( ChanOffsetTable[c] == find ) { + find = 0; + break; + } + } + if ( find ) { + find = find; + } + } + for ( Bitu i = 0; i < 36; i++ ) { + Bit32u find = (Bit16u)( &(chip->chan[ i / 2 ].op[i % 2]) ); + for ( Bitu c = 0; c < 64; c++ ) { + if ( OpOffsetTable[c] == find ) { + find = 0; + break; + } + } + if ( find ) { + find = find; + } + } +#endif +} + +/* + +Bit32u Handler::WriteAddr( Bit32u port, Bit8u val ) { + return chip.WriteAddr( port, val ); + +} +void Handler::WriteReg( Bit32u addr, Bit8u val ) { + chip.WriteReg( addr, val ); +} + +void Handler::Generate( MixerChannel* chan, Bitu samples ) { + Bit32s buffer[ 512 * 2 ]; + if ( GCC_UNLIKELY(samples > 512) ) + samples = 512; + if ( !chip.opl3Active ) { + chip.GenerateBlock2( samples, buffer ); + chan->AddSamples_m32( samples, buffer ); + } else { + chip.GenerateBlock3( samples, buffer ); + chan->AddSamples_s32( samples, buffer ); + } +} + +void Handler::Init( Bitu rate ) { + InitTables(); + chip.Setup( rate ); +} +*/ + diff --git a/opl/dbopl.h b/opl/dbopl.h new file mode 100644 index 00000000..f7d2e416 --- /dev/null +++ b/opl/dbopl.h @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2002-2010 The DOSBox Team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include + +//Use 8 handlers based on a small logatirmic wavetabe and an exponential table for volume +#define WAVE_HANDLER 10 +//Use a logarithmic wavetable with an exponential table for volume +#define WAVE_TABLELOG 11 +//Use a linear wavetable with a multiply table for volume +#define WAVE_TABLEMUL 12 + +//Select the type of wave generator routine +#define DBOPL_WAVE WAVE_TABLEMUL + +typedef struct _Chip Chip; +typedef struct _Operator Operator; +typedef struct _Channel Channel; + +typedef uintptr_t Bitu; +typedef intptr_t Bits; +typedef uint32_t Bit32u; +typedef int32_t Bit32s; +typedef uint16_t Bit16u; +typedef int16_t Bit16s; +typedef uint8_t Bit8u; +typedef int8_t Bit8s; + +#if (DBOPL_WAVE == WAVE_HANDLER) +typedef Bits ( DB_FASTCALL *WaveHandler) ( Bitu i, Bitu volume ); +#endif + +#define DB_FASTCALL + +typedef Bits (*VolumeHandler)(); +typedef Channel* (*SynthHandler)(Channel *self, Chip* chip, Bit32u samples, Bit32s* output ); + +//Different synth modes that can generate blocks of data +typedef enum { + sm2AM, + sm2FM, + sm3AM, + sm3FM, + sm4Start, + sm3FMFM, + sm3AMFM, + sm3FMAM, + sm3AMAM, + sm6Start, + sm2Percussion, + sm3Percussion, +} SynthMode; + +//Shifts for the values contained in chandata variable +enum { + SHIFT_KSLBASE = 16, + SHIFT_KEYCODE = 24, +}; + +//Masks for operator 20 values +enum { + MASK_KSR = 0x10, + MASK_SUSTAIN = 0x20, + MASK_VIBRATO = 0x40, + MASK_TREMOLO = 0x80, +}; + +typedef enum { + OFF, + RELEASE, + SUSTAIN, + DECAY, + ATTACK, +} OperatorState; + +struct _Operator { + VolumeHandler volHandler; + +#if (DBOPL_WAVE == WAVE_HANDLER) + WaveHandler waveHandler; //Routine that generate a wave +#else + Bit16s* waveBase; + Bit32u waveMask; + Bit32u waveStart; +#endif + Bit32u waveIndex; //WAVE_BITS shifted counter of the frequency index + Bit32u waveAdd; //The base frequency without vibrato + Bit32u waveCurrent; //waveAdd + vibratao + + Bit32u chanData; //Frequency/octave and derived data coming from whatever channel controls this + Bit32u freqMul; //Scale channel frequency with this, TODO maybe remove? + Bit32u vibrato; //Scaled up vibrato strength + Bit32s sustainLevel; //When stopping at sustain level stop here + Bit32s totalLevel; //totalLevel is added to every generated volume + Bit32u currentLevel; //totalLevel + tremolo + Bit32s volume; //The currently active volume + + Bit32u attackAdd; //Timers for the different states of the envelope + Bit32u decayAdd; + Bit32u releaseAdd; + Bit32u rateIndex; //Current position of the evenlope + + Bit8u rateZero; //Bits for the different states of the envelope having no changes + Bit8u keyOn; //Bitmask of different values that can generate keyon + //Registers, also used to check for changes + Bit8u reg20, reg40, reg60, reg80, regE0; + //Active part of the envelope we're in + Bit8u state; + //0xff when tremolo is enabled + Bit8u tremoloMask; + //Strength of the vibrato + Bit8u vibStrength; + //Keep track of the calculated KSR so we can check for changes + Bit8u ksr; +}; + +struct _Channel { + Operator op[2]; + SynthHandler synthHandler; + Bit32u chanData; //Frequency/octave and derived values + Bit32s old[2]; //Old data for feedback + + Bit8u feedback; //Feedback shift + Bit8u regB0; //Register values to check for changes + Bit8u regC0; + //This should correspond with reg104, bit 6 indicates a Percussion channel, bit 7 indicates a silent channel + Bit8u fourMask; + Bit8s maskLeft; //Sign extended values for both channel's panning + Bit8s maskRight; + +}; + +struct _Chip { + //This is used as the base counter for vibrato and tremolo + Bit32u lfoCounter; + Bit32u lfoAdd; + + + Bit32u noiseCounter; + Bit32u noiseAdd; + Bit32u noiseValue; + + //Frequency scales for the different multiplications + Bit32u freqMul[16]; + //Rates for decay and release for rate of this chip + Bit32u linearRates[76]; + //Best match attack rates for the rate of this chip + Bit32u attackRates[76]; + + //18 channels with 2 operators each + Channel chan[18]; + + Bit8u reg104; + Bit8u reg08; + Bit8u reg04; + Bit8u regBD; + Bit8u vibratoIndex; + Bit8u tremoloIndex; + Bit8s vibratoSign; + Bit8u vibratoShift; + Bit8u tremoloValue; + Bit8u vibratoStrength; + Bit8u tremoloStrength; + //Mask for allowed wave forms + Bit8u waveFormMask; + //0 or -1 when enabled + Bit8s opl3Active; + +}; + +/* +struct Handler : public Adlib::Handler { + DBOPL::Chip chip; + virtual Bit32u WriteAddr( Bit32u port, Bit8u val ); + virtual void WriteReg( Bit32u addr, Bit8u val ); + virtual void Generate( MixerChannel* chan, Bitu samples ); + virtual void Init( Bitu rate ); +}; +*/ + + -- cgit v1.2.3 From 578a06c798f9b0d52f74d8bfc9258ef74a9bb6c9 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Sun, 15 Aug 2010 14:57:37 +0000 Subject: Hook DBOPL into OPL library and remove FMOPL. Does not generate any sound yet. Subversion-branch: /trunk/chocolate-doom Subversion-revision: 1956 --- opl/Makefile.am | 2 +- opl/dbopl.c | 52 +-- opl/dbopl.h | 9 +- opl/fmopl.c | 1155 ------------------------------------------------------- opl/fmopl.h | 167 -------- opl/opl_sdl.c | 158 +++++--- 6 files changed, 143 insertions(+), 1400 deletions(-) delete mode 100644 opl/fmopl.c delete mode 100644 opl/fmopl.h (limited to 'opl') diff --git a/opl/Makefile.am b/opl/Makefile.am index d099b875..be1619d8 100644 --- a/opl/Makefile.am +++ b/opl/Makefile.am @@ -15,5 +15,5 @@ libopl_a_SOURCES = \ opl_timer.c opl_timer.h \ opl_win32.c \ ioperm_sys.c ioperm_sys.h \ - fmopl.c fmopl.h + dbopl.c dbopl.h diff --git a/opl/dbopl.c b/opl/dbopl.c index 556c570d..a4bd6fe5 100644 --- a/opl/dbopl.c +++ b/opl/dbopl.c @@ -324,7 +324,7 @@ static const WaveHandler WaveHandlerTable[8] = { */ //We zero out when rate == 0 -inline void Operator__UpdateAttack(Operator *self, const Chip* chip ) { +static inline void Operator__UpdateAttack(Operator *self, const Chip* chip ) { Bit8u rate = self->reg60 >> 4; if ( rate ) { Bit8u val = (rate << 2) + self->ksr; @@ -335,7 +335,7 @@ inline void Operator__UpdateAttack(Operator *self, const Chip* chip ) { self->rateZero |= (1 << ATTACK); } } -inline void Operator__UpdateDecay(Operator *self, const Chip* chip ) { +static inline void Operator__UpdateDecay(Operator *self, const Chip* chip ) { Bit8u rate = self->reg60 & 0xf; if ( rate ) { Bit8u val = (rate << 2) + self->ksr; @@ -346,7 +346,7 @@ inline void Operator__UpdateDecay(Operator *self, const Chip* chip ) { self->rateZero |= (1 << DECAY); } } -inline void Operator__UpdateRelease(Operator *self, const Chip* chip ) { +static inline void Operator__UpdateRelease(Operator *self, const Chip* chip ) { Bit8u rate = self->reg80 & 0xf; if ( rate ) { Bit8u val = (rate << 2) + self->ksr; @@ -364,7 +364,7 @@ inline void Operator__UpdateRelease(Operator *self, const Chip* chip ) { } } -inline void Operator__UpdateAttenuation(Operator *self) { +static inline void Operator__UpdateAttenuation(Operator *self) { Bit8u kslBase = (Bit8u)((self->chanData >> SHIFT_KSLBASE) & 0xff); Bit32u tl = self->reg40 & 0x3f; Bit8u kslShift = KslShiftTable[ self->reg40 >> 6 ]; @@ -373,7 +373,7 @@ inline void Operator__UpdateAttenuation(Operator *self) { self->totalLevel += ( kslBase << ENV_EXTRA ) >> kslShift; } -void Operator__UpdateFrequency(Operator *self) { +static void Operator__UpdateFrequency(Operator *self) { Bit32u freq = self->chanData & (( 1 << 10 ) - 1); Bit32u block = (self->chanData >> 10) & 0xff; #ifdef WAVE_PRECISION @@ -396,7 +396,7 @@ void Operator__UpdateFrequency(Operator *self) { } } -void Operator__UpdateRates(Operator *self, const Chip* chip ) { +static void Operator__UpdateRates(Operator *self, const Chip* chip ) { //Mame seems to reverse this where enabling ksr actually lowers //the rate, but pdf manuals says otherwise? Bit8u newKsr = (Bit8u)((self->chanData >> SHIFT_KEYCODE) & 0xff); @@ -489,7 +489,7 @@ static const VolumeHandler VolumeHandlerTable[5] = { }; static inline Bitu Operator__ForwardVolume(Operator *self) { - return self->currentLevel + (self->volHandler)(); + return self->currentLevel + (self->volHandler)(self); } @@ -498,7 +498,7 @@ static inline Bitu Operator__ForwardWave(Operator *self) { return self->waveIndex >> WAVE_SH; } -void Operator__Write20(Operator *self, const Chip* chip, Bit8u val ) { +static void Operator__Write20(Operator *self, const Chip* chip, Bit8u val ) { Bit8u change = (self->reg20 ^ val ); if ( !change ) return; @@ -523,14 +523,14 @@ void Operator__Write20(Operator *self, const Chip* chip, Bit8u val ) { } } -void Operator__Write40(Operator *self, const Chip *chip, Bit8u val ) { +static void Operator__Write40(Operator *self, const Chip *chip, Bit8u val ) { if (!(self->reg40 ^ val )) return; self->reg40 = val; Operator__UpdateAttenuation( self ); } -void Operator__Write60(Operator *self, const Chip* chip, Bit8u val ) { +static void Operator__Write60(Operator *self, const Chip* chip, Bit8u val ) { Bit8u change = self->reg60 ^ val; self->reg60 = val; if ( change & 0x0f ) { @@ -541,7 +541,7 @@ void Operator__Write60(Operator *self, const Chip* chip, Bit8u val ) { } } -void Operator__Write80(Operator *self, const Chip* chip, Bit8u val ) { +static void Operator__Write80(Operator *self, const Chip* chip, Bit8u val ) { Bit8u change = (self->reg80 ^ val ); if ( !change ) return; @@ -555,7 +555,7 @@ void Operator__Write80(Operator *self, const Chip* chip, Bit8u val ) { } } -void Operator__WriteE0(Operator *self, const Chip* chip, Bit8u val ) { +static void Operator__WriteE0(Operator *self, const Chip* chip, Bit8u val ) { if ( !(self->regE0 ^ val) ) return; //in opl3 mode you can always selet 7 waveforms regardless of waveformselect @@ -596,7 +596,7 @@ static inline void Operator__Prepare(Operator *self, const Chip* chip ) { } } -void Operator__KeyOn(Operator *self, Bit8u mask ) { +static void Operator__KeyOn(Operator *self, Bit8u mask ) { if ( !self->keyOn ) { //Restart the frequency generator #if( DBOPL_WAVE > WAVE_HANDLER ) @@ -610,7 +610,7 @@ void Operator__KeyOn(Operator *self, Bit8u mask ) { self->keyOn |= mask; } -void Operator__KeyOff(Operator *self, Bit8u mask ) { +static void Operator__KeyOff(Operator *self, Bit8u mask ) { self->keyOn &= ~mask; if ( !self->keyOn ) { if ( self->state != OFF ) { @@ -649,7 +649,7 @@ static inline Bits Operator__GetSample(Operator *self, Bits modulation ) { } } -void Operator__Operator(Operator *self) { +static void Operator__Operator(Operator *self) { self->chanData = 0; self->freqMul = 0; self->waveIndex = 0; @@ -675,7 +675,7 @@ void Operator__Operator(Operator *self) { Channel */ -void Channel__Channel(Channel *self) { +static void Channel__Channel(Channel *self) { Operator__Operator(&self->op[0]); Operator__Operator(&self->op[1]); self->old[0] = self->old[1] = 0; @@ -693,7 +693,7 @@ static inline Operator* Channel__Op( Channel *self, Bitu index ) { return &( ( self + (index >> 1) )->op[ index & 1 ]); } -void Channel__SetChanData(Channel *self, const Chip* chip, Bit32u data ) { +static void Channel__SetChanData(Channel *self, const Chip* chip, Bit32u data ) { Bit32u change = self->chanData ^ data; self->chanData = data; Channel__Op( self, 0 )->chanData = data; @@ -711,7 +711,7 @@ void Channel__SetChanData(Channel *self, const Chip* chip, Bit32u data ) { } } -void Channel__UpdateFrequency(Channel *self, const Chip* chip, Bit8u fourOp ) { +static void Channel__UpdateFrequency(Channel *self, const Chip* chip, Bit8u fourOp ) { //Extrace the frequency bits Bit32u data = self->chanData & 0xffff; Bit32u kslBase = KslTable[ data >> 6 ]; @@ -729,7 +729,7 @@ void Channel__UpdateFrequency(Channel *self, const Chip* chip, Bit8u fourOp ) { } } -void Channel__WriteA0(Channel *self, const Chip* chip, Bit8u val ) { +static void Channel__WriteA0(Channel *self, const Chip* chip, Bit8u val ) { Bit8u fourOp = chip->reg104 & chip->opl3Active & self->fourMask; //Don't handle writes to silent fourop channels if ( fourOp > 0x80 ) @@ -741,7 +741,7 @@ void Channel__WriteA0(Channel *self, const Chip* chip, Bit8u val ) { } } -void Channel__WriteB0(Channel *self, const Chip* chip, Bit8u val ) { +static void Channel__WriteB0(Channel *self, const Chip* chip, Bit8u val ) { Bit8u fourOp = chip->reg104 & chip->opl3Active & self->fourMask; //Don't handle writes to silent fourop channels if ( fourOp > 0x80 ) @@ -772,7 +772,7 @@ void Channel__WriteB0(Channel *self, const Chip* chip, Bit8u val ) { } } -void Channel__WriteC0(Channel *self, const Chip* chip, Bit8u val ) { +static void Channel__WriteC0(Channel *self, const Chip* chip, Bit8u val ) { Bit8u change = val ^ self->regC0; if ( !change ) return; @@ -838,7 +838,7 @@ void Channel__WriteC0(Channel *self, const Chip* chip, Bit8u val ) { } } -void Channel__ResetC0(Channel *self, const Chip* chip ) { +static void Channel__ResetC0(Channel *self, const Chip* chip ) { Bit8u val = self->regC0; self->regC0 ^= 0xff; Channel__WriteC0( self, chip, val ); @@ -975,7 +975,7 @@ Channel* Channel__BlockTemplate(Channel *self, Chip* chip, Bit32s mod = (Bit32u)((self->old[0] + self->old[1])) >> self->feedback; self->old[0] = self->old[1]; self->old[1] = Operator__GetSample( Channel__Op(self, 0), mod ); - Bit32s sample; + Bit32s sample = 0; Bit32s out0 = self->old[0]; if ( mode == sm2AM || mode == sm3AM ) { sample = out0 + Operator__GetSample( Channel__Op(self, 1), 0 ); @@ -1095,7 +1095,7 @@ static inline Bit32u Chip__ForwardLFO(Chip *self, Bit32u samples ) { } -void Chip__WriteBD(Chip *self, Bit8u val ) { +static void Chip__WriteBD(Chip *self, Bit8u val ) { Bit8u change = self->regBD ^ val; if ( !change ) return; @@ -1240,7 +1240,7 @@ void Chip__WriteReg(Chip *self, Bit32u reg, Bit8u val ) { } -Bit32u Chip__WriteAddr(Chip *self, Bit32u port, Bit8u val ) { +static Bit32u Chip__WriteAddr(Chip *self, Bit32u port, Bit8u val ) { switch ( port & 3 ) { case 0: return val; @@ -1415,7 +1415,7 @@ void Chip__Setup(Chip *self, Bit32u rate ) { } static int doneTables = FALSE; -void InitTables( void ) { +void DBOPL_InitTables( void ) { int i, oct; if ( doneTables ) diff --git a/opl/dbopl.h b/opl/dbopl.h index f7d2e416..a5c10bfd 100644 --- a/opl/dbopl.h +++ b/opl/dbopl.h @@ -47,7 +47,7 @@ typedef Bits ( DB_FASTCALL *WaveHandler) ( Bitu i, Bitu volume ); #define DB_FASTCALL -typedef Bits (*VolumeHandler)(); +typedef Bits (*VolumeHandler)(Operator *self); typedef Channel* (*SynthHandler)(Channel *self, Chip* chip, Bit32u samples, Bit32s* output ); //Different synth modes that can generate blocks of data @@ -194,3 +194,10 @@ struct Handler : public Adlib::Handler { */ +void Chip__Setup(Chip *self, Bit32u rate ); +void DBOPL_InitTables( void ); +void Chip__Chip(Chip *self); +void Chip__WriteReg(Chip *self, Bit32u reg, Bit8u val ); +void Chip__GenerateBlock2(Chip *self, Bitu total, Bit32s* output ); + + diff --git a/opl/fmopl.c b/opl/fmopl.c deleted file mode 100644 index 1671244e..00000000 --- a/opl/fmopl.c +++ /dev/null @@ -1,1155 +0,0 @@ -/* This file is derived from fmopl.cpp from ScummVM. - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * LGPL licensed version of MAMEs fmopl (V0.37a modified) by - * Tatsuyuki Satoh. Included from LGPL'ed AdPlug. - */ - -#include -#include -#include -#include -#include - -#include "fmopl.h" - -#define PI 3.1415926539 - -#define CLIP(value, min, max) \ - ( (value) < (min) ? (min) : \ - (value) > (max) ? (max) : (value) ) - -/* -------------------- preliminary define section --------------------- */ -/* attack/decay rate time rate */ -#define OPL_ARRATE 141280 /* RATE 4 = 2826.24ms @ 3.6MHz */ -#define OPL_DRRATE 1956000 /* RATE 4 = 39280.64ms @ 3.6MHz */ - -#define FREQ_BITS 24 /* frequency turn */ - -/* counter bits = 20 , octerve 7 */ -#define FREQ_RATE (1<<(FREQ_BITS-20)) -#define TL_BITS (FREQ_BITS+2) - -/* final output shift , limit minimum and maximum */ -#define OPL_OUTSB (TL_BITS+3-16) /* OPL output final shift 16bit */ -#define OPL_MAXOUT (0x7fff<status |= flag; - if(!(OPL->status & 0x80)) { - if(OPL->status & OPL->statusmask) { /* IRQ on */ - OPL->status |= 0x80; - /* callback user interrupt handler (IRQ is OFF to ON) */ - if(OPL->IRQHandler) - (OPL->IRQHandler)(OPL->IRQParam,1); - } - } -} - -/* status reset and IRQ handling */ -static inline void OPL_STATUS_RESET(FM_OPL *OPL, int flag) { - /* reset status flag */ - OPL->status &= ~flag; - if((OPL->status & 0x80)) { - if (!(OPL->status & OPL->statusmask)) { - OPL->status &= 0x7f; - /* callback user interrupt handler (IRQ is ON to OFF) */ - if(OPL->IRQHandler) (OPL->IRQHandler)(OPL->IRQParam,0); - } - } -} - -/* IRQ mask set */ -static inline void OPL_STATUSMASK_SET(FM_OPL *OPL, int flag) { - OPL->statusmask = flag; - /* IRQ handling check */ - OPL_STATUS_SET(OPL,0); - OPL_STATUS_RESET(OPL,0); -} - -/* ----- key on ----- */ -static inline void OPL_KEYON(OPL_SLOT *SLOT) { - /* sin wave restart */ - SLOT->Cnt = 0; - /* set attack */ - SLOT->evm = ENV_MOD_AR; - SLOT->evs = SLOT->evsa; - SLOT->evc = EG_AST; - SLOT->eve = EG_AED; -} - -/* ----- key off ----- */ -static inline void OPL_KEYOFF(OPL_SLOT *SLOT) { - if( SLOT->evm > ENV_MOD_RR) { - /* set envelope counter from envleope output */ - - // WORKAROUND: The Kyra engine does something very strange when - // starting a new song. For each channel: - // - // * The release rate is set to "fastest". - // * Any note is keyed off. - // * A very low-frequency note is keyed on. - // - // Usually, what happens next is that the real notes is keyed - // on immediately, in which case there's no problem. - // - // However, if the note is again keyed off (because the channel - // begins on a rest rather than a note), the envelope counter - // was moved from the very lowest point on the attack curve to - // the very highest point on the release curve. - // - // Again, this might not be a problem, if the release rate is - // still set to "fastest". But in many cases, it had already - // been increased. And, possibly because of inaccuracies in the - // envelope generator, that would cause the note to "fade out" - // for quite a long time. - // - // What we really need is a way to find the correct starting - // point for the envelope counter, and that may be what the - // commented-out line below is meant to do. For now, simply - // handle the pathological case. - - if (SLOT->evm == ENV_MOD_AR && SLOT->evc == EG_AST) - SLOT->evc = EG_DED; - else if( !(SLOT->evc & EG_DST) ) - //SLOT->evc = (ENV_CURVE[SLOT->evc>>ENV_BITS]<evc = EG_DST; - SLOT->eve = EG_DED; - SLOT->evs = SLOT->evsr; - SLOT->evm = ENV_MOD_RR; - } -} - -/* ---------- calcrate Envelope Generator & Phase Generator ---------- */ - -/* return : envelope output */ -static inline uint32_t OPL_CALC_SLOT(OPL_SLOT *SLOT) { - /* calcrate envelope generator */ - if((SLOT->evc += SLOT->evs) >= SLOT->eve) { - switch( SLOT->evm ) { - case ENV_MOD_AR: /* ATTACK -> DECAY1 */ - /* next DR */ - SLOT->evm = ENV_MOD_DR; - SLOT->evc = EG_DST; - SLOT->eve = SLOT->SL; - SLOT->evs = SLOT->evsd; - break; - case ENV_MOD_DR: /* DECAY -> SL or RR */ - SLOT->evc = SLOT->SL; - SLOT->eve = EG_DED; - if(SLOT->eg_typ) { - SLOT->evs = 0; - } else { - SLOT->evm = ENV_MOD_RR; - SLOT->evs = SLOT->evsr; - } - break; - case ENV_MOD_RR: /* RR -> OFF */ - SLOT->evc = EG_OFF; - SLOT->eve = EG_OFF + 1; - SLOT->evs = 0; - break; - } - } - /* calcrate envelope */ - return SLOT->TLL + ENV_CURVE[SLOT->evc>>ENV_BITS] + (SLOT->ams ? ams : 0); -} - -/* set algorythm connection */ -static void set_algorythm(OPL_CH *CH) { - int *carrier = &outd[0]; - CH->connect1 = CH->CON ? carrier : &feedback2; - CH->connect2 = carrier; -} - -/* ---------- frequency counter for operater update ---------- */ -static inline void CALC_FCSLOT(OPL_CH *CH, OPL_SLOT *SLOT) { - int ksr; - - /* frequency step counter */ - SLOT->Incr = CH->fc * SLOT->mul; - ksr = CH->kcode >> SLOT->KSR; - - if( SLOT->ksr != ksr ) { - SLOT->ksr = ksr; - /* attack , decay rate recalcration */ - SLOT->evsa = SLOT->AR[ksr]; - SLOT->evsd = SLOT->DR[ksr]; - SLOT->evsr = SLOT->RR[ksr]; - } - SLOT->TLL = SLOT->TL + (CH->ksl_base>>SLOT->ksl); -} - -/* set multi,am,vib,EG-TYP,KSR,mul */ -static inline void set_mul(FM_OPL *OPL, int slot, int v) { - OPL_CH *CH = &OPL->P_CH[slot>>1]; - OPL_SLOT *SLOT = &CH->SLOT[slot & 1]; - - SLOT->mul = MUL_TABLE[v & 0x0f]; - SLOT->KSR = (v & 0x10) ? 0 : 2; - SLOT->eg_typ = (v & 0x20) >> 5; - SLOT->vib = (v & 0x40); - SLOT->ams = (v & 0x80); - CALC_FCSLOT(CH, SLOT); -} - -/* set ksl & tl */ -static inline void set_ksl_tl(FM_OPL *OPL, int slot, int v) { - OPL_CH *CH = &OPL->P_CH[slot>>1]; - OPL_SLOT *SLOT = &CH->SLOT[slot & 1]; - int ksl = v >> 6; /* 0 / 1.5 / 3 / 6 db/OCT */ - - SLOT->ksl = ksl ? 3-ksl : 31; - SLOT->TL = (int)((v & 0x3f) * (0.75 / EG_STEP)); /* 0.75db step */ - - if(!(OPL->mode & 0x80)) { /* not CSM latch total level */ - SLOT->TLL = SLOT->TL + (CH->ksl_base >> SLOT->ksl); - } -} - -/* set attack rate & decay rate */ -static inline void set_ar_dr(FM_OPL *OPL, int slot, int v) { - OPL_CH *CH = &OPL->P_CH[slot>>1]; - OPL_SLOT *SLOT = &CH->SLOT[slot & 1]; - int ar = v >> 4; - int dr = v & 0x0f; - - SLOT->AR = ar ? &OPL->AR_TABLE[ar << 2] : RATE_0; - SLOT->evsa = SLOT->AR[SLOT->ksr]; - if(SLOT->evm == ENV_MOD_AR) - SLOT->evs = SLOT->evsa; - - SLOT->DR = dr ? &OPL->DR_TABLE[dr<<2] : RATE_0; - SLOT->evsd = SLOT->DR[SLOT->ksr]; - if(SLOT->evm == ENV_MOD_DR) - SLOT->evs = SLOT->evsd; -} - -/* set sustain level & release rate */ -static inline void set_sl_rr(FM_OPL *OPL, int slot, int v) { - OPL_CH *CH = &OPL->P_CH[slot>>1]; - OPL_SLOT *SLOT = &CH->SLOT[slot & 1]; - int sl = v >> 4; - int rr = v & 0x0f; - - SLOT->SL = SL_TABLE[sl]; - if(SLOT->evm == ENV_MOD_DR) - SLOT->eve = SLOT->SL; - SLOT->RR = &OPL->DR_TABLE[rr<<2]; - SLOT->evsr = SLOT->RR[SLOT->ksr]; - if(SLOT->evm == ENV_MOD_RR) - SLOT->evs = SLOT->evsr; -} - -/* operator output calcrator */ - -#define OP_OUT(slot,env,con) slot->wavetable[((slot->Cnt + con)>>(24-SIN_ENT_SHIFT)) & (SIN_ENT-1)][env] -/* ---------- calcrate one of channel ---------- */ -static inline void OPL_CALC_CH(OPL_CH *CH) { - uint32_t env_out; - OPL_SLOT *SLOT; - - feedback2 = 0; - /* SLOT 1 */ - SLOT = &CH->SLOT[SLOT1]; - env_out=OPL_CALC_SLOT(SLOT); - if(env_out < (uint32_t)(EG_ENT - 1)) { - /* PG */ - if(SLOT->vib) - SLOT->Cnt += (SLOT->Incr * vib) >> VIB_RATE_SHIFT; - else - SLOT->Cnt += SLOT->Incr; - /* connection */ - if(CH->FB) { - int feedback1 = (CH->op1_out[0] + CH->op1_out[1]) >> CH->FB; - CH->op1_out[1] = CH->op1_out[0]; - *CH->connect1 += CH->op1_out[0] = OP_OUT(SLOT, env_out, feedback1); - } else { - *CH->connect1 += OP_OUT(SLOT, env_out, 0); - } - } else { - CH->op1_out[1] = CH->op1_out[0]; - CH->op1_out[0] = 0; - } - /* SLOT 2 */ - SLOT = &CH->SLOT[SLOT2]; - env_out=OPL_CALC_SLOT(SLOT); - if(env_out < (uint32_t)(EG_ENT - 1)) { - /* PG */ - if(SLOT->vib) - SLOT->Cnt += (SLOT->Incr * vib) >> VIB_RATE_SHIFT; - else - SLOT->Cnt += SLOT->Incr; - /* connection */ - outd[0] += OP_OUT(SLOT, env_out, feedback2); - } -} - -/* ---------- calcrate rythm block ---------- */ -#define WHITE_NOISE_db 6.0 -static inline void OPL_CALC_RH(FM_OPL *OPL, OPL_CH *CH) { - uint32_t env_tam, env_sd, env_top, env_hh; - // This code used to do int(OPL->rnd.getRandomBit() * (WHITE_NOISE_db / EG_STEP)), - // but EG_STEP = 96.0/EG_ENT, and WHITE_NOISE_db=6.0. So, that's equivalent to - // int(OPL->rnd.getRandomBit() * EG_ENT/16). We know that EG_ENT is 4096, or 1024, - // or 128, so we can safely avoid any FP ops. - int whitenoise = (rand() & 1) * (EG_ENT>>4); - - int tone8; - - OPL_SLOT *SLOT; - int env_out; - - /* BD : same as FM serial mode and output level is large */ - feedback2 = 0; - /* SLOT 1 */ - SLOT = &CH[6].SLOT[SLOT1]; - env_out = OPL_CALC_SLOT(SLOT); - if(env_out < EG_ENT-1) { - /* PG */ - if(SLOT->vib) - SLOT->Cnt += (SLOT->Incr * vib) >> VIB_RATE_SHIFT; - else - SLOT->Cnt += SLOT->Incr; - /* connection */ - if(CH[6].FB) { - int feedback1 = (CH[6].op1_out[0] + CH[6].op1_out[1]) >> CH[6].FB; - CH[6].op1_out[1] = CH[6].op1_out[0]; - feedback2 = CH[6].op1_out[0] = OP_OUT(SLOT, env_out, feedback1); - } - else { - feedback2 = OP_OUT(SLOT, env_out, 0); - } - } else { - feedback2 = 0; - CH[6].op1_out[1] = CH[6].op1_out[0]; - CH[6].op1_out[0] = 0; - } - /* SLOT 2 */ - SLOT = &CH[6].SLOT[SLOT2]; - env_out = OPL_CALC_SLOT(SLOT); - if(env_out < EG_ENT-1) { - /* PG */ - if(SLOT->vib) - SLOT->Cnt += (SLOT->Incr * vib) >> VIB_RATE_SHIFT; - else - SLOT->Cnt += SLOT->Incr; - /* connection */ - outd[0] += OP_OUT(SLOT, env_out, feedback2) * 2; - } - - // SD (17) = mul14[fnum7] + white noise - // TAM (15) = mul15[fnum8] - // TOP (18) = fnum6(mul18[fnum8]+whitenoise) - // HH (14) = fnum7(mul18[fnum8]+whitenoise) + white noise - env_sd = OPL_CALC_SLOT(SLOT7_2) + whitenoise; - env_tam =OPL_CALC_SLOT(SLOT8_1); - env_top = OPL_CALC_SLOT(SLOT8_2); - env_hh = OPL_CALC_SLOT(SLOT7_1) + whitenoise; - - /* PG */ - if(SLOT7_1->vib) - SLOT7_1->Cnt += (SLOT7_1->Incr * vib) >> (VIB_RATE_SHIFT-1); - else - SLOT7_1->Cnt += 2 * SLOT7_1->Incr; - if(SLOT7_2->vib) - SLOT7_2->Cnt += (CH[7].fc * vib) >> (VIB_RATE_SHIFT-3); - else - SLOT7_2->Cnt += (CH[7].fc * 8); - if(SLOT8_1->vib) - SLOT8_1->Cnt += (SLOT8_1->Incr * vib) >> VIB_RATE_SHIFT; - else - SLOT8_1->Cnt += SLOT8_1->Incr; - if(SLOT8_2->vib) - SLOT8_2->Cnt += ((CH[8].fc * 3) * vib) >> (VIB_RATE_SHIFT-4); - else - SLOT8_2->Cnt += (CH[8].fc * 48); - - tone8 = OP_OUT(SLOT8_2,whitenoise,0 ); - - /* SD */ - if(env_sd < (uint32_t)(EG_ENT - 1)) - outd[0] += OP_OUT(SLOT7_1, env_sd, 0) * 8; - /* TAM */ - if(env_tam < (uint32_t)(EG_ENT - 1)) - outd[0] += OP_OUT(SLOT8_1, env_tam, 0) * 2; - /* TOP-CY */ - if(env_top < (uint32_t)(EG_ENT - 1)) - outd[0] += OP_OUT(SLOT7_2, env_top, tone8) * 2; - /* HH */ - if(env_hh < (uint32_t)(EG_ENT-1)) - outd[0] += OP_OUT(SLOT7_2, env_hh, tone8) * 2; -} - -/* ----------- initialize time tabls ----------- */ -static void init_timetables(FM_OPL *OPL, int ARRATE, int DRRATE) { - int i; - double rate; - - /* make attack rate & decay rate tables */ - for (i = 0; i < 4; i++) - OPL->AR_TABLE[i] = OPL->DR_TABLE[i] = 0; - for (i = 4; i <= 60; i++) { - rate = OPL->freqbase; /* frequency rate */ - if(i < 60) - rate *= 1.0 + (i & 3) * 0.25; /* b0-1 : x1 , x1.25 , x1.5 , x1.75 */ - rate *= 1 << ((i >> 2) - 1); /* b2-5 : shift bit */ - rate *= (double)(EG_ENT << ENV_BITS); - OPL->AR_TABLE[i] = (int)(rate / ARRATE); - OPL->DR_TABLE[i] = (int)(rate / DRRATE); - } - for (i = 60; i < 76; i++) { - OPL->AR_TABLE[i] = EG_AED-1; - OPL->DR_TABLE[i] = OPL->DR_TABLE[60]; - } -} - -/* ---------- generic table initialize ---------- */ -static int OPLOpenTable(void) { - int s,t; - double rate; - int i,j; - double pom; - - /* allocate dynamic tables */ - if((TL_TABLE = (int *)malloc(TL_MAX * 2 * sizeof(int))) == NULL) - return 0; - - if((SIN_TABLE = (int **)malloc(SIN_ENT * 4 * sizeof(int *))) == NULL) { - free(TL_TABLE); - return 0; - } - - if((AMS_TABLE = (int *)malloc(AMS_ENT * 2 * sizeof(int))) == NULL) { - free(TL_TABLE); - free(SIN_TABLE); - return 0; - } - - if((VIB_TABLE = (int *)malloc(VIB_ENT * 2 * sizeof(int))) == NULL) { - free(TL_TABLE); - free(SIN_TABLE); - free(AMS_TABLE); - return 0; - } - /* make total level table */ - for (t = 0; t < EG_ENT - 1 ; t++) { - rate = ((1 << TL_BITS) - 1) / pow(10.0, EG_STEP * t / 20); /* dB -> voltage */ - TL_TABLE[ t] = (int)rate; - TL_TABLE[TL_MAX + t] = -TL_TABLE[t]; - } - /* fill volume off area */ - for (t = EG_ENT - 1; t < TL_MAX; t++) { - TL_TABLE[t] = TL_TABLE[TL_MAX + t] = 0; - } - - /* make sinwave table (total level offet) */ - /* degree 0 = degree 180 = off */ - SIN_TABLE[0] = SIN_TABLE[SIN_ENT /2 ] = &TL_TABLE[EG_ENT - 1]; - for (s = 1;s <= SIN_ENT / 4; s++) { - pom = sin(2 * PI * s / SIN_ENT); /* sin */ - pom = 20 * log10(1 / pom); /* decibel */ - j = (int) (pom / EG_STEP); /* TL_TABLE steps */ - - /* degree 0 - 90 , degree 180 - 90 : plus section */ - SIN_TABLE[ s] = SIN_TABLE[SIN_ENT / 2 - s] = &TL_TABLE[j]; - /* degree 180 - 270 , degree 360 - 270 : minus section */ - SIN_TABLE[SIN_ENT / 2 + s] = SIN_TABLE[SIN_ENT - s] = &TL_TABLE[TL_MAX + j]; - } - for (s = 0;s < SIN_ENT; s++) { - SIN_TABLE[SIN_ENT * 1 + s] = s < (SIN_ENT / 2) ? SIN_TABLE[s] : &TL_TABLE[EG_ENT]; - SIN_TABLE[SIN_ENT * 2 + s] = SIN_TABLE[s % (SIN_ENT / 2)]; - SIN_TABLE[SIN_ENT * 3 + s] = (s / (SIN_ENT / 4)) & 1 ? &TL_TABLE[EG_ENT] : SIN_TABLE[SIN_ENT * 2 + s]; - } - - - ENV_CURVE = (int *)malloc(sizeof(int) * (2*EG_ENT+1)); - - /* envelope counter -> envelope output table */ - for (i=0; i < EG_ENT; i++) { - /* ATTACK curve */ - pom = pow(((double)(EG_ENT - 1 - i) / EG_ENT), 8) * EG_ENT; - /* if( pom >= EG_ENT ) pom = EG_ENT-1; */ - ENV_CURVE[i] = (int)pom; - /* DECAY ,RELEASE curve */ - ENV_CURVE[(EG_DST >> ENV_BITS) + i]= i; - } - /* off */ - ENV_CURVE[EG_OFF >> ENV_BITS]= EG_ENT - 1; - /* make LFO ams table */ - for (i=0; i < AMS_ENT; i++) { - pom = (1.0 + sin(2 * PI * i / AMS_ENT)) / 2; /* sin */ - AMS_TABLE[i] = (int)((1.0 / EG_STEP) * pom); /* 1dB */ - AMS_TABLE[AMS_ENT + i] = (int)((4.8 / EG_STEP) * pom); /* 4.8dB */ - } - /* make LFO vibrate table */ - for (i=0; i < VIB_ENT; i++) { - /* 100cent = 1seminote = 6% ?? */ - pom = (double)VIB_RATE * 0.06 * sin(2 * PI * i / VIB_ENT); /* +-100sect step */ - VIB_TABLE[i] = (int)(VIB_RATE + (pom * 0.07)); /* +- 7cent */ - VIB_TABLE[VIB_ENT + i] = (int)(VIB_RATE + (pom * 0.14)); /* +-14cent */ - } - return 1; -} - -static void OPLCloseTable(void) { - free(TL_TABLE); - free(SIN_TABLE); - free(AMS_TABLE); - free(VIB_TABLE); - free(ENV_CURVE); -} - -/* CSM Key Controll */ -static inline void CSMKeyControll(OPL_CH *CH) { - OPL_SLOT *slot1 = &CH->SLOT[SLOT1]; - OPL_SLOT *slot2 = &CH->SLOT[SLOT2]; - /* all key off */ - OPL_KEYOFF(slot1); - OPL_KEYOFF(slot2); - /* total level latch */ - slot1->TLL = slot1->TL + (CH->ksl_base>>slot1->ksl); - slot1->TLL = slot1->TL + (CH->ksl_base>>slot1->ksl); - /* key on */ - CH->op1_out[0] = CH->op1_out[1] = 0; - OPL_KEYON(slot1); - OPL_KEYON(slot2); -} - -/* ---------- opl initialize ---------- */ -static void OPL_initalize(FM_OPL *OPL) { - int fn; - - /* frequency base */ - OPL->freqbase = (OPL->rate) ? ((double)OPL->clock / OPL->rate) / 72 : 0; - /* Timer base time */ - OPL->TimerBase = 1.0/((double)OPL->clock / 72.0 ); - /* make time tables */ - init_timetables(OPL, OPL_ARRATE, OPL_DRRATE); - /* make fnumber -> increment counter table */ - for( fn=0; fn < 1024; fn++) { - OPL->FN_TABLE[fn] = (uint32_t)(OPL->freqbase * fn * FREQ_RATE * (1<<7) / 2); - } - /* LFO freq.table */ - OPL->amsIncr = (int)(OPL->rate ? (double)AMS_ENT * (1 << AMS_SHIFT) / OPL->rate * 3.7 * ((double)OPL->clock/3600000) : 0); - OPL->vibIncr = (int)(OPL->rate ? (double)VIB_ENT * (1 << VIB_SHIFT) / OPL->rate * 6.4 * ((double)OPL->clock/3600000) : 0); -} - -/* ---------- write a OPL registers ---------- */ -void OPLWriteReg(FM_OPL *OPL, int r, int v) { - OPL_CH *CH; - int slot; - uint32_t block_fnum; - - switch(r & 0xe0) { - case 0x00: /* 00-1f:controll */ - switch(r & 0x1f) { - case 0x01: - /* wave selector enable */ - if(OPL->type&OPL_TYPE_WAVESEL) { - OPL->wavesel = v & 0x20; - if(!OPL->wavesel) { - /* preset compatible mode */ - int c; - for(c=0; cmax_ch; c++) { - OPL->P_CH[c].SLOT[SLOT1].wavetable = &SIN_TABLE[0]; - OPL->P_CH[c].SLOT[SLOT2].wavetable = &SIN_TABLE[0]; - } - } - } - return; - case 0x02: /* Timer 1 */ - OPL->T[0] = (256-v) * 4; - break; - case 0x03: /* Timer 2 */ - OPL->T[1] = (256-v) * 16; - return; - case 0x04: /* IRQ clear / mask and Timer enable */ - if(v & 0x80) { /* IRQ flag clear */ - OPL_STATUS_RESET(OPL, 0x7f); - } else { /* set IRQ mask ,timer enable*/ - uint8_t st1 = v & 1; - uint8_t 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); - /* timer 2 */ - if(OPL->st[1] != st2) { - double interval = st2 ? (double)OPL->T[1] * OPL->TimerBase : 0.0; - OPL->st[1] = st2; - if (OPL->TimerHandler) (OPL->TimerHandler)(OPL->TimerParam + 1, interval); - } - /* timer 1 */ - if(OPL->st[0] != st1) { - double interval = st1 ? (double)OPL->T[0] * OPL->TimerBase : 0.0; - OPL->st[0] = st1; - if (OPL->TimerHandler) (OPL->TimerHandler)(OPL->TimerParam + 0, interval); - } - } - return; - } - break; - case 0x20: /* am,vib,ksr,eg type,mul */ - slot = slot_array[r&0x1f]; - if(slot == -1) - return; - set_mul(OPL,slot,v); - return; - case 0x40: - slot = slot_array[r&0x1f]; - if(slot == -1) - return; - set_ksl_tl(OPL,slot,v); - return; - case 0x60: - slot = slot_array[r&0x1f]; - if(slot == -1) - return; - set_ar_dr(OPL,slot,v); - return; - case 0x80: - slot = slot_array[r&0x1f]; - if(slot == -1) - return; - set_sl_rr(OPL,slot,v); - return; - case 0xa0: - switch(r) { - case 0xbd: - /* amsep,vibdep,r,bd,sd,tom,tc,hh */ - { - uint8_t rkey = OPL->rythm ^ v; - OPL->ams_table = &AMS_TABLE[v & 0x80 ? AMS_ENT : 0]; - OPL->vib_table = &VIB_TABLE[v & 0x40 ? VIB_ENT : 0]; - OPL->rythm = v & 0x3f; - if(OPL->rythm & 0x20) { - /* BD key on/off */ - if(rkey & 0x10) { - if(v & 0x10) { - OPL->P_CH[6].op1_out[0] = OPL->P_CH[6].op1_out[1] = 0; - OPL_KEYON(&OPL->P_CH[6].SLOT[SLOT1]); - OPL_KEYON(&OPL->P_CH[6].SLOT[SLOT2]); - } else { - OPL_KEYOFF(&OPL->P_CH[6].SLOT[SLOT1]); - OPL_KEYOFF(&OPL->P_CH[6].SLOT[SLOT2]); - } - } - /* SD key on/off */ - if(rkey & 0x08) { - if(v & 0x08) - OPL_KEYON(&OPL->P_CH[7].SLOT[SLOT2]); - else - OPL_KEYOFF(&OPL->P_CH[7].SLOT[SLOT2]); - }/* TAM key on/off */ - if(rkey & 0x04) { - if(v & 0x04) - OPL_KEYON(&OPL->P_CH[8].SLOT[SLOT1]); - else - OPL_KEYOFF(&OPL->P_CH[8].SLOT[SLOT1]); - } - /* TOP-CY key on/off */ - if(rkey & 0x02) { - if(v & 0x02) - OPL_KEYON(&OPL->P_CH[8].SLOT[SLOT2]); - else - OPL_KEYOFF(&OPL->P_CH[8].SLOT[SLOT2]); - } - /* HH key on/off */ - if(rkey & 0x01) { - if(v & 0x01) - OPL_KEYON(&OPL->P_CH[7].SLOT[SLOT1]); - else - OPL_KEYOFF(&OPL->P_CH[7].SLOT[SLOT1]); - } - } - } - return; - - default: - break; - } - /* keyon,block,fnum */ - if((r & 0x0f) > 8) - return; - CH = &OPL->P_CH[r & 0x0f]; - if(!(r&0x10)) { /* a0-a8 */ - block_fnum = (CH->block_fnum & 0x1f00) | v; - } else { /* b0-b8 */ - int keyon = (v >> 5) & 1; - block_fnum = ((v & 0x1f) << 8) | (CH->block_fnum & 0xff); - if(CH->keyon != keyon) { - if((CH->keyon=keyon)) { - CH->op1_out[0] = CH->op1_out[1] = 0; - OPL_KEYON(&CH->SLOT[SLOT1]); - OPL_KEYON(&CH->SLOT[SLOT2]); - } else { - OPL_KEYOFF(&CH->SLOT[SLOT1]); - OPL_KEYOFF(&CH->SLOT[SLOT2]); - } - } - } - /* update */ - if(CH->block_fnum != block_fnum) { - int blockRv = 7 - (block_fnum >> 10); - int fnum = block_fnum & 0x3ff; - CH->block_fnum = block_fnum; - CH->ksl_base = KSL_TABLE[block_fnum >> 6]; - CH->fc = OPL->FN_TABLE[fnum] >> blockRv; - CH->kcode = CH->block_fnum >> 9; - if((OPL->mode & 0x40) && CH->block_fnum & 0x100) - CH->kcode |=1; - CALC_FCSLOT(CH,&CH->SLOT[SLOT1]); - CALC_FCSLOT(CH,&CH->SLOT[SLOT2]); - } - return; - case 0xc0: - /* FB,C */ - if((r & 0x0f) > 8) - return; - CH = &OPL->P_CH[r&0x0f]; - { - int feedback = (v >> 1) & 7; - CH->FB = feedback ? (8 + 1) - feedback : 0; - CH->CON = v & 1; - set_algorythm(CH); - } - return; - case 0xe0: /* wave type */ - slot = slot_array[r & 0x1f]; - if(slot == -1) - return; - CH = &OPL->P_CH[slot>>1]; - if(OPL->wavesel) { - CH->SLOT[slot&1].wavetable = &SIN_TABLE[(v & 0x03) * SIN_ENT]; - } - return; - } -} - -/* lock/unlock for common table */ -static int OPL_LockTable(void) { - num_lock++; - if(num_lock>1) - return 0; - /* first time */ - cur_chip = NULL; - /* allocate total level table (128kb space) */ - if(!OPLOpenTable()) { - num_lock--; - return -1; - } - return 0; -} - -static void OPL_UnLockTable(void) { - if(num_lock) - num_lock--; - if(num_lock) - return; - /* last time */ - cur_chip = NULL; - OPLCloseTable(); -} - -/*******************************************************************************/ -/* YM3812 local section */ -/*******************************************************************************/ - -/* ---------- update one of chip ----------- */ -void YM3812UpdateOne(FM_OPL *OPL, int16_t *buffer, int length, int interleave) { - int i; - int data; - int16_t *buf = buffer; - uint32_t amsCnt = OPL->amsCnt; - uint32_t vibCnt = OPL->vibCnt; - uint8_t rythm = OPL->rythm & 0x20; - OPL_CH *CH, *R_CH; - - - if((void *)OPL != cur_chip) { - cur_chip = (void *)OPL; - /* channel pointers */ - S_CH = OPL->P_CH; - E_CH = &S_CH[9]; - /* rythm slot */ - SLOT7_1 = &S_CH[7].SLOT[SLOT1]; - SLOT7_2 = &S_CH[7].SLOT[SLOT2]; - SLOT8_1 = &S_CH[8].SLOT[SLOT1]; - SLOT8_2 = &S_CH[8].SLOT[SLOT2]; - /* LFO state */ - amsIncr = OPL->amsIncr; - vibIncr = OPL->vibIncr; - ams_table = OPL->ams_table; - vib_table = OPL->vib_table; - } - R_CH = rythm ? &S_CH[6] : E_CH; - for(i = 0; i < length; i++) { - /* channel A channel B channel C */ - /* LFO */ - ams = ams_table[(amsCnt += amsIncr) >> AMS_SHIFT]; - vib = vib_table[(vibCnt += vibIncr) >> VIB_SHIFT]; - outd[0] = 0; - /* FM part */ - for(CH=S_CH; CH < R_CH; CH++) - OPL_CALC_CH(CH); - /* Rythn part */ - if(rythm) - OPL_CALC_RH(OPL, S_CH); - /* limit check */ - data = CLIP(outd[0], OPL_MINOUT, OPL_MAXOUT); - /* store to sound buffer */ - buf[i << interleave] = data >> OPL_OUTSB; - } - - OPL->amsCnt = amsCnt; - OPL->vibCnt = vibCnt; -} - -/* ---------- reset a chip ---------- */ -void OPLResetChip(FM_OPL *OPL) { - int c,s; - int i; - - /* reset chip */ - OPL->mode = 0; /* normal mode */ - OPL_STATUS_RESET(OPL, 0x7f); - /* reset with register write */ - OPLWriteReg(OPL, 0x01,0); /* wabesel disable */ - OPLWriteReg(OPL, 0x02,0); /* Timer1 */ - OPLWriteReg(OPL, 0x03,0); /* Timer2 */ - OPLWriteReg(OPL, 0x04,0); /* IRQ mask clear */ - for(i = 0xff; i >= 0x20; i--) - OPLWriteReg(OPL,i,0); - /* reset OPerator parameter */ - for(c = 0; c < OPL->max_ch ;c++ ) { - OPL_CH *CH = &OPL->P_CH[c]; - /* OPL->P_CH[c].PAN = OPN_CENTER; */ - for(s = 0; s < 2; s++ ) { - /* wave table */ - CH->SLOT[s].wavetable = &SIN_TABLE[0]; - /* CH->SLOT[s].evm = ENV_MOD_RR; */ - CH->SLOT[s].evc = EG_OFF; - CH->SLOT[s].eve = EG_OFF + 1; - CH->SLOT[s].evs = 0; - } - } -} - -/* ---------- Create a virtual YM3812 ---------- */ -/* 'rate' is sampling rate and 'bufsiz' is the size of the */ -FM_OPL *OPLCreate(int type, int clock, int rate) { - char *ptr; - FM_OPL *OPL; - int state_size; - int max_ch = 9; /* normaly 9 channels */ - - if( OPL_LockTable() == -1) - return NULL; - /* allocate OPL state space */ - state_size = sizeof(FM_OPL); - state_size += sizeof(OPL_CH) * max_ch; - - /* allocate memory block */ - ptr = (char *)calloc(state_size, 1); - if(ptr == NULL) - return NULL; - - /* clear */ - memset(ptr, 0, state_size); - OPL = (FM_OPL *)ptr; ptr += sizeof(FM_OPL); - OPL->P_CH = (OPL_CH *)ptr; ptr += sizeof(OPL_CH) * max_ch; - - /* set channel state pointer */ - OPL->type = type; - OPL->clock = clock; - OPL->rate = rate; - OPL->max_ch = max_ch; - - /* init grobal tables */ - OPL_initalize(OPL); - - /* reset chip */ - OPLResetChip(OPL); - return OPL; -} - -/* ---------- Destroy one of vietual YM3812 ---------- */ -void OPLDestroy(FM_OPL *OPL) { - OPL_UnLockTable(); - free(OPL); -} - -/* ---------- Option handlers ---------- */ -void OPLSetTimerHandler(FM_OPL *OPL, OPL_TIMERHANDLER TimerHandler,int channelOffset) { - OPL->TimerHandler = TimerHandler; - OPL->TimerParam = channelOffset; -} - -void OPLSetIRQHandler(FM_OPL *OPL, OPL_IRQHANDLER IRQHandler, int param) { - OPL->IRQHandler = IRQHandler; - OPL->IRQParam = param; -} - -void OPLSetUpdateHandler(FM_OPL *OPL, OPL_UPDATEHANDLER UpdateHandler,int param) { - OPL->UpdateHandler = UpdateHandler; - OPL->UpdateParam = param; -} - -/* ---------- YM3812 I/O interface ---------- */ -int OPLWrite(FM_OPL *OPL,int a,int v) { - if(!(a & 1)) { /* address port */ - OPL->address = v & 0xff; - } else { /* data port */ - if(OPL->UpdateHandler) - OPL->UpdateHandler(OPL->UpdateParam,0); - OPLWriteReg(OPL, OPL->address,v); - } - return OPL->status >> 7; -} - -unsigned char OPLRead(FM_OPL *OPL,int a) { - if(!(a & 1)) { /* status port */ - return OPL->status & (OPL->statusmask | 0x80); - } - - return 0; -} - -int OPLTimerOver(FM_OPL *OPL, int c) { - if(c) { /* Timer B */ - OPL_STATUS_SET(OPL, 0x20); - } else { /* Timer A */ - OPL_STATUS_SET(OPL, 0x40); - /* CSM mode key,TL controll */ - if(OPL->mode & 0x80) { /* CSM mode total level latch and auto key on */ - int ch; - if(OPL->UpdateHandler) - OPL->UpdateHandler(OPL->UpdateParam,0); - for(ch = 0; ch < 9; ch++) - CSMKeyControll(&OPL->P_CH[ch]); - } - } - /* reload timer */ - if (OPL->TimerHandler) - (OPL->TimerHandler)(OPL->TimerParam + c, (double)OPL->T[c] * OPL->TimerBase); - return OPL->status >> 7; -} - -FM_OPL *makeAdlibOPL(int rate) { - // We need to emulate one YM3812 chip - int env_bits = FMOPL_ENV_BITS_HQ; - int eg_ent = FMOPL_EG_ENT_HQ; - - OPLBuildTables(env_bits, eg_ent); - return OPLCreate(OPL_TYPE_YM3812, 3579545, rate); -} - diff --git a/opl/fmopl.h b/opl/fmopl.h deleted file mode 100644 index 2bbe8363..00000000 --- a/opl/fmopl.h +++ /dev/null @@ -1,167 +0,0 @@ -/* This file is derived from fmopl.h from ScummVM. - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * LGPL licensed version of MAMEs fmopl (V0.37a modified) by - * Tatsuyuki Satoh. Included from LGPL'ed AdPlug. - */ - - -#ifndef OPL_FMOPL_H -#define OPL_FMOPL_H - -#include "inttypes.h" - -enum { - FMOPL_ENV_BITS_HQ = 16, - FMOPL_ENV_BITS_MQ = 8, - FMOPL_ENV_BITS_LQ = 8, - FMOPL_EG_ENT_HQ = 4096, - FMOPL_EG_ENT_MQ = 1024, - FMOPL_EG_ENT_LQ = 128 -}; - -typedef void (*OPL_TIMERHANDLER)(int channel,double interval_Sec); -typedef void (*OPL_IRQHANDLER)(int param,int irq); -typedef void (*OPL_UPDATEHANDLER)(int param,int min_interval_us); - -#define OPL_TYPE_WAVESEL 0x01 /* waveform select */ - -/* Saving is necessary for member of the 'R' mark for suspend/resume */ -/* ---------- OPL one of slot ---------- */ -typedef struct fm_opl_slot { - int TL; /* total level :TL << 8 */ - int TLL; /* adjusted now TL */ - uint8_t KSR; /* key scale rate :(shift down bit) */ - int *AR; /* attack rate :&AR_TABLE[AR<<2] */ - int *DR; /* decay rate :&DR_TABLE[DR<<2] */ - int SL; /* sustain level :SL_TABLE[SL] */ - int *RR; /* release rate :&DR_TABLE[RR<<2] */ - uint8_t ksl; /* keyscale level :(shift down bits) */ - uint8_t ksr; /* key scale rate :kcode>>KSR */ - unsigned int mul; /* multiple :ML_TABLE[ML] */ - unsigned int Cnt; /* frequency count */ - unsigned int Incr; /* frequency step */ - - /* envelope generator state */ - uint8_t eg_typ;/* envelope type flag */ - uint8_t evm; /* envelope phase */ - int evc; /* envelope counter */ - int eve; /* envelope counter end point */ - int evs; /* envelope counter step */ - int evsa; /* envelope step for AR :AR[ksr] */ - int evsd; /* envelope step for DR :DR[ksr] */ - int evsr; /* envelope step for RR :RR[ksr] */ - - /* LFO */ - uint8_t ams; /* ams flag */ - uint8_t vib; /* vibrate flag */ - /* wave selector */ - int **wavetable; -} OPL_SLOT; - -/* ---------- OPL one of channel ---------- */ -typedef struct fm_opl_channel { - OPL_SLOT SLOT[2]; - uint8_t CON; /* connection type */ - uint8_t FB; /* feed back :(shift down bit)*/ - int *connect1; /* slot1 output pointer */ - int *connect2; /* slot2 output pointer */ - int op1_out[2]; /* slot1 output for selfeedback */ - - /* phase generator state */ - unsigned int block_fnum; /* block+fnum */ - uint8_t kcode; /* key code : KeyScaleCode */ - unsigned int fc; /* Freq. Increment base */ - unsigned int ksl_base; /* KeyScaleLevel Base step */ - uint8_t keyon; /* key on/off flag */ -} OPL_CH; - -/* OPL state */ -typedef struct fm_opl_f { - uint8_t type; /* chip type */ - int clock; /* master clock (Hz) */ - int rate; /* sampling rate (Hz) */ - double freqbase; /* frequency base */ - double TimerBase; /* Timer base time (==sampling time) */ - uint8_t address; /* address register */ - uint8_t status; /* status flag */ - uint8_t statusmask; /* status mask */ - unsigned int mode; /* Reg.08 : CSM , notesel,etc. */ - - /* Timer */ - int T[2]; /* timer counter */ - uint8_t st[2]; /* timer enable */ - - /* FM channel slots */ - OPL_CH *P_CH; /* pointer of CH */ - int max_ch; /* maximum channel */ - - /* Rythm sention */ - uint8_t rythm; /* Rythm mode , key flag */ - - /* time tables */ - int AR_TABLE[76]; /* atttack rate tables */ - int DR_TABLE[76]; /* decay rate tables */ - unsigned int FN_TABLE[1024];/* fnumber -> increment counter */ - - /* LFO */ - int *ams_table; - int *vib_table; - int amsCnt; - int amsIncr; - int vibCnt; - int vibIncr; - - /* wave selector enable flag */ - uint8_t wavesel; - - /* external event callback handler */ - OPL_TIMERHANDLER TimerHandler; /* TIMER handler */ - int TimerParam; /* TIMER parameter */ - OPL_IRQHANDLER IRQHandler; /* IRQ handler */ - int IRQParam; /* IRQ parameter */ - OPL_UPDATEHANDLER UpdateHandler; /* stream update handler */ - int UpdateParam; /* stream update parameter */ -} FM_OPL; - -/* ---------- Generic interface section ---------- */ -#define OPL_TYPE_YM3526 (0) -#define OPL_TYPE_YM3812 (OPL_TYPE_WAVESEL) - -void OPLBuildTables(int ENV_BITS_PARAM, int EG_ENT_PARAM); - -FM_OPL *OPLCreate(int type, int clock, int rate); -void OPLDestroy(FM_OPL *OPL); -void OPLSetTimerHandler(FM_OPL *OPL, OPL_TIMERHANDLER TimerHandler, int channelOffset); -void OPLSetIRQHandler(FM_OPL *OPL, OPL_IRQHANDLER IRQHandler, int param); -void OPLSetUpdateHandler(FM_OPL *OPL, OPL_UPDATEHANDLER UpdateHandler, int param); - -void OPLResetChip(FM_OPL *OPL); -int OPLWrite(FM_OPL *OPL, int a, int v); -unsigned char OPLRead(FM_OPL *OPL, int a); -int OPLTimerOver(FM_OPL *OPL, int c); -void OPLWriteReg(FM_OPL *OPL, int r, int v); -void YM3812UpdateOne(FM_OPL *OPL, int16_t *buffer, int length, int interleave); - -// Factory method -FM_OPL *makeAdlibOPL(int rate); - -#endif - diff --git a/opl/opl_sdl.c b/opl/opl_sdl.c index 1963d5cd..8b7d76e5 100644 --- a/opl/opl_sdl.c +++ b/opl/opl_sdl.c @@ -33,7 +33,7 @@ #include "SDL.h" #include "SDL_mixer.h" -#include "fmopl.h" +#include "dbopl.h" #include "opl.h" #include "opl_internal.h" @@ -42,6 +42,14 @@ #define MAX_SOUND_SLICE_TIME 100 /* ms */ +typedef struct +{ + unsigned int rate; // Number of times the timer is advanced per sec. + unsigned int enabled; // Non-zero if timer is enabled. + unsigned int value; // Last value that was set. + unsigned int expire_time; // Calculated time that timer will expire. +} opl_timer_t; + // When the callback mutex is locked using OPL_Lock, callback functions // are not invoked. @@ -70,11 +78,20 @@ static unsigned int pause_offset; // OPL software emulator structure. -static FM_OPL *opl_emulator = NULL; +static Chip opl_chip; // Temporary mixing buffer used by the mixing callback. -static int16_t *mix_buffer = NULL; +static int32_t *mix_buffer = NULL; + +// Register number that was written. + +static int register_num = 0; + +// Timers; DBOPL does not do timer stuff itself. + +static opl_timer_t timer1 = { 12500, 0, 0, 0 }; +static opl_timer_t timer2 = { 3125, 0, 0, 0 }; // SDL parameters. @@ -153,14 +170,14 @@ static void FillBuffer(int16_t *buffer, unsigned int nsamples) assert(nsamples < mixing_freq); - YM3812UpdateOne(opl_emulator, mix_buffer, nsamples, 0); + Chip__GenerateBlock2(&opl_chip, nsamples, mix_buffer); // Mix into the destination buffer, doubling up into stereo. for (i=0; i timer1.expire_time) { - return OPLRead(opl_emulator, port); + result |= 0x80; // Either have expired + result |= 0x40; // Timer 1 has expired } - else + + if (timer2.enabled && current_time > timer2.expire_time) { - return 0; + result |= 0x80; // Either have expired + result |= 0x20; // Timer 2 has expired + } + + return result; +} + +static void OPLTimer_CalculateEndTime(opl_timer_t *timer) +{ + int tics; + + // If the timer is enabled, calculate the time when the timer + // will expire. + + if (timer->enabled) + { + tics = 0x100 - timer->value; + timer->expire_time = current_time + + (tics * opl_sample_rate) / timer->rate; + } +} + +static void WriteRegister(unsigned int reg_num, unsigned int value) +{ + switch (reg_num) + { + case OPL_REG_TIMER1: + timer1.value = value; + OPLTimer_CalculateEndTime(&timer1); + break; + + case OPL_REG_TIMER2: + timer2.value = value; + OPLTimer_CalculateEndTime(&timer2); + break; + + case OPL_REG_TIMER_CTRL: + if (value & 0x80) + { + timer1.enabled = 0; + timer2.enabled = 0; + } + else + { + if ((value & 0x40) == 0) + { + timer1.enabled = (value & 0x01) != 0; + OPLTimer_CalculateEndTime(&timer1); + } + + if ((value & 0x20) == 0) + { + timer1.enabled = (value & 0x02) != 0; + OPLTimer_CalculateEndTime(&timer2); + } + } + + break; + + default: + Chip__WriteReg(&opl_chip, reg_num, value); + break; } } static void OPL_SDL_PortWrite(opl_port_t port, unsigned int value) { - if (opl_emulator != NULL) + if (port == OPL_REGISTER_PORT) + { + register_num = value; + } + else if (port == OPL_DATA_PORT) { - OPLWrite(opl_emulator, port, value); + WriteRegister(register_num, value); } } -- cgit v1.2.3 From 60226b1183f5537fdde881e34e863e21c7e24d7e Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Sun, 15 Aug 2010 15:23:28 +0000 Subject: Fix volume multiply; DBOPL now generating output. Subversion-branch: /trunk/chocolate-doom Subversion-revision: 1957 --- opl/opl_sdl.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'opl') diff --git a/opl/opl_sdl.c b/opl/opl_sdl.c index 8b7d76e5..f6a3b229 100644 --- a/opl/opl_sdl.c +++ b/opl/opl_sdl.c @@ -176,8 +176,8 @@ static void FillBuffer(int16_t *buffer, unsigned int nsamples) for (i=0; iopl3Active || (val == 0x05) ) return 0x100 | val; - else + else return val; } return 0; -- cgit v1.2.3