Browse Source

Rolled back audio device to original implementation

main
Devine Lu Linvega 1 year ago
parent
commit
9b92f7b8e2
  1. 16
      projects/software/piano.tal
  2. 379
      src/devices/audio.c
  3. 10
      src/devices/audio.h
  4. 47
      src/uxnemu.c

16
projects/software/piano.tal

@ -91,17 +91,10 @@ BRK
#1000 #1000
&loop &loop
.adsr-view/x2 LDZ2 #003a SUB2 .Screen/x DEO2 .adsr-view/x2 LDZ2 #003a SUB2 .Screen/x DEO2
( left ) #10 OVR SUB .Audio0/output DEI
#10 OVR SUB DUP2 #0f AND LTH .Screen/pixel DEO
#00 .Audio0/output DEI
#00 .Audio0/volume DEI #04 SFT
MUL2 #08 SFT2 NIP LTH .Screen/pixel DEO
.Screen/x DEI2k INC2 INC2 ROT DEO2 .Screen/x DEI2k INC2 INC2 ROT DEO2
( right ) #04 SFT LTH .Screen/pixel DEO
#10 OVR SUB
#00 .Audio0/output DEI
#00 .Audio0/volume DEI #0f AND
MUL2 #08 SFT2 NIP LTH .Screen/pixel DEO
.Screen/y DEI2k INC2 INC2 ROT DEO2 .Screen/y DEI2k INC2 INC2 ROT DEO2
INC GTHk ?&loop INC GTHk ?&loop
POP2 POP2
@ -556,5 +549,4 @@ JMP2r
2729 2b2e 3032 3537 3a3d 3f42 4547 4a4d 2729 2b2e 3032 3537 3a3d 3f42 4547 4a4d
5053 5659 5c5f 6265 686b 6e71 7477 7a7d ] 5053 5659 5c5f 6265 686b 6e71 7477 7a7d ]
&end &end
( pad ) [ 8080 8080 ] ( pad ) [ 8080 8080 ]

379
src/devices/audio.c

