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, &current_cycle, wait_time);
158			break;
159		}
160		case CMD_WAIT_60:
161			vgm_wait(&y_context, &p_context, &current_cycle, 735 * mclks_sample);
162			break;
163		case CMD_WAIT_50:
164			vgm_wait(&y_context, &p_context, &current_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, &current_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, &current_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}