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
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
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
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
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
--- /dev/null
+/* 8254.c
+ *
+ * 8254 programmable interrupt timer control library.
+ * (C) 2008-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]
+ *
+ * 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 <stdio.h>
+#include <conio.h> /* this is where Open Watcom hides the outp() etc. functions */
+#include <stdlib.h>
+
+#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);
+}
+
--- /dev/null
+/* 8254.h
+ *
+ * 8254 programmable interrupt timer control library.
+ * (C) 2008-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] */
+
+#ifndef __HW_8254_8254_H
+#define __HW_8254_8254_H
+
+#include "src/lib/doslib/cpu.h"
+#include <stdint.h>
+
+/* 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 */
+
--- /dev/null
+/* 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.
+ * <insert LGPL legal text here>
+ *
+ * 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 <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 <fcntl.h>
+#include <dos.h>
+
+#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;
+}
+
--- /dev/null
+/* 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.
+ * <insert LGPL legal text here>
+ *
+ * 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 <stdint.h>
+
+#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));
+}
+
--- /dev/null
+/* cpu.c
+ *
+ * Runtime CPU detection library.
+ * (C) 2009-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
+ * - 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 <windows.h>
+# include <toolhelp.h>
+#endif
+
+#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 <assert.h>
+#include <fcntl.h>
+#include <dos.h>
+
+#include "src/lib/doslib/cpu.h"
+//#include <hw/dos/dos.h>
+
+/* 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 <hw/dos/winfcon.h>
+#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
+}
+
--- /dev/null
+/* cpu.h
+ *
+ * Runtime CPU detection library.
+ * (C) 2009-2012 Jonathan Campbell.
+ * Hackipedia DOS library.
+ *
+ * This code is licensed under the LGPL.
+ * <insert LGPL legal text here>
+ *
+ */
+
+#ifndef __HW_CPU_CPU_H
+#define __HW_CPU_CPU_H
+
+#include <stdint.h>
+#include <dos.h>
+
+#if !defined(FAR)
+# if defined(TARGET_WINDOWS)
+# include <windows.h>
+# 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 */
#include <stdio.h>
#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[])\r
{
- static FMInstrument testInst =\r
-{\r
-0x00, 0x01, /* modulator frequency multiple... 0x20 */\r
-0x00, 0x00, /* modulator frequency level... 0x40 */\r
-0xF0, 0xF0, /* modulator attack/decay... 0x60 */\r
-0x73, 0x73, /* modulator sustain/release... 0x80 */\r
-0x03, 0x00, /* output waveform distortion 0xE0 */\r
-0x36, /* feedback algorithm and strength 0xC0 */\r
-};
+ word i;
+// static FMInstrument testInst =\r
+//{\r
+//0x00, 0x01, /* modulator frequency multiple... 0x20 */\r
+//0x00, 0x00, /* modulator frequency level... 0x40 */\r
+//0xF0, 0xF0, /* modulator attack/decay... 0x60 */\r
+//0x73, 0x73, /* modulator sustain/release... 0x80 */\r
+//0x03, 0x00, /* output waveform distortion 0xE0 */\r
+//0x36, /* feedback algorithm and strength 0xC0 */\r
+//};
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");\r
- FMKeyOn(0, 0x106, 4);
+ printf("e");
+ adlib_fm[0].mod.key_on = 1;\r
+ //FMKeyOn(0, 0x106, 4);
}
else
{
- FMKeyOff(0);
- }\r
+ adlib_fm[0].mod.key_on = 0;
+ //FMKeyOff(0);
+ }
+ adlib_update_groupA0(0,&adlib_fm[0]);\r
}
printf("!\n");
IN_Shutdown();
-}
\ No newline at end of file
+}