@ -1,10 +1,8 @@
#include "../uxn.h" #include "../uxn.h"
#include "audio.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 Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above 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. WITH REGARD TO THIS SOFTWARE.
*/ */
#define SOUND_TIMER (AUDIO_BUFSIZE / SAMPLE_FREQUENCY * 1000.0f) #define NOTE_PERIOD (SAMPLE_FREQUENCY * 0x4000 / 11025)
#define XFADE_SAMPLES 100 #define ADSR_STEP (SAMPLE_FREQUENCY / 0xf)
#define INTERPOL_METHOD 1
typedef enum EnvStage { typedef struct {
ENV_ATTACK = (1 << 0), Uint8 *addr;
ENV_DECAY = (1 << 1), Uint32 count, advance, period, age, a, d, s, r;
ENV_SUSTAIN = (1 << 2), Uint16 i, len;
ENV_RELEASE = (1 << 3) Sint8 volume[2];
} EnvStage; Uint8 pitch, repeat;
} UxnAudio;
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];
/* clang-format off */ /* clang-format off */
const float tuning[109] = { static Uint32 advances[12] = {
0.00058853f, 0.00062352f, 0.00066060f, 0.00069988f, 0.00074150f, 0x80000, 0x879c8, 0x8facd, 0x9837f, 0xa1451, 0xaadc1,
0.00078559f, 0.00083230f, 0.00088179f, 0.00093423f, 0.00098978f, 0xb504f, 0xbfc88, 0xcb2ff, 0xd7450, 0xe411f, 0xf1a1c
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,
}; };
/* clang-format on */ static UxnAudio uxn_audio[POLYPHONY];
void /* clang-format on */
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
}
Sint16 static Sint32
next_sample(Sample *sample) envelope(UxnAudio *c, Uint32 age)
{ {
if(sample->pos >= sample->len) { if(!c->r) return 0x0888;
if(sample->loop == 0) { if(age < c->a) return 0x0888 * age / c->a;
sample->data = 0; if(age < c->d) return 0x0444 * (2 * c->d - c->a - age) / (c->d - c->a);
return 0; if(age < c->s) return 0x0444;
} if(age < c->r) return 0x0444 * (c->r - age) / (c->r - c->s);
while(sample->pos >= sample->len) { c->advance = 0;
sample->pos -= sample->loop; return 0x0000;
}
}
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;
} }
void int
audio_handler(void *ctx, Uint8 *out_stream, int len) audio_render(int instance, Sint16 *sample, Sint16 *end)
{ {
Sint16 *stream = (Sint16 *)out_stream; UxnAudio *c = &uxn_audio[instance];
memset(stream, 0x00, len); Sint32 s;
if(!c->advance || !c->period) return 0;
int n; while(sample < end) {
for(n = 0; n < POLYPHONY; n++) { c->count += c->advance;
Uint8 device = (3 + n) << 4; c->i += c->count / c->period;
Uxn *u = (Uxn *)ctx; c->count %= c->period;
Uint8 *addr = &u->dev[device]; if(c->i >= c->len) {
if(channel[n].duration <= 0 && PEEK2(addr)) { if(!c->repeat) {
uxn_eval(PEEK2(addr)); c->advance = 0;
}
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) {
break; break;
} }
Sint16 next = next_sample(sample); c->i %= c->len;
stream[x++] += next * channel[n].vol_l;
stream[x++] += next * channel[n].vol_r;
} }
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; if(!c->advance) audio_finished_handler(instance);
for(i = 0; i < len / 2; i++) { return 1;
stream[i] <<= 6;
}
}
float
calc_duration(Uint16 len, Uint8 pitch)
{
float scale = tuning[pitch - 20] / tuning[0x3c - 20];
return len / (scale * 44.1f);
} }
void void
audio_start(int idx, Uint8 *d, Uxn *u) audio_start(int instance, Uint8 *d, Uxn *u)
{ {
Uint16 dur = PEEK2(d + 0x5); UxnAudio *c = &uxn_audio[instance];
Uint8 off = d[0xf] == 0x00;
Uint16 len = PEEK2(d + 0xa);
Uint8 pitch = d[0xf] & 0x7f; Uint8 pitch = d[0xf] & 0x7f;
if(pitch < 20) { Uint16 addr = PEEK2(d + 0xc), adsr = PEEK2(d + 0x8);
pitch = 20; c->len = PEEK2(d + 0xa);
} if(c->len > 0x10000 - addr)
float duration = dur > 0 ? dur : calc_duration(len, pitch); c->len = 0x10000 - addr;
c->addr = &u->ram[addr];
if(!off) { c->volume[0] = d[0xe] >> 4;
Uint16 addr = PEEK2(d + 0xc); c->volume[1] = d[0xe] & 0xf;
Uint8 *data = &u->ram[addr]; c->repeat = !(d[0xf] & 0x80);
Uint8 volume = d[0xe]; if(pitch < 108 && c->len)
bool loop = !(d[0xf] & 0x80); c->advance = advances[pitch % 12] >> (8 - pitch / 12);
Uint16 adsr = PEEK2(d + 0x8); else {
Uint8 attack = (adsr >> 12) & 0xF; c->advance = 0;
Uint8 decay = (adsr >> 8) & 0xF; return;
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);
} }
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 Uint8
audio_get_vu(int instance) 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 Uint16
audio_get_position(int instance) audio_get_position(int instance)
{ {
return channel[instance].sample.pos; UxnAudio *c = &uxn_audio[instance];
} return c->i;
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];
} }

