comparison ppl.c @ 71:b908fda1036e

Add a simplistic skeleton of a module player with SDL-based view.
author Matti Hamalainen <ccr@tnsp.org>
date Mon, 01 Oct 2012 13:43:09 +0300
parents
children 3b3908d28a4b
comparison
equal deleted inserted replaced
70:a791146e3094 71:b908fda1036e
1 #include "jss.h"
2 #include "jssmod.h"
3 #include "jssmix.h"
4 #include "jssplr.h"
5
6 #include "dmlib.h"
7 #include "dmargs.h"
8 #include "dmimage.h"
9 #include "dmtext.h"
10
11 #include <errno.h>
12
13 struct
14 {
15 BOOL exitFlag;
16 SDL_Surface *screen;
17 SDL_Event event;
18 int optScrWidth, optScrHeight, optVFlags, optScrDepth;
19 } engine;
20
21 struct
22 {
23 Uint32 boxBg, inboxBg, box1, box2, viewDiv, activeRow;
24 } col;
25
26
27 DMBitmapFont *font = NULL;
28
29 char *optFilename = NULL,
30 *optFontFilename = "c64font.png";
31 int optOutFormat = JSS_AUDIO_S16,
32 optOutChannels = 2,
33 optOutFreq = 48000,
34 optMuteOChannels = -1,
35 optStartOrder = -1;
36 BOOL optUsePlayTime = FALSE;
37 size_t optPlayTime;
38
39
40 DMOptArg optList[] =
41 {
42 { 0, '?', "help", "Show this help", OPT_NONE },
43 { 1, 'v', "verbose", "Be more verbose", OPT_NONE },
44 { 2, 0, "fs", "Fullscreen", OPT_NONE },
45 { 3, 'w', "window", "Initial window size/resolution -w 640x480", OPT_ARGREQ },
46
47 { 4, '1', "16bit", "16-bit output", OPT_NONE },
48 { 5, '8', "8bit", "8-bit output", OPT_NONE },
49 { 6, 'm', "mono", "Mono output", OPT_NONE },
50 { 7, 's', "stereo", "Stereo output", OPT_NONE },
51 { 8, 'f', "freq", "Output frequency", OPT_ARGREQ },
52
53 { 9, 'M', "mute", "Mute other channels than #", OPT_ARGREQ },
54 { 10, 'o', "order", "Start from order #", OPT_ARGREQ },
55 { 11, 't', "time", "Play for # seconds", OPT_ARGREQ },
56 };
57
58 const int optListN = sizeof(optList) / sizeof(optList[0]);
59
60
61 void argShowHelp()
62 {
63 dmPrintBanner(stdout, dmProgName, "[options] <module>");
64 dmArgsPrintHelp(stdout, optList, optListN);
65 }
66
67
68 BOOL argHandleOpt(const int optN, char *optArg, char *currArg)
69 {
70 switch (optN) {
71 case 0:
72 argShowHelp();
73 exit(0);
74 break;
75
76 case 1:
77 dmVerbosity++;
78 break;
79
80 case 2:
81 engine.optVFlags |= SDL_FULLSCREEN;
82 break;
83
84 case 3:
85 {
86 int w, h;
87 if (sscanf(optArg, "%dx%d", &w, &h) == 2)
88 {
89 if (w < 320 || h < 200 || w > 3200 || h > 3200)
90 {
91 dmError("Invalid width or height: %d x %d\n", w, h);
92 return FALSE;
93 }
94 engine.optScrWidth = w;
95 engine.optScrHeight = h;
96 }
97 else
98 {
99 dmError("Invalid size argument '%s'.\n", optArg);
100 return FALSE;
101 }
102 }
103 break;
104
105 case 4:
106 optOutFormat = JSS_AUDIO_S16;
107 break;
108
109 case 5:
110 optOutFormat = JSS_AUDIO_U8;
111 break;
112
113 case 6:
114 optOutChannels = JSS_AUDIO_MONO;
115 break;
116
117 case 7:
118 optOutChannels = JSS_AUDIO_STEREO;
119 break;
120
121 case 8:
122 optOutFreq = atoi(optArg);
123 break;
124
125 case 9:
126 optMuteOChannels = atoi(optArg);
127 break;
128
129 case 10:
130 optStartOrder = atoi(optArg);
131 break;
132
133 case 11:
134 optPlayTime = atoi(optArg);
135 optUsePlayTime = TRUE;
136 break;
137
138 default:
139 dmError("Unknown option '%s'.\n", currArg);
140 return FALSE;
141 }
142
143 return TRUE;
144 }
145
146
147 BOOL argHandleFile(char *currArg)
148 {
149 if (!optFilename)
150 optFilename = currArg;
151 else
152 {
153 dmError("Too many filename arguments '%s'\n", currArg);
154 return FALSE;
155 }
156
157 return TRUE;
158 }
159
160
161 BOOL dmInitializeVideo()
162 {
163 engine.screen = SDL_SetVideoMode(
164 engine.optScrWidth, engine.optScrHeight, engine.optScrDepth,
165 engine.optVFlags | SDL_RESIZABLE | SDL_SWSURFACE | SDL_HWPALETTE);
166
167 if (engine.screen == NULL)
168 {
169 dmError("Can't SDL_SetVideoMode(): %s\n", SDL_GetError());
170 return FALSE;
171 }
172
173 col.inboxBg = dmMapRGB(engine.screen, 0, 0, 0);
174 col.boxBg = dmMapRGB(engine.screen, 200, 100, 30);
175 col.box1 = dmMapRGB(engine.screen, 250, 250, 200);
176 col.box2 = dmMapRGB(engine.screen, 100, 50, 0);
177 col.viewDiv = dmMapRGB(engine.screen, 0,0,0);
178 col.activeRow = dmMapRGB(engine.screen, 150,80,0);
179
180 return TRUE;
181 }
182
183
184 static const char patNoteTable[12][3] =
185 {
186 "C-", "C#", "D-",
187 "D#", "E-", "F-",
188 "F#", "G-", "G#",
189 "A-", "A#", "B-"
190 };
191
192
193 #define jmpNMODEffectTable (36)
194 static const char jmpMODEffectTable[jmpNMODEffectTable] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
195
196 void dmPrintNote(SDL_Surface *screen, int xc, int yc, JSSNote *n)
197 {
198 char text[32];
199 char *ptr = text;
200
201 switch (n->note)
202 {
203 case jsetNotSet:
204 sprintf(ptr, "...§");
205 break;
206 case jsetNoteOff:
207 sprintf(ptr, "===§");
208 break;
209 default:
210 sprintf(ptr, "%s%i§",
211 patNoteTable[n->note % 12],
212 n->note / 12);
213 break;
214 }
215
216 ptr += 4;
217
218 if (n->instrument != jsetNotSet)
219 sprintf(ptr, "%.2x§", n->instrument + 1);
220 else
221 sprintf(ptr, "..§");
222
223 ptr += 3;
224
225 if (n->volume == jsetNotSet)
226 sprintf(ptr, "..§");
227 else
228 if (n->volume >= 0x00 && n->volume <= 0x40)
229 sprintf(ptr, "%.2x§", n->volume);
230 else
231 {
232 char c;
233 switch (n->volume & 0xf0)
234 {
235 case 0x50: c = '-'; break;
236 case 0x60: c = '+'; break;
237 case 0x70: c = '/'; break;
238 case 0x80: c = '\\'; break;
239 case 0x90: c = 'S'; break;
240 case 0xa0: c = 'V'; break;
241 case 0xb0: c = 'P'; break;
242 case 0xc0: c = '<'; break;
243 case 0xd0: c = '>'; break;
244 case 0xe0: c = 'M'; break;
245 default: c = '?'; break;
246 }
247 sprintf(ptr, "%c%x§", c, (n->volume & 0x0f));
248 }
249
250 ptr += 3;
251
252 if (n->effect >= 0 && n->effect < jmpNMODEffectTable)
253 *ptr = jmpMODEffectTable[n->effect];
254 else
255 if (n->effect == jsetNotSet)
256 *ptr = '.';
257 else
258 *ptr = '?';
259
260 ptr += 1;
261
262 if (n->param != jsetNotSet)
263 sprintf(ptr, "%.2x", n->param);
264 else
265 sprintf(ptr, "..");
266
267 dmDrawBMTextConst(screen, font, DMD_SATURATE, xc, yc, text);
268 }
269
270
271 void dmDisplayPattern(SDL_Surface *screen, int x0, int y0, int x1, int y1, JSSPattern *pat, int row, int choffs)
272 {
273 int cwidth = (font->width * 10 + 3 * font->width + 5),
274 lwidth = 6 + font->width * 3,
275 qwidth = ((x1 - x0 - lwidth) / cwidth),
276 qheight = ((y1 - y0 - 4) / (font->height + 1)),
277 nrow, nchannel,
278 midrow = qheight / 2;
279
280 dmDrawBox3D(screen, x0 + lwidth, y0, x1, y1,
281 col.boxBg, col.box2, col.box1);
282
283 for (nchannel = 1; nchannel < qwidth; nchannel++)
284 {
285 dmDrawVLine(screen, y0 + 1, y1 - 1, x0 + lwidth + 1 + nchannel * cwidth, col.viewDiv);
286 }
287
288
289 for (nrow = 0; nrow < qheight; nrow++)
290 {
291 int yc = y0 + 2 + (font->height + 1) * nrow;
292
293 dmDrawBMText(screen, font, DMD_SATURATE, x0, yc, "%03d", nrow);
294
295 if (nrow == row)
296 {
297 dmFillRect(screen, x0 + lwidth + 1, yc - 1, x1 - 1, yc + font->height, col.activeRow);
298 }
299
300 for (nchannel = 0; nchannel < qwidth; nchannel++)
301 {
302 if (choffs + nchannel >= pat->nchannels)
303 break;
304
305 dmPrintNote(screen, x0 + lwidth + 4 + nchannel * cwidth, yc,
306 pat->data + (pat->nchannels * nrow) + choffs + nchannel);
307 }
308 }
309 }
310
311
312 void audioCallback(void *userdata, Uint8 *stream, int len)
313 {
314 JSSMixer *d = (JSSMixer *) userdata;
315
316 if (d != NULL)
317 {
318 jvmRenderAudio(d, stream, len / jvmGetSampleSize(d));
319 }
320 }
321
322
323 int main(int argc, char *argv[])
324 {
325 BOOL initSDL = FALSE;
326 DMResource *file = NULL;
327 JSSModule *mod = NULL;
328 JSSMixer *dev = NULL;
329 JSSPlayer *plr = NULL;
330 SDL_AudioSpec afmt;
331 int result = -1;
332
333 memset(&afmt, 0, sizeof(afmt));
334 memset(&engine, 0, sizeof(engine));
335
336 engine.optScrWidth = 640;
337 engine.optScrHeight = 480;
338 engine.optScrDepth = 32;
339
340 dmInitProg("ppl", "Penis Player", "0.1", NULL, NULL);
341
342 // Parse arguments
343 if (!dmArgsProcess(argc, argv, optList, optListN,
344 argHandleOpt, argHandleFile, TRUE))
345 exit(1);
346
347 // Open the files
348 if (optFilename == NULL)
349 {
350 dmError("No filename specified.\n");
351 return 1;
352 }
353
354 if ((file = dmf_create_stdio(optFilename)) == NULL)
355 {
356 dmError("Error opening input file '%s'. (%s)\n",
357 optFilename, strerror(errno));
358 return 1;
359 }
360
361 if ((file = dmf_create_stdio(optFilename)) == NULL)
362 {
363 dmError("Error opening input file '%s'. (%s)\n",
364 optFilename, strerror(errno));
365 return 1;
366 }
367
368 // Initialize miniJSS
369 jssInit();
370
371 // Read module file
372 dmMsg(1, "Reading file: %s\n", optFilename);
373 #ifdef JSS_SUP_XM
374 dmMsg(2, "* Trying XM...\n");
375 result = jssLoadXM(file, &mod);
376 #endif
377 #ifdef JSS_SUP_JSSMOD
378 if (result != 0)
379 {
380 size_t bufgot, bufsize = dmfsize(file);
381 Uint8 *buf = dmMalloc(bufsize);
382 dmfseek(file, 0L, SEEK_SET);
383 dmMsg(2, "* Trying JSSMOD (%d bytes, %p)...\n", bufsize, buf);
384 if ((bufgot = dmfread(buf, 1, bufsize, file)) != bufsize)
385 {
386 dmf_close(file);
387 dmError("Error reading file (not enough data %d), #%d: %s\n",
388 bufgot, dmferror(file), dmErrorStr(dmferror(file)));
389 goto error_exit;
390 }
391 result = jssLoadJSSMOD(buf, bufsize, &mod);
392 dmFree(buf);
393 }
394 #endif
395 dmf_close(file);
396
397 if (result != DMERR_OK)
398 {
399 dmError("Error loading module file, %d: %s\n",
400 result, dmErrorStr(result));
401 goto error_exit;
402 }
403
404 // Try to convert it
405 if ((result = jssConvertModuleForPlaying(mod)) != DMERR_OK)
406 {
407 dmError("Could not convert module for playing, %d: %s\n",
408 result, dmErrorStr(result));
409 goto error_exit;
410 }
411
412 if ((file = dmf_create_stdio(optFontFilename)) == NULL)
413 {
414 dmError("Error opening input file '%s'. (%s)\n",
415 optFontFilename, strerror(errno));
416 goto error_exit;
417 }
418 SDL_Surface *fontbmap = dmLoadImage(file);
419 dmf_close(file);
420 if (fontbmap == NULL)
421 {
422 dmError("Could not load image file '%s'.\n", optFontFilename);
423 goto error_exit;
424 }
425
426 if ((result = dmCreateBitmapFontFromImage(fontbmap, 8, 8, &font)) != DMERR_OK)
427 {
428 dmError("Could not create a font from image, %d: %s\n",
429 result, dmErrorStr(result));
430 goto error_exit;
431 }
432 SDL_FreeSurface(fontbmap);
433
434
435 // Initialize SDL components
436 if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER) != 0)
437 {
438 dmError("Could not initialize SDL: %s\n", SDL_GetError());
439 goto error_exit;
440 }
441 initSDL = TRUE;
442
443
444 // Initialize mixing device
445 dmMsg(2, "Initializing miniJSS mixer with: %d, %d, %d\n",
446 optOutFormat, optOutChannels, optOutFreq);
447
448 dev = jvmInit(optOutFormat, optOutChannels, optOutFreq, JMIX_AUTO);
449 if (dev == NULL)
450 {
451 dmError("jvmInit() returned NULL\n");
452 goto error_exit;
453 }
454
455 switch (optOutFormat)
456 {
457 case JSS_AUDIO_S16: afmt.format = AUDIO_S16SYS; break;
458 case JSS_AUDIO_U16: afmt.format = AUDIO_U16SYS; break;
459 case JSS_AUDIO_S8: afmt.format = AUDIO_S8; break;
460 case JSS_AUDIO_U8: afmt.format = AUDIO_U8; break;
461 default:
462 dmError("Unsupported audio format %d (could not set matching SDL format)\n",
463 optOutFormat);
464 goto error_exit;
465 }
466
467 afmt.freq = optOutFreq;
468 afmt.channels = optOutChannels;
469 afmt.samples = optOutFreq * optOutChannels * 4;
470 afmt.callback = audioCallback;
471 afmt.userdata = (void *) dev;
472
473 // Open the audio device
474 if (SDL_OpenAudio(&afmt, NULL) < 0)
475 {
476 dmError("Couldn't open SDL audio: %s\n",
477 SDL_GetError());
478 goto error_exit;
479 }
480
481 // Initialize player
482 if ((plr = jmpInit(dev)) == NULL)
483 {
484 dmError("jmpInit() returned NULL\n");
485 goto error_exit;
486 }
487
488 jvmSetCallback(dev, jmpExec, plr);
489 jmpSetModule(plr, mod);
490 jmpPlayOrder(plr, 0);
491 jvmSetGlobalVol(dev, 60);
492
493 // Initialize video
494 if (!dmInitializeVideo())
495 goto error_exit;
496
497 SDL_WM_SetCaption(dmProgDesc, dmProgName);
498
499
500 // okay, main loop here ... "play" module and print out info
501 SDL_PauseAudio(0);
502
503 while (!engine.exitFlag && plr->isPlaying)
504 {
505 while (SDL_PollEvent(&engine.event))
506 switch (engine.event.type)
507 {
508 case SDL_KEYDOWN:
509 switch (engine.event.key.keysym.sym)
510 {
511 case SDLK_ESCAPE:
512 engine.exitFlag = TRUE;
513 break;
514
515 default:
516 break;
517 }
518
519 break;
520
521 case SDL_VIDEORESIZE:
522 engine.optScrWidth = engine.event.resize.w;
523 engine.optScrHeight = engine.event.resize.h;
524
525 if (!dmInitializeVideo())
526 goto error_exit;
527
528 break;
529
530 case SDL_VIDEOEXPOSE:
531 break;
532
533 case SDL_QUIT:
534 engine.exitFlag = TRUE;
535 break;
536 }
537
538 // Draw frame
539 if (SDL_MUSTLOCK(engine.screen) != 0 && SDL_LockSurface(engine.screen) != 0)
540 {
541 dmError("Can't lock surface.\n");
542 goto error_exit;
543 }
544
545 dmDrawBox3D(engine.screen, 0, 0, engine.screen->w - 1, engine.screen->h - 1,
546 col.boxBg, col.box1, col.box2);
547
548 dmDrawBMText(engine.screen, font, DMD_SATURATE, 5, 5, "%s v%s by ccr/TNSP - (c) Copyright 2012 TNSP", dmProgDesc, dmProgVersion);
549
550
551 JSS_LOCK(dev);
552 JSS_LOCK(plr);
553
554 dmDrawBMText(engine.screen, font, DMD_SATURATE, 5, 5 + 9,
555 "Tempo %3d | Speed: %3d | Row: %3d | Order: %d",
556 plr->tempo, plr->speed, plr->row, plr->order
557 );
558
559 dmDisplayPattern(engine.screen, 5, 30, engine.screen->w - 6, engine.screen->h - 10,
560 plr->pattern, plr->row, 0);
561 JSS_UNLOCK(plr);
562 JSS_UNLOCK(dev);
563
564 // Flip screen
565 if (SDL_MUSTLOCK(engine.screen) != 0)
566 SDL_UnlockSurface(engine.screen);
567
568 SDL_Flip(engine.screen);
569 SDL_Delay(10);
570 }
571
572 error_exit:
573 SDL_PauseAudio(1);
574
575 if (engine.screen)
576 SDL_FreeSurface(engine.screen);
577
578 SDL_LockAudio();
579 jmpClose(plr);
580 jvmClose(dev);
581 jssFreeModule(mod);
582 SDL_UnlockAudio();
583
584 dmFreeBitmapFont(font);
585
586 if (initSDL)
587 SDL_Quit();
588
589 jssClose();
590
591 return 0;
592 }