diff --git a/projects/software/piano.tal b/projects/software/piano.tal index 099bca7..8b23cbb 100644 --- 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 index fcc5acb..aab7cb7 100644 --- a/src/devices/audio.c +++ b/src/devices/audio.c @@ -1,10 +1,8 @@ #include "../uxn.h" #include "audio.h" -#include -#include /* -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 index a5b2fb0..e96480c 100644 --- 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 index 7097b71..ee9224e 100644 --- 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;