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