• R/O
  • SSH
  • HTTPS

mdr: Commit


Commit MetaInfo

Revision100 (tree)
Time2023-10-13 05:52:13
Authormateuszviste

Log Message

added mdr_opl_imf_init() and mdr_opl_imf_playback()

Change Summary

Incremental Difference

--- trunk/inc/mdr/opl.h (revision 99)
+++ trunk/inc/mdr/opl.h (revision 100)
@@ -76,8 +76,8 @@
7676 MDR_OPL_FGROUP7 = 7 << 2
7777 };
7878
79-/* Hardware detection and initialization.
80- * Returns 0 on success, non-zero otherwise. */
79+/* Hardware detection and initialization. Must be called before any other
80+ * OPL function. Returns 0 on success, non-zero otherwise. */
8181 int mdr_opl_init(void);
8282
8383 /* close OPL device */
@@ -110,4 +110,30 @@
110110 /* adjusts volume of a voice. volume goes from 63 (mute) to 0 (loudest) */
111111 void mdr_opl_voicevolume(unsigned char voice, unsigned char volume);
112112
113+/* this is a LOW-LEVEL function that writes a data byte into the reg register
114+ * of the OPL chip. Use this only if you know exactly what you are doing. */
115+void mdr_opl_regwr(unsigned char reg, unsigned char data);
116+
117+
118+/*****************************************************************************
119+ * IMF PLAYBACK *
120+ * *
121+ * It is possible to mix IMF playback calls with manual notes, but you must *
122+ * take care to use only voices not used by your IMF audio. Typically games *
123+ * tend to use the voice #0 for sound effects and voices #1 to #8 for music. *
124+ ****************************************************************************/
125+
126+/* playback initialization, this function must be called immediately before
127+ * playback. imf points to the start of the IMF file and must contain at least
128+ * the first 6 bytes of the audio file.
129+ * clock must be an incrementing value that wraps to 0 after 65535.
130+ * the clock speed will control the playback's tempo.
131+ * returns the amount of consumed bytes (4 or 6) */
132+unsigned short mdr_opl_imf_init(void *imf, unsigned short ticks);
133+
134+/* feeds the IMF playback routine with IMF data. returns the amount of bytes
135+ * that have been consumed. this function must be called repeatedly at a high
136+ * frequency for best playback quality. */
137+unsigned short mdr_opl_imf_play(void *imf, unsigned short imflen, unsigned short ticks);
138+
113139 #endif
--- trunk/opl/opl-imf.c (nonexistent)
+++ trunk/opl/opl-imf.c (revision 100)
@@ -0,0 +1,100 @@
1+/*
2+ * IMF reader/player
3+ *
4+ * This file is part of the Mateusz' DOS Routines (MDR): http://mdr.osdn.io
5+ * Published under the terms of the MIT License, as stated below.
6+ *
7+ * Copyright (C) 2023 Mateusz Viste
8+ *
9+ * Permission is hereby granted, free of charge, to any person obtaining a copy
10+ * of this software and associated documentation files (the "Software"), to
11+ * deal in the Software without restriction, including without limitation the
12+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
13+ * sell copies of the Software, and to permit persons to whom the Software is
14+ * furnished to do so, subject to the following conditions:
15+ *
16+ * The above copyright notice and this permission notice shall be included in
17+ * all copies or substantial portions of the Software.
18+ *
19+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
24+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
25+ * IN THE SOFTWARE.
26+ */
27+
28+#include <mdr/bios.h> /* mdr_bios_ticks() */
29+#include <mdr/opl.h>
30+
31+/* the IMF file format is a raw stream of OPL register-value-timing tuples.
32+ * each tuple is 4 bytes long and consists of:
33+ *
34+ * unsigned char ; ADLIB REGISTER
35+ * unsigned char ; ADLIB VALUE
36+ * unsigned short ; DELAY
37+ *
38+ * The DELAY is provided as a number of "ticks", while the length of a tick
39+ * is undefined and must be known in advance by the IMF-playing implementation.
40+ * Commonly found values in games are 560 and 700 Hz.
41+ */
42+
43+
44+struct IMFCHUNK {
45+ unsigned char adreg;
46+ unsigned char adval;
47+ unsigned short delay;
48+};
49+
50+static unsigned short next_wakeup = 0;
51+
52+
53+/* playback initialization, this function must be called immediately before
54+ * playback. imf points to the start of the IMF file and must contain at least
55+ * the first 6 bytes of the audio file.
56+ * clock must be an incrementing value that wraps to 0 after 65535.
57+ * the clock speed will control the playback's tempo.
58+ * returns the amount of consumed bytes (0, 4 or 6) */
59+unsigned short mdr_opl_imf_init(void *imf, unsigned short clock) {
60+ next_wakeup = clock;
61+ if (((short *)imf)[0] == 0) { /* type-0 IMF (no header, only the 4-bytes zeroed 1st instruction) */
62+ return(4);
63+ } else if (((short *)imf)[1] == 0) { /* type-1 (2-bytes header) */
64+ return(6);
65+ } else {
66+ return(0);
67+ }
68+}
69+
70+
71+/* feeds the IMF playback routine with IMF data. returns the amount of bytes
72+ * that have been consumed. this function must be called repeatedly at a high
73+ * frequency for best playback quality. */
74+unsigned short mdr_opl_imf_play(void *imf, unsigned short imflen, unsigned short clock) {
75+ unsigned short consumedbytes = 0;
76+ struct IMFCHUNK *imfnode = imf;
77+
78+ while (imflen >= 4) {
79+
80+ /* it's not time yet (unless timer wraped) */
81+ if (clock < next_wakeup) {
82+ /* detect time wrap */
83+ if (next_wakeup - clock < 16384) return(consumedbytes);
84+ } else if (clock - next_wakeup > 16384) {
85+ return(consumedbytes);
86+ }
87+
88+ /* ignore writes to reg 0: such register does not exist on OPL2, it is
89+ * likely some kind of marker in the IMF file */
90+ if (imfnode->adreg != 0) mdr_opl_regwr(imfnode->adreg, imfnode->adval);
91+
92+ next_wakeup += imfnode->delay;
93+
94+ imfnode++;
95+ consumedbytes += 4;
96+ imflen -= 4;
97+ }
98+
99+ return(consumedbytes);
100+}
--- trunk/opl/opl.c (revision 99)
+++ trunk/opl/opl.c (revision 100)
@@ -47,9 +47,9 @@
4747 static unsigned char carr40_cache[9];
4848
4949
50-/* function used to write into a register 'reg' of the OPL chip, writing byte
51- * 'data' into it. */
52-static void oplregwr(unsigned char reg, unsigned char data) {
50+/* this is a LOW-LEVEL function that writes a data byte into the reg register
51+ * of the OPL chip. Use only if you know exactly what you are doing. */
52+void mdr_opl_regwr(unsigned char reg, unsigned char data) {
5353 int i;
5454 /* select the register we want to write to, via the index register */
5555 outp(PORT_CTRL, reg);
@@ -72,17 +72,17 @@
7272 int x, y;
7373
7474 /* detect the hardware and return error if not found */
75- oplregwr(0x04, 0x60); /* reset both timers by writing 60h to register 4 */
76- oplregwr(0x04, 0x80); /* enable interrupts by writing 80h to register 4 (must be a separate write from the 1st one) */
75+ mdr_opl_regwr(0x04, 0x60); /* reset both timers by writing 60h to register 4 */
76+ mdr_opl_regwr(0x04, 0x80); /* enable interrupts by writing 80h to register 4 (must be a separate write from the 1st one) */
7777 x = inp(PORT_CTRL) & 0xE0; /* read the status register (port 388h) and store the result */
78- oplregwr(0x02, 0xff); /* write FFh to register 2 (Timer 1) */
79- oplregwr(0x04, 0x21); /* start timer 1 by writing 21h to register 4 */
78+ mdr_opl_regwr(0x02, 0xff); /* write FFh to register 2 (Timer 1) */
79+ mdr_opl_regwr(0x04, 0x21); /* start timer 1 by writing 21h to register 4 */
8080 mdr_bios_tickswait(1); /* Creative Labs recommends a delay of at least 80 microseconds
8181 DO NOT perform inp() calls for delay here, some cards do not
8282 initialize well then (reported for CT2760) */
8383 y = inp(PORT_CTRL) & 0xE0; /* read the upper bits of the status register */
84- oplregwr(0x04, 0x60); /* reset both timers and interrupts (see steps 1 and 2) */
85- oplregwr(0x04, 0x80); /* reset both timers and interrupts (see steps 1 and 2) */
84+ mdr_opl_regwr(0x04, 0x60); /* reset both timers and interrupts (see steps 1 and 2) */
85+ mdr_opl_regwr(0x04, 0x80); /* reset both timers and interrupts (see steps 1 and 2) */
8686 /* test the stored results of steps 3 and 7 by ANDing them with E0h. The result of step 3 should be */
8787 if (x != 0) return(-1); /* 00h, and the result of step 7 should be C0h. If both are */
8888 if (y != 0xC0) return(-2); /* ok, an AdLib-compatible board is installed in the computer */
@@ -93,21 +93,21 @@
9393
9494 /* enable OPL3 (if detected) and put it into 36 operators mode */
9595 if (opl3 != 0) {
96- oplregwr(PORT_CTRL, 0x105, 1); /* enable OPL3 mode (36 operators) */
97- oplregwr(PORT_CTRL, 0x104, 0); /* disable four-operator voices */
96+ mdr_opl_regwr(PORT_CTRL, 0x105, 1); /* enable OPL3 mode (36 operators) */
97+ mdr_opl_regwr(PORT_CTRL, 0x104, 0); /* disable four-operator voices */
9898 }
9999 #endif
100100
101- oplregwr(0x01, 0x20); /* enable Waveform Select */
102- oplregwr(0x04, 0x00); /* turn off timers IRQs */
103- oplregwr(0x08, 0x40); /* turn off CSW mode and activate FM synth mode */
104- oplregwr(0xBD, 0x00); /* set vibrato/tremolo depth to low, set melodic mode */
101+ mdr_opl_regwr(0x01, 0x20); /* enable Waveform Select */
102+ mdr_opl_regwr(0x04, 0x00); /* turn off timers IRQs */
103+ mdr_opl_regwr(0x08, 0x40); /* turn off CSW mode and activate FM synth mode */
104+ mdr_opl_regwr(0xBD, 0x00); /* set vibrato/tremolo depth to low, set melodic mode */
105105
106106 for (x = 0; x < VOICESCOUNT; x++) {
107- oplregwr(0x20 + op1offsets[x], 0x1); /* set the modulator's multiple to 1 */
108- oplregwr(0x20 + op2offsets[x], 0x1); /* set the modulator's multiple to 1 */
109- oplregwr(0x40 + op1offsets[x], 0x10); /* set volume of all channels to about 40 dB */
110- oplregwr(0x40 + op2offsets[x], 0x10); /* set volume of all channels to about 40 dB */
107+ mdr_opl_regwr(0x20 + op1offsets[x], 0x1); /* set the modulator's multiple to 1 */
108+ mdr_opl_regwr(0x20 + op2offsets[x], 0x1); /* set the modulator's multiple to 1 */
109+ mdr_opl_regwr(0x40 + op1offsets[x], 0x10); /* set volume of all channels to about 40 dB */
110+ mdr_opl_regwr(0x40 + op2offsets[x], 0x10); /* set volume of all channels to about 40 dB */
111111 }
112112
113113 mdr_opl_clear();
@@ -126,13 +126,13 @@
126126
127127 /* set volume to lowest level on all voices */
128128 for (x = 0; x < VOICESCOUNT; x++) {
129- oplregwr(0x40 + op1offsets[x], 0x1f);
130- oplregwr(0x40 + op2offsets[x], 0x1f);
129+ mdr_opl_regwr(0x40 + op1offsets[x], 0x1f);
130+ mdr_opl_regwr(0x40 + op2offsets[x], 0x1f);
131131 }
132132
133133 #ifdef MDR_OPL3
134134 /* if OPL3, switch the chip back into its default OPL2 mode */
135- if (oplmem->opl3 != 0) oplregwr(port, 0x105, 0);
135+ if (oplmem->opl3 != 0) mdr_opl_regwr(port, 0x105, 0);
136136 #endif
137137 }
138138
@@ -139,7 +139,7 @@
139139
140140 /* releases a note on selected voice. */
141141 void mdr_opl_noteoff(unsigned char voice) {
142- oplregwr(0xB0 + voice, 0);
142+ mdr_opl_regwr(0xB0 + voice, 0);
143143 }
144144
145145
@@ -151,8 +151,8 @@
151151 *
152152 * The note will be kept "pressed" until mdr_opl_noteoff() is called. */
153153 void mdr_opl_noteon(unsigned char voice, unsigned short freq, enum mdr_opl_fgroup_t fgroup) {
154- oplregwr(0xA0 + voice, freq & 0xff); /* set lowfreq */
155- oplregwr(0xB0 + voice, (freq >> 8) | fgroup | 32); /* KEY ON + hifreq + octave */
154+ mdr_opl_regwr(0xA0 + voice, freq & 0xff); /* set lowfreq */
155+ mdr_opl_regwr(0xB0 + voice, (freq >> 8) | fgroup | 32); /* KEY ON + hifreq + octave */
156156 }
157157
158158
@@ -159,8 +159,8 @@
159159 /* changes the frequency of the note currently playing on voice channel, this
160160 * can be used for pitch bending. */
161161 void mdr_opl_notebend(unsigned char voice, unsigned short freq, enum mdr_opl_fgroup_t fgroup) {
162- oplregwr(0xA0 + voice, freq & 0xff); /* set lowfreq */
163- oplregwr(0xB0 + voice, (freq >> 8) | fgroup); /* hifreq + octave */
162+ mdr_opl_regwr(0xA0 + voice, freq & 0xff); /* set lowfreq */
163+ mdr_opl_regwr(0xB0 + voice, (freq >> 8) | fgroup); /* hifreq + octave */
164164 }
165165
166166
@@ -170,7 +170,7 @@
170170 for (i = 0; i < VOICESCOUNT; i++) mdr_opl_noteoff(i);
171171
172172 /* reset the percussion bits at the 0xBD register */
173- oplregwr(0xBD, 0);
173+ mdr_opl_regwr(0xBD, 0);
174174 }
175175
176176
@@ -182,37 +182,37 @@
182182 carr40_cache[voice] = timbre->carrier_40 & 0xC0;
183183
184184 /* KSL (key level scaling) / attenuation */
185- oplregwr(0x40 + op1offsets[voice], timbre->modulator_40);
186- oplregwr(0x40 + op2offsets[voice], carr40_cache[voice] | 0x3f); /* force volume to 0, it will be reajusted during 'note on' */
185+ mdr_opl_regwr(0x40 + op1offsets[voice], timbre->modulator_40);
186+ mdr_opl_regwr(0x40 + op2offsets[voice], carr40_cache[voice] | 0x3f); /* force volume to 0, it will be reajusted during 'note on' */
187187
188188 /* select waveform on both operators */
189- oplregwr(0xE0 + op1offsets[voice], timbre->modulator_E862 >> 24);
190- oplregwr(0xE0 + op2offsets[voice], timbre->carrier_E862 >> 24);
189+ mdr_opl_regwr(0xE0 + op1offsets[voice], timbre->modulator_E862 >> 24);
190+ mdr_opl_regwr(0xE0 + op2offsets[voice], timbre->carrier_E862 >> 24);
191191
192192 /* sustain / release */
193- oplregwr(0x80 + op1offsets[voice], (timbre->modulator_E862 >> 16) & 0xff);
194- oplregwr(0x80 + op2offsets[voice], (timbre->carrier_E862 >> 16) & 0xff);
193+ mdr_opl_regwr(0x80 + op1offsets[voice], (timbre->modulator_E862 >> 16) & 0xff);
194+ mdr_opl_regwr(0x80 + op2offsets[voice], (timbre->carrier_E862 >> 16) & 0xff);
195195
196196 /* attack rate / decay */
197- oplregwr(0x60 + op1offsets[voice], (timbre->modulator_E862 >> 8) & 0xff);
198- oplregwr(0x60 + op2offsets[voice], (timbre->carrier_E862 >> 8) & 0xff);
197+ mdr_opl_regwr(0x60 + op1offsets[voice], (timbre->modulator_E862 >> 8) & 0xff);
198+ mdr_opl_regwr(0x60 + op2offsets[voice], (timbre->carrier_E862 >> 8) & 0xff);
199199
200200 /* AM / vibrato / envelope */
201- oplregwr(0x20 + op1offsets[voice], timbre->modulator_E862 & 0xff);
202- oplregwr(0x20 + op2offsets[voice], timbre->carrier_E862 & 0xff);
201+ mdr_opl_regwr(0x20 + op1offsets[voice], timbre->modulator_E862 & 0xff);
202+ mdr_opl_regwr(0x20 + op2offsets[voice], timbre->carrier_E862 & 0xff);
203203
204204 #ifdef MDR_OPL3
205205 if (oplmem->opl3 != 0) { /* on OPL3 make sure to enable LEFT/RIGHT unmute bits */
206- oplregwr(oplport, 0xC0 + voice, timbre->feedconn | 0x30);
206+ mdr_opl_regwr(oplport, 0xC0 + voice, timbre->feedconn | 0x30);
207207 return;
208208 }
209209 #endif
210210
211- oplregwr(0xC0 + voice, timbre->feedconn);
211+ mdr_opl_regwr(0xC0 + voice, timbre->feedconn);
212212 }
213213
214214
215215 /* adjusts volume of a voice. volume goes from 63 (mute) to 0 (loudest) */
216216 void mdr_opl_voicevolume(unsigned char voice, unsigned char volume) {
217- oplregwr(0x40 + op2offsets[voice], carr40_cache[voice] | volume);
217+ mdr_opl_regwr(0x40 + op2offsets[voice], carr40_cache[voice] | volume);
218218 }
--- trunk/history.txt (revision 99)
+++ trunk/history.txt (revision 100)
@@ -1,5 +1,5 @@
11 version xxxx (xx xxx xxxx)
2-- added the OPL driver (Adlib-style OPL2 FM synth)
2+- added the OPL driver (Adlib-style OPL2 FM synth) along with IMF playback
33 - new mdr_dos_selfexe()
44 - new mdr_dos_truename()
55 - new mdr_coutraw_str() and mdr_coutraw_crlf()
Show on old repository browser