Mercurial > repos > blastem
comparison mediaplayer.c @ 2289:92449b47cce8
Integrate VGM player into main blastem binary
author | Michael Pavone <pavone@retrodev.com> |
---|---|
date | Sat, 04 Feb 2023 22:44:44 -0800 |
parents | |
children | c4980d89614b |
comparison
equal
deleted
inserted
replaced
2288:efc75ea79164 | 2289:92449b47cce8 |
---|---|
1 #include <stdlib.h> | |
2 #include <stddef.h> | |
3 #include <limits.h> | |
4 #include <string.h> | |
5 #include "mediaplayer.h" | |
6 #include "io.h" | |
7 #include "ym2612.h" | |
8 #include "psg.h" | |
9 #include "rf5c164.h" | |
10 #include "util.h" | |
11 #include "render.h" | |
12 | |
13 #define ADJUST_BUFFER (12500000) | |
14 #define MAX_NO_ADJUST (UINT_MAX-ADJUST_BUFFER) | |
15 #define MAX_RUN_SAMPLES 128 | |
16 | |
17 enum { | |
18 AUDIO_VGM, | |
19 AUDIO_WAVE, | |
20 AUDIO_FLAC, | |
21 MEDIA_UNKNOWN | |
22 }; | |
23 | |
24 enum { | |
25 STATE_PLAY, | |
26 STATE_PAUSED | |
27 }; | |
28 | |
29 uint32_t cycles_to_samples(uint32_t clock_rate, uint32_t cycles) | |
30 { | |
31 return ((uint64_t)cycles) * ((uint64_t)44100) / ((uint64_t)clock_rate); | |
32 } | |
33 | |
34 uint32_t samples_to_cycles(uint32_t clock_rate, uint32_t cycles) | |
35 { | |
36 return ((uint64_t)cycles) * ((uint64_t)clock_rate) / ((uint64_t)44100); | |
37 } | |
38 | |
39 void ym_adjust(chip_info *chip) | |
40 { | |
41 ym2612_context *ym = chip->context; | |
42 if (ym->current_cycle >= MAX_NO_ADJUST) { | |
43 uint32_t deduction = ym->current_cycle - ADJUST_BUFFER; | |
44 chip->samples -= cycles_to_samples(chip->clock, deduction); | |
45 ym->current_cycle -= deduction; | |
46 } | |
47 } | |
48 | |
49 void psg_adjust(chip_info *chip) | |
50 { | |
51 psg_context *psg = chip->context; | |
52 if (psg->cycles >= MAX_NO_ADJUST) { | |
53 uint32_t deduction = psg->cycles - ADJUST_BUFFER; | |
54 chip->samples -= cycles_to_samples(chip->clock, deduction); | |
55 psg->cycles -= deduction; | |
56 } | |
57 } | |
58 | |
59 void pcm_adjust(chip_info *chip) | |
60 { | |
61 rf5c164 *pcm = chip->context; | |
62 if (pcm->cycle >= MAX_NO_ADJUST) { | |
63 uint32_t deduction = pcm->cycle - ADJUST_BUFFER; | |
64 chip->samples -= cycles_to_samples(chip->clock, deduction); | |
65 pcm->cycle -= deduction; | |
66 } | |
67 } | |
68 | |
69 uint8_t *find_block(data_block *head, uint32_t offset, uint32_t size) | |
70 { | |
71 if (!head) { | |
72 return NULL; | |
73 } | |
74 while (head->size < offset) { | |
75 offset -= head->size; | |
76 head = head->next; | |
77 } | |
78 if (head->size - offset < size) { | |
79 return NULL; | |
80 } | |
81 return head->data + offset; | |
82 } | |
83 | |
84 void vgm_wait(media_player *player, uint32_t samples) | |
85 { | |
86 chip_info *chips = player->chips; | |
87 uint32_t num_chips = player->num_chips; | |
88 while (samples > MAX_RUN_SAMPLES) | |
89 { | |
90 vgm_wait(player, MAX_RUN_SAMPLES); | |
91 samples -= MAX_RUN_SAMPLES; | |
92 } | |
93 for (uint32_t i = 0; i < num_chips; i++) | |
94 { | |
95 chips[i].samples += samples; | |
96 chips[i].run(chips[i].context, samples_to_cycles(chips[i].clock, chips[i].samples)); | |
97 chips[i].adjust(chips + i); | |
98 } | |
99 } | |
100 | |
101 void vgm_stop(media_player *player) | |
102 { | |
103 player->state = STATE_PAUSED; | |
104 player->playback_time = 0; | |
105 player->current_offset = player->vgm->data_offset + offsetof(vgm_header, data_offset); | |
106 } | |
107 | |
108 chip_info *find_chip(media_player *player, uint8_t cmd) | |
109 { | |
110 for (uint32_t i = 0; i < player->num_chips; i++) | |
111 { | |
112 if (player->chips[i].cmd == cmd) { | |
113 return player->chips + i; | |
114 } | |
115 } | |
116 return NULL; | |
117 } | |
118 | |
119 void *find_chip_context(media_player *player, uint8_t cmd) | |
120 { | |
121 chip_info *chip = find_chip(player, cmd); | |
122 return chip ? chip->context : NULL; | |
123 } | |
124 | |
125 chip_info *find_chip_by_data(media_player *player, uint8_t data_type) | |
126 { | |
127 for (uint32_t i = 0; i < player->num_chips; i++) | |
128 { | |
129 if (player->chips[i].data_type == data_type) { | |
130 return &player->chips[i]; | |
131 } | |
132 } | |
133 return NULL; | |
134 } | |
135 | |
136 static uint8_t read_byte(media_player *player) | |
137 { | |
138 uint8_t *buffer = player->media->buffer; | |
139 return buffer[player->current_offset++]; | |
140 } | |
141 | |
142 static uint16_t read_word_le(media_player *player) | |
143 { | |
144 uint8_t *buffer = player->media->buffer; | |
145 uint16_t value = buffer[player->current_offset++]; | |
146 value |= buffer[player->current_offset++] << 8; | |
147 return value; | |
148 } | |
149 | |
150 static uint32_t read_24_le(media_player *player) | |
151 { | |
152 uint8_t *buffer = player->media->buffer; | |
153 uint32_t value = buffer[player->current_offset++]; | |
154 value |= buffer[player->current_offset++] << 8; | |
155 value |= buffer[player->current_offset++] << 16; | |
156 return value; | |
157 } | |
158 | |
159 static uint32_t read_long_le(media_player *player) | |
160 { | |
161 uint8_t *buffer = player->media->buffer; | |
162 uint32_t value = buffer[player->current_offset++]; | |
163 value |= buffer[player->current_offset++] << 8; | |
164 value |= buffer[player->current_offset++] << 16; | |
165 value |= buffer[player->current_offset++] << 24; | |
166 return value; | |
167 } | |
168 | |
169 void vgm_frame(media_player *player) | |
170 { | |
171 for (uint32_t remaining_samples = 44100 / 60; remaining_samples > 0;) | |
172 { | |
173 if (player->wait_samples) { | |
174 uint32_t to_wait = player->wait_samples; | |
175 if (to_wait > remaining_samples) { | |
176 to_wait = remaining_samples; | |
177 } | |
178 vgm_wait(player, to_wait); | |
179 player->wait_samples -= to_wait; | |
180 remaining_samples -= to_wait; | |
181 if (player->wait_samples) { | |
182 return; | |
183 } | |
184 } | |
185 if (player->current_offset >= player->media->size) { | |
186 vgm_stop(player); | |
187 return; | |
188 } | |
189 uint8_t cmd = read_byte(player); | |
190 psg_context *psg; | |
191 ym2612_context *ym; | |
192 rf5c164 *pcm; | |
193 switch (cmd) | |
194 { | |
195 case CMD_PSG_STEREO: | |
196 psg = find_chip_context(player, CMD_PSG); | |
197 if (!psg || player->current_offset > player->media->size - 1) { | |
198 vgm_stop(player); | |
199 return; | |
200 } | |
201 psg->pan = read_byte(player); | |
202 break; | |
203 case CMD_PSG: | |
204 psg = find_chip_context(player, CMD_PSG); | |
205 if (!psg || player->current_offset > player->media->size - 1) { | |
206 vgm_stop(player); | |
207 return; | |
208 } | |
209 psg_write(psg, read_byte(player)); | |
210 break; | |
211 case CMD_YM2612_0: | |
212 ym = find_chip_context(player, CMD_YM2612_0); | |
213 if (!ym || player->current_offset > player->media->size - 2) { | |
214 vgm_stop(player); | |
215 return; | |
216 } | |
217 ym_address_write_part1(ym, read_byte(player)); | |
218 ym_data_write(ym, read_byte(player)); | |
219 break; | |
220 case CMD_YM2612_1: | |
221 ym = find_chip_context(player, CMD_YM2612_0); | |
222 if (!ym || player->current_offset > player->media->size - 2) { | |
223 vgm_stop(player); | |
224 return; | |
225 } | |
226 ym_address_write_part2(ym, read_byte(player)); | |
227 ym_data_write(ym, read_byte(player)); | |
228 break; | |
229 case CMD_WAIT: { | |
230 if (player->current_offset > player->media->size - 2) { | |
231 vgm_stop(player); | |
232 return; | |
233 } | |
234 player->wait_samples += read_word_le(player); | |
235 break; | |
236 } | |
237 case CMD_WAIT_60: | |
238 player->wait_samples += 735; | |
239 break; | |
240 case CMD_WAIT_50: | |
241 player->wait_samples += 882; | |
242 break; | |
243 case CMD_END: | |
244 //TODO: loops | |
245 vgm_stop(player); | |
246 return; | |
247 case CMD_PCM_WRITE: { | |
248 if (player->current_offset > player->media->size - 11) { | |
249 vgm_stop(player); | |
250 return; | |
251 } | |
252 player->current_offset++; //skip compatibility command | |
253 uint8_t data_type = read_byte(player); | |
254 uint32_t read_offset = read_24_le(player); | |
255 uint32_t write_offset = read_24_le(player); | |
256 uint16_t size = read_24_le(player); | |
257 chip_info *chip = find_chip_by_data(player, data_type); | |
258 if (!chip || !chip->blocks) { | |
259 warning("Failed to find data block list for type %d\n", data_type); | |
260 break; | |
261 } | |
262 uint8_t *src = find_block(chip->blocks, read_offset, size); | |
263 if (!src) { | |
264 warning("Failed to find data offset %X with size %X for chip type %d\n", read_offset, size, data_type); | |
265 break; | |
266 } | |
267 switch (data_type) | |
268 { | |
269 case DATA_RF5C68: | |
270 case DATA_RF5C164: | |
271 pcm = chip->context; | |
272 write_offset |= pcm->ram_bank; | |
273 write_offset &= 0xFFFF; | |
274 if (size + write_offset > 0x10000) { | |
275 size = 0x10000 - write_offset; | |
276 } | |
277 memcpy(pcm->ram + write_offset, src, size); | |
278 break; | |
279 default: | |
280 warning("Unknown PCM write read_offset %X, write_offset %X, size %X\n", read_offset, write_offset, size); | |
281 } | |
282 break; | |
283 } | |
284 case CMD_PCM68_REG: | |
285 pcm = find_chip_context(player, CMD_PCM68_REG); | |
286 if (!pcm || player->current_offset > player->media->size - 2) { | |
287 vgm_stop(player); | |
288 return; | |
289 } else { | |
290 uint8_t reg = read_byte(player); | |
291 uint8_t value = read_byte(player); | |
292 rf5c164_write(pcm, reg, value); | |
293 } | |
294 break; | |
295 case CMD_PCM164_REG: | |
296 pcm = find_chip_context(player, CMD_PCM164_REG); | |
297 if (!pcm || player->current_offset > player->media->size - 2) { | |
298 vgm_stop(player); | |
299 return; | |
300 } else { | |
301 uint8_t reg = read_byte(player); | |
302 uint8_t value = read_byte(player); | |
303 rf5c164_write(pcm, reg, value); | |
304 } | |
305 break; | |
306 case CMD_PCM68_RAM: | |
307 pcm = find_chip_context(player, CMD_PCM68_REG); | |
308 if (!pcm || player->current_offset > player->media->size - 3) { | |
309 vgm_stop(player); | |
310 return; | |
311 } else { | |
312 uint16_t address = read_word_le(player); | |
313 address &= 0xFFF; | |
314 address |= 0x1000; | |
315 rf5c164_write(pcm, address, read_byte(player)); | |
316 } | |
317 break; | |
318 case CMD_PCM164_RAM: | |
319 pcm = find_chip_context(player, CMD_PCM164_REG); | |
320 if (!pcm || player->current_offset > player->media->size - 3) { | |
321 vgm_stop(player); | |
322 return; | |
323 } else { | |
324 uint16_t address = read_word_le(player); | |
325 address &= 0xFFF; | |
326 address |= 0x1000; | |
327 rf5c164_write(pcm, address, read_byte(player)); | |
328 } | |
329 break; | |
330 case CMD_DATA: | |
331 if (player->current_offset > player->media->size - 6) { | |
332 vgm_stop(player); | |
333 return; | |
334 } else { | |
335 player->current_offset++; //skip compat command | |
336 uint8_t data_type = read_byte(player); | |
337 uint32_t data_size = read_long_le(player); | |
338 if (data_size > player->media->size || player->current_offset > player->media->size - data_size) { | |
339 vgm_stop(player); | |
340 return; | |
341 } | |
342 chip_info *chip = find_chip_by_data(player, data_type); | |
343 if (chip) { | |
344 data_block **cur = &(chip->blocks); | |
345 while (*cur) | |
346 { | |
347 cur = &((*cur)->next); | |
348 } | |
349 *cur = calloc(1, sizeof(data_block)); | |
350 (*cur)->size = data_size; | |
351 (*cur)->type = data_type; | |
352 (*cur)->data = ((uint8_t *)player->media->buffer) + player->current_offset; | |
353 } else { | |
354 fprintf(stderr, "Skipping data block with unrecognized type %X\n", data_type); | |
355 } | |
356 player->current_offset += data_size; | |
357 } | |
358 break; | |
359 case CMD_DATA_SEEK: | |
360 if (player->current_offset > player->media->size - 4) { | |
361 vgm_stop(player); | |
362 return; | |
363 } else { | |
364 uint32_t new_offset = read_long_le(player); | |
365 if (!player->ym_seek_block || new_offset < player->ym_seek_offset) { | |
366 chip_info *chip = find_chip(player, CMD_YM2612_0); | |
367 if (!chip) { | |
368 break; | |
369 } | |
370 player->ym_seek_block = chip->blocks; | |
371 player->ym_seek_offset = 0; | |
372 player->ym_block_offset = 0; | |
373 } | |
374 while (player->ym_seek_block && (player->ym_seek_offset - player->ym_block_offset + player->ym_seek_block->size) < new_offset) | |
375 { | |
376 player->ym_seek_offset += player->ym_seek_block->size - player->ym_block_offset; | |
377 player->ym_seek_block = player->ym_seek_block->next; | |
378 player->ym_block_offset = 0; | |
379 } | |
380 player->ym_block_offset += new_offset - player->ym_seek_offset; | |
381 player->ym_seek_offset = new_offset; | |
382 } | |
383 break; | |
384 default: | |
385 if (cmd >= CMD_WAIT_SHORT && cmd < (CMD_WAIT_SHORT + 0x10)) { | |
386 uint32_t wait_time = (cmd & 0xF) + 1; | |
387 player->wait_samples += wait_time; | |
388 } else if (cmd >= CMD_YM2612_DAC && cmd < CMD_DAC_STREAM_SETUP) { | |
389 if (player->ym_seek_block) { | |
390 ym = find_chip_context(player, CMD_YM2612_0); | |
391 ym_address_write_part1(ym, 0x2A); | |
392 ym_data_write(ym, player->ym_seek_block->data[player->ym_block_offset++]); | |
393 player->ym_seek_offset++; | |
394 if (player->ym_block_offset > player->ym_seek_block->size) { | |
395 player->ym_seek_block = player->ym_seek_block->next; | |
396 player->ym_block_offset = 0; | |
397 } | |
398 } else { | |
399 fputs("Encountered DAC write command but data seek pointer is invalid!\n", stderr); | |
400 } | |
401 player->wait_samples += cmd & 0xF; | |
402 } else { | |
403 warning("unimplemented command: %X at offset %X\n", cmd, player->current_offset); | |
404 vgm_stop(player); | |
405 return; | |
406 } | |
407 } | |
408 } | |
409 } | |
410 | |
411 void wave_frame(media_player *player) | |
412 { | |
413 render_sleep_ms(15); | |
414 } | |
415 | |
416 void flac_frame(media_player *player) | |
417 { | |
418 render_sleep_ms(15); | |
419 } | |
420 | |
421 void vgm_init(media_player *player, uint32_t opts) | |
422 { | |
423 player->vgm = calloc(1, sizeof(vgm_header)); | |
424 player->vgm_ext = NULL; | |
425 memcpy(player->vgm, player->media->buffer, sizeof(vgm_header)); | |
426 if (player->vgm->version < 0x150 || !player->vgm->data_offset) { | |
427 player->vgm->data_offset = 0xC; | |
428 } | |
429 if (player->vgm->data_offset + offsetof(vgm_header, data_offset) > player->media->size) { | |
430 player->vgm->data_offset = player->media->size - offsetof(vgm_header, data_offset); | |
431 } | |
432 if (player->vgm->version <= 0x101 && player->vgm->ym2413_clk > 4000000) { | |
433 player->vgm->ym2612_clk = player->vgm->ym2413_clk; | |
434 player->vgm->ym2413_clk = 0; | |
435 } | |
436 if (player->vgm->data_offset > 0xC) { | |
437 player->vgm_ext = calloc(1, sizeof(vgm_extended_header)); | |
438 size_t additional_header = player->vgm->data_offset + offsetof(vgm_header, data_offset) - sizeof(vgm_header); | |
439 if (additional_header > sizeof(vgm_extended_header)) { | |
440 additional_header = sizeof(vgm_extended_header); | |
441 } | |
442 memcpy(player->vgm_ext, ((uint8_t *)player->media->buffer) + sizeof(vgm_header), additional_header); | |
443 } | |
444 player->num_chips = 0; | |
445 if (player->vgm->sn76489_clk) { | |
446 player->num_chips++; | |
447 } | |
448 if (player->vgm->ym2612_clk) { | |
449 player->num_chips++; | |
450 } | |
451 if (player->vgm_ext && player->vgm_ext->rf5c68_clk) { | |
452 player->num_chips++; | |
453 } | |
454 if (player->vgm_ext && player->vgm_ext->rf5c164_clk) { | |
455 player->num_chips++; | |
456 } | |
457 player->chips = calloc(player->num_chips, sizeof(chip_info)); | |
458 uint32_t chip = 0; | |
459 if (player->vgm->sn76489_clk) { | |
460 psg_context *psg = calloc(1, sizeof(psg_context)); | |
461 psg_init(psg, player->vgm->sn76489_clk, 1); | |
462 player->chips[chip++] = (chip_info) { | |
463 .context = psg, | |
464 .run = (chip_run_fun)psg_run, | |
465 .adjust = psg_adjust, | |
466 .clock = player->vgm->sn76489_clk, | |
467 .samples = 0, | |
468 .cmd = CMD_PSG, | |
469 .data_type = 0xFF | |
470 }; | |
471 } | |
472 if (player->vgm->ym2612_clk) { | |
473 ym2612_context *ym = calloc(1, sizeof(ym2612_context)); | |
474 ym_init(ym, player->vgm->ym2612_clk, 1, opts); | |
475 player->chips[chip++] = (chip_info) { | |
476 .context = ym, | |
477 .run = (chip_run_fun)ym_run, | |
478 .adjust = ym_adjust, | |
479 .clock = player->vgm->ym2612_clk, | |
480 .samples = 0, | |
481 .cmd = CMD_YM2612_0, | |
482 .data_type = DATA_YM2612_PCM | |
483 }; | |
484 } | |
485 if (player->vgm_ext && player->vgm_ext->rf5c68_clk) { | |
486 rf5c164 *pcm = calloc(1, sizeof(rf5c164)); | |
487 rf5c164_init(pcm, player->vgm_ext->rf5c68_clk, 1); | |
488 player->chips[chip++] = (chip_info) { | |
489 .context = pcm, | |
490 .run = (chip_run_fun)rf5c164_run, | |
491 .adjust = pcm_adjust, | |
492 .clock = player->vgm_ext->rf5c68_clk, | |
493 .samples = 0, | |
494 .cmd = CMD_PCM68_REG, | |
495 .data_type = DATA_RF5C68 | |
496 }; | |
497 } | |
498 if (player->vgm_ext && player->vgm_ext->rf5c164_clk) { | |
499 rf5c164 *pcm = calloc(1, sizeof(rf5c164)); | |
500 rf5c164_init(pcm, player->vgm_ext->rf5c164_clk, 1); | |
501 player->chips[chip++] = (chip_info) { | |
502 .context = pcm, | |
503 .run = (chip_run_fun)rf5c164_run, | |
504 .adjust = pcm_adjust, | |
505 .clock = player->vgm_ext->rf5c164_clk, | |
506 .samples = 0, | |
507 .cmd = CMD_PCM164_REG, | |
508 .data_type = DATA_RF5C164 | |
509 }; | |
510 } | |
511 player->current_offset = player->vgm->data_offset + offsetof(vgm_header, data_offset); | |
512 } | |
513 | |
514 static void resume_player(system_header *system) | |
515 { | |
516 media_player *player = (media_player *)system; | |
517 player->should_return = 0; | |
518 while (!player->header.should_exit && !player->should_return) | |
519 { | |
520 switch (player->state) | |
521 { | |
522 case STATE_PLAY: | |
523 switch(player->media_type) | |
524 { | |
525 case AUDIO_VGM: | |
526 vgm_frame(player); | |
527 break; | |
528 case AUDIO_WAVE: | |
529 wave_frame(player); | |
530 break; | |
531 case AUDIO_FLAC: | |
532 flac_frame(player); | |
533 break; | |
534 } | |
535 break; | |
536 case STATE_PAUSED: | |
537 render_sleep_ms(15); | |
538 break; | |
539 } | |
540 render_update_display(); | |
541 } | |
542 } | |
543 | |
544 static void gamepad_down(system_header *system, uint8_t pad, uint8_t button) | |
545 { | |
546 if (button >= BUTTON_A && button <= BUTTON_C) { | |
547 media_player *player = (media_player *)system; | |
548 if (player->state == STATE_PAUSED) { | |
549 player->state = STATE_PLAY; | |
550 puts("Now playing"); | |
551 } else { | |
552 player->state = STATE_PAUSED; | |
553 puts("Now paused"); | |
554 } | |
555 } | |
556 } | |
557 | |
558 static void gamepad_up(system_header *system, uint8_t pad, uint8_t button) | |
559 { | |
560 } | |
561 | |
562 static void start_player(system_header *system, char *statefile) | |
563 { | |
564 resume_player(system); | |
565 } | |
566 | |
567 static void free_player(system_header *system) | |
568 { | |
569 media_player *player = (media_player *)system; | |
570 for (uint32_t i = 0; i < player->num_chips; i++) | |
571 { | |
572 //TODO properly free chips | |
573 free(player->chips[i].context); | |
574 } | |
575 free(player->chips); | |
576 free(player->vgm); | |
577 free(player); | |
578 } | |
579 | |
580 uint8_t detect_media_type(system_media *media) | |
581 { | |
582 if (media->size < 4) { | |
583 return MEDIA_UNKNOWN; | |
584 } | |
585 if (!memcmp(media->buffer, "Vgm ", 4)) { | |
586 if (media->size < sizeof(vgm_header)) { | |
587 return MEDIA_UNKNOWN; | |
588 } | |
589 return AUDIO_VGM; | |
590 } | |
591 if (!memcmp(media->buffer, "RIFF", 4)) { | |
592 return AUDIO_WAVE; | |
593 } | |
594 if (!memcmp(media->buffer, "fLaC", 4)) { | |
595 return AUDIO_FLAC; | |
596 } | |
597 return MEDIA_UNKNOWN; | |
598 } | |
599 | |
600 static void request_exit(system_header *system) | |
601 { | |
602 media_player *player = (media_player *)system; | |
603 player->should_return = 1; | |
604 } | |
605 | |
606 media_player *alloc_media_player(system_media *media, uint32_t opts) | |
607 { | |
608 media_player *player = calloc(1, sizeof(media_player)); | |
609 player->header.start_context = start_player; | |
610 player->header.resume_context = resume_player; | |
611 player->header.request_exit = request_exit; | |
612 player->header.free_context = free_player; | |
613 player->header.gamepad_down = gamepad_down; | |
614 player->header.gamepad_up = gamepad_down; | |
615 player->header.type = SYSTEM_MEDIA_PLAYER; | |
616 player->header.info.name = strdup(media->name); | |
617 | |
618 player->media = media; | |
619 player->media_type = detect_media_type(media); | |
620 player->state = STATE_PLAY; | |
621 switch (player->media_type) | |
622 { | |
623 case AUDIO_VGM: | |
624 vgm_init(player, opts); | |
625 break; | |
626 } | |
627 | |
628 return player; | |
629 } |