diff --git a/prboom2/src/CMakeLists.txt b/prboom2/src/CMakeLists.txt index ce3d57d7c..f79c8f956 100644 --- a/prboom2/src/CMakeLists.txt +++ b/prboom2/src/CMakeLists.txt @@ -457,6 +457,14 @@ set(DOOMMUSIC_SOURCES MUSIC/midifile.c MUSIC/midifile.h MUSIC/musicplayer.h + MUSIC/opl.c + MUSIC/opl.h + MUSIC/opl3.c + MUSIC/opl3.h + MUSIC/oplplayer.c + MUSIC/oplplayer.h + MUSIC/opl_queue.c + MUSIC/opl_queue.h MUSIC/portmidiplayer.c MUSIC/portmidiplayer.h MUSIC/vorbisplayer.c diff --git a/prboom2/src/MUSIC/midifile.c b/prboom2/src/MUSIC/midifile.c index 8ef8366b5..d1a18d171 100644 --- a/prboom2/src/MUSIC/midifile.c +++ b/prboom2/src/MUSIC/midifile.c @@ -28,8 +28,14 @@ #include #include +#ifndef TEST #include "doomdef.h" #include "doomtype.h" +#else +typedef enum {false, true} dboolean; +typedef unsigned char byte; +#define PACKEDATTR __attribute__((packed)) +#endif #include "lprintf.h" #include "midifile.h" @@ -1029,3 +1035,237 @@ double MIDI_spmc (const midi_file_t *file, const midi_event_t *ev, unsigned sndr return compute_spmc_normal (headerval, tempo, sndrate); } + +/* +The timing system used by the OPL driver is very interesting. But there are too many edge cases +in multitrack (type 1) midi tempo changes that it simply can't handle without a major rework. +The alternative is that we recook the file into a single track file with no tempo changes at +load time. +*/ + +midi_file_t *MIDI_LoadFileSpecial (midimem_t *mf) +{ + midi_event_t **flatlist; + midi_file_t *base = MIDI_LoadFile (mf); + midi_file_t *ret; + + double opi; + + int epos = 0; + + if (!base) + return NULL; + + flatlist = MIDI_GenerateFlatList (base); + if (!flatlist) + { + MIDI_FreeFile (base); + return NULL; + } + + ret = (midi_file_t*)Z_Malloc (sizeof (midi_file_t)); + + ret->header.format_type = 0; + ret->header.num_tracks = 1; + ret->header.time_division = 10000; + ret->num_tracks = 1; + ret->buffer_size = 0; + ret->buffer = NULL; + ret->tracks = (midi_track_t*)Z_Malloc (sizeof (midi_track_t)); + + ret->tracks->num_events = 0; + ret->tracks->num_event_mem = 0; + ret->tracks->events = NULL; + + opi = MIDI_spmc (base, NULL, 20000); + + + while (1) + { + midi_event_t *oldev; + midi_event_t *nextev; + + if (ret->tracks->num_events == ret->tracks->num_event_mem) + { + ret->tracks->num_event_mem += 100; + ret->tracks->events = (midi_event_t*)Z_Realloc (ret->tracks->events, sizeof (midi_event_t) * ret->tracks->num_event_mem); + } + + oldev = flatlist[epos]; + nextev = ret->tracks->events + ret->tracks->num_events; + + + // figure delta time + nextev->delta_time = (unsigned int)(opi * oldev->delta_time); + + if (oldev->event_type == MIDI_EVENT_SYSEX || + oldev->event_type == MIDI_EVENT_SYSEX_SPLIT) + // opl player can't process any sysex... + { + epos++; + continue; + } + + if (oldev->event_type == MIDI_EVENT_META) + { + if (oldev->data.meta.type == MIDI_META_SET_TEMPO) + { // adjust future tempo scaling + opi = MIDI_spmc (base, oldev, 20000); + // insert event as dummy + nextev->event_type = MIDI_EVENT_META; + nextev->data.meta.type = MIDI_META_TEXT; + nextev->data.meta.length = 0; + nextev->data.meta.data = (byte*)Z_Malloc (4); + epos++; + ret->tracks->num_events++; + continue; + } + if (oldev->data.meta.type == MIDI_META_END_OF_TRACK) + { // reproduce event and break + nextev->event_type = MIDI_EVENT_META; + nextev->data.meta.type = MIDI_META_END_OF_TRACK; + nextev->data.meta.length = 0; + nextev->data.meta.data = (byte*)Z_Malloc (4); + epos++; + ret->tracks->num_events++; + break; + } + // other meta events not needed + epos++; + continue; + } + // non meta events can simply be copied (excluding delta time) + memcpy (&nextev->event_type, &oldev->event_type, sizeof (midi_event_t) - sizeof (unsigned)); + epos++; + ret->tracks->num_events++; + } + + MIDI_DestroyFlatList (flatlist); + MIDI_FreeFile (base); + return ret; +} + + + +#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)"; + } +} + +void PrintTrack (midi_track_t *track) +{ + midi_event_t *event; + unsigned int i; + + for (i=0; inum_events; ++i) + { + 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); + + 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; + } + } +} + +int main(int argc, char *argv[]) +{ + FILE *f; + midimem_t mf; + midi_file_t *file; + unsigned int i; + + if (argc < 2) + { + printf("Usage: %s \n", argv[0]); + exit(1); + } + f = fopen (argv[1], "rb"); + if (!f) + { + fprintf(stderr, "Failed to open %s\n", argv[1]); + exit(1); + } + fseek (f, 0, SEEK_END); + mf.len = ftell (f); + mf.pos = 0; + rewind (f); + mf.data = Z_Malloc (mf.len); + fread (mf.data, 1, mf.len, f); + fclose (f); + + file = MIDI_LoadFile (&mf); + + 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; +} + +#endif diff --git a/prboom2/src/MUSIC/midifile.h b/prboom2/src/MUSIC/midifile.h index ce4e84608..33c0e3e55 100644 --- a/prboom2/src/MUSIC/midifile.h +++ b/prboom2/src/MUSIC/midifile.h @@ -66,7 +66,9 @@ typedef enum MIDI_CONTROLLER_DATA_ENTRY = 0x5, MIDI_CONTROLLER_MAIN_VOLUME = 0x7, - MIDI_CONTROLLER_PAN = 0xa + MIDI_CONTROLLER_PAN = 0xa, + + MIDI_CONTROLLER_ALL_NOTES_OFF = 0x7b } midi_controller_t; typedef enum @@ -188,4 +190,6 @@ void MIDI_DestroyFlatList (midi_event_t **evs); // NSM: timing calculator double MIDI_spmc (const midi_file_t *file, const midi_event_t *ev, unsigned sndrate); +midi_file_t *MIDI_LoadFileSpecial (midimem_t *mf); + #endif /* #ifndef MIDIFILE_H */ diff --git a/prboom2/src/MUSIC/opl.c b/prboom2/src/MUSIC/opl.c new file mode 100644 index 000000000..adcc4948e --- /dev/null +++ b/prboom2/src/MUSIC/opl.c @@ -0,0 +1,423 @@ +// 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 +#include + +#include "opl3.h" +#include "opl.h" +#include "opl_queue.h" + +#include "dsda/configuration.h" + +static int init_stage_reg_writes = 1; + +unsigned int opl_sample_rate = 22050; + +#define MAX_SOUND_SLICE_TIME 100 /* ms */ + +typedef struct +{ + unsigned int rate; // Number of times the timer is advanced per sec. + unsigned int enabled; // Non-zero if timer is enabled. + unsigned int value; // Last value that was set. + uint64_t expire_time; // Calculated time that timer will expire. +} opl_timer_t; + +// Queue of callbacks waiting to be invoked. +static opl_callback_queue_t *callback_queue; + +// Current time, in us since startup: +static uint64_t current_time; + +// If non-zero, playback is currently paused. +static int opl_paused; + +// Time offset (in us) due to the fact that callbacks +// were previously paused. +static uint64_t pause_offset; + +// OPL software emulator structure. +static opl3_chip opl_chip; +static int opl_opl3mode; + +// Register number that was written. +static int register_num = 0; + +// Timers; DBOPL does not do timer stuff itself. +static opl_timer_t timer1 = { 12500, 0, 0, 0 }; +static opl_timer_t timer2 = { 3125, 0, 0, 0 }; + +// Initialize the OPL library. Returns true if initialized +// successfully. +int OPL_Init (unsigned int rate) +{ + opl_opl3mode = dsda_IntConfig(dsda_config_mus_opl_opl3mode); + + opl_sample_rate = rate; + opl_paused = 0; + pause_offset = 0; + + // Queue structure of callbacks to invoke. + + callback_queue = OPL_Queue_Create(); + current_time = 0; + + // Create the emulator structure: + OPL3_Reset(&opl_chip, opl_sample_rate); + + OPL_InitRegisters(opl_opl3mode); + + init_stage_reg_writes = 0; + + return 1; +} + +// Shut down the OPL library. +void OPL_Shutdown(void) +{ + if (callback_queue) + { + OPL_Queue_Destroy(callback_queue); + + callback_queue = NULL; + } +} + +void OPL_SetCallback(uint64_t us, + opl_callback_t callback, + void *data) +{ + OPL_Queue_Push(callback_queue, callback, data, + current_time - pause_offset + us); +} + +void OPL_ClearCallbacks(void) +{ + OPL_Queue_Clear(callback_queue); +} + +static void OPLTimer_CalculateEndTime(opl_timer_t *timer) +{ + int tics; + + // If the timer is enabled, calculate the time when the timer + // will expire. + if (timer->enabled) + { + tics = 0x100 - timer->value; + timer->expire_time = current_time + + ((uint64_t) tics * OPL_SECOND) / timer->rate; + } +} + +static void WriteRegister(unsigned int reg_num, unsigned int value) +{ + switch (reg_num) + { + case OPL_REG_TIMER1: + timer1.value = value; + OPLTimer_CalculateEndTime(&timer1); + break; + + case OPL_REG_TIMER2: + timer2.value = value; + OPLTimer_CalculateEndTime(&timer2); + break; + + case OPL_REG_TIMER_CTRL: + if (value & 0x80) + { + timer1.enabled = 0; + timer2.enabled = 0; + } + else + { + if ((value & 0x40) == 0) + { + timer1.enabled = (value & 0x01) != 0; + OPLTimer_CalculateEndTime(&timer1); + } + + if ((value & 0x20) == 0) + { + timer1.enabled = (value & 0x02) != 0; + OPLTimer_CalculateEndTime(&timer2); + } + } + + break; + + case OPL_REG_NEW: + opl_opl3mode = value & 0x01; + + default: + OPL3_WriteRegBuffered(&opl_chip, reg_num, value); + break; + } +} + +static void OPL_AdvanceTime(unsigned int nsamples) +{ + opl_callback_t callback; + void *callback_data; + uint64_t us; + + // Advance time. + us = ((uint64_t) nsamples * OPL_SECOND) / opl_sample_rate; + current_time += us; + + if (opl_paused) + { + pause_offset += us; + } + + // 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; + } + + callback(callback_data); + } +} + +void OPL_AdjustCallbacks(float factor) +{ + OPL_Queue_AdjustCallbacks(callback_queue, current_time, factor); +} + +void OPL_Render_Samples (void *dest, unsigned buffer_len) +{ + unsigned int filled = 0; + int16_t *buffer = (int16_t *) dest; + + // Repeatedly call the OPL emulator update function until the buffer is + // full. + while (filled < buffer_len) + { + uint64_t next_callback_time; + uint64_t nsamples; + + // 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_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) * opl_sample_rate; + nsamples = (nsamples + OPL_SECOND - 1) / OPL_SECOND; + + if (nsamples > buffer_len - filled) + { + nsamples = buffer_len - filled; + } + } + + // Add emulator output to buffer. + OPL3_GenerateStream(&opl_chip, buffer + filled * 2, nsamples); + filled += nsamples; + + // Invoke callbacks for this point in time. + OPL_AdvanceTime(nsamples); + } +} + +void OPL_WritePort(opl_port_t port, unsigned int value) +{ + if (port == OPL_REGISTER_PORT) + { + register_num = value; + } + else if (port == OPL_REGISTER_PORT_OPL3) + { + register_num = value | 0x100; + } + else if (port == OPL_DATA_PORT) + { + WriteRegister(register_num, value); + } +} + +unsigned int OPL_ReadPort(opl_port_t port) +{ + unsigned int result = 0; + + if (port == OPL_REGISTER_PORT_OPL3) + { + return 0xff; + } + + if (timer1.enabled && current_time > timer1.expire_time) + { + result |= 0x80; // Either have expired + result |= 0x40; // Timer 1 has expired + } + + if (timer2.enabled && current_time > timer2.expire_time) + { + result |= 0x80; // Either have expired + result |= 0x20; // Timer 2 has expired + } + + return result; +} + +// +// 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; + + if (reg & 0x100) + { + OPL_WritePort(OPL_REGISTER_PORT_OPL3, reg); + } + else + { + 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(); + } +} + +// Initialize registers on startup +void OPL_InitRegisters(int opl3) +{ + 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); + + if (opl3) + { + OPL_WriteRegister(OPL_REG_NEW, 0x01); + + // Initialize level registers + for (r=OPL_REGS_LEVEL; r <= OPL_REGS_LEVEL + OPL_NUM_OPERATORS; ++r) + { + OPL_WriteRegister(r | 0x100, 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 | 0x100, 0x00); + } + + // More registers ... + for (r=1; r < OPL_REGS_LEVEL; ++r) + { + OPL_WriteRegister(r | 0x100, 0x00); + } + } + + // Keyboard split point on (?) + OPL_WriteRegister(OPL_REG_FM_MODE, 0x40); + + if (opl3) + { + OPL_WriteRegister(OPL_REG_NEW, 0x01); + } +} + +void OPL_SetPaused(int paused) +{ + opl_paused = paused; +} diff --git a/prboom2/src/MUSIC/opl.h b/prboom2/src/MUSIC/opl.h new file mode 100644 index 000000000..507524298 --- /dev/null +++ b/prboom2/src/MUSIC/opl.h @@ -0,0 +1,130 @@ +// 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_REGISTER_PORT_OPL3 = 2 +} 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 +#define OPL_REG_NEW 0x105 + +// 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 + +// Times + +#define OPL_SECOND ((uint64_t) 1000 * 1000) + +// +// 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(int opl3); + + +// Block until the specified number of milliseconds have elapsed. + +void OPL_Delay(unsigned int ms); + +// Pause the OPL callbacks. + +void OPL_SetPaused(int paused); + + +extern unsigned int opl_sample_rate; + +void OPL_Render_Samples (void *dest, unsigned nsamp); + + +void OPL_SetCallback(uint64_t us, opl_callback_t callback, void *data); + +void OPL_ClearCallbacks(void); + +void OPL_AdjustCallbacks(float tempo); + +#endif diff --git a/prboom2/src/MUSIC/opl3.c b/prboom2/src/MUSIC/opl3.c new file mode 100644 index 000000000..25acd991c --- /dev/null +++ b/prboom2/src/MUSIC/opl3.c @@ -0,0 +1,1387 @@ +// +// Copyright (C) 2013-2018 Alexey Khokholov (Nuke.YKT) +// +// 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. +// +// +// Nuked OPL3 emulator. +// Thanks: +// MAME Development Team(Jarek Burczynski, Tatsuyuki Satoh): +// Feedback and Rhythm part calculation information. +// forums.submarine.org.uk(carbon14, opl3): +// Tremolo and phase generator calculation information. +// OPLx decapsulated(Matthew Gambrell, Olli Niemitalo): +// OPL2 ROMs. +// siliconpr0n.org(John McMaster, digshadow): +// YMF262 and VRC VII decaps and die shots. +// +// version: 1.8 +// + +#include +#include +#include +#include "opl3.h" + +#include "dsda/configuration.h" +static int mus_opl_gain; + +#define RSM_FRAC 10 + +// Channel types + +enum { + ch_2op = 0, + ch_4op = 1, + ch_4op2 = 2, + ch_drum = 3 +}; + +// Envelope key types + +enum { + egk_norm = 0x01, + egk_drum = 0x02 +}; + + +// +// logsin table +// + +static const Bit16u logsinrom[256] = { + 0x859, 0x6c3, 0x607, 0x58b, 0x52e, 0x4e4, 0x4a6, 0x471, + 0x443, 0x41a, 0x3f5, 0x3d3, 0x3b5, 0x398, 0x37e, 0x365, + 0x34e, 0x339, 0x324, 0x311, 0x2ff, 0x2ed, 0x2dc, 0x2cd, + 0x2bd, 0x2af, 0x2a0, 0x293, 0x286, 0x279, 0x26d, 0x261, + 0x256, 0x24b, 0x240, 0x236, 0x22c, 0x222, 0x218, 0x20f, + 0x206, 0x1fd, 0x1f5, 0x1ec, 0x1e4, 0x1dc, 0x1d4, 0x1cd, + 0x1c5, 0x1be, 0x1b7, 0x1b0, 0x1a9, 0x1a2, 0x19b, 0x195, + 0x18f, 0x188, 0x182, 0x17c, 0x177, 0x171, 0x16b, 0x166, + 0x160, 0x15b, 0x155, 0x150, 0x14b, 0x146, 0x141, 0x13c, + 0x137, 0x133, 0x12e, 0x129, 0x125, 0x121, 0x11c, 0x118, + 0x114, 0x10f, 0x10b, 0x107, 0x103, 0x0ff, 0x0fb, 0x0f8, + 0x0f4, 0x0f0, 0x0ec, 0x0e9, 0x0e5, 0x0e2, 0x0de, 0x0db, + 0x0d7, 0x0d4, 0x0d1, 0x0cd, 0x0ca, 0x0c7, 0x0c4, 0x0c1, + 0x0be, 0x0bb, 0x0b8, 0x0b5, 0x0b2, 0x0af, 0x0ac, 0x0a9, + 0x0a7, 0x0a4, 0x0a1, 0x09f, 0x09c, 0x099, 0x097, 0x094, + 0x092, 0x08f, 0x08d, 0x08a, 0x088, 0x086, 0x083, 0x081, + 0x07f, 0x07d, 0x07a, 0x078, 0x076, 0x074, 0x072, 0x070, + 0x06e, 0x06c, 0x06a, 0x068, 0x066, 0x064, 0x062, 0x060, + 0x05e, 0x05c, 0x05b, 0x059, 0x057, 0x055, 0x053, 0x052, + 0x050, 0x04e, 0x04d, 0x04b, 0x04a, 0x048, 0x046, 0x045, + 0x043, 0x042, 0x040, 0x03f, 0x03e, 0x03c, 0x03b, 0x039, + 0x038, 0x037, 0x035, 0x034, 0x033, 0x031, 0x030, 0x02f, + 0x02e, 0x02d, 0x02b, 0x02a, 0x029, 0x028, 0x027, 0x026, + 0x025, 0x024, 0x023, 0x022, 0x021, 0x020, 0x01f, 0x01e, + 0x01d, 0x01c, 0x01b, 0x01a, 0x019, 0x018, 0x017, 0x017, + 0x016, 0x015, 0x014, 0x014, 0x013, 0x012, 0x011, 0x011, + 0x010, 0x00f, 0x00f, 0x00e, 0x00d, 0x00d, 0x00c, 0x00c, + 0x00b, 0x00a, 0x00a, 0x009, 0x009, 0x008, 0x008, 0x007, + 0x007, 0x007, 0x006, 0x006, 0x005, 0x005, 0x005, 0x004, + 0x004, 0x004, 0x003, 0x003, 0x003, 0x002, 0x002, 0x002, + 0x002, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000 +}; + +// +// exp table +// + +static const Bit16u exprom[256] = { + 0x7fa, 0x7f5, 0x7ef, 0x7ea, 0x7e4, 0x7df, 0x7da, 0x7d4, + 0x7cf, 0x7c9, 0x7c4, 0x7bf, 0x7b9, 0x7b4, 0x7ae, 0x7a9, + 0x7a4, 0x79f, 0x799, 0x794, 0x78f, 0x78a, 0x784, 0x77f, + 0x77a, 0x775, 0x770, 0x76a, 0x765, 0x760, 0x75b, 0x756, + 0x751, 0x74c, 0x747, 0x742, 0x73d, 0x738, 0x733, 0x72e, + 0x729, 0x724, 0x71f, 0x71a, 0x715, 0x710, 0x70b, 0x706, + 0x702, 0x6fd, 0x6f8, 0x6f3, 0x6ee, 0x6e9, 0x6e5, 0x6e0, + 0x6db, 0x6d6, 0x6d2, 0x6cd, 0x6c8, 0x6c4, 0x6bf, 0x6ba, + 0x6b5, 0x6b1, 0x6ac, 0x6a8, 0x6a3, 0x69e, 0x69a, 0x695, + 0x691, 0x68c, 0x688, 0x683, 0x67f, 0x67a, 0x676, 0x671, + 0x66d, 0x668, 0x664, 0x65f, 0x65b, 0x657, 0x652, 0x64e, + 0x649, 0x645, 0x641, 0x63c, 0x638, 0x634, 0x630, 0x62b, + 0x627, 0x623, 0x61e, 0x61a, 0x616, 0x612, 0x60e, 0x609, + 0x605, 0x601, 0x5fd, 0x5f9, 0x5f5, 0x5f0, 0x5ec, 0x5e8, + 0x5e4, 0x5e0, 0x5dc, 0x5d8, 0x5d4, 0x5d0, 0x5cc, 0x5c8, + 0x5c4, 0x5c0, 0x5bc, 0x5b8, 0x5b4, 0x5b0, 0x5ac, 0x5a8, + 0x5a4, 0x5a0, 0x59c, 0x599, 0x595, 0x591, 0x58d, 0x589, + 0x585, 0x581, 0x57e, 0x57a, 0x576, 0x572, 0x56f, 0x56b, + 0x567, 0x563, 0x560, 0x55c, 0x558, 0x554, 0x551, 0x54d, + 0x549, 0x546, 0x542, 0x53e, 0x53b, 0x537, 0x534, 0x530, + 0x52c, 0x529, 0x525, 0x522, 0x51e, 0x51b, 0x517, 0x514, + 0x510, 0x50c, 0x509, 0x506, 0x502, 0x4ff, 0x4fb, 0x4f8, + 0x4f4, 0x4f1, 0x4ed, 0x4ea, 0x4e7, 0x4e3, 0x4e0, 0x4dc, + 0x4d9, 0x4d6, 0x4d2, 0x4cf, 0x4cc, 0x4c8, 0x4c5, 0x4c2, + 0x4be, 0x4bb, 0x4b8, 0x4b5, 0x4b1, 0x4ae, 0x4ab, 0x4a8, + 0x4a4, 0x4a1, 0x49e, 0x49b, 0x498, 0x494, 0x491, 0x48e, + 0x48b, 0x488, 0x485, 0x482, 0x47e, 0x47b, 0x478, 0x475, + 0x472, 0x46f, 0x46c, 0x469, 0x466, 0x463, 0x460, 0x45d, + 0x45a, 0x457, 0x454, 0x451, 0x44e, 0x44b, 0x448, 0x445, + 0x442, 0x43f, 0x43c, 0x439, 0x436, 0x433, 0x430, 0x42d, + 0x42a, 0x428, 0x425, 0x422, 0x41f, 0x41c, 0x419, 0x416, + 0x414, 0x411, 0x40e, 0x40b, 0x408, 0x406, 0x403, 0x400 +}; + +// +// freq mult table multiplied by 2 +// +// 1/2, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 12, 12, 15, 15 +// + +static const Bit8u mt[16] = { + 1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 20, 24, 24, 30, 30 +}; + +// +// ksl table +// + +static const Bit8u kslrom[16] = { + 0, 32, 40, 45, 48, 51, 53, 55, 56, 58, 59, 60, 61, 62, 63, 64 +}; + +static const Bit8u kslshift[4] = { + 8, 1, 2, 0 +}; + +// +// envelope generator constants +// + +static const Bit8u eg_incstep[4][4] = { + { 0, 0, 0, 0 }, + { 1, 0, 0, 0 }, + { 1, 0, 1, 0 }, + { 1, 1, 1, 0 } +}; + +// +// address decoding +// + +static const Bit8s ad_slot[0x20] = { + 0, 1, 2, 3, 4, 5, -1, -1, 6, 7, 8, 9, 10, 11, -1, -1, + 12, 13, 14, 15, 16, 17, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 +}; + +static const Bit8u ch_slot[18] = { + 0, 1, 2, 6, 7, 8, 12, 13, 14, 18, 19, 20, 24, 25, 26, 30, 31, 32 +}; + +// +// Envelope generator +// + +typedef Bit16s(*envelope_sinfunc)(Bit16u phase, Bit16u envelope); +typedef void(*envelope_genfunc)(opl3_slot *slott); + +static Bit16s OPL3_EnvelopeCalcExp(Bit32u level) +{ + if (level > 0x1fff) + { + level = 0x1fff; + } + return (exprom[level & 0xff] << 1) >> (level >> 8); +} + +static Bit16s OPL3_EnvelopeCalcSin0(Bit16u phase, Bit16u envelope) +{ + Bit16u out = 0; + Bit16u neg = 0; + phase &= 0x3ff; + if (phase & 0x200) + { + neg = 0xffff; + } + if (phase & 0x100) + { + out = logsinrom[(phase & 0xff) ^ 0xff]; + } + else + { + out = logsinrom[phase & 0xff]; + } + return OPL3_EnvelopeCalcExp(out + (envelope << 3)) ^ neg; +} + +static Bit16s OPL3_EnvelopeCalcSin1(Bit16u phase, Bit16u envelope) +{ + Bit16u out = 0; + phase &= 0x3ff; + if (phase & 0x200) + { + out = 0x1000; + } + else if (phase & 0x100) + { + out = logsinrom[(phase & 0xff) ^ 0xff]; + } + else + { + out = logsinrom[phase & 0xff]; + } + return OPL3_EnvelopeCalcExp(out + (envelope << 3)); +} + +static Bit16s OPL3_EnvelopeCalcSin2(Bit16u phase, Bit16u envelope) +{ + Bit16u out = 0; + phase &= 0x3ff; + if (phase & 0x100) + { + out = logsinrom[(phase & 0xff) ^ 0xff]; + } + else + { + out = logsinrom[phase & 0xff]; + } + return OPL3_EnvelopeCalcExp(out + (envelope << 3)); +} + +static Bit16s OPL3_EnvelopeCalcSin3(Bit16u phase, Bit16u envelope) +{ + Bit16u out = 0; + phase &= 0x3ff; + if (phase & 0x100) + { + out = 0x1000; + } + else + { + out = logsinrom[phase & 0xff]; + } + return OPL3_EnvelopeCalcExp(out + (envelope << 3)); +} + +static Bit16s OPL3_EnvelopeCalcSin4(Bit16u phase, Bit16u envelope) +{ + Bit16u out = 0; + Bit16u neg = 0; + phase &= 0x3ff; + if ((phase & 0x300) == 0x100) + { + neg = 0xffff; + } + if (phase & 0x200) + { + out = 0x1000; + } + else if (phase & 0x80) + { + out = logsinrom[((phase ^ 0xff) << 1) & 0xff]; + } + else + { + out = logsinrom[(phase << 1) & 0xff]; + } + return OPL3_EnvelopeCalcExp(out + (envelope << 3)) ^ neg; +} + +static Bit16s OPL3_EnvelopeCalcSin5(Bit16u phase, Bit16u envelope) +{ + Bit16u out = 0; + phase &= 0x3ff; + if (phase & 0x200) + { + out = 0x1000; + } + else if (phase & 0x80) + { + out = logsinrom[((phase ^ 0xff) << 1) & 0xff]; + } + else + { + out = logsinrom[(phase << 1) & 0xff]; + } + return OPL3_EnvelopeCalcExp(out + (envelope << 3)); +} + +static Bit16s OPL3_EnvelopeCalcSin6(Bit16u phase, Bit16u envelope) +{ + Bit16u neg = 0; + phase &= 0x3ff; + if (phase & 0x200) + { + neg = 0xffff; + } + return OPL3_EnvelopeCalcExp(envelope << 3) ^ neg; +} + +static Bit16s OPL3_EnvelopeCalcSin7(Bit16u phase, Bit16u envelope) +{ + Bit16u out = 0; + Bit16u neg = 0; + phase &= 0x3ff; + if (phase & 0x200) + { + neg = 0xffff; + phase = (phase & 0x1ff) ^ 0x1ff; + } + out = phase << 3; + return OPL3_EnvelopeCalcExp(out + (envelope << 3)) ^ neg; +} + +static const envelope_sinfunc envelope_sin[8] = { + OPL3_EnvelopeCalcSin0, + OPL3_EnvelopeCalcSin1, + OPL3_EnvelopeCalcSin2, + OPL3_EnvelopeCalcSin3, + OPL3_EnvelopeCalcSin4, + OPL3_EnvelopeCalcSin5, + OPL3_EnvelopeCalcSin6, + OPL3_EnvelopeCalcSin7 +}; + +enum envelope_gen_num +{ + envelope_gen_num_attack = 0, + envelope_gen_num_decay = 1, + envelope_gen_num_sustain = 2, + envelope_gen_num_release = 3 +}; + +static void OPL3_EnvelopeUpdateKSL(opl3_slot *slot) +{ + Bit16s ksl = (kslrom[slot->channel->f_num >> 6] << 2) + - ((0x08 - slot->channel->block) << 5); + if (ksl < 0) + { + ksl = 0; + } + slot->eg_ksl = (Bit8u)ksl; +} + +static void OPL3_EnvelopeCalc(opl3_slot *slot) +{ + Bit8u nonzero; + Bit8u rate; + Bit8u rate_hi; + Bit8u rate_lo; + Bit8u reg_rate = 0; + Bit8u ks; + Bit8u eg_shift, shift; + Bit16u eg_rout; + Bit16s eg_inc; + Bit8u eg_off; + Bit8u reset = 0; + slot->eg_out = slot->eg_rout + (slot->reg_tl << 2) + + (slot->eg_ksl >> kslshift[slot->reg_ksl]) + *slot->trem; + if (slot->key && slot->eg_gen == envelope_gen_num_release) + { + reset = 1; + reg_rate = slot->reg_ar; + } + else + { + switch (slot->eg_gen) + { + case envelope_gen_num_attack: + reg_rate = slot->reg_ar; + break; + case envelope_gen_num_decay: + reg_rate = slot->reg_dr; + break; + case envelope_gen_num_sustain: + if (!slot->reg_type) + { + reg_rate = slot->reg_rr; + } + break; + case envelope_gen_num_release: + reg_rate = slot->reg_rr; + break; + } + } + slot->pg_reset = reset; + ks = slot->channel->ksv >> ((slot->reg_ksr ^ 1) << 1); + nonzero = (reg_rate != 0); + rate = ks + (reg_rate << 2); + rate_hi = rate >> 2; + rate_lo = rate & 0x03; + if (rate_hi & 0x10) + { + rate_hi = 0x0f; + } + eg_shift = rate_hi + slot->chip->eg_add; + shift = 0; + if (nonzero) + { + if (rate_hi < 12) + { + if (slot->chip->eg_state) + { + switch (eg_shift) + { + case 12: + shift = 1; + break; + case 13: + shift = (rate_lo >> 1) & 0x01; + break; + case 14: + shift = rate_lo & 0x01; + break; + default: + break; + } + } + } + else + { + shift = (rate_hi & 0x03) + eg_incstep[rate_lo][slot->chip->timer & 0x03]; + if (shift & 0x04) + { + shift = 0x03; + } + if (!shift) + { + shift = slot->chip->eg_state; + } + } + } + eg_rout = slot->eg_rout; + eg_inc = 0; + eg_off = 0; + // Instant attack + if (reset && rate_hi == 0x0f) + { + eg_rout = 0x00; + } + // Envelope off + if ((slot->eg_rout & 0x1f8) == 0x1f8) + { + eg_off = 1; + } + if (slot->eg_gen != envelope_gen_num_attack && !reset && eg_off) + { + eg_rout = 0x1ff; + } + switch (slot->eg_gen) + { + case envelope_gen_num_attack: + if (!slot->eg_rout) + { + slot->eg_gen = envelope_gen_num_decay; + } + else if (slot->key && shift > 0 && rate_hi != 0x0f) + { + eg_inc = ((~slot->eg_rout) << shift) >> 4; + } + break; + case envelope_gen_num_decay: + if ((slot->eg_rout >> 4) == slot->reg_sl) + { + slot->eg_gen = envelope_gen_num_sustain; + } + else if (!eg_off && !reset && shift > 0) + { + eg_inc = 1 << (shift - 1); + } + break; + case envelope_gen_num_sustain: + case envelope_gen_num_release: + if (!eg_off && !reset && shift > 0) + { + eg_inc = 1 << (shift - 1); + } + break; + } + slot->eg_rout = (eg_rout + eg_inc) & 0x1ff; + // Key off + if (reset) + { + slot->eg_gen = envelope_gen_num_attack; + } + if (!slot->key) + { + slot->eg_gen = envelope_gen_num_release; + } +} + +static void OPL3_EnvelopeKeyOn(opl3_slot *slot, Bit8u type) +{ + slot->key |= type; +} + +static void OPL3_EnvelopeKeyOff(opl3_slot *slot, Bit8u type) +{ + slot->key &= ~type; +} + +// +// Phase Generator +// + +static void OPL3_PhaseGenerate(opl3_slot *slot) +{ + opl3_chip *chip; + Bit16u f_num; + Bit32u basefreq; + Bit8u rm_xor, n_bit; + Bit32u noise; + Bit16u phase; + + chip = slot->chip; + f_num = slot->channel->f_num; + if (slot->reg_vib) + { + Bit8s range; + Bit8u vibpos; + + range = (f_num >> 7) & 7; + vibpos = slot->chip->vibpos; + + if (!(vibpos & 3)) + { + range = 0; + } + else if (vibpos & 1) + { + range >>= 1; + } + range >>= slot->chip->vibshift; + + if (vibpos & 4) + { + range = -range; + } + f_num += range; + } + basefreq = (f_num << slot->channel->block) >> 1; + phase = (Bit16u)(slot->pg_phase >> 9); + if (slot->pg_reset) + { + slot->pg_phase = 0; + } + slot->pg_phase += (basefreq * mt[slot->reg_mult]) >> 1; + // Rhythm mode + noise = chip->noise; + slot->pg_phase_out = phase; + if (slot->slot_num == 13) // hh + { + chip->rm_hh_bit2 = (phase >> 2) & 1; + chip->rm_hh_bit3 = (phase >> 3) & 1; + chip->rm_hh_bit7 = (phase >> 7) & 1; + chip->rm_hh_bit8 = (phase >> 8) & 1; + } + if (slot->slot_num == 17 && (chip->rhy & 0x20)) // tc + { + chip->rm_tc_bit3 = (phase >> 3) & 1; + chip->rm_tc_bit5 = (phase >> 5) & 1; + } + if (chip->rhy & 0x20) + { + rm_xor = (chip->rm_hh_bit2 ^ chip->rm_hh_bit7) + | (chip->rm_hh_bit3 ^ chip->rm_tc_bit5) + | (chip->rm_tc_bit3 ^ chip->rm_tc_bit5); + switch (slot->slot_num) + { + case 13: // hh + slot->pg_phase_out = rm_xor << 9; + if (rm_xor ^ (noise & 1)) + { + slot->pg_phase_out |= 0xd0; + } + else + { + slot->pg_phase_out |= 0x34; + } + break; + case 16: // sd + slot->pg_phase_out = (chip->rm_hh_bit8 << 9) + | ((chip->rm_hh_bit8 ^ (noise & 1)) << 8); + break; + case 17: // tc + slot->pg_phase_out = (rm_xor << 9) | 0x80; + break; + default: + break; + } + } + n_bit = ((noise >> 14) ^ noise) & 0x01; + chip->noise = (noise >> 1) | (n_bit << 22); +} + +// +// Slot +// + +static void OPL3_SlotWrite20(opl3_slot *slot, Bit8u data) +{ + if ((data >> 7) & 0x01) + { + slot->trem = &slot->chip->tremolo; + } + else + { + slot->trem = (Bit8u*)&slot->chip->zeromod; + } + slot->reg_vib = (data >> 6) & 0x01; + slot->reg_type = (data >> 5) & 0x01; + slot->reg_ksr = (data >> 4) & 0x01; + slot->reg_mult = data & 0x0f; +} + +static void OPL3_SlotWrite40(opl3_slot *slot, Bit8u data) +{ + slot->reg_ksl = (data >> 6) & 0x03; + slot->reg_tl = data & 0x3f; + OPL3_EnvelopeUpdateKSL(slot); +} + +static void OPL3_SlotWrite60(opl3_slot *slot, Bit8u data) +{ + slot->reg_ar = (data >> 4) & 0x0f; + slot->reg_dr = data & 0x0f; +} + +static void OPL3_SlotWrite80(opl3_slot *slot, Bit8u data) +{ + slot->reg_sl = (data >> 4) & 0x0f; + if (slot->reg_sl == 0x0f) + { + slot->reg_sl = 0x1f; + } + slot->reg_rr = data & 0x0f; +} + +static void OPL3_SlotWriteE0(opl3_slot *slot, Bit8u data) +{ + slot->reg_wf = data & 0x07; + if (slot->chip->newm == 0x00) + { + slot->reg_wf &= 0x03; + } +} + +static void OPL3_SlotGenerate(opl3_slot *slot) +{ + slot->out = envelope_sin[slot->reg_wf](slot->pg_phase_out + *slot->mod, slot->eg_out); +} + +static void OPL3_SlotCalcFB(opl3_slot *slot) +{ + if (slot->channel->fb != 0x00) + { + slot->fbmod = (slot->prout + slot->out) >> (0x09 - slot->channel->fb); + } + else + { + slot->fbmod = 0; + } + slot->prout = slot->out; +} + +// +// Channel +// + +static void OPL3_ChannelSetupAlg(opl3_channel *channel); + +static void OPL3_ChannelUpdateRhythm(opl3_chip *chip, Bit8u data) +{ + opl3_channel *channel6; + opl3_channel *channel7; + opl3_channel *channel8; + Bit8u chnum; + + chip->rhy = data & 0x3f; + if (chip->rhy & 0x20) + { + channel6 = &chip->channel[6]; + channel7 = &chip->channel[7]; + channel8 = &chip->channel[8]; + channel6->out[0] = &channel6->slots[1]->out; + channel6->out[1] = &channel6->slots[1]->out; + channel6->out[2] = &chip->zeromod; + channel6->out[3] = &chip->zeromod; + channel7->out[0] = &channel7->slots[0]->out; + channel7->out[1] = &channel7->slots[0]->out; + channel7->out[2] = &channel7->slots[1]->out; + channel7->out[3] = &channel7->slots[1]->out; + channel8->out[0] = &channel8->slots[0]->out; + channel8->out[1] = &channel8->slots[0]->out; + channel8->out[2] = &channel8->slots[1]->out; + channel8->out[3] = &channel8->slots[1]->out; + for (chnum = 6; chnum < 9; chnum++) + { + chip->channel[chnum].chtype = ch_drum; + } + OPL3_ChannelSetupAlg(channel6); + OPL3_ChannelSetupAlg(channel7); + OPL3_ChannelSetupAlg(channel8); + //hh + if (chip->rhy & 0x01) + { + OPL3_EnvelopeKeyOn(channel7->slots[0], egk_drum); + } + else + { + OPL3_EnvelopeKeyOff(channel7->slots[0], egk_drum); + } + //tc + if (chip->rhy & 0x02) + { + OPL3_EnvelopeKeyOn(channel8->slots[1], egk_drum); + } + else + { + OPL3_EnvelopeKeyOff(channel8->slots[1], egk_drum); + } + //tom + if (chip->rhy & 0x04) + { + OPL3_EnvelopeKeyOn(channel8->slots[0], egk_drum); + } + else + { + OPL3_EnvelopeKeyOff(channel8->slots[0], egk_drum); + } + //sd + if (chip->rhy & 0x08) + { + OPL3_EnvelopeKeyOn(channel7->slots[1], egk_drum); + } + else + { + OPL3_EnvelopeKeyOff(channel7->slots[1], egk_drum); + } + //bd + if (chip->rhy & 0x10) + { + OPL3_EnvelopeKeyOn(channel6->slots[0], egk_drum); + OPL3_EnvelopeKeyOn(channel6->slots[1], egk_drum); + } + else + { + OPL3_EnvelopeKeyOff(channel6->slots[0], egk_drum); + OPL3_EnvelopeKeyOff(channel6->slots[1], egk_drum); + } + } + else + { + for (chnum = 6; chnum < 9; chnum++) + { + chip->channel[chnum].chtype = ch_2op; + OPL3_ChannelSetupAlg(&chip->channel[chnum]); + OPL3_EnvelopeKeyOff(chip->channel[chnum].slots[0], egk_drum); + OPL3_EnvelopeKeyOff(chip->channel[chnum].slots[1], egk_drum); + } + } +} + +static void OPL3_ChannelWriteA0(opl3_channel *channel, Bit8u data) +{ + if (channel->chip->newm && channel->chtype == ch_4op2) + { + return; + } + channel->f_num = (channel->f_num & 0x300) | data; + channel->ksv = (channel->block << 1) + | ((channel->f_num >> (0x09 - channel->chip->nts)) & 0x01); + OPL3_EnvelopeUpdateKSL(channel->slots[0]); + OPL3_EnvelopeUpdateKSL(channel->slots[1]); + if (channel->chip->newm && channel->chtype == ch_4op) + { + channel->pair->f_num = channel->f_num; + channel->pair->ksv = channel->ksv; + OPL3_EnvelopeUpdateKSL(channel->pair->slots[0]); + OPL3_EnvelopeUpdateKSL(channel->pair->slots[1]); + } +} + +static void OPL3_ChannelWriteB0(opl3_channel *channel, Bit8u data) +{ + if (channel->chip->newm && channel->chtype == ch_4op2) + { + return; + } + channel->f_num = (channel->f_num & 0xff) | ((data & 0x03) << 8); + channel->block = (data >> 2) & 0x07; + channel->ksv = (channel->block << 1) + | ((channel->f_num >> (0x09 - channel->chip->nts)) & 0x01); + OPL3_EnvelopeUpdateKSL(channel->slots[0]); + OPL3_EnvelopeUpdateKSL(channel->slots[1]); + if (channel->chip->newm && channel->chtype == ch_4op) + { + channel->pair->f_num = channel->f_num; + channel->pair->block = channel->block; + channel->pair->ksv = channel->ksv; + OPL3_EnvelopeUpdateKSL(channel->pair->slots[0]); + OPL3_EnvelopeUpdateKSL(channel->pair->slots[1]); + } +} + +static void OPL3_ChannelSetupAlg(opl3_channel *channel) +{ + if (channel->chtype == ch_drum) + { + if (channel->ch_num == 7 || channel->ch_num == 8) + { + channel->slots[0]->mod = &channel->chip->zeromod; + channel->slots[1]->mod = &channel->chip->zeromod; + return; + } + switch (channel->alg & 0x01) + { + case 0x00: + channel->slots[0]->mod = &channel->slots[0]->fbmod; + channel->slots[1]->mod = &channel->slots[0]->out; + break; + case 0x01: + channel->slots[0]->mod = &channel->slots[0]->fbmod; + channel->slots[1]->mod = &channel->chip->zeromod; + break; + } + return; + } + if (channel->alg & 0x08) + { + return; + } + if (channel->alg & 0x04) + { + channel->pair->out[0] = &channel->chip->zeromod; + channel->pair->out[1] = &channel->chip->zeromod; + channel->pair->out[2] = &channel->chip->zeromod; + channel->pair->out[3] = &channel->chip->zeromod; + switch (channel->alg & 0x03) + { + case 0x00: + channel->pair->slots[0]->mod = &channel->pair->slots[0]->fbmod; + channel->pair->slots[1]->mod = &channel->pair->slots[0]->out; + channel->slots[0]->mod = &channel->pair->slots[1]->out; + channel->slots[1]->mod = &channel->slots[0]->out; + channel->out[0] = &channel->slots[1]->out; + channel->out[1] = &channel->chip->zeromod; + channel->out[2] = &channel->chip->zeromod; + channel->out[3] = &channel->chip->zeromod; + break; + case 0x01: + channel->pair->slots[0]->mod = &channel->pair->slots[0]->fbmod; + channel->pair->slots[1]->mod = &channel->pair->slots[0]->out; + channel->slots[0]->mod = &channel->chip->zeromod; + channel->slots[1]->mod = &channel->slots[0]->out; + channel->out[0] = &channel->pair->slots[1]->out; + channel->out[1] = &channel->slots[1]->out; + channel->out[2] = &channel->chip->zeromod; + channel->out[3] = &channel->chip->zeromod; + break; + case 0x02: + channel->pair->slots[0]->mod = &channel->pair->slots[0]->fbmod; + channel->pair->slots[1]->mod = &channel->chip->zeromod; + channel->slots[0]->mod = &channel->pair->slots[1]->out; + channel->slots[1]->mod = &channel->slots[0]->out; + channel->out[0] = &channel->pair->slots[0]->out; + channel->out[1] = &channel->slots[1]->out; + channel->out[2] = &channel->chip->zeromod; + channel->out[3] = &channel->chip->zeromod; + break; + case 0x03: + channel->pair->slots[0]->mod = &channel->pair->slots[0]->fbmod; + channel->pair->slots[1]->mod = &channel->chip->zeromod; + channel->slots[0]->mod = &channel->pair->slots[1]->out; + channel->slots[1]->mod = &channel->chip->zeromod; + channel->out[0] = &channel->pair->slots[0]->out; + channel->out[1] = &channel->slots[0]->out; + channel->out[2] = &channel->slots[1]->out; + channel->out[3] = &channel->chip->zeromod; + break; + } + } + else + { + switch (channel->alg & 0x01) + { + case 0x00: + channel->slots[0]->mod = &channel->slots[0]->fbmod; + channel->slots[1]->mod = &channel->slots[0]->out; + channel->out[0] = &channel->slots[1]->out; + channel->out[1] = &channel->chip->zeromod; + channel->out[2] = &channel->chip->zeromod; + channel->out[3] = &channel->chip->zeromod; + break; + case 0x01: + channel->slots[0]->mod = &channel->slots[0]->fbmod; + channel->slots[1]->mod = &channel->chip->zeromod; + channel->out[0] = &channel->slots[0]->out; + channel->out[1] = &channel->slots[1]->out; + channel->out[2] = &channel->chip->zeromod; + channel->out[3] = &channel->chip->zeromod; + break; + } + } +} + +static void OPL3_ChannelWriteC0(opl3_channel *channel, Bit8u data) +{ + channel->fb = (data & 0x0e) >> 1; + channel->con = data & 0x01; + channel->alg = channel->con; + if (channel->chip->newm) + { + if (channel->chtype == ch_4op) + { + channel->pair->alg = 0x04 | (channel->con << 1) | (channel->pair->con); + channel->alg = 0x08; + OPL3_ChannelSetupAlg(channel->pair); + } + else if (channel->chtype == ch_4op2) + { + channel->alg = 0x04 | (channel->pair->con << 1) | (channel->con); + channel->pair->alg = 0x08; + OPL3_ChannelSetupAlg(channel); + } + else + { + OPL3_ChannelSetupAlg(channel); + } + } + else + { + OPL3_ChannelSetupAlg(channel); + } + if (channel->chip->newm) + { + channel->cha = ((data >> 4) & 0x01) ? ~0 : 0; + channel->chb = ((data >> 5) & 0x01) ? ~0 : 0; + } + else + { + channel->cha = channel->chb = (Bit16u)~0; + } +} + +static void OPL3_ChannelKeyOn(opl3_channel *channel) +{ + if (channel->chip->newm) + { + if (channel->chtype == ch_4op) + { + OPL3_EnvelopeKeyOn(channel->slots[0], egk_norm); + OPL3_EnvelopeKeyOn(channel->slots[1], egk_norm); + OPL3_EnvelopeKeyOn(channel->pair->slots[0], egk_norm); + OPL3_EnvelopeKeyOn(channel->pair->slots[1], egk_norm); + } + else if (channel->chtype == ch_2op || channel->chtype == ch_drum) + { + OPL3_EnvelopeKeyOn(channel->slots[0], egk_norm); + OPL3_EnvelopeKeyOn(channel->slots[1], egk_norm); + } + } + else + { + OPL3_EnvelopeKeyOn(channel->slots[0], egk_norm); + OPL3_EnvelopeKeyOn(channel->slots[1], egk_norm); + } +} + +static void OPL3_ChannelKeyOff(opl3_channel *channel) +{ + if (channel->chip->newm) + { + if (channel->chtype == ch_4op) + { + OPL3_EnvelopeKeyOff(channel->slots[0], egk_norm); + OPL3_EnvelopeKeyOff(channel->slots[1], egk_norm); + OPL3_EnvelopeKeyOff(channel->pair->slots[0], egk_norm); + OPL3_EnvelopeKeyOff(channel->pair->slots[1], egk_norm); + } + else if (channel->chtype == ch_2op || channel->chtype == ch_drum) + { + OPL3_EnvelopeKeyOff(channel->slots[0], egk_norm); + OPL3_EnvelopeKeyOff(channel->slots[1], egk_norm); + } + } + else + { + OPL3_EnvelopeKeyOff(channel->slots[0], egk_norm); + OPL3_EnvelopeKeyOff(channel->slots[1], egk_norm); + } +} + +static void OPL3_ChannelSet4Op(opl3_chip *chip, Bit8u data) +{ + Bit8u bit; + Bit8u chnum; + for (bit = 0; bit < 6; bit++) + { + chnum = bit; + if (bit >= 3) + { + chnum += 9 - 3; + } + if ((data >> bit) & 0x01) + { + chip->channel[chnum].chtype = ch_4op; + chip->channel[chnum + 3].chtype = ch_4op2; + } + else + { + chip->channel[chnum].chtype = ch_2op; + chip->channel[chnum + 3].chtype = ch_2op; + } + } +} + +static Bit16s OPL3_ClipSample(Bit32s sample) +{ + if (sample > 32767) + { + sample = 32767; + } + else if (sample < -32768) + { + sample = -32768; + } + return (Bit16s)sample; +} + +void OPL3_Generate(opl3_chip *chip, Bit16s *buf) +{ + Bit8u ii; + Bit8u jj; + Bit16s accm; + Bit8u shift = 0; + + buf[1] = OPL3_ClipSample(chip->mixbuff[1]); + + for (ii = 0; ii < 15; ii++) + { + OPL3_SlotCalcFB(&chip->slot[ii]); + OPL3_EnvelopeCalc(&chip->slot[ii]); + OPL3_PhaseGenerate(&chip->slot[ii]); + OPL3_SlotGenerate(&chip->slot[ii]); + } + + chip->mixbuff[0] = 0; + for (ii = 0; ii < 18; ii++) + { + accm = 0; + for (jj = 0; jj < 4; jj++) + { + accm += *chip->channel[ii].out[jj]; + } + chip->mixbuff[0] += (Bit16s)(accm & chip->channel[ii].cha); + } + + for (ii = 15; ii < 18; ii++) + { + OPL3_SlotCalcFB(&chip->slot[ii]); + OPL3_EnvelopeCalc(&chip->slot[ii]); + OPL3_PhaseGenerate(&chip->slot[ii]); + OPL3_SlotGenerate(&chip->slot[ii]); + } + + buf[0] = OPL3_ClipSample(chip->mixbuff[0]); + + for (ii = 18; ii < 33; ii++) + { + OPL3_SlotCalcFB(&chip->slot[ii]); + OPL3_EnvelopeCalc(&chip->slot[ii]); + OPL3_PhaseGenerate(&chip->slot[ii]); + OPL3_SlotGenerate(&chip->slot[ii]); + } + + chip->mixbuff[1] = 0; + for (ii = 0; ii < 18; ii++) + { + accm = 0; + for (jj = 0; jj < 4; jj++) + { + accm += *chip->channel[ii].out[jj]; + } + chip->mixbuff[1] += (Bit16s)(accm & chip->channel[ii].chb); + } + + for (ii = 33; ii < 36; ii++) + { + OPL3_SlotCalcFB(&chip->slot[ii]); + OPL3_EnvelopeCalc(&chip->slot[ii]); + OPL3_PhaseGenerate(&chip->slot[ii]); + OPL3_SlotGenerate(&chip->slot[ii]); + } + + if ((chip->timer & 0x3f) == 0x3f) + { + chip->tremolopos = (chip->tremolopos + 1) % 210; + } + if (chip->tremolopos < 105) + { + chip->tremolo = chip->tremolopos >> chip->tremoloshift; + } + else + { + chip->tremolo = (210 - chip->tremolopos) >> chip->tremoloshift; + } + + if ((chip->timer & 0x3ff) == 0x3ff) + { + chip->vibpos = (chip->vibpos + 1) & 7; + } + + chip->timer++; + + chip->eg_add = 0; + if (chip->eg_timer) + { + while (shift < 36 && ((chip->eg_timer >> shift) & 1) == 0) + { + shift++; + } + if (shift > 12) + { + chip->eg_add = 0; + } + else + { + chip->eg_add = shift + 1; + } + } + + if (chip->eg_timerrem || chip->eg_state) + { + if (chip->eg_timer == 0xfffffffff) + { + chip->eg_timer = 0; + chip->eg_timerrem = 1; + } + else + { + chip->eg_timer++; + chip->eg_timerrem = 0; + } + } + + chip->eg_state ^= 1; + + while (chip->writebuf[chip->writebuf_cur].time <= chip->writebuf_samplecnt) + { + if (!(chip->writebuf[chip->writebuf_cur].reg & 0x200)) + { + break; + } + chip->writebuf[chip->writebuf_cur].reg &= 0x1ff; + OPL3_WriteReg(chip, chip->writebuf[chip->writebuf_cur].reg, + chip->writebuf[chip->writebuf_cur].data); + chip->writebuf_cur = (chip->writebuf_cur + 1) % OPL_WRITEBUF_SIZE; + } + chip->writebuf_samplecnt++; +} + +void OPL3_GenerateResampled(opl3_chip *chip, Bit16s *buf) +{ + while (chip->samplecnt >= chip->rateratio) + { + chip->oldsamples[0] = chip->samples[0]; + chip->oldsamples[1] = chip->samples[1]; + OPL3_Generate(chip, chip->samples); + chip->samplecnt -= chip->rateratio; + } + buf[0] = (Bit16s)((chip->oldsamples[0] * (chip->rateratio - chip->samplecnt) + + chip->samples[0] * chip->samplecnt) / chip->rateratio); + buf[1] = (Bit16s)((chip->oldsamples[1] * (chip->rateratio - chip->samplecnt) + + chip->samples[1] * chip->samplecnt) / chip->rateratio); + chip->samplecnt += 1 << RSM_FRAC; +} + +void OPL3_Reset(opl3_chip *chip, Bit32u samplerate) +{ + Bit8u slotnum; + Bit8u channum; + + memset(chip, 0, sizeof(opl3_chip)); + for (slotnum = 0; slotnum < 36; slotnum++) + { + chip->slot[slotnum].chip = chip; + chip->slot[slotnum].mod = &chip->zeromod; + chip->slot[slotnum].eg_rout = 0x1ff; + chip->slot[slotnum].eg_out = 0x1ff; + chip->slot[slotnum].eg_gen = envelope_gen_num_release; + chip->slot[slotnum].trem = (Bit8u*)&chip->zeromod; + chip->slot[slotnum].slot_num = slotnum; + } + for (channum = 0; channum < 18; channum++) + { + chip->channel[channum].slots[0] = &chip->slot[ch_slot[channum]]; + chip->channel[channum].slots[1] = &chip->slot[ch_slot[channum] + 3]; + chip->slot[ch_slot[channum]].channel = &chip->channel[channum]; + chip->slot[ch_slot[channum] + 3].channel = &chip->channel[channum]; + if ((channum % 9) < 3) + { + chip->channel[channum].pair = &chip->channel[channum + 3]; + } + else if ((channum % 9) < 6) + { + chip->channel[channum].pair = &chip->channel[channum - 3]; + } + chip->channel[channum].chip = chip; + chip->channel[channum].out[0] = &chip->zeromod; + chip->channel[channum].out[1] = &chip->zeromod; + chip->channel[channum].out[2] = &chip->zeromod; + chip->channel[channum].out[3] = &chip->zeromod; + chip->channel[channum].chtype = ch_2op; + chip->channel[channum].cha = 0xffff; + chip->channel[channum].chb = 0xffff; + chip->channel[channum].ch_num = channum; + OPL3_ChannelSetupAlg(&chip->channel[channum]); + } + chip->noise = 1; + chip->rateratio = (samplerate << RSM_FRAC) / 49716; + chip->tremoloshift = 4; + chip->vibshift = 1; + + mus_opl_gain = dsda_IntConfig(dsda_config_mus_opl_gain); +} + +void OPL3_WriteReg(opl3_chip *chip, Bit16u reg, Bit8u v) +{ + Bit8u high = (reg >> 8) & 0x01; + Bit8u regm = reg & 0xff; + switch (regm & 0xf0) + { + case 0x00: + if (high) + { + switch (regm & 0x0f) + { + case 0x04: + OPL3_ChannelSet4Op(chip, v); + break; + case 0x05: + chip->newm = v & 0x01; + break; + } + } + else + { + switch (regm & 0x0f) + { + case 0x08: + chip->nts = (v >> 6) & 0x01; + break; + } + } + break; + case 0x20: + case 0x30: + if (ad_slot[regm & 0x1f] >= 0) + { + OPL3_SlotWrite20(&chip->slot[18 * high + ad_slot[regm & 0x1f]], v); + } + break; + case 0x40: + case 0x50: + if (ad_slot[regm & 0x1f] >= 0) + { + OPL3_SlotWrite40(&chip->slot[18 * high + ad_slot[regm & 0x1f]], v); + } + break; + case 0x60: + case 0x70: + if (ad_slot[regm & 0x1f] >= 0) + { + OPL3_SlotWrite60(&chip->slot[18 * high + ad_slot[regm & 0x1f]], v); + } + break; + case 0x80: + case 0x90: + if (ad_slot[regm & 0x1f] >= 0) + { + OPL3_SlotWrite80(&chip->slot[18 * high + ad_slot[regm & 0x1f]], v); + } + break; + case 0xe0: + case 0xf0: + if (ad_slot[regm & 0x1f] >= 0) + { + OPL3_SlotWriteE0(&chip->slot[18 * high + ad_slot[regm & 0x1f]], v); + } + break; + case 0xa0: + if ((regm & 0x0f) < 9) + { + OPL3_ChannelWriteA0(&chip->channel[9 * high + (regm & 0x0f)], v); + } + break; + case 0xb0: + if (regm == 0xbd && !high) + { + chip->tremoloshift = (((v >> 7) ^ 1) << 1) + 2; + chip->vibshift = ((v >> 6) & 0x01) ^ 1; + OPL3_ChannelUpdateRhythm(chip, v); + } + else if ((regm & 0x0f) < 9) + { + OPL3_ChannelWriteB0(&chip->channel[9 * high + (regm & 0x0f)], v); + if (v & 0x20) + { + OPL3_ChannelKeyOn(&chip->channel[9 * high + (regm & 0x0f)]); + } + else + { + OPL3_ChannelKeyOff(&chip->channel[9 * high + (regm & 0x0f)]); + } + } + break; + case 0xc0: + if ((regm & 0x0f) < 9) + { + OPL3_ChannelWriteC0(&chip->channel[9 * high + (regm & 0x0f)], v); + } + break; + } +} + +void OPL3_WriteRegBuffered(opl3_chip *chip, Bit16u reg, Bit8u v) +{ + Bit64u time1, time2; + + if (chip->writebuf[chip->writebuf_last].reg & 0x200) + { + OPL3_WriteReg(chip, chip->writebuf[chip->writebuf_last].reg & 0x1ff, + chip->writebuf[chip->writebuf_last].data); + + chip->writebuf_cur = (chip->writebuf_last + 1) % OPL_WRITEBUF_SIZE; + chip->writebuf_samplecnt = chip->writebuf[chip->writebuf_last].time; + } + + chip->writebuf[chip->writebuf_last].reg = reg | 0x200; + chip->writebuf[chip->writebuf_last].data = v; + time1 = chip->writebuf_lasttime + OPL_WRITEBUF_DELAY; + time2 = chip->writebuf_samplecnt; + + if (time1 < time2) + { + time1 = time2; + } + + chip->writebuf[chip->writebuf_last].time = time1; + chip->writebuf_lasttime = time1; + chip->writebuf_last = (chip->writebuf_last + 1) % OPL_WRITEBUF_SIZE; +} + +void OPL3_GenerateStream(opl3_chip *chip, Bit16s *sndptr, Bit32u numsamples) +{ + Bit32u i; + + for(i = 0; i < numsamples; i++) + { + Bit32s sample; + OPL3_GenerateResampled(chip, sndptr); + sample = sndptr[0] * mus_opl_gain / 50; + sndptr[0] = OPL3_ClipSample(sample); + sample = sndptr[1] * mus_opl_gain / 50; + sndptr[1] = OPL3_ClipSample(sample); + sndptr += 2; + } +} diff --git a/prboom2/src/MUSIC/opl3.h b/prboom2/src/MUSIC/opl3.h new file mode 100644 index 000000000..2a64037a5 --- /dev/null +++ b/prboom2/src/MUSIC/opl3.h @@ -0,0 +1,150 @@ +// +// Copyright (C) 2013-2018 Alexey Khokholov (Nuke.YKT) +// +// 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. +// +// +// Nuked OPL3 emulator. +// Thanks: +// MAME Development Team(Jarek Burczynski, Tatsuyuki Satoh): +// Feedback and Rhythm part calculation information. +// forums.submarine.org.uk(carbon14, opl3): +// Tremolo and phase generator calculation information. +// OPLx decapsulated(Matthew Gambrell, Olli Niemitalo): +// OPL2 ROMs. +// siliconpr0n.org(John McMaster, digshadow): +// YMF262 and VRC VII decaps and die shots. +// +// version: 1.8 +// + +#ifndef OPL_OPL3_H +#define OPL_OPL3_H + +#include + +#define OPL_WRITEBUF_SIZE 1024 +#define OPL_WRITEBUF_DELAY 2 + +typedef uintptr_t Bitu; +typedef intptr_t Bits; +typedef uint64_t Bit64u; +typedef int64_t Bit64s; +typedef uint32_t Bit32u; +typedef int32_t Bit32s; +typedef uint16_t Bit16u; +typedef int16_t Bit16s; +typedef uint8_t Bit8u; +typedef int8_t Bit8s; + +typedef struct _opl3_slot opl3_slot; +typedef struct _opl3_channel opl3_channel; +typedef struct _opl3_chip opl3_chip; + +struct _opl3_slot { + opl3_channel *channel; + opl3_chip *chip; + Bit16s out; + Bit16s fbmod; + Bit16s *mod; + Bit16s prout; + Bit16s eg_rout; + Bit16s eg_out; + Bit8u eg_inc; + Bit8u eg_gen; + Bit8u eg_rate; + Bit8u eg_ksl; + Bit8u *trem; + Bit8u reg_vib; + Bit8u reg_type; + Bit8u reg_ksr; + Bit8u reg_mult; + Bit8u reg_ksl; + Bit8u reg_tl; + Bit8u reg_ar; + Bit8u reg_dr; + Bit8u reg_sl; + Bit8u reg_rr; + Bit8u reg_wf; + Bit8u key; + Bit32u pg_reset; + Bit32u pg_phase; + Bit16u pg_phase_out; + Bit8u slot_num; +}; + +struct _opl3_channel { + opl3_slot *slots[2]; + opl3_channel *pair; + opl3_chip *chip; + Bit16s *out[4]; + Bit8u chtype; + Bit16u f_num; + Bit8u block; + Bit8u fb; + Bit8u con; + Bit8u alg; + Bit8u ksv; + Bit16u cha, chb; + Bit8u ch_num; +}; + +typedef struct _opl3_writebuf { + Bit64u time; + Bit16u reg; + Bit8u data; +} opl3_writebuf; + +struct _opl3_chip { + opl3_channel channel[18]; + opl3_slot slot[36]; + Bit16u timer; + Bit64u eg_timer; + Bit8u eg_timerrem; + Bit8u eg_state; + Bit8u eg_add; + Bit8u newm; + Bit8u nts; + Bit8u rhy; + Bit8u vibpos; + Bit8u vibshift; + Bit8u tremolo; + Bit8u tremolopos; + Bit8u tremoloshift; + Bit32u noise; + Bit16s zeromod; + Bit32s mixbuff[2]; + Bit8u rm_hh_bit2; + Bit8u rm_hh_bit3; + Bit8u rm_hh_bit7; + Bit8u rm_hh_bit8; + Bit8u rm_tc_bit3; + Bit8u rm_tc_bit5; + //OPL3L + Bit32s rateratio; + Bit32s samplecnt; + Bit16s oldsamples[2]; + Bit16s samples[2]; + + Bit64u writebuf_samplecnt; + Bit32u writebuf_cur; + Bit32u writebuf_last; + Bit64u writebuf_lasttime; + opl3_writebuf writebuf[OPL_WRITEBUF_SIZE]; +}; + +void OPL3_Generate(opl3_chip *chip, Bit16s *buf); +void OPL3_GenerateResampled(opl3_chip *chip, Bit16s *buf); +void OPL3_Reset(opl3_chip *chip, Bit32u samplerate); +void OPL3_WriteReg(opl3_chip *chip, Bit16u reg, Bit8u v); +void OPL3_WriteRegBuffered(opl3_chip *chip, Bit16u reg, Bit8u v); +void OPL3_GenerateStream(opl3_chip *chip, Bit16s *sndptr, Bit32u numsamples); +#endif diff --git a/prboom2/src/MUSIC/opl_queue.c b/prboom2/src/MUSIC/opl_queue.c new file mode 100644 index 000000000..50a4eb8a8 --- /dev/null +++ b/prboom2/src/MUSIC/opl_queue.c @@ -0,0 +1,292 @@ +// 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 "lprintf.h" + +#include "opl_queue.h" + +#define MAX_OPL_QUEUE 64 + +typedef struct +{ + opl_callback_t callback; + void *data; + uint64_t 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 = (opl_callback_queue_t*)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, + uint64_t time) +{ + int entry_id; + int parent_id; + + if (queue->num_entries >= MAX_OPL_QUEUE) + { + lprintf (LO_WARN, "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; +} + +uint64_t OPL_Queue_Peek(opl_callback_queue_t *queue) +{ + if (queue->num_entries > 0) + { + return queue->entries[0].time; + } + else + { + return 0; + } +} + +void OPL_Queue_AdjustCallbacks(opl_callback_queue_t *queue, + uint64_t time, float factor) +{ + int64_t offset; + int i; + + for (i = 0; i < queue->num_entries; ++i) + { + offset = queue->entries[i].time - time; + queue->entries[i].time = time + (uint64_t) (offset / factor); + } +} + +#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 diff --git a/prboom2/src/MUSIC/opl_queue.h b/prboom2/src/MUSIC/opl_queue.h new file mode 100644 index 000000000..36bb3a802 --- /dev/null +++ b/prboom2/src/MUSIC/opl_queue.h @@ -0,0 +1,46 @@ +// 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, + uint64_t time); +int OPL_Queue_Pop(opl_callback_queue_t *queue, + opl_callback_t *callback, void **data); +uint64_t OPL_Queue_Peek(opl_callback_queue_t *queue); +void OPL_Queue_AdjustCallbacks(opl_callback_queue_t *queue, + uint64_t time, float factor); + +#endif /* #ifndef OPL_QUEUE_H */ diff --git a/prboom2/src/MUSIC/oplplayer.c b/prboom2/src/MUSIC/oplplayer.c new file mode 100644 index 000000000..366d84748 --- /dev/null +++ b/prboom2/src/MUSIC/oplplayer.c @@ -0,0 +1,1746 @@ +// 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 + +#include "doomdef.h" +#include "doomstat.h" +#include "memio.h" +#include "mus2mid.h" + +#include "m_misc.h" +#include "s_sound.h" +#include "w_wad.h" +#include "z_zone.h" + +#include "opl.h" +#include "midifile.h" + +#include "musicplayer.h" + +#include "lprintf.h" + +#include "dsda/configuration.h" + +static int opl_opl3mode; + +// #define OPL_MIDI_DEBUG + +#define MAXMIDLENGTH (96 * 1024) +#define GENMIDI_NUM_INSTRS 128 +#define GENMIDI_NUM_PERCUSSION 47 + +#define GENMIDI_HEADER "#OPL_II#" +#define GENMIDI_FLAG_FIXED 0x0001 /* fixed pitch */ +#define GENMIDI_FLAG_2VOICE 0x0004 /* 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; + short base_note_offset; +} PACKEDATTR genmidi_voice_t; + +typedef struct +{ + unsigned short flags; + byte fine_tuning; + byte fixed_note; + + genmidi_voice_t voices[2]; +} PACKEDATTR genmidi_instr_t; + +typedef struct +{ + // The instrument currently used for this track. + + const genmidi_instr_t *instrument; + + // Volume level + + int volume; + int volume_base; + + // Pan + + int pan; + + // Pitch bend value: + + int bend; + +} 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; +} opl_track_data_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; + + // Array used by voice: + int array; + + // Currently-loaded instrument data + const 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; + + // 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. + unsigned int freq; + + // The volume of the note being played on this channel. + unsigned int note_volume; + + // The current volume (register value) that has been set for this channel. + unsigned int car_volume; + unsigned int mod_volume; + + // Pan. + unsigned int reg_pan; + + // Priority. + unsigned int priority; +}; + +// 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 } +}; + +// Frequency values to use for each note. + +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. + +static const unsigned int volume_mapping_table[] = { + 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 +}; + +// DMX version to emulate for OPL emulation: +typedef enum { + opl_doom1_1_666, // Doom 1 v1.666 + opl_doom2_1_666, // Doom 2 v1.666, Hexen, Heretic + opl_doom_1_9 // Doom v1.9, Strife +} opl_driver_ver_t; + +static opl_driver_ver_t opl_drv_ver = opl_doom_1_9; +static dboolean music_initialized = false; + +//static dboolean musicpaused = false; +static int start_music_volume; +static int current_music_volume; + +// GENMIDI lump instrument data: + +static genmidi_instr_t *main_instrs; +static genmidi_instr_t *percussion_instrs; +static char (*main_instr_names)[32]; +static char (*percussion_names)[32]; + +// Voices: + +static opl_voice_t voices[OPL_NUM_VOICES * 2]; +static opl_voice_t *voice_free_list[OPL_NUM_VOICES * 2]; +static opl_voice_t *voice_alloced_list[OPL_NUM_VOICES * 2]; +static int voice_free_num; +static int voice_alloced_num; +static int opl_opl3mode; +static int num_opl_voices; + +// Data for each channel. + +static opl_channel_data_t channels[MIDI_CHANNELS_PER_TRACK]; + +// Track data for playing tracks: + +static opl_track_data_t *tracks; +static unsigned int num_tracks = 0; +static unsigned int running_tracks = 0; +static dboolean song_looping; + +// Tempo control variables + +static unsigned int ticks_per_beat; +static unsigned int us_per_beat; + +// If true, OPL sound channels are reversed to their correct arrangement +// (as intended by the MIDI standard) rather than the backwards one +// used by DMX due to a bug. + +static dboolean opl_stereo_correct = false; + +// Load instrument table from GENMIDI lump: + +static dboolean LoadInstrumentTable(void) +{ + const byte *lump; + + lump = (const byte*)W_LumpByName("GENMIDI"); + + // Check header + + if (strncmp((const char *) lump, GENMIDI_HEADER, strlen(GENMIDI_HEADER)) != 0) + { + return false; + } + + main_instrs = (const genmidi_instr_t *) (lump + strlen(GENMIDI_HEADER)); + percussion_instrs = main_instrs + GENMIDI_NUM_INSTRS; + main_instr_names = + (char (*)[32]) (percussion_instrs + GENMIDI_NUM_PERCUSSION); + percussion_names = main_instr_names + GENMIDI_NUM_INSTRS; + + return true; +} + +// Get the next available voice from the freelist. + +static opl_voice_t *GetFreeVoice(void) +{ + opl_voice_t *result; + int i; + + // None available? + + if (voice_free_num == 0) + { + return NULL; + } + + // Remove from free list + + result = voice_free_list[0]; + + voice_free_num--; + + for (i = 0; i < voice_free_num; i++) + { + voice_free_list[i] = voice_free_list[i + 1]; + } + + // Add to allocated list + + voice_alloced_list[voice_alloced_num++] = result; + + return result; +} + +// Release a voice back to the freelist. + +static void VoiceKeyOff(opl_voice_t *voice); + +static void ReleaseVoice(int index) +{ + opl_voice_t *voice; + dboolean double_voice; + int i; + + // Doom 2 1.666 OPL crash emulation. + if (index >= voice_alloced_num) + { + + voice_alloced_num = 0; + voice_free_num = 0; + return; + } + + voice = voice_alloced_list[index]; + + VoiceKeyOff(voice); + + voice->channel = NULL; + voice->note = 0; + + double_voice = voice->current_instr_voice != 0; + + // Remove from alloced list. + + voice_alloced_num--; + + for (i = index; i < voice_alloced_num; i++) + { + voice_alloced_list[i] = voice_alloced_list[i + 1]; + } + + // Search to the end of the freelist (This is how Doom behaves!) + + voice_free_list[voice_free_num++] = voice; + + if (double_voice && opl_drv_ver < opl_doom_1_9) + { + ReleaseVoice(index); + } +} + +// Load data to the specified Operator + +static void LoadOperatorData(int Operator, + const genmidi_op_t *data, + dboolean max_level, + unsigned int *volume) +{ + 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; + + if (max_level) + { + level |= 0x3f; + } + else + { + level |= data->level; + } + + *volume = level; + + 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. + +static void SetVoiceInstrument(opl_voice_t *voice, + const genmidi_instr_t *instr, + unsigned int instr_voice) +{ + const genmidi_voice_t *data; + unsigned int modulating; + + // Instrument already set for this channel? + + if (voice->current_instr == instr + && voice->current_instr_voice == instr_voice) + { + return; + } + + voice->current_instr = instr; + voice->current_instr_voice = instr_voice; + + data = &instr->voices[instr_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 | voice->array, &data->carrier, true, + &voice->car_volume); + LoadOperatorData(voice->op1 | voice->array, &data->modulator, !modulating, + &voice->mod_volume); + + // 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. + + OPL_WriteRegister((OPL_REGS_FEEDBACK + voice->index) | voice->array, + data->feedback | voice->reg_pan); + + // Calculate voice priority. + + voice->priority = 0x0f - (data->carrier.attack >> 4) + + 0x0f - (data->carrier.sustain & 0x0f); +} + +static void SetVoiceVolume(opl_voice_t *voice, unsigned int volume) +{ + const genmidi_voice_t *opl_voice; + unsigned int midi_volume; + unsigned int full_volume; + unsigned int car_volume; + unsigned int mod_volume; + + voice->note_volume = volume; + + opl_voice = &voice->current_instr->voices[voice->current_instr_voice]; + + // Multiply note volume and channel volume to get the actual volume. + + midi_volume = 2 * (volume_mapping_table[voice->channel->volume] + 1); + + full_volume = (volume_mapping_table[voice->note_volume] * midi_volume) + >> 9; + + // The volume value to use in the register: + car_volume = 0x3f - full_volume; + + // Update the volume register(s) if necessary. + + if (car_volume != (voice->car_volume & 0x3f)) + { + voice->car_volume = car_volume | (voice->car_volume & 0xc0); + + OPL_WriteRegister((OPL_REGS_LEVEL + voice->op2) | voice->array, + voice->car_volume); + + // If we are using non-modulated feedback mode, we must set the + // volume for both voices. + + if ((opl_voice->feedback & 0x01) != 0 + && opl_voice->modulator.level != 0x3f) + { + mod_volume = opl_voice->modulator.level; + if (mod_volume < car_volume) + { + mod_volume = car_volume; + } + + mod_volume |= voice->mod_volume & 0xc0; + + if(mod_volume != voice->mod_volume) + { + voice->mod_volume = mod_volume; + OPL_WriteRegister((OPL_REGS_LEVEL + voice->op1) | voice->array, + mod_volume | + (opl_voice->modulator.scale & 0xc0)); + } + } + } +} + +static void SetVoicePan(opl_voice_t *voice, unsigned int pan) +{ + genmidi_voice_t *opl_voice; + + voice->reg_pan = pan; + opl_voice = &voice->current_instr->voices[voice->current_instr_voice];; + + OPL_WriteRegister((OPL_REGS_FEEDBACK + voice->index) | voice->array, + opl_voice->feedback | pan); +} + +// Initialize the voice table and freelist + +static void InitVoices(void) +{ + int i; + + // Start with an empty free list. + + voice_free_num = num_opl_voices; + voice_alloced_num = 0; + + // Initialize each voice. + + for (i = 0; i < num_opl_voices; ++i) + { + voices[i].index = i % OPL_NUM_VOICES; + voices[i].op1 = voice_operators[0][i % OPL_NUM_VOICES]; + voices[i].op2 = voice_operators[1][i % OPL_NUM_VOICES]; + voices[i].array = (i / OPL_NUM_VOICES) << 8; + voices[i].current_instr = NULL; + + // Add this voice to the freelist. + + voice_free_list[i] = &voices[i]; + } +} + +static void SetChannelVolume(opl_channel_data_t *channel, unsigned int volume, + dboolean clip_start); + +// Set music volume (0 - 127) + +static void I_OPL_SetMusicVolume(int volume) +{ + int opl_vol = volume * 127 / 15; // OPL synth internally uses 0-127 + + unsigned int i; + + // Internal state variable. + + current_music_volume = opl_vol; + + // Update the volume of all voices. + + for (i = 0; i < MIDI_CHANNELS_PER_TRACK; ++i) + { + if (i == 15) + { + SetChannelVolume(&channels[i], opl_vol, false); + } + else + { + SetChannelVolume(&channels[i], channels[i].volume_base, false); + } + } +} + +static void VoiceKeyOff(opl_voice_t *voice) +{ + OPL_WriteRegister((OPL_REGS_FREQ_2 + voice->index) | voice->array, + voice->freq >> 8); +} + +static opl_channel_data_t *TrackChannelForEvent(opl_track_data_t *track, + midi_event_t *event) +{ + unsigned int channel_num = event->data.channel.channel; + + // MIDI uses track #9 for percussion, but for MUS it's track #15 + // instead. Because DMX works on MUS data internally, we need to + // swap back to the MUS version of the channel number. + if (channel_num == 9) + { + channel_num = 15; + } + else if (channel_num == 15) + { + channel_num = 9; + } + + return &channels[channel_num]; +} + +// Get the frequency that we should be using for a voice. + +static void KeyOffEvent(opl_track_data_t *track, midi_event_t *event) +{ + opl_channel_data_t *channel; + 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 = TrackChannelForEvent(track, event); + key = event->data.channel.param1; + + // Turn off voices being used to play this key. + // If it is a double voice instrument there will be two. + + for (i = 0; i < voice_alloced_num; i++) + { + if (voice_alloced_list[i]->channel == channel + && voice_alloced_list[i]->key == key) + { + // Finished with this voice now. + + ReleaseVoice(i); + + i--; + } + } +} + +// 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 void ReplaceExistingVoice(void) +{ + int i; + int result; + + // Check the allocated voices, if we find an instrument that is + // of a lower priority to the new instrument, discard it. + // 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 = 0; + + for (i = 0; i < voice_alloced_num; i++) + { + if (voice_alloced_list[i]->current_instr_voice != 0 + || voice_alloced_list[i]->channel + >= voice_alloced_list[result]->channel) + { + result = i; + } + } + + ReleaseVoice(result); +} + +// Alternate versions of ReplaceExistingVoice() used when emulating old +// versions of the DMX library used in Doom 1.666, Heretic and Hexen. + +static void ReplaceExistingVoiceDoom1(void) +{ + int i; + int result; + + result = 0; + + for (i = 0; i < voice_alloced_num; i++) + { + if (voice_alloced_list[i]->channel + > voice_alloced_list[result]->channel) + { + result = i; + } + } + + ReleaseVoice(result); +} + +static void ReplaceExistingVoiceDoom2(opl_channel_data_t *channel) +{ + int i; + int result; + int priority; + + result = 0; + + priority = 0x8000; + + for (i = 0; i < voice_alloced_num - 3; i++) + { + if (voice_alloced_list[i]->priority < priority + && voice_alloced_list[i]->channel >= channel) + { + priority = voice_alloced_list[i]->priority; + result = i; + } + } + + ReleaseVoice(result); +} + + +static unsigned int FrequencyForVoice(opl_voice_t *voice) +{ + const genmidi_voice_t *gm_voice; + signed int freq_index; + unsigned int octave; + unsigned int sub_index; + signed int note; + + note = voice->note; + + // 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]; + + if ((LittleShort(voice->current_instr->flags) & GENMIDI_FLAG_FIXED) == 0) + { + note += (signed short) LittleShort(gm_voice->base_note_offset); + } + + // Avoid possible overflow due to base note offset: + + while (note < 0) + { + note += 12; + } + + while (note > 95) + { + note -= 12; + } + + 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. + + if (voice->current_instr_voice != 0) + { + freq_index += (voice->current_instr->fine_tuning / 2) - 64; + } + + if (freq_index < 0) + { + freq_index = 0; + } + + // The first 7 notes use the start of the table, while + // consecutive notes loop around the latter part. + + if (freq_index < 284) + { + return frequency_curve[freq_index]; + } + + 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) + { + octave = 7; + } + + // 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) + { + OPL_WriteRegister((OPL_REGS_FREQ_1 + voice->index) | voice->array, + freq & 0xff); + OPL_WriteRegister((OPL_REGS_FREQ_2 + voice->index) | voice->array, + (freq >> 8) | 0x20); + + voice->freq = freq; + } +} + +// Program a single voice for an instrument. For a double voice +// instrument (GENMIDI_FLAG_2VOICE), this is called twice for each +// key on event. + +static void VoiceKeyOn(opl_channel_data_t *channel, + genmidi_instr_t *instrument, + unsigned int instrument_voice, + unsigned int note, + unsigned int key, + unsigned int volume) +{ + opl_voice_t *voice; + + if (!opl_opl3mode && opl_drv_ver == opl_doom1_1_666) + { + instrument_voice = 0; + } + + // Find a voice to use for this new note. + + voice = GetFreeVoice(); + + if (voice == NULL) + { + return; + } + + voice->channel = channel; + 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 ((LittleShort(instrument->flags) & GENMIDI_FLAG_FIXED) != 0) + { + voice->note = instrument->fixed_note; + } + else + { + voice->note = note; + } + + voice->reg_pan = channel->pan; + + // Program the voice with the instrument data: + + SetVoiceInstrument(voice, instrument, instrument_voice); + + // Set the volume level. + + SetVoiceVolume(voice, volume); + + // Write the frequency value to turn the note on. + + voice->freq = 0; + 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 note, key, volume, voicenum; + dboolean double_voice; + + /* + printf("note on: channel %i, %i, %i\n", + event->data.channel.channel, + event->data.channel.param1, + event->data.channel.param2); + */ + + note = event->data.channel.param1; + key = event->data.channel.param1; + volume = event->data.channel.param2; + + // A volume of zero means key off. Some MIDI tracks, eg. the ones + // in AV.wad, use a second key on with a volume of zero to mean + // key off. + if (volume <= 0) + { + KeyOffEvent(track, event); + return; + } + + // The channel. + channel = TrackChannelForEvent(track, event); + + // Percussion channel is treated differently. + if (event->data.channel.channel == 9) + { + if (key < 35 || key > 81) + { + return; + } + + instrument = &percussion_instrs[key - 35]; + + note = 60; + } + else + { + instrument = channel->instrument; + } + + double_voice = (LittleShort(instrument->flags) & GENMIDI_FLAG_2VOICE) != 0; + + switch (opl_drv_ver) + { + case opl_doom1_1_666: + voicenum = double_voice + 1; + if (!opl_opl3mode) + { + voicenum = 1; + } + while (voice_alloced_num > num_opl_voices - voicenum) + { + ReplaceExistingVoiceDoom1(); + } + + // Find and program a voice for this instrument. If this + // is a double voice instrument, we must do this twice. + + if (double_voice) + { + VoiceKeyOn(channel, instrument, 1, note, key, volume); + } + + VoiceKeyOn(channel, instrument, 0, note, key, volume); + break; + case opl_doom2_1_666: + if (voice_alloced_num == num_opl_voices) + { + ReplaceExistingVoiceDoom2(channel); + } + if (voice_alloced_num == num_opl_voices - 1 && double_voice) + { + ReplaceExistingVoiceDoom2(channel); + } + + // Find and program a voice for this instrument. If this + // is a double voice instrument, we must do this twice. + + if (double_voice) + { + VoiceKeyOn(channel, instrument, 1, note, key, volume); + } + + VoiceKeyOn(channel, instrument, 0, note, key, volume); + break; + default: + case opl_doom_1_9: + if (voice_free_num == 0) + { + ReplaceExistingVoice(); + } + + // Find and program a voice for this instrument. If this + // is a double voice instrument, we must do this twice. + + VoiceKeyOn(channel, instrument, 0, note, key, volume); + + if (double_voice) + { + VoiceKeyOn(channel, instrument, 1, note, key, volume); + } + break; + } +} + +static void ProgramChangeEvent(opl_track_data_t *track, midi_event_t *event) +{ + opl_channel_data_t *channel; + int instrument; + + // Set the instrument used on this channel. + + channel = TrackChannelForEvent(track, event); + instrument = event->data.channel.param1; + channel->instrument = &main_instrs[instrument]; + + // TODO: Look through existing voices that are turned on on this + // channel, and change the instrument. +} + +static void SetChannelVolume(opl_channel_data_t *channel, unsigned int volume, + dboolean clip_start) +{ + unsigned int i; + + channel->volume_base = volume; + + if (volume > current_music_volume) + { + volume = current_music_volume; + } + + if (clip_start && volume > start_music_volume) + { + volume = start_music_volume; + } + + channel->volume = volume; + + // Update all voices that this channel is using. + + for (i = 0; i < num_opl_voices; ++i) + { + if (voices[i].channel == channel) + { + SetVoiceVolume(&voices[i], voices[i].note_volume); + } + } +} + +static void SetChannelPan(opl_channel_data_t *channel, unsigned int pan) +{ + unsigned int reg_pan; + unsigned int i; + + // The DMX library has the stereo channels backwards, maybe because + // Paul Radek had a Soundblaster card with the channels reversed, or + // perhaps it was just a bug in the OPL3 support that was never + // finished. By default we preserve this bug, but we also provide a + // secret DMXOPTION to fix it. + if (opl_stereo_correct) + { + pan = 144 - pan; + } + + if (opl_opl3mode) + { + if (pan >= 96) + { + reg_pan = 0x10; + } + else if (pan <= 48) + { + reg_pan = 0x20; + } + else + { + reg_pan = 0x30; + } + if (channel->pan != reg_pan) + { + channel->pan = reg_pan; + for (i = 0; i < num_opl_voices; i++) + { + if (voices[i].channel == channel) + { + SetVoicePan(&voices[i], reg_pan); + } + } + } + } +} + +// Handler for the MIDI_CONTROLLER_ALL_NOTES_OFF channel event. +static void AllNotesOff(opl_channel_data_t *channel, unsigned int param) +{ + int i; + + for (i = 0; i < voice_alloced_num; i++) + { + if (voice_alloced_list[i]->channel == channel) + { + // Finished with this voice now. + + ReleaseVoice(i); + + i--; + } + } +} + +static void ControllerEvent(opl_track_data_t *track, midi_event_t *event) +{ + opl_channel_data_t *channel; + unsigned int controller; + unsigned int param; + + /* + printf("change controller: channel %i, %i, %i\n", + event->data.channel.channel, + event->data.channel.param1, + event->data.channel.param2); + */ + + channel = TrackChannelForEvent(track, event); + controller = event->data.channel.param1; + param = event->data.channel.param2; + + switch (controller) + { + case MIDI_CONTROLLER_MAIN_VOLUME: + SetChannelVolume(channel, param, true); + break; + + case MIDI_CONTROLLER_PAN: + SetChannelPan(channel, param); + break; + + case MIDI_CONTROLLER_ALL_NOTES_OFF: + AllNotesOff(channel, param); + break; + + default: +#ifdef OPL_MIDI_DEBUG + lprintf (LO_WARN, "Unknown MIDI controller type: %i\n", controller); +#endif + break; + } +} + +// 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; + opl_voice_t *voice_updated_list[OPL_NUM_VOICES * 2]; + unsigned int voice_updated_num = 0; + opl_voice_t *voice_not_updated_list[OPL_NUM_VOICES * 2]; + unsigned int voice_not_updated_num = 0; + + // Update the channel bend value. Only the MSB of the pitch bend + // value is considered: this is what Doom does. + + channel = TrackChannelForEvent(track, event); + channel->bend = event->data.channel.param2 - 64; + + // Update all voices for this channel. + + for (i = 0; i < voice_alloced_num; ++i) + { + if (voice_alloced_list[i]->channel == channel) + { + UpdateVoiceFrequency(voice_alloced_list[i]); + voice_updated_list[voice_updated_num++] = voice_alloced_list[i]; + } + else + { + voice_not_updated_list[voice_not_updated_num++] = + voice_alloced_list[i]; + } + } + + for (i = 0; i < voice_not_updated_num; i++) + { + voice_alloced_list[i] = voice_not_updated_list[i]; + } + + for (i = 0; i < voice_updated_num; i++) + { + voice_alloced_list[i + voice_not_updated_num] = voice_updated_list[i]; + } +} + +static void MetaSetTempo(unsigned int tempo) +{ + OPL_AdjustCallbacks((float) us_per_beat / tempo); + us_per_beat = tempo; +} + +// Process a meta event. + +static void MetaEvent(opl_track_data_t *track, midi_event_t *event) +{ + byte *data = event->data.meta.data; + unsigned int data_len = event->data.meta.length; + + 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; + + case MIDI_META_SET_TEMPO: + if (data_len == 3) + { + MetaSetTempo((data[0] << 16) | (data[1] << 8) | data[2]); + } + 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: +#ifdef OPL_MIDI_DEBUG + lprintf (LO_WARN, "Unknown MIDI meta event type: %i\n", + event->data.meta.type); +#endif + break; + } +} + +// 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: + KeyOffEvent(track, event); + break; + + case MIDI_EVENT_NOTE_ON: + KeyOnEvent(track, event); + break; + + case MIDI_EVENT_CONTROLLER: + ControllerEvent(track, event); + break; + + case MIDI_EVENT_PROGRAM_CHANGE: + ProgramChangeEvent(track, event); + break; + + case MIDI_EVENT_PITCH_BEND: + PitchBendEvent(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: +#ifdef OPL_MIDI_DEBUG + lprintf (LO_WARN, "Unknown MIDI event type %i\n", event->event_type); +#endif + break; + } +} + +static void ScheduleTrack(opl_track_data_t *track); +static void InitChannel(opl_channel_data_t *channel); + +// Restart a song from the beginning. + +static void RestartSong(void) +{ + unsigned int i; + + running_tracks = num_tracks; + + // fix buggy songs that forget to terminate notes held over loop point + // sdl_mixer does this as well + for (i=0; iiter, &event)) + { + return; + } + + ProcessEvent(track, event); + + // End of track? + + if (event->event_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); +} + +static void ScheduleTrack(opl_track_data_t *track) +{ + unsigned int nticks; + uint64_t us; + + // Get the number of milliseconds until the next event. + + nticks = MIDI_GetDeltaTime(track->iter); + us = ((uint64_t) nticks * us_per_beat) / ticks_per_beat; + + // Set a timer to be invoked when the next event is + // ready to play. + + OPL_SetCallback(us, TrackTimerCallback, track); +} + +// Initialize a channel. + +static void InitChannel(opl_channel_data_t *channel) +{ + // TODO: Work out sensible defaults? + + channel->instrument = &main_instrs[0]; + channel->volume = current_music_volume; + channel->volume_base = 100; + if (channel->volume > channel->volume_base) + { + channel->volume = channel->volume_base; + } + channel->pan = 0x30; + channel->bend = 0; +} + +// Start a MIDI track playing: + +static void StartTrack(const midi_file_t *file, unsigned int track_num) +{ + opl_track_data_t *track; + + track = &tracks[track_num]; + track->iter = MIDI_IterateTrack(file, track_num); + + // Schedule the first event. + + ScheduleTrack(track); +} + +// Start playing a mid + +static void I_OPL_PlaySong(const void *handle, int looping) +{ + const midi_file_t *file; + unsigned int i; + + if (!music_initialized || handle == NULL) + { + return; + } + + // This is required to interface with OPL (I think) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wcast-qual" + file = (midi_file_t*)handle; + #pragma GCC diagnostic pop + + // Allocate track data. + + tracks = (opl_track_data_t*)Z_Malloc(MIDI_NumTracks(file) * sizeof(opl_track_data_t)); + + num_tracks = MIDI_NumTracks(file); + running_tracks = num_tracks; + song_looping = looping; + + ticks_per_beat = MIDI_GetFileTimeDivision(file); + + // Default is 120 bpm. + // TODO: this is wrong + + us_per_beat = 500 * 1000; + + start_music_volume = current_music_volume = snd_MusicVolume * 127 / 15; + + for (i = 0; i < num_tracks; ++i) + { + StartTrack(file, i); + } + + for (i = 0; i < MIDI_CHANNELS_PER_TRACK; ++i) + { + InitChannel(&channels[i]); + } +} + +static void I_OPL_PauseSong(void) +{ + unsigned int i; + + if (!music_initialized) + { + return; + } + + // Pause OPL callbacks. + + OPL_SetPaused(1); + + // Turn off all main instrument voices (not percussion). + // This is what Vanilla does. + + for (i = 0; i < num_opl_voices; ++i) + { + if (voices[i].channel != NULL + && voices[i].current_instr < percussion_instrs) + { + VoiceKeyOff(&voices[i]); + } + } +} + +static void I_OPL_ResumeSong(void) +{ + if (!music_initialized) + { + return; + } + + OPL_SetPaused(0); +} + +static void I_OPL_StopSong(void) +{ + unsigned int i; + + if (!music_initialized) + { + return; + } + + + // Stop all playback. + + OPL_ClearCallbacks(); + + // Free all voices. + + for (i = 0; i < MIDI_CHANNELS_PER_TRACK; ++i) + { + AllNotesOff(&channels[i], 0); + } + + // Free all track data. + + for (i=0; i 4 && !memcmp(mem, "MThd", 4); +} + +// now only takes files in MIDI format +static const void *I_OPL_RegisterSong(const void *data, unsigned len) +{ + midi_file_t *result; + midimem_t mf; + + if (!music_initialized) + { + return NULL; + } + mf.len = len; + mf.pos = 0; + mf.data = data; + + // NSM: if a file has a miniscule timecode we have to not load it. + // if it's 0, we'll hang in scheduling and never finish. if it's + // very small but close to 0, there's probably something wrong with it + + // this check of course isn't very accurate, but to actually get the + // time numbers we have to traverse the tracks and everything + if (mf.len < 100) + { + lprintf (LO_WARN, "I_OPL_RegisterSong: Very short MIDI (%llu bytes)\n", (unsigned long long)mf.len); + return NULL; + } + + result = MIDI_LoadFileSpecial (&mf); + + if (result == NULL) + { + lprintf (LO_WARN, "I_OPL_RegisterSong: Failed to load MID.\n"); + } + + + return result; +} + + +static void I_OPL_ShutdownMusic(void) +{ + if (music_initialized) + { + // Stop currently-playing track, if there is one: + + I_OPL_StopSong(); + + OPL_Shutdown(); + + music_initialized = false; + } +} + +// Initialize music subsystem + +int I_OPL_InitMusic(int samplerate) +{ + opl_opl3mode = dsda_IntConfig(dsda_config_mus_opl_opl3mode); + + if (!OPL_Init(samplerate)) + { + //printf("Dude. The Adlib isn't responding.\n"); + return 0; + } + + if (opl_opl3mode) + { + num_opl_voices = OPL_NUM_VOICES * 2; + } + else + { + num_opl_voices = OPL_NUM_VOICES; + } + + // Load instruments from GENMIDI lump: + + if (!LoadInstrumentTable()) + { + OPL_Shutdown(); + return false; + } + + InitVoices(); + + tracks = NULL; + num_tracks = 0; + music_initialized = true; + + return 1; +} + +const char *I_OPL_SynthName (void) +{ + return "opl synth player"; +} + +void I_OPL_RenderSamples (void *dest, unsigned nsamp) +{ + OPL_Render_Samples (dest, nsamp); +} + +const music_player_t opl_synth_player = +{ + I_OPL_SynthName, + 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_RenderSamples +}; diff --git a/prboom2/src/MUSIC/oplplayer.h b/prboom2/src/MUSIC/oplplayer.h new file mode 100644 index 000000000..f335724c7 --- /dev/null +++ b/prboom2/src/MUSIC/oplplayer.h @@ -0,0 +1,34 @@ +// 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. +// +//----------------------------------------------------------------------------- + + +#ifndef OPLPLAYER_H +#define OPLPLAYER_H + +extern const music_player_t opl_synth_player; + + +#endif diff --git a/prboom2/src/SDL/i_sound.c b/prboom2/src/SDL/i_sound.c index d30ca8c9f..c151afaa4 100644 --- a/prboom2/src/SDL/i_sound.c +++ b/prboom2/src/SDL/i_sound.c @@ -862,6 +862,7 @@ static void PlaySong(int handle, int looping); #include "mus2mid.h" #include "MUSIC/musicplayer.h" +#include "MUSIC/oplplayer.h" #include "MUSIC/madplayer.h" #include "MUSIC/dumbplayer.h" #include "MUSIC/flplayer.h" @@ -885,6 +886,7 @@ static const music_player_t *music_players[] = &mp_player, // madplayer.h &db_player, // dumbplayer.h &fl_player, // flplayer.h + &opl_synth_player, // oplplayer.h &pm_player, // portmidiplayer.h NULL }; @@ -896,6 +898,7 @@ static int music_player_was_init[NUM_MUS_PLAYERS]; #define PLAYER_MAD "mad mp3 player" #define PLAYER_DUMB "dumb tracker player" #define PLAYER_FLUIDSYNTH "fluidsynth midi player" +#define PLAYER_OPL "opl synth player" #define PLAYER_PORTMIDI "portmidi midi player" // order in which players are to be tried @@ -905,11 +908,12 @@ char music_player_order[NUM_MUS_PLAYERS][200] = PLAYER_MAD, PLAYER_DUMB, PLAYER_FLUIDSYNTH, + PLAYER_OPL, PLAYER_PORTMIDI, }; const char *midiplayers[midi_player_last + 1] = { - "fluidsynth", "portmidi", NULL }; + "fluidsynth", "opl", "portmidi", NULL }; static int current_player = -1; static const void *music_handle = NULL; @@ -1335,12 +1339,20 @@ void M_ChangeMIDIPlayer(void) if (!strcasecmp(snd_midiplayer, midiplayers[midi_player_fluidsynth])) { strcpy(music_player_order[3], PLAYER_FLUIDSYNTH); - strcpy(music_player_order[4], PLAYER_PORTMIDI); + strcpy(music_player_order[4], PLAYER_OPL); + strcpy(music_player_order[5], PLAYER_PORTMIDI); + } + else if (!strcasecmp(snd_midiplayer, midiplayers[midi_player_opl])) + { + strcpy(music_player_order[3], PLAYER_OPL); + strcpy(music_player_order[4], PLAYER_FLUIDSYNTH); + strcpy(music_player_order[5], PLAYER_PORTMIDI); } else if (!strcasecmp(snd_midiplayer, midiplayers[midi_player_portmidi])) { strcpy(music_player_order[3], PLAYER_PORTMIDI); strcpy(music_player_order[4], PLAYER_FLUIDSYNTH); + strcpy(music_player_order[5], PLAYER_OPL); } S_StopMusic(); diff --git a/prboom2/src/dsda/configuration.c b/prboom2/src/dsda/configuration.c index 4e7ff1979..812231826 100644 --- a/prboom2/src/dsda/configuration.c +++ b/prboom2/src/dsda/configuration.c @@ -752,6 +752,14 @@ dsda_config_t dsda_config[dsda_config_count] = { "mus_fluidsynth_reverb_room_size", dsda_config_mus_fluidsynth_reverb_room_size, dsda_config_int, 0, 1000, { 60 } }, + [dsda_config_mus_opl_gain] = { + "mus_opl_gain", dsda_config_mus_opl_gain, + dsda_config_int, 0, 1000, { 50 } + }, + [dsda_config_mus_opl_opl3mode] = { + "mus_opl_opl3mode", dsda_config_mus_opl_opl3mode, + CONF_BOOL(1) + }, [dsda_config_mus_portmidi_reset_type] = { "mus_portmidi_reset_type", dsda_config_mus_portmidi_reset_type, CONF_STRING("gm") // none, gs, gm, gm2, xg diff --git a/prboom2/src/dsda/configuration.h b/prboom2/src/dsda/configuration.h index 2e851c7b9..a69795578 100644 --- a/prboom2/src/dsda/configuration.h +++ b/prboom2/src/dsda/configuration.h @@ -159,6 +159,8 @@ typedef enum { dsda_config_mus_fluidsynth_reverb_level, dsda_config_mus_fluidsynth_reverb_width, dsda_config_mus_fluidsynth_reverb_room_size, + dsda_config_mus_opl_gain, + dsda_config_mus_opl_opl3mode, dsda_config_mus_portmidi_reset_type, dsda_config_mus_portmidi_reset_delay, dsda_config_mus_portmidi_filter_sysex, diff --git a/prboom2/src/i_sound.h b/prboom2/src/i_sound.h index 54612a873..8f6f02e0c 100644 --- a/prboom2/src/i_sound.h +++ b/prboom2/src/i_sound.h @@ -124,6 +124,7 @@ extern int snd_samplerate; typedef enum { midi_player_fluidsynth, + midi_player_opl, midi_player_portmidi, midi_player_last diff --git a/prboom2/src/m_misc.c b/prboom2/src/m_misc.c index eae63811c..62b7160a1 100644 --- a/prboom2/src/m_misc.c +++ b/prboom2/src/m_misc.c @@ -128,6 +128,8 @@ cfg_def_t cfg_defs[] = MIGRATED_SETTING(dsda_config_mus_fluidsynth_reverb_level), MIGRATED_SETTING(dsda_config_mus_fluidsynth_reverb_width), MIGRATED_SETTING(dsda_config_mus_fluidsynth_reverb_room_size), + MIGRATED_SETTING(dsda_config_mus_opl_gain), + MIGRATED_SETTING(dsda_config_mus_opl_opl3mode), MIGRATED_SETTING(dsda_config_mus_portmidi_reset_type), MIGRATED_SETTING(dsda_config_mus_portmidi_reset_delay), MIGRATED_SETTING(dsda_config_mus_portmidi_filter_sysex),