1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
|
#include <string>
#include <cstdio>
#include <cctype>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <deque>
#include <cstdarg>
#include <algorithm>
#include <signal.h>
#if defined(__DJGPP__) || (defined(__WATCOMC__) && (defined(__DOS__) || defined(__DOS4G__) || defined(__DOS4GNZ__)))
#define HW_OPL_MSDOS
#include <conio.h>
#include <dos.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#ifdef __DJGPP__
#include <pc.h>
#include <dpmi.h>
#include <go32.h>
#include <sys/farptr.h>
#include <sys/exceptn.h>
#define BIOStimer _farpeekl(_dos_ds, 0x46C)
#endif//__DJGPP__
#ifdef __WATCOMC__
//#include <wdpmi.h>
#include <i86.h>
#include <bios.h>
#include <time.h>
unsigned long biostime(unsigned cmd, unsigned long lon)
{
long tval = (long)lon;
_bios_timeofday(cmd, &tval);
return (unsigned long)tval;
}
#define BIOStimer biostime(0, 0l)
#define BIOSTICK 55 /* biostime() increases one tick about
every 55 msec */
void mch_delay(int32_t msec)
{
/*
* We busy-wait here. Unfortunately, delay() and usleep() have been
* reported to give problems with the original Windows 95. This is
* fixed in service pack 1, but not everybody installed that.
*/
long starttime = biostime(0, 0L);
while(biostime(0, 0L) < starttime + msec / BIOSTICK);
}
//#define BIOStimer _farpeekl(_dos_ds, 0x46C)
//#define DOSMEM(s,o,t) *(t _far *)(SS_DOSMEM + (DWORD)((o)|(s)<<4))
//#define BIOStimer DOSMEM(0x46, 0x6C, WORD);
//#define VSYNC_STATUS Ox3da
//#define VSYNC_MASK Ox08
/* #define SYNC { while(inp(VSYNCSTATUS) & VSYNCMASK);\
while (!(inp(VSYNCSTATUS) & VSYNCMASK)); } */
#endif//__WATCOMC__
#endif
#include <adlmidi.h>
#ifndef HARDWARE_OPL3
#define SDL_MAIN_HANDLED
#include <SDL2/SDL.h>
#include "wave_writer.h"
class MutexType
{
SDL_mutex *mut;
public:
MutexType() : mut(SDL_CreateMutex()) { }
~MutexType()
{
SDL_DestroyMutex(mut);
}
void Lock()
{
SDL_mutexP(mut);
}
void Unlock()
{
SDL_mutexV(mut);
}
};
static std::deque<short> AudioBuffer;
static MutexType AudioBuffer_lock;
static void SDL_AudioCallbackX(void *, Uint8 *stream, int len)
{
SDL_LockAudio();
short *target = (short *) stream;
AudioBuffer_lock.Lock();
/*if(len != AudioBuffer.size())
fprintf(stderr, "len=%d stereo samples, AudioBuffer has %u stereo samples",
len/4, (unsigned) AudioBuffer.size()/2);*/
unsigned ate = (unsigned)len / 2; // number of shorts
if(ate > AudioBuffer.size())
ate = (unsigned)AudioBuffer.size();
for(unsigned a = 0; a < ate; ++a)
{
target[a] = AudioBuffer[a];
}
AudioBuffer.erase(AudioBuffer.begin(), AudioBuffer.begin() + ate);
AudioBuffer_lock.Unlock();
SDL_UnlockAudio();
}
#endif //HARDWARE_OPL3
static bool is_number(const std::string &s)
{
std::string::const_iterator it = s.begin();
while(it != s.end() && std::isdigit(*it)) ++it;
return !s.empty() && it == s.end();
}
static void printError(const char *err)
{
std::fprintf(stderr, "\nERROR: %s\n\n", err);
std::fflush(stderr);
}
static int stop = 0;
#ifndef HARDWARE_OPL3
static void sighandler(int dum)
{
if((dum == SIGINT)
|| (dum == SIGTERM)
#ifndef _WIN32
|| (dum == SIGHUP)
#endif
)
stop = 1;
}
#endif
static void debugPrint(void * /*userdata*/, const char *fmt, ...)
{
char buffer[4096];
std::va_list args;
va_start(args, fmt);
int rc = std::vsnprintf(buffer, sizeof(buffer), fmt, args);
va_end(args);
if(rc > 0)
{
std::fprintf(stdout, " - Debug: %s\n", buffer);
std::fflush(stdout);
}
}
#ifdef DEBUG_TRACE_ALL_EVENTS
static void debugPrintEvent(void * /*userdata*/, ADL_UInt8 type, ADL_UInt8 subtype, ADL_UInt8 channel, const ADL_UInt8 * /*data*/, size_t len)
{
std::fprintf(stdout, " - E: 0x%02X 0x%02X %02d (%d)\r\n", type, subtype, channel, (int)len);
std::fflush(stdout);
}
#endif
int main(int argc, char **argv)
{
std::fprintf(stdout, "==========================================\n"
#ifdef HARDWARE_OPL3
" libADLMIDI demo utility (HW OPL)\n"
#else
" libADLMIDI demo utility\n"
#endif
"==========================================\n\n");
std::fflush(stdout);
if(argc < 2 || std::string(argv[1]) == "--help" || std::string(argv[1]) == "-h")
{
std::printf(
"Usage: adlmidi <midifilename> [ <options> ] [ <bank> [ <numchips> [ <numfourops>] ] ]\n"
" -p Enables adlib percussion instrument mode\n"
" -t Enables tremolo amplification mode\n"
" -v Enables vibrato amplification mode\n"
" -s Enables scaling of modulator volumes\n"
" -nl Quit without looping\n"
" -w Write WAV file rather than playing\n"
"\n"
"Where <bank> - number of embeeded bank or filepath to custom WOPL bank file\n"
"\n"
"Note: To create WOPL bank files use OPL Bank Editor you can get here: \n"
"https://github.com/Wohlstand/OPL3BankEditor\n"
"\n"
);
// Get count of embedded banks (no initialization needed)
int banksCount = adl_getBanksCount();
//Get pointer to list of embedded bank names
const char *const *banknames = adl_getBankNames();
if(banksCount > 0)
{
std::printf(" Available embedded banks by number:\n\n");
for(int a = 0; a < banksCount; ++a)
std::printf("%10s%2u = %s\n", a ? "" : "Banks:", a, banknames[a]);
std::printf(
"\n"
" Use banks 2-5 to play Descent \"q\" soundtracks.\n"
" Look up the relevant bank number from descent.sng.\n"
"\n"
" The fourth parameter can be used to specify the number\n"
" of four-op channels to use. Each four-op channel eats\n"
" the room of two regular channels. Use as many as required.\n"
" The Doom & Hexen sets require one or two, while\n"
" Miles four-op set requires the maximum of numcards*6.\n"
"\n"
);
}
else
{
std::printf(" This build of libADLMIDI has no embedded banks!\n\n");
}
std::fflush(stdout);
return 0;
}
#ifndef HARDWARE_OPL3
//const unsigned MaxSamplesAtTime = 512; // 512=dbopl limitation
// How long is SDL buffer, in seconds?
// The smaller the value, the more often SDL_AudioCallBack()
// is called.
const double AudioBufferLength = 0.08;
// How much do WE buffer, in seconds? The smaller the value,
// the more prone to sound chopping we are.
const double OurHeadRoomLength = 0.1;
// The lag between visual content and audio content equals
// the sum of these two buffers.
SDL_AudioSpec spec;
SDL_AudioSpec obtained;
spec.freq = 44100;
spec.format = AUDIO_S16SYS;
spec.channels = 2;
spec.samples = Uint16((double)spec.freq * AudioBufferLength);
spec.callback = SDL_AudioCallbackX;
#endif
ADL_MIDIPlayer *myDevice;
//Initialize libADLMIDI and create the instance (you can initialize multiple of them!)
myDevice = adl_init(44100);
if(myDevice == NULL)
{
printError("Failed to init MIDI device!\n");
return 1;
}
//Set internal debug messages hook to print all libADLMIDI's internal debug messages
adl_setDebugMessageHook(myDevice, debugPrint, NULL);
/*
* Set library options by parsing of command line arguments
*/
bool recordWave = false;
int loopEnabled = 1;
while(argc > 2)
{
bool had_option = false;
if(!std::strcmp("-p", argv[2]))
adl_setPercMode(myDevice, 1);//Turn on AdLib percussion mode
else if(!std::strcmp("-v", argv[2]))
adl_setHVibrato(myDevice, 1);//Turn on deep vibrato
else if(!std::strcmp("-w", argv[2]))
recordWave = true;//Record library output into WAV file
else if(!std::strcmp("-t", argv[2]))
adl_setHTremolo(myDevice, 1);//Turn on deep tremolo
else if(!std::strcmp("-nl", argv[2]))
loopEnabled = 0; //Enable loop
else if(!std::strcmp("-s", argv[2]))
adl_setScaleModulators(myDevice, 1);//Turn on modulators scaling by volume
else break;
std::copy(argv + (had_option ? 4 : 3),
argv + argc,
argv + 2);
argc -= (had_option ? 2 : 1);
}
//Turn loop on/off (for WAV recording loop must be disabled!)
adl_setLoopEnabled(myDevice, recordWave ? 0 : loopEnabled);
#ifdef DEBUG_TRACE_ALL_EVENTS
//Hook all MIDI events are ticking while generating an output buffer
if(!recordWave)
adl_setRawEventHook(myDevice, debugPrintEvent, NULL);
#endif
#ifdef HARDWARE_OPL3
std::fprintf(stdout, " - Hardware OPL3 chip in use\n");
#else
std::fprintf(stdout, " - %s OPL3 Emulator in use\n", adl_emulatorName());
#endif
#ifndef HARDWARE_OPL3
if(!recordWave)
{
// Set up SDL
if(SDL_OpenAudio(&spec, &obtained) < 0)
{
std::fprintf(stderr, "\nERROR: Couldn't open audio: %s\n\n", SDL_GetError());
//return 1;
}
if(spec.samples != obtained.samples)
{
std::fprintf(stderr, " - Audio wanted (samples=%u,rate=%u,channels=%u);\n"
" - Audio obtained (samples=%u,rate=%u,channels=%u)\n",
spec.samples, spec.freq, spec.channels,
obtained.samples, obtained.freq, obtained.channels);
}
}
#endif
if(argc >= 3)
{
if(is_number(argv[2]))
{
int bankno = std::atoi(argv[2]);
//Choose one of embedded banks
if(adl_setBank(myDevice, bankno) != 0)
{
printError(adl_errorInfo(myDevice));
return 1;
}
std::fprintf(stdout, " - Use embedded bank #%d [%s]\n", bankno, adl_getBankNames()[bankno]);
}
else
{
std::fprintf(stdout, " - Use custom bank [%s]...", argv[2]);
std::fflush(stdout);
//Open external bank file (WOPL format is supported)
//to create or edit them, use OPL3 Bank Editor you can take here https://github.com/Wohlstand/OPL3BankEditor
if(adl_openBankFile(myDevice, argv[2]) != 0)
{
std::fprintf(stdout, "FAILED!\n");
std::fflush(stdout);
printError(adl_errorInfo(myDevice));
return 1;
}
std::fprintf(stdout, "OK!\n");
}
}
#ifndef HARDWARE_OPL3
int numOfChips = 4;
if(argc >= 4)
numOfChips = std::atoi(argv[3]);
//Set count of concurrent emulated chips count to excite channels limit of one chip
if(adl_setNumChips(myDevice, numOfChips) != 0)
{
printError(adl_errorInfo(myDevice));
return 1;
}
std::fprintf(stdout, " - Number of chips %d\n", adl_getNumChips(myDevice));
#else
int numOfChips = 1;
adl_setNumChips(myDevice, numOfChips);
#endif
if(argc >= 5)
{
//Set total count of 4-operator channels between all emulated chips
if(adl_setNumFourOpsChn(myDevice, std::atoi(argv[4])) != 0)
{
printError(adl_errorInfo(myDevice));
return 1;
}
}
std::fprintf(stdout, " - Number of four-ops %d\n", adl_getNumFourOpsChn(myDevice));
//Open MIDI file to play
if(adl_openFile(myDevice, argv[1]) != 0)
{
printError(adl_errorInfo(myDevice));
return 2;
}
std::fprintf(stdout, "File opened!\n");
std::fflush(stdout);
#ifndef HARDWARE_OPL3
signal(SIGINT, sighandler);
signal(SIGTERM, sighandler);
#ifndef _WIN32
signal(SIGHUP, sighandler);
#endif
#else//HARDWARE_OPL3
static const unsigned NewTimerFreq = 209;
unsigned TimerPeriod = 0x1234DDul / NewTimerFreq;
#ifdef __DJGPP__
//disable();
outportb(0x43, 0x34);
outportb(0x40, TimerPeriod & 0xFF);
outportb(0x40, TimerPeriod >> 8);
//enable();
#endif//__DJGPP__
#ifdef __WATCOMC__
std::fprintf(stdout, " - Initializing BIOS timer...\n");
std::fflush(stdout);
//disable();
outp(0x43, 0x34);
outp(0x40, TimerPeriod & 0xFF);
outp(0x40, TimerPeriod >> 8);
//enable();
std::fprintf(stdout, " - Ok!\n");
std::fflush(stdout);
#endif//__WATCOMC__
unsigned long BIOStimer_begin = BIOStimer;
double tick_delay = 0.0;
#endif//HARDWARE_OPL3
double total = adl_totalTimeLength(myDevice);
double loopStart = adl_loopStartTime(myDevice);
double loopEnd = adl_loopEndTime(myDevice);
#ifndef HARDWARE_OPL3
if(!recordWave)
#endif
{
std::fprintf(stdout, " - Loop is turned %s\n", loopEnabled ? "ON" : "OFF");
if(loopStart >= 0.0 && loopEnd >= 0.0)
std::fprintf(stdout, " - Has loop points: %10f ... %10f\n", loopStart, loopEnd);
std::fprintf(stdout, "\n==========================================\n");
std::fflush(stdout);
#ifndef HARDWARE_OPL3
SDL_PauseAudio(0);
#endif
#ifdef DEBUG_SEEKING_TEST
int delayBeforeSeek = 50;
std::fprintf(stdout, "DEBUG: === Random position set test is active! ===\n");
std::fflush(stdout);
#endif
while(!stop)
{
#ifndef HARDWARE_OPL3
short buff[4096];
size_t got = (size_t)adl_play(myDevice, 4096, buff);
if(got <= 0)
break;
#endif
#ifndef DEBUG_TRACE_ALL_EVENTS
std::fprintf(stdout, " \r");
std::fprintf(stdout, "Time position: %10f / %10f\r", adl_positionTell(myDevice), total);
std::fflush(stdout);
#endif
#ifndef HARDWARE_OPL3
AudioBuffer_lock.Lock();
size_t pos = AudioBuffer.size();
AudioBuffer.resize(pos + got);
for(size_t p = 0; p < got; ++p)
AudioBuffer[pos + p] = buff[p];
AudioBuffer_lock.Unlock();
const SDL_AudioSpec &spec = obtained;
while(AudioBuffer.size() > spec.samples + (spec.freq * 2) * OurHeadRoomLength)
{
SDL_Delay(1);
}
#ifdef DEBUG_SEEKING_TEST
if(delayBeforeSeek-- <= 0)
{
delayBeforeSeek = rand() % 50;
double seekTo = double((rand() % int(adl_totalTimeLength(myDevice)) - delayBeforeSeek - 1 ));
adl_positionSeek(myDevice, seekTo);
}
#endif
#else//HARDWARE_OPL3
const double mindelay = 1.0 / NewTimerFreq;
//__asm__ volatile("sti\nhlt");
//usleep(10000);
#ifdef __DJGPP__
__dpmi_yield();
#endif
#ifdef __WATCOMC__
//dpmi_dos_yield();
mch_delay((unsigned int)(tick_delay * 1000.0));
#endif
static unsigned long PrevTimer = BIOStimer;
const unsigned long CurTimer = BIOStimer;
const double eat_delay = (CurTimer - PrevTimer) / (double)NewTimerFreq;
PrevTimer = CurTimer;
tick_delay = adl_tickEvents(myDevice, eat_delay, mindelay);
if(adl_atEnd(myDevice) && tick_delay <= 0)
stop = true;
if(kbhit())
{ // Quit on ESC key!
int c = getch();
if(c == 27)
stop = true;
}
#endif//HARDWARE_OPL3
}
std::fprintf(stdout, " \n\n");
#ifndef HARDWARE_OPL3
SDL_CloseAudio();
#endif
}
#ifndef HARDWARE_OPL3
else
{
std::string wave_out = std::string(argv[1]) + ".wav";
std::fprintf(stdout, " - Recording WAV file %s...\n", wave_out.c_str());
std::fprintf(stdout, "\n==========================================\n");
std::fflush(stdout);
if(wave_open(spec.freq, wave_out.c_str()) == 0)
{
wave_enable_stereo();
while(!stop)
{
short buff[4096];
size_t got = (size_t)adl_play(myDevice, 4096, buff);
if(got <= 0)
break;
wave_write(buff, (long)got);
double complete = std::floor(100.0 * adl_positionTell(myDevice) / total);
std::fprintf(stdout, " \r");
std::fprintf(stdout, "Recording WAV... [%d%% completed]\r", (int)complete);
std::fflush(stdout);
}
wave_close();
std::fprintf(stdout, " \n\n");
if(stop)
std::fprintf(stdout, "Interrupted! Recorded WAV is incomplete, but playable!\n");
else
std::fprintf(stdout, "Completed!\n");
std::fflush(stdout);
}
else
{
adl_close(myDevice);
return 1;
}
}
#endif
#ifdef HARDWARE_OPL3
#ifdef __DJGPP__
// Fix the skewed clock and reset BIOS tick rate
_farpokel(_dos_ds, 0x46C, BIOStimer_begin +
(BIOStimer - BIOStimer_begin)
* (0x1234DD / 65536.0) / NewTimerFreq);
//disable();
outportb(0x43, 0x34);
outportb(0x40, 0);
outportb(0x40, 0);
//enable();
#endif
#ifdef __WATCOMC__
outp(0x43, 0x34);
outp(0x40, 0);
outp(0x40, 0);
#endif
adl_panic(myDevice); //Shut up all sustaining notes
#endif
adl_close(myDevice);
return 0;
}
|