Mercurial > hg > dmlib
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 } |