#pragma once #include "playback_backend.hpp" #include #include "x16emu/ymglue.h" #include #include #include #include #include #include #include #include #include extern "C" { #include "x16emu/audio.h" #include "x16emu/vera_pcm.h" #include "x16emu/vera_psg.h" #include "x16emu/ymglue.h" } #include "file_backend.hpp" #define YM_FREQ (3579545/64) #define PSG_FREQ (AUDIO_SAMPLERATE) enum ZsmCommandId { PsgWrite, ExtCmd, FmWrite, ZsmEOF, Delay }; struct reg_pair { uint8_t reg; uint8_t val; }; struct ZsmCommand { ZsmCommandId id; union { struct { uint8_t reg; uint8_t val; } psg_write; struct { uint8_t channel; uint8_t bytes; union { uint8_t *pcm; struct { uint8_t chip_id; uint8_t writes; uint8_t *write_bytes; } expansion; uint8_t *sync; uint8_t *custom; }; } extcmd; struct { uint8_t len; reg_pair *regs; } fm_write; uint8_t delay; }; ~ZsmCommand(); }; class ZsmBackend : public PlaybackBackend { File *file; Fifo audio_buf; DynPtr psg_buf; DynPtr pcm_buf; DynPtr out_buf; DynPtr ym_buf; DynPtr ym_resample_buf; bool ym_recorded = false; uint8_t ym_data[256]; uint32_t loop_rem; uint32_t pcm_data_offs; uint8_t pcm_data_instruments; uint32_t loop; bool islooped; uint32_t remain; uint32_t cur; uint32_t pcm_loop_point; uint32_t rem_point; SDL_AudioStream *fm_stream; int16_t combine_audio(int16_t a, int16_t b) { return (int16_t)((((int32_t)a) + ((int32_t)b)) >> 1); } void audio_step(size_t samples) { while (((pcm_read_ctrl() & 0x80) == 0) && remain > 0) { remain--; size_t oldpos = file->get_pos(); file->seek((cur++) + pcm_data_offs, SeekType::SET); uint8_t sample; file->read(&sample, 1, 1); pcm_write_fifo(sample); if (remain == 0) { if (islooped) { cur = loop; remain = loop_rem; } } file->seek(oldpos, SeekType::SET); } if (samples == 0) return; samples *= 2; int16_t *psg_ptr = psg_buf.get_item_sized(samples); int16_t *pcm_ptr = pcm_buf.get_item_sized(samples); psg_render(psg_ptr, samples / 2); pcm_render(pcm_ptr, samples / 2); int16_t *out_ptr = out_buf.get_item_sized(samples); // The exact amount of samples needed for the stream. double ratio = ((double)YM_FREQ) / ((double)PSG_FREQ); size_t needed_samples = ((size_t)std::floor(samples * ratio)) / 2; int16_t *ym_ptr = ym_buf.get_item_sized(needed_samples * 2); YM_stream_update(ym_ptr, needed_samples); assert(SDL_AudioStreamPut(fm_stream, ym_ptr, needed_samples * 2 * sizeof(int16_t)) == 0); while (SDL_AudioStreamAvailable(fm_stream) < ((samples + 2) * sizeof(int16_t))) { YM_stream_update(ym_ptr, 1); assert(SDL_AudioStreamPut(fm_stream, ym_ptr, 2 * sizeof(int16_t)) == 0); } int16_t *ym_resample_ptr = ym_resample_buf.get_item_sized(samples); ssize_t ym_resample_len = SDL_AudioStreamGet(fm_stream, ym_resample_ptr, (samples + 2) * sizeof(int16_t)); assert(ym_resample_len >= 0); ym_resample_len /= sizeof(int16_t); for (size_t i = 0; i < samples / 2; i++) { size_t j = i * 2; int32_t psg[2] = {psg_ptr[j], psg_ptr[j + 1]}; int32_t pcm[2] = {pcm_ptr[j], pcm_ptr[j + 1]}; int32_t vera[2] = {psg[0] + pcm[0], psg[1] + pcm[1]}; int32_t fm[2] = {ym_resample_ptr[j], ym_resample_ptr[j + 1]}; int16_t mix[2] = {(vera[0] + fm[0]) / 3, (vera[1] + fm[1]) / 3}; out_ptr[j++] = mix[0]; out_ptr[j++] = mix[1]; } audio_buf.push(out_ptr, samples); } inline void *reserve(size_t len) { return (void*)audio_buf.reserve(len); } inline size_t copy_out(void *buf, size_t len) { return audio_buf.pop((int16_t*)buf, len); } uint32_t loop_point; double loop_pos = 0.0; uint32_t pcm_offset; uint8_t fm_mask; uint16_t psg_channel_mask; uint16_t tick_rate; size_t music_data_start; size_t music_data_len; double ticks; ssize_t delayTicks = 0; double position; double cpuClocks = 0; inline double get_delay_per_frame() { return 1.0; } void tick(bool step = true); void add_clocks(double amount, bool step = true); void seek_internal(double position, bool loop = true); ZsmCommand get_command(); public: uint64_t get_min_samples() override; std::optional get_max_samples() override; inline std::string get_id() override { return "zsm"; } inline std::string get_name() override { return "ZSM player"; } void seek(double position) override; void load(const char *filename) override; void switch_stream(int idx) override; void cleanup() override; int get_stream_idx() override; size_t render(void *buf, size_t maxlen) override; double get_position() override; inline double get_length() override { return length; } inline ~ZsmBackend() override { } };