main
vgmplay.c
1/*
2 Copyright 2013 Michael Pavone
3 This file is part of BlastEm.
4 BlastEm is free software distributed under the terms of the GNU General Public License version 3 or greater. See COPYING for full license text.
5*/
6#include "render.h"
7#include "ym2612.h"
8#include "psg.h"
9#include "config.h"
10#include "util.h"
11#include <stdint.h>
12#include <stdio.h>
13#include <stdlib.h>
14#include <string.h>
15#include "vgm.h"
16
17#define MCLKS_NTSC 53693175
18#define MCLKS_PAL 53203395
19
20#define MCLKS_PER_68K 7
21#define MCLKS_PER_YM MCLKS_PER_68K
22#define MCLKS_PER_Z80 15
23#define MCLKS_PER_PSG (MCLKS_PER_Z80*16)
24
25void handle_keydown(int keycode)
26{
27}
28
29void handle_keyup(int keycode)
30{
31}
32
33void handle_joydown(int joystick, int button)
34{
35}
36
37void handle_joyup(int joystick, int button)
38{
39}
40
41void handle_joy_dpad(int joystick, int dpadnum, uint8_t value)
42{
43}
44
45void handle_joy_axis(int joystick, int axis, int16_t value)
46{
47}
48
49void handle_joy_added(int joystick)
50{
51}
52
53void handle_mouse_moved(int mouse, uint16_t x, uint16_t y, int16_t deltax, int16_t deltay)
54{
55}
56
57void handle_mousedown(int mouse, int button)
58{
59}
60
61void handle_mouseup(int mouse, int button)
62{
63}
64
65int headless = 0;
66
67#define CYCLE_LIMIT MCLKS_NTSC/60
68#define MAX_SOUND_CYCLES 100000
69tern_node * config;
70
71void vgm_wait(ym2612_context * y_context, psg_context * p_context, uint32_t * current_cycle, uint32_t cycles)
72{
73 while (cycles > MAX_SOUND_CYCLES)
74 {
75 vgm_wait(y_context, p_context, current_cycle, MAX_SOUND_CYCLES);
76 cycles -= MAX_SOUND_CYCLES;
77 }
78 *current_cycle += cycles;
79 psg_run(p_context, *current_cycle);
80 ym_run(y_context, *current_cycle);
81
82 if (*current_cycle > CYCLE_LIMIT) {
83 *current_cycle -= CYCLE_LIMIT;
84 p_context->cycles -= CYCLE_LIMIT;
85 y_context->current_cycle -= CYCLE_LIMIT;
86 process_events();
87 }
88}
89
90int main(int argc, char ** argv)
91{
92 set_exe_str(argv[0]);
93 data_block *blocks = NULL;
94 data_block *seek_block = NULL;
95 uint32_t seek_offset;
96 uint32_t block_offset;
97
98 uint32_t fps = 60;
99 config = load_config(argv[0]);
100 render_init(320, 240, "vgm play", 0);
101
102 uint32_t opts = 0;
103 if (argc >= 3 && !strcmp(argv[2], "-y")) {
104 opts |= YM_OPT_WAVE_LOG;
105 }
106
107 char * lowpass_cutoff_str = tern_find_path(config, "audio\0lowpass_cutoff\0", TVAL_PTR).ptrval;
108 uint32_t lowpass_cutoff = lowpass_cutoff_str ? atoi(lowpass_cutoff_str) : 3390;
109
110 ym2612_context y_context;
111 ym_init(&y_context, MCLKS_NTSC, MCLKS_PER_YM, opts);
112
113 psg_context p_context;
114 psg_init(&p_context, MCLKS_NTSC, MCLKS_PER_PSG);
115
116 FILE * f = fopen(argv[1], "rb");
117 vgm_header header;
118 fread(&header, sizeof(header), 1, f);
119 if (header.version < 0x150 || !header.data_offset) {
120 header.data_offset = 0xC;
121 }
122 fseek(f, header.data_offset + 0x34, SEEK_SET);
123 uint32_t data_size = header.eof_offset + 4 - (header.data_offset + 0x34);
124 uint8_t * data = malloc(data_size);
125 fread(data, 1, data_size, f);
126 fclose(f);
127
128 uint32_t mclks_sample = MCLKS_NTSC / 44100;
129 uint32_t loop_count = 2;
130
131 uint8_t * end = data + data_size;
132 uint8_t * cur = data;
133 uint32_t current_cycle = 0;
134 while (cur < end) {
135 uint8_t cmd = *(cur++);
136 switch(cmd)
137 {
138 case CMD_PSG_STEREO:
139 //ignore for now
140 cur++;
141 break;
142 case CMD_PSG:
143 psg_write(&p_context, *(cur++));
144 break;
145 case CMD_YM2612_0:
146 ym_address_write_part1(&y_context, *(cur++));
147 ym_data_write(&y_context, *(cur++));
148 break;
149 case CMD_YM2612_1:
150 ym_address_write_part2(&y_context, *(cur++));
151 ym_data_write(&y_context, *(cur++));
152 break;
153 case CMD_WAIT: {
154 uint32_t wait_time = *(cur++);
155 wait_time |= *(cur++) << 8;
156 wait_time *= mclks_sample;
157 vgm_wait(&y_context, &p_context, ¤t_cycle, wait_time);
158 break;
159 }
160 case CMD_WAIT_60:
161 vgm_wait(&y_context, &p_context, ¤t_cycle, 735 * mclks_sample);
162 break;
163 case CMD_WAIT_50:
164 vgm_wait(&y_context, &p_context, ¤t_cycle, 882 * mclks_sample);
165 break;
166 case CMD_END:
167 if (header.loop_offset && --loop_count) {
168 cur = data + header.loop_offset + 0x1C - (header.data_offset + 0x34);
169 } else {
170 //TODO: fade out
171 return 0;
172 }
173 break;
174 case CMD_DATA: {
175 cur++; //skip compat command
176 uint8_t data_type = *(cur++);
177 uint32_t data_size = *(cur++);
178 data_size |= *(cur++) << 8;
179 data_size |= *(cur++) << 16;
180 data_size |= *(cur++) << 24;
181 if (data_type == DATA_YM2612_PCM) {
182 data_block ** curblock = &blocks;
183 while(*curblock)
184 {
185 curblock = &((*curblock)->next);
186 }
187 *curblock = malloc(sizeof(data_block));
188 (*curblock)->size = data_size;
189 (*curblock)->type = data_type;
190 (*curblock)->data = cur;
191 (*curblock)->next = NULL;
192 } else {
193 fprintf(stderr, "Skipping data block with unrecognized type %X\n", data_type);
194 }
195 cur += data_size;
196 break;
197 }
198 case CMD_DATA_SEEK: {
199 uint32_t new_offset = *(cur++);
200 new_offset |= *(cur++) << 8;
201 new_offset |= *(cur++) << 16;
202 new_offset |= *(cur++) << 24;
203 if (!seek_block || new_offset < seek_offset) {
204 seek_block = blocks;
205 seek_offset = 0;
206 block_offset = 0;
207 }
208 while (seek_block && (seek_offset - block_offset + seek_block->size) < new_offset)
209 {
210 seek_offset += seek_block->size - block_offset;
211 seek_block = seek_block->next;
212 block_offset = 0;
213 }
214 block_offset += new_offset-seek_offset;
215 seek_offset = new_offset;
216 break;
217 }
218
219 default:
220 if (cmd >= CMD_WAIT_SHORT && cmd < (CMD_WAIT_SHORT + 0x10)) {
221 uint32_t wait_time = (cmd & 0xF) + 1;
222 wait_time *= mclks_sample;
223 vgm_wait(&y_context, &p_context, ¤t_cycle, wait_time);
224 } else if (cmd >= CMD_YM2612_DAC && cmd < CMD_DAC_STREAM_SETUP) {
225 if (seek_block) {
226 ym_address_write_part1(&y_context, 0x2A);
227 ym_data_write(&y_context, seek_block->data[block_offset++]);
228 seek_offset++;
229 if (block_offset > seek_block->size) {
230 seek_block = seek_block->next;
231 block_offset = 0;
232 }
233 } else {
234 fputs("Encountered DAC write command but data seek pointer is invalid!\n", stderr);
235 }
236 uint32_t wait_time = (cmd & 0xF);
237 if (wait_time)
238 {
239 wait_time *= mclks_sample;
240 vgm_wait(&y_context, &p_context, ¤t_cycle, wait_time);
241 }
242 } else {
243 fatal_error("unimplemented command: %X at offset %X\n", cmd, (unsigned int)(cur - data - 1));
244 }
245 }
246 }
247 return 0;
248}