--- /dev/null
+/* midi.c
+ *
+ * Adlib OPL2/OPL3 FM synthesizer chipset test program.
+ * Play MIDI file using the OPLx synthesizer (well, poorly anyway)
+ * (C) 2010-2012 Jonathan Campbell.
+ * Hackipedia DOS library.
+ *
+ * This code is licensed under the LGPL.
+ * <insert LGPL legal text here>
+ *
+ * Compiles for intended target environments:
+ * - MS-DOS [pure DOS mode, or Windows or OS/2 DOS Box]
+ */
+
+#include <stdio.h>
+#include <conio.h> /* this is where Open Watcom hides the outp() etc. functions */
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <malloc.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <math.h>
+#include <dos.h>
+
+#include "src/lib/doslib/vga.h"
+#include "src/lib/doslib/dos.h"
+#include "src/lib/doslib/8254.h" /* 8254 timer */
+#include "src/lib/doslib/8259.h"
+#include "src/lib/doslib/vgagui.h"
+#include "src/lib/doslib/vgatty.h"
+#include "src/lib/doslib/adlib.h"
+
+/* one per OPL channel */
+struct midi_note {
+ unsigned char note_number;
+ unsigned char note_velocity;
+ unsigned char note_track; /* from what MIDI track */
+ unsigned char note_channel; /* from what MIDI channel */
+ unsigned int busy:1; /* if occupied */
+};
+
+struct midi_channel {
+ unsigned char program;
+};
+
+struct midi_track {
+ /* track data, raw */
+ unsigned char* raw; /* raw data base */
+ unsigned char* fence; /* raw data end (last byte + 1) */
+ unsigned char* read; /* raw data read ptr */
+ /* state */
+ unsigned long us_per_quarter_note; /* Microseconds per quarter note (def 120 BPM) */
+ unsigned long us_tick_cnt_mtpq; /* Microseconds advanced (up to 10000 us or one unit at 100Hz) x ticks per quarter note */
+ unsigned long wait;
+ unsigned char last_status; /* MIDI last status byte */
+ unsigned int eof:1; /* we hit the end of the track */
+};
+
+#define MIDI_MAX_CHANNELS 16
+#define MIDI_MAX_TRACKS 64
+
+struct midi_note midi_notes[ADLIB_FM_VOICES];
+struct midi_channel midi_ch[MIDI_MAX_CHANNELS];
+struct midi_track midi_trk[MIDI_MAX_TRACKS];
+static unsigned int midi_trk_count=0;
+static volatile unsigned char midi_playing=0;
+
+/* MIDI params. Nobody ever said it was a straightforward standard!
+ * NTS: These are for reading reference. Internally we convert everything to 100Hz time base. */
+static unsigned int ticks_per_quarter_note=0; /* "Ticks per beat" */
+
+static void (interrupt *old_irq0)();
+static volatile unsigned long irq0_ticks=0;
+static volatile unsigned int irq0_cnt=0,irq0_add=0,irq0_max=0;
+
+#if TARGET_MSDOS == 16 && (defined(__LARGE__) || defined(__COMPACT__))
+static inline unsigned long farptr2phys(unsigned char far *p) { /* take 16:16 pointer convert to physical memory address */
+ return ((unsigned long)FP_SEG(p) << 4UL) + ((unsigned long)FP_OFF(p));
+}
+#endif
+
+static inline unsigned char midi_trk_read(struct midi_track *t) {
+ unsigned char c;
+
+ /* NTS: 16-bit large/compact builds MUST compare pointers as unsigned long to compare FAR pointers correctly! */
+ if (t->read == NULL || (unsigned long)t->read >= (unsigned long)t->fence) {
+ t->eof = 1;
+ return 0xFF;
+ }
+
+ c = *(t->read);
+#if TARGET_MSDOS == 16 && (defined(__LARGE__) || defined(__COMPACT__))
+ if (FP_OFF(t->read) >= 0xF) /* 16:16 far pointer aware (NTS: Programs reassigning this pointer MUST normalize the FAR pointer) */
+ t->read = MK_FP(FP_SEG(t->read)+0x1,0);
+ else
+ t->read++;
+#else
+ t->read++;
+#endif
+ return c;
+}
+
+void midi_trk_end(struct midi_track *t) {
+ t->wait = ~0UL;
+ t->read = t->fence;
+}
+
+void midi_trk_skip(struct midi_track *t,unsigned long len) {
+ unsigned long rem;
+
+ /* NTS: 16-bit large/compact builds MUST compare pointers as unsigned long to compare FAR pointers correctly! */
+ if (t->read == NULL || (unsigned long)t->read >= (unsigned long)t->fence)
+ return;
+
+ if (len > 0xFFF0UL) {
+ midi_trk_end(t);
+ return;
+ }
+#if TARGET_MSDOS == 16 && (defined(__LARGE__) || defined(__COMPACT__))
+ {
+ unsigned long tt;
+
+ tt = farptr2phys(t->read);
+ rem = farptr2phys(t->fence) - tt;
+ if (rem > len) rem = len;
+ tt += rem;
+ t->read = MK_FP(tt>>4,tt&0xF);
+ }
+#else
+ rem = (unsigned long)(t->fence - t->read);
+ if (len > rem) len = rem;
+ t->read += len;
+#endif
+}
+
+static const uint32_t midikeys_freqs[0x80] = {
+ 0x00082d01, /* key 0 = 8.17579891564371Hz */
+ 0x0008a976, /* key 1 = 8.66195721802725Hz */
+ 0x00092d51, /* key 2 = 9.17702399741899Hz */
+ 0x0009b904, /* key 3 = 9.72271824131503Hz */
+ 0x000a4d05, /* key 4 = 10.3008611535272Hz */
+ 0x000ae9d3, /* key 5 = 10.9133822322814Hz */
+ 0x000b8ff4, /* key 6 = 11.5623257097386Hz */
+ 0x000c3ff6, /* key 7 = 12.2498573744297Hz */
+ 0x000cfa70, /* key 8 = 12.9782717993733Hz */
+ 0x000dc000, /* key 9 = 13.75Hz */
+ 0x000e914f, /* key 10 = 14.5676175474403Hz */
+ 0x000f6f11, /* key 11 = 15.4338531642539Hz */
+ 0x00105a02, /* key 12 = 16.3515978312874Hz */
+ 0x001152ec, /* key 13 = 17.3239144360545Hz */
+ 0x00125aa2, /* key 14 = 18.354047994838Hz */
+ 0x00137208, /* key 15 = 19.4454364826301Hz */
+ 0x00149a0a, /* key 16 = 20.6017223070544Hz */
+ 0x0015d3a6, /* key 17 = 21.8267644645627Hz */
+ 0x00171fe9, /* key 18 = 23.1246514194771Hz */
+ 0x00187fed, /* key 19 = 24.4997147488593Hz */
+ 0x0019f4e0, /* key 20 = 25.9565435987466Hz */
+ 0x001b8000, /* key 21 = 27.5Hz */
+ 0x001d229e, /* key 22 = 29.1352350948806Hz */
+ 0x001ede22, /* key 23 = 30.8677063285078Hz */
+ 0x0020b404, /* key 24 = 32.7031956625748Hz */
+ 0x0022a5d8, /* key 25 = 34.647828872109Hz */
+ 0x0024b545, /* key 26 = 36.7080959896759Hz */
+ 0x0026e410, /* key 27 = 38.8908729652601Hz */
+ 0x00293414, /* key 28 = 41.2034446141087Hz */
+ 0x002ba74d, /* key 29 = 43.6535289291255Hz */
+ 0x002e3fd2, /* key 30 = 46.2493028389543Hz */
+ 0x0030ffda, /* key 31 = 48.9994294977187Hz */
+ 0x0033e9c0, /* key 32 = 51.9130871974931Hz */
+ 0x00370000, /* key 33 = 55Hz */
+ 0x003a453d, /* key 34 = 58.2704701897612Hz */
+ 0x003dbc44, /* key 35 = 61.7354126570155Hz */
+ 0x00416809, /* key 36 = 65.4063913251497Hz */
+ 0x00454bb0, /* key 37 = 69.295657744218Hz */
+ 0x00496a8b, /* key 38 = 73.4161919793519Hz */
+ 0x004dc820, /* key 39 = 77.7817459305202Hz */
+ 0x00526829, /* key 40 = 82.4068892282175Hz */
+ 0x00574e9b, /* key 41 = 87.307057858251Hz */
+ 0x005c7fa4, /* key 42 = 92.4986056779086Hz */
+ 0x0061ffb5, /* key 43 = 97.9988589954373Hz */
+ 0x0067d380, /* key 44 = 103.826174394986Hz */
+ 0x006e0000, /* key 45 = 110Hz */
+ 0x00748a7b, /* key 46 = 116.540940379522Hz */
+ 0x007b7888, /* key 47 = 123.470825314031Hz */
+ 0x0082d012, /* key 48 = 130.812782650299Hz */
+ 0x008a9760, /* key 49 = 138.591315488436Hz */
+ 0x0092d517, /* key 50 = 146.832383958704Hz */
+ 0x009b9041, /* key 51 = 155.56349186104Hz */
+ 0x00a4d053, /* key 52 = 164.813778456435Hz */
+ 0x00ae9d36, /* key 53 = 174.614115716502Hz */
+ 0x00b8ff49, /* key 54 = 184.997211355817Hz */
+ 0x00c3ff6a, /* key 55 = 195.997717990875Hz */
+ 0x00cfa700, /* key 56 = 207.652348789973Hz */
+ 0x00dc0000, /* key 57 = 220Hz */
+ 0x00e914f6, /* key 58 = 233.081880759045Hz */
+ 0x00f6f110, /* key 59 = 246.941650628062Hz */
+ 0x0105a025, /* key 60 = 261.625565300599Hz */
+ 0x01152ec0, /* key 61 = 277.182630976872Hz */
+ 0x0125aa2e, /* key 62 = 293.664767917408Hz */
+ 0x01372082, /* key 63 = 311.126983722081Hz */
+ 0x0149a0a7, /* key 64 = 329.62755691287Hz */
+ 0x015d3a6d, /* key 65 = 349.228231433004Hz */
+ 0x0171fe92, /* key 66 = 369.994422711634Hz */
+ 0x0187fed4, /* key 67 = 391.995435981749Hz */
+ 0x019f4e00, /* key 68 = 415.304697579945Hz */
+ 0x01b80000, /* key 69 = 440Hz */
+ 0x01d229ec, /* key 70 = 466.16376151809Hz */
+ 0x01ede220, /* key 71 = 493.883301256124Hz */
+ 0x020b404a, /* key 72 = 523.251130601197Hz */
+ 0x022a5d81, /* key 73 = 554.365261953744Hz */
+ 0x024b545c, /* key 74 = 587.329535834815Hz */
+ 0x026e4104, /* key 75 = 622.253967444162Hz */
+ 0x0293414f, /* key 76 = 659.25511382574Hz */
+ 0x02ba74da, /* key 77 = 698.456462866008Hz */
+ 0x02e3fd24, /* key 78 = 739.988845423269Hz */
+ 0x030ffda9, /* key 79 = 783.990871963499Hz */
+ 0x033e9c01, /* key 80 = 830.60939515989Hz */
+ 0x03700000, /* key 81 = 880Hz */
+ 0x03a453d8, /* key 82 = 932.32752303618Hz */
+ 0x03dbc440, /* key 83 = 987.766602512248Hz */
+ 0x04168094, /* key 84 = 1046.50226120239Hz */
+ 0x0454bb03, /* key 85 = 1108.73052390749Hz */
+ 0x0496a8b8, /* key 86 = 1174.65907166963Hz */
+ 0x04dc8208, /* key 87 = 1244.50793488832Hz */
+ 0x0526829e, /* key 88 = 1318.51022765148Hz */
+ 0x0574e9b5, /* key 89 = 1396.91292573202Hz */
+ 0x05c7fa49, /* key 90 = 1479.97769084654Hz */
+ 0x061ffb53, /* key 91 = 1567.981743927Hz */
+ 0x067d3802, /* key 92 = 1661.21879031978Hz */
+ 0x06e00000, /* key 93 = 1760Hz */
+ 0x0748a7b1, /* key 94 = 1864.65504607236Hz */
+ 0x07b78880, /* key 95 = 1975.5332050245Hz */
+ 0x082d0128, /* key 96 = 2093.00452240479Hz */
+ 0x08a97607, /* key 97 = 2217.46104781498Hz */
+ 0x092d5171, /* key 98 = 2349.31814333926Hz */
+ 0x09b90410, /* key 99 = 2489.01586977665Hz */
+ 0x0a4d053c, /* key 100 = 2637.02045530296Hz */
+ 0x0ae9d36b, /* key 101 = 2793.82585146403Hz */
+ 0x0b8ff493, /* key 102 = 2959.95538169308Hz */
+ 0x0c3ff6a7, /* key 103 = 3135.96348785399Hz */
+ 0x0cfa7005, /* key 104 = 3322.43758063956Hz */
+ 0x0dc00000, /* key 105 = 3520Hz */
+ 0x0e914f62, /* key 106 = 3729.31009214472Hz */
+ 0x0f6f1100, /* key 107 = 3951.06641004899Hz */
+ 0x105a0250, /* key 108 = 4186.00904480958Hz */
+ 0x1152ec0e, /* key 109 = 4434.92209562995Hz */
+ 0x125aa2e3, /* key 110 = 4698.63628667852Hz */
+ 0x13720820, /* key 111 = 4978.03173955329Hz */
+ 0x149a0a79, /* key 112 = 5274.04091060592Hz */
+ 0x15d3a6d6, /* key 113 = 5587.65170292806Hz */
+ 0x171fe927, /* key 114 = 5919.91076338615Hz */
+ 0x187fed4e, /* key 115 = 6271.92697570799Hz */
+ 0x19f4e00a, /* key 116 = 6644.87516127912Hz */
+ 0x1b800000, /* key 117 = 7040Hz */
+ 0x1d229ec4, /* key 118 = 7458.62018428944Hz */
+ 0x1ede2200, /* key 119 = 7902.13282009799Hz */
+ 0x20b404a1, /* key 120 = 8372.01808961916Hz */
+ 0x22a5d81c, /* key 121 = 8869.84419125991Hz */
+ 0x24b545c7, /* key 122 = 9397.27257335704Hz */
+ 0x26e41040, /* key 123 = 9956.06347910659Hz */
+ 0x293414f2, /* key 124 = 10548.0818212118Hz */
+ 0x2ba74dac, /* key 125 = 11175.3034058561Hz */
+ 0x2e3fd24f, /* key 126 = 11839.8215267723Hz */
+ 0x30ffda9c /* key 127 = 12543.853951416Hz */
+};
+
+static uint32_t midi_note_freq(struct midi_channel *ch,unsigned char key) {
+ return midikeys_freqs[key&0x7F];
+}
+
+static struct midi_note *get_fm_note(struct midi_track *t,struct midi_channel *ch,unsigned char key,unsigned char do_alloc) {
+ unsigned int tch = (unsigned int)(t - midi_trk); /* pointer math */
+ unsigned int ach = (unsigned int)(ch - midi_ch); /* pointer math */
+ unsigned int i,freen=~0;
+
+ for (i=0;i < ADLIB_FM_VOICES;i++) {
+ if (midi_notes[i].busy) {
+ if (midi_notes[i].note_channel == ach && midi_notes[i].note_track == tch && midi_notes[i].note_number == key)
+ return &midi_notes[i];
+ }
+ else {
+ if (freen == ~0) freen = i;
+ }
+ }
+
+ if (do_alloc && freen != ~0) return &midi_notes[freen];
+ return NULL;
+}
+
+static void drop_fm_note(struct midi_channel *ch,unsigned char key) {
+ unsigned int ach = (unsigned int)(ch - midi_ch); /* pointer math */
+ unsigned int i;
+
+ for (i=0;i < ADLIB_FM_VOICES;i++) {
+ if (midi_notes[i].busy && midi_notes[i].note_channel == ach) {
+ midi_notes[i].busy = 0;
+ break;
+ }
+ }
+}
+
+static inline void on_key_aftertouch(struct midi_track *t,struct midi_channel *ch,unsigned char key,unsigned char vel) {
+ struct midi_note *note = get_fm_note(t,ch,key,/*do_alloc*/0);
+ uint32_t freq = midi_note_freq(ch,key);
+ unsigned int ach;
+
+ if (note == NULL) return;
+
+ note->busy = 1;
+ note->note_number = key;
+ note->note_velocity = vel;
+ note->note_track = (unsigned int)(t - midi_trk);
+ note->note_channel = (unsigned int)(ch - midi_ch);
+ ach = (unsigned int)(note - midi_notes); /* which FM channel? */
+ adlib_freq_to_fm_op(&adlib_fm[ach].mod,(double)freq / 65536);
+ adlib_fm[ach].mod.attack_rate = vel >> 3; /* 0-127 to 0-15 */
+ adlib_fm[ach].mod.sustain_level = vel >> 3;
+ adlib_fm[ach].mod.key_on = 1;
+ adlib_update_groupA0(ach,&adlib_fm[ach]);
+}
+
+static inline void on_key_on(struct midi_track *t,struct midi_channel *ch,unsigned char key,unsigned char vel) {
+ struct midi_note *note = get_fm_note(t,ch,key,/*do_alloc*/1);
+ uint32_t freq = midi_note_freq(ch,key);
+ unsigned int ach;
+
+ /* HACK: Ignore percussion */
+ if ((ch->program >= 8 && ch->program <= 15)/*Chromatic percussion*/ ||
+ (ch->program >= 112 && ch->program <= 119)/*Percussive*/ ||
+ ch == &midi_ch[9]/*MIDI channel 10 (DAMN YOU 1-BASED COUNTING)*/)
+ return;
+
+ if (note == NULL) {
+ /* then we'll have to knock one off to make room */
+ drop_fm_note(ch,key);
+ note = get_fm_note(t,ch,key,1);
+ if (note == NULL) return;
+ }
+
+ note->busy = 1;
+ note->note_number = key;
+ note->note_velocity = vel;
+ note->note_track = (unsigned int)(t - midi_trk);
+ note->note_channel = (unsigned int)(ch - midi_ch);
+ ach = (unsigned int)(note - midi_notes); /* which FM channel? */
+ adlib_freq_to_fm_op(&adlib_fm[ach].mod,(double)freq / 65536);
+ adlib_fm[ach].mod.attack_rate = vel >> 3; /* 0-127 to 0-15 */
+ adlib_fm[ach].mod.sustain_level = vel >> 3;
+ adlib_fm[ach].mod.key_on = 1;
+ adlib_update_groupA0(ach,&adlib_fm[ach]);
+}
+
+static inline void on_key_off(struct midi_track *t,struct midi_channel *ch,unsigned char key,unsigned char vel) {
+ struct midi_note *note = get_fm_note(t,ch,key,/*do_alloc*/0);
+ uint32_t freq = midi_note_freq(ch,key);
+ unsigned int ach;
+
+ if (note == NULL) return;
+
+ note->busy = 0;
+ ach = (unsigned int)(note - midi_notes); /* which FM channel? */
+ adlib_freq_to_fm_op(&adlib_fm[ach].mod,(double)freq / 65536);
+ adlib_fm[ach].mod.attack_rate = vel >> 3; /* 0-127 to 0-15 */
+ adlib_fm[ach].mod.sustain_level = vel >> 3;
+ adlib_fm[ach].mod.key_on = 0;
+ adlib_update_groupA0(ach,&adlib_fm[ach]);
+}
+
+static inline void on_control_change(struct midi_track *t,struct midi_channel *ch,unsigned char num,unsigned char val) {
+}
+
+static inline void on_program_change(struct midi_track *t,struct midi_channel *ch,unsigned char inst) {
+ ch->program = inst;
+}
+
+static inline void on_channel_aftertouch(struct midi_track *t,struct midi_channel *ch,unsigned char velocity) {
+}
+
+static inline void on_pitch_bend(struct midi_track *t,struct midi_channel *ch,int bend/*-8192 to 8192*/) {
+}
+
+unsigned long midi_trk_read_delta(struct midi_track *t) {
+ unsigned long tc = 0;
+ unsigned char c = 0,b;
+
+ /* NTS: 16-bit large/compact builds MUST compare pointers as unsigned long to compare FAR pointers correctly! */
+ if (t->read == NULL || (unsigned long)t->read >= (unsigned long)t->fence)
+ return tc;
+
+ while (c < 4) {
+ b = midi_trk_read(t);
+ tc = (tc << 7UL) + (unsigned long)(b&0x7F);
+ if (!(b&0x80)) break;
+ c++;
+ }
+
+ return tc;
+}
+
+void midi_tick_track(unsigned int i) {
+ struct midi_track *t = midi_trk + i;
+ struct midi_channel *ch;
+ unsigned char b,c,d;
+ int cnt=0;
+
+ /* NTS: 16-bit large/compact builds MUST compare pointers as unsigned long to compare FAR pointers correctly! */
+ if (t->read == NULL || (unsigned long)t->read >= (unsigned long)t->fence) {
+ t->eof = 1;
+ return;
+ }
+
+ t->us_tick_cnt_mtpq += 10000UL * (unsigned long)ticks_per_quarter_note;
+ while (t->us_tick_cnt_mtpq >= t->us_per_quarter_note) {
+ t->us_tick_cnt_mtpq -= t->us_per_quarter_note;
+ cnt++;
+
+ while (t->wait == 0) {
+ if ((unsigned long)t->read >= (unsigned long)t->fence) {
+ t->eof = 1;
+ break;
+ }
+
+ /* read pointer should be pointing at MIDI event bytes, just after the time delay */
+ b = midi_trk_read(t);
+ if (b&0x80) {
+ if (b < 0xF8) {
+ if (b >= 0xF0)
+ t->last_status = 0;
+ else
+ t->last_status = b;
+ }
+ if (b != 0x00 && ((b&0xF8) != 0xF0))
+ c = midi_trk_read(t);
+ }
+ else {
+ /* blegh. last status */
+ c = b;
+ b = t->last_status;
+ }
+ switch (b>>4) {
+ case 0x8: { /* note off */
+ d = midi_trk_read(t);
+ ch = midi_ch + (b&0xF); /* c=key d=velocity */
+ on_key_off(t,ch,c,d);
+ } break;
+ case 0x9: { /* note on */
+ d = midi_trk_read(t);
+ ch = midi_ch + (b&0xF); /* c=key d=velocity */
+ if (d != 0) on_key_on(t,ch,c,d); /* "A Note On with a velocity of 0 is actually a note off" Bleh, really? */
+ else on_key_off(t,ch,c,d);
+ } break;
+ case 0xA: { /* polyphonic aftertouch */
+ d = midi_trk_read(t);
+ ch = midi_ch + (b&0xF); /* c=key d=velocity */
+ on_key_aftertouch(t,ch,c,d);
+ } break;
+ case 0xB: { /* control change */
+ d = midi_trk_read(t);
+ ch = midi_ch + (b&0xF); /* c=key d=velocity */
+ on_control_change(t,ch,c,d);
+ } break;
+ case 0xC: { /* program change */
+ on_program_change(t,ch,c); /* c=instrument d=not used */
+ } break;
+ case 0xD: { /* channel aftertouch */
+ on_channel_aftertouch(t,ch,c); /* c=velocity d=not used */
+ } break;
+ case 0xE: { /* pitch bend */
+ d = midi_trk_read(t);
+ on_pitch_bend(t,ch,((c&0x7F)|((d&0x7F)<<7))-8192); /* c=LSB d=MSB */
+ } break;
+ case 0xF: { /* event */
+ if (b == 0xFF) {
+ if (c == 0x7F) { /* c=type d=len */
+ unsigned long len = midi_trk_read_delta(t);
+// fprintf(stderr,"Type 0x7F len=%lu %p/%p/%p\n",len,t->raw,t->read,t->fence);
+ if (len < 512UL) {
+ /* unknown */
+ midi_trk_skip(t,len);
+ }
+ else {
+ midi_trk_end(t);
+ }
+ }
+ else if (c < 0x7F) {
+ d = midi_trk_read(t);
+
+ if (c == 0x51 && d >= 3) {
+ d -= 3;
+ t->us_per_quarter_note = ((unsigned long)midi_trk_read(t)<<16UL)+
+ ((unsigned long)midi_trk_read(t)<<8UL)+
+ ((unsigned long)midi_trk_read(t)<<0UL);
+
+ if (1/*TODO: If format 0 or format 1*/) {
+ /* Ugh. Unless format 2, the tempo applies to all tracks */
+ int j;
+
+ for (j=0;j < midi_trk_count;j++) {
+ if (j != i) midi_trk[j].us_per_quarter_note =
+ t->us_per_quarter_note;
+ }
+ }
+ }
+ else {
+// fprintf(stderr,"Type 0x%02x len=%lu %p/%p/%p\n",c,d,t->raw,t->read,t->fence);
+ }
+
+ midi_trk_skip(t,d);
+ }
+ else {
+ fprintf(stderr,"t=%u Unknown MIDI f message 0x%02x 0x%02x %p/%p/%p\n",i,b,c,t->raw,t->read,t->fence);
+ }
+ }
+ else {
+ unsigned long len = midi_trk_read_delta(t);
+// fprintf(stderr,"Sysex len=%lu %p/%p/%p\n",len,t->raw,t->read,t->fence);
+ midi_trk_skip(t,len);
+ }
+ } break;
+ default:
+ if (b != 0x00) {
+ fprintf(stderr,"t=%u Unknown MIDI message 0x%02x at %p/%p/%p\n",i,b,t->raw,t->read,t->fence);
+ midi_trk_end(t);
+ }
+ break;
+ };
+
+ /* and then read the next event */
+ t->wait = midi_trk_read_delta(t);
+ }
+ if (t->wait != 0) {
+ t->wait--;
+ }
+ }
+}
+
+void adlib_shut_up();
+void midi_reset_tracks();
+void midi_reset_channels();
+
+void midi_tick() {
+ if (midi_playing) {
+ unsigned int i;
+ int eof=0;
+
+ for (i=0;i < midi_trk_count;i++) {
+ midi_tick_track(i);
+ eof += midi_trk[i].eof?1:0;
+ }
+
+ if (eof >= midi_trk_count) {
+ adlib_shut_up();
+ midi_reset_tracks();
+ midi_reset_channels();
+ }
+ }
+}
+
+/* WARNING: subroutine call in interrupt handler. make sure you compile with -zu flag for large/compact memory models */
+void interrupt irq0() {
+// midi_tick();
+ irq0_ticks++;
+ if ((irq0_cnt += irq0_add) >= irq0_max) {
+ irq0_cnt -= irq0_max;
+ old_irq0();
+ }
+ else {
+ p8259_OCW2(0,P8259_OCW2_NON_SPECIFIC_EOI);
+ }
+}
+
+void adlib_shut_up() {
+ int i;
+
+ memset(adlib_fm,0,sizeof(adlib_fm));
+ memset(&adlib_reg_bd,0,sizeof(adlib_reg_bd));
+ for (i=0;i < adlib_fm_voices;i++) {
+ struct adlib_fm_operator *f;
+ f = &adlib_fm[i].mod;
+ f->ch_a = f->ch_b = f->ch_c = f->ch_d = 1;
+ f = &adlib_fm[i].car;
+ f->ch_a = f->ch_b = f->ch_c = f->ch_d = 1;
+ }
+
+ for (i=0;i < adlib_fm_voices;i++) {
+ struct adlib_fm_operator *f;
+
+ midi_notes[i].busy = 0;
+ midi_notes[i].note_channel = 0;
+
+ f = &adlib_fm[i].mod;
+ f->mod_multiple = 1;
+ f->total_level = 63 - 16;
+ f->attack_rate = 15;
+ f->decay_rate = 4;
+ f->sustain_level = 0;
+ f->release_rate = 8;
+ f->f_number = 400;
+ f->sustain = 1;
+ f->octave = 4;
+ f->key_on = 0;
+
+ f = &adlib_fm[i].car;
+ f->mod_multiple = 1;
+ f->total_level = 63 - 16;
+ f->attack_rate = 15;
+ f->decay_rate = 4;
+ f->sustain_level = 0;
+ f->release_rate = 8;
+ f->f_number = 0;
+ f->sustain = 1;
+ f->octave = 0;
+ f->key_on = 0;
+ }
+
+ adlib_apply_all();
+}
+
+void midi_reset_track(unsigned int i) {
+ struct midi_track *t;
+
+ if (i >= MIDI_MAX_TRACKS) return;
+ t = &midi_trk[i];
+ t->eof = 0;
+ t->last_status = 0;
+ t->us_tick_cnt_mtpq = 0;
+ t->us_per_quarter_note = (60000000UL / 120UL); /* 120BPM */
+ t->read = midi_trk[i].raw;
+ t->wait = midi_trk_read_delta(t); /* and then the read pointer will point at the MIDI event when wait counts down */
+}
+
+void midi_reset_tracks() {
+ int i;
+
+ for (i=0;i < midi_trk_count;i++)
+ midi_reset_track(i);
+}
+
+void midi_reset_channels() {
+ int i;
+
+ for (i=0;i < MIDI_MAX_CHANNELS;i++) {
+ midi_ch[i].program = 0;
+ }
+}
+
+int load_midi_file(const char *path) {
+ unsigned char tmp[256];
+ unsigned int tracks=0;
+ unsigned int tracki=0;
+ int fd;
+
+ fd = open(path,O_RDONLY|O_BINARY);
+ if (fd < 0) {
+ printf("Failed to load file %s\n",path);
+ return 0;
+ }
+
+ ticks_per_quarter_note = 0;
+ while (read(fd,tmp,8) == 8) {
+ uint32_t sz;
+
+ sz = ((uint32_t)tmp[4] << (uint32_t)24) |
+ ((uint32_t)tmp[5] << (uint32_t)16) |
+ ((uint32_t)tmp[6] << (uint32_t)8) |
+ ((uint32_t)tmp[7] << (uint32_t)0);
+ if (!memcmp(tmp,"MThd",4)) {
+ unsigned short t,tdiv;
+
+ if (sz < 6 || sz > 255) {
+ fprintf(stderr,"Invalid MThd size %lu\n",(unsigned long)sz);
+ goto err;
+ }
+ if (read(fd,tmp,(int)sz) != (int)sz) {
+ fprintf(stderr,"MThd read error\n");
+ goto err;
+ }
+
+ /* byte 0-1 = format type (0,1 or 2) */
+ /* byte 2-3 = number of tracks */
+ /* byte 4-5 = time divison */
+ t = tmp[1] | (tmp[0] << 8);
+ if (t > 1) {
+ fprintf(stderr,"MThd type %u not supported\n",t);
+ goto err; /* we only take type 0 or 1, don't support 2 */
+ }
+ tracks = tmp[3] | (tmp[2] << 8);
+ if (tracks > MIDI_MAX_TRACKS) {
+ fprintf(stderr,"MThd too many (%u) tracks\n",tracks);
+ goto err;
+ }
+ tdiv = tmp[5] | (tmp[4] << 8);
+ if (tdiv & 0x8000) {
+ fprintf(stderr,"MThd SMPTE time division not supported\n");
+ goto err; /* we do not support the SMPTE form */
+ }
+ if (tdiv == 0) {
+ fprintf(stderr,"MThd time division == 0\n");
+ goto err;
+ }
+ ticks_per_quarter_note = tdiv;
+ }
+ else if (!memcmp(tmp,"MTrk",4)) {
+ if (sz == 0UL) continue;
+#if TARGET_MSDOS == 16 && (defined(__LARGE__) || defined(__COMPACT__))
+ if (sz > (640UL << 10UL)) goto err; /* 640KB */
+#elif TARGET_MSDOS == 32
+ if (sz > (1UL << 20UL)) goto err; /* 1MB */
+#else
+ if (sz > (60UL << 10UL)) goto err; /* 60KB */
+#endif
+ if (tracki >= MIDI_MAX_TRACKS) goto err;
+#if TARGET_MSDOS == 16 && (defined(__LARGE__) || defined(__COMPACT__))
+ {
+ unsigned segv;
+
+ /* NTS: _fmalloc() is still limited to 64KB sizes */
+ if (_dos_allocmem((unsigned)((sz+15UL)>>4UL),&segv) != 0) goto err;
+ midi_trk[tracki].raw = MK_FP(segv,0);
+ }
+#else
+ midi_trk[tracki].raw = malloc(sz);
+#endif
+ if (midi_trk[tracki].raw == NULL) goto err;
+ midi_trk[tracki].read = midi_trk[tracki].raw;
+#if TARGET_MSDOS == 16 && (defined(__LARGE__) || defined(__COMPACT__))
+ {
+ unsigned char far *p = midi_trk[tracki].raw;
+ unsigned long rem = (unsigned long)sz;
+ unsigned long cando;
+ unsigned read;
+
+ while (rem != 0UL) {
+ read = 0;
+
+ cando = 0x10000UL - (unsigned long)FP_OFF(p);
+ if (cando > rem) cando = rem;
+ if (cando > 0xFFFFUL) cando = 0xFFFFUL; /* we're limited to 64KB-1 of reading */
+
+ if (_dos_read(fd,p,(unsigned)cando,&read) != 0) goto err;
+ if (read != (unsigned)cando) goto err;
+
+ rem -= cando;
+ if ((((unsigned long)FP_OFF(p))+cando) == 0x10000UL)
+ p = MK_FP(FP_SEG(p)+0x1000,0);
+ else
+ p += (unsigned)cando;
+ }
+
+ cando = farptr2phys(p);
+ midi_trk[tracki].fence = MK_FP(cando>>4,cando&0xF);
+ }
+#else
+ midi_trk[tracki].fence = midi_trk[tracki].raw + (unsigned)sz;
+ if (read(fd,midi_trk[tracki].raw,(unsigned)sz) != (int)sz) goto err;
+#endif
+ tracki++;
+ }
+ else {
+ fprintf(stderr,"Unknown MIDI chunk %c%c%c%c\n",tmp[0],tmp[1],tmp[2],tmp[3]);
+ goto err;
+ }
+ }
+ if (tracki == 0 || ticks_per_quarter_note == 0) goto err;
+ midi_trk_count = tracki;
+
+ fprintf(stderr,"Ticks per quarter note: %u\n",ticks_per_quarter_note);
+
+ close(fd);
+ return 1;
+err:
+ close(fd);
+ return 0;
+}
+
+int main(int argc,char **argv) {
+ unsigned long ptick;
+ int i,c;
+
+ printf("ADLIB FM test program\n");
+ if (argc < 2) {
+ printf("You must specify a MIDI file to play\n");
+ return 1;
+ }
+
+ if (!probe_vga()) {
+ printf("Cannot init VGA\n");
+ return 1;
+ }
+ if (!init_adlib()) {
+ printf("Cannot init library\n");
+ return 1;
+ }
+ if (!probe_8254()) { /* we need the timer to keep time with the music */
+ printf("8254 timer not found\n");
+ return 1;
+ }
+
+ for (i=0;i < MIDI_MAX_TRACKS;i++) {
+ midi_trk[i].raw = NULL;
+ midi_trk[i].read = NULL;
+ midi_trk[i].fence = NULL;
+ }
+
+ if (load_midi_file(argv[1]) == 0) {
+ printf("Failed to load MIDI\n");
+ return 1;
+ }
+
+ write_8254_system_timer(T8254_REF_CLOCK_HZ / 100); /* tick faster at 100Hz please */
+ irq0_cnt = 0;
+ irq0_add = 182;
+ irq0_max = 1000; /* about 18.2Hz */
+ old_irq0 = _dos_getvect(8);/*IRQ0*/
+ _dos_setvect(8,irq0);
+
+ adlib_shut_up();
+ midi_reset_channels();
+ midi_reset_tracks();
+ _cli();
+ irq0_ticks = ptick = 0;
+ _sti();
+ midi_playing = 1;
+
+ while (1) {
+ unsigned long adv;
+
+ _cli();
+ adv = irq0_ticks - ptick;
+ if (adv >= 100UL) adv = 100UL;
+ ptick = irq0_ticks;
+ _sti();
+
+ while (adv != 0) {
+ midi_tick();
+ adv--;
+ }
+
+ if (kbhit()) {
+ c = getch();
+ if (c == 0) c = getch() << 8;
+
+ if (c == 27) {
+ break;
+ }
+ }
+ }
+
+ midi_playing = 0;
+ adlib_shut_up();
+ shutdown_adlib();
+ _dos_setvect(8,old_irq0);
+ write_8254_system_timer(0); /* back to normal 18.2Hz */
+
+ for (i=0;i < MIDI_MAX_TRACKS;i++) {
+ if (midi_trk[i].raw) {
+#if TARGET_MSDOS == 16 && (defined(__LARGE__) || defined(__COMPACT__))
+ _dos_freemem(FP_SEG(midi_trk[i].raw)); /* NTS: Because we allocated with _dos_allocmem */
+#else
+ free(midi_trk[i].raw);
+#endif
+ midi_trk[i].raw = NULL;
+ }
+ midi_trk[i].fence = NULL;
+ midi_trk[i].read = NULL;
+ }
+
+ return 0;
+}
--- /dev/null
+/* test.c
+ *
+ * Adlib OPL2/OPL3 FM synthesizer chipset test program.
+ * (C) 2010-2012 Jonathan Campbell.
+ * Hackipedia DOS library.
+ *
+ * This code is licensed under the LGPL.
+ * <insert LGPL legal text here>
+ *
+ * Compiles for intended target environments:
+ * - MS-DOS [pure DOS mode, or Windows or OS/2 DOS Box]
+ *
+ * This test program uses a "text user interface" to allow you to play
+ * with the OPL2/OPL3 chipset and it's parameters. Some "instruments"
+ * are preset for you if you want to make noise faster.
+ */
+
+#include <stdio.h>
+#include <conio.h> /* this is where Open Watcom hides the outp() etc. functions */
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <malloc.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <dos.h>
+
+#include <hw/vga/vga.h>
+#include <hw/dos/dos.h>
+#include <hw/8254/8254.h> /* 8254 timer */
+#include <hw/vga/vgagui.h>
+#include <hw/vga/vgatty.h>
+#include <hw/adlib/adlib.h>
+
+static unsigned int musical_scale[18] = {
+ 0x1B0, /* E */
+ 0x1CA, /* F */
+ 0x1E5, /* f# */
+ 0x202, /* G */
+ 0x220, /* G# */
+ 0x241, /* A */
+ 0x263, /* A# */
+ 0x287, /* B */
+ 0x2AE, /* C */
+
+ 0x2B0, /* E */
+ 0x2CA, /* F */
+ 0x2E5, /* f# */
+ 0x302, /* G */
+ 0x320, /* G# */
+ 0x341, /* A */
+ 0x363, /* A# */
+ 0x387, /* B */
+ 0x3AE, /* C */
+};
+
+int main(int argc,char **argv) {
+ int i,loop,redraw,c,cc,selector=0,redrawln=0,hselect=0,selectsub=0;
+ VGA_ALPHA_PTR vga;
+ char tmp[128];
+
+ printf("ADLIB FM test program\n");
+
+ if (!probe_vga()) {
+ printf("Cannot init VGA\n");
+ return 1;
+ }
+ if (!init_adlib()) {
+ printf("Cannot init library\n");
+ return 1;
+ }
+
+ int10_setmode(3);
+
+ /* for VGA: free up space if needed by going to 80x50 */
+ if (adlib_fm_voices > 9)
+ vga_bios_set_80x50_text();
+
+ memset(adlib_fm,0,sizeof(adlib_fm));
+ memset(&adlib_reg_bd,0,sizeof(adlib_reg_bd));
+ for (i=0;i < adlib_fm_voices;i++) {
+ struct adlib_fm_operator *f;
+ f = &adlib_fm[i].mod;
+ f->ch_a = f->ch_b = f->ch_c = f->ch_d = 1;
+ f = &adlib_fm[i].car;
+ f->ch_a = f->ch_b = f->ch_c = f->ch_d = 1;
+ }
+
+ for (i=0;i < adlib_fm_voices;i++) {
+ struct adlib_fm_operator *f;
+
+ f = &adlib_fm[i].mod;
+ f->mod_multiple = 1;
+ f->total_level = 63 - 16;
+ f->attack_rate = 15;
+ f->decay_rate = 0;
+ f->sustain_level = 7;
+ f->release_rate = 7;
+ f->f_number = musical_scale[i%18];
+ f->octave = 4;
+ f->key_on = 0;
+
+ f = &adlib_fm[i].car;
+ f->mod_multiple = 1;
+ f->total_level = 63 - 16;
+ f->attack_rate = 15;
+ f->decay_rate = 0;
+ f->sustain_level = 7;
+ f->release_rate = 7;
+ f->f_number = 0;
+ f->octave = 0;
+ f->key_on = 0;
+ }
+
+ adlib_apply_all();
+
+ vga_write_color(0x07);
+ vga_clear();
+
+ loop=1;
+ redraw=1;
+ while (loop) {
+ if (redraw || redrawln) {
+ if (redraw) {
+ for (vga=vga_alpha_ram,cc=0;cc < (80*vga_height);cc++) *vga++ = 0x1E00 | 177;
+ vga_moveto(0,0);
+ vga_write_color(0x1F);
+ sprintf(tmp,"Adlib FM, %u-voice %s. Use Z & X to adj F10=PRESET F1=QUIET ",adlib_fm_voices,
+ (adlib_flags & ADLIB_FM_OPL3) ? "OPL3" :
+ (adlib_flags & ADLIB_FM_DUAL_OPL2) ? "Dual OPL2" : "OPL2");
+ vga_write(tmp);
+ if (adlib_flags & ADLIB_FM_OPL3) vga_write("F2=OPL3 off ");
+ }
+
+ if (redrawln || redraw) {
+ struct adlib_reg_bd *bd = &adlib_reg_bd;
+ static const char *hsel_str[18] = {
+ "Amplitude modulatation",
+ "Vibrato",
+ "Sustain",
+ "Key scaling rate",
+ "Modulator frequency multiple",
+ "Level key scaling",
+ "Total level",
+ "Attack rate",
+ "Decay rate",
+ "Sustain level",
+ "Release rate",
+ "KEY ON",
+ "Octave",
+ "F-Number",
+ "Feedback",
+ "Connection (operator 1 -> operator 2)",
+ "Waveform",
+ "Channel mapping (OPL3)"
+ };
+
+ vga_write_color(0x1A);
+
+ vga_moveto(0,2);
+ sprintf(tmp,"AM=%u VB=%u RYTHM=%u BAS=%u SNA=%u TOM=%u CYM=%u HI=%u\n",
+ bd->am_depth,
+ bd->vibrato_depth,
+ bd->rythm_enable,
+ bd->bass_drum_on,
+ bd->snare_drum_on,
+ bd->tom_tom_on,
+ bd->cymbal_on,
+ bd->hi_hat_on);
+ vga_write(tmp);
+
+ vga_moveto(0,3);
+ vga_write(" ");
+ vga_moveto(0,3);
+ vga_write(hsel_str[hselect]);
+
+ vga_moveto(0,4);
+ vga_write_color(hselect == 0 ? 0x70 : 0x1E); vga_write("AM ");
+ vga_write_color(hselect == 1 ? 0x70 : 0x1E); vga_write("VB ");
+ vga_write_color(hselect == 2 ? 0x70 : 0x1E); vga_write("SUST ");
+ vga_write_color(hselect == 3 ? 0x70 : 0x1E); vga_write("KSR ");
+ vga_write_color(hselect == 4 ? 0x70 : 0x1E); vga_write("MMUL ");
+ vga_write_color(hselect == 5 ? 0x70 : 0x1E); vga_write("LKS ");
+ vga_write_color(hselect == 6 ? 0x70 : 0x1E); vga_write("TL ");
+ vga_write_color(hselect == 7 ? 0x70 : 0x1E); vga_write("AR ");
+ vga_write_color(hselect == 8 ? 0x70 : 0x1E); vga_write("DR ");
+ vga_write_color(hselect == 9 ? 0x70 : 0x1E); vga_write("SL ");
+ vga_write_color(hselect == 10 ? 0x70 : 0x1E); vga_write("RR ");
+ vga_write_color(hselect == 11 ? 0x70 : 0x1E); vga_write("KEY ");
+ vga_write_color(hselect == 12 ? 0x70 : 0x1E); vga_write("OCT ");
+ vga_write_color(hselect == 13 ? 0x70 : 0x1E); vga_write("FNUM ");
+ vga_write_color(hselect == 14 ? 0x70 : 0x1E); vga_write("FEED ");
+ vga_write_color(hselect == 15 ? 0x70 : 0x1E); vga_write("CON ");
+ vga_write_color(hselect == 16 ? 0x70 : 0x1E); vga_write("WV ");
+ vga_write_color(hselect == 17 ? 0x70 : 0x1E); vga_write("ABCD ");
+
+ for (i=0;i < adlib_fm_voices;i++) {
+ struct adlib_fm_operator *f;
+ double freq;
+
+ f = &adlib_fm[i].mod;
+ vga_moveto(0,5+i*2);
+ freq = adlib_fm_op_to_freq(f);
+ vga_write_color(i == selector && selectsub == 0 ? 0x70 : 0x1E);
+ cc = sprintf(tmp,"%u %u %u %u %-2u %u %-2u %-2u %-2u %-2u %-2u %u %u %-4u %u %u %u %c%c%c%c %u %.1fHz ",
+ f->am, f->vibrato, f->sustain, f->key_scaling_rate,
+ f->mod_multiple, f->level_key_scale, f->total_level, f->attack_rate,
+ f->decay_rate, f->sustain_level, f->release_rate, f->key_on,
+ f->octave, f->f_number, f->feedback, f->connection,
+ f->waveform, f->ch_a?'*':'-', f->ch_b?'*':'-', f->ch_c?'*':'-',
+ f->ch_d?'*':'-', i+1, freq);
+ vga_write(tmp);
+
+ f = &adlib_fm[i].car;
+ vga_moveto(0,5+i*2+1);
+ vga_write_color(i == selector && selectsub == 1 ? 0x70 : 0x1E);
+ cc = sprintf(tmp,"%u %u %u %u %-2u %u %-2u %-2u %-2u %-2u %-2u %u CAR ",
+ f->am, f->vibrato, f->sustain, f->key_scaling_rate,
+ f->mod_multiple, f->level_key_scale, f->total_level, f->attack_rate,
+ f->decay_rate, f->sustain_level, f->release_rate, f->waveform);
+ vga_write(tmp);
+ }
+ }
+
+ redrawln = 0;
+ redraw = 0;
+ }
+
+ if (kbhit()) {
+ c = getch();
+ if (c == 0) c = getch() << 8;
+
+ if (c == 27) {
+ loop = 0;
+ }
+ else if (c == 0x3B00) { /* F1 */
+ for (i=0;i < adlib_fm_voices;i++) {
+ adlib_fm[i].mod.key_on = 0;
+ adlib_fm[i].car.key_on = 0;
+ adlib_update_groupA0(i,&adlib_fm[i]);
+ }
+ redrawln = 1;
+ }
+ else if (c == 0x3C00) { /* F2 */
+ if (adlib_flags & ADLIB_FM_OPL3) {
+ shutdown_adlib_opl3();
+ int10_setmode(3);
+ redraw = 1;
+ }
+ }
+ else if (c == 0x4400) { /* F10 */
+ unsigned short op = adlib_voice_to_op[selector];
+
+ vga_write_color(0x07);
+ vga_clear();
+ vga_moveto(0,0);
+
+ vga_write("Choose an instrument to load into the channel:\n");
+ vga_write(" 1. Violin 2. Piano 3. Harpsichord 4. Horn 5. Deep bass drum\n");
+ vga_write(" 6. Small drum \n");
+ vga_write_sync();
+
+ c = getch();
+
+ if (c == '1')
+ adlib_fm[selector] =
+ (adlib_flags & ADLIB_FM_OPL3 ?
+ adlib_fm_preset_violin_opl3 : adlib_fm_preset_violin_opl2);
+ else if (c == '2')
+ adlib_fm[selector] = adlib_fm_preset_piano;
+ else if (c == '3')
+ adlib_fm[selector] = adlib_fm_preset_harpsichord;
+ else if (c == '4')
+ adlib_fm[selector] = adlib_fm_preset_horn;
+ else if (c == '5')
+ adlib_fm[selector] = adlib_fm_preset_deep_bass_drum;
+ else if (c == '6')
+ adlib_fm[selector] = adlib_fm_preset_small_drum;
+
+ adlib_update_groupA0(selector,&adlib_fm[selector]);
+ adlib_update_groupC0(selector,&adlib_fm[selector]);
+ adlib_update_operator(op,&adlib_fm[selector].mod);
+ adlib_update_operator(op+3,&adlib_fm[selector].car);
+
+ redraw = 1;
+ }
+ else if (c == ' ') {
+ adlib_fm[selector].mod.key_on ^= 1;
+ adlib_update_groupA0(selector,&adlib_fm[selector]);
+ redrawln=1;
+ }
+ else if (c == 'a') {
+ if (hselect == 17) {
+ struct adlib_fm_operator *f = &adlib_fm[selector].mod; f->ch_a ^= 1;
+ adlib_update_groupC0(selector,&adlib_fm[selector]);
+ }
+ else {
+ adlib_reg_bd.am_depth ^= 1;
+ adlib_update_bd(&adlib_reg_bd);
+ }
+ redrawln = 1;
+ }
+ else if (c == 'v') {
+ adlib_reg_bd.vibrato_depth ^= 1;
+ adlib_update_bd(&adlib_reg_bd);
+ redrawln = 1;
+ }
+ else if (c == 'r') {
+ adlib_reg_bd.rythm_enable ^= 1;
+ adlib_update_bd(&adlib_reg_bd);
+ redrawln = 1;
+ }
+ else if (c == 'b') {
+ if (hselect == 17) {
+ struct adlib_fm_operator *f = &adlib_fm[selector].mod; f->ch_b ^= 1;
+ adlib_update_groupC0(selector,&adlib_fm[selector]);
+ }
+ else {
+ adlib_reg_bd.bass_drum_on ^= 1;
+ adlib_update_bd(&adlib_reg_bd);
+ }
+ redrawln = 1;
+ }
+ else if (c == 's') {
+ adlib_reg_bd.snare_drum_on ^= 1;
+ adlib_update_bd(&adlib_reg_bd);
+ redrawln = 1;
+ }
+ else if (c == 't') {
+ adlib_reg_bd.tom_tom_on ^= 1;
+ adlib_update_bd(&adlib_reg_bd);
+ redrawln = 1;
+ }
+ else if (c == 'c') {
+ if (hselect == 17) {
+ struct adlib_fm_operator *f = &adlib_fm[selector].mod; f->ch_c ^= 1;
+ adlib_update_groupC0(selector,&adlib_fm[selector]);
+ }
+ else {
+ adlib_reg_bd.cymbal_on ^= 1;
+ adlib_update_bd(&adlib_reg_bd);
+ }
+ redrawln = 1;
+ }
+ else if (c == 'd') {
+ if (hselect == 17) {
+ struct adlib_fm_operator *f = &adlib_fm[selector].mod; f->ch_d ^= 1;
+ adlib_update_groupC0(selector,&adlib_fm[selector]);
+ }
+ else {
+ }
+ redrawln = 1;
+ }
+ else if (c == 'h') {
+ adlib_reg_bd.hi_hat_on ^= 1;
+ adlib_update_bd(&adlib_reg_bd);
+ redrawln = 1;
+ }
+ else if (c == 'z' || c == 'Z' || c == 'x' || c == 'X') {
+ struct adlib_fm_operator *f;
+ int dec = tolower(c) == 'z';
+ unsigned short operator;
+
+ switch (hselect) {
+ case 11:selectsub = 0;
+ break;
+ }
+
+ if (selectsub) f = &adlib_fm[selector].car;
+ else f = &adlib_fm[selector].mod;
+ operator = adlib_voice_to_op[selector] + (selectsub*3);
+
+ switch (hselect) {
+ case 0: f->am ^= 1; adlib_update_group20(operator,f); break;
+ case 11: f->key_on ^= 1; adlib_update_groupA0(selector,&adlib_fm[selector]); break;
+ case 1: f->vibrato ^= 1; adlib_update_group20(operator,f); break;
+ case 2: f->sustain ^= 1; adlib_update_group20(operator,f); break;
+ case 15: f->connection ^= 1; adlib_update_group20(operator,f); break;
+ case 3: f->key_scaling_rate ^= 1; adlib_update_group20(operator,f); break;
+
+ case 4: if (dec) f->mod_multiple--; else f->mod_multiple++;
+ adlib_update_group20(operator,f); break;
+ case 5: if (dec) f->level_key_scale--; else f->level_key_scale++;
+ adlib_update_group40(operator,f); break;
+ case 6: if (dec) f->total_level--; else f->total_level++;
+ adlib_update_group40(operator,f); break;
+ case 7: if (dec) f->attack_rate--; else f->attack_rate++;
+ adlib_update_group60(operator,f); break;
+ case 8: if (dec) f->decay_rate--; else f->decay_rate++;
+ adlib_update_group60(operator,f); break;
+ case 9: if (dec) f->sustain_level--; else f->sustain_level++;
+ adlib_update_group80(operator,f); break;
+ case 10: if (dec) f->release_rate--; else f->release_rate++;
+ adlib_update_group80(operator,f); break;
+ case 12: if (dec) f->octave--; else f->octave++;
+ adlib_update_groupA0(selector,&adlib_fm[selector]); break;
+ case 13: if (dec) f->f_number--; else f->f_number++;
+ adlib_update_groupA0(selector,&adlib_fm[selector]); break;
+ case 14: if (dec) f->feedback--; else f->feedback++;
+ adlib_update_groupC0(selector,&adlib_fm[selector]); break;
+ case 16: if (dec) f->waveform--; else f->waveform++;
+ adlib_update_groupE0(operator,f); break;
+ };
+
+ redrawln=1;
+ }
+ else if (c == 0x4800) {
+ if (selectsub && !(hselect >= 11 && hselect <= 15)) {
+ selectsub = 0;
+ redrawln = 1;
+ }
+ else if (selector > 0) {
+ selectsub = !(hselect >= 11 && hselect <= 15);
+ selector--;
+ redrawln = 1;
+ }
+ }
+ else if (c == 0x4B00) {
+ if (hselect > 0) {
+ hselect--;
+ redrawln=1;
+ }
+ }
+ else if (c == 0x4D00) {
+ if (hselect < 17) {
+ hselect++;
+ redrawln=1;
+ }
+ }
+ else if (c == 0x5000) {
+ if (selectsub == 0 && !(hselect >= 11 && hselect <= 15)) {
+ selectsub = 1;
+ redrawln = 1;
+ }
+ else if ((selector+1) < adlib_fm_voices) {
+ selectsub = 0;
+ selector++;
+ redrawln=1;
+ }
+ }
+
+ }
+ }
+
+ shutdown_adlib();
+ int10_setmode(3);
+
+ return 0;
+}
+