From: sparky4 Date: Wed, 15 Jul 2015 21:06:49 +0000 (-0500) Subject: added some files~ X-Git-Url: http://4ch.mooo.com/gitweb/?a=commitdiff_plain;h=c4337307969449556e1c8841d763bed971c30f7c;p=16.git added some files~ modified: sountest.exe new file: src/midi.c modified: src/sountest.c new file: src/testsnd.c --- diff --git a/sountest.exe b/sountest.exe index 74c09fff..7429f02c 100644 Binary files a/sountest.exe and b/sountest.exe differ diff --git a/src/midi.c b/src/midi.c new file mode 100644 index 00000000..e36a8532 --- /dev/null +++ b/src/midi.c @@ -0,0 +1,870 @@ +/* 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. + * + * + * Compiles for intended target environments: + * - MS-DOS [pure DOS mode, or Windows or OS/2 DOS Box] + */ + +#include +#include /* this is where Open Watcom hides the outp() etc. functions */ +#include +#include +#include +#include +#include +#include +#include +#include + +#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; +} diff --git a/src/sountest.c b/src/sountest.c index e8574425..8fd5b025 100644 --- a/src/sountest.c +++ b/src/sountest.c @@ -131,5 +131,6 @@ void main(int argc, char near *argv[]) adlib_update_groupA0(0,&adlib_fm[0]); } printf("!\n"); + shutdown_adlib(); IN_Shutdown(); } diff --git a/src/testsnd.c b/src/testsnd.c new file mode 100644 index 00000000..4c5f6534 --- /dev/null +++ b/src/testsnd.c @@ -0,0 +1,450 @@ +/* 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. + * + * + * 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 +#include /* this is where Open Watcom hides the outp() etc. functions */ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include /* 8254 timer */ +#include +#include +#include + +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; +} +