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