/*************************************************************************** opl2.c Copyright (C) 2002/04 Solomon Peachy, Christoph Reichenbach This program may be modified and copied freely according to the terms of the GNU general public license (GPL), as long as the above copyright notice and the licensing information contained herein are preserved. Please refer to www.gnu.org for licensing details. This work is provided AS IS, without warranty of any kind, expressed or implied, including but not limited to the warranties of merchantibility, noninfringement, and fitness for a specific purpose. The author will not be held liable for any damage caused by this work or derivatives of it. By using this source code, you agree to the licensing terms as stated above. ***************************************************************************/ #include "sci/include/resource.h" #include "sci/include/sfx_iterator.h" #include "../softseq.h" #include "../adlib.h" #include #include "fmopl.h" #ifdef _DREAMCAST #define SAMPLE_RATE 22050 #define CHANNELS SFX_PCM_MONO #define STEREO 0 #else #define SAMPLE_RATE 44100 #define CHANNELS SFX_PCM_STEREO_LR #define STEREO 1 #endif /* local function declarations */ static void opl2_allstop(sfx_softseq_t *self); //#define DEBUG_ADLIB /* portions shamelessly lifted from claudio's XMP */ /* other portions lifted from sound/opl3.c in the Linux kernel */ #define ADLIB_LEFT 0 #define ADLIB_RIGHT 1 /* #define OPL_INTERNAL_FREQ 3600000 */ #define OPL_INTERNAL_FREQ 3579545 static int ready = 0; static int pcmout_stereo = STEREO; static int register_base[11] = { 0x20, 0x23, 0x40, 0x43, 0x60, 0x63, 0x80, 0x83, 0xe0, 0xe3, 0xc0 }; static int register_offset[12] = { /* Channel 1 2 3 4 5 6 7 8 9 */ /* Operator 1 */ 0x00, 0x01, 0x02, 0x08, 0x09, 0x0A, 0x10, 0x11, 0x12, 0x18, 0x19, 0x1A }; static int ym3812_note[13] = { 0x157, 0x16b, 0x181, 0x198, 0x1b0, 0x1ca, 0x1e5, 0x202, 0x220, 0x241, 0x263, 0x287, 0x2ae }; static guint8 sci_adlib_vol_base[16] = { 0x00, 0x11, 0x15, 0x19, 0x1D, 0x22, 0x26, 0x2A, 0x2E, 0x23, 0x37, 0x3B, 0x3F, 0x3F, 0x3F, 0x3F }; static guint8 sci_adlib_vol_tables[16][64]; /* ripped out of the linux kernel, of all places. */ static gint8 fm_volume_table[128] = { -64, -48, -40, -35, -32, -29, -27, -26, -24, -23, -21, -20, -19, -18, -18, -17, -16, -15, -15, -14, -13, -13, -12, -12, -11, -11, -10, -10, -10, -9, -9, -8, -8, -8, -7, -7, -7, -6, -6, -6, -5, -5, -5, -5, -4, -4, -4, -4, -3, -3, -3, -3, -2, -2, -2, -2, -2, -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8 }; /* back to your regularly scheduled definitions */ static guint8 instr[MIDI_CHANNELS]; static guint16 pitch[MIDI_CHANNELS]; static guint8 vol[MIDI_CHANNELS]; static guint8 pan[MIDI_CHANNELS]; static int free_voices = ADLIB_VOICES; static guint8 oper_note[ADLIB_VOICES]; static guint8 oper_chn[ADLIB_VOICES]; static FM_OPL *ym3812_L = NULL; static FM_OPL *ym3812_R = NULL; static guint8 adlib_reg_L[256]; static guint8 adlib_reg_R[256]; static guint8 adlib_master; /* initialise note/operator lists, etc. */ void adlibemu_init_lists(void) { int i; int j; for (i = 0 ; i < 16 ; i++) { for (j = 0; j < 64 ; j++) { sci_adlib_vol_tables[i][j] = ((guint16)sci_adlib_vol_base[i]) * j / 63; } } for (i = 0; i < MIDI_CHANNELS ; i++) { pitch[i] = 8192; /* center the pitch wheel */ } free_voices = ADLIB_VOICES; memset(instr, 0, sizeof(instr)); memset(vol, 0x7f, sizeof(vol)); memset(pan, 0x3f, sizeof(pan)); memset(adlib_reg_L, 0, sizeof(adlib_reg_L)); memset(adlib_reg_R, 0, sizeof(adlib_reg_R)); memset(oper_chn, 0xff, sizeof(oper_chn)); memset(oper_note, 0xff, sizeof(oper_note)); adlib_master=12; } /* more shamelessly lifted from xmp and adplug. And altered. :) */ static inline int opl_write_L (int a, int v) { adlib_reg_L[a] = v; OPLWrite (ym3812_L, 0x388, a); return OPLWrite (ym3812_L, 0x389, v); } static inline int opl_write_R (int a, int v) { adlib_reg_R[a] = v; OPLWrite (ym3812_R, 0x388, a); return OPLWrite (ym3812_R, 0x389, v); } static inline int opl_write(int a, int v) { opl_write_L(a,v); return opl_write_R(a,v); } /* static inline guint8 opl_read (int a) { OPLWrite (ym3812_L, 0x388, a); return OPLRead (ym3812_L, 0x389); } */ void synth_setpatch (int voice, guint8 *data) { int i; opl_write(0xBD, 0); for (i = 0; i < 10; i++) opl_write(register_base[i] + register_offset[voice], data[i]); opl_write(register_base[10] + voice, data[10]); /* mute voice after patch change */ opl_write_L(0xb0 + voice, adlib_reg_L[0xb0+voice] & 0xdf); opl_write_R(0xb0 + voice, adlib_reg_R[0xb0+voice] & 0xdf); #ifdef DEBUG_ADLIB for (i = 0; i < 10; i++) printf("%02x ", adlib_reg_L[register_base[i]+register_offset[voice]]); printf("%02x ", adlib_reg_L[register_base[10]+voice]); #endif } void synth_setvolume_L (int voice, int volume) { gint8 level1, level2; level1 = ~adlib_reg_L[register_base[2]+register_offset[voice]] & 0x3f; level2 = ~adlib_reg_L[register_base[3]+register_offset[voice]] & 0x3f; if (level1) { level1 += sci_adlib_vol_tables[adlib_master][volume>>1]; } if (level2) { level2 += sci_adlib_vol_tables[adlib_master][volume>>1]; } if (level1 > 0x3f) level1 = 0x3f; if (level1 < 0) level1 = 0; if (level2 > 0x3f) level2 = 0x3f; if (level2 < 0) level2 = 0; /* algorithm-dependent; we may need to set both operators. */ if (adlib_reg_L[register_base[10]+voice] & 1) opl_write_L(register_base[2]+register_offset[voice], (guint8)((~level1 &0x3f) | (adlib_reg_L[register_base[2]+register_offset[voice]]&0xc0))); opl_write_L(register_base[3]+register_offset[voice], (guint8)((~level2 &0x3f) | (adlib_reg_L[register_base[3]+register_offset[voice]]&0xc0))); } void synth_setvolume_R (int voice, int volume) { gint8 level1, level2; level1 = ~adlib_reg_R[register_base[2]+register_offset[voice]] & 0x3f; level2 = ~adlib_reg_R[register_base[3]+register_offset[voice]] & 0x3f; if (level1) { level1 += sci_adlib_vol_tables[adlib_master][volume>>1]; } if (level2) { level2 += sci_adlib_vol_tables[adlib_master][volume>>1]; } if (level1 > 0x3f) level1 = 0x3f; if (level1 < 0) level1 = 0; if (level2 > 0x3f) level2 = 0x3f; if (level2 < 0) level2 = 0; /* now for the other side. */ if (adlib_reg_R[register_base[10]+voice] & 1) opl_write_R(register_base[2]+register_offset[voice], (guint8)((~level1 &0x3f) | (adlib_reg_R[register_base[2]+register_offset[voice]]&0xc0))); opl_write_R(register_base[3]+register_offset[voice], (guint8)((~level2 &0x3f) | (adlib_reg_R[register_base[3]+register_offset[voice]]&0xc0))); } void synth_setnote (int voice, int note, int bend) { int n, fre, oct; float delta; delta = 0; n = note % 12; if (bend < 8192) bend = 8192-bend; delta = pow(2, (float) (bend%8192)/8192.0); if (bend > 8192) fre = ym3812_note[n]*delta; else fre = ym3812_note[n]/delta; oct = note / 12 - 1; if (oct < 0) oct = 0; opl_write(0xa0 + voice, fre & 0xff); opl_write(0xb0 + voice, 0x20 | ((oct << 2) & 0x1c) | ((fre >> 8) & 0x03)); #ifdef DEBUG_ADLIB printf("-- %02x %02x\n", adlib_reg_L[0xa0+voice], adlib_reg_L[0xb0+voice]); #endif } /* back to your regularly scheduled driver */ int adlibemu_stop_note(int chn, int note, int velocity) { int i, op=255; // sciprintf("Note off %d %d %d\n", chn, note, velocity); for (i=0;i 0x3f) /* pan right; so we scale the left down. */ volume_L = volume_L / 0x3f * (0x3f - (pan[chn] - 0x3f)); else if (pan[chn] < 0x3f) /* pan left; so we scale the right down.*/ volume_R = volume_R / 0x3f * (0x3f - (0x3f-pan[chn])); } inst = instr[chn]; synth_setpatch(op, adlib_sbi[inst]); synth_setvolume_L(op, volume_L); synth_setvolume_R(op, volume_R); synth_setnote(op, note, pitch[chn]); oper_chn[op] = chn; oper_note[op] = note; free_voices--; #ifdef DEBUG_ADLIB printf("play voice %d (%d rem): C%02x N%02x V%02x/%02x-%02x P%02x (%02x/%02x)\n", op, free_voices, chn, note, velocity, volume_L, volume_R, inst, adlib_reg_L[register_base[2]+register_offset[op]] & 0x3f, adlib_reg_L[register_base[3]+register_offset[op]] & 0x3f); #endif return 0; } static void adlibemu_update_pitch(int chn, int note, int newpitch) { int i; int matched = 0; pitch[chn] = newpitch; for (i=0;i 0) { samples = remaining_delta * pcmout_sample_rate / 1000000; samples = sci_min(samples, remaining); if (samples) { YM3812UpdateOne(ADLIB_LEFT, ptr, samples, 1); YM3812UpdateOne(ADLIB_RIGHT, ptr+1, samples, 1); } if (remaining > samples) { remaining_delta = (remaining - samples) * 1000000 / pcmout_sample_rate; } else { song->play_next_note(); remaining_delta = song->get_next_delta(); song->advance(); } remaining -= samples; } } #endif if (pcmout_stereo) { YM3812UpdateOne (ym3812_L, ptr, count, 1); YM3812UpdateOne (ym3812_R, ptr+1, count, 1); } else { YM3812UpdateOne (ym3812_L, ptr, count, 0); } } static int opl2_init(sfx_softseq_t *self, byte *data_ptr, int data_length, byte *data2_ptr, int data2_length) { int i; /* load up the patch.003 file, parse out the instruments */ if (data_length < 1344) { sciprintf ("[sfx:seq:opl2] Invalid patch.003: Expected %d, got %d\n", 1344, data_length); return -1; } for (i = 0; i < 48; i++) make_sbi((adlib_def *)(data_ptr+(28 * i)), adlib_sbi[i]); if (data_length > 1344) for (i = 48; i < 96; i++) make_sbi((adlib_def *)(data_ptr+2+(28 * i)), adlib_sbi[i]); OPLBuildTables(FMOPL_ENV_BITS_HQ, FMOPL_EG_ENT_HQ); if (!(ym3812_L = OPLCreate (OPL_TYPE_YM3812, OPL_INTERNAL_FREQ, SAMPLE_RATE)) || !(ym3812_R = OPLCreate (OPL_TYPE_YM3812, OPL_INTERNAL_FREQ, SAMPLE_RATE))) { sciprintf ("[sfx:seq:opl2] Failure: Emulator init failed!\n"); return SFX_ERROR; } ready = 1; opl2_allstop(self); return SFX_OK; } static void opl2_exit(sfx_softseq_t *self) { FM_OPL *opl = ym3812_L; ym3812_L = NULL; OPLDestroy(opl); opl = ym3812_R; ym3812_R = NULL; OPLDestroy(opl); // XXX deregister with pcm layer. } static void opl2_allstop(sfx_softseq_t *self) { // printf("AdlibEmu: Reset\n"); if (! ready) return; adlibemu_init_lists(); OPLResetChip (ym3812_L); OPLResetChip (ym3812_R); opl_write(0x01, 0x20); opl_write(0xBD, 0xc0); #ifdef DEBUG_ADLIB printf("ADLIB: reset complete\n"); #endif // test_adlib(); } int midi_adlibemu_reverb(short param) { printf("ADLIB: reverb NYI %04x \n", param); return 0; } int midi_adlibemu_event(guint8 command, guint8 note, guint8 velocity, guint32 delta) { guint8 channel, oper; channel = command & 0x0f; oper = command & 0xf0; switch (oper) { case 0x80: return adlibemu_stop_note(channel, note, velocity); case 0x90: /* noteon and noteoff */ return adlibemu_start_note(channel, note, velocity); case 0xe0: /* Pitch bend */ { int bend = (note & 0x7f) | ((velocity & 0x7f) << 7); // printf("Bend to %d\n", bend); adlibemu_update_pitch(channel, note, bend); } case 0xb0: /* CC changes. */ switch (note) { case 0x07: vol[channel] = velocity; break; case 0x0a: pan[channel] = velocity; break; case 0x4b: break; case 0x7b: { /* all notes off */ int i = 0; for (i=0;i