diff options
author | Eugene Sandulenko | 2009-02-15 11:39:07 +0000 |
---|---|---|
committer | Eugene Sandulenko | 2009-02-15 11:39:07 +0000 |
commit | e241843bec22600ab4ef98e7a085e82aac73fc93 (patch) | |
tree | 61a793884d3462e1feb80e80f202d8816d0c8ec4 /engines/sci/sfx/softseq/opl2.cpp | |
parent | e9f742806362a84ffdb176a7414318dd2ab4df89 (diff) | |
download | scummvm-rg350-e241843bec22600ab4ef98e7a085e82aac73fc93.tar.gz scummvm-rg350-e241843bec22600ab4ef98e7a085e82aac73fc93.tar.bz2 scummvm-rg350-e241843bec22600ab4ef98e7a085e82aac73fc93.zip |
- Remove some unneeded files
- Mass rename .c to .cpp
svn-id: r38227
Diffstat (limited to 'engines/sci/sfx/softseq/opl2.cpp')
-rw-r--r-- | engines/sci/sfx/softseq/opl2.cpp | 718 |
1 files changed, 718 insertions, 0 deletions
diff --git a/engines/sci/sfx/softseq/opl2.cpp b/engines/sci/sfx/softseq/opl2.cpp new file mode 100644 index 0000000000..c19de1388a --- /dev/null +++ b/engines/sci/sfx/softseq/opl2.cpp @@ -0,0 +1,718 @@ +/*************************************************************************** + 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 <math.h> + +#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<ADLIB_VOICES && op==255;i++) { + if (oper_chn[i] == chn) + if (oper_note[i] == note) + op=i; + } + + if (op==255) { +#ifdef DEBUG_ADLIB + printf ("ADLIB: can't stop note: C%02x N%02x V%02x\n", chn, note, velocity); + printf ("C "); + for (i = 0; i < ADLIB_VOICES ; i++ ) { + printf ("%02x ", oper_chn[i]); + } + printf ("\n"); + printf ("N "); + for (i = 0; i < ADLIB_VOICES ; i++ ) { + printf ("%02x ", oper_note[i]); + } + printf ("\n"); +#endif + return -1; /* that note isn't playing.. */ + } + + opl_write_L(0xb0+op,(adlib_reg_L[0xb0+op] & 0xdf)); + opl_write_R(0xb0+op,(adlib_reg_R[0xb0+op] & 0xdf)); + + oper_chn[op] = 255; + oper_note[op] = 255; + + free_voices++; + +#ifdef DEBUG_ADLIB + printf("stop voice %d (%d rem): C%02x N%02x V%02x\n", op, free_voices, chn, note, velocity); +#endif + + return 0; +} + +int adlibemu_start_note(int chn, int note, int velocity) +{ + int op, volume_L, volume_R, inst = 0; + + // sciprintf("Note on %d %d %d\n", chn, note, velocity); + + if (velocity == 0) { + return adlibemu_stop_note(chn, note, velocity); + } + + if (free_voices <= 0) { + printf("ADLIB: All voices full\n"); /* XXX implement overflow code */ + return -1; + } + + for (op = 0; op < ADLIB_VOICES ; op++) + if (oper_chn[op] == 255) + break; + + if (op == ADLIB_VOICES) { + printf("ADLIB: WTF? We couldn't find a voice yet it we have %d left.\n", free_voices); + return -1; + } + + /* Scale channel volume */ + volume_L = velocity * vol[chn] / 127; + volume_R = velocity * vol[chn] / 127; + + /* Apply a pan */ + if (pcmout_stereo) { + if (pan[chn] > 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<ADLIB_VOICES;i++) { + if (oper_chn[i] == chn) + { + matched++; + synth_setnote(i, oper_note[i], newpitch); + } + } + +// printf("Matched %d notes on channel %d.\n", matched, chn); +} + +void test_adlib (void) { + + int voice = 0; +#if 0 + guint8 data[] = { 0x25, 0x21, 0x48, 0x48, 0xf0, 0xf2, 0xf0, 0xa5, 0x00, 0x00, 0x06 }; +#else + guint8 *data = adlib_sbi[0x0a]; +#endif + +#if 1 + opl_write(register_base[0]+register_offset[voice], data[0]); + opl_write(register_base[1]+register_offset[voice], data[1]); + opl_write(register_base[2]+register_offset[voice], data[2]); + opl_write(register_base[3]+register_offset[voice], data[3]); + opl_write(register_base[4]+register_offset[voice], data[4]); + opl_write(register_base[5]+register_offset[voice], data[5]); + opl_write(register_base[6]+register_offset[voice], data[6]); + opl_write(register_base[7]+register_offset[voice], data[7]); + opl_write(register_base[8]+register_offset[voice], data[8]); + opl_write(register_base[9]+register_offset[voice], data[9]); + opl_write(register_base[10]+register_offset[voice], data[10]); +#else + synth_setpatch(voice, data); +#endif + +#if 0 + opl_write(0xA0 + voice, 0x57); + opl_write(0xB0 + voice, 0x2d); +#else + synth_setvolume_L(voice, 0x50); + synth_setvolume_R(voice, 0x50); + synth_setnote(voice, 0x30, 0); +#endif + + /* + instr[0x0e] = 0x0a; + instr[0x03] = 0x26; + + adlibemu_start_note(0x0e, 0x30, 0x40); + sleep(1); + adlibemu_start_note(0x03, 0x48, 0x40); + sleep(1); + */ +} + + +/* count is # of FRAMES, not bytes. + We assume 16-bit stereo frames (ie 4 bytes) +*/ +static void +opl2_poll (sfx_softseq_t *self, byte *dest, int count) +{ + gint16 *buffer = (gint16 *) dest; + gint16 *ptr = buffer; + + if (!ready) { + fprintf(stderr, "synth_mixer(): !ready \n"); + exit(1); + } + + if (!buffer) { + fprintf(stderr, "synth_mixer(): !buffer \n"); + exit(1); + } + +#if 0 + { + static unsigned long remaining_delta = 0; + int samples; + int remaining = count; + + while (remaining > 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<ADLIB_VOICES;i++) + if (oper_chn[i] == channel) + adlibemu_stop_note(channel, oper_note[i], 0); + break; + } + default: + ; /* XXXX ignore everything else for now */ + } + return 0; + case 0xd0: /* aftertouch */ + /* XXX Aftertouch in the OPL thing? */ + return 0; + default: + printf("ADLIB: Unknown event %02x\n", command); + return 0; + } + + return 0; +} + +int midi_adlibemu_event2(guint8 command, guint8 param, guint32 delta) +{ + guint8 channel; + guint8 oper; + + channel = command & 0x0f; + oper = command & 0xf0; + switch (oper) { + case 0xc0: /* change instrument */ +#ifdef DEBUG_ADLIB + printf("ADLIB: Selecting instrument %d on channel %d\n", param, channel); +#endif + instr[channel] = param; + return 0; + default: + printf("ADLIB: Unknown event %02x\n", command); + } + + return 0; +} + +static void +opl2_volume(sfx_softseq_t *self, int volume) +{ + guint8 i; + + i = (guint8)volume * 15 / 100; + + adlib_master=i; + +#ifdef DEBUG_ADLIB + printf("ADLIB: master volume set to %d\n", adlib_master); +#endif +} + +int +opl2_set_option(sfx_softseq_t *self, char *name, char *value) +{ + return SFX_ERROR; +} + +void +opl2_event(sfx_softseq_t *self, byte cmd, int argc, byte *argv) +{ + if (argc == 1) + midi_adlibemu_event2(cmd, argv[0], 0); + else if (argc == 2) + midi_adlibemu_event(cmd, argv[0], argv[1], 0); +} + +/* the driver struct */ + +sfx_softseq_t sfx_softseq_opl2 = { + "adlibemu", + "0.1", + opl2_set_option, + opl2_init, + opl2_exit, + opl2_volume, + opl2_event, + opl2_poll, + opl2_allstop, + NULL, + 3, /* use patch.003 */ + SFX_SEQ_PATCHFILE_NONE, + 0x4, /* Play flags */ + 0, /* No rhythm channel (9) */ + ADLIB_VOICES, /* # of voices */ + {SAMPLE_RATE, CHANNELS, SFX_PCM_FORMAT_S16_NATIVE} +}; |