summaryrefslogtreecommitdiffstats
path: root/lib/rbcodec/codecs/libayumi/ayumi_render.c
diff options
context:
space:
mode:
authorSolomon Peachy <pizza@shaftnet.org>2020-09-26 17:19:07 -0400
committerSolomon Peachy <pizza@shaftnet.org>2020-10-09 11:39:25 -0400
commit4231c2c83f2b5331e3e38b10a308ee3752315f9c (patch)
tree1e14867e9c9f0d7b778e7c4c18103a7cbd491794 /lib/rbcodec/codecs/libayumi/ayumi_render.c
parent278522f8118bd2cfce065ec95f0a93ca53e3ca44 (diff)
downloadrockbox-4231c2c83f2b5331e3e38b10a308ee3752315f9c.tar.gz
rockbox-4231c2c83f2b5331e3e38b10a308ee3752315f9c.zip
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
Diffstat (limited to 'lib/rbcodec/codecs/libayumi/ayumi_render.c')
-rw-r--r--lib/rbcodec/codecs/libayumi/ayumi_render.c328
1 files changed, 328 insertions, 0 deletions
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 <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+
+#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;
+}