#include "zsm_backend.hpp" extern "C" { #include "x16emu/audio.h" #include "x16emu/vera_pcm.h" #include "x16emu/vera_psg.h" #include "x16emu/ymglue.h" } #include #include #include "file_backend.hpp" #include #include #include void ZsmBackend::load(const char *filename) { spec.format = AUDIO_S16SYS; spec.samples = SAMPLES_PER_BUFFER; spec.channels = 2; spec.size = spec.samples * SAMPLE_BYTES; file = open_file(filename); char magic[2]; file->read(magic, 2, 1); if (magic[0] != 0x7a || magic[1] != 0x6d) { throw std::exception(); } uint8_t version; file->read(&version, 1, 1); uint8_t loop_point[3]; file->read(loop_point, 3, 1); this->loop_point = loop_point[0] | ((uint32_t)(loop_point[1]) << 8) | ((uint32_t)(loop_point[2]) << 16); file->read(loop_point, 3, 1); this->pcm_offset = loop_point[0] | ((uint32_t)(loop_point[1]) << 8) | ((uint32_t)(loop_point[2]) << 16); file->read(&fm_mask, 1, 1); file->read(loop_point, 2, 1); this->psg_channel_mask = loop_point[0] | ((uint16_t)(loop_point[1]) << 8); file->read(loop_point, 2, 1); this->tick_rate = loop_point[0] | ((uint16_t)(loop_point[1]) << 8); file->read(loop_point, 2, 1); // Reserved. music_data_start = file->get_pos(); while (true) { ZsmCommand cmd = get_command(); if (cmd.id == ZsmEOF) { break; } } music_data_len = file->get_pos(); switch_stream(0); } extern SDL_AudioSpec obtained; void ZsmBackend::switch_stream(int idx) { psg_reset(); audio_close(); audio_init(NULL, 16); spec = obtained; } void ZsmBackend::cleanup() { audio_close(); delete file; file = nullptr; } size_t ZsmBackend::render(void *buf, size_t maxlen) { size_t sample_type_len = size_of_sample_type(spec.format); maxlen /= sample_type_len; double prevTicks = ticks; double delta = (double)(maxlen) / (double)(spec.freq); double deltaTicks = delta / (double)(tick_rate); ticks += deltaTicks; double clocks = delta * (double)(8000000); for (size_t i = 0; i < (size_t)(ticks - prevTicks); i++) { double deltaClocks = (double)(tick_rate) * (double)(8000000); clocks -= deltaClocks; if (clocks < 0) { break; } while (true) { ZsmCommand cmd = get_command(); bool tick_end = false; if (delayTicks > 0.0) { delayTicks -= 1.0 / double(tick_rate); break; } switch (cmd.id) { case ZsmEOF: { seek_internal((double)(loop_point) / (double)(tick_rate)); } break; case PsgWrite: { psg_writereg(cmd.psg_write.reg, cmd.psg_write.val); } break; case FmWrite: { for (uint8_t i = 0; i < cmd.fm_write.len; i++) { YM_write_reg(cmd.fm_write.regs[i].reg, cmd.fm_write.regs[i].val); } } break; case Delay: { delayTicks = cmd.delay; tick_end = true; } break; case ExtCmd: { // Nothing handled yet. } break; } if (tick_end) break; } audio_step((int)(clocks)); } audio_render(); maxlen *= sample_type_len; if (audio_buf.size() < maxlen) { size_t oldlen = audio_buf.size(); audio_buf.resize(audio_buf.size() + spec.size); audio_callback(nullptr, (Uint8*)(audio_buf.data() + oldlen), spec.size); } memcpy(audio_buf.data(), buf, maxlen); memmove(audio_buf.data(), audio_buf.data() + maxlen, audio_buf.size() - maxlen); audio_buf.resize(audio_buf.size() - maxlen); return maxlen; } ZsmCommand ZsmBackend::get_command() { ZsmCommandId cmdid; uint8_t cmd_byte; file->read(&cmd_byte, 1, 1); if (cmd_byte == 0x80) { cmdid = ZsmEOF; } else { if ((cmd_byte & 0b11000000) == 0) { cmdid = PsgWrite; } else if ((cmd_byte & 0b11000000) == 0b01) { if (cmd_byte == 0b01000000) { cmdid = ExtCmd; } else { cmdid = FmWrite; } } else { cmdid = Delay; } } ZsmCommand output; output.id = cmdid; if (cmdid == ZsmEOF) { return output; } else if (cmdid == PsgWrite) { uint8_t value; file->read(&value, 1, 1); output.psg_write.reg = cmd_byte & 0x3F; output.psg_write.val = value; } else if (cmdid == FmWrite) { uint8_t value[2]; uint8_t pairs = cmd_byte & 0b111111; output.fm_write.len = pairs; output.fm_write.regs = (reg_pair*)malloc((sizeof(uint8_t)*2)*pairs); for (uint8_t i = 0; i < pairs; i++) { output.fm_write.regs[i].reg = value[0]; output.fm_write.regs[i].val = value[1]; file->read(value, 2, 1); } } else if (cmdid == ExtCmd) { uint8_t ext_cmd_byte; file->read(&ext_cmd_byte, 1, 1); uint8_t bytes = ext_cmd_byte & 0x3F; uint8_t ch = ext_cmd_byte >> 6; output.extcmd.channel = ch; output.extcmd.bytes = bytes; if (ch == 1) { output.extcmd.expansion.write_bytes = (uint8_t*)malloc(bytes - 2); } else { output.extcmd.pcm = (uint8_t*)malloc(bytes); // Handles all other cases due to them being in a union, and each having the same type. } for (size_t i = 0; i < bytes; i++) { uint8_t byte; file->read(&byte, 1, 1); switch (ch) { case 0: { output.extcmd.pcm[i] = byte; } break; case 1: { if (i == 0) { output.extcmd.expansion.chip_id = byte; } else if (i == 1) { output.extcmd.expansion.writes = byte; } else { output.extcmd.expansion.write_bytes[i - 2] = byte; } } break; case 2: { output.extcmd.sync[i] = byte; } break; case 3: { output.extcmd.custom[i] = byte; } break; } } } return output; } ZsmCommand::~ZsmCommand() { switch (id) { case ExtCmd: { if (extcmd.channel == 1) { free(extcmd.expansion.write_bytes); } else { free(extcmd.pcm); } } break; case FmWrite: { free(fm_write.regs); } } } void ZsmBackend::seek_internal(double position) { double ticks = 0; file->seek(music_data_start, SeekType::SET); while (ticks < position) { ZsmCommand cmd = get_command(); if (cmd.id == ZsmEOF) { file->seek(music_data_start, SeekType::SET); break; } else if (cmd.id == Delay) { ticks += (double)(cmd.delay) / (double)(tick_rate); } } this->position = position; } void ZsmBackend::seek(double position) { seek_internal(position); } double ZsmBackend::get_position() { return position; } int ZsmBackend::get_stream_idx() { return 0; }