looper/backends/playback/zsm/zsm_backend.cpp
2024-09-06 10:06:32 -07:00

219 lines
6.8 KiB
C++

#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 <exception>
#include <filesystem>
#include "file_backend.hpp"
#include <stddef.h>
#include <string.h>
#include <file_backend.hpp>
void ZsmBackend::load(const char *filename) {
spec.format = AUDIO_S16SYS;
file = open_file(filename);
char magic[2];
file->read(magic, 2);
if (magic[0] != 0x7a || magic[1] != 0x6d) {
throw std::exception();
}
uint8_t version;
file->read(&version, 1);
uint8_t loop_point[3];
file->read(loop_point, 3);
this->loop_point = loop_point[0] | ((uint32_t)(loop_point[1]) << 8) | ((uint32_t)(loop_point[2]) << 16);
file->read(loop_point, 3);
this->pcm_offset = loop_point[0] | ((uint32_t)(loop_point[1]) << 8) | ((uint32_t)(loop_point[2]) << 16);
file->read(&fm_mask, 1);
file->read(loop_point, 2);
this->psg_channel_mask = loop_point[0] | ((uint16_t)(loop_point[1]) << 8);
file->read(loop_point, 2);
this->tick_rate = loop_point[0] | ((uint16_t)(loop_point[1]) << 8);
file->read(loop_point, 2); // 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();
audio_callback(nullptr, (Uint8*)(buf), sample_type_len * maxlen);
return maxlen * sample_type_len;
}
ZsmCommand ZsmBackend::get_command() {
ZsmCommandId cmdid;
uint8_t cmd_byte;
file->read(&cmd_byte, 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);
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);
}
} else if (cmdid == ExtCmd) {
uint8_t ext_cmd_byte;
file->read(&ext_cmd_byte, 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);
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;
}