commit 9b92f7b8e2fe2b7a63656663816360eac37ad003
parent 0e22bb2efeaac9fa47116fc5bea20c8865d32053
Author: Devine Lu Linvega <aliceffekt@gmail.com>
Date: Sat, 25 Jan 2025 16:31:38 -0800
Rolled back audio device to original implementation
Diffstat:
4 files changed, 127 insertions(+), 327 deletions(-)
diff --git a/projects/software/piano.tal b/projects/software/piano.tal
@@ -91,17 +91,10 @@ BRK
#1000
&loop
.adsr-view/x2 LDZ2 #003a SUB2 .Screen/x DEO2
- ( left )
- #10 OVR SUB
- #00 .Audio0/output DEI
- #00 .Audio0/volume DEI #04 SFT
- MUL2 #08 SFT2 NIP LTH .Screen/pixel DEO
+ #10 OVR SUB .Audio0/output DEI
+ DUP2 #0f AND LTH .Screen/pixel DEO
.Screen/x DEI2k INC2 INC2 ROT DEO2
- ( right )
- #10 OVR SUB
- #00 .Audio0/output DEI
- #00 .Audio0/volume DEI #0f AND
- MUL2 #08 SFT2 NIP LTH .Screen/pixel DEO
+ #04 SFT LTH .Screen/pixel DEO
.Screen/y DEI2k INC2 INC2 ROT DEO2
INC GTHk ?&loop
POP2
@@ -556,5 +549,4 @@ JMP2r
2729 2b2e 3032 3537 3a3d 3f42 4547 4a4d
5053 5659 5c5f 6265 686b 6e71 7477 7a7d ]
&end
-( pad ) [ 8080 8080 ]
-
+( pad ) [ 8080 8080 ]
+\ No newline at end of file
diff --git a/src/devices/audio.c b/src/devices/audio.c
@@ -1,10 +1,8 @@
#include "../uxn.h"
#include "audio.h"
-#include <stdbool.h>
-#include <string.h>
/*
-Copyright (c) 2021-2023 Devine Lu Linvega, Andrew Alderwick, Bad Diode
+Copyright (c) 2021-2023 Devine Lu Linvega, Andrew Alderwick
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -14,333 +12,114 @@ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE.
*/
-#define SOUND_TIMER (AUDIO_BUFSIZE / SAMPLE_FREQUENCY * 1000.0f)
-#define XFADE_SAMPLES 100
-#define INTERPOL_METHOD 1
+#define NOTE_PERIOD (SAMPLE_FREQUENCY * 0x4000 / 11025)
+#define ADSR_STEP (SAMPLE_FREQUENCY / 0xf)
-typedef enum EnvStage {
- ENV_ATTACK = (1 << 0),
- ENV_DECAY = (1 << 1),
- ENV_SUSTAIN = (1 << 2),
- ENV_RELEASE = (1 << 3)
-} EnvStage;
-
-typedef struct Envelope {
- float a;
- float d;
- float s;
- float r;
- float vol;
- EnvStage stage;
-} Envelope;
-
-typedef struct Sample {
- Uint8 *data;
- float len;
- float pos;
- float inc;
- float loop;
- Uint8 pitch;
- Envelope env;
-} Sample;
-
-typedef struct AudioChannel {
- Sample sample;
- Sample next_sample;
- bool xfade;
- float duration;
- float vol_l;
- float vol_r;
-} AudioChannel;
-
-AudioChannel channel[POLYPHONY];
+typedef struct {
+ Uint8 *addr;
+ Uint32 count, advance, period, age, a, d, s, r;
+ Uint16 i, len;
+ Sint8 volume[2];
+ Uint8 pitch, repeat;
+} UxnAudio;
/* clang-format off */
-const float tuning[109] = {
- 0.00058853f, 0.00062352f, 0.00066060f, 0.00069988f, 0.00074150f,
- 0.00078559f, 0.00083230f, 0.00088179f, 0.00093423f, 0.00098978f,
- 0.00104863f, 0.00111099f, 0.00117705f, 0.00124704f, 0.00132120f,
- 0.00139976f, 0.00148299f, 0.00157118f, 0.00166460f, 0.00176359f,
- 0.00186845f, 0.00197956f, 0.00209727f, 0.00222198f, 0.00235410f,
- 0.00249409f, 0.00264239f, 0.00279952f, 0.00296599f, 0.00314235f,
- 0.00332921f, 0.00352717f, 0.00373691f, 0.00395912f, 0.00419454f,
- 0.00444396f, 0.00470821f, 0.00498817f, 0.00528479f, 0.00559904f,
- 0.00593197f, 0.00628471f, 0.00665841f, 0.00705434f, 0.00747382f,
- 0.00791823f, 0.00838908f, 0.00888792f, 0.00941642f, 0.00997635f,
- 0.01056957f, 0.01119807f, 0.01186395f, 0.01256941f, 0.01331683f,
- 0.01410869f, 0.01494763f, 0.01583647f, 0.01677815f, 0.01777583f,
- 0.01883284f, 0.01995270f, 0.02113915f, 0.02239615f, 0.02372789f,
- 0.02513882f, 0.02663366f, 0.02821738f, 0.02989527f, 0.03167293f,
- 0.03355631f, 0.03555167f, 0.03766568f, 0.03990540f, 0.04227830f,
- 0.04479229f, 0.04745578f, 0.05027765f, 0.05326731f, 0.05643475f,
- 0.05979054f, 0.06334587f, 0.06711261f, 0.07110333f, 0.07533136f,
- 0.07981079f, 0.08455659f, 0.08958459f, 0.09491156f, 0.10055530f,
- 0.10653463f, 0.11286951f, 0.11958108f, 0.12669174f, 0.13422522f,
- 0.14220667f, 0.15066272f, 0.15962159f, 0.16911318f, 0.17916918f,
- 0.18982313f, 0.20111060f, 0.21306926f, 0.22573902f, 0.23916216f,
- 0.25338348f, 0.26845044f, 0.28441334f, 0.30132544f,
+static Uint32 advances[12] = {
+ 0x80000, 0x879c8, 0x8facd, 0x9837f, 0xa1451, 0xaadc1,
+ 0xb504f, 0xbfc88, 0xcb2ff, 0xd7450, 0xe411f, 0xf1a1c
};
-/* clang-format on */
+static UxnAudio uxn_audio[POLYPHONY];
-void
-env_on(Envelope *env)
-{
- env->stage = ENV_ATTACK;
- env->vol = 0.0f;
- if(env->a > 0) {
- env->a = (SOUND_TIMER / AUDIO_BUFSIZE) / env->a;
- } else if(env->stage == ENV_ATTACK) {
- env->stage = ENV_DECAY;
- env->vol = 1.0f;
- }
- if(env->d < 10.0f) {
- env->d = 10.0f;
- }
- env->d = (SOUND_TIMER / AUDIO_BUFSIZE) / env->d;
- if(env->r < 10.0f) {
- env->r = 10.0f;
- }
- env->r = (SOUND_TIMER / AUDIO_BUFSIZE) / env->r;
-}
-
-void
-env_off(Envelope *env)
-{
- env->stage = ENV_RELEASE;
-}
-
-void
-note_on(AudioChannel *channel, float duration, Uint8 *data, Uint16 len, Uint8 vol, Uint8 attack, Uint8 decay, Uint8 sustain, Uint8 release, Uint8 pitch, bool loop)
-{
- channel->duration = duration;
- channel->vol_l = (vol >> 4) / 15.0f;
- channel->vol_r = (vol & 0xf) / 15.0f;
-
- Sample sample = {0};
- sample.data = data;
- sample.len = len;
- sample.pos = 0;
- sample.env.a = attack * 64.0f;
- sample.env.d = decay * 64.0f;
- sample.env.s = sustain / 16.0f;
- sample.env.r = release * 64.0f;
- if(loop) {
- sample.loop = len;
- } else {
- sample.loop = 0;
- }
- env_on(&sample.env);
- float sample_rate = 44100 / 261.60;
- if(len <= 256) {
- sample_rate = len;
- }
- const float *inc = &tuning[pitch - 20];
- sample.inc = *(inc)*sample_rate;
-
- channel->next_sample = sample;
- channel->xfade = true;
-}
-
-void
-note_off(AudioChannel *channel, float duration)
-{
- channel->duration = duration;
- env_off(&channel->sample.env);
-}
-
-void
-env_advance(Envelope *env)
-{
- switch(env->stage) {
- case ENV_ATTACK: {
- env->vol += env->a;
- if(env->vol >= 1.0f) {
- env->stage = ENV_DECAY;
- env->vol = 1.0f;
- }
- } break;
- case ENV_DECAY: {
- env->vol -= env->d;
- if(env->vol <= env->s || env->d <= 0) {
- env->stage = ENV_SUSTAIN;
- env->vol = env->s;
- }
- } break;
- case ENV_SUSTAIN: {
- env->vol = env->s;
- } break;
- case ENV_RELEASE: {
- if(env->vol <= 0 || env->r <= 0) {
- env->vol = 0;
- } else {
- env->vol -= env->r;
- }
- } break;
- }
-}
-
-float
-interpolate_sample(Uint8 *data, Uint16 len, float pos)
-{
-#if INTERPOL_METHOD == 0
- return data[(int)pos];
-
-#elif INTERPOL_METHOD == 1
- float x = pos;
- int x0 = (int)x;
- int x1 = (x0 + 1);
- float y0 = data[x0];
- float y1 = data[x1 % len];
- x = x - x0;
- float y = y0 + x * (y1 - y0);
- return y;
-
-#elif INTERPOL_METHOD == 2
- float x = pos;
- int x0 = x - 1;
- int x1 = x;
- int x2 = x + 1;
- int x3 = x + 2;
- float y0 = data[x0 % len];
- float y1 = data[x1];
- float y2 = data[x2 % len];
- float y3 = data[x3 % len];
- x = x - x1;
- float c0 = y1;
- float c1 = 0.5f * (y2 - y0);
- float c2 = y0 - 2.5f * y1 + 2.f * y2 - 0.5f * y3;
- float c3 = 1.5f * (y1 - y2) + 0.5f * (y3 - y0);
- return ((c3 * x + c2) * x + c1) * x + c0;
-#endif
-}
+/* clang-format on */
-Sint16
-next_sample(Sample *sample)
+static Sint32
+envelope(UxnAudio *c, Uint32 age)
{
- if(sample->pos >= sample->len) {
- if(sample->loop == 0) {
- sample->data = 0;
- return 0;
- }
- while(sample->pos >= sample->len) {
- sample->pos -= sample->loop;
- }
- }
-
- float val = interpolate_sample(sample->data, sample->len, sample->pos);
- val *= sample->env.vol;
- Sint8 next = (Sint8)0x80 ^ (Uint8)val;
-
- sample->pos += sample->inc;
- env_advance(&sample->env);
- return next;
+ if(!c->r) return 0x0888;
+ if(age < c->a) return 0x0888 * age / c->a;
+ if(age < c->d) return 0x0444 * (2 * c->d - c->a - age) / (c->d - c->a);
+ if(age < c->s) return 0x0444;
+ if(age < c->r) return 0x0444 * (c->r - age) / (c->r - c->s);
+ c->advance = 0;
+ return 0x0000;
}
-void
-audio_handler(void *ctx, Uint8 *out_stream, int len)
+int
+audio_render(int instance, Sint16 *sample, Sint16 *end)
{
- Sint16 *stream = (Sint16 *)out_stream;
- memset(stream, 0x00, len);
-
- int n;
- for(n = 0; n < POLYPHONY; n++) {
- Uint8 device = (3 + n) << 4;
- Uxn *u = (Uxn *)ctx;
- Uint8 *addr = &u->dev[device];
- if(channel[n].duration <= 0 && PEEK2(addr)) {
- uxn_eval(PEEK2(addr));
- }
- channel[n].duration -= SOUND_TIMER;
- int x = 0;
- if(channel[n].xfade) {
- float delta = 1.0f / (XFADE_SAMPLES * 2);
- while(x < XFADE_SAMPLES * 2) {
- float alpha = x * delta;
- float beta = 1.0f - alpha;
- Sint16 next_a = next_sample(&channel[n].next_sample);
- Sint16 next_b = 0;
- if(channel[n].sample.data != 0) {
- next_b = next_sample(&channel[n].sample);
- }
- Sint16 next = alpha * next_a + beta * next_b;
- stream[x++] += next * channel[n].vol_l;
- stream[x++] += next * channel[n].vol_r;
- }
- channel[n].sample = channel[n].next_sample;
- channel[n].xfade = false;
- }
- Sample *sample = &channel[n].sample;
- while(x < len / 2) {
- if(sample->data == 0) {
+ UxnAudio *c = &uxn_audio[instance];
+ Sint32 s;
+ if(!c->advance || !c->period) return 0;
+ while(sample < end) {
+ c->count += c->advance;
+ c->i += c->count / c->period;
+ c->count %= c->period;
+ if(c->i >= c->len) {
+ if(!c->repeat) {
+ c->advance = 0;
break;
}
- Sint16 next = next_sample(sample);
- stream[x++] += next * channel[n].vol_l;
- stream[x++] += next * channel[n].vol_r;
+ c->i %= c->len;
}
+ s = (Sint8)(c->addr[c->i] + 0x80) * envelope(c, c->age++);
+ *sample++ += s * c->volume[0] / 0x180;
+ *sample++ += s * c->volume[1] / 0x180;
}
- int i;
- for(i = 0; i < len / 2; i++) {
- stream[i] <<= 6;
- }
-}
-
-float
-calc_duration(Uint16 len, Uint8 pitch)
-{
- float scale = tuning[pitch - 20] / tuning[0x3c - 20];
- return len / (scale * 44.1f);
+ if(!c->advance) audio_finished_handler(instance);
+ return 1;
}
void
-audio_start(int idx, Uint8 *d, Uxn *u)
+audio_start(int instance, Uint8 *d, Uxn *u)
{
- Uint16 dur = PEEK2(d + 0x5);
- Uint8 off = d[0xf] == 0x00;
- Uint16 len = PEEK2(d + 0xa);
+ UxnAudio *c = &uxn_audio[instance];
Uint8 pitch = d[0xf] & 0x7f;
- if(pitch < 20) {
- pitch = 20;
- }
- float duration = dur > 0 ? dur : calc_duration(len, pitch);
-
- if(!off) {
- Uint16 addr = PEEK2(d + 0xc);
- Uint8 *data = &u->ram[addr];
- Uint8 volume = d[0xe];
- bool loop = !(d[0xf] & 0x80);
- Uint16 adsr = PEEK2(d + 0x8);
- Uint8 attack = (adsr >> 12) & 0xF;
- Uint8 decay = (adsr >> 8) & 0xF;
- Uint8 sustain = (adsr >> 4) & 0xF;
- Uint8 release = (adsr >> 0) & 0xF;
- note_on(&channel[idx], duration, data, len, volume, attack, decay, sustain, release, pitch, loop);
- } else {
- note_off(&channel[idx], duration);
+ Uint16 addr = PEEK2(d + 0xc), adsr = PEEK2(d + 0x8);
+ c->len = PEEK2(d + 0xa);
+ if(c->len > 0x10000 - addr)
+ c->len = 0x10000 - addr;
+ c->addr = &u->ram[addr];
+ c->volume[0] = d[0xe] >> 4;
+ c->volume[1] = d[0xe] & 0xf;
+ c->repeat = !(d[0xf] & 0x80);
+ if(pitch < 108 && c->len)
+ c->advance = advances[pitch % 12] >> (8 - pitch / 12);
+ else {
+ c->advance = 0;
+ return;
}
+ c->a = ADSR_STEP * (adsr >> 12);
+ c->d = ADSR_STEP * (adsr >> 8 & 0xf) + c->a;
+ c->s = ADSR_STEP * (adsr >> 4 & 0xf) + c->d;
+ c->r = ADSR_STEP * (adsr >> 0 & 0xf) + c->s;
+ c->age = 0;
+ c->i = 0;
+ if(c->len <= 0x100) /* single cycle mode */
+ c->period = NOTE_PERIOD * 337 / 2 / c->len;
+ else /* sample repeat mode */
+ c->period = NOTE_PERIOD;
}
Uint8
audio_get_vu(int instance)
{
- return channel[instance].sample.env.vol * 255.0f;
+ UxnAudio *c = &uxn_audio[instance];
+ int i;
+ Sint32 sum[2] = {0, 0};
+ if(!c->advance || !c->period) return 0;
+ for(i = 0; i < 2; i++) {
+ if(!c->volume[i]) continue;
+ sum[i] = 1 + envelope(c, c->age) * c->volume[i] / 0x800;
+ if(sum[i] > 0xf) sum[i] = 0xf;
+ }
+ return (sum[0] << 4) | sum[1];
}
Uint16
audio_get_position(int instance)
{
- return channel[instance].sample.pos;
-}
-
-Uint8
-audio_dei(int instance, Uint8 *d, Uint8 port)
-{
- switch(port) {
- case 0x2: return audio_get_position(instance) >> 8;
- case 0x3: return audio_get_position(instance);
- case 0x4: return audio_get_vu(instance);
- }
- return d[port];
+ UxnAudio *c = &uxn_audio[instance];
+ return c->i;
}
diff --git a/src/devices/audio.h b/src/devices/audio.h
@@ -1,5 +1,6 @@
/*
-Copyright (c) 2021 Devine Lu Linvega, Andrew Alderwick
+Copyright (c) 2021 Devine Lu Linvega
+Copyright (c) 2021 Andrew Alderwick
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -11,14 +12,11 @@ WITH REGARD TO THIS SOFTWARE.
typedef signed int Sint32;
-#define AUDIO_BUFSIZE 256.0f
-#define SAMPLE_FREQUENCY 44100.0f
+#define SAMPLE_FREQUENCY 44100
#define POLYPHONY 4
Uint8 audio_get_vu(int instance);
Uint16 audio_get_position(int instance);
int audio_render(int instance, Sint16 *sample, Sint16 *end);
void audio_start(int instance, Uint8 *d, Uxn *u);
-void audio_finished_handler(int instance);
-void audio_handler(void *ctx, Uint8 *out_stream, int len);
-Uint8 audio_dei(int instance, Uint8 *d, Uint8 port);
+void audio_finished_handler(int instance);
+\ No newline at end of file
diff --git a/src/uxnemu.c b/src/uxnemu.c
@@ -59,13 +59,24 @@ static int window_created, fullscreen, borderless;
static Uint32 stdin_event, audio0_event, zoom = 1;
static Uint64 exec_deadline, deadline_interval, ms_interval;
+static Uint8
+audio_dei(int instance, Uint8 *d, Uint8 port)
+{
+ if(!audio_id) return d[port];
+ switch(port) {
+ case 0x4: return audio_get_vu(instance);
+ case 0x2: POKE2(d + 0x2, audio_get_position(instance)); /* fall through */
+ default: return d[port];
+ }
+}
+
static void
-audio_deo(int instance, Uint8 *d, Uint8 port)
+audio_deo(int instance, Uint8 *d, Uint8 port, Uxn *u)
{
if(!audio_id) return;
if(port == 0xf) {
SDL_LockAudioDevice(audio_id);
- audio_start(instance, d, &uxn);
+ audio_start(instance, d, u);
SDL_UnlockAudioDevice(audio_id);
SDL_PauseAudioDevice(audio_id, 0);
}
@@ -99,10 +110,10 @@ emu_deo(Uint8 addr, Uint8 value)
break;
case 0x10: console_deo(addr); break;
case 0x20: screen_deo(addr); break;
- case 0x30: audio_deo(0, &uxn.dev[d], p); break;
- case 0x40: audio_deo(1, &uxn.dev[d], p); break;
- case 0x50: audio_deo(2, &uxn.dev[d], p); break;
- case 0x60: audio_deo(3, &uxn.dev[d], p); break;
+ case 0x30: audio_deo(0, &uxn.dev[d], p, &uxn); break;
+ case 0x40: audio_deo(1, &uxn.dev[d], p, &uxn); break;
+ case 0x50: audio_deo(2, &uxn.dev[d], p, &uxn); break;
+ case 0x60: audio_deo(3, &uxn.dev[d], p, &uxn); break;
case 0x80: controller_deo(addr); break;
case 0x90: mouse_deo(addr); break;
case 0xa0: file_deo(addr); break;
@@ -112,6 +123,19 @@ emu_deo(Uint8 addr, Uint8 value)
/* Handlers */
+static void
+audio_callback(void *u, Uint8 *stream, int len)
+{
+ int instance, running = 0;
+ Sint16 *samples = (Sint16 *)stream;
+ USED(u);
+ SDL_memset(stream, 0, len);
+ for(instance = 0; instance < POLYPHONY; instance++)
+ running += audio_render(instance, samples, samples + len / 2);
+ if(!running)
+ SDL_PauseAudioDevice(audio_id, 1);
+}
+
void
audio_finished_handler(int instance)
{
@@ -219,9 +243,9 @@ emu_init(void)
as.freq = SAMPLE_FREQUENCY;
as.format = AUDIO_S16SYS;
as.channels = 2;
- as.callback = audio_handler;
- as.samples = AUDIO_BUFSIZE;
- as.userdata = &uxn;
+ as.callback = audio_callback;
+ as.samples = 512;
+ as.userdata = NULL;
if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK) < 0)
return system_error("sdl", SDL_GetError());
audio_id = SDL_OpenAudioDevice(NULL, 0, &as, NULL, 0);
@@ -319,6 +343,11 @@ handle_events(void)
mouse_down(SDL_BUTTON(event.button.button));
else if(event.type == SDL_MOUSEWHEEL)
mouse_scroll(event.wheel.x, event.wheel.y);
+ /* Audio */
+ else if(event.type >= audio0_event && event.type < audio0_event + POLYPHONY) {
+ Uint8 *port_value = &uxn.dev[0x30 + 0x10 * (event.type - audio0_event)];
+ uxn_eval(port_value[0] << 8 | port_value[1]);
+ }
/* Controller */
else if(event.type == SDL_TEXTINPUT) {
char *c;