1 //
2 // SIDPLAY (simple console front end)
3 // Copyright (C) Michael Schwendt.
4 //
5 // This program is free software; you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation; either version 2 of the License, or
8 // (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 //
19
20 #include <ctype.h>
21 #include <iomanip>
22 #include <iostream>
23 #include <signal.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <unistd.h>
27
28 #include <sidplay/player.h>
29 #include <sidplay/fformat.h>
30 #include <sidplay/myendian.h>
31 #include "audiodrv.h"
32
33 #if defined(__amigaos__)
34 #define EXIT_ERROR_STATUS (20)
35 #else
36 #define EXIT_ERROR_STATUS (-1)
37 #endif
38
39 #if defined(HAVE_SGI)
40 #define DISALLOW_16BIT_SOUND
41 #define DISALLOW_STEREO_SOUND
42 #endif
43
44 using std::cerr;
45 using std::cout;
46 using std::dec;
47 using std::endl;
48 using std::hex;
49 using std::setfill;
50 using std::setw;
51
52 // Error and status message numbers.
53 enum
54 {
55 ERR_SYNTAX,
56 ERR_NOT_ENOUGH_MEMORY,
57 ERR_SIGHANDLER
58 };
59
60 void printError(char* arg0, int messageNum);
61 void printSyntax(char* arg0);
62 int parseTimeStamp(const char* arg);
63
64 bool exitFlag;
65
66 void (*oldSigHupHandler)(int);
67 void (*oldSigIntHandler)(int);
68 void (*oldSigQuitHandler)(int);
69 void (*oldSigTermHandler)(int);
mysighandler(int signum)70 void mysighandler(int signum)
71 {
72 switch (signum)
73 {
74 case SIGHUP:
75 {
76 exitFlag = true;
77 break;
78 }
79 case SIGINT:
80 {
81 exitFlag = true;
82 break;
83 }
84 case SIGQUIT:
85 {
86 exitFlag = true;
87 break;
88 }
89 case SIGTERM:
90 {
91 exitFlag = true;
92 break;
93 }
94 default:
95 break;
96 }
97 }
98
99 static bool verboseOutput = false;
100
main(int argc,char * argv[])101 int main(int argc, char *argv[])
102 {
103 // ======================================================================
104 // INITIALIZE THE EMULATOR ENGINE
105 // ======================================================================
106
107 // Initialize the SID-Emulator Engine to defaults.
108 emuEngine myEmuEngine;
109 // Everything went okay ?
110 if ( !myEmuEngine )
111 {
112 // So far the only possible error.
113 printError(argv[0],ERR_NOT_ENOUGH_MEMORY);
114 exit(EXIT_ERROR_STATUS);
115 }
116 if ( !myEmuEngine.verifyEndianess() )
117 {
118 cerr << argv[0] << ": Hardware endianess improperly configured during compilation." << endl;
119 exit(EXIT_ERROR_STATUS);
120 }
121
122 // Get the default configuration.
123 struct emuConfig myEmuConfig;
124 myEmuEngine.getConfig(myEmuConfig);
125
126 // ======================================================================
127
128 if ( argc < 2 ) // at least one argument required
129 {
130 printError(argv[0],ERR_SYNTAX);
131 exit(EXIT_ERROR_STATUS);
132 }
133
134 cout
135 << "SIDPLAY Music player and C64 SID chip emulator "
136 << "Version " << myEmuEngine.getVersionString() << endl;
137
138 uword selectedSong = 0;
139 int duration = 0;
140
141 // Default audio settings.
142 myEmuConfig.frequency = 22050;
143 myEmuConfig.channels = SIDEMU_MONO;
144 myEmuConfig.bitsPerSample = SIDEMU_8BIT;
145 uword fragments = 16;
146 uword fragSizeBase = 12;
147 int forceBufSize = 0; // get it from argument line
148
149 int infile = 0;
150
151 // parse command line arguments
152 int a = 1;
153 while ((a < argc) && (argv[a] != NULL))
154 {
155 if ( argv[a][0] == '-')
156 {
157 // Reading from stdin?
158 if ( strlen(argv[a]) == 1 )
159 if ( infile == 0 )
160 {
161 infile = a;
162 break;
163 }
164 else
165 {
166 printError(argv[0],ERR_SYNTAX);
167 exit(EXIT_ERROR_STATUS);
168 }
169 if ( myStrNcaseCmp(argv[a],"--help") == 0)
170 {
171 printSyntax(argv[0]);
172 exit(0);
173 }
174 switch ( argv[a][1] )
175 {
176 #if !defined(DISALLOW_16BIT_SOUND)
177 case '1':
178 if ( argv[a][2] == '6' )
179 myEmuConfig.bitsPerSample = SIDEMU_16BIT;
180 break;
181 #endif
182 case 'a':
183 if ( argv[a][2] == '2' )
184 myEmuConfig.memoryMode = MPU_BANK_SWITCHING;
185 else
186 myEmuConfig.memoryMode = MPU_PLAYSID_ENVIRONMENT;
187 break;
188 case 'b':
189 if ( argv[a][2] == 'n' )
190 {
191 fragments = (unsigned)atoi(argv[a]+3);
192 if (( fragments < 2 ) || ( fragments > 255 ))
193 fragments = 2;
194 }
195 else if ( argv[a][2] == 's' )
196 {
197 fragSizeBase = (unsigned)atoi(argv[a]+3);
198 if (( fragSizeBase < 7 ) || ( fragSizeBase > 17 ))
199 fragSizeBase = 14;
200 }
201 else
202 {
203 forceBufSize = atoi(argv[a]+2);
204 }
205 break;
206 case 'c':
207 myEmuConfig.forceSongSpeed = true;
208 break;
209 case 'f':
210 myEmuConfig.frequency = (udword)atol(argv[a]+2);
211 break;
212 case 'h':
213 printSyntax(argv[0]);
214 exit(0);
215 case 'n':
216 if ( argv[a][2] == 'f' )
217 myEmuConfig.emulateFilter = false;
218 else if ( argv[a][2] == 's' )
219 myEmuConfig.mos8580 = true;
220 else
221 myEmuConfig.clockSpeed = SIDTUNE_CLOCK_NTSC;
222 break;
223 case 'o':
224 selectedSong = atoi(argv[a]+2);
225 break;
226 #if !defined(DISALLOW_STEREO_SOUND)
227 case 'p':
228 if ( argv[a][2] == 'c' )
229 {
230 myEmuConfig.autoPanning = SIDEMU_CENTEREDAUTOPANNING;
231 }
232 break;
233 case 's':
234 myEmuConfig.channels = SIDEMU_STEREO;
235 if ( argv[a][2] == 's' )
236 {
237 myEmuConfig.volumeControl = SIDEMU_STEREOSURROUND;
238 }
239 break;
240 #endif
241 case 't':
242 duration = parseTimeStamp(argv[a]+2);
243 break;
244 case 'v':
245 verboseOutput = true;
246 break;
247 default:
248 printSyntax(argv[0]);
249 exit(0);
250 }
251 }
252 else
253 {
254 if ( infile == 0 )
255 infile = a; // filename argument
256 else
257 {
258 printSyntax(argv[0]);
259 exit(0);
260 }
261 }
262 a++; // next argument
263 };
264
265 if (infile == 0)
266 {
267 // Neither file nor stdin.
268 printSyntax(argv[0]);
269 exit(0);
270 }
271
272 // ======================================================================
273 // VALIDATE SID EMULATOR SETTINGS
274 // ======================================================================
275
276 if ((myEmuConfig.autoPanning!=SIDEMU_NONE)
277 && (myEmuConfig.channels==SIDEMU_MONO))
278 {
279 myEmuConfig.channels = SIDEMU_STEREO; // sane
280 }
281 if ((myEmuConfig.autoPanning!=SIDEMU_NONE)
282 && (myEmuConfig.volumeControl==SIDEMU_NONE))
283 {
284 myEmuConfig.volumeControl = SIDEMU_FULLPANNING; // working
285 }
286
287 // ======================================================================
288 // INSTANTIATE A SIDTUNE OBJECT
289 // ======================================================================
290
291 sidTune myTune( argv[infile] );
292 struct sidTuneInfo mySidInfo;
293 myTune.getInfo( mySidInfo );
294 if ( !myTune )
295 {
296 cerr << argv[0] << ": " << mySidInfo.statusString << endl;
297 exit(EXIT_ERROR_STATUS);
298 }
299 else
300 {
301 if (verboseOutput)
302 {
303 cout << "File format : " << mySidInfo.formatString << endl;
304 cout << "Filenames : ";
305 if ( mySidInfo.dataFileName )
306 cout << mySidInfo.dataFileName;
307 else
308 cout << "(None)";
309 cout << ", ";
310 if ( mySidInfo.infoFileName )
311 cout << mySidInfo.infoFileName;
312 else
313 cout << "(None)";
314 cout << endl;
315 cout << "Condition : " << mySidInfo.statusString << endl;
316 }
317 cout << "--------------------------------------------------" << endl;
318 if ( mySidInfo.numberOfInfoStrings == 3 )
319 {
320 cout << "Name : " << mySidInfo.nameString << endl;
321 cout << "Author : " << mySidInfo.authorString << endl;
322 cout << "Copyright : " << mySidInfo.copyrightString << endl;
323 }
324 else
325 {
326 for ( int infoi = 0; infoi < mySidInfo.numberOfInfoStrings; infoi++ )
327 cout << "Description : " << mySidInfo.infoString[infoi] << endl;
328 }
329 cout << "--------------------------------------------------" << endl;
330 if (verboseOutput)
331 {
332 cout << "Load address : $" << hex << setw(4) << setfill('0')
333 << mySidInfo.loadAddr << endl;
334 cout << "Init address : $" << hex << setw(4) << setfill('0')
335 << mySidInfo.initAddr << endl;
336 cout << "Play address : $" << hex << setw(4) << setfill('0')
337 << mySidInfo.playAddr << dec << endl;
338 }
339 }
340
341 // ======================================================================
342 // CONFIGURE THE AUDIO DRIVER
343 // ======================================================================
344
345 // Instantiate the audio driver. The capabilities of the audio driver
346 // can override the settings of the SID emulator.
347 audioDriver myAudio;
348 if ( !myAudio.IsThere() )
349 {
350 cerr << argv[0] << ": No audio device available!" << endl;
351 exit(EXIT_ERROR_STATUS);
352 }
353 // Open() does not accept the "bitsize" value on all platforms, e.g.
354 // Sparcstations 5 and 10 tend to be 16-bit only at rates above 8000 Hz.
355 if ( !myAudio.Open(myEmuConfig.frequency,myEmuConfig.bitsPerSample,
356 myEmuConfig.channels,fragments,fragSizeBase))
357 {
358 cerr << argv[0] << ": " << myAudio.GetErrorString() << endl;
359 exit(EXIT_ERROR_STATUS);
360 }
361 if (verboseOutput)
362 {
363 cout << "Block size : " << (udword)myAudio.GetBlockSize() << endl
364 << "Fragments : " << myAudio.GetFragments() << endl;
365 }
366
367 // ======================================================================
368 // CONFIGURE THE EMULATOR ENGINE
369 // ======================================================================
370
371 // Configure the SID emulator according to the audio driver settings.
372 myEmuConfig.frequency = myAudio.GetFrequency();
373 myEmuConfig.bitsPerSample = myAudio.GetSamplePrecision();
374 myEmuConfig.sampleFormat = myAudio.GetSampleEncoding();
375 myEmuEngine.setConfig( myEmuConfig );
376 // Print the relevant settings.
377 if (verboseOutput)
378 {
379 cout << "Frequency : " << dec << myEmuConfig.frequency << " Hz"
380 << " (" << ((myEmuConfig.bitsPerSample==SIDEMU_8BIT) ? "8" : "16")
381 << "-bit " << ((myEmuConfig.channels==SIDEMU_MONO) ? "mono" : "stereo")
382 << ")" << endl;
383 cout << "SID Filter : " << ((myEmuConfig.emulateFilter == true) ? "Yes" : "No") << endl;
384 if (myEmuConfig.memoryMode == MPU_PLAYSID_ENVIRONMENT)
385 {
386 cout << "Memory mode : PlaySID (this is supposed to fix PlaySID-specific rips)" << endl;
387 }
388 else if (myEmuConfig.memoryMode == MPU_TRANSPARENT_ROM)
389 {
390 cout << "Memory mode : Transparent ROM (SIDPLAY default)" << endl;
391 }
392 else if (myEmuConfig.memoryMode == MPU_BANK_SWITCHING)
393 {
394 cout << "Memory mode : Bank Switching" << endl;
395 }
396 }
397
398 // ======================================================================
399 // INITIALIZE THE EMULATOR ENGINE TO PREPARE PLAYING A SIDTUNE
400 // ======================================================================
401
402 if ( !sidEmuInitializeSong(myEmuEngine,myTune,selectedSong) )
403 {
404 cerr << argv[0] << ": SID Emulator Engine components not ready." << endl;
405 exit(EXIT_ERROR_STATUS);
406 }
407 // Read out the current settings of the sidtune.
408 myTune.getInfo( mySidInfo );
409 if ( !myTune )
410 {
411 cerr << argv[0] << ": " << mySidInfo.statusString << endl;
412 exit(EXIT_ERROR_STATUS);
413 }
414 cout << "Setting song : " << mySidInfo.currentSong
415 << " out of " << mySidInfo.songs
416 << " (default = " << mySidInfo.startSong << ')' << endl;
417 if (verboseOutput)
418 {
419 cout << "Song speed : " << mySidInfo.speedString << endl;
420 }
421
422 // ======================================================================
423 // KEEP UP A CONTINUOUS OUTPUT SAMPLE STREAM
424 // ======================================================================
425
426 int bufSize = myAudio.GetBlockSize();
427 if (forceBufSize != 0)
428 {
429 bufSize = forceBufSize;
430 if (verboseOutput)
431 {
432 cout << "Buffer size : " << bufSize << endl;
433 }
434 }
435 ubyte* buffer;
436 if ((buffer = new ubyte[bufSize]) == 0)
437 {
438 printError(argv[0],ERR_NOT_ENOUGH_MEMORY);
439 exit(EXIT_ERROR_STATUS);
440 }
441
442 exitFlag = false;
443 if ((signal(SIGHUP,&mysighandler) == SIG_ERR)
444 || (signal(SIGINT,&mysighandler) == SIG_ERR)
445 || (signal(SIGQUIT,&mysighandler) == SIG_ERR)
446 || (signal(SIGTERM,&mysighandler) == SIG_ERR))
447 {
448 printError(argv[0],ERR_SIGHANDLER);
449 exit(EXIT_ERROR_STATUS);
450 }
451
452 cout << "Playing";
453 if (duration)
454 cout << ' ' << duration << " seconds";
455 cout << ", press ^C to stop ..." << endl;
456 myEmuEngine.resetSecondsThisSong();
457 while (!duration || duration>myEmuEngine.getSecondsThisSong())
458 {
459 sidEmuFillBuffer(myEmuEngine,myTune,buffer,bufSize);
460 myAudio.Play(buffer,bufSize);
461 if (exitFlag)
462 break;
463 }
464
465 if (exitFlag)
466 myAudio.Reset();
467 myAudio.Close();
468 delete[] buffer;
469 exit(0);
470 }
471
printError(char * arg0,int num)472 void printError(char* arg0, int num)
473 {
474 switch (num)
475 {
476 case ERR_SYNTAX:
477 {
478 cerr << arg0 << ": command line syntax error" << endl
479 << "Try `" << arg0 << " --help' for more information." << endl;
480 break;
481 }
482 case ERR_NOT_ENOUGH_MEMORY:
483 {
484 cerr << arg0 << ": ERROR: Not enough memory." << endl;
485 break;
486 }
487 case ERR_SIGHANDLER:
488 {
489 cerr << arg0 << ": ERROR: Could not install signal handler." << endl;
490 break;
491 }
492 default:
493 break;
494 }
495 }
496
printSyntax(char * arg0)497 void printSyntax(char* arg0)
498 {
499 cout
500 << "Syntax: " << arg0 << " [-<option>...] <datafile>|-" << endl
501 << "Options:" << endl
502 << " --help|-h display this screen" << endl
503 << " -v verbose output" << endl
504 << " -f<num> set frequency in Hz (default: 22050)" << endl
505 << " -o<num> set song number (default: preset)" << endl
506 << " -a strict PlaySID song compatibility (deprecated!)" << endl
507 << " -a2 bank switching mode (overrides -a)" << endl
508 #if !defined(DISALLOW_16BIT_SOUND)
509 << " -16 enable 16-bit sample mixing" << endl
510 #endif
511 #if !defined(DISALLOW_STEREO_SOUND)
512 << " -s enable stereo playback" << endl
513 << " -ss enable stereo surround" << endl
514 << " -pc enable centered auto-panning (stereo only)" << endl
515 #endif
516 << " -n set NTSC clock speed (default: PAL)" << endl
517 << " -nf no SID filter emulation" << endl
518 << " -ns MOS 8580 waveforms (default: MOS 6581)" << endl
519 << " -c force song speed = clock speed (PAL/NTSC)" << endl
520 << " -t<num> set play time in [[h:]m:]s format" << endl
521 << " -bn<num> set number of audio buffer fragments to use" << endl
522 << " -bs<num> set size 2^<num> of audio buffer fragments" << endl
523 << " -b<num> set sample buffer size" << endl
524 << endl;
525 }
526
527 /* Read in h:m:s format at most. Could use a system function
528 if available. */
parseTimeStamp(const char * arg)529 int parseTimeStamp(const char* arg)
530 {
531 int seconds = 0;
532 int passes = 3; /* hours, minutes, seconds */
533 int t;
534 while (passes--)
535 {
536 if (isdigit(*arg))
537 {
538 t = atoi(arg);
539 seconds += t;
540 }
541 while (*arg && isdigit(*arg))
542 {
543 ++arg;
544 }
545 if (*arg == ':')
546 {
547 seconds *= 60;
548 ++arg;
549 }
550 }
551 return seconds;
552 }
553