197 lines
6 KiB
C++
197 lines
6 KiB
C++
#pragma once
|
|
#include "playback_backend.hpp"
|
|
#include <omp.h>
|
|
#include "x16emu/ymglue.h"
|
|
#include <cstdint>
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <streambuf>
|
|
#include <vector>
|
|
#include <util.hpp>
|
|
#include <SDL.h>
|
|
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();
|
|
};
|
|
struct pcm_instrument {
|
|
uint8_t geom;
|
|
uint32_t loop_rem;
|
|
uint32_t loop;
|
|
bool islooped;
|
|
uint32_t remain;
|
|
uint8_t *data = nullptr;
|
|
inline ~pcm_instrument() {
|
|
if (data != nullptr) free((void*)data);
|
|
}
|
|
};
|
|
class ZsmBackend : public PlaybackBackend {
|
|
File *file;
|
|
Fifo<int16_t> 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;
|
|
std::vector<pcm_instrument*> instruments;
|
|
uint8_t *audio_sample = nullptr;
|
|
bool pcm_enable();
|
|
bool psg_enable();
|
|
bool fm_enable();
|
|
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) {
|
|
if (samples == 0) return;
|
|
while (remain != 0 && pcm_fifo_avail() < samples) {
|
|
if (pcm_read_rate() == 0) break;
|
|
if ((--remain) == 0) {
|
|
if (islooped) {
|
|
cur = loop;
|
|
remain = loop_rem;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
size_t oldpos = file->get_pos();
|
|
uint8_t sample = audio_sample[cur++];
|
|
pcm_write_fifo(sample);
|
|
}
|
|
samples *= 2;
|
|
int16_t *psg_ptr = psg_buf.get_item_sized<int16_t>(samples);
|
|
int16_t *pcm_ptr = pcm_buf.get_item_sized<int16_t>(samples);
|
|
psg_render(psg_ptr, samples / 2);
|
|
pcm_render(pcm_ptr, samples / 2);
|
|
int16_t *out_ptr = out_buf.get_item_sized<int16_t>(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<int16_t>(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<int16_t>(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;
|
|
int16_t psg[2] = {psg_ptr[j] >> 1, psg_ptr[j + 1] >> 1};
|
|
int16_t pcm[2] = {pcm_ptr[j] >> 1, pcm_ptr[j + 1] >> 1};
|
|
if (!pcm_enable()) memset(pcm, 0, sizeof(pcm));
|
|
if (!psg_enable()) memset(psg, 0, sizeof(psg));
|
|
int16_t vera[2] = {psg[0] + pcm[0], psg[1] + pcm[1]};
|
|
int16_t fm[2] = {ym_resample_ptr[j], ym_resample_ptr[j + 1]};
|
|
if (!fm_enable()) memset(fm, 0, sizeof(fm));
|
|
int16_t mix[2] = {vera[0] + (fm[0] >> 1), vera[1] + (fm[1] >> 1)};
|
|
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 seek_internal(double position, bool loop = true);
|
|
ZsmCommand get_command();
|
|
public:
|
|
uint64_t get_min_samples() override;
|
|
std::optional<uint64_t> 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 { }
|
|
};
|