-- cgit v1.2.3 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 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 --- Makefile.am | 2 +- configure.in | 1 + opl/Makefile.am | 8 +++++ opl/fmopl.c | 106 +++++++++++++++++++------------------------------------- opl/fmopl.h | 70 +++++++++++++++++-------------------- 5 files changed, 77 insertions(+), 110 deletions(-) create mode 100644 opl/Makefile.am diff --git a/Makefile.am b/Makefile.am index 40de5828..4f678149 100644 --- a/Makefile.am +++ b/Makefile.am @@ -44,7 +44,7 @@ EXTRA_DIST= \ MAINTAINERCLEANFILES = $(AUX_DIST_GEN) docdir=$(prefix)/share/doc/@PACKAGE@ -SUBDIRS=textscreen pcsound src man setup +SUBDIRS=textscreen opl pcsound src man setup if HAVE_PYTHON diff --git a/configure.in b/configure.in index 15ef200a..24f0bc97 100644 --- a/configure.in +++ b/configure.in @@ -91,6 +91,7 @@ textscreen/examples/Makefile setup/Makefile man/Makefile src/Makefile +opl/Makefile pcsound/Makefile src/resource.rc src/doom-screensaver.desktop 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 997d2ee86ef77422ce7ddd08859bd5a4aef66145 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Wed, 4 Mar 2009 22:07:27 +0000 Subject: Add initial stub for OPL backend. Subversion-branch: /branches/opl-branch Subversion-revision: 1447 --- src/Makefile.am | 2 + src/i_oplmusic.c | 214 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/i_sdlmusic.c | 2 - src/s_sound.c | 2 + 4 files changed, 218 insertions(+), 2 deletions(-) create mode 100644 src/i_oplmusic.c diff --git a/src/Makefile.am b/src/Makefile.am index 63ddc98f..66909dd1 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -155,6 +155,7 @@ FEATURE_SOUND_SOURCE_FILES = \ i_pcsound.c \ i_sdlsound.c \ i_sdlmusic.c \ +i_oplmusic.c \ mus2mid.c mus2mid.h SOURCE_FILES = $(MAIN_SOURCE_FILES) \ @@ -172,6 +173,7 @@ endif chocolate_doom_LDADD = \ ../textscreen/libtextscreen.a \ ../pcsound/libpcsound.a \ + ../opl/libopl.a \ @LDFLAGS@ \ @SDLMIXER_LIBS@ \ @SDLNET_LIBS@ diff --git a/src/i_oplmusic.c b/src/i_oplmusic.c new file mode 100644 index 00000000..ee909193 --- /dev/null +++ b/src/i_oplmusic.c @@ -0,0 +1,214 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005 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: +// System interface for music. +// +//----------------------------------------------------------------------------- + + +#include +#include + +#include "doomdef.h" +#include "memio.h" +#include "mus2mid.h" + +#include "deh_main.h" +#include "m_misc.h" +#include "s_sound.h" +#include "w_wad.h" +#include "z_zone.h" + +#define MAXMIDLENGTH (96 * 1024) + +static boolean music_initialised = false; + +//static boolean musicpaused = false; +static int current_music_volume; + +// Shutdown music + +static void I_OPL_ShutdownMusic(void) +{ +} + +// Initialise music subsystem + +static boolean I_OPL_InitMusic(void) +{ + music_initialised = true; + + return true; +} + +// Set music volume (0 - 127) + +static void I_OPL_SetMusicVolume(int volume) +{ + // Internal state variable. + current_music_volume = volume; +} + +// Start playing a mid + +static void I_OPL_PlaySong(void *handle, int looping) +{ + if (!music_initialised) + { + return; + } +} + +static void I_OPL_PauseSong(void) +{ + if (!music_initialised) + { + return; + } +} + +static void I_OPL_ResumeSong(void) +{ + if (!music_initialised) + { + return; + } +} + +static void I_OPL_StopSong(void) +{ + if (!music_initialised) + { + return; + } +} + +static void I_OPL_UnRegisterSong(void *handle) +{ + if (!music_initialised) + { + return; + } +} + +// Determine whether memory block is a .mid file + +static boolean IsMid(byte *mem, int len) +{ + return len > 4 && !memcmp(mem, "MThd", 4); +} + +static boolean ConvertMus(byte *musdata, int len, char *filename) +{ + MEMFILE *instream; + MEMFILE *outstream; + void *outbuf; + size_t outbuf_len; + int result; + + instream = mem_fopen_read(musdata, len); + outstream = mem_fopen_write(); + + result = mus2mid(instream, outstream); + + if (result == 0) + { + mem_get_buf(outstream, &outbuf, &outbuf_len); + + M_WriteFile(filename, outbuf, outbuf_len); + } + + mem_fclose(instream); + mem_fclose(outstream); + + return result; +} + +static void *I_OPL_RegisterSong(void *data, int len) +{ + char *filename; + + if (!music_initialised) + { + return NULL; + } + + // MUS files begin with "MUS" + // Reject anything which doesnt have this signature + + filename = M_TempFile("doom.mid"); + + if (IsMid(data, len) && len < MAXMIDLENGTH) + { + M_WriteFile(filename, data, len); + } + else + { + // Assume a MUS file and try to convert + + ConvertMus(data, len, filename); + } + + // .... + + // remove file now + + remove(filename); + + Z_Free(filename); + + return NULL; +} + +// Is the song playing? +static boolean I_OPL_MusicIsPlaying(void) +{ + if (!music_initialised) + { + return false; + } + + return false; +} + +static snddevice_t music_opl_devices[] = +{ + SNDDEVICE_ADLIB, + SNDDEVICE_SB, +}; + +music_module_t music_opl_module = +{ + music_opl_devices, + arrlen(music_opl_devices), + I_OPL_InitMusic, + I_OPL_ShutdownMusic, + I_OPL_SetMusicVolume, + I_OPL_PauseSong, + I_OPL_ResumeSong, + I_OPL_RegisterSong, + I_OPL_UnRegisterSong, + I_OPL_PlaySong, + I_OPL_StopSong, + I_OPL_MusicIsPlaying, +}; + diff --git a/src/i_sdlmusic.c b/src/i_sdlmusic.c index 313e2a58..c1ab340b 100644 --- a/src/i_sdlmusic.c +++ b/src/i_sdlmusic.c @@ -324,8 +324,6 @@ static boolean I_SDL_MusicIsPlaying(void) static snddevice_t music_sdl_devices[] = { - SNDDEVICE_ADLIB, - SNDDEVICE_SB, SNDDEVICE_PAS, SNDDEVICE_GUS, SNDDEVICE_WAVEBLASTER, diff --git a/src/s_sound.c b/src/s_sound.c index 70fa75f3..f038e9cd 100644 --- a/src/s_sound.c +++ b/src/s_sound.c @@ -136,6 +136,7 @@ int snd_sfxdevice = SNDDEVICE_SB; extern sound_module_t sound_sdl_module; extern sound_module_t sound_pcsound_module; extern music_module_t music_sdl_module; +extern music_module_t music_opl_module; // Compiled-in sound modules: @@ -154,6 +155,7 @@ static music_module_t *music_modules[] = { #ifdef FEATURE_SOUND &music_sdl_module, + &music_opl_module, #endif NULL, }; -- 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(-) 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 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 3efee3d5e2b662df97aabc4c8fd275b60b6f08f4 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Tue, 10 Mar 2009 00:03:54 +0000 Subject: Add initial GENMIDI lump loading, OPL detection. Subversion-branch: /branches/opl-branch Subversion-revision: 1456 --- src/Makefile.am | 2 +- src/i_oplmusic.c | 171 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- src/m_config.c | 2 +- 3 files changed, 172 insertions(+), 3 deletions(-) diff --git a/src/Makefile.am b/src/Makefile.am index 66909dd1..406d0af8 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -3,7 +3,7 @@ gamesdir = $(prefix)/games games_PROGRAMS = chocolate-doom chocolate-server -AM_CFLAGS = -I../textscreen -I../pcsound @SDLMIXER_CFLAGS@ @SDLNET_CFLAGS@ +AM_CFLAGS = -I../opl -I../textscreen -I../pcsound @SDLMIXER_CFLAGS@ @SDLNET_CFLAGS@ DEDSERV_FILES=\ d_dedicated.c \ diff --git a/src/i_oplmusic.c b/src/i_oplmusic.c index ee909193..98170284 100644 --- a/src/i_oplmusic.c +++ b/src/i_oplmusic.c @@ -27,6 +27,7 @@ #include #include +#include #include "doomdef.h" #include "memio.h" @@ -38,23 +39,191 @@ #include "w_wad.h" #include "z_zone.h" +#include "opl.h" + #define MAXMIDLENGTH (96 * 1024) +#define GENMIDI_NUM_INSTRS 128 + +#define GENMIDI_HEADER "#OPL_II#" +#define GENMIDI_FLAG_FIXED 0x0000 /* fixed pitch */ +#define GENMIDI_FLAG_2VOICE 0x0002 /* double voice (OPL3) */ + +typedef struct +{ + byte tremolo; + byte attack; + byte sustain; + byte waveform; + byte scale; + byte level; +} PACKEDATTR genmidi_op_t; + +typedef struct +{ + genmidi_op_t modulator; + byte feedback; + genmidi_op_t carrier; + byte unused; + byte base_note_offset; +} PACKEDATTR genmidi_voice_t; + +typedef struct +{ + unsigned short flags; + byte fine_tuning; + byte fixed_note; + + genmidi_voice_t opl2_voice; + genmidi_voice_t opl3_voice; +} PACKEDATTR genmidi_instr_t; static boolean music_initialised = false; //static boolean musicpaused = false; static int current_music_volume; +static genmidi_instr_t *main_instrs; +static genmidi_instr_t *percussion_instrs; + +// Configuration file variable, containing the port number for the +// adlib chip. + +int snd_mport = 0x388; + +static unsigned int GetStatus(void) +{ + return OPL_ReadPort(OPL_REGISTER_PORT); +} + +// Write an OPL register value + +static void 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) + { + GetStatus(); + } + + OPL_WritePort(OPL_DATA_PORT, value); + + // Read the register port 25 times after writing the value to + // cause the appropriate delay + + for (i=0; i<25; ++i) + { + GetStatus(); + } +} + +// Detect the presence of an OPL chip + +static boolean DetectOPL(void) +{ + int result1, result2; + + // Reset both timers: + WriteRegister(OPL_REG_TIMER_CTRL, 0x60); + + // Enable interrupts: + WriteRegister(OPL_REG_TIMER_CTRL, 0x80); + + // Read status + result1 = GetStatus(); + + // Set timer: + WriteRegister(OPL_REG_TIMER1, 0xff); + + // Start timer 1: + WriteRegister(OPL_REG_TIMER_CTRL, 0x21); + + // Wait for 80 microseconds + + // Read status + result2 = GetStatus(); + + // Reset both timers: + WriteRegister(OPL_REG_TIMER_CTRL, 0x60); + + // Enable interrupts: + WriteRegister(OPL_REG_TIMER_CTRL, 0x80); + + return (result1 & 0xe0) == 0x00 + && (result2 & 0xe0) == 0xc0; +} + + +// Load instrument table from GENMIDI lump: + +static boolean LoadInstrumentTable(void) +{ + byte *lump; + + lump = W_CacheLumpName("GENMIDI", PU_STATIC); + + // Check header + + if (strncmp((char *) lump, GENMIDI_HEADER, strlen(GENMIDI_HEADER)) != 0) + { + W_ReleaseLumpName("GENMIDI"); + + return false; + } + + main_instrs = (genmidi_instr_t *) (lump + strlen(GENMIDI_HEADER)); + percussion_instrs = main_instrs + GENMIDI_NUM_INSTRS; + + return true; +} + // Shutdown music static void I_OPL_ShutdownMusic(void) { + if (music_initialised) + { + OPL_Shutdown(); + + // Release GENMIDI lump + + W_ReleaseLumpName("GENMIDI"); + + music_initialised = false; + } } // Initialise music subsystem static boolean I_OPL_InitMusic(void) -{ +{ + if (!OPL_Init(snd_mport)) + { + return false; + } + + // Doom does the detection sequence twice, for some reason: + + if (!DetectOPL() || !DetectOPL()) + { + printf("Dude. The Adlib isn't responding.\n"); + OPL_Shutdown(); + return false; + } + + // Load instruments from GENMIDI lump: + + if (!LoadInstrumentTable()) + { + OPL_Shutdown(); + return false; + } + music_initialised = true; return true; diff --git a/src/m_config.c b/src/m_config.c index 4066cb3c..c54b3774 100644 --- a/src/m_config.c +++ b/src/m_config.c @@ -124,6 +124,7 @@ extern int vanilla_demo_limit; extern int snd_musicdevice; extern int snd_sfxdevice; extern int snd_samplerate; +extern int snd_mport; // controls whether to use libsamplerate for sample rate conversions @@ -136,7 +137,6 @@ extern int use_libsamplerate; static int snd_sbport = 0; static int snd_sbirq = 0; static int snd_sbdma = 0; -static int snd_mport = 0; typedef enum { -- 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 + src/i_oplmusic.c | 178 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 181 insertions(+) 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. diff --git a/src/i_oplmusic.c b/src/i_oplmusic.c index 98170284..5b3ae041 100644 --- a/src/i_oplmusic.c +++ b/src/i_oplmusic.c @@ -77,14 +77,45 @@ typedef struct genmidi_voice_t opl3_voice; } PACKEDATTR genmidi_instr_t; +typedef struct opl_voice_s opl_voice_t; + +struct opl_voice_s +{ + // Index of this voice: + int index; + + // The operators used by this voice: + int op1, op2; + + // Currently-loaded instrument data + genmidi_instr_t *current_instr; + + // Next in freelist + opl_voice_t *next; +}; + +// Operators used by the different voices. + +static const int voice_operators[2][OPL_NUM_VOICES] = { + { 0x00, 0x01, 0x02, 0x08, 0x09, 0x0a, 0x10, 0x11, 0x12 }, + { 0x03, 0x04, 0x05, 0x0b, 0x0c, 0x0d, 0x13, 0x14, 0x15 } +}; + static boolean music_initialised = false; //static boolean musicpaused = false; static int current_music_volume; +// GENMIDI lump instrument data: + static genmidi_instr_t *main_instrs; static genmidi_instr_t *percussion_instrs; +// Voices: + +static opl_voice_t voices[OPL_NUM_VOICES]; +static opl_voice_t *voice_free_list; + // Configuration file variable, containing the port number for the // adlib chip. @@ -158,6 +189,47 @@ static boolean DetectOPL(void) && (result2 & 0xe0) == 0xc0; } +// Initialise registers on startup + +static void InitRegisters(void) +{ + int r; + + // Initialise level registers + + for (r=OPL_REGS_LEVEL; r < OPL_REGS_LEVEL + OPL_NUM_OPERATORS; ++r) + { + WriteRegister(r, 0x3f); + } + + // Initialise other registers + // These two loops write to registers that actually don't exist, + // but this is what Doom does ... + + for (r=OPL_REGS_ATTACK; r < OPL_REGS_WAVEFORM + OPL_NUM_OPERATORS; ++r) + { + WriteRegister(r, 0x00); + } + + // More registers ... + + for (r=0; r < OPL_REGS_LEVEL; ++r) + { + WriteRegister(r, 0x00); + } + + // Re-initialise the low registers: + + // Reset both timers and enable interrupts: + WriteRegister(OPL_REG_TIMER_CTRL, 0x60); + WriteRegister(OPL_REG_TIMER_CTRL, 0x80); + + // "Allow FM chips to control the waveform of each operator": + WriteRegister(OPL_REG_WAVEFORM_ENABLE, 0x20); + + // Keyboard split point on (?) + WriteRegister(OPL_REG_FM_MODE, 0x40); +} // Load instrument table from GENMIDI lump: @@ -182,6 +254,109 @@ static boolean LoadInstrumentTable(void) return true; } +// Get the next available voice from the freelist. + +static opl_voice_t *GetFreeVoice(void) +{ + opl_voice_t *result; + + // None available? + + if (voice_free_list == NULL) + { + return NULL; + } + + result = voice_free_list; + voice_free_list = voice_free_list->next; + + return result; +} + +// Release a voice back to the freelist. + +static void ReleaseVoice(opl_voice_t *voice) +{ + opl_voice_t **rover; + + // Search to the end of the freelist (This is how Doom behaves!) + + rover = &voice_free_list; + + while (*rover != NULL) + { + rover = &(*rover)->next; + } + + *rover = voice; +} + +// Load data to the specified operator + +static void LoadOperatorData(int operator, genmidi_op_t *data, + boolean max_level) +{ + int level; + + // The scale and level fields must be combined for the level register. + // For the carrier wave we always set the maximum level. + + level = (data->scale & 0xc0) | (data->level & 0x3f); + + if (max_level) + { + level |= 0x3f; + } + + WriteRegister(OPL_REGS_LEVEL + operator, level); + WriteRegister(OPL_REGS_TREMOLO + operator, data->tremolo); + WriteRegister(OPL_REGS_ATTACK + operator, data->attack); + WriteRegister(OPL_REGS_SUSTAIN + operator, data->sustain); + WriteRegister(OPL_REGS_WAVEFORM + operator, data->waveform); +} + +// Set the instrument for a particular voice. + +static void SetVoiceInstrument(opl_voice_t *voice, genmidi_voice_t *data) +{ + // Doom loads the second operator first, then the first. + + LoadOperatorData(voice->op2, &data->carrier, true); + LoadOperatorData(voice->op1, &data->modulator, false); + + // Set feedback register that control the connection between the + // two operators. Turn on bits in the upper nybble; I think this + // is for OPL3, where it turns on channel A/B. + + WriteRegister(OPL_REGS_FEEDBACK + voice->index, + data->feedback | 0x30); +} + +// Initialise the voice table and freelist + +static void InitVoices(void) +{ + int i; + + // Start with an empty free list. + + voice_free_list = NULL; + + // Initialise each voice. + + for (i=0; i + #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 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(-) 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 b796c64d34847434d053aeacca5b6ec10ace2450 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Tue, 10 Mar 2009 22:20:32 +0000 Subject: Add delay to allow OPL detection to work. Subversion-branch: /branches/opl-branch Subversion-revision: 1461 --- src/i_oplmusic.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/i_oplmusic.c b/src/i_oplmusic.c index 5b3ae041..7ead7f02 100644 --- a/src/i_oplmusic.c +++ b/src/i_oplmusic.c @@ -175,6 +175,7 @@ static boolean DetectOPL(void) WriteRegister(OPL_REG_TIMER_CTRL, 0x21); // Wait for 80 microseconds + SDL_Delay(1); // Read status result2 = GetStatus(); -- 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(-) 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 99420c03a1cb10ea69207f2ed89f5fd1ec892bee Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Wed, 11 Mar 2009 21:28:28 +0000 Subject: More accurate initialisation; set registers to match what Doom does. Subversion-branch: /branches/opl-branch Subversion-revision: 1463 --- src/i_oplmusic.c | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/src/i_oplmusic.c b/src/i_oplmusic.c index 7ead7f02..baf17bd6 100644 --- a/src/i_oplmusic.c +++ b/src/i_oplmusic.c @@ -147,7 +147,7 @@ static void WriteRegister(int reg, int value) // Read the register port 25 times after writing the value to // cause the appropriate delay - for (i=0; i<25; ++i) + for (i=0; i<24; ++i) { GetStatus(); } @@ -158,6 +158,7 @@ static void WriteRegister(int reg, int value) static boolean DetectOPL(void) { int result1, result2; + int i; // Reset both timers: WriteRegister(OPL_REG_TIMER_CTRL, 0x60); @@ -175,7 +176,12 @@ static boolean DetectOPL(void) WriteRegister(OPL_REG_TIMER_CTRL, 0x21); // Wait for 80 microseconds - SDL_Delay(1); + // This is how Doom does it: + + for (i=0; i<200; ++i) + { + GetStatus(); + } // Read status result2 = GetStatus(); @@ -198,7 +204,7 @@ static void InitRegisters(void) // Initialise level registers - for (r=OPL_REGS_LEVEL; r < OPL_REGS_LEVEL + OPL_NUM_OPERATORS; ++r) + for (r=OPL_REGS_LEVEL; r <= OPL_REGS_LEVEL + OPL_NUM_OPERATORS; ++r) { WriteRegister(r, 0x3f); } @@ -206,15 +212,16 @@ static void InitRegisters(void) // Initialise 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) + for (r=OPL_REGS_ATTACK; r <= OPL_REGS_WAVEFORM + OPL_NUM_OPERATORS; ++r) { WriteRegister(r, 0x00); } // More registers ... - for (r=0; r < OPL_REGS_LEVEL; ++r) + for (r=1; r < OPL_REGS_LEVEL; ++r) { WriteRegister(r, 0x00); } @@ -364,6 +371,9 @@ static void I_OPL_ShutdownMusic(void) { if (music_initialised) { +#ifdef TEST + InitRegisters(); +#endif OPL_Shutdown(); // Release GENMIDI lump @@ -403,6 +413,22 @@ static boolean I_OPL_InitMusic(void) InitRegisters(); InitVoices(); +#ifdef TEST + { + opl_voice_t *voice; + + voice = GetFreeVoice(); + SetVoiceInstrument(voice, &main_instrs[34].opl2_voice); + + // Set level: + WriteRegister(OPL_REGS_LEVEL + voice->op2, 0x94); + + // Note on: + WriteRegister(OPL_REGS_FREQ_1 + voice->index, 0x65); + WriteRegister(OPL_REGS_FREQ_2 + voice->index, 0x2b); + } +#endif + music_initialised = true; return true; -- cgit v1.2.3 From 90fd85b32dd85884fb9a9cf0a7063c4509147d40 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Thu, 12 Mar 2009 18:50:15 +0000 Subject: Read from register port when doing register writes during startup phase; afterwards, read from the data port. Subversion-branch: /branches/opl-branch Subversion-revision: 1464 --- src/i_oplmusic.c | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/i_oplmusic.c b/src/i_oplmusic.c index baf17bd6..0543be2e 100644 --- a/src/i_oplmusic.c +++ b/src/i_oplmusic.c @@ -116,6 +116,13 @@ static genmidi_instr_t *percussion_instrs; static opl_voice_t voices[OPL_NUM_VOICES]; static opl_voice_t *voice_free_list; +// In the initialisation stage, register writes are spaced by reading +// from the register port (0). After initialisation, spacing is +// peformed by reading from the data port instead. I have no idea +// why. + +static boolean init_stage_reg_writes = false; + // Configuration file variable, containing the port number for the // adlib chip. @@ -139,7 +146,18 @@ static void WriteRegister(int reg, int value) for (i=0; i<6; ++i) { - GetStatus(); + // 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); @@ -393,6 +411,8 @@ static boolean I_OPL_InitMusic(void) return false; } + init_stage_reg_writes = true; + // Doom does the detection sequence twice, for some reason: if (!DetectOPL() || !DetectOPL()) @@ -413,6 +433,11 @@ static boolean I_OPL_InitMusic(void) InitRegisters(); InitVoices(); + // Now that initialisation has finished, switch the + // register writing mode: + + init_stage_reg_writes = false; + #ifdef TEST { opl_voice_t *voice; -- cgit v1.2.3 From 543b9bdc85dfeb84ed855084595407d6bf49d8f6 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Thu, 12 Mar 2009 18:52:12 +0000 Subject: Make base_note_offset a 16-bit integer rather than an 8-bit integer. Subversion-branch: /branches/opl-branch Subversion-revision: 1465 --- src/i_oplmusic.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i_oplmusic.c b/src/i_oplmusic.c index 0543be2e..f50c3322 100644 --- a/src/i_oplmusic.c +++ b/src/i_oplmusic.c @@ -64,7 +64,7 @@ typedef struct byte feedback; genmidi_op_t carrier; byte unused; - byte base_note_offset; + short base_note_offset; } PACKEDATTR genmidi_voice_t; typedef struct -- cgit v1.2.3 From b96cdbe82fa6cb51ac91072ad86e084d739fd9be Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Sat, 28 Mar 2009 00:24:50 +0000 Subject: Initial MIDI file parsing code. Subversion-branch: /branches/opl-branch Subversion-revision: 1489 --- src/Makefile.am | 1 + src/midifile.c | 287 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/midifile.h | 135 ++++++++++++++++++++++++++ 3 files changed, 423 insertions(+) create mode 100644 src/midifile.c create mode 100644 src/midifile.h diff --git a/src/Makefile.am b/src/Makefile.am index 406d0af8..92095b5c 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -156,6 +156,7 @@ i_pcsound.c \ i_sdlsound.c \ i_sdlmusic.c \ i_oplmusic.c \ +midifile.c midifile.h \ mus2mid.c mus2mid.h SOURCE_FILES = $(MAIN_SOURCE_FILES) \ diff --git a/src/midifile.c b/src/midifile.c new file mode 100644 index 00000000..e74cf17f --- /dev/null +++ b/src/midifile.c @@ -0,0 +1,287 @@ +// 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: +// Reading of MIDI files. +// +//----------------------------------------------------------------------------- + +#include +#include +#include + +#include "doomtype.h" +#include "i_swap.h" +#include "midifile.h" + +#define HEADER_CHUNK_ID "MThd" +#define TRACK_CHUNK_ID "MTrk" + +typedef struct +{ + byte chunk_id[4]; + unsigned int chunk_size; +} chunk_header_t; + +typedef struct +{ + chunk_header_t chunk_header; + unsigned short format_type; + unsigned short num_tracks; + unsigned int time_division; +} midi_header_t; + +struct midi_file_s +{ + FILE *stream; + midi_header_t header; + unsigned int data_len; +}; + +static boolean CheckChunkHeader(chunk_header_t *chunk, + char *expected_id) +{ + boolean result; + + result = (strcmp((char *) chunk->chunk_id, expected_id) == 0); + + if (!result) + { + fprintf(stderr, "CheckChunkHeader: Expected '%s' chunk header!\n", + expected_id); + } + + return result; +} + +// Read and check the header chunk. + +static boolean ReadHeaderChunk(midi_file_t *file) +{ + size_t bytes_read; + + bytes_read = fread(&file->header, sizeof(midi_header_t), 1, file->stream); + + if (bytes_read < sizeof(midi_header_t)) + { + return false; + } + + if (!CheckChunkHeader(&file->header.chunk_header, HEADER_CHUNK_ID) + || LONG(file->header.chunk_header.chunk_size) != 6) + { + fprintf(stderr, "ReadHeaderChunk: Invalid MIDI chunk header!\n"); + return false; + } + + if (SHORT(file->header.format_type) != 0 + || SHORT(file->header.num_tracks) != 1) + { + fprintf(stderr, "ReadHeaderChunk: Only single track, " + "type 0 MIDI files supported!\n"); + return false; + } + + return true; +} + +// Read and check the track chunk header + +static boolean ReadTrackChunk(midi_file_t *file) +{ + size_t bytes_read; + chunk_header_t chunk_header; + + bytes_read = fread(&chunk_header, sizeof(chunk_header_t), 1, file->stream); + + if (bytes_read < sizeof(chunk_header)) + { + return false; + } + + if (!CheckChunkHeader(&chunk_header, TRACK_CHUNK_ID)) + { + return false; + } + + file->data_len = LONG(chunk_header.chunk_size); + + return true; +} + +midi_file_t *MIDI_OpenFile(char *filename) +{ + midi_file_t *file; + + file = malloc(sizeof(midi_file_t)); + + if (file == NULL) + { + return NULL; + } + + // Open file + + file->stream = fopen(filename, "rb"); + + if (file->stream == NULL) + { + fprintf(stderr, "Failed to open '%s'\n", filename); + free(file); + return NULL; + } + + // Read MIDI file header + + if (!ReadHeaderChunk(file)) + { + fclose(file->stream); + free(file); + return NULL; + } + + // Read track header + + if (!ReadTrackChunk(file)) + { + fclose(file->stream); + free(file); + return NULL; + } + + return file; +} + +void MIDI_CloseFile(midi_file_t *file) +{ + fclose(file->stream); + free(file); +} + +// Read a MIDI channel event. +// two_param indicates that the event type takes two parameters +// (three byte) otherwise it is single parameter (two byte) + +static boolean ReadChannelEvent(midi_file_t *file, midi_event_t *event, + int event_type, boolean two_param) +{ + int c; + + // Set basics: + + event->event_type = event_type >> 4; + event->data.channel.channel = event_type & 0xf; + + // Read parameters: + + c = fgetc(file->stream); + + if (c == EOF) + { + return false; + } + + event->data.channel.param1 = c; + + // Second parameter: + + if (two_param) + { + c = fgetc(file->stream); + + if (c == EOF) + { + return false; + } + + event->data.channel.param2 = c; + } + + return true; +} + +// Read sysex event: + +static boolean ReadSysExEvent(midi_file_t *file, midi_event_t *event, + int event_type) +{ + // TODO + return false; +} + +// Read meta event: + +static boolean ReadMetaEvent(midi_file_t *file, midi_event_t *event) +{ + // TODO + return false; +} + +boolean MIDI_ReadEvent(midi_file_t *file, midi_event_t *event) +{ + int event_type; + + event_type = fgetc(file->stream); + + if (event_type == EOF) + { + return false; + } + + // Check event type: + + switch (event_type >> 4) + { + // Two parameter channel events: + + case MIDI_EVENT_NOTE_OFF: + case MIDI_EVENT_NOTE_ON: + case MIDI_EVENT_AFTERTOUCH: + case MIDI_EVENT_CONTROLLER: + case MIDI_EVENT_PITCH_BEND: + return ReadChannelEvent(file, event, event_type, true); + + // Single parameter channel events: + + case MIDI_EVENT_PROGRAM_CHANGE: + case MIDI_EVENT_CHAN_AFTERTOUCH: + return ReadChannelEvent(file, event, event_type, false); + + // Other event types: + + case 0xf: + if (event_type == MIDI_EVENT_SYSEX + || event_type == MIDI_EVENT_SYSEX_SPLIT) + { + return ReadSysExEvent(file, event, event_type); + } + else if (event_type == MIDI_EVENT_META) + { + return ReadMetaEvent(file, event); + } + + // Fall-through deliberate - + + default: + fprintf(stderr, "Unknown MIDI event type: 0x%x\n", event_type); + return false; + } +} + diff --git a/src/midifile.h b/src/midifile.h new file mode 100644 index 00000000..d7df8b86 --- /dev/null +++ b/src/midifile.h @@ -0,0 +1,135 @@ +// 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: +// MIDI file parsing. +// +//----------------------------------------------------------------------------- + +#ifndef MIDIFILE_H +#define MIDIFILE_H + +typedef struct midi_file_s midi_file_t; + +typedef enum +{ + MIDI_EVENT_NOTE_OFF = 0x8, + MIDI_EVENT_NOTE_ON = 0x9, + MIDI_EVENT_AFTERTOUCH = 0xa, + MIDI_EVENT_CONTROLLER = 0xb, + MIDI_EVENT_PROGRAM_CHANGE = 0xc, + MIDI_EVENT_CHAN_AFTERTOUCH = 0xd, + MIDI_EVENT_PITCH_BEND = 0xe, + + MIDI_EVENT_SYSEX = 0xf0, + MIDI_EVENT_SYSEX_SPLIT = 0xf7, + MIDI_EVENT_META = 0xff, +} midi_event_type_t; + +typedef enum +{ + MIDI_CONTROLLER_BANK_SELECT = 0x0, + MIDI_CONTROLLER_MODULATION = 0x1, + MIDI_CONTROLLER_BREATH_CONTROL = 0x2, + MIDI_CONTROLLER_FOOT_CONTROL = 0x3, + MIDI_CONTROLLER_PORTAMENTO = 0x4, + MIDI_CONTROLLER_DATA_ENTRY = 0x5, +} midi_controller_t; + +typedef enum +{ + MIDI_META_SEQUENCE_NUMBER = 0x0, + + MIDI_META_TEXT = 0x1, + MIDI_META_COPYRIGHT = 0x2, + MIDI_META_TRACK_NAME = 0x3, + MIDI_META_INSTR_NAME = 0x4, + MIDI_META_LYRICS = 0x5, + MIDI_META_MARKER = 0x6, + MIDI_META_CUE_POINT = 0x7, + + MIDI_META_CHANNEL_PREFIX = 0x8, + MIDI_META_END_OF_TRACK = 0x9, + MIDI_META_SET_TEMPO = 0xa, + MIDI_META_SMPTE_OFFSET = 0xb, + MIDI_META_TIME_SIGNATURE = 0xc, + MIDI_META_KEY_SIGNATURE = 0xd, + MIDI_META_SEQUENCER_SPECIFIC = 0xe, +} midi_meta_event_type_t; + +typedef struct +{ + // Meta event type: + + unsigned int type; + + // Length: + + unsigned int length; + + // Meta event data: + + byte *data; +} midi_meta_event_data_t; + +typedef struct +{ + // Length: + + unsigned int length; + + // Event data: + + byte *data; +} midi_sysex_event_data_t; + +typedef struct +{ + // The channel number to which this applies: + + unsigned int channel; + + // Extra parameters: + + unsigned int param1; + unsigned int param2; +} midi_channel_event_data_t; + +typedef struct +{ + // Time between the previous event and this event. + unsigned int delta_time; + + // Type of event: + midi_event_type_t event_type; + + union + { + midi_channel_event_data_t channel; + midi_meta_event_data_t meta; + midi_sysex_event_data_t sysex; + } data; +} midi_event_t; + +midi_file_t *MIDI_OpenFile(char *filename); +void MIDI_CloseFile(midi_file_t *file); + +#endif /* #ifndef MIDIFILE_H */ + -- cgit v1.2.3 From 2b64dfc84981dbbd9de1a0d9cb11995bf0ba9379 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Sun, 29 Mar 2009 22:39:25 +0000 Subject: Parse SysEx, meta events Subversion-branch: /branches/opl-branch Subversion-revision: 1490 --- src/midifile.c | 196 ++++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 180 insertions(+), 16 deletions(-) diff --git a/src/midifile.c b/src/midifile.c index e74cf17f..1b8a8786 100644 --- a/src/midifile.c +++ b/src/midifile.c @@ -53,6 +53,10 @@ struct midi_file_s FILE *stream; midi_header_t header; unsigned int data_len; + + // Data buffer used to store data read for SysEx or meta events: + byte *buffer; + unsigned int buffer_size; }; static boolean CheckChunkHeader(chunk_header_t *chunk, @@ -137,6 +141,9 @@ midi_file_t *MIDI_OpenFile(char *filename) return NULL; } + file->buffer = NULL; + file->buffer_size = 0; + // Open file file->stream = fopen(filename, "rb"); @@ -172,9 +179,116 @@ midi_file_t *MIDI_OpenFile(char *filename) void MIDI_CloseFile(midi_file_t *file) { fclose(file->stream); + free(file->buffer); free(file); } +// Read a single byte. Returns false on error. + +static boolean ReadByte(midi_file_t *file, byte *result) +{ + int c; + + c = fgetc(file->stream); + + if (c == EOF) + { + return false; + } + else + { + *result = (byte) c; + + return true; + } +} + +// Read a variable-length value. + +static boolean ReadVariableLength(midi_file_t *file, unsigned int *result) +{ + int i; + byte b; + + *result = 0; + + for (i=0; i<4; ++i) + { + if (!ReadByte(file, &b)) + { + fprintf(stderr, "Error while reading variable-length value\n"); + return false; + } + + // Insert the bottom seven bits from this byte. + + *result <<= 7; + *result |= b & 0x7f; + + // If the top bit is not set, this is the end. + + if ((b & 0x80) == 0) + { + return true; + } + } + + fprintf(stderr, "Variable-length value too long: maximum of four bytes!\n");; + return false; +} + +// Expand the size of the buffer used for SysEx/Meta events: + +static boolean ExpandBuffer(midi_file_t *file, unsigned int new_size) +{ + byte *new_buffer; + + if (file->buffer_size < new_size) + { + // Reallocate to a larger size: + + new_buffer = realloc(file->buffer, new_size); + + if (new_buffer == NULL) + { + fprintf(stderr, "ExpandBuffer: Failed to expand buffer to %u " + "bytes\n", new_size); + return false; + } + + file->buffer = new_buffer; + file->buffer_size = new_size; + } + + return true; +} + +// Read a byte sequence into the data buffer. + +static boolean ReadByteSequence(midi_file_t *file, unsigned int num_bytes) +{ + unsigned int i; + + // Check that we have enough space: + + if (!ExpandBuffer(file, num_bytes)) + { + return false; + } + + for (i=0; ibuffer[i])) + { + fprintf(stderr, "ReadByteSequence: Error while reading byte %u\n", + i); + return false; + } + } + + return true; +} + // Read a MIDI channel event. // two_param indicates that the event type takes two parameters // (three byte) otherwise it is single parameter (two byte) @@ -182,7 +296,7 @@ void MIDI_CloseFile(midi_file_t *file) static boolean ReadChannelEvent(midi_file_t *file, midi_event_t *event, int event_type, boolean two_param) { - int c; + byte b; // Set basics: @@ -191,27 +305,23 @@ static boolean ReadChannelEvent(midi_file_t *file, midi_event_t *event, // Read parameters: - c = fgetc(file->stream); - - if (c == EOF) + if (!ReadByte(file, &b)) { return false; } - event->data.channel.param1 = c; + event->data.channel.param1 = b; // Second parameter: if (two_param) { - c = fgetc(file->stream); - - if (c == EOF) + if (!ReadByte(file, &b)) { return false; } - event->data.channel.param2 = c; + event->data.channel.param2 = b; } return true; @@ -222,26 +332,79 @@ static boolean ReadChannelEvent(midi_file_t *file, midi_event_t *event, static boolean ReadSysExEvent(midi_file_t *file, midi_event_t *event, int event_type) { - // TODO - return false; + event->event_type = event_type; + + if (!ReadVariableLength(file, &event->data.sysex.length)) + { + fprintf(stderr, "ReadSysExEvent: Failed to read length of " + "SysEx block\n"); + return false; + } + + // Read the byte sequence: + + if (!ReadByteSequence(file, event->data.sysex.length)) + { + fprintf(stderr, "ReadSysExEvent: Failed while reading SysEx event\n"); + return false; + } + + event->data.sysex.data = file->buffer; + + return true; } // Read meta event: static boolean ReadMetaEvent(midi_file_t *file, midi_event_t *event) { - // TODO + byte b; + + // Read meta event type: + + if (!ReadByte(file, &b)) + { + fprintf(stderr, "ReadMetaEvent: Failed to read meta event type\n"); + return false; + } + + event->data.meta.type = b; + + // Read length of meta event data: + + if (!ReadVariableLength(file, &event->data.meta.length)) + { + fprintf(stderr, "ReadSysExEvent: Failed to read length of " + "SysEx block\n"); + return false; + } + + // Read the byte sequence: + + if (!ReadByteSequence(file, event->data.meta.length)) + { + fprintf(stderr, "ReadSysExEvent: Failed while reading SysEx event\n"); + return false; + } + + event->data.meta.data = file->buffer; + return false; } boolean MIDI_ReadEvent(midi_file_t *file, midi_event_t *event) { - int event_type; + byte event_type; - event_type = fgetc(file->stream); + if (!ReadVariableLength(file, &event->delta_time)) + { + fprintf(stderr, "MIDI_ReadEvent: Failed to read event timestamp\n"); + return false; + } - if (event_type == EOF) + if (!ReadByte(file, &event_type)) { + fprintf(stderr, "MIDI_ReadEvent: Failed to read event type\n"); return false; } @@ -277,7 +440,8 @@ boolean MIDI_ReadEvent(midi_file_t *file, midi_event_t *event) return ReadMetaEvent(file, event); } - // Fall-through deliberate - + // --- Fall-through deliberate --- + // Other 0xfx event types are unknown default: fprintf(stderr, "Unknown MIDI event type: 0x%x\n", event_type); -- 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(+) 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 c4f640be999e80351f1c89c893a0d5ce39c20cd9 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Sun, 29 Mar 2009 22:55:55 +0000 Subject: Clean up error messages, minor bugs etc. Subversion-branch: /branches/opl-branch Subversion-revision: 1492 --- src/midifile.c | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/midifile.c b/src/midifile.c index 1b8a8786..ed60958d 100644 --- a/src/midifile.c +++ b/src/midifile.c @@ -33,6 +33,7 @@ #define HEADER_CHUNK_ID "MThd" #define TRACK_CHUNK_ID "MTrk" +#define MAX_BUFFER_SIZE 0x10000 typedef struct { @@ -68,7 +69,7 @@ static boolean CheckChunkHeader(chunk_header_t *chunk, if (!result) { - fprintf(stderr, "CheckChunkHeader: Expected '%s' chunk header!\n", + fprintf(stderr, "CheckChunkHeader: Expected '%s' chunk header\n", expected_id); } @@ -150,7 +151,7 @@ midi_file_t *MIDI_OpenFile(char *filename) if (file->stream == NULL) { - fprintf(stderr, "Failed to open '%s'\n", filename); + fprintf(stderr, "MIDI_OpenFile: Failed to open '%s'\n", filename); free(file); return NULL; } @@ -193,6 +194,7 @@ static boolean ReadByte(midi_file_t *file, byte *result) if (c == EOF) { + fprintf(stderr, "ReadByte: Unexpected end of file\n"); return false; } else @@ -216,7 +218,8 @@ static boolean ReadVariableLength(midi_file_t *file, unsigned int *result) { if (!ReadByte(file, &b)) { - fprintf(stderr, "Error while reading variable-length value\n"); + fprintf(stderr, "ReadVariableLength: Error while reading " + "variable-length value\n"); return false; } @@ -233,7 +236,8 @@ static boolean ReadVariableLength(midi_file_t *file, unsigned int *result) } } - fprintf(stderr, "Variable-length value too long: maximum of four bytes!\n");; + fprintf(stderr, "ReadVariableLength: Variable-length value too " + "long: maximum of four bytes\n"); return false; } @@ -243,6 +247,13 @@ static boolean ExpandBuffer(midi_file_t *file, unsigned int new_size) { byte *new_buffer; + if (new_size > MAX_BUFFER_SIZE) + { + fprintf(stderr, "ExpandBuffer: Tried to expand buffer to %u bytes\n", + new_size); + return false; + } + if (file->buffer_size < new_size) { // Reallocate to a larger size: @@ -294,7 +305,7 @@ static boolean ReadByteSequence(midi_file_t *file, unsigned int num_bytes) // (three byte) otherwise it is single parameter (two byte) static boolean ReadChannelEvent(midi_file_t *file, midi_event_t *event, - int event_type, boolean two_param) + byte event_type, boolean two_param) { byte b; @@ -307,6 +318,8 @@ static boolean ReadChannelEvent(midi_file_t *file, midi_event_t *event, if (!ReadByte(file, &b)) { + fprintf(stderr, "ReadChannelEvent: Error while reading channel " + "event parameters\n"); return false; } @@ -318,6 +331,8 @@ static boolean ReadChannelEvent(midi_file_t *file, midi_event_t *event, { if (!ReadByte(file, &b)) { + fprintf(stderr, "ReadChannelEvent: Error while reading channel " + "event parameters\n"); return false; } @@ -389,7 +404,7 @@ static boolean ReadMetaEvent(midi_file_t *file, midi_event_t *event) event->data.meta.data = file->buffer; - return false; + return true; } boolean MIDI_ReadEvent(midi_file_t *file, midi_event_t *event) -- cgit v1.2.3 From 9b192c44f932542ccd835936d593c43ce3e9e9e3 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Fri, 3 Apr 2009 19:58:08 +0000 Subject: Fix up MIDI reading code; add test code. Subversion-branch: /branches/opl-branch Subversion-revision: 1494 --- src/Makefile.am | 3 ++ src/midifile.c | 104 +++++++++++++++++++++++++++++++++++++++++++++++--------- src/midifile.h | 15 ++++---- 3 files changed, 98 insertions(+), 24 deletions(-) diff --git a/src/Makefile.am b/src/Makefile.am index 92095b5c..3874c35f 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -195,3 +195,6 @@ icon.c : ../data/doom.ico endif +midiread : midifile.c + $(CC) -DTEST $(CFLAGS) @LDFLAGS@ $^ -o $@ + diff --git a/src/midifile.c b/src/midifile.c index ed60958d..e4273c12 100644 --- a/src/midifile.c +++ b/src/midifile.c @@ -27,6 +27,7 @@ #include #include +#include "doomdef.h" #include "doomtype.h" #include "i_swap.h" #include "midifile.h" @@ -39,15 +40,15 @@ typedef struct { byte chunk_id[4]; unsigned int chunk_size; -} chunk_header_t; +} PACKEDATTR chunk_header_t; typedef struct { chunk_header_t chunk_header; unsigned short format_type; unsigned short num_tracks; - unsigned int time_division; -} midi_header_t; + unsigned short time_division; +} PACKEDATTR midi_header_t; struct midi_file_s { @@ -65,12 +66,15 @@ static boolean CheckChunkHeader(chunk_header_t *chunk, { boolean result; - result = (strcmp((char *) chunk->chunk_id, expected_id) == 0); + result = (memcmp((char *) chunk->chunk_id, expected_id, 4) == 0); if (!result) { - fprintf(stderr, "CheckChunkHeader: Expected '%s' chunk header\n", - expected_id); + fprintf(stderr, "CheckChunkHeader: Expected '%s' chunk header, " + "got '%c%c%c%c'\n", + expected_id, + chunk->chunk_id[0], chunk->chunk_id[1], + chunk->chunk_id[2], chunk->chunk_id[3]); } return result; @@ -80,24 +84,26 @@ static boolean CheckChunkHeader(chunk_header_t *chunk, static boolean ReadHeaderChunk(midi_file_t *file) { - size_t bytes_read; + size_t records_read; - bytes_read = fread(&file->header, sizeof(midi_header_t), 1, file->stream); + records_read = fread(&file->header, sizeof(midi_header_t), 1, file->stream); - if (bytes_read < sizeof(midi_header_t)) + if (records_read < 1) { return false; } if (!CheckChunkHeader(&file->header.chunk_header, HEADER_CHUNK_ID) - || LONG(file->header.chunk_header.chunk_size) != 6) + || SDL_SwapBE32(file->header.chunk_header.chunk_size) != 6) { - fprintf(stderr, "ReadHeaderChunk: Invalid MIDI chunk header!\n"); + fprintf(stderr, "ReadHeaderChunk: Invalid MIDI chunk header! " + "chunk_size=%i\n", + SDL_SwapBE32(file->header.chunk_header.chunk_size)); return false; } - if (SHORT(file->header.format_type) != 0 - || SHORT(file->header.num_tracks) != 1) + if (SDL_SwapBE16(file->header.format_type) != 0 + || SDL_SwapBE16(file->header.num_tracks) != 1) { fprintf(stderr, "ReadHeaderChunk: Only single track, " "type 0 MIDI files supported!\n"); @@ -111,12 +117,12 @@ static boolean ReadHeaderChunk(midi_file_t *file) static boolean ReadTrackChunk(midi_file_t *file) { - size_t bytes_read; + size_t records_read; chunk_header_t chunk_header; - bytes_read = fread(&chunk_header, sizeof(chunk_header_t), 1, file->stream); + records_read = fread(&chunk_header, sizeof(chunk_header_t), 1, file->stream); - if (bytes_read < sizeof(chunk_header)) + if (records_read < 1) { return false; } @@ -126,7 +132,7 @@ static boolean ReadTrackChunk(midi_file_t *file) return false; } - file->data_len = LONG(chunk_header.chunk_size); + file->data_len = SDL_SwapBE32(chunk_header.chunk_size); return true; } @@ -375,6 +381,8 @@ static boolean ReadMetaEvent(midi_file_t *file, midi_event_t *event) { byte b; + event->event_type = MIDI_EVENT_META; + // Read meta event type: if (!ReadByte(file, &b)) @@ -464,3 +472,65 @@ boolean MIDI_ReadEvent(midi_file_t *file, midi_event_t *event) } } +#ifdef TEST + +int main(int argc, char *argv[]) +{ + midi_file_t *file; + midi_event_t event; + + if (argc < 2) + { + printf("Usage: %s \n", argv[0]); + exit(1); + } + + file = MIDI_OpenFile(argv[1]); + + if (file == NULL) + { + fprintf(stderr, "Failed to open %s\n", argv[1]); + exit(1); + } + + while (MIDI_ReadEvent(file, &event)) + { + printf("Event type: %i\n", event.event_type); + + switch(event.event_type) + { + case MIDI_EVENT_NOTE_OFF: + case MIDI_EVENT_NOTE_ON: + case MIDI_EVENT_AFTERTOUCH: + case MIDI_EVENT_CONTROLLER: + case MIDI_EVENT_PROGRAM_CHANGE: + case MIDI_EVENT_CHAN_AFTERTOUCH: + case MIDI_EVENT_PITCH_BEND: + printf("\tChannel: %i\n", event.data.channel.channel); + printf("\tParameter 1: %i\n", event.data.channel.param1); + printf("\tParameter 2: %i\n", event.data.channel.param2); + break; + + case MIDI_EVENT_SYSEX: + case MIDI_EVENT_SYSEX_SPLIT: + printf("\tLength: %i\n", event.data.sysex.length); + break; + + case MIDI_EVENT_META: + printf("\tMeta type: %i\n", event.data.meta.type); + printf("\tLength: %i\n", event.data.meta.length); + break; + } + + if (event.event_type == MIDI_EVENT_META + && event.data.meta.type == MIDI_META_END_OF_TRACK) + { + break; + } + } + + return 0; +} + +#endif + diff --git a/src/midifile.h b/src/midifile.h index d7df8b86..7928fdda 100644 --- a/src/midifile.h +++ b/src/midifile.h @@ -65,13 +65,14 @@ typedef enum MIDI_META_MARKER = 0x6, MIDI_META_CUE_POINT = 0x7, - MIDI_META_CHANNEL_PREFIX = 0x8, - MIDI_META_END_OF_TRACK = 0x9, - MIDI_META_SET_TEMPO = 0xa, - MIDI_META_SMPTE_OFFSET = 0xb, - MIDI_META_TIME_SIGNATURE = 0xc, - MIDI_META_KEY_SIGNATURE = 0xd, - MIDI_META_SEQUENCER_SPECIFIC = 0xe, + MIDI_META_CHANNEL_PREFIX = 0x20, + MIDI_META_END_OF_TRACK = 0x2f, + + MIDI_META_SET_TEMPO = 0x51, + MIDI_META_SMPTE_OFFSET = 0x54, + MIDI_META_TIME_SIGNATURE = 0x58, + MIDI_META_KEY_SIGNATURE = 0x59, + MIDI_META_SEQUENCER_SPECIFIC = 0x7f, } midi_meta_event_type_t; typedef struct -- cgit v1.2.3 From b2d78c296a5a2f9cf37bf8bd3dafb5c8ba166812 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Fri, 3 Apr 2009 20:07:08 +0000 Subject: Show MIDI event types with string identifiers. Subversion-branch: /branches/opl-branch Subversion-revision: 1495 --- src/midifile.c | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/midifile.c b/src/midifile.c index e4273c12..ef2ceb88 100644 --- a/src/midifile.c +++ b/src/midifile.c @@ -474,6 +474,36 @@ boolean MIDI_ReadEvent(midi_file_t *file, midi_event_t *event) #ifdef TEST +static char *MIDI_EventTypeToString(midi_event_type_t event_type) +{ + switch (event_type) + { + case MIDI_EVENT_NOTE_OFF: + return "MIDI_EVENT_NOTE_OFF"; + case MIDI_EVENT_NOTE_ON: + return "MIDI_EVENT_NOTE_ON"; + case MIDI_EVENT_AFTERTOUCH: + return "MIDI_EVENT_AFTERTOUCH"; + case MIDI_EVENT_CONTROLLER: + return "MIDI_EVENT_CONTROLLER"; + case MIDI_EVENT_PROGRAM_CHANGE: + return "MIDI_EVENT_PROGRAM_CHANGE"; + case MIDI_EVENT_CHAN_AFTERTOUCH: + return "MIDI_EVENT_CHAN_AFTERTOUCH"; + case MIDI_EVENT_PITCH_BEND: + return "MIDI_EVENT_PITCH_BEND"; + case MIDI_EVENT_SYSEX: + return "MIDI_EVENT_SYSEX"; + case MIDI_EVENT_SYSEX_SPLIT: + return "MIDI_EVENT_SYSEX_SPLIT"; + case MIDI_EVENT_META: + return "MIDI_EVENT_META"; + + default: + return "(unknown)"; + } +} + int main(int argc, char *argv[]) { midi_file_t *file; @@ -495,7 +525,9 @@ int main(int argc, char *argv[]) while (MIDI_ReadEvent(file, &event)) { - printf("Event type: %i\n", event.event_type); + printf("Event type: %s (%i)\n", + MIDI_EventTypeToString(event.event_type), + event.event_type); switch(event.event_type) { -- cgit v1.2.3 From 3dd7e1db9bf26e1e50bc13f6b176e54d63f2da85 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Mon, 13 Apr 2009 22:23:05 +0000 Subject: Extend MIDI file code to support reading multi-track files. Subversion-branch: /branches/opl-branch Subversion-revision: 1498 --- src/midifile.c | 562 +++++++++++++++++++++++++++++++++++---------------------- src/midifile.h | 2 +- 2 files changed, 351 insertions(+), 213 deletions(-) diff --git a/src/midifile.c b/src/midifile.c index ef2ceb88..cca0189e 100644 --- a/src/midifile.c +++ b/src/midifile.c @@ -50,17 +50,33 @@ typedef struct unsigned short time_division; } PACKEDATTR midi_header_t; +typedef struct +{ + // Length in bytes: + + unsigned int data_len; + + // Events in this track: + + midi_event_t *events; + int num_events; +} PACKEDATTR midi_track_t; + struct midi_file_s { - FILE *stream; midi_header_t header; - unsigned int data_len; + + // All tracks in this file: + midi_track_t *tracks; + unsigned int num_tracks; // Data buffer used to store data read for SysEx or meta events: byte *buffer; unsigned int buffer_size; }; +// Check the header of a chunk: + static boolean CheckChunkHeader(chunk_header_t *chunk, char *expected_id) { @@ -80,123 +96,13 @@ static boolean CheckChunkHeader(chunk_header_t *chunk, return result; } -// Read and check the header chunk. - -static boolean ReadHeaderChunk(midi_file_t *file) -{ - size_t records_read; - - records_read = fread(&file->header, sizeof(midi_header_t), 1, file->stream); - - if (records_read < 1) - { - return false; - } - - if (!CheckChunkHeader(&file->header.chunk_header, HEADER_CHUNK_ID) - || SDL_SwapBE32(file->header.chunk_header.chunk_size) != 6) - { - fprintf(stderr, "ReadHeaderChunk: Invalid MIDI chunk header! " - "chunk_size=%i\n", - SDL_SwapBE32(file->header.chunk_header.chunk_size)); - return false; - } - - if (SDL_SwapBE16(file->header.format_type) != 0 - || SDL_SwapBE16(file->header.num_tracks) != 1) - { - fprintf(stderr, "ReadHeaderChunk: Only single track, " - "type 0 MIDI files supported!\n"); - return false; - } - - return true; -} - -// Read and check the track chunk header - -static boolean ReadTrackChunk(midi_file_t *file) -{ - size_t records_read; - chunk_header_t chunk_header; - - records_read = fread(&chunk_header, sizeof(chunk_header_t), 1, file->stream); - - if (records_read < 1) - { - return false; - } - - if (!CheckChunkHeader(&chunk_header, TRACK_CHUNK_ID)) - { - return false; - } - - file->data_len = SDL_SwapBE32(chunk_header.chunk_size); - - return true; -} - -midi_file_t *MIDI_OpenFile(char *filename) -{ - midi_file_t *file; - - file = malloc(sizeof(midi_file_t)); - - if (file == NULL) - { - return NULL; - } - - file->buffer = NULL; - file->buffer_size = 0; - - // Open file - - file->stream = fopen(filename, "rb"); - - if (file->stream == NULL) - { - fprintf(stderr, "MIDI_OpenFile: Failed to open '%s'\n", filename); - free(file); - return NULL; - } - - // Read MIDI file header - - if (!ReadHeaderChunk(file)) - { - fclose(file->stream); - free(file); - return NULL; - } - - // Read track header - - if (!ReadTrackChunk(file)) - { - fclose(file->stream); - free(file); - return NULL; - } - - return file; -} - -void MIDI_CloseFile(midi_file_t *file) -{ - fclose(file->stream); - free(file->buffer); - free(file); -} - // Read a single byte. Returns false on error. -static boolean ReadByte(midi_file_t *file, byte *result) +static boolean ReadByte(byte *result, FILE *stream) { int c; - c = fgetc(file->stream); + c = fgetc(stream); if (c == EOF) { @@ -213,7 +119,7 @@ static boolean ReadByte(midi_file_t *file, byte *result) // Read a variable-length value. -static boolean ReadVariableLength(midi_file_t *file, unsigned int *result) +static boolean ReadVariableLength(unsigned int *result, FILE *stream) { int i; byte b; @@ -222,7 +128,7 @@ static boolean ReadVariableLength(midi_file_t *file, unsigned int *result) for (i=0; i<4; ++i) { - if (!ReadByte(file, &b)) + if (!ReadByte(&b, stream)) { fprintf(stderr, "ReadVariableLength: Error while reading " "variable-length value\n"); @@ -247,71 +153,46 @@ static boolean ReadVariableLength(midi_file_t *file, unsigned int *result) return false; } -// Expand the size of the buffer used for SysEx/Meta events: - -static boolean ExpandBuffer(midi_file_t *file, unsigned int new_size) -{ - byte *new_buffer; - - if (new_size > MAX_BUFFER_SIZE) - { - fprintf(stderr, "ExpandBuffer: Tried to expand buffer to %u bytes\n", - new_size); - return false; - } - - if (file->buffer_size < new_size) - { - // Reallocate to a larger size: - - new_buffer = realloc(file->buffer, new_size); - - if (new_buffer == NULL) - { - fprintf(stderr, "ExpandBuffer: Failed to expand buffer to %u " - "bytes\n", new_size); - return false; - } - - file->buffer = new_buffer; - file->buffer_size = new_size; - } - - return true; -} - // Read a byte sequence into the data buffer. -static boolean ReadByteSequence(midi_file_t *file, unsigned int num_bytes) +static void *ReadByteSequence(unsigned int num_bytes, FILE *stream) { unsigned int i; + byte *result; + + // Allocate a buffer: - // Check that we have enough space: + result = malloc(num_bytes); - if (!ExpandBuffer(file, num_bytes)) + if (result == NULL) { - return false; + fprintf(stderr, "ReadByteSequence: Failed to allocate buffer\n"); + return NULL; } + // Read the data: + for (i=0; ibuffer[i])) + if (!ReadByte(&result[i], stream)) { fprintf(stderr, "ReadByteSequence: Error while reading byte %u\n", i); - return false; + free(result); + return NULL; } } - return true; + return result; } // Read a MIDI channel event. // two_param indicates that the event type takes two parameters // (three byte) otherwise it is single parameter (two byte) -static boolean ReadChannelEvent(midi_file_t *file, midi_event_t *event, - byte event_type, boolean two_param) +static boolean ReadChannelEvent(midi_event_t *event, + byte event_type, boolean two_param, + FILE *stream) { byte b; @@ -322,7 +203,7 @@ static boolean ReadChannelEvent(midi_file_t *file, midi_event_t *event, // Read parameters: - if (!ReadByte(file, &b)) + if (!ReadByte(&b, stream)) { fprintf(stderr, "ReadChannelEvent: Error while reading channel " "event parameters\n"); @@ -335,7 +216,7 @@ static boolean ReadChannelEvent(midi_file_t *file, midi_event_t *event, if (two_param) { - if (!ReadByte(file, &b)) + if (!ReadByte(&b, stream)) { fprintf(stderr, "ReadChannelEvent: Error while reading channel " "event parameters\n"); @@ -350,12 +231,12 @@ static boolean ReadChannelEvent(midi_file_t *file, midi_event_t *event, // Read sysex event: -static boolean ReadSysExEvent(midi_file_t *file, midi_event_t *event, - int event_type) +static boolean ReadSysExEvent(midi_event_t *event, int event_type, + FILE *stream) { event->event_type = event_type; - if (!ReadVariableLength(file, &event->data.sysex.length)) + if (!ReadVariableLength(&event->data.sysex.length, stream)) { fprintf(stderr, "ReadSysExEvent: Failed to read length of " "SysEx block\n"); @@ -364,20 +245,20 @@ static boolean ReadSysExEvent(midi_file_t *file, midi_event_t *event, // Read the byte sequence: - if (!ReadByteSequence(file, event->data.sysex.length)) + event->data.sysex.data = ReadByteSequence(event->data.sysex.length, stream); + + if (event->data.sysex.data == NULL) { fprintf(stderr, "ReadSysExEvent: Failed while reading SysEx event\n"); return false; } - event->data.sysex.data = file->buffer; - return true; } // Read meta event: -static boolean ReadMetaEvent(midi_file_t *file, midi_event_t *event) +static boolean ReadMetaEvent(midi_event_t *event, FILE *stream) { byte b; @@ -385,7 +266,7 @@ static boolean ReadMetaEvent(midi_file_t *file, midi_event_t *event) // Read meta event type: - if (!ReadByte(file, &b)) + if (!ReadByte(&b, stream)) { fprintf(stderr, "ReadMetaEvent: Failed to read meta event type\n"); return false; @@ -395,7 +276,7 @@ static boolean ReadMetaEvent(midi_file_t *file, midi_event_t *event) // Read length of meta event data: - if (!ReadVariableLength(file, &event->data.meta.length)) + if (!ReadVariableLength(&event->data.meta.length, stream)) { fprintf(stderr, "ReadSysExEvent: Failed to read length of " "SysEx block\n"); @@ -404,30 +285,30 @@ static boolean ReadMetaEvent(midi_file_t *file, midi_event_t *event) // Read the byte sequence: - if (!ReadByteSequence(file, event->data.meta.length)) + event->data.meta.data = ReadByteSequence(event->data.meta.length, stream); + + if (event->data.meta.data == NULL) { fprintf(stderr, "ReadSysExEvent: Failed while reading SysEx event\n"); return false; } - event->data.meta.data = file->buffer; - return true; } -boolean MIDI_ReadEvent(midi_file_t *file, midi_event_t *event) +static boolean ReadEvent(midi_event_t *event, FILE *stream) { byte event_type; - if (!ReadVariableLength(file, &event->delta_time)) + if (!ReadVariableLength(&event->delta_time, stream)) { - fprintf(stderr, "MIDI_ReadEvent: Failed to read event timestamp\n"); + fprintf(stderr, "ReadEvent: Failed to read event timestamp\n"); return false; } - if (!ReadByte(file, &event_type)) + if (!ReadByte(&event_type, stream)) { - fprintf(stderr, "MIDI_ReadEvent: Failed to read event type\n"); + fprintf(stderr, "ReadEvent: Failed to read event type\n"); return false; } @@ -442,13 +323,13 @@ boolean MIDI_ReadEvent(midi_file_t *file, midi_event_t *event) case MIDI_EVENT_AFTERTOUCH: case MIDI_EVENT_CONTROLLER: case MIDI_EVENT_PITCH_BEND: - return ReadChannelEvent(file, event, event_type, true); + return ReadChannelEvent(event, event_type, true, stream); // Single parameter channel events: case MIDI_EVENT_PROGRAM_CHANGE: case MIDI_EVENT_CHAN_AFTERTOUCH: - return ReadChannelEvent(file, event, event_type, false); + return ReadChannelEvent(event, event_type, false, stream); // Other event types: @@ -456,11 +337,11 @@ boolean MIDI_ReadEvent(midi_file_t *file, midi_event_t *event) if (event_type == MIDI_EVENT_SYSEX || event_type == MIDI_EVENT_SYSEX_SPLIT) { - return ReadSysExEvent(file, event, event_type); + return ReadSysExEvent(event, event_type, stream); } else if (event_type == MIDI_EVENT_META) { - return ReadMetaEvent(file, event); + return ReadMetaEvent(event, stream); } // --- Fall-through deliberate --- @@ -472,6 +353,254 @@ boolean MIDI_ReadEvent(midi_file_t *file, midi_event_t *event) } } +// Free an event: + +static void FreeEvent(midi_event_t *event) +{ + // Some event types have dynamically allocated buffers assigned + // to them that must be freed. + + switch (event->event_type) + { + case MIDI_EVENT_SYSEX: + case MIDI_EVENT_SYSEX_SPLIT: + free(event->data.sysex.data); + break; + + case MIDI_EVENT_META: + free(event->data.meta.data); + break; + + default: + // Nothing to do. + break; + } +} + +// Read and check the track chunk header + +static boolean ReadTrackHeader(midi_track_t *track, FILE *stream) +{ + size_t records_read; + chunk_header_t chunk_header; + + records_read = fread(&chunk_header, sizeof(chunk_header_t), 1, stream); + + if (records_read < 1) + { + return false; + } + + if (!CheckChunkHeader(&chunk_header, TRACK_CHUNK_ID)) + { + return false; + } + + track->data_len = SDL_SwapBE32(chunk_header.chunk_size); + + return true; +} + +static boolean ReadTrack(midi_track_t *track, FILE *stream) +{ + midi_event_t *new_events; + midi_event_t *event; + + track->num_events = 0; + track->events = NULL; + + // Read the header: + + if (!ReadTrackHeader(track, stream)) + { + return false; + } + + // Then the events: + + for (;;) + { + // Resize the track slightly larger to hold another event: + + new_events = realloc(track->events, + sizeof(midi_event_t) * (track->num_events + 1)); + + if (new_events == NULL) + { + return false; + } + + track->events = new_events; + + // Read the next event: + + event = &track->events[track->num_events]; + if (!ReadEvent(event, stream)) + { + return false; + } + + ++track->num_events; + + // End of track? + + if (event->event_type == MIDI_EVENT_META + && event->data.meta.type == MIDI_META_END_OF_TRACK) + { + break; + } + } + + return true; +} + +// Free a track: + +static void FreeTrack(midi_track_t *track) +{ + unsigned int i; + + for (i=0; inum_events; ++i) + { + FreeEvent(&track->events[i]); + } + + free(track->events); +} + +static boolean ReadAllTracks(midi_file_t *file, FILE *stream) +{ + unsigned int i; + + // Allocate list of tracks and read each track: + + file->tracks = malloc(sizeof(midi_track_t) * file->num_tracks); + + if (file->tracks == NULL) + { + return false; + } + + memset(file->tracks, 0, sizeof(midi_track_t) * file->num_tracks); + + // Read each track: + + for (i=0; inum_tracks; ++i) + { + if (!ReadTrack(&file->tracks[i], stream)) + { + return false; + } + } + + return true; +} + +// Read and check the header chunk. + +static boolean ReadFileHeader(midi_file_t *file, FILE *stream) +{ + size_t records_read; + unsigned int format_type; + + records_read = fread(&file->header, sizeof(midi_header_t), 1, stream); + + if (records_read < 1) + { + return false; + } + + if (!CheckChunkHeader(&file->header.chunk_header, HEADER_CHUNK_ID) + || SDL_SwapBE32(file->header.chunk_header.chunk_size) != 6) + { + fprintf(stderr, "ReadFileHeader: Invalid MIDI chunk header! " + "chunk_size=%i\n", + SDL_SwapBE32(file->header.chunk_header.chunk_size)); + return false; + } + + format_type = SDL_SwapBE16(file->header.format_type); + file->num_tracks = SDL_SwapBE16(file->header.num_tracks); + + if ((format_type != 0 && format_type != 1) + || file->num_tracks < 1) + { + fprintf(stderr, "ReadFileHeader: Only type 0/1 " + "MIDI files supported!\n"); + return false; + } + + return true; +} + +void MIDI_FreeFile(midi_file_t *file) +{ + int i; + + if (file->tracks != NULL) + { + for (i=0; inum_tracks; ++i) + { + FreeTrack(&file->tracks[i]); + } + + free(file->tracks); + } + + free(file); +} + +midi_file_t *MIDI_OpenFile(char *filename) +{ + midi_file_t *file; + FILE *stream; + + file = malloc(sizeof(midi_file_t)); + + if (file == NULL) + { + return NULL; + } + + file->tracks = NULL; + file->num_tracks = 0; + file->buffer = NULL; + file->buffer_size = 0; + + // Open file + + stream = fopen(filename, "rb"); + + if (stream == NULL) + { + fprintf(stderr, "MIDI_OpenFile: Failed to open '%s'\n", filename); + MIDI_FreeFile(file); + return NULL; + } + + // Read MIDI file header + + if (!ReadFileHeader(file, stream)) + { + fclose(stream); + MIDI_FreeFile(file); + return NULL; + } + + // Read all tracks: + + if (!ReadAllTracks(file, stream)) + { + fclose(stream); + MIDI_FreeFile(file); + return NULL; + } + + fclose(stream); + + return file; +} + #ifdef TEST static char *MIDI_EventTypeToString(midi_event_type_t event_type) @@ -504,32 +633,20 @@ static char *MIDI_EventTypeToString(midi_event_type_t event_type) } } -int main(int argc, char *argv[]) +void PrintTrack(midi_track_t *track) { - midi_file_t *file; - midi_event_t event; - - if (argc < 2) - { - printf("Usage: %s \n", argv[0]); - exit(1); - } - - file = MIDI_OpenFile(argv[1]); + midi_event_t *event; + unsigned int i; - if (file == NULL) + for (i=0; inum_events; ++i) { - fprintf(stderr, "Failed to open %s\n", argv[1]); - exit(1); - } + event = &track->events[i]; - while (MIDI_ReadEvent(file, &event)) - { printf("Event type: %s (%i)\n", - MIDI_EventTypeToString(event.event_type), - event.event_type); + MIDI_EventTypeToString(event->event_type), + event->event_type); - switch(event.event_type) + switch(event->event_type) { case MIDI_EVENT_NOTE_OFF: case MIDI_EVENT_NOTE_ON: @@ -538,27 +655,48 @@ int main(int argc, char *argv[]) case MIDI_EVENT_PROGRAM_CHANGE: case MIDI_EVENT_CHAN_AFTERTOUCH: case MIDI_EVENT_PITCH_BEND: - printf("\tChannel: %i\n", event.data.channel.channel); - printf("\tParameter 1: %i\n", event.data.channel.param1); - printf("\tParameter 2: %i\n", event.data.channel.param2); + printf("\tChannel: %i\n", event->data.channel.channel); + printf("\tParameter 1: %i\n", event->data.channel.param1); + printf("\tParameter 2: %i\n", event->data.channel.param2); break; case MIDI_EVENT_SYSEX: case MIDI_EVENT_SYSEX_SPLIT: - printf("\tLength: %i\n", event.data.sysex.length); + printf("\tLength: %i\n", event->data.sysex.length); break; case MIDI_EVENT_META: - printf("\tMeta type: %i\n", event.data.meta.type); - printf("\tLength: %i\n", event.data.meta.length); + printf("\tMeta type: %i\n", event->data.meta.type); + printf("\tLength: %i\n", event->data.meta.length); break; } + } +} - if (event.event_type == MIDI_EVENT_META - && event.data.meta.type == MIDI_META_END_OF_TRACK) - { - break; - } +int main(int argc, char *argv[]) +{ + midi_file_t *file; + unsigned int i; + + if (argc < 2) + { + printf("Usage: %s \n", argv[0]); + exit(1); + } + + file = MIDI_OpenFile(argv[1]); + + if (file == NULL) + { + fprintf(stderr, "Failed to open %s\n", argv[1]); + exit(1); + } + + for (i=0; inum_tracks; ++i) + { + printf("\n== Track %i ==\n\n", i); + + PrintTrack(&file->tracks[i]); } return 0; diff --git a/src/midifile.h b/src/midifile.h index 7928fdda..16f911e7 100644 --- a/src/midifile.h +++ b/src/midifile.h @@ -130,7 +130,7 @@ typedef struct } midi_event_t; midi_file_t *MIDI_OpenFile(char *filename); -void MIDI_CloseFile(midi_file_t *file); +void MIDI_FreeFile(midi_file_t *file); #endif /* #ifndef MIDIFILE_H */ -- cgit v1.2.3 From d8ada5c41da3eec5ef163d9efac9cb5dbdc2cb6b Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Wed, 15 Apr 2009 19:00:25 +0000 Subject: Parse MIDI events that reuse the event type from the previous event. Subversion-branch: /branches/opl-branch Subversion-revision: 1499 --- src/midifile.c | 70 +++++++++++++++++++++++++++++++++++++++++----------------- src/midifile.h | 14 ++++++------ 2 files changed, 57 insertions(+), 27 deletions(-) diff --git a/src/midifile.c b/src/midifile.c index cca0189e..1be6ec75 100644 --- a/src/midifile.c +++ b/src/midifile.c @@ -198,8 +198,8 @@ static boolean ReadChannelEvent(midi_event_t *event, // Set basics: - event->event_type = event_type >> 4; - event->data.channel.channel = event_type & 0xf; + event->event_type = event_type & 0xf0; + event->data.channel.channel = event_type & 0x0f; // Read parameters: @@ -296,7 +296,8 @@ static boolean ReadMetaEvent(midi_event_t *event, FILE *stream) return true; } -static boolean ReadEvent(midi_event_t *event, FILE *stream) +static boolean ReadEvent(midi_event_t *event, unsigned int *last_event_type, + FILE *stream) { byte event_type; @@ -312,9 +313,29 @@ static boolean ReadEvent(midi_event_t *event, FILE *stream) return false; } + // All event types have their top bit set. Therefore, if + // the top bit is not set, it is because we are using the "same + // as previous event type" shortcut to save a byte. Skip back + // a byte so that we read this byte again. + + if ((event_type & 0x80) == 0) + { + event_type = *last_event_type; + + if (fseek(stream, -1, SEEK_CUR) < 0) + { + fprintf(stderr, "ReadEvent: Unable to seek in stream\n"); + return false; + } + } + else + { + *last_event_type = event_type; + } + // Check event type: - switch (event_type >> 4) + switch (event_type & 0xf0) { // Two parameter channel events: @@ -331,26 +352,27 @@ static boolean ReadEvent(midi_event_t *event, FILE *stream) case MIDI_EVENT_CHAN_AFTERTOUCH: return ReadChannelEvent(event, event_type, false, stream); - // Other event types: + default: + break; + } + + // Specific value? - case 0xf: - if (event_type == MIDI_EVENT_SYSEX - || event_type == MIDI_EVENT_SYSEX_SPLIT) - { - return ReadSysExEvent(event, event_type, stream); - } - else if (event_type == MIDI_EVENT_META) - { - return ReadMetaEvent(event, stream); - } + switch (event_type) + { + case MIDI_EVENT_SYSEX: + case MIDI_EVENT_SYSEX_SPLIT: + return ReadSysExEvent(event, event_type, stream); - // --- Fall-through deliberate --- - // Other 0xfx event types are unknown + case MIDI_EVENT_META: + return ReadMetaEvent(event, stream); default: - fprintf(stderr, "Unknown MIDI event type: 0x%x\n", event_type); - return false; + break; } + + fprintf(stderr, "ReadEvent: Unknown MIDI event type: 0x%x\n", event_type); + return false; } // Free an event: @@ -405,6 +427,7 @@ static boolean ReadTrack(midi_track_t *track, FILE *stream) { midi_event_t *new_events; midi_event_t *event; + unsigned int last_event_type; track->num_events = 0; track->events = NULL; @@ -418,6 +441,8 @@ static boolean ReadTrack(midi_track_t *track, FILE *stream) // Then the events: + last_event_type = 0; + for (;;) { // Resize the track slightly larger to hold another event: @@ -435,7 +460,7 @@ static boolean ReadTrack(midi_track_t *track, FILE *stream) // Read the next event: event = &track->events[track->num_events]; - if (!ReadEvent(event, stream)) + if (!ReadEvent(event, &last_event_type, stream)) { return false; } @@ -642,6 +667,11 @@ void PrintTrack(midi_track_t *track) { event = &track->events[i]; + if (event->delta_time > 0) + { + printf("Delay: %i ticks\n", event->delta_time); + } + printf("Event type: %s (%i)\n", MIDI_EventTypeToString(event->event_type), event->event_type); diff --git a/src/midifile.h b/src/midifile.h index 16f911e7..490b0171 100644 --- a/src/midifile.h +++ b/src/midifile.h @@ -30,13 +30,13 @@ typedef struct midi_file_s midi_file_t; typedef enum { - MIDI_EVENT_NOTE_OFF = 0x8, - MIDI_EVENT_NOTE_ON = 0x9, - MIDI_EVENT_AFTERTOUCH = 0xa, - MIDI_EVENT_CONTROLLER = 0xb, - MIDI_EVENT_PROGRAM_CHANGE = 0xc, - MIDI_EVENT_CHAN_AFTERTOUCH = 0xd, - MIDI_EVENT_PITCH_BEND = 0xe, + MIDI_EVENT_NOTE_OFF = 0x80, + MIDI_EVENT_NOTE_ON = 0x90, + MIDI_EVENT_AFTERTOUCH = 0xa0, + MIDI_EVENT_CONTROLLER = 0xb0, + MIDI_EVENT_PROGRAM_CHANGE = 0xc0, + MIDI_EVENT_CHAN_AFTERTOUCH = 0xd0, + MIDI_EVENT_PITCH_BEND = 0xe0, MIDI_EVENT_SYSEX = 0xf0, MIDI_EVENT_SYSEX_SPLIT = 0xf7, -- 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(-) 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 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 --- configure.in | 3 +- opl/Makefile.am | 2 + opl/examples/Makefile.am | 8 +++ opl/examples/droplay.c | 177 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 189 insertions(+), 1 deletion(-) create mode 100644 opl/examples/Makefile.am create mode 100644 opl/examples/droplay.c diff --git a/configure.in b/configure.in index 00dadb4e..3ee2e59e 100644 --- a/configure.in +++ b/configure.in @@ -90,8 +90,9 @@ textscreen/Makefile textscreen/examples/Makefile setup/Makefile man/Makefile -src/Makefile opl/Makefile +opl/examples/Makefile +src/Makefile pcsound/Makefile src/resource.rc src/doom-screensaver.desktop 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(-) 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(+) 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 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(-) 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(-) 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(-) 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 37db69b86bc5052901e250578e9c3920886d79ff Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Sat, 11 Jul 2009 12:32:08 +0000 Subject: Remove entry in bug list about OPL music. Subversion-branch: /branches/opl-branch Subversion-revision: 1618 --- BUGS | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/BUGS b/BUGS index 5dd8a531..ee6bf09a 100644 --- a/BUGS +++ b/BUGS @@ -6,21 +6,6 @@ effects are cut off at the wrong distance. This needs further investigation. -* Music plays back differently. - - Vanilla Doom was typically played with a SoundBlaster (or compatible) - card. It programmed the registers for the OPL music chip directly - in order to emulate the various General MIDI instruments. However, - Chocolate Doom uses the OS's native MIDI playback interfaces to play - MIDI sound. As the OPL is programmed differently, the music sounds - different to the original, even when using an original SoundBlaster - card. - - This can be worked around in the future: OPL emulation code exists that - simulates an OPL chip in software. Furthermore, depending on the OS, - it may be possible to program the OPL directly in order to get the - same sound. - * A small number of Doom bugs are almost impossible to emulate. An example of this can be seen in Ledmeister's "Blackbug" demo which -- 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(-) 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(-) 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(-) 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(+) 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 324c1d8776054394d30ea987f84c1ba2f1b1ff6f Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Sat, 29 Aug 2009 21:05:36 +0000 Subject: Rename MIDI_OpenFile to MIDI_LoadFile, remove unneeded structure packing. Subversion-branch: /branches/opl-branch Subversion-revision: 1636 --- src/midifile.c | 4 ++-- src/midifile.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/midifile.c b/src/midifile.c index 1be6ec75..6eb9e7db 100644 --- a/src/midifile.c +++ b/src/midifile.c @@ -60,7 +60,7 @@ typedef struct midi_event_t *events; int num_events; -} PACKEDATTR midi_track_t; +} midi_track_t; struct midi_file_s { @@ -575,7 +575,7 @@ void MIDI_FreeFile(midi_file_t *file) free(file); } -midi_file_t *MIDI_OpenFile(char *filename) +midi_file_t *MIDI_LoadFile(char *filename) { midi_file_t *file; FILE *stream; diff --git a/src/midifile.h b/src/midifile.h index 490b0171..f9acea35 100644 --- a/src/midifile.h +++ b/src/midifile.h @@ -129,7 +129,7 @@ typedef struct } data; } midi_event_t; -midi_file_t *MIDI_OpenFile(char *filename); +midi_file_t *MIDI_LoadFile(char *filename); void MIDI_FreeFile(midi_file_t *file); #endif /* #ifndef MIDIFILE_H */ -- cgit v1.2.3 From 9cc843c60027cb9365f3eaae3028343769d17a26 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Sat, 29 Aug 2009 21:22:32 +0000 Subject: Load MIDI file. Subversion-branch: /branches/opl-branch Subversion-revision: 1637 --- src/i_oplmusic.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/i_oplmusic.c b/src/i_oplmusic.c index f50c3322..c444dfd8 100644 --- a/src/i_oplmusic.c +++ b/src/i_oplmusic.c @@ -40,6 +40,7 @@ #include "z_zone.h" #include "opl.h" +#include "midifile.h" #define MAXMIDLENGTH (96 * 1024) #define GENMIDI_NUM_INSTRS 128 @@ -507,6 +508,11 @@ static void I_OPL_UnRegisterSong(void *handle) { return; } + + if (handle != NULL) + { + MIDI_FreeFile(handle); + } } // Determine whether memory block is a .mid file @@ -544,6 +550,7 @@ static boolean ConvertMus(byte *musdata, int len, char *filename) static void *I_OPL_RegisterSong(void *data, int len) { + midi_file_t *result; char *filename; if (!music_initialised) @@ -567,7 +574,12 @@ static void *I_OPL_RegisterSong(void *data, int len) ConvertMus(data, len, filename); } - // .... + result = MIDI_LoadFile(filename); + + if (result == NULL) + { + fprintf(stderr, "I_OPL_RegisterSong: Failed to load MID.\n"); + } // remove file now @@ -575,7 +587,7 @@ static void *I_OPL_RegisterSong(void *data, int len) Z_Free(filename); - return NULL; + return result; } // Is the song playing? -- 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 ++++++- src/i_oplmusic.c | 2 ++ 2 files changed, 8 insertions(+), 1 deletion(-) 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); diff --git a/src/i_oplmusic.c b/src/i_oplmusic.c index c444dfd8..0e1b69a3 100644 --- a/src/i_oplmusic.c +++ b/src/i_oplmusic.c @@ -202,6 +202,8 @@ static boolean DetectOPL(void) GetStatus(); } + OPL_Delay(1); + // Read status result2 = GetStatus(); -- cgit v1.2.3 From f2f36117c889c19b643058a0af33070baf4b48be Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Sat, 29 Aug 2009 21:30:26 +0000 Subject: MIDI_OpenFile -> MIDI_LoadFile. Subversion-branch: /branches/opl-branch Subversion-revision: 1639 --- src/midifile.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/midifile.c b/src/midifile.c index 6eb9e7db..61e06463 100644 --- a/src/midifile.c +++ b/src/midifile.c @@ -598,7 +598,7 @@ midi_file_t *MIDI_LoadFile(char *filename) if (stream == NULL) { - fprintf(stderr, "MIDI_OpenFile: Failed to open '%s'\n", filename); + fprintf(stderr, "MIDI_LoadFile: Failed to open '%s'\n", filename); MIDI_FreeFile(file); return NULL; } @@ -714,7 +714,7 @@ int main(int argc, char *argv[]) exit(1); } - file = MIDI_OpenFile(argv[1]); + file = MIDI_LoadFile(argv[1]); if (file == NULL) { -- cgit v1.2.3 From f6ce7dfea99cf32beb2afc8e7b02fb5f19f7544f Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Sun, 30 Aug 2009 01:56:33 +0000 Subject: Make some noise. Subversion-branch: /branches/opl-branch Subversion-revision: 1640 --- src/i_oplmusic.c | 42 +++++++++++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/src/i_oplmusic.c b/src/i_oplmusic.c index 0e1b69a3..2c0efa92 100644 --- a/src/i_oplmusic.c +++ b/src/i_oplmusic.c @@ -42,6 +42,8 @@ #include "opl.h" #include "midifile.h" +#define TEST + #define MAXMIDLENGTH (96 * 1024) #define GENMIDI_NUM_INSTRS 128 @@ -405,6 +407,30 @@ static void I_OPL_ShutdownMusic(void) } } +#ifdef TEST +static void TestCallback(void *arg) +{ + opl_voice_t *voice = arg; + int note; + int wait_time; + + // Set level: + WriteRegister(OPL_REGS_LEVEL + voice->op2, 0); + + // Note off: + + WriteRegister(OPL_REGS_FREQ_2 + voice->index, 0x00); + // Note on: + + note = (rand() % (0x2ae - 0x16b)) + 0x16b; + WriteRegister(OPL_REGS_FREQ_1 + voice->index, note & 0xff); + WriteRegister(OPL_REGS_FREQ_2 + voice->index, 0x30 + (note >> 8)); + + wait_time = (rand() % 700) + 50; + OPL_SetCallback(wait_time, TestCallback, arg); +} +#endif + // Initialise music subsystem static boolean I_OPL_InitMusic(void) @@ -443,17 +469,19 @@ static boolean I_OPL_InitMusic(void) #ifdef TEST { + int i; opl_voice_t *voice; + int instr_num; - voice = GetFreeVoice(); - SetVoiceInstrument(voice, &main_instrs[34].opl2_voice); + for (i=0; i<3; ++i) + { + voice = GetFreeVoice(); + instr_num = rand() % 100; - // Set level: - WriteRegister(OPL_REGS_LEVEL + voice->op2, 0x94); + SetVoiceInstrument(voice, &main_instrs[instr_num].opl2_voice); - // Note on: - WriteRegister(OPL_REGS_FREQ_1 + voice->index, 0x65); - WriteRegister(OPL_REGS_FREQ_2 + voice->index, 0x2b); + OPL_SetCallback(0, TestCallback, voice); + } } #endif -- cgit v1.2.3 From 4d13a1330013cfcf70c8bc13f6183ea404bbdbe4 Mon Sep 17 00:00:00 2001 From: Russell Rice Date: Sun, 30 Aug 2009 04:31:50 +0000 Subject: - Add OPL to codeblocks project Subversion-branch: /branches/opl-branch Subversion-revision: 1641 --- codeblocks/game.cbp | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/codeblocks/game.cbp b/codeblocks/game.cbp index bf1c249a..ab6350eb 100644 --- a/codeblocks/game.cbp +++ b/codeblocks/game.cbp @@ -46,6 +46,7 @@ + @@ -55,6 +56,29 @@ + + + + + + + + + + + + + + + + + @@ -165,6 +189,9 @@ + + @@ -235,6 +262,10 @@ + + + -- 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(+) 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 ca23c95db8053b82fb817241e5de83a881f931e5 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Sun, 30 Aug 2009 21:34:12 +0000 Subject: Initial/basic MIDI track playback. Subversion-branch: /branches/opl-branch Subversion-revision: 1643 --- src/i_oplmusic.c | 328 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- src/midifile.c | 69 ++++++++++++ src/midifile.h | 28 +++++ 3 files changed, 423 insertions(+), 2 deletions(-) diff --git a/src/i_oplmusic.c b/src/i_oplmusic.c index 2c0efa92..3d9a2649 100644 --- a/src/i_oplmusic.c +++ b/src/i_oplmusic.c @@ -42,7 +42,7 @@ #include "opl.h" #include "midifile.h" -#define TEST +//#define TEST #define MAXMIDLENGTH (96 * 1024) #define GENMIDI_NUM_INSTRS 128 @@ -80,6 +80,37 @@ typedef struct genmidi_voice_t opl3_voice; } PACKEDATTR genmidi_instr_t; +// Data associated with a channel of a track that is currently playing. + +typedef struct +{ + // The instrument currently used for this track. + + genmidi_instr_t *instrument; + + // Volume level + + int volume; +} opl_channel_data_t; + +// Data associated with a track that is currently playing. + +typedef struct +{ + // Data for each channel. + + opl_channel_data_t channels[MIDI_CHANNELS_PER_TRACK]; + + // Track iterator used to read new events. + + midi_track_iter_t *iter; + + // Tempo control variables + + unsigned int ticks_per_beat; + unsigned int us_per_beat; +} opl_track_data_t; + typedef struct opl_voice_s opl_voice_t; struct opl_voice_s @@ -93,6 +124,15 @@ struct opl_voice_s // Currently-loaded instrument data genmidi_instr_t *current_instr; + // The channel currently using this voice. + opl_channel_data_t *channel; + + // The note that this voice is playing. + unsigned int note; + + // The frequency value being used. + unsigned int freq; + // Next in freelist opl_voice_t *next; }; @@ -104,6 +144,22 @@ static const int voice_operators[2][OPL_NUM_VOICES] = { { 0x03, 0x04, 0x05, 0x0b, 0x0c, 0x0d, 0x13, 0x14, 0x15 } }; +// Frequency values to use for each note. + +static const unsigned int note_frequencies[] = { + + // These frequencies are only used for the first seven + // MIDI note values: + + 0x158, 0x16d, 0x183, 0x19a, 0x1b2, 0x1cc, 0x1e7, + + // These frequencies are used repeatedly, cycling around + // for each octave: + + 0x204, 0x223, 0x244, 0x266, 0x28b, 0x2b1, + 0x2da, 0x306, 0x334, 0x365, 0x398, 0x3cf, +}; + static boolean music_initialised = false; //static boolean musicpaused = false; @@ -119,6 +175,10 @@ static genmidi_instr_t *percussion_instrs; static opl_voice_t voices[OPL_NUM_VOICES]; static opl_voice_t *voice_free_list; +// Track data for playing tracks: + +static opl_track_data_t *tracks; + // In the initialisation stage, register writes are spaced by reading // from the register port (0). After initialisation, spacing is // peformed by reading from the data port instead. I have no idea @@ -310,6 +370,9 @@ static void ReleaseVoice(opl_voice_t *voice) { opl_voice_t **rover; + voice->channel = NULL; + voice->note = 0; + // Search to the end of the freelist (This is how Doom behaves!) rover = &voice_free_list; @@ -320,6 +383,7 @@ static void ReleaseVoice(opl_voice_t *voice) } *rover = voice; + voice->next = NULL; } // Load data to the specified operator @@ -498,14 +562,274 @@ static void I_OPL_SetMusicVolume(int volume) current_music_volume = volume; } +static opl_voice_t *FindVoiceForNote(opl_channel_data_t *channel, int note) +{ + unsigned int i; + + for (i=0; idata.channel.channel, + event->data.channel.param1, + event->data.channel.param2); + + channel = &track->channels[event->data.channel.channel]; + + // Find the voice being used to play the note. + + voice = FindVoiceForNote(channel, event->data.channel.param1); + + if (voice == NULL) + { + return; + } + + // Note off. + + WriteRegister(OPL_REGS_FREQ_2 + voice->index, voice->freq >> 8); + + // Finished with this voice now. + + ReleaseVoice(voice); +} + +// Given a MIDI note number, get the corresponding OPL +// frequency value to use. + +static unsigned int FrequencyForNote(unsigned int note) +{ + unsigned int octave; + unsigned int key_num; + + // The first seven frequencies in the frequencies array are used + // only for the first seven MIDI notes. After this, the frequency + // value loops around the same twelve notes, increasing the + // octave. + + if (note < 7) + { + return note_frequencies[note]; + } + else + { + octave = (note - 7) / 12; + key_num = (note - 7) % 12; + + return note_frequencies[key_num + 7] | (octave << 10); + } +} + +static void NoteOnEvent(opl_track_data_t *track, midi_event_t *event) +{ + opl_voice_t *voice; + opl_channel_data_t *channel; + + printf("note on: channel %i, %i, %i\n", + event->data.channel.channel, + event->data.channel.param1, + event->data.channel.param2); + + // The channel. + + channel = &track->channels[event->data.channel.channel]; + + // Find a voice to use for this new note. + + voice = GetFreeVoice(); + + if (voice == NULL) + { + return; + } + + // Program the voice with the instrument data: + + SetVoiceInstrument(voice, &channel->instrument->opl2_voice); + + // TODO: Set the volume level. + + WriteRegister(OPL_REGS_LEVEL + voice->op2, 0); + + // Play the note. + + voice->channel = channel; + voice->note = event->data.channel.param1; + + // Write the frequency value to turn the note on. + + voice->freq = FrequencyForNote(voice->note); + + WriteRegister(OPL_REGS_FREQ_1 + voice->index, voice->freq & 0xff); + WriteRegister(OPL_REGS_FREQ_2 + voice->index, (voice->freq >> 8) | 0x20); +} + +static void ProgramChangeEvent(opl_track_data_t *track, midi_event_t *event) +{ + int channel; + int instrument; + + // Set the instrument used on this channel. + + channel = event->data.channel.channel; + instrument = event->data.channel.param1; + track->channels[channel].instrument = &main_instrs[instrument]; + + // TODO: Look through existing voices that are turned on on this + // channel, and change the instrument. +} + +static void ControllerEvent(opl_track_data_t *track, midi_event_t *event) +{ + printf("change controller: channel %i, %i, %i\n", + event->data.channel.channel, + event->data.channel.param1, + event->data.channel.param2); + + // TODO: Volume, pan. +} + +// Process a MIDI event from a track. + +static void ProcessEvent(opl_track_data_t *track, midi_event_t *event) +{ + switch (event->event_type) + { + case MIDI_EVENT_NOTE_OFF: + NoteOffEvent(track, event); + break; + + case MIDI_EVENT_NOTE_ON: + NoteOnEvent(track, event); + break; + + case MIDI_EVENT_CONTROLLER: + ControllerEvent(track, event); + break; + + case MIDI_EVENT_PROGRAM_CHANGE: + ProgramChangeEvent(track, event); + break; + + default: + fprintf(stderr, "Unknown MIDI event type %i\n", event->event_type); + break; + } +} + +static void ScheduleTrack(opl_track_data_t *track); + +// Callback function invoked when another event needs to be read from +// a track. + +static void TrackTimerCallback(void *arg) +{ + opl_track_data_t *track = arg; + midi_event_t *event; + + // Get the next event and process it. + + if (!MIDI_GetNextEvent(track->iter, &event)) + { + return; + } + + ProcessEvent(track, event); + + // Reschedule the callback for the next event in the track. + + ScheduleTrack(track); +} + +static void ScheduleTrack(opl_track_data_t *track) +{ + unsigned int nticks; + unsigned int us; + static int total = 0; + + // Get the number of microseconds until the next event. + + nticks = MIDI_GetDeltaTime(track->iter); + us = (nticks * track->us_per_beat) / track->ticks_per_beat; + total += us; + + // Set a timer to be invoked when the next event is + // ready to play. + + OPL_SetCallback(us / 1000, TrackTimerCallback, track); +} + +// Initialise a channel. + +static void InitChannel(opl_track_data_t *track, opl_channel_data_t *channel) +{ + // TODO: Work out sensible defaults? + + channel->instrument = &main_instrs[0]; + channel->volume = 127; +} + +// Start a MIDI track playing: + +static void StartTrack(midi_file_t *file, unsigned int track_num) +{ + opl_track_data_t *track; + unsigned int i; + + track = &tracks[track_num]; + track->iter = MIDI_IterateTrack(file, track_num); + track->ticks_per_beat = MIDI_GetFileTimeDivision(file); + + // Default is 120 bpm. + // TODO: this is wrong + + track->us_per_beat = 500 * 1000 * 200; + + for (i=0; ichannels[i]); + } + + // Schedule the first event. + + ScheduleTrack(track); +} + // Start playing a mid static void I_OPL_PlaySong(void *handle, int looping) { - if (!music_initialised) + midi_file_t *file; + unsigned int i; + + if (!music_initialised || handle == NULL) { return; } + + file = handle; + + // Allocate track data. + + tracks = malloc(MIDI_NumTracks(file) * sizeof(opl_track_data_t)); + + for (i=0; i #include #include +#include #include "doomdef.h" #include "doomtype.h" @@ -62,6 +63,12 @@ typedef struct int num_events; } midi_track_t; +struct midi_track_iter_s +{ + midi_track_t *track; + unsigned int position; +}; + struct midi_file_s { midi_header_t header; @@ -626,6 +633,68 @@ midi_file_t *MIDI_LoadFile(char *filename) return file; } +// Get the number of tracks in a MIDI file. + +unsigned int MIDI_NumTracks(midi_file_t *file) +{ + return file->num_tracks; +} + +// Start iterating over the events in a track. + +midi_track_iter_t *MIDI_IterateTrack(midi_file_t *file, unsigned int track) +{ + midi_track_iter_t *iter; + + assert(track < file->num_tracks); + + iter = malloc(sizeof(*iter)); + iter->track = &file->tracks[track]; + iter->position = 0; + + return iter; +} + +// Get the time until the next MIDI event in a track. + +unsigned int MIDI_GetDeltaTime(midi_track_iter_t *iter) +{ + if (iter->position < iter->track->num_events) + { + midi_event_t *next_event; + + next_event = &iter->track->events[iter->position]; + + return next_event->delta_time; + } + else + { + return 0; + } +} + +// Get a pointer to the next MIDI event. + +int MIDI_GetNextEvent(midi_track_iter_t *iter, midi_event_t **event) +{ + if (iter->position < iter->track->num_events) + { + *event = &iter->track->events[iter->position]; + ++iter->position; + + return 1; + } + else + { + return 0; + } +} + +unsigned int MIDI_GetFileTimeDivision(midi_file_t *file) +{ + return file->header.time_division; +} + #ifdef TEST static char *MIDI_EventTypeToString(midi_event_type_t event_type) diff --git a/src/midifile.h b/src/midifile.h index f9acea35..a1ab4976 100644 --- a/src/midifile.h +++ b/src/midifile.h @@ -27,6 +27,9 @@ #define MIDIFILE_H typedef struct midi_file_s midi_file_t; +typedef struct midi_track_iter_s midi_track_iter_t; + +#define MIDI_CHANNELS_PER_TRACK 16 typedef enum { @@ -129,8 +132,33 @@ typedef struct } data; } midi_event_t; +// Load a MIDI file. + midi_file_t *MIDI_LoadFile(char *filename); + +// Free a MIDI file. + void MIDI_FreeFile(midi_file_t *file); +// Get the time division value from the MIDI header. + +unsigned int MIDI_GetFileTimeDivision(midi_file_t *file); + +// Get the number of tracks in a MIDI file. + +unsigned int MIDI_NumTracks(midi_file_t *file); + +// Start iterating over the events in a track. + +midi_track_iter_t *MIDI_IterateTrack(midi_file_t *file, unsigned int track_num); + +// Get the time until the next MIDI event in a track. + +unsigned int MIDI_GetDeltaTime(midi_track_iter_t *iter); + +// Get a pointer to the next MIDI event. + +int MIDI_GetNextEvent(midi_track_iter_t *iter, midi_event_t **event); + #endif /* #ifndef MIDIFILE_H */ -- cgit v1.2.3 From 1d045ed19c58abff063ac69db24a2e73d62ef530 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Sun, 30 Aug 2009 22:03:20 +0000 Subject: Initial, broken, volume level setting. Subversion-branch: /branches/opl-branch Subversion-revision: 1644 --- src/i_oplmusic.c | 48 ++++++++++++++++++++++++++++++++++++++++++++++-- src/midifile.h | 3 +++ 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/src/i_oplmusic.c b/src/i_oplmusic.c index 3d9a2649..7a619c5d 100644 --- a/src/i_oplmusic.c +++ b/src/i_oplmusic.c @@ -160,6 +160,27 @@ static const unsigned int note_frequencies[] = { 0x2da, 0x306, 0x334, 0x365, 0x398, 0x3cf, }; +// Mapping from MIDI volume level to OPL level value. + +static const unsigned int volume_mapping_table[] = { + 0x3f, 0x3f, 0x3e, 0x3d, 0x3c, 0x3b, 0x3a, 0x3a, + 0x39, 0x38, 0x37, 0x37, 0x36, 0x35, 0x34, 0x34, + 0x33, 0x32, 0x32, 0x31, 0x30, 0x2f, 0x2f, 0x2e, + 0x2d, 0x2d, 0x2c, 0x2b, 0x2a, 0x29, 0x28, 0x27, + 0x27, 0x26, 0x25, 0x24, 0x23, 0x22, 0x22, 0x21, + 0x20, 0x20, 0x1f, 0x1e, 0x1e, 0x1d, 0x1c, 0x1c, + 0x1b, 0x1b, 0x1a, 0x1a, 0x19, 0x18, 0x18, 0x17, + 0x17, 0x16, 0x16, 0x16, 0x16, 0x15, 0x15, 0x14, + 0x14, 0x13, 0x13, 0x12, 0x12, 0x12, 0x11, 0x11, + 0x10, 0x10, 0x10, 0x0f, 0x0f, 0x0f, 0x0e, 0x0e, + 0x0e, 0x0d, 0x0d, 0x0d, 0x0c, 0x0c, 0x0c, 0x0b, + 0x0b, 0x0b, 0x0a, 0x0a, 0x0a, 0x09, 0x09, 0x09, + 0x08, 0x08, 0x08, 0x08, 0x07, 0x07, 0x07, 0x07, + 0x06, 0x06, 0x06, 0x05, 0x05, 0x05, 0x05, 0x04, + 0x04, 0x04, 0x04, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, +}; + static boolean music_initialised = false; //static boolean musicpaused = false; @@ -662,7 +683,10 @@ static void NoteOnEvent(opl_track_data_t *track, midi_event_t *event) // TODO: Set the volume level. - WriteRegister(OPL_REGS_LEVEL + voice->op2, 0); + WriteRegister(OPL_REGS_LEVEL + voice->op2, + volume_mapping_table[channel->volume]); + + printf("volume = %i\n", channel->volume); // Play the note. @@ -694,12 +718,32 @@ static void ProgramChangeEvent(opl_track_data_t *track, midi_event_t *event) static void ControllerEvent(opl_track_data_t *track, midi_event_t *event) { + unsigned int controller; + unsigned int param; + opl_channel_data_t *channel; + printf("change controller: channel %i, %i, %i\n", event->data.channel.channel, event->data.channel.param1, event->data.channel.param2); - // TODO: Volume, pan. + channel = &track->channels[event->data.channel.channel]; + controller = event->data.channel.param1; + param = event->data.channel.param2; + + switch (controller) + { + case MIDI_CONTROLLER_MAIN_VOLUME: + channel->volume = param; + break; + + case MIDI_CONTROLLER_PAN: + break; + + default: + fprintf(stderr, "Unknown MIDI controller type: %i\n", controller); + break; + } } // Process a MIDI event from a track. diff --git a/src/midifile.h b/src/midifile.h index a1ab4976..faef549c 100644 --- a/src/midifile.h +++ b/src/midifile.h @@ -54,6 +54,9 @@ typedef enum MIDI_CONTROLLER_FOOT_CONTROL = 0x3, MIDI_CONTROLLER_PORTAMENTO = 0x4, MIDI_CONTROLLER_DATA_ENTRY = 0x5, + + MIDI_CONTROLLER_MAIN_VOLUME = 0x7, + MIDI_CONTROLLER_PAN = 0xa } midi_controller_t; typedef enum -- cgit v1.2.3 From 61b2e3d2b8b6b2b6ddf8b92842ac4bcf47945160 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Sun, 30 Aug 2009 22:26:30 +0000 Subject: Set the right instrument for percussion notes. Subversion-branch: /branches/opl-branch Subversion-revision: 1645 --- src/i_oplmusic.c | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/i_oplmusic.c b/src/i_oplmusic.c index 7a619c5d..d844fd7b 100644 --- a/src/i_oplmusic.c +++ b/src/i_oplmusic.c @@ -656,8 +656,10 @@ static unsigned int FrequencyForNote(unsigned int note) static void NoteOnEvent(opl_track_data_t *track, midi_event_t *event) { + genmidi_instr_t *instrument; opl_voice_t *voice; opl_channel_data_t *channel; + unsigned int note; printf("note on: channel %i, %i, %i\n", event->data.channel.channel, @@ -667,6 +669,23 @@ static void NoteOnEvent(opl_track_data_t *track, midi_event_t *event) // The channel. channel = &track->channels[event->data.channel.channel]; + note = event->data.channel.param1; + + // Percussion channel (10) is treated differently to normal notes. + + if (event->data.channel.channel == 9) + { + if (note < 35 || note > 81) + { + return; + } + + instrument = &percussion_instrs[note - 35]; + } + else + { + instrument = channel->instrument; + } // Find a voice to use for this new note. @@ -679,19 +698,17 @@ static void NoteOnEvent(opl_track_data_t *track, midi_event_t *event) // Program the voice with the instrument data: - SetVoiceInstrument(voice, &channel->instrument->opl2_voice); + SetVoiceInstrument(voice, &instrument->opl2_voice); // TODO: Set the volume level. WriteRegister(OPL_REGS_LEVEL + voice->op2, volume_mapping_table[channel->volume]); - printf("volume = %i\n", channel->volume); - // Play the note. voice->channel = channel; - voice->note = event->data.channel.param1; + voice->note = note; // Write the frequency value to turn the note on. @@ -840,7 +857,7 @@ static void StartTrack(midi_file_t *file, unsigned int track_num) // Default is 120 bpm. // TODO: this is wrong - track->us_per_beat = 500 * 1000 * 200; + track->us_per_beat = 500 * 1000 * 260; for (i=0; idata.channel.channel, @@ -670,6 +671,7 @@ static void NoteOnEvent(opl_track_data_t *track, midi_event_t *event) channel = &track->channels[event->data.channel.channel]; note = event->data.channel.param1; + volume = event->data.channel.param2; // Percussion channel (10) is treated differently to normal notes. @@ -703,7 +705,7 @@ static void NoteOnEvent(opl_track_data_t *track, midi_event_t *event) // TODO: Set the volume level. WriteRegister(OPL_REGS_LEVEL + voice->op2, - volume_mapping_table[channel->volume]); + volume_mapping_table[volume]); // Play the note. -- cgit v1.2.3 From ab14960e3a24208709270fb11476f0bbb629b145 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Sun, 30 Aug 2009 23:04:54 +0000 Subject: Make I_OPL_StopSong work. Subversion-branch: /branches/opl-branch Subversion-revision: 1647 --- src/i_oplmusic.c | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/src/i_oplmusic.c b/src/i_oplmusic.c index db7e0ffc..eff3f942 100644 --- a/src/i_oplmusic.c +++ b/src/i_oplmusic.c @@ -598,6 +598,11 @@ static opl_voice_t *FindVoiceForNote(opl_channel_data_t *channel, int note) return NULL; } +static void VoiceNoteOff(opl_voice_t *voice) +{ + WriteRegister(OPL_REGS_FREQ_2 + voice->index, voice->freq >> 8); +} + static void NoteOffEvent(opl_track_data_t *track, midi_event_t *event) { opl_voice_t *voice; @@ -619,9 +624,7 @@ static void NoteOffEvent(opl_track_data_t *track, midi_event_t *event) return; } - // Note off. - - WriteRegister(OPL_REGS_FREQ_2 + voice->index, voice->freq >> 8); + VoiceNoteOff(voice); // Finished with this voice now. @@ -913,10 +916,27 @@ static void I_OPL_ResumeSong(void) static void I_OPL_StopSong(void) { + unsigned int i; + if (!music_initialised) { return; } + + // Stop all playback. + + OPL_ClearCallbacks(); + + // Free all voices. + + for (i=0; iiter); - us = (nticks * track->us_per_beat) / track->ticks_per_beat; - total += us; + ms = (nticks * track->ms_per_beat) / track->ticks_per_beat; + total += ms; // Set a timer to be invoked when the next event is // ready to play. - OPL_SetCallback(us / 1000, TrackTimerCallback, track); + OPL_SetCallback(ms, TrackTimerCallback, track); } // Initialise a channel. @@ -862,7 +862,7 @@ static void StartTrack(midi_file_t *file, unsigned int track_num) // Default is 120 bpm. // TODO: this is wrong - track->us_per_beat = 500 * 1000 * 260; + track->ms_per_beat = 500 * 260; for (i=0; ichannel = channel; + voice->note = note; + // Program the voice with the instrument data: SetVoiceInstrument(voice, &instrument->opl2_voice); @@ -710,14 +713,16 @@ static void NoteOnEvent(opl_track_data_t *track, midi_event_t *event) WriteRegister(OPL_REGS_LEVEL + voice->op2, volume_mapping_table[volume]); - // Play the note. + // Fixed pitch? - voice->channel = channel; - voice->note = note; + if ((instrument->flags & GENMIDI_FLAG_FIXED) != 0) + { + note = instrument->fixed_note; + } // Write the frequency value to turn the note on. - voice->freq = FrequencyForNote(voice->note); + voice->freq = FrequencyForNote(note); WriteRegister(OPL_REGS_FREQ_1 + voice->index, voice->freq & 0xff); WriteRegister(OPL_REGS_FREQ_2 + voice->index, (voice->freq >> 8) | 0x20); -- cgit v1.2.3 From a43e60ee11f11fa8140b7b22da70f06b598fc49d Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Mon, 31 Aug 2009 17:32:22 +0000 Subject: Make channel volume work. Subversion-branch: /branches/opl-branch Subversion-revision: 1650 --- src/i_oplmusic.c | 108 +++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 86 insertions(+), 22 deletions(-) diff --git a/src/i_oplmusic.c b/src/i_oplmusic.c index 8a416f57..1d482595 100644 --- a/src/i_oplmusic.c +++ b/src/i_oplmusic.c @@ -133,6 +133,12 @@ struct opl_voice_s // The frequency value being used. unsigned int freq; + // The volume of the note being played on this channel. + unsigned int note_volume; + + // The current volume that has been set for this channel. + unsigned int volume; + // Next in freelist opl_voice_t *next; }; @@ -163,22 +169,22 @@ static const unsigned int note_frequencies[] = { // Mapping from MIDI volume level to OPL level value. static const unsigned int volume_mapping_table[] = { - 0x3f, 0x3f, 0x3e, 0x3d, 0x3c, 0x3b, 0x3a, 0x3a, - 0x39, 0x38, 0x37, 0x37, 0x36, 0x35, 0x34, 0x34, - 0x33, 0x32, 0x32, 0x31, 0x30, 0x2f, 0x2f, 0x2e, - 0x2d, 0x2d, 0x2c, 0x2b, 0x2a, 0x29, 0x28, 0x27, - 0x27, 0x26, 0x25, 0x24, 0x23, 0x22, 0x22, 0x21, - 0x20, 0x20, 0x1f, 0x1e, 0x1e, 0x1d, 0x1c, 0x1c, - 0x1b, 0x1b, 0x1a, 0x1a, 0x19, 0x18, 0x18, 0x17, - 0x17, 0x16, 0x16, 0x16, 0x16, 0x15, 0x15, 0x14, - 0x14, 0x13, 0x13, 0x12, 0x12, 0x12, 0x11, 0x11, - 0x10, 0x10, 0x10, 0x0f, 0x0f, 0x0f, 0x0e, 0x0e, - 0x0e, 0x0d, 0x0d, 0x0d, 0x0c, 0x0c, 0x0c, 0x0b, - 0x0b, 0x0b, 0x0a, 0x0a, 0x0a, 0x09, 0x09, 0x09, - 0x08, 0x08, 0x08, 0x08, 0x07, 0x07, 0x07, 0x07, - 0x06, 0x06, 0x06, 0x05, 0x05, 0x05, 0x05, 0x04, - 0x04, 0x04, 0x04, 0x03, 0x03, 0x03, 0x03, 0x03, - 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0, 1, 3, 5, 6, 8, 10, 11, + 13, 14, 16, 17, 19, 20, 22, 23, + 25, 26, 27, 29, 30, 32, 33, 34, + 36, 37, 39, 41, 43, 45, 47, 49, + 50, 52, 54, 55, 57, 59, 60, 61, + 63, 64, 66, 67, 68, 69, 71, 72, + 73, 74, 75, 76, 77, 79, 80, 81, + 82, 83, 84, 84, 85, 86, 87, 88, + 89, 90, 91, 92, 92, 93, 94, 95, + 96, 96, 97, 98, 99, 99, 100, 101, + 101, 102, 103, 103, 104, 105, 105, 106, + 107, 107, 108, 109, 109, 110, 110, 111, + 112, 112, 113, 113, 114, 114, 115, 115, + 116, 117, 117, 118, 118, 119, 119, 120, + 120, 121, 121, 122, 122, 123, 123, 123, + 124, 124, 125, 125, 126, 126, 127, 127 }; static boolean music_initialised = false; @@ -433,8 +439,13 @@ static void LoadOperatorData(int operator, genmidi_op_t *data, // Set the instrument for a particular voice. -static void SetVoiceInstrument(opl_voice_t *voice, genmidi_voice_t *data) +static void SetVoiceInstrument(opl_voice_t *voice, genmidi_instr_t *instr) { + genmidi_voice_t *data; + + voice->current_instr = instr; + data = &instr->opl2_voice; + // Doom loads the second operator first, then the first. LoadOperatorData(voice->op2, &data->carrier, true); @@ -446,6 +457,42 @@ static void SetVoiceInstrument(opl_voice_t *voice, genmidi_voice_t *data) WriteRegister(OPL_REGS_FEEDBACK + voice->index, data->feedback | 0x30); + + // Hack to force a volume update. + + voice->volume = 999; +} + +static void SetVoiceVolume(opl_voice_t *voice, unsigned int volume) +{ + unsigned int full_volume; + unsigned int instr_volume; + unsigned int reg_volume; + + voice->note_volume = volume; + + // Multiply note volume and channel volume to get the actual volume. + + full_volume = (voice->note_volume * voice->channel->volume) / 127; + + // The volume of each instrument can be controlled via GENMIDI: + + instr_volume = 0x3f - voice->current_instr->opl2_voice.carrier.level; + + // The volume value to use in the register: + + reg_volume = ((instr_volume * volume_mapping_table[full_volume]) / 128); + reg_volume = (0x3f - reg_volume) + | voice->current_instr->opl2_voice.carrier.scale; + + // Update the register, if necessary: + + if (voice->volume != reg_volume) + { + voice->volume = reg_volume; + + WriteRegister(OPL_REGS_LEVEL + voice->op2, reg_volume); + } } // Initialise the voice table and freelist @@ -698,6 +745,7 @@ static void NoteOnEvent(opl_track_data_t *track, midi_event_t *event) if (voice == NULL) { + printf("\tno free voice\n"); return; } @@ -706,12 +754,11 @@ static void NoteOnEvent(opl_track_data_t *track, midi_event_t *event) // Program the voice with the instrument data: - SetVoiceInstrument(voice, &instrument->opl2_voice); + SetVoiceInstrument(voice, instrument); - // TODO: Set the volume level. + // Set the volume level. - WriteRegister(OPL_REGS_LEVEL + voice->op2, - volume_mapping_table[volume]); + SetVoiceVolume(voice, volume); // Fixed pitch? @@ -743,6 +790,23 @@ static void ProgramChangeEvent(opl_track_data_t *track, midi_event_t *event) // channel, and change the instrument. } +static void SetChannelVolume(opl_channel_data_t *channel, unsigned int volume) +{ + unsigned int i; + + channel->volume = volume; + + // Update all voices that this channel is using. + + for (i=0; ivolume = param; + SetChannelVolume(channel, param); break; case MIDI_CONTROLLER_PAN: -- cgit v1.2.3 From a26925cf8f27eb38b3266629a3bc259f098b1e19 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Mon, 31 Aug 2009 18:18:51 +0000 Subject: Set the volume on both operators for instruments that use non-modulating voice mode. Subversion-branch: /branches/opl-branch Subversion-revision: 1651 --- src/i_oplmusic.c | 70 +++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 51 insertions(+), 19 deletions(-) diff --git a/src/i_oplmusic.c b/src/i_oplmusic.c index 1d482595..51a609d9 100644 --- a/src/i_oplmusic.c +++ b/src/i_oplmusic.c @@ -137,7 +137,8 @@ struct opl_voice_s unsigned int note_volume; // The current volume that has been set for this channel. - unsigned int volume; + unsigned int carrier_volume; + unsigned int modulator_volume; // Next in freelist opl_voice_t *next; @@ -442,14 +443,22 @@ static void LoadOperatorData(int operator, genmidi_op_t *data, static void SetVoiceInstrument(opl_voice_t *voice, genmidi_instr_t *instr) { genmidi_voice_t *data; + unsigned int modulating; voice->current_instr = instr; data = &instr->opl2_voice; + // Are we usind modulated feedback mode? + + modulating = (data->feedback & 0x01) == 0; + // Doom loads the second operator first, then the first. + // The carrier is set to minimum volume until the voice volume + // is set in SetVoiceVolume (below). If we are not using + // modulating mode, we must set both to minimum volume. LoadOperatorData(voice->op2, &data->carrier, true); - LoadOperatorData(voice->op1, &data->modulator, false); + LoadOperatorData(voice->op1, &data->modulator, !modulating); // Set feedback register that control the connection between the // two operators. Turn on bits in the upper nybble; I think this @@ -460,38 +469,61 @@ static void SetVoiceInstrument(opl_voice_t *voice, genmidi_instr_t *instr) // Hack to force a volume update. - voice->volume = 999; + voice->carrier_volume = 999; + voice->modulator_volume = 999; } -static void SetVoiceVolume(opl_voice_t *voice, unsigned int volume) +// Calculate the volume level to use for a given operator. + +static void SetOperatorVolume(genmidi_op_t *op, unsigned int volume, + unsigned int opnum, + unsigned int *current_volume) { - unsigned int full_volume; - unsigned int instr_volume; + unsigned int op_volume; unsigned int reg_volume; - voice->note_volume = volume; - - // Multiply note volume and channel volume to get the actual volume. - - full_volume = (voice->note_volume * voice->channel->volume) / 127; - // The volume of each instrument can be controlled via GENMIDI: - instr_volume = 0x3f - voice->current_instr->opl2_voice.carrier.level; + op_volume = 0x3f - op->level; // The volume value to use in the register: - reg_volume = ((instr_volume * volume_mapping_table[full_volume]) / 128); - reg_volume = (0x3f - reg_volume) - | voice->current_instr->opl2_voice.carrier.scale; + reg_volume = ((op_volume * volume_mapping_table[volume]) / 128); + reg_volume = (0x3f - reg_volume) | op->scale; // Update the register, if necessary: - if (voice->volume != reg_volume) + if (*current_volume != reg_volume) { - voice->volume = reg_volume; + *current_volume = reg_volume; + + WriteRegister(OPL_REGS_LEVEL + opnum, reg_volume); + } +} - WriteRegister(OPL_REGS_LEVEL + voice->op2, reg_volume); +static void SetVoiceVolume(opl_voice_t *voice, unsigned int volume) +{ + genmidi_voice_t *opl_voice; + unsigned int full_volume; + + voice->note_volume = volume; + + opl_voice = &voice->current_instr->opl2_voice; + + // Multiply note volume and channel volume to get the actual volume. + + full_volume = (voice->note_volume * voice->channel->volume) / 127; + + SetOperatorVolume(&opl_voice->carrier, full_volume, + voice->op2, &voice->carrier_volume); + + // If we are using non-modulated feedback mode, we must set the + // volume for both voices. + + if ((opl_voice->feedback & 0x01) != 0) + { + SetOperatorVolume(&opl_voice->modulator, full_volume, + voice->op1, &voice->modulator_volume); } } -- cgit v1.2.3 From fe96ae292d2b0e67020d77493ae366360cd93ec8 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Mon, 31 Aug 2009 18:33:36 +0000 Subject: Dont program an instrument if it is already set. Subversion-branch: /branches/opl-branch Subversion-revision: 1652 --- src/i_oplmusic.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/i_oplmusic.c b/src/i_oplmusic.c index 51a609d9..ab64505c 100644 --- a/src/i_oplmusic.c +++ b/src/i_oplmusic.c @@ -445,6 +445,13 @@ static void SetVoiceInstrument(opl_voice_t *voice, genmidi_instr_t *instr) genmidi_voice_t *data; unsigned int modulating; + // Instrument already set for this channel? + + if (voice->current_instr == instr) + { + return; + } + voice->current_instr = instr; data = &instr->opl2_voice; -- cgit v1.2.3 From eb2291030ae0f1e005a6014193fdfeaa796a913a Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Mon, 31 Aug 2009 22:50:28 +0000 Subject: Perform volume mapping on note and channel volumes before multiplying them. This gives voice volume values that are almost identical to Doom's. Subversion-branch: /branches/opl-branch Subversion-revision: 1653 --- src/i_oplmusic.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/i_oplmusic.c b/src/i_oplmusic.c index ab64505c..74a76b00 100644 --- a/src/i_oplmusic.c +++ b/src/i_oplmusic.c @@ -495,7 +495,7 @@ static void SetOperatorVolume(genmidi_op_t *op, unsigned int volume, // The volume value to use in the register: - reg_volume = ((op_volume * volume_mapping_table[volume]) / 128); + reg_volume = (op_volume * volume) / 128; reg_volume = (0x3f - reg_volume) | op->scale; // Update the register, if necessary: @@ -519,7 +519,8 @@ static void SetVoiceVolume(opl_voice_t *voice, unsigned int volume) // Multiply note volume and channel volume to get the actual volume. - full_volume = (voice->note_volume * voice->channel->volume) / 127; + full_volume = (volume_mapping_table[voice->note_volume] + * volume_mapping_table[voice->channel->volume]) / 127; SetOperatorVolume(&opl_voice->carrier, full_volume, voice->op2, &voice->carrier_volume); -- cgit v1.2.3 From 556f7291ea0199144794166af2757aa7ad832a7a Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Tue, 1 Sep 2009 18:17:11 +0000 Subject: Loop songs (when appropriate) Subversion-branch: /branches/opl-branch Subversion-revision: 1654 --- src/i_oplmusic.c | 93 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-- src/midifile.c | 10 ++++++ src/midifile.h | 8 +++++ 3 files changed, 108 insertions(+), 3 deletions(-) diff --git a/src/i_oplmusic.c b/src/i_oplmusic.c index 74a76b00..1ac502f5 100644 --- a/src/i_oplmusic.c +++ b/src/i_oplmusic.c @@ -206,6 +206,9 @@ static opl_voice_t *voice_free_list; // Track data for playing tracks: static opl_track_data_t *tracks; +static unsigned int num_tracks; +static unsigned int running_tracks = 0; +static boolean song_looping; // In the initialisation stage, register writes are spaced by reading // from the register port (0). After initialisation, spacing is @@ -868,11 +871,40 @@ static void ControllerEvent(opl_track_data_t *track, midi_event_t *event) SetChannelVolume(channel, param); break; - case MIDI_CONTROLLER_PAN: + default: + fprintf(stderr, "Unknown MIDI controller type: %i\n", controller); + break; + } +} + +// Process a meta event. + +static void MetaEvent(opl_track_data_t *track, midi_event_t *event) +{ + switch (event->data.meta.type) + { + // Things we can just ignore. + + case MIDI_META_SEQUENCE_NUMBER: + case MIDI_META_TEXT: + case MIDI_META_COPYRIGHT: + case MIDI_META_TRACK_NAME: + case MIDI_META_INSTR_NAME: + case MIDI_META_LYRICS: + case MIDI_META_MARKER: + case MIDI_META_CUE_POINT: + case MIDI_META_SEQUENCER_SPECIFIC: + break; + + // End of track - actually handled when we run out of events + // in the track, see below. + + case MIDI_META_END_OF_TRACK: break; default: - fprintf(stderr, "Unknown MIDI controller type: %i\n", controller); + fprintf(stderr, "Unknown MIDI meta event type: %i\n", + event->data.meta.type); break; } } @@ -899,6 +931,16 @@ static void ProcessEvent(opl_track_data_t *track, midi_event_t *event) ProgramChangeEvent(track, event); break; + case MIDI_EVENT_META: + MetaEvent(track, event); + break; + + // SysEx events can be ignored. + + case MIDI_EVENT_SYSEX: + case MIDI_EVENT_SYSEX_SPLIT: + break; + default: fprintf(stderr, "Unknown MIDI event type %i\n", event->event_type); break; @@ -907,6 +949,21 @@ static void ProcessEvent(opl_track_data_t *track, midi_event_t *event) static void ScheduleTrack(opl_track_data_t *track); +// Restart a song from the beginning. + +static void RestartSong(void) +{ + unsigned int i; + + running_tracks = num_tracks; + + for (i=0; ievent_type == MIDI_EVENT_META + && event->data.meta.type == MIDI_META_END_OF_TRACK) + { + --running_tracks; + + // When all tracks have finished, restart the song. + + if (running_tracks <= 0 && song_looping) + { + RestartSong(); + } + + return; + } + // Reschedule the callback for the next event in the track. ScheduleTrack(track); @@ -1001,7 +1075,11 @@ static void I_OPL_PlaySong(void *handle, int looping) tracks = malloc(MIDI_NumTracks(file) * sizeof(opl_track_data_t)); - for (i=0; iheader.time_division; } +void MIDI_RestartIterator(midi_track_iter_t *iter) +{ + iter->position = 0; +} + #ifdef TEST static char *MIDI_EventTypeToString(midi_event_type_t event_type) diff --git a/src/midifile.h b/src/midifile.h index faef549c..4ee0ddb2 100644 --- a/src/midifile.h +++ b/src/midifile.h @@ -155,6 +155,10 @@ unsigned int MIDI_NumTracks(midi_file_t *file); midi_track_iter_t *MIDI_IterateTrack(midi_file_t *file, unsigned int track_num); +// Free an iterator. + +void MIDI_FreeIterator(midi_track_iter_t *iter); + // Get the time until the next MIDI event in a track. unsigned int MIDI_GetDeltaTime(midi_track_iter_t *iter); @@ -163,5 +167,9 @@ unsigned int MIDI_GetDeltaTime(midi_track_iter_t *iter); int MIDI_GetNextEvent(midi_track_iter_t *iter, midi_event_t **event); +// Reset an iterator to the beginning of a track. + +void MIDI_RestartIterator(midi_track_iter_t *iter); + #endif /* #ifndef MIDIFILE_H */ -- cgit v1.2.3 From d372d65b3de579fe40207a308c3f7c4bf3076c0a Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Mon, 7 Sep 2009 18:11:25 +0000 Subject: Implement pitch bend. Subversion-branch: /branches/opl-branch Subversion-revision: 1658 --- src/i_oplmusic.c | 262 ++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 213 insertions(+), 49 deletions(-) diff --git a/src/i_oplmusic.c b/src/i_oplmusic.c index 1ac502f5..e9fdb395 100644 --- a/src/i_oplmusic.c +++ b/src/i_oplmusic.c @@ -91,6 +91,11 @@ typedef struct // Volume level int volume; + + // Pitch bend value: + + int bend; + } opl_channel_data_t; // Data associated with a track that is currently playing. @@ -127,7 +132,12 @@ struct opl_voice_s // The channel currently using this voice. opl_channel_data_t *channel; - // The note that this voice is playing. + // The midi key that this voice is playing. + unsigned int key; + + // The note being played. This is normally the same as + // the key, but if the instrument is a fixed pitch + // instrument, it is different. unsigned int note; // The frequency value being used. @@ -153,18 +163,120 @@ static const int voice_operators[2][OPL_NUM_VOICES] = { // Frequency values to use for each note. -static const unsigned int note_frequencies[] = { - - // These frequencies are only used for the first seven - // MIDI note values: - - 0x158, 0x16d, 0x183, 0x19a, 0x1b2, 0x1cc, 0x1e7, - - // These frequencies are used repeatedly, cycling around - // for each octave: - - 0x204, 0x223, 0x244, 0x266, 0x28b, 0x2b1, - 0x2da, 0x306, 0x334, 0x365, 0x398, 0x3cf, +static const unsigned short frequency_curve[] = { + + 0x133, 0x133, 0x134, 0x134, 0x135, 0x136, 0x136, 0x137, // -1 + 0x137, 0x138, 0x138, 0x139, 0x139, 0x13a, 0x13b, 0x13b, + 0x13c, 0x13c, 0x13d, 0x13d, 0x13e, 0x13f, 0x13f, 0x140, + 0x140, 0x141, 0x142, 0x142, 0x143, 0x143, 0x144, 0x144, + + 0x145, 0x146, 0x146, 0x147, 0x147, 0x148, 0x149, 0x149, // -2 + 0x14a, 0x14a, 0x14b, 0x14c, 0x14c, 0x14d, 0x14d, 0x14e, + 0x14f, 0x14f, 0x150, 0x150, 0x151, 0x152, 0x152, 0x153, + 0x153, 0x154, 0x155, 0x155, 0x156, 0x157, 0x157, 0x158, + + // These are used for the first seven MIDI note values: + + 0x158, 0x159, 0x15a, 0x15a, 0x15b, 0x15b, 0x15c, 0x15d, // 0 + 0x15d, 0x15e, 0x15f, 0x15f, 0x160, 0x161, 0x161, 0x162, + 0x162, 0x163, 0x164, 0x164, 0x165, 0x166, 0x166, 0x167, + 0x168, 0x168, 0x169, 0x16a, 0x16a, 0x16b, 0x16c, 0x16c, + + 0x16d, 0x16e, 0x16e, 0x16f, 0x170, 0x170, 0x171, 0x172, // 1 + 0x172, 0x173, 0x174, 0x174, 0x175, 0x176, 0x176, 0x177, + 0x178, 0x178, 0x179, 0x17a, 0x17a, 0x17b, 0x17c, 0x17c, + 0x17d, 0x17e, 0x17e, 0x17f, 0x180, 0x181, 0x181, 0x182, + + 0x183, 0x183, 0x184, 0x185, 0x185, 0x186, 0x187, 0x188, // 2 + 0x188, 0x189, 0x18a, 0x18a, 0x18b, 0x18c, 0x18d, 0x18d, + 0x18e, 0x18f, 0x18f, 0x190, 0x191, 0x192, 0x192, 0x193, + 0x194, 0x194, 0x195, 0x196, 0x197, 0x197, 0x198, 0x199, + + 0x19a, 0x19a, 0x19b, 0x19c, 0x19d, 0x19d, 0x19e, 0x19f, // 3 + 0x1a0, 0x1a0, 0x1a1, 0x1a2, 0x1a3, 0x1a3, 0x1a4, 0x1a5, + 0x1a6, 0x1a6, 0x1a7, 0x1a8, 0x1a9, 0x1a9, 0x1aa, 0x1ab, + 0x1ac, 0x1ad, 0x1ad, 0x1ae, 0x1af, 0x1b0, 0x1b0, 0x1b1, + + 0x1b2, 0x1b3, 0x1b4, 0x1b4, 0x1b5, 0x1b6, 0x1b7, 0x1b8, // 4 + 0x1b8, 0x1b9, 0x1ba, 0x1bb, 0x1bc, 0x1bc, 0x1bd, 0x1be, + 0x1bf, 0x1c0, 0x1c0, 0x1c1, 0x1c2, 0x1c3, 0x1c4, 0x1c4, + 0x1c5, 0x1c6, 0x1c7, 0x1c8, 0x1c9, 0x1c9, 0x1ca, 0x1cb, + + 0x1cc, 0x1cd, 0x1ce, 0x1ce, 0x1cf, 0x1d0, 0x1d1, 0x1d2, // 5 + 0x1d3, 0x1d3, 0x1d4, 0x1d5, 0x1d6, 0x1d7, 0x1d8, 0x1d8, + 0x1d9, 0x1da, 0x1db, 0x1dc, 0x1dd, 0x1de, 0x1de, 0x1df, + 0x1e0, 0x1e1, 0x1e2, 0x1e3, 0x1e4, 0x1e5, 0x1e5, 0x1e6, + + 0x1e7, 0x1e8, 0x1e9, 0x1ea, 0x1eb, 0x1ec, 0x1ed, 0x1ed, // 6 + 0x1ee, 0x1ef, 0x1f0, 0x1f1, 0x1f2, 0x1f3, 0x1f4, 0x1f5, + 0x1f6, 0x1f6, 0x1f7, 0x1f8, 0x1f9, 0x1fa, 0x1fb, 0x1fc, + 0x1fd, 0x1fe, 0x1ff, 0x200, 0x201, 0x201, 0x202, 0x203, + + // First note of looped range used for all octaves: + + 0x204, 0x205, 0x206, 0x207, 0x208, 0x209, 0x20a, 0x20b, // 7 + 0x20c, 0x20d, 0x20e, 0x20f, 0x210, 0x210, 0x211, 0x212, + 0x213, 0x214, 0x215, 0x216, 0x217, 0x218, 0x219, 0x21a, + 0x21b, 0x21c, 0x21d, 0x21e, 0x21f, 0x220, 0x221, 0x222, + + 0x223, 0x224, 0x225, 0x226, 0x227, 0x228, 0x229, 0x22a, // 8 + 0x22b, 0x22c, 0x22d, 0x22e, 0x22f, 0x230, 0x231, 0x232, + 0x233, 0x234, 0x235, 0x236, 0x237, 0x238, 0x239, 0x23a, + 0x23b, 0x23c, 0x23d, 0x23e, 0x23f, 0x240, 0x241, 0x242, + + 0x244, 0x245, 0x246, 0x247, 0x248, 0x249, 0x24a, 0x24b, // 9 + 0x24c, 0x24d, 0x24e, 0x24f, 0x250, 0x251, 0x252, 0x253, + 0x254, 0x256, 0x257, 0x258, 0x259, 0x25a, 0x25b, 0x25c, + 0x25d, 0x25e, 0x25f, 0x260, 0x262, 0x263, 0x264, 0x265, + + 0x266, 0x267, 0x268, 0x269, 0x26a, 0x26c, 0x26d, 0x26e, // 10 + 0x26f, 0x270, 0x271, 0x272, 0x273, 0x275, 0x276, 0x277, + 0x278, 0x279, 0x27a, 0x27b, 0x27d, 0x27e, 0x27f, 0x280, + 0x281, 0x282, 0x284, 0x285, 0x286, 0x287, 0x288, 0x289, + + 0x28b, 0x28c, 0x28d, 0x28e, 0x28f, 0x290, 0x292, 0x293, // 11 + 0x294, 0x295, 0x296, 0x298, 0x299, 0x29a, 0x29b, 0x29c, + 0x29e, 0x29f, 0x2a0, 0x2a1, 0x2a2, 0x2a4, 0x2a5, 0x2a6, + 0x2a7, 0x2a9, 0x2aa, 0x2ab, 0x2ac, 0x2ae, 0x2af, 0x2b0, + + 0x2b1, 0x2b2, 0x2b4, 0x2b5, 0x2b6, 0x2b7, 0x2b9, 0x2ba, // 12 + 0x2bb, 0x2bd, 0x2be, 0x2bf, 0x2c0, 0x2c2, 0x2c3, 0x2c4, + 0x2c5, 0x2c7, 0x2c8, 0x2c9, 0x2cb, 0x2cc, 0x2cd, 0x2ce, + 0x2d0, 0x2d1, 0x2d2, 0x2d4, 0x2d5, 0x2d6, 0x2d8, 0x2d9, + + 0x2da, 0x2dc, 0x2dd, 0x2de, 0x2e0, 0x2e1, 0x2e2, 0x2e4, // 13 + 0x2e5, 0x2e6, 0x2e8, 0x2e9, 0x2ea, 0x2ec, 0x2ed, 0x2ee, + 0x2f0, 0x2f1, 0x2f2, 0x2f4, 0x2f5, 0x2f6, 0x2f8, 0x2f9, + 0x2fb, 0x2fc, 0x2fd, 0x2ff, 0x300, 0x302, 0x303, 0x304, + + 0x306, 0x307, 0x309, 0x30a, 0x30b, 0x30d, 0x30e, 0x310, // 14 + 0x311, 0x312, 0x314, 0x315, 0x317, 0x318, 0x31a, 0x31b, + 0x31c, 0x31e, 0x31f, 0x321, 0x322, 0x324, 0x325, 0x327, + 0x328, 0x329, 0x32b, 0x32c, 0x32e, 0x32f, 0x331, 0x332, + + 0x334, 0x335, 0x337, 0x338, 0x33a, 0x33b, 0x33d, 0x33e, // 15 + 0x340, 0x341, 0x343, 0x344, 0x346, 0x347, 0x349, 0x34a, + 0x34c, 0x34d, 0x34f, 0x350, 0x352, 0x353, 0x355, 0x357, + 0x358, 0x35a, 0x35b, 0x35d, 0x35e, 0x360, 0x361, 0x363, + + 0x365, 0x366, 0x368, 0x369, 0x36b, 0x36c, 0x36e, 0x370, // 16 + 0x371, 0x373, 0x374, 0x376, 0x378, 0x379, 0x37b, 0x37c, + 0x37e, 0x380, 0x381, 0x383, 0x384, 0x386, 0x388, 0x389, + 0x38b, 0x38d, 0x38e, 0x390, 0x392, 0x393, 0x395, 0x397, + + 0x398, 0x39a, 0x39c, 0x39d, 0x39f, 0x3a1, 0x3a2, 0x3a4, // 17 + 0x3a6, 0x3a7, 0x3a9, 0x3ab, 0x3ac, 0x3ae, 0x3b0, 0x3b1, + 0x3b3, 0x3b5, 0x3b7, 0x3b8, 0x3ba, 0x3bc, 0x3bd, 0x3bf, + 0x3c1, 0x3c3, 0x3c4, 0x3c6, 0x3c8, 0x3ca, 0x3cb, 0x3cd, + + // The last note has an incomplete range, and loops round back to + // the start. Note that the last value is actually a buffer overrun + // and does not fit with the other values. + + 0x3cf, 0x3d1, 0x3d2, 0x3d4, 0x3d6, 0x3d8, 0x3da, 0x3db, // 18 + 0x3dd, 0x3df, 0x3e1, 0x3e3, 0x3e4, 0x3e6, 0x3e8, 0x3ea, + 0x3ec, 0x3ed, 0x3ef, 0x3f1, 0x3f3, 0x3f5, 0x3f6, 0x3f8, + 0x3fa, 0x3fc, 0x3fe, 0x36c, }; // Mapping from MIDI volume level to OPL level value. @@ -501,7 +613,7 @@ static void SetOperatorVolume(genmidi_op_t *op, unsigned int volume, reg_volume = (op_volume * volume) / 128; reg_volume = (0x3f - reg_volume) | op->scale; - // Update the register, if necessary: + // Update the register, if neccessary: if (*current_volume != reg_volume) { @@ -673,13 +785,13 @@ static void I_OPL_SetMusicVolume(int volume) current_music_volume = volume; } -static opl_voice_t *FindVoiceForNote(opl_channel_data_t *channel, int note) +static opl_voice_t *FindVoiceForKey(opl_channel_data_t *channel, int key) { unsigned int i; for (i=0; iindex, voice->freq >> 8); } +// Get the frequency that we should be using for a voice. + static void NoteOffEvent(opl_track_data_t *track, midi_event_t *event) { opl_voice_t *voice; @@ -707,7 +821,7 @@ static void NoteOffEvent(opl_track_data_t *track, midi_event_t *event) // Find the voice being used to play the note. - voice = FindVoiceForNote(channel, event->data.channel.param1); + voice = FindVoiceForKey(channel, event->data.channel.param1); if (voice == NULL) { @@ -721,29 +835,47 @@ static void NoteOffEvent(opl_track_data_t *track, midi_event_t *event) ReleaseVoice(voice); } -// Given a MIDI note number, get the corresponding OPL -// frequency value to use. - -static unsigned int FrequencyForNote(unsigned int note) +static unsigned int FrequencyForVoice(opl_voice_t *voice) { + unsigned int freq_index; unsigned int octave; - unsigned int key_num; + unsigned int sub_index; - // The first seven frequencies in the frequencies array are used - // only for the first seven MIDI notes. After this, the frequency - // value loops around the same twelve notes, increasing the - // octave. + freq_index = 64 + 32 * voice->note + voice->channel->bend; - if (note < 7) + // The first 7 notes use the start of the table, while + // consecutive notes loop around the latter part. + + if (freq_index < 284) { - return note_frequencies[note]; + return frequency_curve[freq_index]; } - else + + sub_index = (freq_index - 284) % (12 * 32); + octave = (freq_index - 284) / (12 * 32); + + // Calculate the resulting register value to use for the frequency. + + return frequency_curve[sub_index + 284] | (octave << 10); +} + +// Update the frequency that a voice is programmed to use. + +static void UpdateVoiceFrequency(opl_voice_t *voice) +{ + unsigned int freq; + + // Calculate the frequency to use for this voice and update it + // if neccessary. + + freq = FrequencyForVoice(voice); + + if (voice->freq != freq) { - octave = (note - 7) / 12; - key_num = (note - 7) % 12; + WriteRegister(OPL_REGS_FREQ_1 + voice->index, freq & 0xff); + WriteRegister(OPL_REGS_FREQ_2 + voice->index, (freq >> 8) | 0x20); - return note_frequencies[key_num + 7] | (octave << 10); + voice->freq = freq; } } @@ -752,7 +884,7 @@ static void NoteOnEvent(opl_track_data_t *track, midi_event_t *event) genmidi_instr_t *instrument; opl_voice_t *voice; opl_channel_data_t *channel; - unsigned int note; + unsigned int key; unsigned int volume; printf("note on: channel %i, %i, %i\n", @@ -763,19 +895,19 @@ static void NoteOnEvent(opl_track_data_t *track, midi_event_t *event) // The channel. channel = &track->channels[event->data.channel.channel]; - note = event->data.channel.param1; + key = event->data.channel.param1; volume = event->data.channel.param2; - // Percussion channel (10) is treated differently to normal notes. + // Percussion channel (10) is treated differently. if (event->data.channel.channel == 9) { - if (note < 35 || note > 81) + if (key < 35 || key > 81) { return; } - instrument = &percussion_instrs[note - 35]; + instrument = &percussion_instrs[key - 35]; } else { @@ -793,7 +925,19 @@ static void NoteOnEvent(opl_track_data_t *track, midi_event_t *event) } voice->channel = channel; - voice->note = note; + voice->key = key; + + // Work out the note to use. This is normally the same as + // the key, unless it is a fixed pitch instrument. + + if ((instrument->flags & GENMIDI_FLAG_FIXED) != 0) + { + voice->note = instrument->fixed_note; + } + else + { + voice->note = key; + } // Program the voice with the instrument data: @@ -803,19 +947,10 @@ static void NoteOnEvent(opl_track_data_t *track, midi_event_t *event) SetVoiceVolume(voice, volume); - // Fixed pitch? - - if ((instrument->flags & GENMIDI_FLAG_FIXED) != 0) - { - note = instrument->fixed_note; - } - // Write the frequency value to turn the note on. - voice->freq = FrequencyForNote(note); - - WriteRegister(OPL_REGS_FREQ_1 + voice->index, voice->freq & 0xff); - WriteRegister(OPL_REGS_FREQ_2 + voice->index, (voice->freq >> 8) | 0x20); + voice->freq = 0; + UpdateVoiceFrequency(voice); } static void ProgramChangeEvent(opl_track_data_t *track, midi_event_t *event) @@ -877,6 +1012,30 @@ static void ControllerEvent(opl_track_data_t *track, midi_event_t *event) } } +// Process a pitch bend event. + +static void PitchBendEvent(opl_track_data_t *track, midi_event_t *event) +{ + opl_channel_data_t *channel; + unsigned int i; + + // Update the channel bend value. Only the MSB of the pitch bend + // value is considered: this is what Doom does. + + channel = &track->channels[event->data.channel.channel]; + channel->bend = event->data.channel.param2 - 64; + + // Update all voices for this channel. + + for (i=0; iinstrument = &main_instrs[0]; channel->volume = 127; + channel->bend = 0; } // Start a MIDI track playing: -- cgit v1.2.3 From e97bc5db814b844ef2e4ee92f8a683031853dadb Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Tue, 8 Sep 2009 17:56:21 +0000 Subject: Program two voices for double voice instruments. Subversion-branch: /branches/opl-branch Subversion-revision: 1660 --- src/i_oplmusic.c | 164 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 91 insertions(+), 73 deletions(-) diff --git a/src/i_oplmusic.c b/src/i_oplmusic.c index e9fdb395..8ec47b80 100644 --- a/src/i_oplmusic.c +++ b/src/i_oplmusic.c @@ -49,7 +49,7 @@ #define GENMIDI_HEADER "#OPL_II#" #define GENMIDI_FLAG_FIXED 0x0001 /* fixed pitch */ -#define GENMIDI_FLAG_2VOICE 0x0002 /* double voice (OPL3) */ +#define GENMIDI_FLAG_2VOICE 0x0004 /* double voice (OPL3) */ typedef struct { @@ -76,8 +76,7 @@ typedef struct byte fine_tuning; byte fixed_note; - genmidi_voice_t opl2_voice; - genmidi_voice_t opl3_voice; + genmidi_voice_t voices[2]; } PACKEDATTR genmidi_instr_t; // Data associated with a channel of a track that is currently playing. @@ -129,6 +128,11 @@ struct opl_voice_s // Currently-loaded instrument data genmidi_instr_t *current_instr; + // The voice number in the instrument to use. + // This is normally set to zero; if this is a double voice + // instrument, it may be one. + unsigned int current_instr_voice; + // The channel currently using this voice. opl_channel_data_t *channel; @@ -555,20 +559,25 @@ static void LoadOperatorData(int operator, genmidi_op_t *data, // Set the instrument for a particular voice. -static void SetVoiceInstrument(opl_voice_t *voice, genmidi_instr_t *instr) +static void SetVoiceInstrument(opl_voice_t *voice, + genmidi_instr_t *instr, + unsigned int instr_voice) { genmidi_voice_t *data; unsigned int modulating; // Instrument already set for this channel? - if (voice->current_instr == instr) + if (voice->current_instr == instr + && voice->current_instr_voice == instr_voice) { return; } voice->current_instr = instr; - data = &instr->opl2_voice; + voice->current_instr_voice = instr_voice; + + data = &instr->voices[instr_voice]; // Are we usind modulated feedback mode? @@ -630,7 +639,7 @@ static void SetVoiceVolume(opl_voice_t *voice, unsigned int volume) voice->note_volume = volume; - opl_voice = &voice->current_instr->opl2_voice; + opl_voice = &voice->current_instr->voices[voice->current_instr_voice]; // Multiply note volume and channel volume to get the actual volume. @@ -765,7 +774,7 @@ static boolean I_OPL_InitMusic(void) voice = GetFreeVoice(); instr_num = rand() % 100; - SetVoiceInstrument(voice, &main_instrs[instr_num].opl2_voice); + SetVoiceInstrument(voice, &main_instrs[instr_num], 0); OPL_SetCallback(0, TestCallback, voice); } @@ -785,32 +794,18 @@ static void I_OPL_SetMusicVolume(int volume) current_music_volume = volume; } -static opl_voice_t *FindVoiceForKey(opl_channel_data_t *channel, int key) -{ - unsigned int i; - - for (i=0; iindex, voice->freq >> 8); } // Get the frequency that we should be using for a voice. -static void NoteOffEvent(opl_track_data_t *track, midi_event_t *event) +static void KeyOffEvent(opl_track_data_t *track, midi_event_t *event) { - opl_voice_t *voice; opl_channel_data_t *channel; + unsigned int key; + unsigned int i; printf("note off: channel %i, %i, %i\n", event->data.channel.channel, @@ -818,21 +813,22 @@ static void NoteOffEvent(opl_track_data_t *track, midi_event_t *event) event->data.channel.param2); channel = &track->channels[event->data.channel.channel]; + key = event->data.channel.param1; - // Find the voice being used to play the note. - - voice = FindVoiceForKey(channel, event->data.channel.param1); + // Turn off voices being used to play this key. + // If it is a double voice instrument there will be two. - if (voice == NULL) + for (i=0; idata.channel.channel, - event->data.channel.param1, - event->data.channel.param2); - - // The channel. - - channel = &track->channels[event->data.channel.channel]; - key = event->data.channel.param1; - volume = event->data.channel.param2; - - // Percussion channel (10) is treated differently. - - if (event->data.channel.channel == 9) - { - if (key < 35 || key > 81) - { - return; - } - - instrument = &percussion_instrs[key - 35]; - } - else - { - instrument = channel->instrument; - } // Find a voice to use for this new note. @@ -920,7 +893,7 @@ static void NoteOnEvent(opl_track_data_t *track, midi_event_t *event) if (voice == NULL) { - printf("\tno free voice\n"); + printf("\tno free voice for voice %i of instrument\n", instrument_voice); return; } @@ -941,7 +914,7 @@ static void NoteOnEvent(opl_track_data_t *track, midi_event_t *event) // Program the voice with the instrument data: - SetVoiceInstrument(voice, instrument); + SetVoiceInstrument(voice, instrument, 0); // Set the volume level. @@ -953,6 +926,51 @@ static void NoteOnEvent(opl_track_data_t *track, midi_event_t *event) UpdateVoiceFrequency(voice); } +static void KeyOnEvent(opl_track_data_t *track, midi_event_t *event) +{ + genmidi_instr_t *instrument; + opl_channel_data_t *channel; + unsigned int key; + unsigned int volume; + + printf("note on: channel %i, %i, %i\n", + event->data.channel.channel, + event->data.channel.param1, + event->data.channel.param2); + + // The channel. + + channel = &track->channels[event->data.channel.channel]; + key = event->data.channel.param1; + volume = event->data.channel.param2; + + // Percussion channel (10) is treated differently. + + if (event->data.channel.channel == 9) + { + if (key < 35 || key > 81) + { + return; + } + + instrument = &percussion_instrs[key - 35]; + } + else + { + instrument = channel->instrument; + } + + // Find and program a voice for this instrument. If this + // is a double voice instrument, we must do this twice. + + VoiceKeyOn(channel, instrument, 0, key, volume); + + if ((instrument->flags & GENMIDI_FLAG_2VOICE) != 0) + { + VoiceKeyOn(channel, instrument, 1, key, volume); + } +} + static void ProgramChangeEvent(opl_track_data_t *track, midi_event_t *event) { int channel; @@ -1075,11 +1093,11 @@ static void ProcessEvent(opl_track_data_t *track, midi_event_t *event) switch (event->event_type) { case MIDI_EVENT_NOTE_OFF: - NoteOffEvent(track, event); + KeyOffEvent(track, event); break; case MIDI_EVENT_NOTE_ON: - NoteOnEvent(track, event); + KeyOnEvent(track, event); break; case MIDI_EVENT_CONTROLLER: @@ -1284,7 +1302,7 @@ static void I_OPL_StopSong(void) { if (voices[i].channel != NULL) { - VoiceNoteOff(&voices[i]); + VoiceKeyOff(&voices[i]); ReleaseVoice(&voices[i]); } } -- cgit v1.2.3 From ba1accec2394c6ee70c5c79a42827099ef20e638 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Tue, 8 Sep 2009 18:12:10 +0000 Subject: Set the correct instrument voice, instead of using the first voice for both. Subversion-branch: /branches/opl-branch Subversion-revision: 1661 --- src/i_oplmusic.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i_oplmusic.c b/src/i_oplmusic.c index 8ec47b80..643f67c5 100644 --- a/src/i_oplmusic.c +++ b/src/i_oplmusic.c @@ -914,7 +914,7 @@ static void VoiceKeyOn(opl_channel_data_t *channel, // Program the voice with the instrument data: - SetVoiceInstrument(voice, instrument, 0); + SetVoiceInstrument(voice, instrument, instrument_voice); // Set the volume level. -- cgit v1.2.3 From cc0cc9fb0c806f6533dca794b1e829a626323d8b Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Fri, 11 Sep 2009 16:54:06 +0000 Subject: Implement the fine tuning field, based on research. Subversion-branch: /branches/opl-branch Subversion-revision: 1664 --- src/i_oplmusic.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/i_oplmusic.c b/src/i_oplmusic.c index 643f67c5..8cb833a2 100644 --- a/src/i_oplmusic.c +++ b/src/i_oplmusic.c @@ -839,6 +839,14 @@ static unsigned int FrequencyForVoice(opl_voice_t *voice) freq_index = 64 + 32 * voice->note + voice->channel->bend; + // If this is the second voice of a double voice instrument, the + // frequency index can be adjusted by the fine tuning field. + + if (voice->current_instr_voice != 0) + { + freq_index += (voice->current_instr->fine_tuning / 2) - 64; + } + // The first 7 notes use the start of the table, while // consecutive notes loop around the latter part. -- cgit v1.2.3 From a6a747e0ab96c45abe1a563f278f3ebc05fdec55 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Sat, 12 Sep 2009 17:22:58 +0000 Subject: Discard an existing voice when no voices are available (based on research with Vanilla). Subversion-branch: /branches/opl-branch Subversion-revision: 1668 --- src/i_oplmusic.c | 113 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 110 insertions(+), 3 deletions(-) diff --git a/src/i_oplmusic.c b/src/i_oplmusic.c index 8cb833a2..bed81109 100644 --- a/src/i_oplmusic.c +++ b/src/i_oplmusic.c @@ -154,7 +154,8 @@ struct opl_voice_s unsigned int carrier_volume; unsigned int modulator_volume; - // Next in freelist + // Next in linked list; a voice is always either in the + // free list or the allocated list. opl_voice_t *next; }; @@ -318,6 +319,7 @@ static genmidi_instr_t *percussion_instrs; static opl_voice_t voices[OPL_NUM_VOICES]; static opl_voice_t *voice_free_list; +static opl_voice_t *voice_alloced_list; // Track data for playing tracks: @@ -505,12 +507,42 @@ static opl_voice_t *GetFreeVoice(void) return NULL; } + // Remove from free list + result = voice_free_list; voice_free_list = voice_free_list->next; + // Add to allocated list + + result->next = voice_alloced_list; + voice_alloced_list = result; + return result; } +// Remove a voice from the allocated voices list. + +static void RemoveVoiceFromAllocedList(opl_voice_t *voice) +{ + opl_voice_t **rover; + + rover = &voice_alloced_list; + + // Search the list until we find the voice, then remove it. + + while (*rover != NULL) + { + if (*rover == voice) + { + *rover = voice->next; + voice->next = NULL; + break; + } + + rover = &(*rover)->next; + } +} + // Release a voice back to the freelist. static void ReleaseVoice(opl_voice_t *voice) @@ -520,6 +552,10 @@ static void ReleaseVoice(opl_voice_t *voice) voice->channel = NULL; voice->note = 0; + // Remove from alloced list. + + RemoveVoiceFromAllocedList(voice); + // Search to the end of the freelist (This is how Doom behaves!) rover = &voice_free_list; @@ -831,6 +867,66 @@ static void KeyOffEvent(opl_track_data_t *track, midi_event_t *event) } } +// When all voices are in use, we must discard an existing voice to +// play a new note. Find and free an existing voice. The channel +// passed to the function is the channel for the new note to be +// played. + +static opl_voice_t *ReplaceExistingVoice(opl_channel_data_t *channel) +{ + opl_voice_t *rover; + opl_voice_t *result; + + // Check the allocated voices, if we find an instrument that is + // of a lower priority to the new instrument, discard it. + // Priority is determined by MIDI instrument number; old + + result = NULL; + + for (rover = voice_alloced_list; rover != NULL; rover = rover->next) + { + if (rover->current_instr > channel->instrument) + { + result = rover; + break; + } + } + + // If we didn't find a voice, find an existing voice being used to + // play a note on the same channel, and use that. + + if (result == NULL) + { + for (rover = voice_alloced_list; rover != NULL; rover = rover->next) + { + if (rover->channel == channel) + { + result = rover; + break; + } + } + } + + // Still nothing found? Give up and just use the first voice in + // the list. + + if (result == NULL) + { + result = voice_alloced_list; + } + + // Stop playing this voice playing and release it back to the free + // list. + + VoiceKeyOff(result); + ReleaseVoice(result); + + // Re-allocate the voice again and return it. + + return GetFreeVoice(); +} + + static unsigned int FrequencyForVoice(opl_voice_t *voice) { unsigned int freq_index; @@ -899,10 +995,21 @@ static void VoiceKeyOn(opl_channel_data_t *channel, voice = GetFreeVoice(); + // If there are no more voices left, we must decide what to do. + // If this is the first voice of the instrument, free an existing + // voice and use that. Otherwise, if this is the second voice, + // it isn't as important; just discard it. + if (voice == NULL) { - printf("\tno free voice for voice %i of instrument\n", instrument_voice); - return; + if (instrument_voice == 0) + { + voice = ReplaceExistingVoice(channel); + } + else + { + return; + } } voice->channel = channel; -- cgit v1.2.3 From 6132a2a3e365ca36b7a1a7d721289763bac971dd Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Sat, 12 Sep 2009 21:13:08 +0000 Subject: Vanilla Doom plays d_introa, not d_intro. Subversion-branch: /branches/opl-branch Subversion-revision: 1670 --- src/d_main.c | 2 +- src/i_oplmusic.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/d_main.c b/src/d_main.c index c59a8fb7..46dd19ac 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -521,7 +521,7 @@ void D_DoAdvanceDemo (void) if ( gamemode == commercial ) S_StartMusic(mus_dm2ttl); else - S_StartMusic (mus_intro); + S_StartMusic (mus_introa); break; case 1: G_DeferedPlayDemo(DEH_String("demo1")); diff --git a/src/i_oplmusic.c b/src/i_oplmusic.c index bed81109..a6c03f6f 100644 --- a/src/i_oplmusic.c +++ b/src/i_oplmusic.c @@ -1513,7 +1513,7 @@ static void *I_OPL_RegisterSong(void *data, int len) // remove file now - remove(filename); +// remove(filename); Z_Free(filename); -- cgit v1.2.3 From 2b40842eeca7386f6bfe9b1b39760c8a4d69ca71 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Sat, 12 Sep 2009 23:04:57 +0000 Subject: Remove separate volume calculations for non-feedback (separate operator) voices. Doom writes the same value to both operators. Subversion-branch: /branches/opl-branch Subversion-revision: 1671 --- src/i_oplmusic.c | 69 ++++++++++++++++++++++++-------------------------------- 1 file changed, 29 insertions(+), 40 deletions(-) diff --git a/src/i_oplmusic.c b/src/i_oplmusic.c index a6c03f6f..ce255ff7 100644 --- a/src/i_oplmusic.c +++ b/src/i_oplmusic.c @@ -150,9 +150,8 @@ struct opl_voice_s // The volume of the note being played on this channel. unsigned int note_volume; - // The current volume that has been set for this channel. - unsigned int carrier_volume; - unsigned int modulator_volume; + // The current volume (register value) that has been set for this channel. + unsigned int reg_volume; // Next in linked list; a voice is always either in the // free list or the allocated list. @@ -636,42 +635,15 @@ static void SetVoiceInstrument(opl_voice_t *voice, // Hack to force a volume update. - voice->carrier_volume = 999; - voice->modulator_volume = 999; -} - -// Calculate the volume level to use for a given operator. - -static void SetOperatorVolume(genmidi_op_t *op, unsigned int volume, - unsigned int opnum, - unsigned int *current_volume) -{ - unsigned int op_volume; - unsigned int reg_volume; - - // The volume of each instrument can be controlled via GENMIDI: - - op_volume = 0x3f - op->level; - - // The volume value to use in the register: - - reg_volume = (op_volume * volume) / 128; - reg_volume = (0x3f - reg_volume) | op->scale; - - // Update the register, if neccessary: - - if (*current_volume != reg_volume) - { - *current_volume = reg_volume; - - WriteRegister(OPL_REGS_LEVEL + opnum, reg_volume); - } + voice->reg_volume = 999; } static void SetVoiceVolume(opl_voice_t *voice, unsigned int volume) { genmidi_voice_t *opl_voice; unsigned int full_volume; + unsigned int op_volume; + unsigned int reg_volume; voice->note_volume = volume; @@ -682,16 +654,33 @@ static void SetVoiceVolume(opl_voice_t *voice, unsigned int volume) full_volume = (volume_mapping_table[voice->note_volume] * volume_mapping_table[voice->channel->volume]) / 127; - SetOperatorVolume(&opl_voice->carrier, full_volume, - voice->op2, &voice->carrier_volume); + // The volume of each instrument can be controlled via GENMIDI: + + op_volume = 0x3f - opl_voice->carrier.level; - // If we are using non-modulated feedback mode, we must set the - // volume for both voices. + // The volume value to use in the register: - if ((opl_voice->feedback & 0x01) != 0) + reg_volume = (op_volume * full_volume) / 128; + reg_volume = (0x3f - reg_volume) | opl_voice->carrier.scale; + + // Update the volume register(s) if necessary. + + if (reg_volume != voice->reg_volume) { - SetOperatorVolume(&opl_voice->modulator, full_volume, - voice->op1, &voice->modulator_volume); + voice->reg_volume = reg_volume; + + WriteRegister(OPL_REGS_LEVEL + voice->op2, reg_volume); + + // If we are using non-modulated feedback mode, we must set the + // volume for both voices. + // Note that the same register volume value is written for + // both voices, always calculated from the carrier's level + // value. + + if ((opl_voice->feedback & 0x01) != 0) + { + WriteRegister(OPL_REGS_LEVEL + voice->op1, reg_volume); + } } } -- cgit v1.2.3 From 7e2eaa105e270586e81c04fdf61bcafd7c96a34c Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Sun, 13 Sep 2009 00:15:24 +0000 Subject: Change intro/introa fix to be more accurate: Doom uses d_intro, but transforms this to d_introa when using OPL playback (thanks entryway) Subversion-branch: /branches/opl-branch Subversion-revision: 1672 --- src/d_main.c | 2 +- src/s_sound.c | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/d_main.c b/src/d_main.c index 46dd19ac..c59a8fb7 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -521,7 +521,7 @@ void D_DoAdvanceDemo (void) if ( gamemode == commercial ) S_StartMusic(mus_dm2ttl); else - S_StartMusic (mus_introa); + S_StartMusic (mus_intro); break; case 1: G_DeferedPlayDemo(DEH_String("demo1")); diff --git a/src/s_sound.c b/src/s_sound.c index f038e9cd..9e70ec73 100644 --- a/src/s_sound.c +++ b/src/s_sound.c @@ -804,6 +804,15 @@ void S_ChangeMusic(int musicnum, int looping) char namebuf[9]; void *handle; + // The Doom IWAD file has two versions of the intro music: d_intro + // and d_introa. The latter is used for OPL playback. + + if (musicnum == mus_intro && (snd_musicdevice == SNDDEVICE_ADLIB + || snd_musicdevice == SNDDEVICE_SB)) + { + musicnum = mus_introa; + } + if (musicnum <= mus_None || musicnum >= NUMMUSIC) { I_Error("Bad music number %d", musicnum); -- cgit v1.2.3 From 47282d2ca9ae99fb7bfa27408158df0544ac11b3 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Thu, 17 Sep 2009 20:20:36 +0000 Subject: Add OPL TODO list. Subversion-branch: /branches/opl-branch Subversion-revision: 1675 --- OPL-TODO | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 OPL-TODO diff --git a/OPL-TODO b/OPL-TODO new file mode 100644 index 00000000..594a2325 --- /dev/null +++ b/OPL-TODO @@ -0,0 +1,22 @@ + +opl-branch TODO list + +Needs research: + + * Some instruments have pitch offset down by one octave (eg. #36) + - what determines this? + * Strategy when no more voices are available is still wrong + * Scale levels don't exactly match Vanilla (off-by-one) + +Bad MIDIs: + + * deca.wad MAP01 + * gothicdm MAP05 + * Hell Revealed + +Other tasks: + + * Menu volume control + * Pause music + * Add option to select MIDI type in setup tool + -- cgit v1.2.3 From 13314576f8d5d8eec8bf413ca4b49255b9e1a6a2 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Sat, 19 Sep 2009 20:42:45 +0000 Subject: Add octave offset table, offset notes on specific instruments down by one octave, as per research. Subversion-branch: /branches/opl-branch Subversion-revision: 1678 --- src/i_oplmusic.c | 50 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/src/i_oplmusic.c b/src/i_oplmusic.c index ce255ff7..09d2ae42 100644 --- a/src/i_oplmusic.c +++ b/src/i_oplmusic.c @@ -304,6 +304,31 @@ static const unsigned int volume_mapping_table[] = { 124, 124, 125, 125, 126, 126, 127, 127 }; +// For octave offset table: + +static const unsigned int octave_offset_table[2][128] = { + { + 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, // 0-15 + 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16-31 + 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, // 32-47 + 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, // 48-63 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, // 64-79 + 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, // 80-95 + 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, // 96-111 + 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, // 112-127 + }, + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0-15 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16-31 + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, // 32-47 + 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, // 48-63 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 64-79 + 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 80-95 + 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 96-111 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 112-127 + } +}; + static boolean music_initialised = false; //static boolean musicpaused = false; @@ -832,10 +857,12 @@ static void KeyOffEvent(opl_track_data_t *track, midi_event_t *event) unsigned int key; unsigned int i; +/* printf("note off: channel %i, %i, %i\n", event->data.channel.channel, event->data.channel.param1, event->data.channel.param2); +*/ channel = &track->channels[event->data.channel.channel]; key = event->data.channel.param1; @@ -921,8 +948,25 @@ static unsigned int FrequencyForVoice(opl_voice_t *voice) unsigned int freq_index; unsigned int octave; unsigned int sub_index; + unsigned int instr_num; + unsigned int note; + + note = voice->note; + + // What instrument number is this? + // Certain instruments have all notes offset down by one octave. + // Use the octave offset table to work out if this voice should + // be offset. + + instr_num = voice->current_instr - main_instrs; + + if (instr_num < 128 && note >= 12 + && octave_offset_table[voice->current_instr_voice][instr_num]) + { + note -= 12; + } - freq_index = 64 + 32 * voice->note + voice->channel->bend; + freq_index = 64 + 32 * note + voice->channel->bend; // If this is the second voice of a double voice instrument, the // frequency index can be adjusted by the fine tuning field. @@ -1037,10 +1081,12 @@ static void KeyOnEvent(opl_track_data_t *track, midi_event_t *event) unsigned int key; unsigned int volume; +/* printf("note on: channel %i, %i, %i\n", event->data.channel.channel, event->data.channel.param1, event->data.channel.param2); +*/ // The channel. @@ -1113,10 +1159,12 @@ static void ControllerEvent(opl_track_data_t *track, midi_event_t *event) unsigned int param; opl_channel_data_t *channel; +/* printf("change controller: channel %i, %i, %i\n", event->data.channel.channel, event->data.channel.param1, event->data.channel.param2); +*/ channel = &track->channels[event->data.channel.channel]; controller = event->data.channel.param1; -- cgit v1.2.3 From dd03cbbe2b688eac26011ac0ea0ada4047e5beb0 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Sun, 20 Sep 2009 20:47:17 +0000 Subject: Use the base note offset field to offset notes, not a fixed lookup table of instruments to offset. Subversion-branch: /branches/opl-branch Subversion-revision: 1680 --- src/i_oplmusic.c | 42 +++++------------------------------------- 1 file changed, 5 insertions(+), 37 deletions(-) diff --git a/src/i_oplmusic.c b/src/i_oplmusic.c index 09d2ae42..de126c63 100644 --- a/src/i_oplmusic.c +++ b/src/i_oplmusic.c @@ -34,6 +34,7 @@ #include "mus2mid.h" #include "deh_main.h" +#include "i_swap.h" #include "m_misc.h" #include "s_sound.h" #include "w_wad.h" @@ -304,31 +305,6 @@ static const unsigned int volume_mapping_table[] = { 124, 124, 125, 125, 126, 126, 127, 127 }; -// For octave offset table: - -static const unsigned int octave_offset_table[2][128] = { - { - 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, // 0-15 - 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16-31 - 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, // 32-47 - 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, // 48-63 - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, // 64-79 - 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, // 80-95 - 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, // 96-111 - 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, // 112-127 - }, - { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0-15 - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16-31 - 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, // 32-47 - 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, // 48-63 - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 64-79 - 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 80-95 - 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 96-111 - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 112-127 - } -}; - static boolean music_initialised = false; //static boolean musicpaused = false; @@ -945,26 +921,18 @@ static opl_voice_t *ReplaceExistingVoice(opl_channel_data_t *channel) static unsigned int FrequencyForVoice(opl_voice_t *voice) { + genmidi_voice_t *gm_voice; unsigned int freq_index; unsigned int octave; unsigned int sub_index; - unsigned int instr_num; unsigned int note; note = voice->note; - // What instrument number is this? - // Certain instruments have all notes offset down by one octave. - // Use the octave offset table to work out if this voice should - // be offset. - - instr_num = voice->current_instr - main_instrs; + // Apply note offset: - if (instr_num < 128 && note >= 12 - && octave_offset_table[voice->current_instr_voice][instr_num]) - { - note -= 12; - } + gm_voice = &voice->current_instr->voices[voice->current_instr_voice]; + note += (signed short) SHORT(gm_voice->base_note_offset); freq_index = 64 + 32 * note + voice->channel->bend; -- cgit v1.2.3 From b600e190fd4593faed8c14da776c991e99e8c86c Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Sun, 20 Sep 2009 20:52:03 +0000 Subject: Update OPL-TODO. Subversion-branch: /branches/opl-branch Subversion-revision: 1682 --- OPL-TODO | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/OPL-TODO b/OPL-TODO index 594a2325..8ca6a4b5 100644 --- a/OPL-TODO +++ b/OPL-TODO @@ -3,8 +3,6 @@ opl-branch TODO list Needs research: - * Some instruments have pitch offset down by one octave (eg. #36) - - what determines this? * Strategy when no more voices are available is still wrong * Scale levels don't exactly match Vanilla (off-by-one) @@ -12,6 +10,7 @@ Bad MIDIs: * deca.wad MAP01 * gothicdm MAP05 + * tnt.wad MAP30 * Hell Revealed Other tasks: -- cgit v1.2.3 From f894f24bcd63dc8b2a0604a9b659dd95aebbb384 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Sun, 20 Sep 2009 21:19:37 +0000 Subject: Avoid possible overflow due to base note offset. Subversion-branch: /branches/opl-branch Subversion-revision: 1683 --- src/i_oplmusic.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/i_oplmusic.c b/src/i_oplmusic.c index de126c63..aa079614 100644 --- a/src/i_oplmusic.c +++ b/src/i_oplmusic.c @@ -934,6 +934,13 @@ static unsigned int FrequencyForVoice(opl_voice_t *voice) gm_voice = &voice->current_instr->voices[voice->current_instr_voice]; note += (signed short) SHORT(gm_voice->base_note_offset); + // Avoid possible overflow due to base note offset: + + if (note > 0x7f) + { + note = voice->note; + } + freq_index = 64 + 32 * note + voice->channel->bend; // If this is the second voice of a double voice instrument, the -- cgit v1.2.3 From fcf4a7241a742e7f3d8189c40ad852d48c674612 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Sun, 20 Sep 2009 21:21:30 +0000 Subject: Remove old test code. Subversion-branch: /branches/opl-branch Subversion-revision: 1684 --- src/i_oplmusic.c | 47 ----------------------------------------------- 1 file changed, 47 deletions(-) diff --git a/src/i_oplmusic.c b/src/i_oplmusic.c index aa079614..b8e93534 100644 --- a/src/i_oplmusic.c +++ b/src/i_oplmusic.c @@ -43,8 +43,6 @@ #include "opl.h" #include "midifile.h" -//#define TEST - #define MAXMIDLENGTH (96 * 1024) #define GENMIDI_NUM_INSTRS 128 @@ -716,9 +714,6 @@ static void I_OPL_ShutdownMusic(void) { if (music_initialised) { -#ifdef TEST - InitRegisters(); -#endif OPL_Shutdown(); // Release GENMIDI lump @@ -729,30 +724,6 @@ static void I_OPL_ShutdownMusic(void) } } -#ifdef TEST -static void TestCallback(void *arg) -{ - opl_voice_t *voice = arg; - int note; - int wait_time; - - // Set level: - WriteRegister(OPL_REGS_LEVEL + voice->op2, 0); - - // Note off: - - WriteRegister(OPL_REGS_FREQ_2 + voice->index, 0x00); - // Note on: - - note = (rand() % (0x2ae - 0x16b)) + 0x16b; - WriteRegister(OPL_REGS_FREQ_1 + voice->index, note & 0xff); - WriteRegister(OPL_REGS_FREQ_2 + voice->index, 0x30 + (note >> 8)); - - wait_time = (rand() % 700) + 50; - OPL_SetCallback(wait_time, TestCallback, arg); -} -#endif - // Initialise music subsystem static boolean I_OPL_InitMusic(void) @@ -789,24 +760,6 @@ static boolean I_OPL_InitMusic(void) init_stage_reg_writes = false; -#ifdef TEST - { - int i; - opl_voice_t *voice; - int instr_num; - - for (i=0; i<3; ++i) - { - voice = GetFreeVoice(); - instr_num = rand() % 100; - - SetVoiceInstrument(voice, &main_instrs[instr_num], 0); - - OPL_SetCallback(0, TestCallback, voice); - } - } -#endif - music_initialised = true; return true; -- cgit v1.2.3 From 4b598f3d40e34b5d786c9236f891fbe2337c1b66 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Sun, 20 Sep 2009 21:25:59 +0000 Subject: Implement volume control. Subversion-branch: /branches/opl-branch Subversion-revision: 1685 --- OPL-TODO | 1 - src/i_oplmusic.c | 16 +++++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/OPL-TODO b/OPL-TODO index 8ca6a4b5..7b9be01c 100644 --- a/OPL-TODO +++ b/OPL-TODO @@ -15,7 +15,6 @@ Bad MIDIs: Other tasks: - * Menu volume control * Pause music * Add option to select MIDI type in setup tool diff --git a/src/i_oplmusic.c b/src/i_oplmusic.c index b8e93534..4ac52754 100644 --- a/src/i_oplmusic.c +++ b/src/i_oplmusic.c @@ -651,7 +651,8 @@ static void SetVoiceVolume(opl_voice_t *voice, unsigned int volume) // Multiply note volume and channel volume to get the actual volume. full_volume = (volume_mapping_table[voice->note_volume] - * volume_mapping_table[voice->channel->volume]) / 127; + * volume_mapping_table[voice->channel->volume] + * volume_mapping_table[current_music_volume]) / (127 * 127); // The volume of each instrument can be controlled via GENMIDI: @@ -769,8 +770,21 @@ static boolean I_OPL_InitMusic(void) static void I_OPL_SetMusicVolume(int volume) { + unsigned int i; + // Internal state variable. + current_music_volume = volume; + + // Update the volume of all voices. + + for (i=0; iset_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 */ diff --git a/src/i_oplmusic.c b/src/i_oplmusic.c index 4ac52754..7bfdc04d 100644 --- a/src/i_oplmusic.c +++ b/src/i_oplmusic.c @@ -1363,10 +1363,28 @@ static void I_OPL_PlaySong(void *handle, int looping) static void I_OPL_PauseSong(void) { + unsigned int i; + if (!music_initialised) { return; } + + // Pause OPL callbacks. + + OPL_SetPaused(1); + + // Turn off all main instrument voices (not percussion). + // This is what Vanilla does. + + for (i=0; i +#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 808b78153c3d3bfe1fb692106323731d17e49489 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Sat, 26 Sep 2009 20:01:58 +0000 Subject: Don't use snd_mport to control OPL base I/O port; Vanilla doesn't do this. Subversion-branch: /branches/opl-branch Subversion-revision: 1691 --- src/i_oplmusic.c | 4 ++-- src/m_config.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/i_oplmusic.c b/src/i_oplmusic.c index 7bfdc04d..dd62076d 100644 --- a/src/i_oplmusic.c +++ b/src/i_oplmusic.c @@ -336,7 +336,7 @@ static boolean init_stage_reg_writes = false; // Configuration file variable, containing the port number for the // adlib chip. -int snd_mport = 0x388; +int opl_io_port = 0x388; static unsigned int GetStatus(void) { @@ -729,7 +729,7 @@ static void I_OPL_ShutdownMusic(void) static boolean I_OPL_InitMusic(void) { - if (!OPL_Init(snd_mport)) + if (!OPL_Init(opl_io_port)) { return false; } diff --git a/src/m_config.c b/src/m_config.c index 0d0faea0..4f789845 100644 --- a/src/m_config.c +++ b/src/m_config.c @@ -184,7 +184,6 @@ extern int vanilla_demo_limit; extern int snd_musicdevice; extern int snd_sfxdevice; extern int snd_samplerate; -extern int snd_mport; // controls whether to use libsamplerate for sample rate conversions @@ -197,6 +196,7 @@ extern int use_libsamplerate; static int snd_sbport = 0; static int snd_sbirq = 0; static int snd_sbdma = 0; +static int snd_mport = 0; typedef enum { -- 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 --- configure.in | 2 +- opl/examples/droplay.c | 31 ------- opl/opl.c | 244 +++++++++++++++++++++++++++++++++++++++++++++++-- opl/opl.h | 31 ++++++- opl/opl_obsd.c | 2 +- src/i_oplmusic.c | 178 +++--------------------------------- 6 files changed, 282 insertions(+), 206 deletions(-) diff --git a/configure.in b/configure.in index 17ec9dc4..d8bad56e 100644 --- a/configure.in +++ b/configure.in @@ -70,7 +70,7 @@ AC_SDL_MAIN_WORKAROUND([ AC_CHECK_LIB(samplerate, src_new) AC_CHECK_HEADERS([linux/kd.h dev/isa/spkrio.h dev/speaker/speaker.h]) - AC_CHECK_FUNCS(mmap sched_setaffinity) + AC_CHECK_FUNCS(mmap sched_setaffinity ioperm) # OpenBSD I/O i386 library for I/O port access. 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 */ diff --git a/src/i_oplmusic.c b/src/i_oplmusic.c index dd62076d..e9e722e4 100644 --- a/src/i_oplmusic.c +++ b/src/i_oplmusic.c @@ -326,149 +326,11 @@ static unsigned int num_tracks; static unsigned int running_tracks = 0; static boolean song_looping; -// In the initialisation stage, register writes are spaced by reading -// from the register port (0). After initialisation, spacing is -// peformed by reading from the data port instead. I have no idea -// why. - -static boolean init_stage_reg_writes = false; - // Configuration file variable, containing the port number for the // adlib chip. int opl_io_port = 0x388; -static unsigned int GetStatus(void) -{ - return OPL_ReadPort(OPL_REGISTER_PORT); -} - -// Write an OPL register value - -static void 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 25 times after writing the value to - // cause the appropriate delay - - for (i=0; i<24; ++i) - { - GetStatus(); - } -} - -// Detect the presence of an OPL chip - -static boolean DetectOPL(void) -{ - int result1, result2; - int i; - - // Reset both timers: - WriteRegister(OPL_REG_TIMER_CTRL, 0x60); - - // Enable interrupts: - WriteRegister(OPL_REG_TIMER_CTRL, 0x80); - - // Read status - result1 = GetStatus(); - - // Set timer: - WriteRegister(OPL_REG_TIMER1, 0xff); - - // Start timer 1: - WriteRegister(OPL_REG_TIMER_CTRL, 0x21); - - // Wait for 80 microseconds - // This is how Doom does it: - - for (i=0; i<200; ++i) - { - GetStatus(); - } - - OPL_Delay(1); - - // Read status - result2 = GetStatus(); - - // Reset both timers: - WriteRegister(OPL_REG_TIMER_CTRL, 0x60); - - // Enable interrupts: - WriteRegister(OPL_REG_TIMER_CTRL, 0x80); - - return (result1 & 0xe0) == 0x00 - && (result2 & 0xe0) == 0xc0; -} - -// Initialise registers on startup - -static void InitRegisters(void) -{ - int r; - - // Initialise level registers - - for (r=OPL_REGS_LEVEL; r <= OPL_REGS_LEVEL + OPL_NUM_OPERATORS; ++r) - { - WriteRegister(r, 0x3f); - } - - // Initialise 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) - { - WriteRegister(r, 0x00); - } - - // More registers ... - - for (r=1; r < OPL_REGS_LEVEL; ++r) - { - WriteRegister(r, 0x00); - } - - // Re-initialise the low registers: - - // Reset both timers and enable interrupts: - WriteRegister(OPL_REG_TIMER_CTRL, 0x60); - WriteRegister(OPL_REG_TIMER_CTRL, 0x80); - - // "Allow FM chips to control the waveform of each operator": - WriteRegister(OPL_REG_WAVEFORM_ENABLE, 0x20); - - // Keyboard split point on (?) - WriteRegister(OPL_REG_FM_MODE, 0x40); -} - // Load instrument table from GENMIDI lump: static boolean LoadInstrumentTable(void) @@ -584,11 +446,11 @@ static void LoadOperatorData(int operator, genmidi_op_t *data, level |= 0x3f; } - WriteRegister(OPL_REGS_LEVEL + operator, level); - WriteRegister(OPL_REGS_TREMOLO + operator, data->tremolo); - WriteRegister(OPL_REGS_ATTACK + operator, data->attack); - WriteRegister(OPL_REGS_SUSTAIN + operator, data->sustain); - WriteRegister(OPL_REGS_WAVEFORM + operator, data->waveform); + OPL_WriteRegister(OPL_REGS_LEVEL + operator, level); + OPL_WriteRegister(OPL_REGS_TREMOLO + operator, data->tremolo); + OPL_WriteRegister(OPL_REGS_ATTACK + operator, data->attack); + OPL_WriteRegister(OPL_REGS_SUSTAIN + operator, data->sustain); + OPL_WriteRegister(OPL_REGS_WAVEFORM + operator, data->waveform); } // Set the instrument for a particular voice. @@ -629,8 +491,8 @@ static void SetVoiceInstrument(opl_voice_t *voice, // two operators. Turn on bits in the upper nybble; I think this // is for OPL3, where it turns on channel A/B. - WriteRegister(OPL_REGS_FEEDBACK + voice->index, - data->feedback | 0x30); + OPL_WriteRegister(OPL_REGS_FEEDBACK + voice->index, + data->feedback | 0x30); // Hack to force a volume update. @@ -669,7 +531,7 @@ static void SetVoiceVolume(opl_voice_t *voice, unsigned int volume) { voice->reg_volume = reg_volume; - WriteRegister(OPL_REGS_LEVEL + voice->op2, reg_volume); + OPL_WriteRegister(OPL_REGS_LEVEL + voice->op2, reg_volume); // If we are using non-modulated feedback mode, we must set the // volume for both voices. @@ -679,7 +541,7 @@ static void SetVoiceVolume(opl_voice_t *voice, unsigned int volume) if ((opl_voice->feedback & 0x01) != 0) { - WriteRegister(OPL_REGS_LEVEL + voice->op1, reg_volume); + OPL_WriteRegister(OPL_REGS_LEVEL + voice->op1, reg_volume); } } } @@ -730,18 +592,8 @@ static void I_OPL_ShutdownMusic(void) static boolean I_OPL_InitMusic(void) { if (!OPL_Init(opl_io_port)) - { - return false; - } - - init_stage_reg_writes = true; - - // Doom does the detection sequence twice, for some reason: - - if (!DetectOPL() || !DetectOPL()) { printf("Dude. The Adlib isn't responding.\n"); - OPL_Shutdown(); return false; } @@ -753,14 +605,8 @@ static boolean I_OPL_InitMusic(void) return false; } - InitRegisters(); InitVoices(); - // Now that initialisation has finished, switch the - // register writing mode: - - init_stage_reg_writes = false; - music_initialised = true; return true; @@ -789,7 +635,7 @@ static void I_OPL_SetMusicVolume(int volume) static void VoiceKeyOff(opl_voice_t *voice) { - WriteRegister(OPL_REGS_FREQ_2 + voice->index, voice->freq >> 8); + OPL_WriteRegister(OPL_REGS_FREQ_2 + voice->index, voice->freq >> 8); } // Get the frequency that we should be using for a voice. @@ -947,8 +793,8 @@ static void UpdateVoiceFrequency(opl_voice_t *voice) if (voice->freq != freq) { - WriteRegister(OPL_REGS_FREQ_1 + voice->index, freq & 0xff); - WriteRegister(OPL_REGS_FREQ_2 + voice->index, (freq >> 8) | 0x20); + OPL_WriteRegister(OPL_REGS_FREQ_1 + voice->index, freq & 0xff); + OPL_WriteRegister(OPL_REGS_FREQ_2 + voice->index, (freq >> 8) | 0x20); voice->freq = freq; } -- cgit v1.2.3 From 43f160000c540b129733cbea11a492d2598758e6 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Sun, 27 Sep 2009 00:08:19 +0000 Subject: Add documentation for OPL_DRIVER environment variable. Subversion-branch: /branches/opl-branch Subversion-revision: 1693 --- man/manpage.template | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/man/manpage.template b/man/manpage.template index 052ccb63..41f06dc0 100644 --- a/man/manpage.template +++ b/man/manpage.template @@ -28,6 +28,17 @@ specifies a PC speaker driver to use for sound effect playback. Valid options are "Linux" for the Linux console mode driver, "BSD" for the NetBSD/OpenBSD PC speaker driver, and "SDL" for SDL-based emulated PC speaker playback (using the digital output). +.TP +\fBOPL_DRIVER\fR +When using OPL MIDI playback, this environment variable specifies an +OPL backend driver to use. Valid options are "SDL" for an SDL-based +software emulated OPL chip, "Linux" for the Linux hardware OPL driver, +and "OpenBSD" for the OpenBSD/NetBSD hardware OPL driver. + +Generally speaking, a real hardware OPL chip sounds better than software +emulation; however, modern machines do not often include one. If +present, it may still require extra work to set up and elevated +security privileges to access. .SH FILES .TP \fB$HOME/.chocolate-doom/default.cfg\fR -- 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(-) 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 614351cebaaf2b1a372c78639d2ede7d1d7091fc Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Mon, 28 Sep 2009 20:44:20 +0000 Subject: Change music enable/disable control in setup tool to a dropdown list, to allow MIDI playback type to be selected. Subversion-branch: /branches/opl-branch Subversion-revision: 1695 --- OPL-TODO | 2 +- setup/sound.c | 74 ++++++++++++++++++++++++++++++++++++++++------------------- 2 files changed, 51 insertions(+), 25 deletions(-) diff --git a/OPL-TODO b/OPL-TODO index 3def4e36..39e5b131 100644 --- a/OPL-TODO +++ b/OPL-TODO @@ -8,6 +8,7 @@ Needs research: Bad MIDIs: + * doom2.wad MAP01 * deca.wad MAP01 * gothicdm MAP05 * tnt.wad MAP30 @@ -15,6 +16,5 @@ Bad MIDIs: Other tasks: - * Add option to select MIDI type in setup tool * DMXOPTIONS opl3/phase option support. diff --git a/setup/sound.c b/setup/sound.c index 72414c83..92a6cbae 100644 --- a/setup/sound.c +++ b/setup/sound.c @@ -49,6 +49,14 @@ typedef enum NUM_SFXMODES } sfxmode_t; +typedef enum +{ + MUSMODE_DISABLED, + MUSMODE_OPL, + MUSMODE_NATIVE, + NUM_MUSMODES +} musmode_t; + static char *sfxmode_strings[] = { "Disabled", @@ -56,14 +64,14 @@ static char *sfxmode_strings[] = "Digital", }; -// Disable MIDI music on OSX: there are problems with the native -// MIDI code in SDL_mixer. +static char *musmode_strings[] = +{ + "Disabled", + "OPL (Adlib/SB)", + "Native MIDI" +}; -#ifdef __MACOSX__ -#define DEFAULT_MUSIC_DEVICE SNDDEVICE_NONE -#else #define DEFAULT_MUSIC_DEVICE SNDDEVICE_SB -#endif int snd_sfxdevice = SNDDEVICE_SB; int numChannels = 8; @@ -77,7 +85,7 @@ int snd_samplerate = 22050; int use_libsamplerate = 0; static int snd_sfxmode; -static int snd_musicenabled; +static int snd_musmode; static void UpdateSndDevices(TXT_UNCAST_ARG(widget), TXT_UNCAST_ARG(data)) { @@ -93,14 +101,18 @@ static void UpdateSndDevices(TXT_UNCAST_ARG(widget), TXT_UNCAST_ARG(data)) snd_sfxdevice = SNDDEVICE_SB; break; } - - if (snd_musicenabled) - { - snd_musicdevice = SNDDEVICE_SB; - } - else + + switch (snd_musmode) { - snd_musicdevice = SNDDEVICE_NONE; + case MUSMODE_DISABLED: + snd_musicdevice = SNDDEVICE_NONE; + break; + case MUSMODE_OPL: + snd_musicdevice = SNDDEVICE_SB; + break; + case MUSMODE_NATIVE: + snd_musicdevice = SNDDEVICE_GENMIDI; + break; } } @@ -110,7 +122,7 @@ void ConfigSound(void) txt_table_t *sfx_table; txt_table_t *music_table; txt_dropdown_list_t *sfx_mode_control; - txt_checkbox_t *music_enabled_control; + txt_dropdown_list_t *mus_mode_control; if (snd_sfxdevice == SNDDEVICE_PCSPEAKER) { @@ -124,8 +136,21 @@ void ConfigSound(void) { snd_sfxmode = SFXMODE_DISABLED; } - - snd_musicenabled = snd_musicdevice != SNDDEVICE_NONE; + + if (snd_musicdevice == SNDDEVICE_GENMIDI) + { + snd_musmode = MUSMODE_NATIVE; + } + else if (snd_musicdevice == SNDDEVICE_SB + || snd_musicdevice == SNDDEVICE_ADLIB + || snd_musicdevice == SNDDEVICE_AWE32) + { + snd_musmode = MUSMODE_OPL; + } + else + { + snd_musmode = MUSMODE_DISABLED; + } window = TXT_NewWindow("Sound configuration"); @@ -133,12 +158,10 @@ void ConfigSound(void) TXT_NewSeparator("Sound effects"), sfx_table = TXT_NewTable(2), TXT_NewSeparator("Music"), - music_enabled_control = TXT_NewCheckBox("Music enabled", - &snd_musicenabled), music_table = TXT_NewTable(2), NULL); - TXT_SetColumnWidths(sfx_table, 20, 5); + TXT_SetColumnWidths(sfx_table, 20, 14); TXT_AddWidgets(sfx_table, TXT_NewLabel("Sound effects"), @@ -151,17 +174,20 @@ void ConfigSound(void) TXT_NewSpinControl(&sfxVolume, 0, 15), NULL); - TXT_SetColumnWidths(music_table, 20, 5); + TXT_SetColumnWidths(music_table, 20, 14); TXT_AddWidgets(music_table, + TXT_NewLabel("Music playback"), + mus_mode_control = TXT_NewDropdownList(&snd_musmode, + musmode_strings, + NUM_MUSMODES), TXT_NewLabel("Music volume"), TXT_NewSpinControl(&musicVolume, 0, 15), NULL); - TXT_SignalConnect(sfx_mode_control, "changed", + TXT_SignalConnect(sfx_mode_control, "changed", UpdateSndDevices, NULL); - TXT_SignalConnect(music_enabled_control, "changed", + TXT_SignalConnect(mus_mode_control, "changed", UpdateSndDevices, NULL); - } -- 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 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(+) 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 +++++++++--------- src/i_oplmusic.c | 30 +++++++++++++++--------------- 3 files changed, 26 insertions(+), 26 deletions(-) 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; } diff --git a/src/i_oplmusic.c b/src/i_oplmusic.c index e9e722e4..35ba046d 100644 --- a/src/i_oplmusic.c +++ b/src/i_oplmusic.c @@ -303,7 +303,7 @@ static const unsigned int volume_mapping_table[] = { 124, 124, 125, 125, 126, 126, 127, 127 }; -static boolean music_initialised = false; +static boolean music_initialized = false; //static boolean musicpaused = false; static int current_music_volume; @@ -546,7 +546,7 @@ static void SetVoiceVolume(opl_voice_t *voice, unsigned int volume) } } -// Initialise the voice table and freelist +// Initialize the voice table and freelist static void InitVoices(void) { @@ -556,7 +556,7 @@ static void InitVoices(void) voice_free_list = NULL; - // Initialise each voice. + // Initialize each voice. for (i=0; i 0; +} + +// Shutdown music + +static void I_OPL_ShutdownMusic(void) +{ + if (music_initialized) + { + // Stop currently-playing track, if there is one: + + I_OPL_StopSong(); + + OPL_Shutdown(); + + // Release GENMIDI lump + + W_ReleaseLumpName("GENMIDI"); + + music_initialized = false; + } +} + +// Initialize music subsystem + +static boolean I_OPL_InitMusic(void) +{ + if (!OPL_Init(opl_io_port)) + { + printf("Dude. The Adlib isn't responding.\n"); + return false; + } + + // Load instruments from GENMIDI lump: + + if (!LoadInstrumentTable()) + { + OPL_Shutdown(); + return false; + } + + InitVoices(); + + tracks = NULL; + num_tracks = 0; + music_initialized = true; + + return true; } static snddevice_t music_opl_devices[] = -- 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(-) 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 63788d629ee842495836bca69bf68fe16e807ad1 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Sun, 4 Oct 2009 16:45:32 +0000 Subject: Add README file for setting up hardware OPL. Subversion-branch: /branches/opl-branch Subversion-revision: 1708 --- README.OPL | 100 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 README.OPL diff --git a/README.OPL b/README.OPL new file mode 100644 index 00000000..2b837c4c --- /dev/null +++ b/README.OPL @@ -0,0 +1,100 @@ +== Chocolate Doom hardware OPL support notes == + +Chocolate Doom is able to play MIDI music as it sounds in Vanilla Doom +with an OPL chip (as found in the Yamaha Adlib card, the Sound Blaster +and its clones). Most modern computers do not include an OPL chip any +more, as CPUs are fast enough to do decent software MIDI synthesis. +For this reason, a software OPL emulator is included as a substitute. + +However, no software emulator sounds exactly like a real (hardware) +OPL chip, so if you do have a sound card with hardware OPL, here's how +to configure Chocolate Doom to use it. + +=== Sound cards with OPL chips === + +If you have an ISA sound card, it almost certainly includes an OPL +chip. Modern computers don't have slots for ISA cards though, so you +must be running a pretty old machine. + +If you have a PCI sound card, you probably don't have an OPL chip. +However, there are some exceptions to this. The following cards are +known to include "legacy" OPL support: + + * C-Media CMI8738 (*) + * Forte Media FM801 + * Cards based on the Yamaha YMF724 (*) + +Other cards that apparently have OPL support but have not been tested: + + * S3 SonicVibes + * AZTech PCI 168 (AZT 3328 chipset) + * ESS Solo-1 sound cards (ES1938, ES1946, ES1969 chipset) + * Conexant Riptide Audio/Modem combo cards + * Cards based on the Crystal Semiconductors CS4281 + * Cards based on the Avance Logic ALS300 + * Cards based on the Avance Logic ALS4000 + +If you desperately want hardware OPL music, you may be able to find +one of these cards for sale cheap on eBay. + +For the cards listed above with (*) next to them, OPL support is +disabled by default and must be explictly enabled in software. + +If your machine is not a PC, you don't have an OPL chip, and you will +have to use the software OPL. + +=== Operating System support === + +If you're certain that you have a sound card with hardware OPL, you +may need to take extra steps to configure your operating system to +allow access to it. To do hardware OPL, Chocolate Doom must access +the chip directly, which is usually not possible in modern operating +systems unless you are running as the superuser (root/Administrator). + +=== Windows 9x === + +If you're running Windows 95, 98 or Me, there is no need to configure +anything. Windows allows direct access to the OPL chip. You can +confirm that hardware OPL is working by checking for this message in +stdout.txt: + + OPL_Init: Using driver 'Win9x'. + +=== Windows NT (including 2000, XP and later) === + +If you're running an NT-based system, it is not possible to directly +access the OPL chip, even when running as Administrator. Fortunately, +it is possible to use the third-party "PortTalk" driver: + + http://www.beyondlogic.org/porttalk/porttalk.htm + +TODO - the NT driver hasn't actually been written yet.. + +=== Linux === + +If you are using a system based on the Linux kernel, you can access +the OPL chip directly, but you must be running as root. You can +confirm that hardware OPL is working, by checking for this message on +startup: + + OPL_Init: Using driver 'Linux'. + +If you are using one of the PCI cards in the list above with a (*) +next to it, you may need to manually enable FM legacy support. Add +the following to your /etc/modprobe.conf file to do this: + + options snd-ymfpci fm_port=0x388 + options snd-cmipci fm_port=0x388 + +=== OpenBSD/NetBSD === + +You must be running as root to access the hardware OPL directly. You +can confirm that hadware OPL is working by checking for this message +on startup: + + OPL_Init: Using driver 'OpenBSD'. + +=== FreeBSD === + +There is no native OPL backend for FreeBSD yet. Sorry! + -- cgit v1.2.3 From c70eec36650e81025da97b2d716d446943cf5818 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Sat, 10 Oct 2009 22:23:24 +0000 Subject: Add README.OPL to dist. Subversion-branch: /branches/opl-branch Subversion-revision: 1713 --- Makefile.am | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile.am b/Makefile.am index 991bf198..01c3c2e2 100644 --- a/Makefile.am +++ b/Makefile.am @@ -38,6 +38,7 @@ EXTRA_DIST= \ config.h \ CMDLINE \ HACKING \ + README.OPL \ TODO \ BUGS -- cgit v1.2.3 From 56a6af426972ed2a334ae7001d19854aca964a75 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Sun, 11 Oct 2009 21:47:07 +0000 Subject: Don't apply base note offset if the instrument is a fixed note instrument. This should stop the ugly bleeping from the electric snare on E1M2. Subversion-branch: /branches/opl-branch Subversion-revision: 1715 --- src/i_oplmusic.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/i_oplmusic.c b/src/i_oplmusic.c index cd331897..d32a163e 100644 --- a/src/i_oplmusic.c +++ b/src/i_oplmusic.c @@ -701,10 +701,15 @@ static unsigned int FrequencyForVoice(opl_voice_t *voice) note = voice->note; - // Apply note offset: + // Apply note offset. + // Don't apply offset if the instrument is a fixed note instrument. gm_voice = &voice->current_instr->voices[voice->current_instr_voice]; - note += (signed short) SHORT(gm_voice->base_note_offset); + + if ((voice->current_instr->flags & GENMIDI_FLAG_FIXED) == 0) + { + note += (signed short) SHORT(gm_voice->base_note_offset); + } // Avoid possible overflow due to base note offset: -- cgit v1.2.3 From 58f71d21b021bddcb24ac2b2b9736bf821221bd1 Mon Sep 17 00:00:00 2001 From: Russell Rice Date: Mon, 12 Oct 2009 23:43:08 +0000 Subject: - Add opl win9x driver to codeblocks project Subversion-branch: /branches/opl-branch Subversion-revision: 1716 --- codeblocks/game.cbp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/codeblocks/game.cbp b/codeblocks/game.cbp index ab6350eb..374936dc 100644 --- a/codeblocks/game.cbp +++ b/codeblocks/game.cbp @@ -79,6 +79,9 @@ + + -- 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 +++++++++++++++---- src/i_oplmusic.c | 2 ++ 5 files changed, 34 insertions(+), 4 deletions(-) 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()); diff --git a/src/i_oplmusic.c b/src/i_oplmusic.c index d32a163e..474877d4 100644 --- a/src/i_oplmusic.c +++ b/src/i_oplmusic.c @@ -1372,6 +1372,8 @@ static void I_OPL_ShutdownMusic(void) static boolean I_OPL_InitMusic(void) { + OPL_SetSampleRate(snd_samplerate); + if (!OPL_Init(opl_io_port)) { printf("Dude. The Adlib isn't responding.\n"); -- 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(-) 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 14bcdd1008db070bc4a12be73e3f692c1990966e Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Sun, 25 Oct 2009 17:07:55 +0000 Subject: Emulate odd octave 7 behavior of Vanilla Doom. Subversion-branch: /branches/opl-branch Subversion-revision: 1725 --- src/i_oplmusic.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/i_oplmusic.c b/src/i_oplmusic.c index 474877d4..89e73f1a 100644 --- a/src/i_oplmusic.c +++ b/src/i_oplmusic.c @@ -739,6 +739,26 @@ static unsigned int FrequencyForVoice(opl_voice_t *voice) sub_index = (freq_index - 284) % (12 * 32); octave = (freq_index - 284) / (12 * 32); + // Once the seventh octave is reached, things break down. + // We can only go up to octave 7 as a maximum anyway (the OPL + // register only has three bits for octave number), but for the + // notes in octave 7, the first five bits have octave=7, the + // following notes have octave=6. This 7/6 pattern repeats in + // following octaves (which are technically impossible to + // represent anyway). + + if (octave >= 7) + { + if (sub_index < 5) + { + octave = 7; + } + else + { + octave = 6; + } + } + // Calculate the resulting register value to use for the frequency. return frequency_curve[sub_index + 284] | (octave << 10); -- cgit v1.2.3 From 299e1c5abf804e2e249c4f77b82fb7949a3f9d7b Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Sat, 31 Oct 2009 16:47:49 +0000 Subject: When replacing an existing voice, discard voices that are the second voice of a two voice instrument. Don't discard instruments from lower numbered MIDI channels for higher numbered MIDI channels. Subversion-branch: /branches/opl-branch Subversion-revision: 1727 --- OPL-TODO | 1 - src/i_oplmusic.c | 20 ++++++++++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/OPL-TODO b/OPL-TODO index 39e5b131..57d315a0 100644 --- a/OPL-TODO +++ b/OPL-TODO @@ -9,7 +9,6 @@ Needs research: Bad MIDIs: * doom2.wad MAP01 - * deca.wad MAP01 * gothicdm MAP05 * tnt.wad MAP30 * Alien Vendetta (title screen, MAP01, etc) diff --git a/src/i_oplmusic.c b/src/i_oplmusic.c index 89e73f1a..9501d51d 100644 --- a/src/i_oplmusic.c +++ b/src/i_oplmusic.c @@ -631,6 +631,16 @@ static void KeyOffEvent(opl_track_data_t *track, midi_event_t *event) } } +// Compare the priorities of channels, returning either -1, 0 or 1. + +static int CompareChannelPriorities(opl_channel_data_t *chan1, + opl_channel_data_t *chan2) +{ + // TODO ... + + return 1; +} + // When all voices are in use, we must discard an existing voice to // play a new note. Find and free an existing voice. The channel // passed to the function is the channel for the new note to be @@ -643,13 +653,19 @@ static opl_voice_t *ReplaceExistingVoice(opl_channel_data_t *channel) // Check the allocated voices, if we find an instrument that is // of a lower priority to the new instrument, discard it. - // Priority is determined by MIDI instrument number; old + // If a voice is being used to play the second voice of an instrument, + // use that, as second voices are non-essential. + // Lower numbered MIDI channels implicitly have a higher priority + // than higher-numbered channels, eg. MIDI channel 1 is never + // discarded for MIDI channel 2. result = NULL; for (rover = voice_alloced_list; rover != NULL; rover = rover->next) { - if (rover->current_instr > channel->instrument) + if (rover->current_instr_voice != 0 + || (rover->channel > channel + && CompareChannelPriorities(channel, rover->channel) > 0)) { result = rover; break; -- cgit v1.2.3 From b42f8f4ff9a282c61c7f578bea13c84e845675c3 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Fri, 5 Feb 2010 03:23:33 +0000 Subject: Add README.OPL to list of documentation files to distribute. Subversion-branch: /branches/opl-branch Subversion-revision: 1842 --- pkg/config.make.in | 1 + rpm.spec.in | 1 + 2 files changed, 2 insertions(+) diff --git a/pkg/config.make.in b/pkg/config.make.in index d8d72c60..6bda7471 100644 --- a/pkg/config.make.in +++ b/pkg/config.make.in @@ -19,6 +19,7 @@ PACKAGE_VERSION = @PACKAGE_VERSION@ # Documentation files to distribute with packages. DOC_FILES = README \ + README.OPL \ COPYING \ ChangeLog \ NEWS \ diff --git a/rpm.spec.in b/rpm.spec.in index 1b7e90c7..58142c1b 100644 --- a/rpm.spec.in +++ b/rpm.spec.in @@ -52,6 +52,7 @@ rm -rf $RPM_BUILD_ROOT %doc NEWS %doc AUTHORS %doc README +%doc README.OPL %doc COPYING %doc CMDLINE %doc BUGS -- cgit v1.2.3 From ff7fff78ec179f401a60b839eb522e08a9f5b5ca Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Fri, 5 Feb 2010 03:27:14 +0000 Subject: Only distribute README.OPL in the Windows package, don't bother including it in the Mac OS X package. Subversion-branch: /branches/opl-branch Subversion-revision: 1843 --- pkg/config.make.in | 1 - pkg/win32/GNUmakefile | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/config.make.in b/pkg/config.make.in index 6bda7471..d8d72c60 100644 --- a/pkg/config.make.in +++ b/pkg/config.make.in @@ -19,7 +19,6 @@ PACKAGE_VERSION = @PACKAGE_VERSION@ # Documentation files to distribute with packages. DOC_FILES = README \ - README.OPL \ COPYING \ ChangeLog \ NEWS \ diff --git a/pkg/win32/GNUmakefile b/pkg/win32/GNUmakefile index 626f1845..31968795 100644 --- a/pkg/win32/GNUmakefile +++ b/pkg/win32/GNUmakefile @@ -11,6 +11,8 @@ DLL_FILES=$(TOPLEVEL)/src/SDL.dll \ $(TOPLEVEL)/src/SDL_mixer.dll \ $(TOPLEVEL)/src/SDL_net.dll +DOC_FILES += README.OPL + ZIP=$(PACKAGE_TARNAME)-$(PACKAGE_VERSION)-win32.zip $(ZIP) : staging -- 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(+) 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-TODO | 1 + README.OPL | 8 +- 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 ------------------------------ 8 files changed, 480 insertions(+), 146 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 diff --git a/OPL-TODO b/OPL-TODO index 57d315a0..6252bc2a 100644 --- a/OPL-TODO +++ b/OPL-TODO @@ -15,5 +15,6 @@ Bad MIDIs: Other tasks: + * Get a better software OPL emulator * DMXOPTIONS opl3/phase option support. diff --git a/README.OPL b/README.OPL index 2b837c4c..1746dd88 100644 --- a/README.OPL +++ b/README.OPL @@ -64,11 +64,13 @@ stdout.txt: If you're running an NT-based system, it is not possible to directly access the OPL chip, even when running as Administrator. Fortunately, -it is possible to use the third-party "PortTalk" driver: +it is possible to use the "ioperm.sys" driver developed for Cygwin: - http://www.beyondlogic.org/porttalk/porttalk.htm + http://openwince.sourceforge.net/ioperm/ -TODO - the NT driver hasn't actually been written yet.. +It is not necessary to have Cygwin installed to use this. Copy the +ioperm.sys file into the same directory as the Chocolate Doom +executable and it should be automatically loaded. === Linux === 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 5f2cfb50d1632b015817f442b507b36c83301332 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Fri, 26 Feb 2010 21:32:41 +0000 Subject: Minor documentation fixups. Subversion-branch: /branches/opl-branch Subversion-revision: 1872 --- README.OPL | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.OPL b/README.OPL index 1746dd88..07699232 100644 --- a/README.OPL +++ b/README.OPL @@ -58,7 +58,7 @@ anything. Windows allows direct access to the OPL chip. You can confirm that hardware OPL is working by checking for this message in stdout.txt: - OPL_Init: Using driver 'Win9x'. + OPL_Init: Using driver 'Win32'. === Windows NT (including 2000, XP and later) === @@ -72,6 +72,11 @@ It is not necessary to have Cygwin installed to use this. Copy the ioperm.sys file into the same directory as the Chocolate Doom executable and it should be automatically loaded. +You can confirm that hardware OPL is working by checking for this +message in stdout.txt: + + OPL_Init: Using driver 'Win32'. + === Linux === If you are using a system based on the Linux kernel, you can access -- 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(-) 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(-) 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(-) 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 662ad8cbcdc389b41693fbe2fcf567275d3408e7 Mon Sep 17 00:00:00 2001 From: Russell Rice Date: Thu, 4 Mar 2010 01:36:32 +0000 Subject: - Fix codeblocks project build Subversion-branch: /branches/opl-branch Subversion-revision: 1876 --- codeblocks/game.cbp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/codeblocks/game.cbp b/codeblocks/game.cbp index 374936dc..d6313bdf 100644 --- a/codeblocks/game.cbp +++ b/codeblocks/game.cbp @@ -60,6 +60,10 @@ + + + @@ -79,7 +83,7 @@ - + -- 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(-) 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