2024-08-08 13:12:37 -07:00
|
|
|
#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) {
|
2024-09-28 10:31:06 -07:00
|
|
|
memset(&spec, 0, sizeof(spec));
|
2024-08-08 13:12:37 -07:00
|
|
|
spec.format = AUDIO_S16SYS;
|
2024-09-16 15:05:53 -07:00
|
|
|
spec.samples = SAMPLES_PER_BUFFER;
|
|
|
|
spec.channels = 2;
|
2024-09-28 10:31:06 -07:00
|
|
|
spec.freq = AUDIO_SAMPLERATE;
|
|
|
|
spec.size = SAMPLES_PER_BUFFER * SAMPLE_BYTES;
|
2024-08-08 13:12:37 -07:00
|
|
|
file = open_file(filename);
|
|
|
|
char magic[2];
|
2024-09-16 15:05:53 -07:00
|
|
|
file->read(magic, 2, 1);
|
2024-08-08 13:12:37 -07:00
|
|
|
if (magic[0] != 0x7a || magic[1] != 0x6d) {
|
|
|
|
throw std::exception();
|
|
|
|
}
|
|
|
|
uint8_t version;
|
2024-09-16 15:05:53 -07:00
|
|
|
file->read(&version, 1, 1);
|
2024-08-08 13:12:37 -07:00
|
|
|
uint8_t loop_point[3];
|
2024-09-16 15:05:53 -07:00
|
|
|
file->read(loop_point, 3, 1);
|
2024-08-08 13:12:37 -07:00
|
|
|
this->loop_point = loop_point[0] | ((uint32_t)(loop_point[1]) << 8) | ((uint32_t)(loop_point[2]) << 16);
|
2024-09-16 15:05:53 -07:00
|
|
|
file->read(loop_point, 3, 1);
|
2024-08-08 13:12:37 -07:00
|
|
|
this->pcm_offset = loop_point[0] | ((uint32_t)(loop_point[1]) << 8) | ((uint32_t)(loop_point[2]) << 16);
|
2024-09-16 15:05:53 -07:00
|
|
|
file->read(&fm_mask, 1, 1);
|
|
|
|
file->read(loop_point, 2, 1);
|
2024-08-08 13:12:37 -07:00
|
|
|
this->psg_channel_mask = loop_point[0] | ((uint16_t)(loop_point[1]) << 8);
|
2024-09-16 15:05:53 -07:00
|
|
|
file->read(loop_point, 2, 1);
|
2024-08-08 13:12:37 -07:00
|
|
|
this->tick_rate = loop_point[0] | ((uint16_t)(loop_point[1]) << 8);
|
2024-09-16 15:05:53 -07:00
|
|
|
file->read(loop_point, 2, 1); // Reserved.
|
2024-08-08 13:12:37 -07:00
|
|
|
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();
|
2024-09-28 10:31:06 -07:00
|
|
|
audio_init(16);
|
2024-08-08 13:12:37 -07:00
|
|
|
spec = obtained;
|
2024-09-28 10:31:06 -07:00
|
|
|
spec.size = SAMPLES_PER_BUFFER * SAMPLE_BYTES;
|
2024-08-08 13:12:37 -07:00
|
|
|
}
|
|
|
|
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));
|
|
|
|
}
|
2024-09-16 15:05:53 -07:00
|
|
|
maxlen *= sample_type_len;
|
2024-09-28 10:31:06 -07:00
|
|
|
if (audio_buf.size() < maxlen || audio_buf.size() == 0) {
|
|
|
|
size_t oldlen = audio_buf.size();
|
|
|
|
uint32_t diff = SAMPLES_PER_BUFFER * SAMPLE_BYTES;
|
|
|
|
audio_buf.resize(audio_buf.size() + diff);
|
|
|
|
audio_callback(nullptr, (Uint8*)(audio_buf.data() + oldlen), diff);
|
|
|
|
}
|
|
|
|
memcpy(buf, audio_buf.data(), maxlen);
|
|
|
|
if (audio_buf.size() - maxlen != 0) {
|
|
|
|
memmove(audio_buf.data(), audio_buf.data() + maxlen, audio_buf.size() - maxlen);
|
2024-09-16 15:05:53 -07:00
|
|
|
}
|
|
|
|
audio_buf.resize(audio_buf.size() - maxlen);
|
|
|
|
return maxlen;
|
2024-08-08 13:12:37 -07:00
|
|
|
}
|
|
|
|
ZsmCommand ZsmBackend::get_command() {
|
|
|
|
ZsmCommandId cmdid;
|
|
|
|
uint8_t cmd_byte;
|
2024-09-16 15:05:53 -07:00
|
|
|
file->read(&cmd_byte, 1, 1);
|
2024-08-08 13:12:37 -07:00
|
|
|
if (cmd_byte == 0x80) {
|
|
|
|
cmdid = ZsmEOF;
|
|
|
|
} else {
|
2024-09-28 10:31:06 -07:00
|
|
|
if ((cmd_byte >> 6) == 0) {
|
2024-08-08 13:12:37 -07:00
|
|
|
cmdid = PsgWrite;
|
2024-09-28 10:31:06 -07:00
|
|
|
} else if ((cmd_byte >> 6) == 0b01) {
|
2024-08-08 13:12:37 -07:00
|
|
|
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;
|
2024-09-16 15:05:53 -07:00
|
|
|
file->read(&value, 1, 1);
|
2024-08-08 13:12:37 -07:00
|
|
|
output.psg_write.reg = cmd_byte & 0x3F;
|
|
|
|
output.psg_write.val = value;
|
|
|
|
} else if (cmdid == FmWrite) {
|
2024-09-28 10:31:06 -07:00
|
|
|
uint16_t _value;
|
|
|
|
uint8_t *value = (uint8_t*)(void*)(&_value);
|
2024-08-08 13:12:37 -07:00
|
|
|
uint8_t pairs = cmd_byte & 0b111111;
|
|
|
|
output.fm_write.len = pairs;
|
2024-09-28 10:31:06 -07:00
|
|
|
output.fm_write.regs = (reg_pair*)malloc((sizeof(reg_pair))*pairs);
|
2024-08-08 13:12:37 -07:00
|
|
|
for (uint8_t i = 0; i < pairs; i++) {
|
2024-09-28 10:31:06 -07:00
|
|
|
file->read(value, 2, 1);
|
2024-08-08 13:12:37 -07:00
|
|
|
output.fm_write.regs[i].reg = value[0];
|
|
|
|
output.fm_write.regs[i].val = value[1];
|
|
|
|
}
|
|
|
|
} else if (cmdid == ExtCmd) {
|
|
|
|
uint8_t ext_cmd_byte;
|
2024-09-16 15:05:53 -07:00
|
|
|
file->read(&ext_cmd_byte, 1, 1);
|
2024-08-08 13:12:37 -07:00
|
|
|
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) {
|
2024-09-28 10:31:06 -07:00
|
|
|
output.extcmd.expansion.write_bytes = NULL;
|
2024-08-08 13:12:37 -07:00
|
|
|
} 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;
|
2024-09-16 15:05:53 -07:00
|
|
|
file->read(&byte, 1, 1);
|
2024-08-08 13:12:37 -07:00
|
|
|
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;
|
2024-09-28 10:31:06 -07:00
|
|
|
output.extcmd.expansion.write_bytes = (uint8_t*)malloc(byte);
|
2024-08-08 13:12:37 -07:00
|
|
|
} 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) {
|
2024-09-28 10:31:06 -07:00
|
|
|
if (extcmd.expansion.write_bytes != NULL) {
|
|
|
|
free(extcmd.expansion.write_bytes);
|
|
|
|
}
|
2024-08-08 13:12:37 -07:00
|
|
|
} 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;
|
|
|
|
}
|