From: sparky4 Date: Wed, 15 Jul 2015 20:42:23 +0000 (-0500) Subject: GPL library! for sound!! ^o^ X-Git-Url: http://4ch.mooo.com/gitweb/?a=commitdiff_plain;h=79b4ebcb955cb173a1391275ae00f8e62360b6d5;p=16.git GPL library! for sound!! ^o^ renamed: src/lib/16_snd.c -> 16/16_snd.c renamed: src/lib/16_snd.h -> 16/16_snd.h modified: makefile modified: sountest.exe new file: src/lib/doslib/8254.c new file: src/lib/doslib/8254.h new file: src/lib/doslib/adlib.c new file: src/lib/doslib/adlib.h new file: src/lib/doslib/cpu.c new file: src/lib/doslib/cpu.h modified: src/sountest.c --- diff --git a/src/lib/16_snd.c b/16/16_snd.c similarity index 100% rename from src/lib/16_snd.c rename to 16/16_snd.c diff --git a/src/lib/16_snd.h b/16/16_snd.h similarity index 100% rename from src/lib/16_snd.h rename to 16/16_snd.h diff --git a/makefile b/makefile index 5b671927..eb1bedab 100644 --- a/makefile +++ b/makefile @@ -20,9 +20,11 @@ SRC=src$(DIRSEP) SRCLIB=$(SRC)lib$(DIRSEP) JSMNLIB=$(SRCLIB)jsmn$(DIRSEP) EXMMLIB=$(SRCLIB)exmm$(DIRSEP) +DOSLIB=$(SRCLIB)doslib$(DIRSEP) WCPULIB=$(SRCLIB)wcpu$(DIRSEP) -16LIBOBJS = 16_in.$(OBJ) 16_mm.$(OBJ) wcpu.$(OBJ) 16_head.$(OBJ) scroll16.$(OBJ) 16_ca.$(OBJ) 16_snd.$(OBJ) +16LIBOBJS = 16_in.$(OBJ) 16_mm.$(OBJ) wcpu.$(OBJ) 16_head.$(OBJ) scroll16.$(OBJ) 16_ca.$(OBJ) adlib.$(OBJ) 8254.$(OBJ) cpu.$(OBJ) +#16_snd.$(OBJ) GFXLIBOBJS = modex16.$(OBJ) bitmap.$(OBJ) planar.$(OBJ) 16text.$(OBJ) all: 16.exe test.exe pcxtest.exe test2.exe palettec.exe maptest.exe fmemtest.exe fonttest.exe exmmtest.exe fonttes0.exe fontgfx.exe sountest.exe @@ -48,16 +50,16 @@ fonttest.exe: fonttest.$(OBJ) 16.lib wcl $(FLAGS) fonttest.$(OBJ) 16.lib fonttes0.exe: fonttes0.$(OBJ) 16.lib - wcl $(FLAGS) fonttes0.$(OBJ) 16.lib + wcl $(FLAGS) fonttes0.$(OBJ) 16.lib fontgfx.exe: fontgfx.$(OBJ) 16.lib - wcl $(FLAGS) fontgfx.$(OBJ) 16.lib + wcl $(FLAGS) fontgfx.$(OBJ) 16.lib inputest.exe: inputest.$(OBJ) 16.lib wcl $(FLAGS) inputest.$(OBJ) 16.lib sountest.exe: sountest.$(OBJ) 16.lib - wcl $(FLAGS) sountest.$(OBJ) 16.lib + wcl $(FLAGS) sountest.$(OBJ) 16.lib pcxtest.exe: pcxtest.$(OBJ) gfx.lib wcl $(FLAGS) pcxtest.$(OBJ) gfx.lib @@ -81,7 +83,7 @@ fmemtest.exe: fmemtest.$(OBJ) 16.lib wcl $(FLAGS) fmemtest.$(OBJ) 16.lib exmmtest.exe: exmmtest.$(OBJ) 16.lib - wcl $(FLAGS) exmmtest.$(OBJ) 16.lib + wcl $(FLAGS) exmmtest.$(OBJ) 16.lib # #executable's objects @@ -120,16 +122,16 @@ fonttest.$(OBJ): $(SRC)fonttest.c wcl $(FLAGS) -c $(SRC)fonttest.c fonttes0.$(OBJ): $(SRC)fonttes0.c - wcl $(FLAGS) -c $(SRC)fonttes0.c + wcl $(FLAGS) -c $(SRC)fonttes0.c fontgfx.$(OBJ): $(SRC)fontgfx.c - wcl $(FLAGS) -c $(SRC)fontgfx.c + wcl $(FLAGS) -c $(SRC)fontgfx.c inputest.$(OBJ): $(SRC)inputest.c wcl $(FLAGS) -c $(SRC)inputest.c sountest.$(OBJ): $(SRC)sountest.c - wcl $(FLAGS) -c $(SRC)sountest.c + wcl $(FLAGS) -c $(SRC)sountest.c exmmtest.$(OBJ): $(SRC)exmmtest.c wcl $(FLAGS) -c $(SRC)exmmtest.c @@ -179,8 +181,17 @@ mapread.$(OBJ): $(SRCLIB)mapread.h $(SRCLIB)mapread.c 16.lib 16_ca.$(OBJ): $(SRCLIB)16_ca.h $(SRCLIB)16_ca.c wcl $(FLAGS) -c $(SRCLIB)16_ca.c -16_snd.$(OBJ): $(SRCLIB)16_snd.h $(SRCLIB)16_snd.c - wcl $(FLAGS) -c $(SRCLIB)16_snd.c +#16_snd.$(OBJ): $(SRCLIB)16_snd.h $(SRCLIB)16_snd.c +# wcl $(FLAGS) -c $(SRCLIB)16_snd.c + +adlib.$(OBJ): $(DOSLIB)adlib.h $(DOSLIB)adlib.c + wcl $(FLAGS) -c $(DOSLIB)adlib.c + +8254.$(OBJ): $(DOSLIB)8254.h $(DOSLIB)8254.c + wcl $(FLAGS) -c $(DOSLIB)8254.c + +cpu.$(OBJ): $(DOSLIB)cpu.h $(DOSLIB)cpu.c + wcl $(FLAGS) -c $(DOSLIB)cpu.c 16_head.$(OBJ): $(SRCLIB)16_head.h $(SRCLIB)16_head.c wcl $(FLAGS) -c $(SRCLIB)16_head.c diff --git a/sountest.exe b/sountest.exe index dd72ba42..74c09fff 100644 Binary files a/sountest.exe and b/sountest.exe differ diff --git a/src/lib/doslib/8254.c b/src/lib/doslib/8254.c new file mode 100644 index 00000000..3d940b0b --- /dev/null +++ b/src/lib/doslib/8254.c @@ -0,0 +1,107 @@ +/* 8254.c + * + * 8254 programmable interrupt timer control library. + * (C) 2008-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] + * + * The 8254 Programmable Interrupt Timer is used on the PC platform for + * several purposes. 3 Timers are provided, which are used as: + * + * Timer 0 - System timer. This is tied to IRQ 0 and under normal operation + * provides the usual IRQ 0 18.2 ticks per second. DOS programs that + * need higher resolution reprogram this timer to get it. + * + * Timer 1 - Misc, varies. On older PC hardware this was tied to DRAM refresh. + * Modern hardware cycles it for whatever purpose. Don't assume it's function. + * + * Timer 2 - PC Speaker. This timer is configured to run as a square wave and it's + * output is gated through the 8042 before going directly to a speaker in the + * PC's computer case. When used, this timer allows your program to generate + * audible beeps. + * + * On modern hardware this chip is either integrated into the core motherboard chipset or + * emulated for backwards compatibility. + */ + +#include +#include /* this is where Open Watcom hides the outp() etc. functions */ +#include + +#include "src/lib/doslib/8254.h" + +uint32_t t8254_counter[3] = {0x10000,0x10000,0x10000}; +int8_t probed_8254_result = -1; + +int probe_8254() { + if (probed_8254_result >= 0) + return (int)probed_8254_result; + + /* NTS: Reading port 0x43 does nothing. Intel's datasheet even mentions this in one of the tables. + * Actual hardware experience tells me some motherboards DO return something but the correct + * response (as seen in emulators in DOSBox) is to ignore, returning 0xFF */ + { + /* read timer 0 and see if it comes back non-0xFF */ + /* NTS: We MUST use the read_8254 function to read it in order. The previous + * version of this code read it byte-wise. For some reason it seems, + * some emulators including DOSBox don't take that well and they fail + * to reset the MSB/LSB flip-flop. When that happens our timer counter + * readings come out byte-swapped and we cannot provide proper timing + * and sleep routines. Symptoms: A DOS program using our timing code + * would have proper timing only on every other run. */ + unsigned int patience = 128,cc; + unsigned short c; + do { + c = read_8254(0); + if (c == 0xFFFF) c = read_8254(0); + if (c != 0xFFFF) break; + for (cc=0;cc != 0xFFFFU;) cc++; /* delay */ + } while (patience-- > 0); + + if (c == 0xFF) + return (probed_8254_result=0); + } + + return (probed_8254_result=1); +} + +unsigned long t8254_us2ticks(unsigned long a) { + /* FIXME: can you write a version that doesn't require 64-bit integers? */ + uint64_t b; + if (a == 0) return 0; + b = (uint64_t)a * (uint64_t)T8254_REF_CLOCK_HZ; + return (unsigned long)(b / 1000000ULL); +} + +unsigned long t8254_us2ticksr(unsigned long a,unsigned long *rem) { + /* FIXME: can you write a version that doesn't require 64-bit integers? */ + uint64_t b; + if (a == 0) return 0; + b = (uint64_t)a * (uint64_t)T8254_REF_CLOCK_HZ; + *rem = (unsigned long)(b % 1000000ULL); + return (unsigned long)(b / 1000000ULL); +} + +void t8254_wait(unsigned long ticks) { + uint16_t dec; + t8254_time_t pr,cr; + if (ticks <= 1) return; + ticks--; + cr = read_8254(0); + do { + pr = cr; + cr = read_8254(0); + if (cr > pr) + dec = (pr + (uint16_t)t8254_counter[0] - cr); + else + dec = (pr - cr); + + ticks -= dec; + } while ((signed long)ticks >= 0L); +} + diff --git a/src/lib/doslib/8254.h b/src/lib/doslib/8254.h new file mode 100644 index 00000000..568f642d --- /dev/null +++ b/src/lib/doslib/8254.h @@ -0,0 +1,146 @@ +/* 8254.h + * + * 8254 programmable interrupt timer control library. + * (C) 2008-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] */ + +#ifndef __HW_8254_8254_H +#define __HW_8254_8254_H + +#include "src/lib/doslib/cpu.h" +#include + +/* WARNING: When calling these functions it is recommended that you disable interrupts + * during the programming procedure. In MS-DOS mode there is nothing to stop + * the BIOS from trying to use the 8254 chip at the same time you are. It is + * entirely possible that it might read values back during an interrupt. If + * you do not watch for that, you will get erroneous results from these + * routines. Use hw/cpu.h _cli() and _sti() functions. + * + * In contrast, it is extremely rare for interrupt handlers to mess with the + * PC speaker gate port (and even if they do, the worst that can happen is + * our code overrides what the IRQ is trying to do) */ + +#define PC_SPEAKER_GATE 0x61 + +/* 1.19318MHz from which the counter values divide down from */ +#define T8254_REF_CLOCK_HZ 1193180 + +#define T8254_IRQ 0 + +#define T8254_PORT(x) ((x) + 0x40) +#define T8254_TIMER_PORT(x) T8254_PORT(x) +#define T8254_CONTROL_PORT T8254_PORT(3) + +#define T8254_MODE_0_INT_ON_TERMINAL_COUNT 0 +#define T8254_MODE_1_HARDWARE_RETRIGGERABLE_ONE_SHOT 1 +#define T8254_MODE_2_RATE_GENERATOR 2 +#define T8254_MODE_3_SQUARE_WAVE_MODE 3 +#define T8254_MODE_4_SOFTWARE_TRIGGERED_STROBE 4 +#define T8254_MODE_5_HARDWARE_TRIGGERED_STROBE 5 + +#define T8254_READBACK_COUNT 0x20 +#define T8254_READBACK_STATUS 0x10 +#define T8254_READBACK_TIMER_2 0x08 +#define T8254_READBACK_TIMER_1 0x04 +#define T8254_READBACK_TIMER_0 0x02 +#define T8254_READBACK_ALL 0x3E + +/* this represents one counter value in the 8254/8253 chipset library, including the + * value the chip reloads on finishing countdown. + * NTS: In 8254 hardware, a value of 0 is treated as 65536 because the chip decrements + * THEN checks against zero. This allows the rate to drop as low as + * 1193180 / 65536 = 18.20648Hz on PC hardware */ +typedef uint16_t t8254_time_t; + +/* the 8254 (NOT 8253!) has a command that allows us to read the status and counter + * value of one or more latches (though contemporary hardware fails to emulate multi- + * counter latching from one command!). The status allows us to know what the counter + * was programmed as, the output of the counter at the time, and whether a new counter + * value was being loaded. The t8254_readback_t structure is used to read this info */ +struct t8254_readback_entry_t { + unsigned char status; + t8254_time_t count; +}; + +struct t8254_readback_t { + struct t8254_readback_entry_t timer[3]; +}; + +extern uint32_t t8254_counter[3]; +extern int8_t probed_8254_result; + +int probe_8254(); +void readback_8254(unsigned char what,struct t8254_readback_t *t); /* WARNING: 8254 only, will not work on 8253 chips in original PC/XT hardware */ +unsigned long t8254_us2ticks(unsigned long a); +unsigned long t8254_us2ticksr(unsigned long a,unsigned long *rem); +void t8254_wait(unsigned long ticks); + +static inline t8254_time_t read_8254_ncli(unsigned char timer) { + t8254_time_t x; + + if (timer > 2) return 0; + outp(T8254_CONTROL_PORT,(timer << 6) | (0 << 4) | 0); /* latch counter N, counter latch read */ + x = (t8254_time_t)inp(T8254_TIMER_PORT(timer)); + x |= (t8254_time_t)inp(T8254_TIMER_PORT(timer)) << 8U; + return x; +} + +static inline t8254_time_t read_8254(unsigned char timer) { + unsigned int flags; + t8254_time_t x; + + flags = get_cpu_flags(); + x = read_8254_ncli(timer); + _sti_if_flags(flags); + return x; +} + +/* NTS: At the hardware level, count == 0 is equivalent to programming 0x10000 into it. + * t8254_time_t is a 16-bit integer, and we write 16 bits, so 0 and 0x10000 is + * the same thing to us anyway */ +static inline void write_8254_ncli(unsigned char timer,t8254_time_t count,unsigned char mode) { + if (timer > 2) return; + outp(T8254_CONTROL_PORT,(timer << 6) | (3 << 4) | (mode << 1)); /* set new time */ + outp(T8254_TIMER_PORT(timer),count); + outp(T8254_TIMER_PORT(timer),count >> 8); + /* for our own timing code, keep track of what that count was. we can't read it back from H/W anyway */ + t8254_counter[timer] = (count == 0 ? 0x10000 : count); +} + +static inline void write_8254(unsigned char timer,t8254_time_t count,unsigned char mode) { + unsigned int flags; + + flags = get_cpu_flags(); + write_8254_ncli(timer,count,mode); + _sti_if_flags(flags); +} + +static inline unsigned char t8254_pc_speaker_read_gate() { + return inp(PC_SPEAKER_GATE) & 3; +} + +static inline void t8254_pc_speaker_set_gate(unsigned char m) { + unsigned char x; + + x = inp(PC_SPEAKER_GATE); + x = (x & ~0x3) | (m & 3); + outp(PC_SPEAKER_GATE,x); +} + +static inline void write_8254_system_timer(t8254_time_t max) { + write_8254(0,max,T8254_MODE_2_RATE_GENERATOR); +} + +static inline void write_8254_pc_speaker(t8254_time_t max) { + write_8254(2,max,T8254_MODE_3_SQUARE_WAVE_MODE); +} + +#endif /* __HW_8254_8254_H */ + diff --git a/src/lib/doslib/adlib.c b/src/lib/doslib/adlib.c new file mode 100644 index 00000000..dcb80eb9 --- /dev/null +++ b/src/lib/doslib/adlib.c @@ -0,0 +1,289 @@ +/* adlib.c + * + * Adlib OPL2/OPL3 FM synthesizer chipset controller library. + * (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] + * + * On most Sound Blaster compatible cards all the way up to the late 1990s, a + * Yamaha OPL2 or OPL3 chipset exists (or may be emulated on PCI cards) that + * responds to ports 388h-389h. Through these I/O ports you control the FM + * synthesizer engine. On some cards, a second OPL2 may exist at 38A-38Bh, + * and on ISA PnP cards, the OPL3 may be located at 38C-38Dh if software + * configured. */ +/* TODO: ISA PnP complementary library */ +/* TODO: Modifications to the library to support OPL2/OPL3 chipsets at I/O ports + * other than 388h */ + +#include +#include /* this is where Open Watcom hides the outp() etc. functions */ +#include +#include +#include +#include +#include +#include + +#include "src/lib/doslib/adlib.h" + +unsigned short adlib_voice_to_op_opl2[9] = {0x00,0x01,0x02, 0x08,0x09,0x0A, 0x10,0x11,0x12}; +/* NTS: There is a HOWTO out there stating that the registers line up 0,1,2,6,7,8,... == WRONG! */ +unsigned short adlib_voice_to_op_opl3[18] = {0x00,0x01,0x02, 0x08,0x09,0x0A, 0x10,0x11,0x12, 0x100,0x101,0x102, 0x108,0x109,0x10A, 0x110,0x111,0x112}; +unsigned short* adlib_voice_to_op = adlib_voice_to_op_opl2; + +struct adlib_reg_bd adlib_reg_bd; +struct adlib_fm_channel adlib_fm[ADLIB_FM_VOICES]; +int adlib_fm_voices = 0; +unsigned char adlib_flags = 0; + +struct adlib_fm_channel adlib_fm_preset_violin_opl3 = { + .mod = {0, 1, 1, 1, 1, 1, 42, 6, 1, 1, 4, 0, + 3, 456, 1, 1, 1, 1, 4, 0, 5}, + .car = {0, 1, 1, 1, 1, 1, 63, 4, 1, 1, 4, 0, + 3, 456, 1, 1, 1, 1, 0, 0, 2} +}; + +struct adlib_fm_channel adlib_fm_preset_violin_opl2 = { + .mod = {0, 1, 1, 1, 1, 1, 42, 6, 1, 1, 4, 0, + 3, 456, 1, 1, 1, 1, 2, 0, 1}, + .car = {0, 1, 1, 1, 1, 1, 63, 4, 1, 1, 4, 0, + 3, 456, 1, 1, 1, 1, 0, 0, 2} +}; + +struct adlib_fm_channel adlib_fm_preset_piano = { + .mod = {0, 0, 1, 1, 1, 1, 42, 10, 4, 2, 3, 0, + 4, 456, 1, 1, 1, 1, 4, 0, 0}, + .car = {0, 0, 1, 1, 1, 1, 63, 10, 1, 8, 3, 0, + 4, 456, 1, 1, 1, 1, 0, 0, 0} +}; + +struct adlib_fm_channel adlib_fm_preset_harpsichord = { + .mod = {0, 0, 1, 1, 1, 1, 42, 10, 3, 2, 3, 0, + 4, 456, 1, 1, 1, 1, 2, 0, 3}, + .car = {0, 0, 1, 1, 1, 1, 63, 10, 5, 3, 3, 0, + 4, 456, 1, 1, 1, 1, 0, 0, 3} +}; + +/* NTS: adjust the modulator total level value to vary between muted (27) and open (47) with + * further adjustment if you want to mimick the change in sound when you blow harder */ +struct adlib_fm_channel adlib_fm_preset_horn = { + .mod = {0, 0, 1, 0, 1, 0, 47, 6, 1, 1, 7, 0, + 4, 514, 1, 1, 1, 1, 0, 0, 0}, + .car = {0, 0, 1, 0, 1, 0, 47, 8, 2, 2, 7, 0, + 4, 456, 1, 1, 1, 1, 0, 0, 0} +}; + +struct adlib_fm_channel adlib_fm_preset_deep_bass_drum = { + .mod = {0, 0, 0, 0, 1, 0, 13, 7, 1, 0, 1, 0, + 2, 456, 1, 1, 1, 1, 7, 0, 1}, + .car = {0, 0, 1, 1, 1, 1, 63, 15, 2, 6, 1, 0, + 2, 456, 1, 1, 1, 1, 0, 0, 0} +}; + +/* NTS: You can simulate hitting software or harder by adjusting the modulator total volume + * as well as raising or lowering the frequency */ +struct adlib_fm_channel adlib_fm_preset_small_drum = { + .mod = {0, 0, 0, 1, 1, 1, 54, 15, 10, 15, 15, 0, + 3, 456, 1, 1, 1, 1, 1, 0, 0}, + .car = {0, 0, 1, 1, 1, 1, 63, 15, 7, 15, 15, 0, + 3, 456, 1, 1, 1, 1, 1, 0, 0} +}; + +unsigned char adlib_read(unsigned short i) { + unsigned char c; + outp(ADLIB_IO_INDEX+((i>>8)*2),(unsigned char)i); + adlib_wait(); + c = inp(ADLIB_IO_DATA+((i>>8)*2)); + adlib_wait(); + return c; +} + +void adlib_write(unsigned short i,unsigned char d) { + outp(ADLIB_IO_INDEX+((i>>8)*2),(unsigned char)i); + adlib_wait(); + outp(ADLIB_IO_DATA+((i>>8)*2),d); + adlib_wait(); +} + +/* TODO: adlib_write_imm_1() and adlib_write_imm_2() + * this would allow DOS programs to use this ADLIB library from within + * an interrupt routine */ + +int probe_adlib(unsigned char sec) { + unsigned char a,b,retry=3; + unsigned short bas = sec ? 0x100 : 0; + + /* this code uses the 8254 for timing */ + if (!probe_8254()) + return 1; + + do { + adlib_write(0x04+bas,0x60); /* reset both timers */ + adlib_write(0x04+bas,0x80); /* enable interrupts */ + a = adlib_status(sec); + adlib_write(0x02+bas,0xFF); /* timer 1 */ + adlib_write(0x04+bas,0x21); /* start timer 1 */ + t8254_wait(t8254_us2ticks(100)); + b = adlib_status(sec); + adlib_write(0x04+bas,0x60); /* reset both timers */ + adlib_write(0x04+bas,0x00); /* disable interrupts */ + + if ((a&0xE0) == 0x00 && (b&0xE0) == 0xC0) + return 1; + + } while (--retry != 0); + + return 0; +} + +int init_adlib() { + adlib_flags = 0; + if (!probe_adlib(0)) + return 0; + + adlib_write(0x01,0x20); /* enable waveform select */ + adlib_voice_to_op = adlib_voice_to_op_opl2; + adlib_fm_voices = 9; + + if (probe_adlib(1)) { + adlib_fm_voices = 18; + adlib_flags = ADLIB_FM_DUAL_OPL2; + } + else { + /* NTS: "unofficial" method of detecting OPL3 */ + if ((adlib_status(0) & 0x06) == 0) { + adlib_fm_voices = 18; + adlib_flags = ADLIB_FM_OPL3; + adlib_voice_to_op = adlib_voice_to_op_opl3; + + /* init like an OPL3 */ + adlib_write(0x105,0x01); /* set OPL3 bit */ + probe_adlib(0); + adlib_write(0x104,0x00); /* disable any 4op connections */ + } + } + + return 1; +} + +void shutdown_adlib_opl3() { + if (adlib_flags & ADLIB_FM_OPL3) { + adlib_write(0x105,0x00); /* clear OPL3 bit */ + probe_adlib(0); + adlib_fm_voices = 9; + adlib_voice_to_op = adlib_voice_to_op_opl2; + adlib_flags &= ~ADLIB_FM_OPL3; + } +} + +void shutdown_adlib() { + shutdown_adlib_opl3(); +} + +void adlib_update_group20(unsigned int op,struct adlib_fm_operator *f) { + adlib_write(0x20+op, (f->am << 7) | + (f->vibrato << 6) | + (f->sustain << 5) | + (f->key_scaling_rate << 4) | + (f->mod_multiple << 0)); +} + +void adlib_update_group40(unsigned int op,struct adlib_fm_operator *f) { + adlib_write(0x40+op, (f->level_key_scale << 6) | + ((f->total_level^63) << 0)); +} + +void adlib_update_group60(unsigned int op,struct adlib_fm_operator *f) { + adlib_write(0x60+op, (f->attack_rate << 4) | + (f->decay_rate << 0)); +} + +void adlib_update_group80(unsigned int op,struct adlib_fm_operator *f) { + adlib_write(0x80+op, (f->sustain_level << 4) | + (f->release_rate << 0)); +} + +void adlib_update_groupA0(unsigned int channel,struct adlib_fm_channel *ch) { + struct adlib_fm_operator *f = &ch->mod; + unsigned int x = (channel >= 9) ? 0x100 : 0; + adlib_write(0xA0+(channel%9)+x, f->f_number); + adlib_write(0xB0+(channel%9)+x, (f->key_on << 5) | + (f->octave << 2) | + (f->f_number >> 8)); +} + +void adlib_update_groupC0(unsigned int channel,struct adlib_fm_channel *ch) { + struct adlib_fm_operator *f = &ch->mod; + unsigned int x = (channel >= 9) ? 0x100 : 0; + adlib_write(0xC0+(channel%9)+x, (f->feedback << 1) | + (f->connection << 0) | + (f->ch_d << 7) | + (f->ch_c << 6) | + (f->ch_b << 5) | + (f->ch_a << 4)); +} + +void adlib_update_groupE0(unsigned int op,struct adlib_fm_operator *f) { + adlib_write(0xE0+op, (f->waveform << 0)); +} + +void adlib_update_operator(unsigned int op,struct adlib_fm_operator *f) { + adlib_update_group20(op,f); + adlib_update_group40(op,f); + adlib_update_group60(op,f); + adlib_update_group80(op,f); + adlib_update_groupE0(op,f); +} + +void adlib_update_bd(struct adlib_reg_bd *b) { + adlib_write(0xBD, (b->am_depth << 7) | + (b->vibrato_depth << 6) | + (b->rythm_enable << 5) | + (b->bass_drum_on << 4) | + (b->snare_drum_on << 3) | + (b->tom_tom_on << 2) | + (b->cymbal_on << 1) | + (b->hi_hat_on << 0)); +} + +void adlib_apply_all() { + struct adlib_fm_operator *f; + unsigned short op; + int ch; + + for (ch=0;ch < adlib_fm_voices;ch++) { + f = &adlib_fm[ch].mod; op = adlib_voice_to_op[ch]; adlib_update_operator(op,f); + f = &adlib_fm[ch].car; op = adlib_voice_to_op[ch]+3; adlib_update_operator(op,f); + adlib_update_groupA0(ch,&adlib_fm[ch]); + adlib_update_groupC0(ch,&adlib_fm[ch]); + } + adlib_update_bd(&adlib_reg_bd); +} + +double adlib_fm_op_to_freq(struct adlib_fm_operator *f) { + unsigned long t = (unsigned long)f->f_number * 49716UL; + return (double)t / (1UL << (20UL - (unsigned long)f->octave)); +} + +void adlib_freq_to_fm_op(struct adlib_fm_operator *f,double freq) { + unsigned long l; + + freq *= (1UL << (20UL - (unsigned long)f->octave)); + l = (unsigned long)freq / 49716UL; + f->octave = 4; + while (l > 1023UL) { + f->octave++; + l >>= 1UL; + } + while (l != 0UL && l < 256UL) { + f->octave--; + l <<= 1UL; + } + f->f_number = l; +} + diff --git a/src/lib/doslib/adlib.h b/src/lib/doslib/adlib.h new file mode 100644 index 00000000..d4d1641b --- /dev/null +++ b/src/lib/doslib/adlib.h @@ -0,0 +1,138 @@ +/* adlib.h + * + * Adlib OPL2/OPL3 FM synthesizer chipset controller library. + * (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 "src/lib/doslib/cpu.h" +#include "src/lib/doslib/8254.h" /* 8254 timer */ +#include + +#define ADLIB_FM_VOICES 18 + +#define ADLIB_IO_INDEX 0x388 +#define ADLIB_IO_STATUS 0x388 +#define ADLIB_IO_DATA 0x389 + +#define ADLIB_IO_INDEX2 0x38A +#define ADLIB_IO_STATUS2 0x38A +#define ADLIB_IO_DATA2 0x38B + +/* Adlib status */ +#define ADLIB_STATUS_TIMERS_EXPIRED 0x80 +#define ADLIB_STATUS_TIMER1_EXPIRED 0x40 +#define ADLIB_STATUS_TIMER2_EXPIRED 0x20 + +enum { + ADLIB_FM_DUAL_OPL2=0x01, + ADLIB_FM_OPL3=0x02 +}; + +struct adlib_fm_operator { + /* 0x20-0x3F */ + uint8_t am:1; /* bit 7: Apply amplitude modulation */ + uint8_t vibrato:1; /* bit 6: Apply vibrato */ + uint8_t sustain:1; /* bit 5: maintain sustain level */ + uint8_t key_scaling_rate:1; /* bit 4: increase ADSR enevelope speed as pitch increases */ + uint8_t mod_multiple:4; /* bits 0-3: modulator multiple (1=voice frequency, 2=one octave above) */ + /* 0x40-0x5F */ + uint8_t level_key_scale:2; /* bits 7-6: decrease volume as frequency rises (0=none 1=1.5dB/8ve 2=3dB/8ve 3=6dB/8ve) */ + uint8_t total_level:6; /* bits 5-0: total output level (for sanity's sake, we maintain here as 0=silent 0x3F=full even though hardware is opposite) */ + /* 0x60-0x7F */ + uint8_t attack_rate:4; /* bits 7-4: attack rate */ + uint8_t decay_rate:4; /* bits 3-0: decay rate */ + /* 0x80-0x9F */ + uint8_t sustain_level:4; /* bits 7-4: sustain level */ + uint8_t release_rate:4; /* bits 3-0: release rate */ + /* 0xA0-0xBF */ + uint16_t key_on:1; /* bit 5: voice the channel */ + uint16_t octave:3; /* bits 4-2: octave */ + uint16_t f_number:10; /* bits 1-0, then bits 7-0: F-number (frequency) */ + /* 0xC0-0xCF */ + uint8_t ch_a:1; /* bit 4: OPL3: Channel A output */ + uint8_t ch_b:1; /* bit 5: OPL3: Channel B output */ + uint8_t ch_c:1; /* bit 6: OPL3: Channel C output */ + uint8_t ch_d:1; /* bit 7: OPL3: Channel D output */ + uint8_t feedback:3; /* bits 3-1: feedback strength */ + uint8_t connection:1; /* bit 0: connection (operator 1 and 2 independent if set) */ + /* 0xE0-0xFF */ + uint8_t waveform:3; /* bits 1-0: which waveform to use */ +}; + +struct adlib_fm_channel { + struct adlib_fm_operator mod,car; +}; + +struct adlib_reg_bd { + uint8_t am_depth:1; + uint8_t vibrato_depth:1; + uint8_t rythm_enable:1; + uint8_t bass_drum_on:1; + uint8_t snare_drum_on:1; + uint8_t tom_tom_on:1; + uint8_t cymbal_on:1; + uint8_t hi_hat_on:1; +}; + +int init_adlib(); +void shutdown_adlib(); +void shutdown_adlib_opl3(); +int probe_adlib(unsigned char sec); +unsigned char adlib_read(unsigned short i); +void adlib_write(unsigned short i,unsigned char d); +void adlib_update_group20(unsigned int op,struct adlib_fm_operator *f); +void adlib_update_group40(unsigned int op,struct adlib_fm_operator *f); +void adlib_update_group60(unsigned int op,struct adlib_fm_operator *f); +void adlib_update_group80(unsigned int op,struct adlib_fm_operator *f); +void adlib_update_groupA0(unsigned int channel,struct adlib_fm_channel *ch); +void adlib_update_groupC0(unsigned int channel,struct adlib_fm_channel *ch); +void adlib_update_groupE0(unsigned int op,struct adlib_fm_operator *f); +void adlib_update_operator(unsigned int op,struct adlib_fm_operator *f); +void adlib_freq_to_fm_op(struct adlib_fm_operator *f,double freq); +double adlib_fm_op_to_freq(struct adlib_fm_operator *f); +void adlib_update_bd(struct adlib_reg_bd *b); +void adlib_apply_all(); + +extern unsigned short adlib_voice_to_op_opl2[9]; +extern unsigned short adlib_voice_to_op_opl3[18]; +extern unsigned short* adlib_voice_to_op; + +extern struct adlib_reg_bd adlib_reg_bd; +extern struct adlib_fm_channel adlib_fm[ADLIB_FM_VOICES]; +extern int adlib_fm_voices; +extern unsigned char adlib_flags; + +extern struct adlib_fm_channel adlib_fm_preset_deep_bass_drum; +extern struct adlib_fm_channel adlib_fm_preset_violin_opl3; +extern struct adlib_fm_channel adlib_fm_preset_violin_opl2; +extern struct adlib_fm_channel adlib_fm_preset_harpsichord; +extern struct adlib_fm_channel adlib_fm_preset_small_drum; +extern struct adlib_fm_channel adlib_fm_preset_piano; +extern struct adlib_fm_channel adlib_fm_preset_horn; + +/* NTS: I have a Creative CT1350B card where we really do have to wait at least + * 33us per I/O access, because the OPL2 chip on it really is that slow. + * + * Peior to this fix, the adlib code would often fail on a real CT1350B + * (unless run just after the Sound Blaster test program) and even if it + * did run, only about 1/3rd of the voices would work. Upping the delay + * to 40us for OPL3 and 100us for OPL2 resolved these issues. */ +static inline void adlib_wait() { + t8254_wait(t8254_us2ticks((adlib_flags & ADLIB_FM_OPL3) ? 40 : 100)); +} + +static inline unsigned char adlib_status(unsigned char which) { + adlib_wait(); + return inp(ADLIB_IO_STATUS+(which*2)); +} + +static inline unsigned char adlib_status_imm(unsigned char which) { + return inp(ADLIB_IO_STATUS+(which*2)); +} + diff --git a/src/lib/doslib/cpu.c b/src/lib/doslib/cpu.c new file mode 100644 index 00000000..9b7caf95 --- /dev/null +++ b/src/lib/doslib/cpu.c @@ -0,0 +1,157 @@ +/* cpu.c + * + * Runtime CPU detection library. + * (C) 2009-2012 Jonathan Campbell. + * Hackipedia DOS library. + * + * This code is licensed under the LGPL. + * + * + * Compiles for intended target environments: + * - MS-DOS + * - Windows 3.0/3.1/95/98/ME + * - Windows NT 3.1/3.51/4.0/2000/XP/Vista/7 + * - OS/2 16-bit + * - OS/2 32-bit + * + * A common library to autodetect the CPU at runtime. If the program calling us + * is interested, we can also provide Pentium CPUID and extended CPUID information. + * Also includes code to autodetect at runtime 1) if SSE is present and 2) if SSE + * is enabled by the OS and 3) if we can enable SSE. */ + +/* FIXME: The 16-bit real mode DOS builds of this program are unable to detect CPUID under OS/2 2.x and OS/2 Warp 3. Why? */ + +#if defined(TARGET_WINDOWS) && TARGET_MSDOS == 16 +/* Win16: We're probably on a 386, but we could be on a 286 if Windows 3.1 is in standard mode. + * If the user manages to run us under Windows 3.0, we could also run in 8086 real mode. + * We still do the tests so the Windows API cannot deceive us, but we still need GetWinFlags + * to tell between 8086 real mode + virtual8086 mode and protected mode. */ +# include +# include +#endif + +#include +#include /* this is where Open Watcom hides the outp() etc. functions */ +#include +#include +#include +#include +#include +#include + +#include "src/lib/doslib/cpu.h" +//#include + +/* DEBUG: Flush out calls that aren't there */ +#ifdef TARGET_OS2 +# define int86 ___EVIL___ +# define ntvdm_RegisterModule ___EVIL___ +# define ntvdm_UnregisterModule ___EVIL___ +# define _dos_getvect ___EVIL___ +# define _dos_setvect ___EVIL___ +#endif + +#if defined(TARGET_WINDOWS) && TARGET_MSDOS == 16 +# include +#endif + +char cpu_cpuid_vendor[13]={0}; +struct cpu_cpuid_features cpu_cpuid_features = {0}; +signed char cpu_basic_level = -1; +uint32_t cpu_cpuid_max = 0; +unsigned char cpu_flags = 0; +uint16_t cpu_tmp1 = 0; + +void cpu_probe() { +#if TARGET_MSDOS == 32 + /* we're obviously in 32-bit protected mode, or else this code would not be running at all */ + /* Applies to: 32-bit DOS, Win32, Win95/98/ME/NT/2000/XP/etc. */ + cpu_flags = CPU_FLAG_PROTECTED_MODE | CPU_FLAG_PROTECTED_MODE_32; +#else + cpu_flags = 0; +#endif + + cpu_basic_level = cpu_basic_probe(); + +#if defined(TARGET_OS2) + /* OS/2 wouldn't let a program like myself touch control registers. Are you crazy?!? */ + cpu_flags |= CPU_FLAG_DONT_WRITE_RDTSC; +#elif defined(TARGET_WINDOWS) && TARGET_MSDOS == 32 + /* Under Windows 3.1 Win32s and Win 9x/ME/NT it's pretty much a given any attempt to work with + * control registers will fail. Win 9x/ME will silently ignore, and NT will fault it */ + cpu_flags |= CPU_FLAG_DONT_WRITE_RDTSC; +#elif !defined(TARGET_WINDOWS) && TARGET_MSDOS == 32 + /* 32-bit DOS: Generally yes we can, but only if we're Ring 0 */ + { + unsigned int s=0; + + __asm { + xor eax,eax + mov ax,cs + and ax,3 + mov s,eax + } + + if (s != 0) cpu_flags |= CPU_FLAG_DONT_WRITE_RDTSC; + } +#endif + +#if defined(TARGET_WINDOWS) && TARGET_MSDOS == 16 + /* Windows 3.0/3.1 specific: are we in 32-bit protected mode? 16-bit protected mode? real mode? + * real mode with v86 mode (does Windows even work that way?). Note that GetWinFlags only appeared + * in Windows 3.0. If we're under Windows 2.x we have to use alternative detection methods, or + * else assume Real Mode since Windows 2.x usually does run that way. But: There are 286 and 386 + * enhanced versions of Windows 2.x, so how do we detect those? + * + * NTS: This code doesn't even run under Windows 2.x. If we patch the binary to report itself as + * a 2.x compatible and try to run it under Windows 2.11, the Windows kernel says it's + * "out of memory" and then everything freezes (?!?). */ + { +# if TARGET_WINDOWS >= 30 /* If targeting Windows 3.0 or higher at compile time, then assume GetWinFlags() exists */ + DWORD flags = GetWinFlags(); + if (1) { +# elif TARGET_WINDOWS >= 20 /* If targeting Windows 2.0 or higher, then check the system first in case we're run under 3.0 or higher */ + /* FIXME: If locating the function by name fails, what ordinal do we search by? */ + DWORD (PASCAL FAR *__GetWinFlags)() = (LPVOID)GetProcAddress(GetModuleHandle("KERNEL"),"GETWINFLAGS"); + if (__GetWinFlags != NULL) { + DWORD flags = __GetWinFlags(); + MessageBox(NULL,"Found it","",MB_OK); +# else /* don't try. Windows 1.0 does not have GetWinFlags() and does not have any dynamic library functions either. There is + no GetModuleHandle, GetProcAddress, etc. */ + if (0) { +# endif + if (WF_PMODE) { + cpu_flags |= CPU_FLAG_DONT_WRITE_RDTSC; + if (flags & WF_ENHANCED) + cpu_flags |= CPU_FLAG_PROTECTED_MODE | CPU_FLAG_PROTECTED_MODE_32; + else if (flags & WF_STANDARD) + cpu_flags |= CPU_FLAG_PROTECTED_MODE; + } + /* I highly doubt Windows 3.0 "real mode" every involves virtual 8086 mode, but + * just in case, check the machine status register. Since Windows 3.0 could run + * on an 8086, we must be cautious to do this test only on a 286 or higher */ + else if (cpu_basic_level >= 2) { + unsigned int tmp=0; + + __asm { + .286 + smsw tmp + } + + if (tmp & 1) { + /* if the PE bit is set, we're under Protected Mode + * that must mean that all of windows is in Real Mode, but overall + * the whole show is in virtual 8086 mode. + * We're assuming here that Windows would not lie to us about what mode is active. + * + * THEORY: Could this happen if Windows 3.0 were started in Real Mode + * while EMM386.EXE is resident and active? */ + cpu_flags |= CPU_FLAG_V86_ACTIVE; + cpu_flags |= CPU_FLAG_DONT_WRITE_RDTSC; + } + } + } + } +#endif +} + diff --git a/src/lib/doslib/cpu.h b/src/lib/doslib/cpu.h new file mode 100644 index 00000000..f84974bb --- /dev/null +++ b/src/lib/doslib/cpu.h @@ -0,0 +1,233 @@ +/* cpu.h + * + * Runtime CPU detection library. + * (C) 2009-2012 Jonathan Campbell. + * Hackipedia DOS library. + * + * This code is licensed under the LGPL. + * + * + */ + +#ifndef __HW_CPU_CPU_H +#define __HW_CPU_CPU_H + +#include +#include + +#if !defined(FAR) +# if defined(TARGET_WINDOWS) +# include +# else +# if TARGET_MSDOS == 32 +# define FAR +# else +# define FAR far +# endif +# endif +#endif + +/* FIX: Open Watcom does not provide inpd/outpd in 16-bit real mode, so we have to provide it ourself */ +/* We assume for the same stupid reasons the pragma aux function can't be used because it's a 386 level instruction */ +#if TARGET_MSDOS == 16 +uint32_t __cdecl inpd(uint16_t port); +void __cdecl outpd(uint16_t port,uint32_t data); +#endif + +#if TARGET_MSDOS == 16 +static inline uint32_t ptr2phys(void far *p) { + return (((uint32_t)FP_SEG(p)) << 4UL) + + ((uint32_t)FP_OFF(p)); +} +#endif + +#pragma pack(push,1) +struct cpu_cpuid_features { + union { + uint32_t raw[4]; /* EAX, EBX, EDX, ECX */ + struct { + uint32_t todo[4]; + } f; + } a; +}; + +struct cpu_cpuid_ext_features { + union { + uint32_t raw[4]; /* EAX, EBX, ECX, EDX */ + struct { + uint32_t todo[4]; + } f; + } a; +}; + +struct cpu_cpuid_ext_cache_tlb { + union { + uint32_t raw[4]; /* EAX, EBX, ECX, EDX */ + struct { + uint32_t todo[4]; + } f; + } a; +}; + +struct cpu_cpuid_ext_cache_tlb_l2 { + union { + uint32_t raw[4]; /* EAX, EBX, ECX, EDX */ + struct { + uint32_t todo[4]; + } f; + } a; +}; + +struct cpu_cpuid_ext_longmode { + union { + uint32_t raw[4]; /* EAX, EBX, ECX, EDX */ + struct { + uint32_t todo[4]; + } f; + } a; +}; + +struct cpu_cpuid_ext_apm { + union { + uint32_t raw[1]; /* EAX */ + struct { + uint32_t todo[1]; + } f; + } a; +}; + +struct cpuid_result { + uint32_t eax,ebx,ecx,edx; +}; +#pragma pack(pop) + +/* "Basic" CPU level */ +enum { + CPU_8086=0, /* 0 */ + CPU_186, + CPU_286, + CPU_386, + CPU_486, + CPU_586, + CPU_686, + CPU_MAX +}; + +extern const char * cpu_basic_level_str[CPU_MAX]; +extern char cpu_cpuid_vendor[13]; +extern struct cpu_cpuid_features cpu_cpuid_features; +extern signed char cpu_basic_level; +extern uint32_t cpu_cpuid_max; +extern unsigned char cpu_flags; +extern uint16_t cpu_tmp1; + +/* compatability */ +#define cpu_v86_active (cpu_flags & CPU_FLAG_V86_ACTIVE) + +#define cpu_basic_level_to_string(x) (x >= 0 ? cpu_basic_level_str[x] : "?") + +/* CPU flag: CPU supports CPUID */ +#define CPU_FLAG_CPUID (1 << 0) +#define CPU_FLAG_FPU (1 << 1) +#define CPU_FLAG_CPUID_EXT (1 << 2) +#define CPU_FLAG_V86_ACTIVE (1 << 3) +#define CPU_FLAG_PROTECTED_MODE (1 << 4) +#define CPU_FLAG_PROTECTED_MODE_32 (1 << 5) +/* ^ Windows-specific: we are not only a 16-bit Win16 app, but Windows is running in 386 enhanced mode + * and we can safely use 32-bit registers and hacks. This will always be set for + * Win32 and 32-bit DOS, obviously. If set, PROTECTED_MODE is also set. */ +#define CPU_FLAG_DONT_WRITE_RDTSC (1 << 6) + +void cpu_probe(); +int cpu_basic_probe(); /* external assembly language function */ + +static void _cli(); +#pragma aux _cli = "cli" +static void _sti(); +#pragma aux _sti = "sti" + +static inline void _sti_if_flags(unsigned int f) { + if (f&0x200) _sti(); /* if IF bit was set, then restore interrupts by STI */ +} + +/* NTS: remember for Watcom: 16-bit realmode sizeof(int) == 2, 32-bit flat mode sizeof(int) == 4 */ +static unsigned int get_cpu_flags(); +#if TARGET_MSDOS == 32 +#pragma aux get_cpu_flags = \ + "pushfd" \ + "pop eax" \ + value [eax]; +#else +#pragma aux get_cpu_flags = \ + "pushf" \ + "pop ax" \ + value [ax]; +#endif + +static void set_cpu_flags(unsigned int f); +#if TARGET_MSDOS == 32 +#pragma aux set_cpu_flags = \ + "push eax" \ + "popfd " \ + parm [eax]; +#else +#pragma aux set_cpu_flags = \ + "push ax" \ + "popf " \ + parm [ax]; +#endif + +#if defined(TARGET_WINDOWS) && TARGET_MSDOS == 32 +/* Watcom does not offer int86/int386 for Win32s/Win9x/NT/etc */ +#else +static inline void just_int86(unsigned char c,union REGS *r1,union REGS *r2) { +# ifdef __386__ + int386(c,r1,r2); +# else + int86(c,r1,r2); +# endif +} +#endif + +#if TARGET_MSDOS == 32 +static inline void cpu_cpuid(uint32_t idx,struct cpuid_result *x); +#pragma aux cpu_cpuid = \ + ".586p" \ + "xor ebx,ebx" \ + "mov ecx,ebx" \ + "mov edx,ebx" \ + "cpuid" \ + "mov [esi],eax" \ + "mov [esi+4],ebx" \ + "mov [esi+8],ecx" \ + "mov [esi+12],edx" \ + parm [eax] [esi] \ + modify [ebx ecx edx] +#else +void cpu_cpuid(uint32_t idx,struct cpuid_result *x); +#endif + +#if TARGET_MSDOS == 32 +static inline uint64_t cpu_rdmsr(const uint32_t idx); +#pragma aux cpu_rdmsr = \ + ".586p" \ + "rdmsr" \ + parm [ecx] \ + value [edx eax] + +static inline void cpu_wrmsr(const uint32_t idx,const uint64_t val); +#pragma aux cpu_wrmsr = \ + ".586p" \ + "wrmsr" \ + parm [ecx] [edx eax] +#else +/* This is too much code to inline insert everywhere---unless you want extra-large EXEs. + * It's better to conform to Watcom's register calling convention and make it a function. + * Note that if you look at the assembly language most of the code is shuffling the values + * around to convert EDX:EAX to AX:BX:CX:DX and disabling interrupts during the call. */ +/* see CPUASM.ASM */ +uint64_t cpu_rdmsr(const uint32_t idx); +void cpu_wrmsr(const uint32_t idx,const uint64_t val); +#endif + +#endif /* __HW_CPU_CPU_H */ diff --git a/src/sountest.c b/src/sountest.c index 76ffeaec..e8574425 100644 --- a/src/sountest.c +++ b/src/sountest.c @@ -23,37 +23,113 @@ #include #include "src/lib/16_in.h" -#include "src/lib/16_snd.h" +//#include "src/lib/16_snd.h" +#include "src/lib/doslib/adlib.h" +#include "src/lib/doslib/8254.h" /* 8254 timer */ + +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 */ +}; void main(int argc, char near *argv[]) { - static FMInstrument testInst = -{ -0x00, 0x01, /* modulator frequency multiple... 0x20 */ -0x00, 0x00, /* modulator frequency level... 0x40 */ -0xF0, 0xF0, /* modulator attack/decay... 0x60 */ -0x73, 0x73, /* modulator sustain/release... 0x80 */ -0x03, 0x00, /* output waveform distortion 0xE0 */ -0x36, /* feedback algorithm and strength 0xC0 */ -}; + word i; +// static FMInstrument testInst = +//{ +//0x00, 0x01, /* modulator frequency multiple... 0x20 */ +//0x00, 0x00, /* modulator frequency level... 0x40 */ +//0xF0, 0xF0, /* modulator attack/decay... 0x60 */ +//0x73, 0x73, /* modulator sustain/release... 0x80 */ +//0x03, 0x00, /* output waveform distortion 0xE0 */ +//0x36, /* feedback algorithm and strength 0xC0 */ +//}; IN_Startup(); - FMReset(); - FMSetVoice(0, &testInst); + //FMReset(); + //FMSetVoice(0, &testInst); + if(!init_adlib()) + { + printf("Cannot init library\n"); + exit(-5); + } + + if (adlib_fm_voices > 9) + printf("OPL3!\n"); +// 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*/1;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(); + printf("press Z! to noise\npress ESC to quit"); printf("p"); while(!IN_qb(1)) { if(IN_qb(44)) { - printf("e"); - FMKeyOn(0, 0x106, 4); + printf("e"); + adlib_fm[0].mod.key_on = 1; + //FMKeyOn(0, 0x106, 4); } else { - FMKeyOff(0); - } + adlib_fm[0].mod.key_on = 0; + //FMKeyOff(0); + } + adlib_update_groupA0(0,&adlib_fm[0]); } printf("!\n"); IN_Shutdown(); -} \ No newline at end of file +}