1542 |
mateusz.vi |
1 |
/*
|
|
|
2 |
* Library to access OPL2/OPL3 hardware (YM3812 / YMF262)
|
|
|
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) 2015-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 |
#ifndef mdr_opl_h
|
|
|
29 |
#define mdr_opl_h
|
|
|
30 |
|
|
|
31 |
struct mdr_opl_timbre {
|
|
|
32 |
unsigned char mod_ws, car_ws; /* waveform select (0-4), reg Exh */
|
|
|
33 |
unsigned char mod_sr, car_sr; /* sustain/release, reg 8xh */
|
|
|
34 |
unsigned char mod_ad, car_ad; /* attack/decay, reg 6xh */
|
|
|
35 |
unsigned char mod_20, car_20; /* tremolo/vibrato/sustain..., reg 2xh */
|
|
|
36 |
unsigned char mod_40, car_40; /* reg 4xh */
|
|
|
37 |
unsigned char feedconn;
|
|
|
38 |
};
|
|
|
39 |
|
|
|
40 |
struct mdr_opl_timbretemplate {
|
|
|
41 |
struct {
|
|
|
42 |
unsigned char ws; /* waveform select 0..3 */
|
|
|
43 |
unsigned char sustlev; /* sustain level 0..15 */
|
|
|
44 |
unsigned char release; /* release level 0..15 */
|
|
|
45 |
unsigned char attack; /* attack rate 0..15 */
|
|
|
46 |
unsigned char decay; /* decay rate 0..15 */
|
|
|
47 |
unsigned char tremolo; /* tremolo flag 0..1 */
|
|
|
48 |
unsigned char vibrato; /* vibrato flag 0..1 */
|
|
|
49 |
unsigned char sustain; /* sustain flag 0..1 */
|
|
|
50 |
unsigned char ksr; /* KSR (envelope scaling) flag 0..1 */
|
|
|
51 |
unsigned char mult; /* frequency multiplication factor 0..15 */
|
|
|
52 |
unsigned char ksl; /* Key Scale Level 0..3 */
|
|
|
53 |
unsigned char outlev; /* output level 0..63 */
|
|
|
54 |
} carrier;
|
|
|
55 |
struct {
|
|
|
56 |
unsigned char ws; /* waveform select 0..3 */
|
|
|
57 |
unsigned char sustlev; /* sustain level 0..15 */
|
|
|
58 |
unsigned char release; /* release level 0..15 */
|
|
|
59 |
unsigned char attack; /* attack rate 0..15 */
|
|
|
60 |
unsigned char decay; /* decay rate 0..15 */
|
|
|
61 |
unsigned char tremolo; /* tremolo flag 0..1 */
|
|
|
62 |
unsigned char vibrato; /* vibrato flag 0..1 */
|
|
|
63 |
unsigned char sustain; /* sustain flag 0..1 */
|
|
|
64 |
unsigned char ksr; /* KSR (envelope scaling) flag 0..1 */
|
|
|
65 |
unsigned char mult; /* frequency multiplication factor 0..15 */
|
|
|
66 |
unsigned char ksl; /* Key Scale Level 0..3 */
|
|
|
67 |
unsigned char outlev; /* output level 0..63 */
|
|
|
68 |
} modultr;
|
|
|
69 |
unsigned char feedback;/* FeedBack Modulation Factor 0..7 */
|
|
|
70 |
unsigned char conn; /* Synthesis type: 0=FM / 1=Additive */
|
|
|
71 |
};
|
|
|
72 |
|
|
|
73 |
enum MDR_OPL_TIMER {
|
|
|
74 |
MDR_OPL_TIMER_80US = 2,
|
|
|
75 |
MDR_OPL_TIMER_320US = 3
|
|
|
76 |
};
|
|
|
77 |
|
|
|
78 |
|
|
|
79 |
/* frequency groups, to be used with mdr_opl_noteon() and mdr_opl_notebend().
|
|
|
80 |
* There are 7 frequency groups to choose from. Each group supports a different
|
|
|
81 |
* span of frequencies. Higher groups have wider spans, but at the cost of larger
|
|
|
82 |
* difference between adjacent notes:
|
|
|
83 |
*
|
|
|
84 |
* Block Note 0 Note 1023 Step gap between adjacent notes
|
|
|
85 |
* FGROUP0 0.047 Hz 48.503 Hz 0.048 Hz
|
|
|
86 |
* FGROUP1 0.094 Hz 97.006 Hz 0.095 Hz
|
|
|
87 |
* FGROUP2 0.189 Hz 194.013 Hz 0.190 Hz
|
|
|
88 |
* FGROUP3 0.379 Hz 388.026 Hz 0.379 Hz
|
|
|
89 |
* FGROUP4 0.758 Hz 776.053 Hz 0.759 Hz
|
|
|
90 |
* FGROUP5 1.517 Hz 1552.107 Hz 1.517 Hz
|
|
|
91 |
* FGROUP6 3.034 Hz 3104.215 Hz 3.034 Hz
|
|
|
92 |
* FGROUP7 6.068 Hz 6208.431 Hz 6.069 Hz
|
|
|
93 |
*
|
|
|
94 |
* This shows that block 7 is capable of reaching the highest note (6.2kHz) but
|
|
|
95 |
* since there are 6 Hz between notes the accuracy suffers. Example: note A-4
|
|
|
96 |
* is 440Hz but in this block, the two closest frequency numbers are 72 and 73,
|
|
|
97 |
* which create tones at 437Hz and 443Hz respectively, neither of which is
|
|
|
98 |
* particularly accurate. Blocks 3 and below are unable to reach as high as
|
|
|
99 |
* 440Hz, but block 4 can. With block 4, frequency numbers 579 and 580 produce
|
|
|
100 |
* 439.4 Hz and 440.2 Hz, considerably closer to the intended frequency.
|
|
|
101 |
*
|
|
|
102 |
* In other words, when calculating notes, the best accuracy is achieved by
|
|
|
103 |
* selecting the lowest possible block number that can reach the desired note
|
|
|
104 |
* frequency.
|
|
|
105 |
*
|
|
|
106 |
* More details: https://moddingwiki.shikadi.net/wiki/OPL_chip#A0-A8:_Frequency_Number
|
|
|
107 |
*/
|
|
|
108 |
|
|
|
109 |
enum mdr_opl_fgroup_t {
|
|
|
110 |
MDR_OPL_FGROUP0 = 0,
|
|
|
111 |
MDR_OPL_FGROUP1 = 1 << 2,
|
|
|
112 |
MDR_OPL_FGROUP2 = 2 << 2,
|
|
|
113 |
MDR_OPL_FGROUP3 = 3 << 2,
|
|
|
114 |
MDR_OPL_FGROUP4 = 4 << 2,
|
|
|
115 |
MDR_OPL_FGROUP5 = 5 << 2,
|
|
|
116 |
MDR_OPL_FGROUP6 = 6 << 2,
|
|
|
117 |
MDR_OPL_FGROUP7 = 7 << 2
|
|
|
118 |
};
|
|
|
119 |
|
|
|
120 |
/* Hardware detection and initialization. Must be called before any other
|
|
|
121 |
* OPL function. Returns 0 on success, non-zero otherwise. */
|
|
|
122 |
int mdr_opl_init(void);
|
|
|
123 |
|
|
|
124 |
/* close OPL device */
|
|
|
125 |
void mdr_opl_close(void);
|
|
|
126 |
|
|
|
127 |
/* turns off all notes */
|
|
|
128 |
void mdr_opl_clear(void);
|
|
|
129 |
|
|
|
130 |
/* loads an instrument described by properties in a timbre_t struct into
|
|
|
131 |
* the defined voice channel. The OPL2 chip supports up to 9 voice channels,
|
|
|
132 |
* from 0 to 8. The timbre struct can be freed right after this call. */
|
|
|
133 |
void mdr_opl_loadinstrument(unsigned char voice, const struct mdr_opl_timbre *timbre);
|
|
|
134 |
|
|
|
135 |
/* generate a timbre structure based on a timbre template. this is a
|
|
|
136 |
* convenience function meant to provide a human-compatible (more readable)
|
|
|
137 |
* way of generating a timbre struct. */
|
|
|
138 |
int mdr_opl_timbre_gen(struct mdr_opl_timbre *timbre, const struct mdr_opl_timbretemplate *tpl);
|
|
|
139 |
|
|
|
140 |
/* Triggers a note on selected voice channel.
|
|
|
141 |
* freqid is a value between 0 and 1023. The following formula can be used to
|
|
|
142 |
* determine the freq number for a given note frequency (Hz) and block:
|
|
|
143 |
*
|
|
|
144 |
* freqid = frequency * 2^(20 - block) / 49716
|
|
|
145 |
*
|
|
|
146 |
* The note will be kept "pressed" until mdr_opl_noteoff() is called. */
|
|
|
147 |
void mdr_opl_noteon(unsigned char voice, unsigned short freqid, enum mdr_opl_fgroup_t fgroup);
|
|
|
148 |
|
|
|
149 |
/* changes the frequency of the note currently playing on voice channel, this
|
|
|
150 |
* can be used for pitch bending. */
|
|
|
151 |
void mdr_opl_notebend(unsigned char voice, unsigned short freqid, enum mdr_opl_fgroup_t fgroup);
|
|
|
152 |
|
|
|
153 |
/* releases a note on selected voice. */
|
|
|
154 |
void mdr_opl_noteoff(unsigned char voice);
|
|
|
155 |
|
|
|
156 |
/* adjusts volume of a voice. volume goes from 63 (mute) to 0 (loudest) */
|
|
|
157 |
void mdr_opl_voicevolume(unsigned char voice, unsigned char volume);
|
|
|
158 |
|
|
|
159 |
/* this is a LOW-LEVEL function that writes a data byte into the reg register
|
|
|
160 |
* of the OPL chip. Use this only if you know exactly what you are doing. */
|
|
|
161 |
void mdr_opl_regwr(unsigned char reg, unsigned char data);
|
|
|
162 |
|
|
|
163 |
|
|
|
164 |
/*****************************************************************************
|
|
|
165 |
* IMF AUDIO FILES PLAYBACK *
|
|
|
166 |
* *
|
|
|
167 |
* It is possible to mix IMF playback calls with manual notes, but you must *
|
|
|
168 |
* take care to use only voices not used by your IMF audio. Typically games *
|
|
|
169 |
* tend to use the voice #0 for sound effects and voices #1 to #8 for music. *
|
|
|
170 |
* *
|
|
|
171 |
* The IMF API comes in two version: the normal one, or "imfeasy". The easy *
|
|
|
172 |
* version is easier to use, but requires to have the entire IMF audio file *
|
|
|
173 |
* loaded in memory, while the normal (non-easy) allows for more flexibility *
|
|
|
174 |
* in this regard, potentially allowing for playback of huge IMF files. *
|
|
|
175 |
*****************************************************************************/
|
|
|
176 |
|
|
|
177 |
/*** EASY INTERFACE ***/
|
|
|
178 |
|
|
|
179 |
/* playback initialization, easy interface. imf points to the start of the IMF
|
|
|
180 |
* file. The imf pointer must not be freed as long as playback is ongoing.
|
|
|
181 |
* imflength is the size (in bytes) of the IMF data.
|
|
|
182 |
* clock must be an incrementing value that wraps to 0 after 65535. The clock
|
|
|
183 |
* speed will control the playback's tempo.
|
|
|
184 |
* loopscount tells how many times the song will have to be looped (0 means
|
|
|
185 |
* "loop forever").
|
|
|
186 |
* returns 0 on success, non-zero otherwise. */
|
|
|
187 |
int mdr_opl_imfeasy_init(void *imf, unsigned short imflength, unsigned short clock, unsigned char loopscount);
|
|
|
188 |
|
|
|
189 |
/* Playback of an IMF file preloaded via mdr_opl_imfeasy_init(). This function
|
|
|
190 |
* must be called repeatedly at a high frequency for best playback quality.
|
|
|
191 |
* Returns 0 on success, 1 if playback ended, -1 on error. */
|
|
|
192 |
int mdr_opl_imfeasy_play(unsigned short clock);
|
|
|
193 |
|
|
|
194 |
/*** ADVANCED INTERFACE ***/
|
|
|
195 |
|
|
|
196 |
/* playback initialization, this function must be called immediately before
|
|
|
197 |
* playback. imf points to the start of the IMF file and must contain at least
|
|
|
198 |
* the first 6 bytes of the audio file.
|
|
|
199 |
* clock must be an incrementing value that wraps to 0 after 65535.
|
|
|
200 |
* the clock speed will control the playback's tempo.
|
|
|
201 |
* returns the amount of consumed bytes (0, 4 or 6) */
|
|
|
202 |
unsigned short mdr_opl_imf_init(void *imf, unsigned short clock);
|
|
|
203 |
|
|
|
204 |
/* Playback, advanced version. Feeds the IMF playback routine with IMF data.
|
|
|
205 |
* Returns the amount of bytes that have been consumed, hence the next call
|
|
|
206 |
* should provide an imf pointer advanced by this many bytes (and imflen
|
|
|
207 |
* decreased accordingly). Such approach might not be the most intuitive, but
|
|
|
208 |
* it allows to load an imf song partially and provide only short chunks of
|
|
|
209 |
* data for playback instead of having to buffer the entire song in memory.
|
|
|
210 |
* For a simpler call that requires to buffer the entire IMF file in memory,
|
|
|
211 |
* see mdr_opl_imf_playeasy().
|
|
|
212 |
* This function must be called repeatedly at a high frequency for best
|
|
|
213 |
* playback quality. */
|
|
|
214 |
unsigned short mdr_opl_imf_play(void *imf, unsigned short imflen, unsigned short clock);
|
|
|
215 |
|
|
|
216 |
|
|
|
217 |
/*****************************************************************************
|
|
|
218 |
* OPL TIMER FUNCTIONS *
|
|
|
219 |
*****************************************************************************/
|
|
|
220 |
|
|
|
221 |
/* configures and starts a timer given type so it emits a tick every count
|
|
|
222 |
* periods. Two timer types are available:
|
|
|
223 |
* MDR_OPL_TIMER_80US - with a period of 80us
|
|
|
224 |
* MDR_OPL_TIMER_320US - with a period of 320us
|
|
|
225 |
* count may range from 0 to 255, but 0 means "256 periods".
|
|
|
226 |
*
|
|
|
227 |
* You may use only one timer at a time.
|
|
|
228 |
*
|
|
|
229 |
* EXAMPLE: setting up MDR_OPL_TIMER_80US with a count of 25 would make the
|
|
|
230 |
* timer tick every 2ms (25 * 80us). */
|
|
|
231 |
void mdr_opl_timer_set(enum MDR_OPL_TIMER timertype, unsigned char count);
|
|
|
232 |
|
|
|
233 |
/* returns 1 if timer tick occured, 0 otherwise. After a tick has been
|
|
|
234 |
* returned, this function will return 0 until next tick.
|
|
|
235 |
*
|
|
|
236 |
* it is important to note that there is no way to know whether one tick
|
|
|
237 |
* passed since last time, or more, so it is up to you to make sure you call
|
|
|
238 |
* this function fast enough. */
|
|
|
239 |
unsigned char mdr_opl_timer_tick(void);
|
|
|
240 |
|
|
|
241 |
#endif
|