From 4231c2c83f2b5331e3e38b10a308ee3752315f9c Mon Sep 17 00:00:00 2001 From: Solomon Peachy Date: Sat, 26 Sep 2020 17:19:07 -0400 Subject: codecs: Add support for the 'VTX' ZX Spectrum chiptunes format. This codec requires floating point. Original author: Peter Sovietov Ported to Rockbox: Roman Skylarov Further integration and bugfixes: Solomon Peachy Change-Id: I781ecd3592dfcdbbc694063334350342534f1d6c --- lib/rbcodec/codecs/libayumi/ayumi_render.c | 328 +++++++++++++++++++++++++++++ 1 file changed, 328 insertions(+) create mode 100644 lib/rbcodec/codecs/libayumi/ayumi_render.c (limited to 'lib/rbcodec/codecs/libayumi/ayumi_render.c') diff --git a/lib/rbcodec/codecs/libayumi/ayumi_render.c b/lib/rbcodec/codecs/libayumi/ayumi_render.c new file mode 100644 index 0000000000..9bf0204597 --- /dev/null +++ b/lib/rbcodec/codecs/libayumi/ayumi_render.c @@ -0,0 +1,328 @@ +#include "ayumi_render.h" + +#include +#include +#include + +#include "ayumi.h" +#include "lzh.h" +#include "codeclib.h" + +ayumi_render_t ay; + +/* default panning settings, 7 stereo types */ +static const double default_pan[7][3] = { +/* A, B, C */ + + {0.50, 0.50, 0.50}, /* MONO */ + {0.10, 0.50, 0.90}, /* ABC */ + {0.10, 0.90, 0.50}, /* ACB */ + {0.50, 0.10, 0.90}, /* BAC */ + {0.90, 0.10, 0.50}, /* BCA */ + {0.50, 0.90, 0.10}, /* CAB */ + {0.90, 0.50, 0.10} /* CBA */ +}; + +static const char *chiptype_name[3] = { + "AY-3-8910", + "YM2149", + "Unknown" +}; + +static const char *layout_name[9] = { + "Mono", + "ABC Stereo", + "ACB Stereo", + "BAC Stereo", + "BCA Stereo", + "CAB Stereo", + "CBA Stereo", + "Custom", + "Unknown" +}; + +/* reader */ + +#define VTX_STRING_MAX 254 + +typedef struct { + uchar *ptr; + uint size; +} reader_t; + +reader_t reader; + +void Reader_Init(void *pBlock) { + reader.ptr = (uchar *) pBlock; + reader.size = 0; +} + +uint Reader_ReadByte(void) { + uint res; + res = *reader.ptr++; + reader.size += 1; + return res; +} + +uint Reader_ReadWord(void) { + uint res; + res = *reader.ptr++; + res += *reader.ptr++ << 8; + reader.size += 2; + return res; +} + +uint Reader_ReadDWord(void) { + uint res; + res = *reader.ptr++; + res += *reader.ptr++ << 8; + res += *reader.ptr++ << 16; + res += *reader.ptr++ << 24; + reader.size += 4; + return res; +} + +char *Reader_ReadString(void) { + char *res; + if (reader.ptr == NULL) + return NULL; + int len = strlen((const char *)reader.ptr); + if (len > VTX_STRING_MAX) + return NULL; + res = reader.ptr; + reader.ptr += len + 1; + reader.size += len + 1; + return res; +} + +uchar *Reader_GetPtr(void) { + return reader.ptr; +} + +uint Reader_GetSize(void) { + return reader.size; +} + +/* ayumi_render */ + +static int AyumiRender_LoadInfo(void *pBlock, uint size) +{ + if (size < 20) + return 0; + + Reader_Init(pBlock); + + uint hdr = Reader_ReadWord(); + + if (hdr == 0x7961) + ay.info.chiptype = VTX_CHIP_AY; + else if (hdr == 0x6d79) + ay.info.chiptype = VTX_CHIP_YM; + else { + return 0; + } + + ay.info.layout = (vtx_layout_t) + Reader_ReadByte(); + ay.info.loop = Reader_ReadWord(); + ay.info.chipfreq = Reader_ReadDWord(); + ay.info.playerfreq = Reader_ReadByte(); + ay.info.year = Reader_ReadWord(); + ay.data.regdata_size = Reader_ReadDWord(); + ay.info.frames = ay.data.regdata_size / 14; + ay.info.title = Reader_ReadString(); + ay.info.author = Reader_ReadString(); + ay.info.from = Reader_ReadString(); + ay.info.tracker = Reader_ReadString(); + ay.info.comment = Reader_ReadString(); + + ay.data.lzhdata_size = size - Reader_GetSize(); + ay.data.lzhdata = (uchar *)codec_malloc(ay.data.lzhdata_size); + memcpy(ay.data.lzhdata, Reader_GetPtr(), ay.data.lzhdata_size); + + return 1; +} + +int AyumiRender_LoadFile(void *pBlock, uint size) +{ + if (!AyumiRender_LoadInfo(pBlock, size)) + return 0; + + ay.data.regdata = (uchar *)codec_malloc(ay.data.regdata_size); + if (ay.data.regdata == NULL) + return 0; + + int bRet = LzUnpack(ay.data.lzhdata, ay.data.lzhdata_size, + ay.data.regdata, ay.data.regdata_size); + + if (bRet) + return 0; + + return 1; +} + +const char *AyumiRender_GetChipTypeName(vtx_chiptype_t chiptype) +{ + if (chiptype > VTX_CHIP_YM) + chiptype = (vtx_chiptype_t) (VTX_CHIP_YM + 1); + return chiptype_name[chiptype]; +} + +const char *AyumiRender_GetLayoutName(vtx_layout_t layout) +{ + if (layout > VTX_LAYOUT_CUSTOM) + layout = (vtx_layout_t) (VTX_LAYOUT_CUSTOM + 1); + return layout_name[layout]; +} + +int AyumiRender_AyInit(vtx_chiptype_t chiptype, uint samplerate, + uint chipfreq, double playerfreq, uint dcfilter) +{ + if (chiptype > VTX_CHIP_YM) + return 0; + if ((samplerate < 8000) || (samplerate > 768000)) + return 0; + if ((chipfreq < 1000000) || (chipfreq > 2000000)) + return 0; + if ((playerfreq < 1) || (playerfreq > 100)) + return 0; + + ay.is_ym = (chiptype == VTX_CHIP_YM) ? 1 : 0; + ay.clock_rate = chipfreq; + ay.sr = samplerate; + + ay.dc_filter_on = dcfilter ? 1 : 0; + + ay.frame = 0; + ay.isr_counter = 1; + ay.isr_step = playerfreq / samplerate; + + if (!ayumi_configure(&ay.ay, ay.is_ym, ay.clock_rate, ay.sr)) + return 0; + + return 1; +} + +int AyumiRender_SetLayout(vtx_layout_t layout, uint eqpower) +{ + if (layout > VTX_LAYOUT_CUSTOM) + return 0; + ay.is_eqp = eqpower ? 1 : 0; + + switch (layout) { + case VTX_LAYOUT_MONO: + case VTX_LAYOUT_ABC: + case VTX_LAYOUT_ACB: + case VTX_LAYOUT_BAC: + case VTX_LAYOUT_BCA: + case VTX_LAYOUT_CAB: + case VTX_LAYOUT_CBA: + for (int i = 0; i < 3; i++) + ay.pan[i] = default_pan[layout][i]; + break; + case VTX_LAYOUT_CUSTOM: + for (int i = 0; i < 3; i++) + ay.pan[i] = 0; // no custom layout + break; + default: + return 0; + } + + for (int i = 0; i < 3; i++) + ayumi_set_pan(&ay.ay, i, ay.pan[i], ay.is_eqp); + + return 1; +} + +uint AyumiRender_GetPos(void) +{ + return ay.frame; +} + +uint AyumiRender_GetMaxPos(void) +{ + return ay.info.frames; +} + +static void AyumiRender_UpdateAyumiState(void) +{ + int r[16]; + + if (ay.frame < ay.info.frames) { + uchar *ptr = ay.data.regdata + ay.frame; + for (int n = 0; n < 14; n++) { + r[n] = *ptr; + ptr += ay.info.frames; + } + } else { + for (int n = 0; n < 14; n++) { + r[n] = 0; + } + } + + ayumi_set_tone(&ay.ay, 0, (r[1] << 8) | r[0]); + ayumi_set_tone(&ay.ay, 1, (r[3] << 8) | r[2]); + ayumi_set_tone(&ay.ay, 2, (r[5] << 8) | r[4]); + ayumi_set_noise(&ay.ay, r[6]); + ayumi_set_mixer(&ay.ay, 0, r[7] & 1, (r[7] >> 3) & 1, r[8] >> 4); + ayumi_set_mixer(&ay.ay, 1, (r[7] >> 1) & 1, (r[7] >> 4) & 1, r[9] >> 4); + ayumi_set_mixer(&ay.ay, 2, (r[7] >> 2) & 1, (r[7] >> 5) & 1, r[10] >> 4); + ayumi_set_volume(&ay.ay, 0, r[8] & 0xf); + ayumi_set_volume(&ay.ay, 1, r[9] & 0xf); + ayumi_set_volume(&ay.ay, 2, r[10] & 0xf); + ayumi_set_envelope(&ay.ay, (r[12] << 8) | r[11]); + if (r[13] != 255) { + ayumi_set_envelope_shape(&ay.ay, r[13]); + } +} + +int AyumiRender_Seek(ulong nSample) +{ + ulong samples = 0; + + ay.frame = 0; + ay.isr_counter = 1; + + ayumi_configure(&ay.ay, ay.is_ym, ay.clock_rate, ay.sr); + + for (int i = 0; i < 3; i++) + ayumi_set_pan(&ay.ay, i, ay.pan[i], ay.is_eqp); + + while (samples < nSample) { + ay.isr_counter += ay.isr_step; + if (ay.isr_counter >= 1) { + ay.isr_counter -= 1; + AyumiRender_UpdateAyumiState(); + ay.frame += 1; + } + ayumi_seek(&ay.ay); + samples++; + } + + return 1; +} + +ulong AyumiRender_AySynth(void *pBuffer, ulong nSamples) +{ + ulong samples = 0; + short *out = (int16_t *) pBuffer; + + for (ulong i = 0; i < nSamples; i++) { + ay.isr_counter += ay.isr_step; + if (ay.isr_counter >= 1) { + ay.isr_counter -= 1; + AyumiRender_UpdateAyumiState(); + ay.frame += 1; + } + ayumi_process(&ay.ay); + if (ay.dc_filter_on) { + ayumi_remove_dc(&ay.ay); + } + out[0] = (int16_t)(ay.ay.left * 16383); + out[1] = (int16_t)(ay.ay.right * 16383); + out += 2; + samples++; + } + + return samples; +} -- cgit v1.2.3