10
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 Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above 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; typedef signed int Sint32;
#define AUDIO_BUFSIZE 256.0f #define SAMPLE_FREQUENCY 44100
#define SAMPLE_FREQUENCY 44100.0f
#define POLYPHONY 4 #define POLYPHONY 4
Uint8 audio_get_vu(int instance); Uint8 audio_get_vu(int instance);
Uint16 audio_get_position(int instance); Uint16 audio_get_position(int instance);
int audio_render(int instance, Sint16 *sample, Sint16 *end); int audio_render(int instance, Sint16 *sample, Sint16 *end);
void audio_start(int instance, Uint8 *d, Uxn *u); void audio_start(int instance, Uint8 *d, Uxn *u);
void audio_finished_handler(int instance); 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);

47
src/uxnemu.c

@ -59,13 +59,24 @@ static int window_created, fullscreen, borderless;
static Uint32 stdin_event, audio0_event, zoom = 1; static Uint32 stdin_event, audio0_event, zoom = 1;
static Uint64 exec_deadline, deadline_interval, ms_interval; 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 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(!audio_id) return;
if(port == 0xf) { if(port == 0xf) {
SDL_LockAudioDevice(audio_id); SDL_LockAudioDevice(audio_id);
audio_start(instance, d, &uxn); audio_start(instance, d, u);
SDL_UnlockAudioDevice(audio_id); SDL_UnlockAudioDevice(audio_id);
SDL_PauseAudioDevice(audio_id, 0); SDL_PauseAudioDevice(audio_id, 0);
} }
@ -99,10 +110,10 @@ emu_deo(Uint8 addr, Uint8 value)
break; break;
case 0x10: console_deo(addr); break; case 0x10: console_deo(addr); break;
case 0x20: screen_deo(addr); break; case 0x20: screen_deo(addr); break;
case 0x30: audio_deo(0, &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); break; case 0x40: audio_deo(1, &uxn.dev[d], p, &uxn); break;
case 0x50: audio_deo(2, &uxn.dev[d], p); break; case 0x50: audio_deo(2, &uxn.dev[d], p, &uxn); break;
case 0x60: audio_deo(3, &uxn.dev[d], p); break; case 0x60: audio_deo(3, &uxn.dev[d], p, &uxn); break;
case 0x80: controller_deo(addr); break; case 0x80: controller_deo(addr); break;
case 0x90: mouse_deo(addr); break; case 0x90: mouse_deo(addr); break;
case 0xa0: file_deo(addr); break; case 0xa0: file_deo(addr); break;
@ -112,6 +123,19 @@ emu_deo(Uint8 addr, Uint8 value)
/* Handlers */ /* 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 void
audio_finished_handler(int instance) audio_finished_handler(int instance)
{ {
@ -219,9 +243,9 @@ emu_init(void)
as.freq = SAMPLE_FREQUENCY; as.freq = SAMPLE_FREQUENCY;
as.format = AUDIO_S16SYS; as.format = AUDIO_S16SYS;
as.channels = 2; as.channels = 2;
as.callback = audio_handler; as.callback = audio_callback;
as.samples = AUDIO_BUFSIZE; as.samples = 512;
as.userdata = &uxn; as.userdata = NULL;
if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK) < 0) if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK) < 0)
return system_error("sdl", SDL_GetError()); return system_error("sdl", SDL_GetError());
audio_id = SDL_OpenAudioDevice(NULL, 0, &as, NULL, 0); audio_id = SDL_OpenAudioDevice(NULL, 0, &as, NULL, 0);
@ -319,6 +343,11 @@ handle_events(void)
mouse_down(SDL_BUTTON(event.button.button)); mouse_down(SDL_BUTTON(event.button.button));
else if(event.type == SDL_MOUSEWHEEL) else if(event.type == SDL_MOUSEWHEEL)
mouse_scroll(event.wheel.x, event.wheel.y); 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 */ /* Controller */
else if(event.type == SDL_TEXTINPUT) { else if(event.type == SDL_TEXTINPUT) {
char *c; char *c;

Loading…
Cancel
Save