1 /*
2 ** music_opl_mididevice.cpp
3 ** Writes raw OPL commands from the emulated OPL MIDI output to disk.
4 **
5 **---------------------------------------------------------------------------
6 ** Copyright 2008 Randy Heit
7 ** All rights reserved.
8 **
9 ** Redistribution and use in source and binary forms, with or without
10 ** modification, are permitted provided that the following conditions
11 ** are met:
12 **
13 ** 1. Redistributions of source code must retain the above copyright
14 ** notice, this list of conditions and the following disclaimer.
15 ** 2. Redistributions in binary form must reproduce the above copyright
16 ** notice, this list of conditions and the following disclaimer in the
17 ** documentation and/or other materials provided with the distribution.
18 ** 3. The name of the author may not be used to endorse or promote products
19 ** derived from this software without specific prior written permission.
20 **
21 ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
22 ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
23 ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
24 ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
25 ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
26 ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
30 ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 **---------------------------------------------------------------------------
32 **
33 */
34
35 // HEADER FILES ------------------------------------------------------------
36
37 #include "i_musicinterns.h"
38 #include "templates.h"
39 #include "doomdef.h"
40 #include "m_swap.h"
41 #include "w_wad.h"
42 #include "opl.h"
43
44 // MACROS ------------------------------------------------------------------
45
46 // TYPES -------------------------------------------------------------------
47
48 class OPLDump : public OPLEmul
49 {
50 public:
OPLDump(FILE * file)51 OPLDump(FILE *file) : File(file), TimePerTick(0), CurTime(0),
52 CurIntTime(0), TickMul(1), CurChip(0) {}
53
54 // If we're doing things right, these should never be reset.
Reset()55 virtual void Reset() { assert(0); }
56
57 // Update() is only used for getting waveform data, which dumps don't do.
Update(float * buffer,int length)58 virtual void Update(float *buffer, int length) { assert(0); }
59
60 // OPL dumps don't pan beyond what OPL3 is capable of (which is
61 // already written using registers from the original data).
SetPanning(int c,float left,float right)62 virtual void SetPanning(int c, float left, float right) {}
63
64 // Only for the OPL dumpers, not the emulators
SetClockRate(double samples_per_tick)65 virtual void SetClockRate(double samples_per_tick) {}
66 virtual void WriteDelay(int ticks) = 0;
67
68 protected:
69 FILE *File;
70 double TimePerTick; // in milliseconds
71 double CurTime;
72 int CurIntTime;
73 int TickMul;
74 BYTE CurChip;
75 };
76
77 // EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
78
79 // PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
80
81 // PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
82
83 // EXTERNAL DATA DECLARATIONS ----------------------------------------------
84
85 // PRIVATE DATA DEFINITIONS ------------------------------------------------
86
87 // PUBLIC DATA DEFINITIONS -------------------------------------------------
88
89 // CODE --------------------------------------------------------------------
90
91 class OPL_RDOSdump : public OPLDump
92 {
93 public:
OPL_RDOSdump(FILE * file)94 OPL_RDOSdump(FILE *file) : OPLDump(file)
95 {
96 assert(File != NULL);
97 fwrite("RAWADATA\0", 1, 10, File);
98 NeedClockRate = true;
99 }
~OPL_RDOSdump()100 virtual ~OPL_RDOSdump()
101 {
102 if (File != NULL)
103 {
104 WORD endmark = 0xFFFF;
105 fwrite(&endmark, 2, 1, File);
106 fclose(File);
107 }
108 }
109
WriteReg(int reg,int v)110 virtual void WriteReg(int reg, int v)
111 {
112 assert(File != NULL);
113 BYTE chipnum = reg >> 8;
114 if (chipnum != CurChip)
115 {
116 BYTE switcher[2] = { (BYTE)(chipnum + 1), 2 };
117 fwrite(switcher, 1, 2, File);
118 }
119 reg &= 255;
120 if (reg != 0 && reg != 2 && (reg != 255 || v != 255))
121 {
122 BYTE cmd[2] = { BYTE(v), BYTE(reg) };
123 fwrite(cmd, 1, 2, File);
124 }
125 }
126
SetClockRate(double samples_per_tick)127 virtual void SetClockRate(double samples_per_tick)
128 {
129 TimePerTick = samples_per_tick / OPL_SAMPLE_RATE * 1000.0;
130
131 double clock_rate;
132 int clock_mul;
133 WORD clock_word;
134
135 clock_rate = samples_per_tick * ADLIB_CLOCK_MUL;
136 clock_mul = 1;
137
138 // The RDos raw format's clock rate is stored in a word. Therefore,
139 // the longest tick that can be stored is only ~55 ms.
140 while (clock_rate / clock_mul + 0.5 > 65535.0)
141 {
142 clock_mul++;
143 }
144 clock_word = WORD(clock_rate / clock_mul + 0.5);
145
146 if (NeedClockRate)
147 { // Set the initial clock rate.
148 clock_word = LittleShort(clock_word);
149 fseek(File, 8, SEEK_SET);
150 fwrite(&clock_word, 2, 1, File);
151 fseek(File, 0, SEEK_END);
152 NeedClockRate = false;
153 }
154 else
155 { // Change the clock rate in the middle of the song.
156 BYTE clock_change[4] = { 0, 2, BYTE(clock_word & 255), BYTE(clock_word >> 8) };
157 fwrite(clock_change, 1, 4, File);
158 }
159 }
WriteDelay(int ticks)160 virtual void WriteDelay(int ticks)
161 {
162 if (ticks > 0)
163 { // RDos raw has very precise delays but isn't very efficient at
164 // storing long delays.
165 BYTE delay[2];
166
167 ticks *= TickMul;
168 delay[1] = 0;
169 while (ticks > 255)
170 {
171 ticks -= 255;
172 delay[0] = 255;
173 fwrite(delay, 1, 2, File);
174 }
175 delay[0] = BYTE(ticks);
176 fwrite(delay, 1, 2, File);
177 }
178 }
179 protected:
180 bool NeedClockRate;
181 };
182
183 class OPL_DOSBOXdump : public OPLDump
184 {
185 public:
OPL_DOSBOXdump(FILE * file,bool dual)186 OPL_DOSBOXdump(FILE *file, bool dual) : OPLDump(file), Dual(dual)
187 {
188 assert(File != NULL);
189 fwrite("DBRAWOPL"
190 "\0\0" // Minor version number
191 "\1\0" // Major version number
192 "\0\0\0\0" // Total milliseconds
193 "\0\0\0", // Total data
194 1, 20, File);
195 char type[4] = { (char)(Dual * 2), 0, 0, 0 }; // Single or dual OPL-2
196 fwrite(type, 1, 4, File);
197 }
~OPL_DOSBOXdump()198 virtual ~OPL_DOSBOXdump()
199 {
200 if (File != NULL)
201 {
202 long where_am_i = ftell(File);
203 DWORD len[2];
204
205 fseek(File, 12, SEEK_SET);
206 len[0] = LittleLong(CurIntTime);
207 len[1] = LittleLong(DWORD(where_am_i - 24));
208 fwrite(len, 4, 2, File);
209 fclose(File);
210 }
211 }
WriteReg(int reg,int v)212 virtual void WriteReg(int reg, int v)
213 {
214 assert(File != NULL);
215 BYTE chipnum = reg >> 8;
216 if (chipnum != CurChip)
217 {
218 CurChip = chipnum;
219 fputc(chipnum + 2, File);
220 }
221 reg &= 255;
222 BYTE cmd[3] = { 4, BYTE(reg), BYTE(v) };
223 fwrite (cmd + (reg > 4), 1, 3 - (reg > 4), File);
224 }
WriteDelay(int ticks)225 virtual void WriteDelay(int ticks)
226 {
227 if (ticks > 0)
228 { // DosBox only has millisecond-precise delays.
229 int delay;
230
231 CurTime += TimePerTick * ticks;
232 delay = int(CurTime + 0.5) - CurIntTime;
233 CurIntTime += delay;
234 while (delay > 65536)
235 {
236 BYTE cmd[3] = { 1, 255, 255 };
237 fwrite(cmd, 1, 2, File);
238 delay -= 65536;
239 }
240 delay--;
241 if (delay <= 255)
242 {
243 BYTE cmd[2] = { 0, BYTE(delay) };
244 fwrite(cmd, 1, 2, File);
245 }
246 else
247 {
248 assert(delay <= 65535);
249 BYTE cmd[3] = { 1, BYTE(delay & 255), BYTE(delay >> 8) };
250 fwrite(cmd, 1, 3, File);
251 }
252 }
253 }
254 protected:
255 bool Dual;
256 };
257
258 //==========================================================================
259 //
260 // OPLDumperMIDIDevice Constructor
261 //
262 //==========================================================================
263
OPLDumperMIDIDevice(const char * filename)264 OPLDumperMIDIDevice::OPLDumperMIDIDevice(const char *filename)
265 : OPLMIDIDevice(NULL)
266 {
267 // Replace the standard OPL device with a disk writer.
268 delete io;
269 io = new DiskWriterIO(filename);
270 }
271
272 //==========================================================================
273 //
274 // OPLDumperMIDIDevice Destructor
275 //
276 //==========================================================================
277
~OPLDumperMIDIDevice()278 OPLDumperMIDIDevice::~OPLDumperMIDIDevice()
279 {
280 }
281
282 //==========================================================================
283 //
284 // OPLDumperMIDIDevice :: Resume
285 //
286 //==========================================================================
287
Resume()288 int OPLDumperMIDIDevice::Resume()
289 {
290 int time;
291
292 time = PlayTick();
293 while (time != 0)
294 {
295 io->WriteDelay(time);
296 time = PlayTick();
297 }
298 return 0;
299 }
300
301 //==========================================================================
302 //
303 // OPLDumperMIDIDevice :: Stop
304 //
305 //==========================================================================
306
Stop()307 void OPLDumperMIDIDevice::Stop()
308 {
309 }
310
311 //==========================================================================
312 //
313 // DiskWriterIO Constructor
314 //
315 //==========================================================================
316
DiskWriterIO(const char * filename)317 DiskWriterIO::DiskWriterIO(const char *filename)
318 : Filename(filename)
319 {
320 }
321
322 //==========================================================================
323 //
324 // DiskWriterIO Destructor
325 //
326 //==========================================================================
327
~DiskWriterIO()328 DiskWriterIO::~DiskWriterIO()
329 {
330 OPLdeinit();
331 }
332
333 //==========================================================================
334 //
335 // DiskWriterIO :: OPLinit
336 //
337 //==========================================================================
338
OPLinit(uint numchips,bool,bool initopl3)339 int DiskWriterIO::OPLinit(uint numchips, bool, bool initopl3)
340 {
341 FILE *file = fopen(Filename, "wb");
342 if (file == NULL)
343 {
344 Printf("Could not open %s for writing.\n", Filename.GetChars());
345 return 0;
346 }
347
348 numchips = clamp(numchips, 1u, 2u);
349 memset(chips, 0, sizeof(chips));
350 // If the file extension is unknown or not present, the default format
351 // is RAW. Otherwise, you can use DRO.
352 if (Filename.Len() < 5 || stricmp(&Filename[Filename.Len() - 4], ".dro") != 0)
353 {
354 chips[0] = new OPL_RDOSdump(file);
355 }
356 else
357 {
358 chips[0] = new OPL_DOSBOXdump(file, numchips > 1);
359 }
360 OPLchannels = OPL2CHANNELS * numchips;
361 NumChips = numchips;
362 IsOPL3 = numchips > 1;
363 OPLwriteInitState(initopl3);
364 return numchips;
365 }
366
367 //==========================================================================
368 //
369 // DiskWriterIO :: SetClockRate
370 //
371 //==========================================================================
372
SetClockRate(double samples_per_tick)373 void DiskWriterIO::SetClockRate(double samples_per_tick)
374 {
375 static_cast<OPLDump *>(chips[0])->SetClockRate(samples_per_tick);
376 }
377
378 //==========================================================================
379 //
380 // DiskWriterIO :: WriteDelay
381 //
382 //==========================================================================
383
WriteDelay(int ticks)384 void DiskWriterIO :: WriteDelay(int ticks)
385 {
386 static_cast<OPLDump *>(chips[0])->WriteDelay(ticks);
387 }
388