diff options
Diffstat (limited to 'opl')
-rw-r--r-- | opl/.gitignore | 5 | ||||
-rw-r--r-- | opl/Makefile.am | 18 | ||||
-rw-r--r-- | opl/examples/.gitignore | 5 | ||||
-rw-r--r-- | opl/examples/Makefile.am | 8 | ||||
-rw-r--r-- | opl/examples/droplay.c | 217 | ||||
-rw-r--r-- | opl/fmopl.c | 1155 | ||||
-rw-r--r-- | opl/fmopl.h | 167 | ||||
-rw-r--r-- | opl/opl.c | 449 | ||||
-rw-r--r-- | opl/opl.h | 133 | ||||
-rw-r--r-- | opl/opl_internal.h | 60 | ||||
-rw-r--r-- | opl/opl_linux.c | 102 | ||||
-rw-r--r-- | opl/opl_obsd.c | 105 | ||||
-rw-r--r-- | opl/opl_queue.c | 280 | ||||
-rw-r--r-- | opl/opl_queue.h | 45 | ||||
-rw-r--r-- | opl/opl_sdl.c | 429 | ||||
-rw-r--r-- | opl/opl_timer.c | 251 | ||||
-rw-r--r-- | opl/opl_timer.h | 42 | ||||
-rw-r--r-- | opl/opl_win9x.c | 140 |
18 files changed, 3611 insertions, 0 deletions
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 diff --git a/opl/Makefile.am b/opl/Makefile.am new file mode 100644 index 00000000..8bbed9f0 --- /dev/null +++ b/opl/Makefile.am @@ -0,0 +1,18 @@ + +AM_CFLAGS=@SDLMIXER_CFLAGS@ + +SUBDIRS = . examples + +noinst_LIBRARIES=libopl.a + +libopl_a_SOURCES = \ + opl_internal.h \ + opl.c opl.h \ + opl_linux.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/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/Makefile.am b/opl/examples/Makefile.am new file mode 100644 index 00000000..7c2c7c8a --- /dev/null +++ b/opl/examples/Makefile.am @@ -0,0 +1,8 @@ + +AM_CFLAGS = -I.. + +noinst_PROGRAMS=droplay + +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 new file mode 100644 index 00000000..36f5c3c0 --- /dev/null +++ b/opl/examples/droplay.c @@ -0,0 +1,217 @@ +// 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 <stdio.h> +#include <stdlib.h> + +#include "SDL.h" + +#include "opl.h" + +#define HEADER_STRING "DBRAWOPL" +#define ADLIB_PORT 0x388 + +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) + { + 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); + } +} + +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); + } +} + +void Shutdown(void) +{ + OPL_Shutdown(); +} + +struct timer_data +{ + int running; + FILE *fstream; +}; + +void TimerCallback(void *data) +{ + struct timer_data *timer_data = data; + int delay; + + if (!timer_data->running) + { + return; + } + + // Read data until we must make a delay. + + for (;;) + { + int reg, val; + + // End of file? + + 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) + { + delay = val; + break; + } + else if (reg == 0x01) + { + val |= (fgetc(timer_data->fstream) << 8); + delay = val; + break; + } + else + { + WriteReg(reg, val); + } + } + + // 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 (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"); + 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[]) +{ + if (argc < 2) + { + printf("Usage: %s <filename>\n", argv[0]); + exit(-1); + } + + Init(); + + PlayFile(argv[1]); + + ClearAllRegs(); + Shutdown(); + + return 0; +} + diff --git a/opl/fmopl.c b/opl/fmopl.c new file mode 100644 index 00000000..1671244e --- /dev/null +++ b/opl/fmopl.c @@ -0,0 +1,1155 @@ +/* This file is derived from fmopl.cpp from ScummVM. + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * LGPL licensed version of MAMEs fmopl (V0.37a modified) by + * Tatsuyuki Satoh. Included from LGPL'ed AdPlug. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include <math.h> + +#include "fmopl.h" + +#define PI 3.1415926539 + +#define CLIP(value, min, max) \ + ( (value) < (min) ? (min) : \ + (value) > (max) ? (max) : (value) ) + +/* -------------------- preliminary define section --------------------- */ +/* attack/decay rate time rate */ +#define OPL_ARRATE 141280 /* RATE 4 = 2826.24ms @ 3.6MHz */ +#define OPL_DRRATE 1956000 /* RATE 4 = 39280.64ms @ 3.6MHz */ + +#define FREQ_BITS 24 /* frequency turn */ + +/* counter bits = 20 , octerve 7 */ +#define FREQ_RATE (1<<(FREQ_BITS-20)) +#define TL_BITS (FREQ_BITS+2) + +/* final output shift , limit minimum and maximum */ +#define OPL_OUTSB (TL_BITS+3-16) /* OPL output final shift 16bit */ +#define OPL_MAXOUT (0x7fff<<OPL_OUTSB) +#define OPL_MINOUT (-0x8000<<OPL_OUTSB) + +/* -------------------- quality selection --------------------- */ + +/* sinwave entries */ +/* used static memory = SIN_ENT * 4 (byte) */ +#define SIN_ENT_SHIFT 11 +#define SIN_ENT (1<<SIN_ENT_SHIFT) + +/* output level entries (envelope,sinwave) */ +/* envelope counter lower bits */ +static int ENV_BITS; +/* envelope output entries */ +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) */ +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 */ + +/* LFO table entries */ +#define VIB_ENT 512 +#define VIB_SHIFT (32-9) +#define AMS_ENT 512 +#define AMS_SHIFT (32-9) + +#define VIB_RATE_SHIFT 8 +#define VIB_RATE (1<<VIB_RATE_SHIFT) + +/* -------------------- local defines , macros --------------------- */ + +/* register number to channel number , slot offset */ +#define SLOT1 0 +#define SLOT2 1 + +/* envelope phase */ +#define ENV_MOD_RR 0x00 +#define ENV_MOD_DR 0x01 +#define ENV_MOD_AR 0x02 + +/* -------------------- tables --------------------- */ +static const int slot_array[32] = { + 0, 2, 4, 1, 3, 5,-1,-1, + 6, 8,10, 7, 9,11,-1,-1, + 12,14,16,13,15,17,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1 +}; + +static uint32_t KSL_TABLE[8 * 16]; + +static const double KSL_TABLE_SEED[8 * 16] = { + /* OCT 0 */ + 0.000, 0.000, 0.000, 0.000, + 0.000, 0.000, 0.000, 0.000, + 0.000, 0.000, 0.000, 0.000, + 0.000, 0.000, 0.000, 0.000, + /* OCT 1 */ + 0.000, 0.000, 0.000, 0.000, + 0.000, 0.000, 0.000, 0.000, + 0.000, 0.750, 1.125, 1.500, + 1.875, 2.250, 2.625, 3.000, + /* OCT 2 */ + 0.000, 0.000, 0.000, 0.000, + 0.000, 1.125, 1.875, 2.625, + 3.000, 3.750, 4.125, 4.500, + 4.875, 5.250, 5.625, 6.000, + /* OCT 3 */ + 0.000, 0.000, 0.000, 1.875, + 3.000, 4.125, 4.875, 5.625, + 6.000, 6.750, 7.125, 7.500, + 7.875, 8.250, 8.625, 9.000, + /* OCT 4 */ + 0.000, 0.000, 3.000, 4.875, + 6.000, 7.125, 7.875, 8.625, + 9.000, 9.750, 10.125, 10.500, + 10.875, 11.250, 11.625, 12.000, + /* OCT 5 */ + 0.000, 3.000, 6.000, 7.875, + 9.000, 10.125, 10.875, 11.625, + 12.000, 12.750, 13.125, 13.500, + 13.875, 14.250, 14.625, 15.000, + /* OCT 6 */ + 0.000, 6.000, 9.000, 10.875, + 12.000, 13.125, 13.875, 14.625, + 15.000, 15.750, 16.125, 16.500, + 16.875, 17.250, 17.625, 18.000, + /* OCT 7 */ + 0.000, 9.000, 12.000, 13.875, + 15.000, 16.125, 16.875, 17.625, + 18.000, 18.750, 19.125, 19.500, + 19.875, 20.250, 20.625, 21.000 +}; + +/* sustain level table (3db per step) */ +/* 0 - 15: 0, 3, 6, 9,12,15,18,21,24,27,30,33,36,39,42,93 (dB)*/ + +static int SL_TABLE[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 +}; + +#define TL_MAX (EG_ENT * 2) /* limit(tl + ksr + envelope) + sinwave */ +/* TotalLevel : 48 24 12 6 3 1.5 0.75 (dB) */ +/* TL_TABLE[ 0 to TL_MAX ] : plus section */ +/* TL_TABLE[ TL_MAX to TL_MAX+TL_MAX-1 ] : minus section */ +static int *TL_TABLE; + +/* pointers to TL_TABLE with sinwave output offset */ +static int **SIN_TABLE; + +/* LFO table */ +static int *AMS_TABLE; +static int *VIB_TABLE; + +/* envelope output curve table */ +/* attack + decay + OFF */ +//static int ENV_CURVE[2*EG_ENT+1]; +//static int ENV_CURVE[2 * 4096 + 1]; // to keep it static ... +static int *ENV_CURVE; + + +/* multiple table */ +#define ML(a) (int)(a * 2) +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) +}; +#undef ML + +/* dummy attack / decay rate ( when rate == 0 ) */ +static int RATE_0[16]= +{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; + +/* -------------------- static state --------------------- */ + +/* lock level of common table */ +static int num_lock = 0; + +/* work table */ +static void *cur_chip = NULL; /* current chip point */ +/* currenct chip state */ +/* static OPLSAMPLE *bufL,*bufR; */ +static OPL_CH *S_CH; +static OPL_CH *E_CH; +static OPL_SLOT *SLOT7_1, *SLOT7_2, *SLOT8_1, *SLOT8_2; + +static int outd[1]; +static int ams; +static int vib; +static int *ams_table; +static int *vib_table; +static int amsIncr; +static int vibIncr; +static int feedback2; /* connect for SLOT 2 */ + +/* --------------------- rebuild tables ------------------- */ + +#define ARRAYSIZE(x) (sizeof(x) / sizeof(*x)) +#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) { + int i; + + ENV_BITS = ENV_BITS_PARAM; + EG_ENT = EG_ENT_PARAM; + EG_OFF = ((2 * EG_ENT)<<ENV_BITS); /* OFF */ + EG_DED = EG_OFF; + EG_DST = (EG_ENT << ENV_BITS); /* DECAY START */ + EG_AED = EG_DST; + //EG_STEP = (96.0/EG_ENT); + + for (i = 0; i < ARRAYSIZE(KSL_TABLE_SEED); i++) + KSL_TABLE[i] = SC_KSL(KSL_TABLE_SEED[i]); + + for (i = 0; i < ARRAYSIZE(SL_TABLE_SEED); i++) + SL_TABLE[i] = SC_SL(SL_TABLE_SEED[i]); +} + +#undef SC_KSL +#undef SC_SL + +/* --------------------- subroutines --------------------- */ + +/* status set and IRQ handling */ +static inline void OPL_STATUS_SET(FM_OPL *OPL, int flag) { + /* set status flag */ + OPL->status |= flag; + if(!(OPL->status & 0x80)) { + if(OPL->status & OPL->statusmask) { /* IRQ on */ + OPL->status |= 0x80; + /* callback user interrupt handler (IRQ is OFF to ON) */ + if(OPL->IRQHandler) + (OPL->IRQHandler)(OPL->IRQParam,1); + } + } +} + +/* status reset and IRQ handling */ +static inline void OPL_STATUS_RESET(FM_OPL *OPL, int flag) { + /* reset status flag */ + OPL->status &= ~flag; + if((OPL->status & 0x80)) { + if (!(OPL->status & OPL->statusmask)) { + OPL->status &= 0x7f; + /* callback user interrupt handler (IRQ is ON to OFF) */ + if(OPL->IRQHandler) (OPL->IRQHandler)(OPL->IRQParam,0); + } + } +} + +/* IRQ mask set */ +static inline void OPL_STATUSMASK_SET(FM_OPL *OPL, int flag) { + OPL->statusmask = flag; + /* IRQ handling check */ + OPL_STATUS_SET(OPL,0); + OPL_STATUS_RESET(OPL,0); +} + +/* ----- key on ----- */ +static inline void OPL_KEYON(OPL_SLOT *SLOT) { + /* sin wave restart */ + SLOT->Cnt = 0; + /* set attack */ + SLOT->evm = ENV_MOD_AR; + SLOT->evs = SLOT->evsa; + SLOT->evc = EG_AST; + SLOT->eve = EG_AED; +} + +/* ----- key off ----- */ +static inline void OPL_KEYOFF(OPL_SLOT *SLOT) { + if( SLOT->evm > ENV_MOD_RR) { + /* set envelope counter from envleope output */ + + // WORKAROUND: The Kyra engine does something very strange when + // starting a new song. For each channel: + // + // * The release rate is set to "fastest". + // * Any note is keyed off. + // * A very low-frequency note is keyed on. + // + // Usually, what happens next is that the real notes is keyed + // on immediately, in which case there's no problem. + // + // However, if the note is again keyed off (because the channel + // begins on a rest rather than a note), the envelope counter + // was moved from the very lowest point on the attack curve to + // the very highest point on the release curve. + // + // Again, this might not be a problem, if the release rate is + // still set to "fastest". But in many cases, it had already + // been increased. And, possibly because of inaccuracies in the + // envelope generator, that would cause the note to "fade out" + // for quite a long time. + // + // What we really need is a way to find the correct starting + // point for the envelope counter, and that may be what the + // commented-out line below is meant to do. For now, simply + // handle the pathological case. + + if (SLOT->evm == ENV_MOD_AR && SLOT->evc == EG_AST) + SLOT->evc = EG_DED; + else if( !(SLOT->evc & EG_DST) ) + //SLOT->evc = (ENV_CURVE[SLOT->evc>>ENV_BITS]<<ENV_BITS) + EG_DST; + SLOT->evc = EG_DST; + SLOT->eve = EG_DED; + SLOT->evs = SLOT->evsr; + SLOT->evm = ENV_MOD_RR; + } +} + +/* ---------- calcrate Envelope Generator & Phase Generator ---------- */ + +/* return : envelope output */ +static inline uint32_t OPL_CALC_SLOT(OPL_SLOT *SLOT) { + /* calcrate envelope generator */ + if((SLOT->evc += SLOT->evs) >= SLOT->eve) { + switch( SLOT->evm ) { + case ENV_MOD_AR: /* ATTACK -> DECAY1 */ + /* next DR */ + SLOT->evm = ENV_MOD_DR; + SLOT->evc = EG_DST; + SLOT->eve = SLOT->SL; + SLOT->evs = SLOT->evsd; + break; + case ENV_MOD_DR: /* DECAY -> SL or RR */ + SLOT->evc = SLOT->SL; + SLOT->eve = EG_DED; + if(SLOT->eg_typ) { + SLOT->evs = 0; + } else { + SLOT->evm = ENV_MOD_RR; + SLOT->evs = SLOT->evsr; + } + break; + case ENV_MOD_RR: /* RR -> OFF */ + SLOT->evc = EG_OFF; + SLOT->eve = EG_OFF + 1; + SLOT->evs = 0; + break; + } + } + /* calcrate envelope */ + return SLOT->TLL + ENV_CURVE[SLOT->evc>>ENV_BITS] + (SLOT->ams ? ams : 0); +} + +/* set algorythm connection */ +static void set_algorythm(OPL_CH *CH) { + int *carrier = &outd[0]; + CH->connect1 = CH->CON ? carrier : &feedback2; + CH->connect2 = carrier; +} + +/* ---------- frequency counter for operater update ---------- */ +static inline void CALC_FCSLOT(OPL_CH *CH, OPL_SLOT *SLOT) { + int ksr; + + /* frequency step counter */ + SLOT->Incr = CH->fc * SLOT->mul; + ksr = CH->kcode >> SLOT->KSR; + + if( SLOT->ksr != ksr ) { + SLOT->ksr = ksr; + /* attack , decay rate recalcration */ + SLOT->evsa = SLOT->AR[ksr]; + SLOT->evsd = SLOT->DR[ksr]; + SLOT->evsr = SLOT->RR[ksr]; + } + SLOT->TLL = SLOT->TL + (CH->ksl_base>>SLOT->ksl); +} + +/* set multi,am,vib,EG-TYP,KSR,mul */ +static inline void set_mul(FM_OPL *OPL, int slot, int v) { + OPL_CH *CH = &OPL->P_CH[slot>>1]; + OPL_SLOT *SLOT = &CH->SLOT[slot & 1]; + + SLOT->mul = MUL_TABLE[v & 0x0f]; + SLOT->KSR = (v & 0x10) ? 0 : 2; + SLOT->eg_typ = (v & 0x20) >> 5; + SLOT->vib = (v & 0x40); + SLOT->ams = (v & 0x80); + CALC_FCSLOT(CH, SLOT); +} + +/* set ksl & tl */ +static inline void set_ksl_tl(FM_OPL *OPL, int slot, int v) { + OPL_CH *CH = &OPL->P_CH[slot>>1]; + OPL_SLOT *SLOT = &CH->SLOT[slot & 1]; + int ksl = v >> 6; /* 0 / 1.5 / 3 / 6 db/OCT */ + + SLOT->ksl = ksl ? 3-ksl : 31; + SLOT->TL = (int)((v & 0x3f) * (0.75 / EG_STEP)); /* 0.75db step */ + + if(!(OPL->mode & 0x80)) { /* not CSM latch total level */ + SLOT->TLL = SLOT->TL + (CH->ksl_base >> SLOT->ksl); + } +} + +/* set attack rate & decay rate */ +static inline void set_ar_dr(FM_OPL *OPL, int slot, int v) { + OPL_CH *CH = &OPL->P_CH[slot>>1]; + OPL_SLOT *SLOT = &CH->SLOT[slot & 1]; + int ar = v >> 4; + int dr = v & 0x0f; + + SLOT->AR = ar ? &OPL->AR_TABLE[ar << 2] : RATE_0; + SLOT->evsa = SLOT->AR[SLOT->ksr]; + if(SLOT->evm == ENV_MOD_AR) + SLOT->evs = SLOT->evsa; + + SLOT->DR = dr ? &OPL->DR_TABLE[dr<<2] : RATE_0; + SLOT->evsd = SLOT->DR[SLOT->ksr]; + if(SLOT->evm == ENV_MOD_DR) + SLOT->evs = SLOT->evsd; +} + +/* set sustain level & release rate */ +static inline void set_sl_rr(FM_OPL *OPL, int slot, int v) { + OPL_CH *CH = &OPL->P_CH[slot>>1]; + OPL_SLOT *SLOT = &CH->SLOT[slot & 1]; + int sl = v >> 4; + int rr = v & 0x0f; + + SLOT->SL = SL_TABLE[sl]; + if(SLOT->evm == ENV_MOD_DR) + SLOT->eve = SLOT->SL; + SLOT->RR = &OPL->DR_TABLE[rr<<2]; + SLOT->evsr = SLOT->RR[SLOT->ksr]; + if(SLOT->evm == ENV_MOD_RR) + SLOT->evs = SLOT->evsr; +} + +/* operator output calcrator */ + +#define OP_OUT(slot,env,con) slot->wavetable[((slot->Cnt + con)>>(24-SIN_ENT_SHIFT)) & (SIN_ENT-1)][env] +/* ---------- calcrate one of channel ---------- */ +static inline void OPL_CALC_CH(OPL_CH *CH) { + uint32_t env_out; + OPL_SLOT *SLOT; + + feedback2 = 0; + /* SLOT 1 */ + SLOT = &CH->SLOT[SLOT1]; + env_out=OPL_CALC_SLOT(SLOT); + if(env_out < (uint32_t)(EG_ENT - 1)) { + /* PG */ + if(SLOT->vib) + SLOT->Cnt += (SLOT->Incr * vib) >> VIB_RATE_SHIFT; + else + SLOT->Cnt += SLOT->Incr; + /* connection */ + if(CH->FB) { + int feedback1 = (CH->op1_out[0] + CH->op1_out[1]) >> CH->FB; + CH->op1_out[1] = CH->op1_out[0]; + *CH->connect1 += CH->op1_out[0] = OP_OUT(SLOT, env_out, feedback1); + } else { + *CH->connect1 += OP_OUT(SLOT, env_out, 0); + } + } else { + CH->op1_out[1] = CH->op1_out[0]; + CH->op1_out[0] = 0; + } + /* SLOT 2 */ + SLOT = &CH->SLOT[SLOT2]; + env_out=OPL_CALC_SLOT(SLOT); + if(env_out < (uint32_t)(EG_ENT - 1)) { + /* PG */ + if(SLOT->vib) + SLOT->Cnt += (SLOT->Incr * vib) >> VIB_RATE_SHIFT; + else + SLOT->Cnt += SLOT->Incr; + /* connection */ + outd[0] += OP_OUT(SLOT, env_out, feedback2); + } +} + +/* ---------- calcrate rythm block ---------- */ +#define WHITE_NOISE_db 6.0 +static inline void OPL_CALC_RH(FM_OPL *OPL, OPL_CH *CH) { + uint32_t env_tam, env_sd, env_top, env_hh; + // This code used to do int(OPL->rnd.getRandomBit() * (WHITE_NOISE_db / EG_STEP)), + // but EG_STEP = 96.0/EG_ENT, and WHITE_NOISE_db=6.0. So, that's equivalent to + // int(OPL->rnd.getRandomBit() * EG_ENT/16). We know that EG_ENT is 4096, or 1024, + // or 128, so we can safely avoid any FP ops. + int whitenoise = (rand() & 1) * (EG_ENT>>4); + + int tone8; + + OPL_SLOT *SLOT; + int env_out; + + /* BD : same as FM serial mode and output level is large */ + feedback2 = 0; + /* SLOT 1 */ + SLOT = &CH[6].SLOT[SLOT1]; + env_out = OPL_CALC_SLOT(SLOT); + if(env_out < EG_ENT-1) { + /* PG */ + if(SLOT->vib) + SLOT->Cnt += (SLOT->Incr * vib) >> VIB_RATE_SHIFT; + else + SLOT->Cnt += SLOT->Incr; + /* connection */ + if(CH[6].FB) { + int feedback1 = (CH[6].op1_out[0] + CH[6].op1_out[1]) >> CH[6].FB; + CH[6].op1_out[1] = CH[6].op1_out[0]; + feedback2 = CH[6].op1_out[0] = OP_OUT(SLOT, env_out, feedback1); + } + else { + feedback2 = OP_OUT(SLOT, env_out, 0); + } + } else { + feedback2 = 0; + CH[6].op1_out[1] = CH[6].op1_out[0]; + CH[6].op1_out[0] = 0; + } + /* SLOT 2 */ + SLOT = &CH[6].SLOT[SLOT2]; + env_out = OPL_CALC_SLOT(SLOT); + if(env_out < EG_ENT-1) { + /* PG */ + if(SLOT->vib) + SLOT->Cnt += (SLOT->Incr * vib) >> VIB_RATE_SHIFT; + else + SLOT->Cnt += SLOT->Incr; + /* connection */ + outd[0] += OP_OUT(SLOT, env_out, feedback2) * 2; + } + + // SD (17) = mul14[fnum7] + white noise + // TAM (15) = mul15[fnum8] + // TOP (18) = fnum6(mul18[fnum8]+whitenoise) + // HH (14) = fnum7(mul18[fnum8]+whitenoise) + white noise + env_sd = OPL_CALC_SLOT(SLOT7_2) + whitenoise; + env_tam =OPL_CALC_SLOT(SLOT8_1); + env_top = OPL_CALC_SLOT(SLOT8_2); + env_hh = OPL_CALC_SLOT(SLOT7_1) + whitenoise; + + /* PG */ + if(SLOT7_1->vib) + SLOT7_1->Cnt += (SLOT7_1->Incr * vib) >> (VIB_RATE_SHIFT-1); + else + SLOT7_1->Cnt += 2 * SLOT7_1->Incr; + if(SLOT7_2->vib) + SLOT7_2->Cnt += (CH[7].fc * vib) >> (VIB_RATE_SHIFT-3); + else + SLOT7_2->Cnt += (CH[7].fc * 8); + if(SLOT8_1->vib) + SLOT8_1->Cnt += (SLOT8_1->Incr * vib) >> VIB_RATE_SHIFT; + else + SLOT8_1->Cnt += SLOT8_1->Incr; + if(SLOT8_2->vib) + SLOT8_2->Cnt += ((CH[8].fc * 3) * vib) >> (VIB_RATE_SHIFT-4); + else + SLOT8_2->Cnt += (CH[8].fc * 48); + + tone8 = OP_OUT(SLOT8_2,whitenoise,0 ); + + /* SD */ + if(env_sd < (uint32_t)(EG_ENT - 1)) + outd[0] += OP_OUT(SLOT7_1, env_sd, 0) * 8; + /* TAM */ + if(env_tam < (uint32_t)(EG_ENT - 1)) + outd[0] += OP_OUT(SLOT8_1, env_tam, 0) * 2; + /* TOP-CY */ + if(env_top < (uint32_t)(EG_ENT - 1)) + outd[0] += OP_OUT(SLOT7_2, env_top, tone8) * 2; + /* HH */ + if(env_hh < (uint32_t)(EG_ENT-1)) + outd[0] += OP_OUT(SLOT7_2, env_hh, tone8) * 2; +} + +/* ----------- initialize time tabls ----------- */ +static void init_timetables(FM_OPL *OPL, int ARRATE, int DRRATE) { + int i; + double rate; + + /* make attack rate & decay rate tables */ + for (i = 0; i < 4; i++) + OPL->AR_TABLE[i] = OPL->DR_TABLE[i] = 0; + for (i = 4; i <= 60; i++) { + rate = OPL->freqbase; /* frequency rate */ + if(i < 60) + rate *= 1.0 + (i & 3) * 0.25; /* b0-1 : x1 , x1.25 , x1.5 , x1.75 */ + rate *= 1 << ((i >> 2) - 1); /* b2-5 : shift bit */ + rate *= (double)(EG_ENT << ENV_BITS); + OPL->AR_TABLE[i] = (int)(rate / ARRATE); + OPL->DR_TABLE[i] = (int)(rate / DRRATE); + } + for (i = 60; i < 76; i++) { + OPL->AR_TABLE[i] = EG_AED-1; + OPL->DR_TABLE[i] = OPL->DR_TABLE[60]; + } +} + +/* ---------- generic table initialize ---------- */ +static int OPLOpenTable(void) { + int s,t; + double rate; + int i,j; + double pom; + + /* allocate dynamic tables */ + if((TL_TABLE = (int *)malloc(TL_MAX * 2 * sizeof(int))) == NULL) + return 0; + + if((SIN_TABLE = (int **)malloc(SIN_ENT * 4 * sizeof(int *))) == NULL) { + free(TL_TABLE); + return 0; + } + + if((AMS_TABLE = (int *)malloc(AMS_ENT * 2 * sizeof(int))) == NULL) { + free(TL_TABLE); + free(SIN_TABLE); + return 0; + } + + if((VIB_TABLE = (int *)malloc(VIB_ENT * 2 * sizeof(int))) == NULL) { + free(TL_TABLE); + free(SIN_TABLE); + free(AMS_TABLE); + return 0; + } + /* make total level table */ + for (t = 0; t < EG_ENT - 1 ; t++) { + rate = ((1 << TL_BITS) - 1) / pow(10.0, EG_STEP * t / 20); /* dB -> voltage */ + TL_TABLE[ t] = (int)rate; + TL_TABLE[TL_MAX + t] = -TL_TABLE[t]; + } + /* fill volume off area */ + for (t = EG_ENT - 1; t < TL_MAX; t++) { + TL_TABLE[t] = TL_TABLE[TL_MAX + t] = 0; + } + + /* make sinwave table (total level offet) */ + /* degree 0 = degree 180 = off */ + SIN_TABLE[0] = SIN_TABLE[SIN_ENT /2 ] = &TL_TABLE[EG_ENT - 1]; + for (s = 1;s <= SIN_ENT / 4; s++) { + pom = sin(2 * PI * s / SIN_ENT); /* sin */ + pom = 20 * log10(1 / pom); /* decibel */ + j = (int) (pom / EG_STEP); /* TL_TABLE steps */ + + /* degree 0 - 90 , degree 180 - 90 : plus section */ + SIN_TABLE[ s] = SIN_TABLE[SIN_ENT / 2 - s] = &TL_TABLE[j]; + /* degree 180 - 270 , degree 360 - 270 : minus section */ + SIN_TABLE[SIN_ENT / 2 + s] = SIN_TABLE[SIN_ENT - s] = &TL_TABLE[TL_MAX + j]; + } + for (s = 0;s < SIN_ENT; s++) { + SIN_TABLE[SIN_ENT * 1 + s] = s < (SIN_ENT / 2) ? SIN_TABLE[s] : &TL_TABLE[EG_ENT]; + SIN_TABLE[SIN_ENT * 2 + s] = SIN_TABLE[s % (SIN_ENT / 2)]; + SIN_TABLE[SIN_ENT * 3 + s] = (s / (SIN_ENT / 4)) & 1 ? &TL_TABLE[EG_ENT] : SIN_TABLE[SIN_ENT * 2 + s]; + } + + + ENV_CURVE = (int *)malloc(sizeof(int) * (2*EG_ENT+1)); + + /* envelope counter -> envelope output table */ + for (i=0; i < EG_ENT; i++) { + /* ATTACK curve */ + pom = pow(((double)(EG_ENT - 1 - i) / EG_ENT), 8) * EG_ENT; + /* if( pom >= EG_ENT ) pom = EG_ENT-1; */ + ENV_CURVE[i] = (int)pom; + /* DECAY ,RELEASE curve */ + ENV_CURVE[(EG_DST >> ENV_BITS) + i]= i; + } + /* off */ + ENV_CURVE[EG_OFF >> ENV_BITS]= EG_ENT - 1; + /* make LFO ams table */ + for (i=0; i < AMS_ENT; i++) { + pom = (1.0 + sin(2 * PI * i / AMS_ENT)) / 2; /* sin */ + AMS_TABLE[i] = (int)((1.0 / EG_STEP) * pom); /* 1dB */ + AMS_TABLE[AMS_ENT + i] = (int)((4.8 / EG_STEP) * pom); /* 4.8dB */ + } + /* make LFO vibrate table */ + for (i=0; i < VIB_ENT; i++) { + /* 100cent = 1seminote = 6% ?? */ + pom = (double)VIB_RATE * 0.06 * sin(2 * PI * i / VIB_ENT); /* +-100sect step */ + VIB_TABLE[i] = (int)(VIB_RATE + (pom * 0.07)); /* +- 7cent */ + VIB_TABLE[VIB_ENT + i] = (int)(VIB_RATE + (pom * 0.14)); /* +-14cent */ + } + return 1; +} + +static void OPLCloseTable(void) { + free(TL_TABLE); + free(SIN_TABLE); + free(AMS_TABLE); + free(VIB_TABLE); + free(ENV_CURVE); +} + +/* CSM Key Controll */ +static inline void CSMKeyControll(OPL_CH *CH) { + OPL_SLOT *slot1 = &CH->SLOT[SLOT1]; + OPL_SLOT *slot2 = &CH->SLOT[SLOT2]; + /* all key off */ + OPL_KEYOFF(slot1); + OPL_KEYOFF(slot2); + /* total level latch */ + slot1->TLL = slot1->TL + (CH->ksl_base>>slot1->ksl); + slot1->TLL = slot1->TL + (CH->ksl_base>>slot1->ksl); + /* key on */ + CH->op1_out[0] = CH->op1_out[1] = 0; + OPL_KEYON(slot1); + OPL_KEYON(slot2); +} + +/* ---------- opl initialize ---------- */ +static void OPL_initalize(FM_OPL *OPL) { + int fn; + + /* frequency base */ + OPL->freqbase = (OPL->rate) ? ((double)OPL->clock / OPL->rate) / 72 : 0; + /* Timer base time */ + OPL->TimerBase = 1.0/((double)OPL->clock / 72.0 ); + /* make time tables */ + init_timetables(OPL, OPL_ARRATE, OPL_DRRATE); + /* make fnumber -> increment counter table */ + for( fn=0; fn < 1024; fn++) { + OPL->FN_TABLE[fn] = (uint32_t)(OPL->freqbase * fn * FREQ_RATE * (1<<7) / 2); + } + /* LFO freq.table */ + OPL->amsIncr = (int)(OPL->rate ? (double)AMS_ENT * (1 << AMS_SHIFT) / OPL->rate * 3.7 * ((double)OPL->clock/3600000) : 0); + OPL->vibIncr = (int)(OPL->rate ? (double)VIB_ENT * (1 << VIB_SHIFT) / OPL->rate * 6.4 * ((double)OPL->clock/3600000) : 0); +} + +/* ---------- write a OPL registers ---------- */ +void OPLWriteReg(FM_OPL *OPL, int r, int v) { + OPL_CH *CH; + int slot; + uint32_t block_fnum; + + switch(r & 0xe0) { + case 0x00: /* 00-1f:controll */ + switch(r & 0x1f) { + case 0x01: + /* wave selector enable */ + if(OPL->type&OPL_TYPE_WAVESEL) { + OPL->wavesel = v & 0x20; + if(!OPL->wavesel) { + /* preset compatible mode */ + int c; + for(c=0; c<OPL->max_ch; c++) { + OPL->P_CH[c].SLOT[SLOT1].wavetable = &SIN_TABLE[0]; + OPL->P_CH[c].SLOT[SLOT2].wavetable = &SIN_TABLE[0]; + } + } + } + return; + case 0x02: /* Timer 1 */ + OPL->T[0] = (256-v) * 4; + break; + case 0x03: /* Timer 2 */ + OPL->T[1] = (256-v) * 16; + return; + case 0x04: /* IRQ clear / mask and Timer enable */ + if(v & 0x80) { /* IRQ flag clear */ + OPL_STATUS_RESET(OPL, 0x7f); + } else { /* set IRQ mask ,timer enable*/ + uint8_t st1 = v & 1; + uint8_t st2 = (v >> 1) & 1; + /* IRQRST,T1MSK,t2MSK,EOSMSK,BRMSK,x,ST2,ST1 */ + OPL_STATUS_RESET(OPL, v & 0x78); + OPL_STATUSMASK_SET(OPL,((~v) & 0x78) | 0x01); + /* timer 2 */ + if(OPL->st[1] != st2) { + double interval = st2 ? (double)OPL->T[1] * OPL->TimerBase : 0.0; + OPL->st[1] = st2; + if (OPL->TimerHandler) (OPL->TimerHandler)(OPL->TimerParam + 1, interval); + } + /* timer 1 */ + if(OPL->st[0] != st1) { + double interval = st1 ? (double)OPL->T[0] * OPL->TimerBase : 0.0; + OPL->st[0] = st1; + if (OPL->TimerHandler) (OPL->TimerHandler)(OPL->TimerParam + 0, interval); + } + } + return; + } + break; + case 0x20: /* am,vib,ksr,eg type,mul */ + slot = slot_array[r&0x1f]; + if(slot == -1) + return; + set_mul(OPL,slot,v); + return; + case 0x40: + slot = slot_array[r&0x1f]; + if(slot == -1) + return; + set_ksl_tl(OPL,slot,v); + return; + case 0x60: + slot = slot_array[r&0x1f]; + if(slot == -1) + return; + set_ar_dr(OPL,slot,v); + return; + case 0x80: + slot = slot_array[r&0x1f]; + if(slot == -1) + return; + set_sl_rr(OPL,slot,v); + return; + case 0xa0: + switch(r) { + case 0xbd: + /* amsep,vibdep,r,bd,sd,tom,tc,hh */ + { + uint8_t rkey = OPL->rythm ^ v; + OPL->ams_table = &AMS_TABLE[v & 0x80 ? AMS_ENT : 0]; + OPL->vib_table = &VIB_TABLE[v & 0x40 ? VIB_ENT : 0]; + OPL->rythm = v & 0x3f; + if(OPL->rythm & 0x20) { + /* BD key on/off */ + if(rkey & 0x10) { + if(v & 0x10) { + OPL->P_CH[6].op1_out[0] = OPL->P_CH[6].op1_out[1] = 0; + OPL_KEYON(&OPL->P_CH[6].SLOT[SLOT1]); + OPL_KEYON(&OPL->P_CH[6].SLOT[SLOT2]); + } else { + OPL_KEYOFF(&OPL->P_CH[6].SLOT[SLOT1]); + OPL_KEYOFF(&OPL->P_CH[6].SLOT[SLOT2]); + } + } + /* SD key on/off */ + if(rkey & 0x08) { + if(v & 0x08) + OPL_KEYON(&OPL->P_CH[7].SLOT[SLOT2]); + else + OPL_KEYOFF(&OPL->P_CH[7].SLOT[SLOT2]); + }/* TAM key on/off */ + if(rkey & 0x04) { + if(v & 0x04) + OPL_KEYON(&OPL->P_CH[8].SLOT[SLOT1]); + else + OPL_KEYOFF(&OPL->P_CH[8].SLOT[SLOT1]); + } + /* TOP-CY key on/off */ + if(rkey & 0x02) { + if(v & 0x02) + OPL_KEYON(&OPL->P_CH[8].SLOT[SLOT2]); + else + OPL_KEYOFF(&OPL->P_CH[8].SLOT[SLOT2]); + } + /* HH key on/off */ + if(rkey & 0x01) { + if(v & 0x01) + OPL_KEYON(&OPL->P_CH[7].SLOT[SLOT1]); + else + OPL_KEYOFF(&OPL->P_CH[7].SLOT[SLOT1]); + } + } + } + return; + + default: + break; + } + /* keyon,block,fnum */ + if((r & 0x0f) > 8) + return; + CH = &OPL->P_CH[r & 0x0f]; + if(!(r&0x10)) { /* a0-a8 */ + block_fnum = (CH->block_fnum & 0x1f00) | v; + } else { /* b0-b8 */ + int keyon = (v >> 5) & 1; + block_fnum = ((v & 0x1f) << 8) | (CH->block_fnum & 0xff); + if(CH->keyon != keyon) { + if((CH->keyon=keyon)) { + CH->op1_out[0] = CH->op1_out[1] = 0; + OPL_KEYON(&CH->SLOT[SLOT1]); + OPL_KEYON(&CH->SLOT[SLOT2]); + } else { + OPL_KEYOFF(&CH->SLOT[SLOT1]); + OPL_KEYOFF(&CH->SLOT[SLOT2]); + } + } + } + /* update */ + if(CH->block_fnum != block_fnum) { + int blockRv = 7 - (block_fnum >> 10); + int fnum = block_fnum & 0x3ff; + CH->block_fnum = block_fnum; + CH->ksl_base = KSL_TABLE[block_fnum >> 6]; + CH->fc = OPL->FN_TABLE[fnum] >> blockRv; + CH->kcode = CH->block_fnum >> 9; + if((OPL->mode & 0x40) && CH->block_fnum & 0x100) + CH->kcode |=1; + CALC_FCSLOT(CH,&CH->SLOT[SLOT1]); + CALC_FCSLOT(CH,&CH->SLOT[SLOT2]); + } + return; + case 0xc0: + /* FB,C */ + if((r & 0x0f) > 8) + return; + CH = &OPL->P_CH[r&0x0f]; + { + int feedback = (v >> 1) & 7; + CH->FB = feedback ? (8 + 1) - feedback : 0; + CH->CON = v & 1; + set_algorythm(CH); + } + return; + case 0xe0: /* wave type */ + slot = slot_array[r & 0x1f]; + if(slot == -1) + return; + CH = &OPL->P_CH[slot>>1]; + if(OPL->wavesel) { + CH->SLOT[slot&1].wavetable = &SIN_TABLE[(v & 0x03) * SIN_ENT]; + } + return; + } +} + +/* lock/unlock for common table */ +static int OPL_LockTable(void) { + num_lock++; + if(num_lock>1) + return 0; + /* first time */ + cur_chip = NULL; + /* allocate total level table (128kb space) */ + if(!OPLOpenTable()) { + num_lock--; + return -1; + } + return 0; +} + +static void OPL_UnLockTable(void) { + if(num_lock) + num_lock--; + if(num_lock) + return; + /* last time */ + cur_chip = NULL; + OPLCloseTable(); +} + +/*******************************************************************************/ +/* YM3812 local section */ +/*******************************************************************************/ + +/* ---------- update one of chip ----------- */ +void YM3812UpdateOne(FM_OPL *OPL, int16_t *buffer, int length, int interleave) { + int i; + int data; + int16_t *buf = buffer; + uint32_t amsCnt = OPL->amsCnt; + uint32_t vibCnt = OPL->vibCnt; + uint8_t rythm = OPL->rythm & 0x20; + OPL_CH *CH, *R_CH; + + + if((void *)OPL != cur_chip) { + cur_chip = (void *)OPL; + /* channel pointers */ + S_CH = OPL->P_CH; + E_CH = &S_CH[9]; + /* rythm slot */ + SLOT7_1 = &S_CH[7].SLOT[SLOT1]; + SLOT7_2 = &S_CH[7].SLOT[SLOT2]; + SLOT8_1 = &S_CH[8].SLOT[SLOT1]; + SLOT8_2 = &S_CH[8].SLOT[SLOT2]; + /* LFO state */ + amsIncr = OPL->amsIncr; + vibIncr = OPL->vibIncr; + ams_table = OPL->ams_table; + vib_table = OPL->vib_table; + } + R_CH = rythm ? &S_CH[6] : E_CH; + for(i = 0; i < length; i++) { + /* channel A channel B channel C */ + /* LFO */ + ams = ams_table[(amsCnt += amsIncr) >> AMS_SHIFT]; + vib = vib_table[(vibCnt += vibIncr) >> VIB_SHIFT]; + outd[0] = 0; + /* FM part */ + for(CH=S_CH; CH < R_CH; CH++) + OPL_CALC_CH(CH); + /* Rythn part */ + if(rythm) + OPL_CALC_RH(OPL, S_CH); + /* limit check */ + data = CLIP(outd[0], OPL_MINOUT, OPL_MAXOUT); + /* store to sound buffer */ + buf[i << interleave] = data >> OPL_OUTSB; + } + + OPL->amsCnt = amsCnt; + OPL->vibCnt = vibCnt; +} + +/* ---------- reset a chip ---------- */ +void OPLResetChip(FM_OPL *OPL) { + int c,s; + int i; + + /* reset chip */ + OPL->mode = 0; /* normal mode */ + OPL_STATUS_RESET(OPL, 0x7f); + /* reset with register write */ + OPLWriteReg(OPL, 0x01,0); /* wabesel disable */ + OPLWriteReg(OPL, 0x02,0); /* Timer1 */ + OPLWriteReg(OPL, 0x03,0); /* Timer2 */ + OPLWriteReg(OPL, 0x04,0); /* IRQ mask clear */ + for(i = 0xff; i >= 0x20; i--) + OPLWriteReg(OPL,i,0); + /* reset OPerator parameter */ + for(c = 0; c < OPL->max_ch ;c++ ) { + OPL_CH *CH = &OPL->P_CH[c]; + /* OPL->P_CH[c].PAN = OPN_CENTER; */ + for(s = 0; s < 2; s++ ) { + /* wave table */ + CH->SLOT[s].wavetable = &SIN_TABLE[0]; + /* CH->SLOT[s].evm = ENV_MOD_RR; */ + CH->SLOT[s].evc = EG_OFF; + CH->SLOT[s].eve = EG_OFF + 1; + CH->SLOT[s].evs = 0; + } + } +} + +/* ---------- Create a virtual YM3812 ---------- */ +/* 'rate' is sampling rate and 'bufsiz' is the size of the */ +FM_OPL *OPLCreate(int type, int clock, int rate) { + char *ptr; + FM_OPL *OPL; + int state_size; + int max_ch = 9; /* normaly 9 channels */ + + if( OPL_LockTable() == -1) + return NULL; + /* allocate OPL state space */ + state_size = sizeof(FM_OPL); + state_size += sizeof(OPL_CH) * max_ch; + + /* allocate memory block */ + ptr = (char *)calloc(state_size, 1); + if(ptr == NULL) + return NULL; + + /* clear */ + memset(ptr, 0, state_size); + OPL = (FM_OPL *)ptr; ptr += sizeof(FM_OPL); + OPL->P_CH = (OPL_CH *)ptr; ptr += sizeof(OPL_CH) * max_ch; + + /* set channel state pointer */ + OPL->type = type; + OPL->clock = clock; + OPL->rate = rate; + OPL->max_ch = max_ch; + + /* init grobal tables */ + OPL_initalize(OPL); + + /* reset chip */ + OPLResetChip(OPL); + return OPL; +} + +/* ---------- Destroy one of vietual YM3812 ---------- */ +void OPLDestroy(FM_OPL *OPL) { + OPL_UnLockTable(); + free(OPL); +} + +/* ---------- Option handlers ---------- */ +void OPLSetTimerHandler(FM_OPL *OPL, OPL_TIMERHANDLER TimerHandler,int channelOffset) { + OPL->TimerHandler = TimerHandler; + OPL->TimerParam = channelOffset; +} + +void OPLSetIRQHandler(FM_OPL *OPL, OPL_IRQHANDLER IRQHandler, int param) { + OPL->IRQHandler = IRQHandler; + OPL->IRQParam = param; +} + +void OPLSetUpdateHandler(FM_OPL *OPL, OPL_UPDATEHANDLER UpdateHandler,int param) { + OPL->UpdateHandler = UpdateHandler; + OPL->UpdateParam = param; +} + +/* ---------- YM3812 I/O interface ---------- */ +int OPLWrite(FM_OPL *OPL,int a,int v) { + if(!(a & 1)) { /* address port */ + OPL->address = v & 0xff; + } else { /* data port */ + if(OPL->UpdateHandler) + OPL->UpdateHandler(OPL->UpdateParam,0); + OPLWriteReg(OPL, OPL->address,v); + } + return OPL->status >> 7; +} + +unsigned char OPLRead(FM_OPL *OPL,int a) { + if(!(a & 1)) { /* status port */ + return OPL->status & (OPL->statusmask | 0x80); + } + + return 0; +} + +int OPLTimerOver(FM_OPL *OPL, int c) { + if(c) { /* Timer B */ + OPL_STATUS_SET(OPL, 0x20); + } else { /* Timer A */ + OPL_STATUS_SET(OPL, 0x40); + /* CSM mode key,TL controll */ + if(OPL->mode & 0x80) { /* CSM mode total level latch and auto key on */ + int ch; + if(OPL->UpdateHandler) + OPL->UpdateHandler(OPL->UpdateParam,0); + for(ch = 0; ch < 9; ch++) + CSMKeyControll(&OPL->P_CH[ch]); + } + } + /* reload timer */ + if (OPL->TimerHandler) + (OPL->TimerHandler)(OPL->TimerParam + c, (double)OPL->T[c] * OPL->TimerBase); + return OPL->status >> 7; +} + +FM_OPL *makeAdlibOPL(int rate) { + // We need to emulate one YM3812 chip + int env_bits = FMOPL_ENV_BITS_HQ; + int eg_ent = FMOPL_EG_ENT_HQ; + + OPLBuildTables(env_bits, eg_ent); + return OPLCreate(OPL_TYPE_YM3812, 3579545, rate); +} + diff --git a/opl/fmopl.h b/opl/fmopl.h new file mode 100644 index 00000000..2bbe8363 --- /dev/null +++ b/opl/fmopl.h @@ -0,0 +1,167 @@ +/* This file is derived from fmopl.h from ScummVM. + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * LGPL licensed version of MAMEs fmopl (V0.37a modified) by + * Tatsuyuki Satoh. Included from LGPL'ed AdPlug. + */ + + +#ifndef OPL_FMOPL_H +#define OPL_FMOPL_H + +#include "inttypes.h" + +enum { + FMOPL_ENV_BITS_HQ = 16, + FMOPL_ENV_BITS_MQ = 8, + FMOPL_ENV_BITS_LQ = 8, + FMOPL_EG_ENT_HQ = 4096, + FMOPL_EG_ENT_MQ = 1024, + FMOPL_EG_ENT_LQ = 128 +}; + +typedef void (*OPL_TIMERHANDLER)(int channel,double interval_Sec); +typedef void (*OPL_IRQHANDLER)(int param,int irq); +typedef void (*OPL_UPDATEHANDLER)(int param,int min_interval_us); + +#define OPL_TYPE_WAVESEL 0x01 /* waveform select */ + +/* Saving is necessary for member of the 'R' mark for suspend/resume */ +/* ---------- OPL one of slot ---------- */ +typedef struct fm_opl_slot { + int TL; /* total level :TL << 8 */ + int TLL; /* adjusted now TL */ + uint8_t KSR; /* key scale rate :(shift down bit) */ + int *AR; /* attack rate :&AR_TABLE[AR<<2] */ + int *DR; /* decay rate :&DR_TABLE[DR<<2] */ + int SL; /* sustain level :SL_TABLE[SL] */ + int *RR; /* release rate :&DR_TABLE[RR<<2] */ + uint8_t ksl; /* keyscale level :(shift down bits) */ + uint8_t ksr; /* key scale rate :kcode>>KSR */ + unsigned int mul; /* multiple :ML_TABLE[ML] */ + unsigned int Cnt; /* frequency count */ + unsigned int Incr; /* frequency step */ + + /* envelope generator state */ + uint8_t eg_typ;/* envelope type flag */ + uint8_t evm; /* envelope phase */ + int evc; /* envelope counter */ + int eve; /* envelope counter end point */ + int evs; /* envelope counter step */ + int evsa; /* envelope step for AR :AR[ksr] */ + int evsd; /* envelope step for DR :DR[ksr] */ + int evsr; /* envelope step for RR :RR[ksr] */ + + /* LFO */ + uint8_t ams; /* ams flag */ + uint8_t vib; /* vibrate flag */ + /* wave selector */ + int **wavetable; +} OPL_SLOT; + +/* ---------- OPL one of channel ---------- */ +typedef struct fm_opl_channel { + OPL_SLOT SLOT[2]; + uint8_t CON; /* connection type */ + uint8_t FB; /* feed back :(shift down bit)*/ + int *connect1; /* slot1 output pointer */ + int *connect2; /* slot2 output pointer */ + int op1_out[2]; /* slot1 output for selfeedback */ + + /* phase generator state */ + unsigned int block_fnum; /* block+fnum */ + uint8_t kcode; /* key code : KeyScaleCode */ + unsigned int fc; /* Freq. Increment base */ + unsigned int ksl_base; /* KeyScaleLevel Base step */ + uint8_t keyon; /* key on/off flag */ +} OPL_CH; + +/* OPL state */ +typedef struct fm_opl_f { + uint8_t type; /* chip type */ + int clock; /* master clock (Hz) */ + int rate; /* sampling rate (Hz) */ + double freqbase; /* frequency base */ + double TimerBase; /* Timer base time (==sampling time) */ + uint8_t address; /* address register */ + uint8_t status; /* status flag */ + uint8_t statusmask; /* status mask */ + unsigned int mode; /* Reg.08 : CSM , notesel,etc. */ + + /* Timer */ + int T[2]; /* timer counter */ + uint8_t st[2]; /* timer enable */ + + /* FM channel slots */ + OPL_CH *P_CH; /* pointer of CH */ + int max_ch; /* maximum channel */ + + /* Rythm sention */ + uint8_t rythm; /* Rythm mode , key flag */ + + /* time tables */ + int AR_TABLE[76]; /* atttack rate tables */ + int DR_TABLE[76]; /* decay rate tables */ + unsigned int FN_TABLE[1024];/* fnumber -> increment counter */ + + /* LFO */ + int *ams_table; + int *vib_table; + int amsCnt; + int amsIncr; + int vibCnt; + int vibIncr; + + /* wave selector enable flag */ + uint8_t wavesel; + + /* external event callback handler */ + OPL_TIMERHANDLER TimerHandler; /* TIMER handler */ + int TimerParam; /* TIMER parameter */ + OPL_IRQHANDLER IRQHandler; /* IRQ handler */ + int IRQParam; /* IRQ parameter */ + OPL_UPDATEHANDLER UpdateHandler; /* stream update handler */ + int UpdateParam; /* stream update parameter */ +} FM_OPL; + +/* ---------- Generic interface section ---------- */ +#define OPL_TYPE_YM3526 (0) +#define OPL_TYPE_YM3812 (OPL_TYPE_WAVESEL) + +void OPLBuildTables(int ENV_BITS_PARAM, int EG_ENT_PARAM); + +FM_OPL *OPLCreate(int type, int clock, int rate); +void OPLDestroy(FM_OPL *OPL); +void OPLSetTimerHandler(FM_OPL *OPL, OPL_TIMERHANDLER TimerHandler, int channelOffset); +void OPLSetIRQHandler(FM_OPL *OPL, OPL_IRQHANDLER IRQHandler, int param); +void OPLSetUpdateHandler(FM_OPL *OPL, OPL_UPDATEHANDLER UpdateHandler, int param); + +void OPLResetChip(FM_OPL *OPL); +int OPLWrite(FM_OPL *OPL, int a, int v); +unsigned char OPLRead(FM_OPL *OPL, int a); +int OPLTimerOver(FM_OPL *OPL, int c); +void OPLWriteReg(FM_OPL *OPL, int r, int v); +void YM3812UpdateOne(FM_OPL *OPL, int16_t *buffer, int length, int interleave); + +// Factory method +FM_OPL *makeAdlibOPL(int rate); + +#endif + diff --git a/opl/opl.c b/opl/opl.c new file mode 100644 index 00000000..bf999d47 --- /dev/null +++ b/opl/opl.c @@ -0,0 +1,449 @@ +// 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 "config.h" + +#include <stdio.h> +#include <stdlib.h> + +#ifdef _WIN32_WCE +#include "libc_wince.h" +#endif + +#include "SDL.h" + +#include "opl.h" +#include "opl_internal.h" + +//#define OPL_DEBUG_TRACE + +#ifdef HAVE_IOPERM +extern opl_driver_t opl_linux_driver; +#endif +#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[] = +{ +#ifdef HAVE_IOPERM + &opl_linux_driver, +#endif +#ifdef HAVE_LIBI386 + &opl_openbsd_driver, +#endif +#ifdef _WIN32 + &opl_win9x_driver, +#endif + &opl_sdl_driver, + NULL +}; + +static opl_driver_t *driver = NULL; +static int init_stage_reg_writes = 1; + +// +// 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) +{ + // 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; + } + + // 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 (InitDriver(drivers[i], port_base)) + { + 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) + { + driver->shutdown_func(); + driver = NULL; + } +} + +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); + } +} + +unsigned int OPL_ReadPort(opl_port_t port) +{ + if (driver != NULL) + { + 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 + { + return 0; + } +} + +// +// 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 initialization, + // the spacing here is performed by reading from the register + // port; after initialization, 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) + { + driver->set_callback_func(ms, callback, data); + } +} + +void OPL_ClearCallbacks(void) +{ + if (driver != NULL) + { + driver->clear_callbacks_func(); + } +} + +void OPL_Lock(void) +{ + if (driver != NULL) + { + driver->lock_func(); + } +} + +void OPL_Unlock(void) +{ + if (driver != NULL) + { + driver->unlock_func(); + } +} + +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); +} + +void OPL_SetPaused(int paused) +{ + if (driver != NULL) + { + driver->set_paused_func(paused); + } +} + diff --git a/opl/opl.h b/opl/opl.h new file mode 100644 index 00000000..9f5d0a9f --- /dev/null +++ b/opl/opl.h @@ -0,0 +1,133 @@ +// 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 void (*opl_callback_t)(void *data); + +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_WAVEFORM_ENABLE 0x01 +#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 +#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 + +// +// Low-level functions. +// + +// Initialize 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); + +// +// 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. + +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. + +void OPL_Lock(void); + +// End critical section. + +void OPL_Unlock(void); + +// Block until the specified number of milliseconds have elapsed. + +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 new file mode 100644 index 00000000..78cbe7b2 --- /dev/null +++ b/opl/opl_internal.h @@ -0,0 +1,60 @@ +// 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 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); +typedef void (*opl_set_paused_func)(int paused); + +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_set_callback_func set_callback_func; + 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 new file mode 100644 index 00000000..089192c9 --- /dev/null +++ b/opl/opl_linux.c @@ -0,0 +1,102 @@ +// 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 <stdio.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <sys/io.h> + +#include "opl.h" +#include "opl_internal.h" +#include "opl_timer.h" + +static unsigned int opl_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; + } + + 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); +} + +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(value, opl_port_base + port); +} + +opl_driver_t opl_linux_driver = +{ + "Linux", + OPL_Linux_Init, + OPL_Linux_Shutdown, + OPL_Linux_PortRead, + OPL_Linux_PortWrite, + OPL_Timer_SetCallback, + OPL_Timer_ClearCallbacks, + OPL_Timer_Lock, + OPL_Timer_Unlock, + OPL_Timer_SetPaused +}; + +#endif /* #ifdef HAVE_IOPERM */ + diff --git a/opl/opl_obsd.c b/opl/opl_obsd.c new file mode 100644 index 00000000..05574333 --- /dev/null +++ b/opl/opl_obsd.c @@ -0,0 +1,105 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 2009 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +// DESCRIPTION: +// OPL OpenBSD interface (also NetBSD) +// +//----------------------------------------------------------------------------- + +#include "config.h" + +#ifdef HAVE_LIBI386 + +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> + +#include <sys/types.h> +#include <machine/sysarch.h> +#include <i386/pio.h> + +#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(3) < 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_LIBI386 */ + diff --git a/opl/opl_queue.c b/opl/opl_queue.c new file mode 100644 index 00000000..f9d4c377 --- /dev/null +++ b/opl/opl_queue.c @@ -0,0 +1,280 @@ +// 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 <stdio.h> +#include <stdlib.h> +#include <string.h> + +#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_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) +{ + int entry_id; + int parent_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) + { + 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[parent_id], + sizeof(opl_queue_entry_t)); + + // Advance to the parent. + + entry_id = parent_id; + } + + // 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; + } +} + +#ifdef TEST + +#include <assert.h> + +static void PrintQueueNode(opl_callback_queue_t *queue, int node, int depth) +{ + int i; + + if (node >= queue->num_entries) + { + return; + } + + for (i=0; i<depth * 3; ++i) + { + printf(" "); + } + + printf("%i\n", queue->entries[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<MAX_OPL_QUEUE; ++i) + { + time = rand() % 0x10000; + OPL_Queue_Push(queue, NULL, NULL, time); + } + + time = 0; + + for (i=0; i<MAX_OPL_QUEUE; ++i) + { + assert(!OPL_Queue_IsEmpty(queue)); + newtime = OPL_Queue_Peek(queue); + assert(OPL_Queue_Pop(queue, &callback, &data)); + + assert(newtime >= time); + time = newtime; + } + + assert(OPL_Queue_IsEmpty(queue)); + assert(!OPL_Queue_Pop(queue, &callback, &data)); + } +} + +#endif + diff --git a/opl/opl_queue.h b/opl/opl_queue.h new file mode 100644 index 00000000..2447702b --- /dev/null +++ b/opl/opl_queue.h @@ -0,0 +1,45 @@ +// 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_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, + 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 new file mode 100644 index 00000000..40430546 --- /dev/null +++ b/opl/opl_sdl.c @@ -0,0 +1,429 @@ +// 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 <stdio.h> +#include <string.h> +#include <errno.h> +#include <assert.h> + +#include "SDL.h" +#include "SDL_mixer.h" + +#include "fmopl.h" + +#include "opl.h" +#include "opl_internal.h" + +#include "opl_queue.h" + +// 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; + +// 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; + +// Temporary mixing buffer used by the mixing callback. + +static int16_t *mix_buffer = NULL; + +// SDL parameters. + +static int sdl_was_initialized = 0; +static int mixing_freq, mixing_channels; +static Uint16 mixing_format; + +static int SDLIsInitialized(void) +{ + int freq, channels; + Uint16 format; + + 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; + + 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) + pause_offset) + { + // 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<nsamples; ++i) + { + buffer[i * 2] += mix_buffer[i] / 2; + buffer[i * 2 + 1] += mix_buffer[i] / 2; + } +} + +// Callback function to fill a new sound buffer: + +static void OPL_Mix_Callback(void *udata, + Uint8 *byte_buffer, + int buffer_bytes) +{ + int16_t *buffer; + unsigned int buffer_len; + unsigned int filled = 0; + + // Buffer length in samples (quadrupled, because of 16-bit and stereo) + + buffer = (int16_t *) byte_buffer; + buffer_len = buffer_bytes / 4; + + // Repeatedly call the FMOPL update function until the buffer is + // full. + + while (filled < buffer_len) + { + unsigned int next_callback_time; + unsigned int nsamples; + + SDL_LockMutex(callback_queue_mutex); + + // Work out the time until the next callback waiting in + // the callback queue must be invoked. We can then fill the + // buffer with this many samples. + + if (opl_sdl_paused || OPL_Queue_IsEmpty(callback_queue)) + { + nsamples = buffer_len - filled; + } + else + { + next_callback_time = OPL_Queue_Peek(callback_queue) + pause_offset; + + nsamples = next_callback_time - current_time; + + if (nsamples > 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) +{ + Mix_SetPostMix(NULL, NULL); + + if (sdl_was_initialized) + { + Mix_CloseAudio(); + SDL_QuitSubSystem(SDL_INIT_AUDIO); + OPL_Queue_Destroy(callback_queue); + free(mix_buffer); + sdl_was_initialized = 0; + } + + if (opl_emulator != NULL) + { + 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; + } +} + +// 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 - pause_offset + interval_samples); + SDL_UnlockMutex(callback_queue_mutex); +} + +static int OPL_SDL_Init(unsigned int port_base) +{ + // Check if SDL_mixer has been opened already + // If not, we must initialize it now + + if (!SDLIsInitialized()) + { + 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_initialized = 1; + } + else + { + sdl_was_initialized = 0; + } + + opl_sdl_paused = 0; + pause_offset = 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); + + // 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; + } + + // Mix buffer: + + mix_buffer = malloc(mixing_freq * 2); + + // Create the emulator structure: + + opl_emulator = makeAdlibOPL(mixing_freq); + + if (opl_emulator == NULL) + { + fprintf(stderr, "Failed to initialize software OPL emulator!\n"); + OPL_SDL_Shutdown(); + return 0; + } + + OPLSetTimerHandler(opl_emulator, TimerHandler, 0); + + callback_mutex = SDL_CreateMutex(); + callback_queue_mutex = SDL_CreateMutex(); + + // 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); + } +} + +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 - pause_offset + (ms * mixing_freq) / 1000); + 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); +} + +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", + OPL_SDL_Init, + OPL_SDL_Shutdown, + OPL_SDL_PortRead, + OPL_SDL_PortWrite, + OPL_SDL_SetCallback, + OPL_SDL_ClearCallbacks, + OPL_SDL_Lock, + OPL_SDL_Unlock, + OPL_SDL_SetPaused +}; + diff --git a/opl/opl_timer.c b/opl/opl_timer.c new file mode 100644 index 00000000..35b2092f --- /dev/null +++ b/opl/opl_timer.c @@ -0,0 +1,251 @@ +// 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; + +// 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. + +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 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. + + 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) + pause_offset; + + 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; +} + +static void InitResources(void) +{ + 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(); + opl_timer_paused = 0; + pause_offset = 0; + + timer_thread = SDL_CreateThread(ThreadFunction, NULL); + + if (timer_thread == NULL) + { + timer_thread_state = THREAD_STATE_STOPPED; + FreeResources(); + + 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); + } + + FreeResources(); +} + +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 - pause_offset); + 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); +} + +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 new file mode 100644 index 00000000..f03fc499 --- /dev/null +++ b/opl/opl_timer.h @@ -0,0 +1,42 @@ +// 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_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/opl/opl_win9x.c b/opl/opl_win9x.c new file mode 100644 index 00000000..ff527b3e --- /dev/null +++ b/opl/opl_win9x.c @@ -0,0 +1,140 @@ +// 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 <windows.h> + +#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 */ + |