From f3bf1a74db17151610327c134f6241b07bff7fb0 Mon Sep 17 00:00:00 2001 From: Andrew Alderwick Date: Wed, 7 Apr 2021 21:50:35 +0100 Subject: [PATCH] Added Uxn-based synth --- build.sh | 5 +- projects/examples/blank.usm | 2 +- projects/examples/dev.audio.usm | 116 ++++++++++++++-------- projects/examples/dev.time.usm | 14 --- src/apu.c | 164 ++++++++++++++++++++++++++++++++ src/emulator.c | 132 +++---------------------- 6 files changed, 257 insertions(+), 176 deletions(-) create mode 100644 src/apu.c diff --git a/build.sh b/build.sh index 99c7f9f..1a4b7c4 100755 --- a/build.sh +++ b/build.sh @@ -6,6 +6,7 @@ clang-format -i src/uxn.h clang-format -i src/uxn.c clang-format -i src/emulator.c clang-format -i src/debugger.c +clang-format -i src/apu.c echo "Cleaning.." rm -f ./bin/assembler @@ -19,12 +20,12 @@ if [ "${1}" = '--debug' ]; then echo "[debug]" cc -std=c89 -DDEBUG -Wall -Wno-unknown-pragmas -Wpedantic -Wshadow -Wextra -Werror=implicit-int -Werror=incompatible-pointer-types -Werror=int-conversion -Wvla -g -Og -fsanitize=address -fsanitize=undefined src/assembler.c -o bin/assembler - cc -std=c89 -DDEBUG -Wall -Wno-unknown-pragmas -Wpedantic -Wshadow -Wextra -Werror=implicit-int -Werror=incompatible-pointer-types -Werror=int-conversion -Wvla -g -Og -fsanitize=address -fsanitize=undefined src/uxn.c src/emulator.c -L/usr/local/lib -lSDL2 -o bin/emulator + cc -std=c89 -DDEBUG -Wall -Wno-unknown-pragmas -Wpedantic -Wshadow -Wextra -Werror=implicit-int -Werror=incompatible-pointer-types -Werror=int-conversion -Wvla -g -Og -fsanitize=address -fsanitize=undefined src/uxn.c src/emulator.c src/apu.c -L/usr/local/lib -lSDL2 -o bin/emulator cc -std=c89 -DDEBUG -Wall -Wno-unknown-pragmas -Wpedantic -Wshadow -Wextra -Werror=implicit-int -Werror=incompatible-pointer-types -Werror=int-conversion -Wvla -g -Og -fsanitize=address -fsanitize=undefined src/uxn.c src/debugger.c -o bin/debugger else cc src/assembler.c -std=c89 -Os -DNDEBUG -g0 -s -Wall -Wno-unknown-pragmas -o bin/assembler cc src/uxn.c src/debugger.c -std=c89 -Os -DNDEBUG -g0 -s -Wall -Wno-unknown-pragmas -o bin/debugger - cc src/uxn.c src/emulator.c -std=c89 -Os -DNDEBUG -g0 -s -Wall -Wno-unknown-pragmas -L/usr/local/lib -lSDL2 -o bin/emulator + cc src/uxn.c src/emulator.c src/apu.c -std=c89 -Os -DNDEBUG -g0 -s -Wall -Wno-unknown-pragmas -L/usr/local/lib -lSDL2 -o bin/emulator fi echo "Assembling.." diff --git a/projects/examples/blank.usm b/projects/examples/blank.usm index 5fc2e19..9278524 100644 --- a/projects/examples/blank.usm +++ b/projects/examples/blank.usm @@ -11,7 +11,7 @@ |0140 ;Keys { key 1 } |0150 ;Mouse { x 2 y 2 state 1 chord 1 } |0160 ;File { pad 8 name 2 length 2 load 2 save 2 } -|0170 ;Audio { ch1asdr 2 ch2asdr 2 ch3asdr 2 ch4asdr 2 ch1pitch 1 ch1vol 1 ch2pitch 1 ch2vol 1 ch3pitch 1 ch3vol 1 ch4pitch 1 ch4vol 1 } +|0180 ;Audio { wave 2 envelope 2 pad 4 volume 1 pitch 1 play 1 value 2 delay 2 finish 1 } |01F0 ;System { pad 8 r 2 g 2 b 2 } ( vectors ) diff --git a/projects/examples/dev.audio.usm b/projects/examples/dev.audio.usm index 4052e71..df62a2c 100644 --- a/projects/examples/dev.audio.usm +++ b/projects/examples/dev.audio.usm @@ -6,6 +6,9 @@ %++ { #0001 ADD2 } %MOD { DUP2 DIV MUL SUB } %TRACK { ,track.ch1 #00 ~track.active #0020 MUL2 ADD2 } +%SOUND { STH #00 =Audio.value STHr #00 =Audio.delay } +%SOUND2 { =Audio.value =Audio.delay } +%SOUND_FINISH { #00 =Audio.finish } ( variables ) @@ -19,6 +22,8 @@ ;knob { x 2 y 2 value 1 } ;head { pos 1 } ;track { active 1 ch1 20 ch2 20 ch3 20 ch4 20 } +;adsr { ch1a 1 ch1d 1 ch1s 1 ch1r 1 ch2a 1 ch2d 1 ch2s 1 ch2r 1 ch3a 1 ch3d 1 ch3s 1 ch3r 1 ch4a 1 ch4d 1 ch4s 1 ch4r 1 } +;volume { ch1 1 ch2 1 ch3 1 ch4 1 } ( devices ) @@ -30,7 +35,7 @@ |0150 ;Keys { key 1 } |0160 ;Mouse { vector 2 x 2 y 2 state 1 chord 1 } |0170 ;File { pad 8 name 2 length 2 load 2 save 2 } -|0180 ;Audio { ch1adsr 2 ch2adsr 2 ch3adsr 2 ch4adsr 2 ch1vol 1 ch1pitch 1 ch2vol 1 ch2pitch 1 ch3vol 1 ch3pitch 1 ch4vol 1 ch4pitch 1 } +|0180 ;Audio { wave 2 envelope 2 pad 4 volume 1 pitch 1 play 1 value 2 delay 2 finish 1 } ( vectors ) @@ -51,9 +56,10 @@ ~trkframe.x2 =ctlframe.x2 ~chnframe.y2 =ctlframe.y2 ( default settings ) - #048c =Audio.ch1adsr #88 =Audio.ch1vol - #159d =Audio.ch2adsr #88 =Audio.ch2vol - #26ae =Audio.ch3adsr #88 =Audio.ch3vol + ,adsr-envelope =Audio.envelope + #00 =adsr.ch1a #40 =adsr.ch1d #80 =adsr.ch1s #c0 =adsr.ch1r #88 =volume.ch1 + #10 =adsr.ch2a #50 =adsr.ch2d #90 =adsr.ch2s #d0 =adsr.ch2r #88 =volume.ch2 + #20 =adsr.ch3a #60 =adsr.ch3d #a0 =adsr.ch3s #e0 =adsr.ch3r #88 =volume.ch3 ,draw-timeline JSR2 ,draw-controls JSR2 @@ -110,29 +116,29 @@ BRK ~Mouse.x ~ctlframe.x1 SUB2 8- 8/ SWP POP #02 DIV DUP #00 NEQ ^$no-a JNZ - ,Audio #00 ~track.active #02 MUL ADD2 PEK2 - #10 ~Mouse.state #10 EQU #e0 MUL ADD ADD - ,Audio #00 ~track.active #02 MUL ADD2 POK2 $no-a + ,adsr #00 ~track.active #04 MUL ADD2 PEK2 + #10 ~Mouse.state #10 EQU #e0 MUL ADD ADD + ,adsr #00 ~track.active #04 MUL ADD2 POK2 $no-a DUP #01 NEQ ^$no-d JNZ - ,Audio #00 ~track.active #02 MUL ADD2 PEK2 - DUP #f0 AND STH #01 ~Mouse.state #10 EQU #0e MUL ADD ADD #0f AND STHr ADD - ,Audio #00 ~track.active #02 MUL ADD2 POK2 $no-d + ,adsr #00 ~track.active #04 MUL ADD2 #0001 ADD2 PEK2 + #10 ~Mouse.state #10 EQU #e0 MUL ADD ADD + ,adsr #00 ~track.active #04 MUL ADD2 #0001 ADD2 POK2 $no-d DUP #02 NEQ ^$no-s JNZ - ,Audio #00 ~track.active #02 MUL ADD2 ++ PEK2 - #10 ~Mouse.state #10 EQU #e0 MUL ADD ADD - ,Audio #00 ~track.active #02 MUL ADD2 ++ POK2 $no-s + ,adsr #00 ~track.active #04 MUL ADD2 #0002 ADD2 PEK2 + #10 ~Mouse.state #10 EQU #e0 MUL ADD ADD + ,adsr #00 ~track.active #04 MUL ADD2 #0002 ADD2 POK2 $no-s DUP #03 NEQ ^$no-r JNZ - ,Audio #00 ~track.active #02 MUL ADD2 ++ PEK2 - DUP #f0 AND STH #01 ~Mouse.state #10 EQU #0e MUL ADD ADD #0f AND STHr ADD - ,Audio #00 ~track.active #02 MUL ADD2 ++ POK2 $no-r + ,adsr #00 ~track.active #04 MUL ADD2 #0003 ADD2 PEK2 + #10 ~Mouse.state #10 EQU #e0 MUL ADD ADD + ,adsr #00 ~track.active #04 MUL ADD2 #0003 ADD2 POK2 $no-r DUP #05 NEQ ^$no-left JNZ - ,Audio 8+ #00 ~track.active #02 MUL ADD2 PEK2 - #10 ~Mouse.state #10 EQU #e0 MUL ADD ADD - ,Audio 8+ #00 ~track.active #02 MUL ADD2 POK2 $no-left + ,volume #00 ~track.active ADD2 PEK2 + #10 ~Mouse.state #10 EQU #e0 MUL ADD ADD + ,volume #00 ~track.active ADD2 POK2 $no-left DUP #06 NEQ ^$no-right JNZ - ,Audio 8+ #00 ~track.active #02 MUL ADD2 PEK2 + ,volume #00 ~track.active ADD2 PEK2 DUP #f0 AND STH #01 ~Mouse.state #10 EQU #0e MUL ADD ADD #0f AND STHr ADD - ,Audio 8+ #00 ~track.active #02 MUL ADD2 POK2 $no-right + ,volume #00 ~track.active ADD2 POK2 $no-right POP ( release ) #00 =Mouse.state ,draw-controls JSR2 @@ -146,21 +152,30 @@ BRK DUP #ff NEQ ^$skip1 JNZ POP ^$listen2 JMP $skip1 - #00 SWP ,notes ADD2 PEK2 =Audio.ch1pitch + #00 SWP ,notes ADD2 PEK2 =Audio.pitch + ~volume.ch1 =Audio.volume + ,square-wave =Audio.wave + #00 =Audio.play $listen2 ,track.ch2 #00 ~head.pos #08 DIV ADD2 PEK2 #01 SUB DUP #ff NEQ ^$skip2 JNZ POP ^$listen3 JMP $skip2 - #00 SWP ,notes ADD2 PEK2 =Audio.ch2pitch + #00 SWP ,notes ADD2 PEK2 =Audio.pitch + ~volume.ch2 =Audio.volume + ,square-wave =Audio.wave + #01 =Audio.play $listen3 ,track.ch3 #00 ~head.pos #08 DIV ADD2 PEK2 #01 SUB DUP #ff NEQ ^$skip3 JNZ POP ^$end JMP $skip3 - #00 SWP ,notes ADD2 PEK2 =Audio.ch3pitch + #00 SWP ,notes ADD2 PEK2 =Audio.pitch + ~volume.ch3 =Audio.volume + ,triangle-wave =Audio.wave + #02 =Audio.play $end RTN @@ -264,18 +279,18 @@ RTN ~trkframe.x1 #0018 SUB2 DUP2 ~trkframe.y1 ,draw-octave JSR2 ~trkframe.y1 #0038 ADD2 ,draw-octave JSR2 ~trkframe.x1 #0028 SUB2 =Sprite.x - ~trkframe.y1 =Sprite.y - ,font_hex #0060 ADD2 =Sprite.addr + ~trkframe.y1 #0030 ADD2 =Sprite.y + ,font_hex #0028 ADD2 =Sprite.addr #03 =Sprite.color ~trkframe.x1 #0030 SUB2 =Sprite.x - ,font_hex #0020 ADD2 =Sprite.addr + ,font_hex #0060 ADD2 =Sprite.addr #03 =Sprite.color ~trkframe.x1 #0028 SUB2 =Sprite.x - ~trkframe.y1 #0038 ADD2 =Sprite.y - ,font_hex #0060 ADD2 =Sprite.addr + ~trkframe.y1 #0068 ADD2 =Sprite.y + ,font_hex #0020 ADD2 =Sprite.addr #03 =Sprite.color ~trkframe.x1 #0030 SUB2 =Sprite.x - ,font_hex #0018 ADD2 =Sprite.addr + ,font_hex #0060 ADD2 =Sprite.addr #03 =Sprite.color RTN @@ -312,24 +327,24 @@ RTN ( env ) ~ctlframe.x1 8+ ~ctlframe.y1 8+ #02 ,env_txt ,draw-label JSR2 ~ctlframe.x1 8+ ~ctlframe.y1 #0010 ADD2 - ,Audio #00 ~track.active #02 MUL ADD2 PEK2 #04 SFT + ,adsr #00 ~track.active #04 MUL ADD2 PEK2 #04 SFT ,draw-knob JSR2 ~ctlframe.x1 #0018 ADD2 ~ctlframe.y1 #0010 ADD2 - ,Audio #00 ~track.active #02 MUL ADD2 PEK2 #0f AND + ,adsr #00 ~track.active #04 MUL ADD2 #0001 ADD2 PEK2 #04 SFT ,draw-knob JSR2 ~ctlframe.x1 #0028 ADD2 ~ctlframe.y1 #0010 ADD2 - ,Audio #00 ~track.active #02 MUL ADD2 ++ PEK2 #04 SFT + ,adsr #00 ~track.active #04 MUL ADD2 #0002 ADD2 PEK2 #04 SFT ,draw-knob JSR2 ~ctlframe.x1 #0038 ADD2 ~ctlframe.y1 #0010 ADD2 - ,Audio #00 ~track.active #02 MUL ADD2 ++ PEK2 #0f AND + ,adsr #00 ~track.active #04 MUL ADD2 #0003 ADD2 PEK2 #04 SFT ,draw-knob JSR2 ( vol ) ~ctlframe.x1 #0058 ADD2 ~ctlframe.y1 8+ #02 ,vol_txt ,draw-label JSR2 ~ctlframe.x1 #0058 ADD2 ~ctlframe.y1 #0010 ADD2 - ,Audio 8+ #00 ~track.active #02 MUL ADD2 PEK2 #04 SFT + ,volume #00 ~track.active ADD2 PEK2 #04 SFT ,draw-knob JSR2 ~ctlframe.x1 #0068 ADD2 ~ctlframe.y1 #0010 ADD2 - ,Audio 8+ #00 ~track.active #02 MUL ADD2 PEK2 #0f AND + ,volume #00 ~track.active ADD2 PEK2 #0f AND ,draw-knob JSR2 RTN @@ -415,6 +430,27 @@ RTN RTN +@adsr-envelope ( -- ) + #7f ,adsr #00 ~Audio.play #04 MUL ADD2 PEK2 SOUND + #40 ,adsr #00 ~Audio.play #04 MUL ADD2 #0001 ADD2 PEK2 SOUND + #40 ,adsr #00 ~Audio.play #04 MUL ADD2 #0002 ADD2 PEK2 SOUND + #00 ,adsr #00 ~Audio.play #04 MUL ADD2 #0003 ADD2 PEK2 SOUND + SOUND_FINISH + BRK + +@square-wave ( -- ) + #5800 SOUND + #5880 SOUND + #a800 SOUND + #a880 SOUND + BRK + +@triangle-wave ( -- ) + #7f40 SOUND + #8180 SOUND + #0040 SOUND + BRK + @ch1_txt [ CHN0 00 ] @ch2_txt [ CHN1 00 ] @ch3_txt [ CHN2 00 ] @@ -458,13 +494,13 @@ RTN ] @knob_offsetx [ - 04 05 06 07 08 07 06 05 - 04 04 03 02 01 00 01 02 + 01 00 00 00 00 01 02 03 + 05 06 07 08 08 08 08 07 ] @knob_offsety [ - 00 01 02 03 04 05 06 07 - 08 07 06 05 04 04 03 02 + 07 06 05 03 02 01 00 00 + 00 00 01 02 03 05 06 07 ] @font_hex ( 0-F ) diff --git a/projects/examples/dev.time.usm b/projects/examples/dev.time.usm index 37564df..921afcf 100644 --- a/projects/examples/dev.time.usm +++ b/projects/examples/dev.time.usm @@ -17,7 +17,6 @@ |0100 ;System { vector 2 pad 6 r 2 g 2 b 2 } |0120 ;Screen { vector 2 width 2 height 2 pad 2 x 2 y 2 color 1 } |0130 ;Sprite { vector 2 pad 6 x 2 y 2 addr 2 color 1 } -|0180 ;Audio { ch1adsr 2 ch2adsr 2 ch3adsr 2 ch4adsr 2 ch1vol 1 ch1pitch 1 ch2vol 1 ch2pitch 1 ch3vol 1 ch3pitch 1 ch4vol 1 ch4pitch 1 } |01a0 ;Time { year 2 month 1 day 1 hour 1 minute 1 second 1 dow 1 doy 2 isdst 1 get 1 } ( program ) @@ -27,12 +26,6 @@ ( theme ) #0ff8 =System.r #0f08 =System.g #0f08 =System.b ( vectors ) ,FRAME =Screen.vector - #1000 =Audio.ch1adsr - #66 =Audio.ch1vol - - #0003 =Audio.ch2adsr - #66 =Audio.ch2vol - BRK @FRAME @@ -43,13 +36,6 @@ BRK ~Time.second ~current.second NEQ #01 JNZ BRK ~Time.second =current.second - ( play sounds ) - #0d =Audio.ch1pitch - - ~Time.second #0f MOD #00 NEQ ^$no-tone JNZ - #0d #02 MUL =Audio.ch2pitch - $no-tone - ( clear ) #0080 SCALEX #0080 SCALEY ~needles.sx ~needles.sy #00 ,draw-line JSR2 #0080 SCALEX #0080 SCALEY ~needles.mx ~needles.my #00 ,draw-line JSR2 diff --git a/src/apu.c b/src/apu.c new file mode 100644 index 0000000..989c046 --- /dev/null +++ b/src/apu.c @@ -0,0 +1,164 @@ +#include +#include + +/* +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 +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE. +*/ + +#include "uxn.h" + +#define SAMPLE_FREQUENCY 48000 + +extern SDL_AudioDeviceID audio_id; +int error(char *msg, const char *err); + +static Uint32 note_advances[12] = { + 0x82d01286 / (SAMPLE_FREQUENCY / 30), /* C7 */ + 0x8a976073 / (SAMPLE_FREQUENCY / 30), + 0x92d5171d / (SAMPLE_FREQUENCY / 30), /* D7 */ + 0x9b904100 / (SAMPLE_FREQUENCY / 30), + 0xa4d053c8 / (SAMPLE_FREQUENCY / 30), /* E7 */ + 0xae9d36b0 / (SAMPLE_FREQUENCY / 30), /* F7 */ + 0xb8ff493e / (SAMPLE_FREQUENCY / 30), + 0xc3ff6a72 / (SAMPLE_FREQUENCY / 30), /* G7 */ + 0xcfa70054 / (SAMPLE_FREQUENCY / 30), + 0xdc000000 / (SAMPLE_FREQUENCY / 30), /* A7 */ + 0xe914f623 / (SAMPLE_FREQUENCY / 30), + 0xf6f11003 / (SAMPLE_FREQUENCY / 30) /* B7 */ +}; + +typedef struct { + Uint16 *dat; + Uint8 i, n, sz, ends; +} Queue; + +typedef struct { + Uint32 count, advance, period; + Uint16 vector; + Sint16 start_value, end_value; + Queue queue; +} WaveformGenerator; + +typedef struct { + WaveformGenerator wv[2]; + Sint8 volume[2], playing; +} Note; + +static Note *notes = NULL; +static int n_notes = 0; +static Queue *q; +static Uint16 id_addr; + +static void +play_note(Uxn *u, int note_i, Sint16 *samples, int n_samples) +{ + int i; + Note *note = ¬es[note_i]; + while(n_samples--) { + Sint32 sample = 1; + for(i = 0; i < 2; ++i) { + WaveformGenerator *wv = ¬e->wv[i]; + q = &wv->queue; + wv->count += wv->advance; + while(wv->count > wv->period) { + wv->count -= wv->period; + wv->start_value = wv->end_value; + if(q->i == q->n) { + q->i = q->n = 0; + if(!q->ends) { + u->ram.dat[id_addr] = note_i; + evaluxn(u, wv->vector); + } + } + if(!q->n) { + note->playing = 0; + return; + } + wv->end_value = (Sint16)q->dat[q->i++]; + wv->period = (30 << 4) * q->dat[q->i++]; + } + if(wv->period >> 9) + sample *= wv->start_value + (Sint32)(wv->end_value - wv->start_value) * (Sint32)(wv->count >> 10) / (Sint32)(wv->period >> 10); + else + sample *= wv->end_value; + } + for(i = 0; i < 2; ++i) + *(samples++) += sample / 0xf * note->volume[i] / 0x10000; + } +} + +static void +play_all_notes(void *u, Uint8 *stream, int len) +{ + int i; + SDL_memset(stream, 0, len); + for(i = 0; i < n_notes; ++i) + if(notes[i].playing) play_note(u, i, (Sint16 *)stream, len >> 2); + q = NULL; +} + +static Note * +get_note(Uint8 i) +{ + if(i >= n_notes) notes = SDL_realloc(notes, (i + 1) * sizeof(Note)); + while(i >= n_notes) SDL_zero(notes[n_notes++]); + return ¬es[i]; +} + +static Uint8 +audio_poke(Uxn *u, Uint16 ptr, Uint8 b0, Uint8 b1) +{ + Uint8 *m = u->ram.dat + ptr; + int i; + if(b0 == 0xa) { + Note *note = get_note(b1); + note->playing = 1; + for(i = 0; i < 2; ++i) { + note->volume[i] = 0xf & (m[0x8] >> 4 * (1 - i)); + note->wv[i].vector = (m[0x0 + i * 2] << 8) + m[0x1 + i * 2]; + note->wv[i].count = note->wv[i].period = 0; + note->wv[i].end_value = 0; + note->wv[i].queue.n = note->wv[i].queue.i = 0; + note->wv[i].queue.ends = 0; + } + note->wv[0].advance = note_advances[m[0x9] % 12] >> (8 - m[0x9] / 12); + note->wv[1].advance = (30 << 20) / SAMPLE_FREQUENCY; + } else if(b0 == 0xe && q != NULL) { + if(q->n == q->sz) { + q->sz = q->sz < 4 ? 4 : q->sz * 2; + q->dat = SDL_realloc(q->dat, q->sz * sizeof(*q->dat)); + } + q->dat[q->n++] = (m[0xb] << 8) + m[0xc]; + q->dat[q->n++] = (m[0xd] << 8) + b1; + } else if(b0 == 0xf && q != NULL) { + q->ends = 1; + return b1; + } +} + +int +initapu(Uxn *u, Uint8 id) +{ + SDL_AudioSpec as; + SDL_zero(as); + as.freq = SAMPLE_FREQUENCY; + as.format = AUDIO_S16; + as.channels = 2; + as.callback = play_all_notes; + as.samples = 2048; + as.userdata = u; + audio_id = SDL_OpenAudioDevice(NULL, 0, &as, NULL, 0); + if(!audio_id) + return error("Audio", SDL_GetError()); + id_addr = portuxn(u, id, "audio", audio_poke)->addr + 0xa; + SDL_PauseAudioDevice(audio_id, 0); + return 1; +} diff --git a/src/emulator.c b/src/emulator.c index a67535b..ba5f15a 100644 --- a/src/emulator.c +++ b/src/emulator.c @@ -15,6 +15,9 @@ WITH REGARD TO THIS SOFTWARE. #include "uxn.h" +int initapu(Uxn *u, Uint8 id); +void stepapu(Uxn *u); + #define HOR 48 #define VER 32 #define PAD 2 @@ -49,38 +52,12 @@ Uint8 font[][8] = { {0x00, 0x7e, 0x40, 0x7c, 0x40, 0x40, 0x7e, 0x00}, {0x00, 0x7e, 0x40, 0x40, 0x7c, 0x40, 0x40, 0x00}}; -#define SAMPLE_FREQUENCY 48000 - -static Uint32 note_periods[12] = { - /* middle C (C4) is note 60 */ - (Uint32)0xfa7e * SAMPLE_FREQUENCY, /* C-1 */ - (Uint32)0xec6f * SAMPLE_FREQUENCY, - (Uint32)0xdf2a * SAMPLE_FREQUENCY, /* D-1 */ - (Uint32)0xd2a4 * SAMPLE_FREQUENCY, - (Uint32)0xc6d1 * SAMPLE_FREQUENCY, /* E-1 */ - (Uint32)0xbba8 * SAMPLE_FREQUENCY, /* F-1 */ - (Uint32)0xb120 * SAMPLE_FREQUENCY, - (Uint32)0xa72f * SAMPLE_FREQUENCY, /* G-1 */ - (Uint32)0x9dcd * SAMPLE_FREQUENCY, - (Uint32)0x94f2 * SAMPLE_FREQUENCY, /* A-1 */ - (Uint32)0x8c95 * SAMPLE_FREQUENCY, - (Uint32)0x84b2 * SAMPLE_FREQUENCY /* B-1 */ -}; - -typedef struct audio_channel { - Uint32 period, count; - Sint32 age, a, d, s, r; - Sint16 value[2]; - Sint8 volume[2], phase; -} Channel; -Channel channels[4]; - static SDL_Window *gWindow; static SDL_Renderer *gRenderer; static SDL_Texture *gTexture; -static SDL_AudioDeviceID audio_id; +SDL_AudioDeviceID audio_id; static Screen screen; -static Device *devsystem, *devscreen, *devmouse, *devkey, *devctrl, *devaudio; +static Device *devsystem, *devscreen, *devmouse, *devkey, *devctrl; #pragma mark - Helpers @@ -241,61 +218,6 @@ togglezoom(Uxn *u) redraw(pixels, u); } -Sint16 -audio_envelope(Channel *c) -{ - if(c->age < c->a) - return 0x0888 * c->age / c->a; - else if(c->age < c->d) - return 0x0444 * (2 * c->d - c->a - c->age) / (c->d - c->a); - else if(c->age < c->s) - return 0x0444; - else if(c->age < c->r) - return 0x0444 * (c->r - c->age) / (c->r - c->s); - else - return 0x0000; -} - -void -audio_callback(void *userdata, Uint8 *stream, int len) -{ - Sint16 *samples = (Sint16 *)stream; - int i, j; - len >>= 2; /* use len for number of samples, not bytes */ - for(j = len * 2 - 1; j >= 0; --j) samples[j] = 0; - for(i = 0; i < 4; ++i) { - Channel *c = &channels[i]; - if(c->period < (1 << 20)) continue; - for(j = 0; j < len; ++j) { - c->age += 1; - c->count += 1 << 20; - while(c->count > c->period) { - Sint16 mul; - c->count -= c->period; - c->phase = !c->phase; - mul = (c->phase * 2 - 1) * audio_envelope(c); - c->value[0] = mul * c->volume[0]; - c->value[1] = mul * c->volume[1]; - } - samples[j * 2] += c->value[0]; - samples[j * 2 + 1] += c->value[1]; - } - } - (void)userdata; -} - -void -silence(void) -{ - int i; - for(i = 0; i < 4; ++i) { - Channel *c = &channels[i]; - c->volume[0] = 0; - c->volume[1] = 0; - c->period = 0; - } -} - void quit(void) { @@ -313,7 +235,6 @@ quit(void) int init(void) { - SDL_AudioSpec as; if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) return error("Init", SDL_GetError()); gWindow = SDL_CreateWindow("Uxn", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, WIDTH * ZOOM, HEIGHT * ZOOM, SDL_WINDOW_SHOWN); @@ -328,18 +249,8 @@ init(void) if(!(pixels = (Uint32 *)malloc(WIDTH * HEIGHT * sizeof(Uint32)))) return error("Pixels", "Failed to allocate memory"); clear(pixels); - silence(); SDL_StartTextInput(); SDL_ShowCursor(SDL_DISABLE); - as.freq = SAMPLE_FREQUENCY; - as.format = AUDIO_S16; - as.channels = 2; - as.callback = audio_callback; - as.samples = 2048; - audio_id = SDL_OpenAudioDevice(NULL, 0, &as, NULL, 0); - if(!audio_id) - return error("Audio", SDL_GetError()); - SDL_PauseAudioDevice(audio_id, 0); screen.x1 = PAD * 8; screen.x2 = WIDTH - PAD * 8 - 1; screen.y1 = PAD * 8; @@ -504,29 +415,6 @@ file_poke(Uxn *u, Uint16 ptr, Uint8 b0, Uint8 b1) return b1; } -Uint8 -audio_poke(Uxn *u, Uint16 ptr, Uint8 b0, Uint8 b1) -{ - Uint8 *m = u->ram.dat; - m[PAGE_DEVICE + 0x0070 + b0] = b1; - if(b0 > 0x08 && b0 & 1) { - Uint16 addr = ptr + (b0 & 0x6); - Channel *c = &channels[(b0 & 0x6) >> 1]; - SDL_LockAudioDevice(audio_id); - c->period = note_periods[m[addr + 9] % 12] >> (m[addr + 9] / 12); - c->count %= c->period; - c->volume[0] = (m[addr + 8] >> 4) & 0xf; - c->volume[1] = m[addr + 8] & 0xf; - c->age = 0; - c->a = (SAMPLE_FREQUENCY >> 4) * ((m[addr] >> 4) & 0xf); - c->d = c->a + (SAMPLE_FREQUENCY >> 4) * (m[addr] & 0xf); - c->s = c->d + (SAMPLE_FREQUENCY >> 4) * ((m[addr + 1] >> 4) & 0xf); - c->r = c->s + (SAMPLE_FREQUENCY >> 4) * (m[addr + 1] & 0xf); - SDL_UnlockAudioDevice(audio_id); - } - return b1; -} - Uint8 midi_poke(Uxn *u, Uint16 ptr, Uint8 b0, Uint8 b1) { @@ -586,9 +474,13 @@ start(Uxn *u) while(1) { SDL_Event event; double elapsed, start = SDL_GetPerformanceCounter(); + SDL_LockAudioDevice(audio_id); while(SDL_PollEvent(&event) != 0) { switch(event.type) { - case SDL_QUIT: quit(); break; + case SDL_QUIT: + SDL_UnlockAudioDevice(audio_id); + quit(); + break; case SDL_MOUSEBUTTONUP: case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEMOTION: @@ -611,6 +503,7 @@ start(Uxn *u) } } evaluxn(u, devscreen->vector); + SDL_UnlockAudioDevice(audio_id); if(screen.reqdraw) redraw(pixels, u); elapsed = (SDL_GetPerformanceCounter() - start) / (double)SDL_GetPerformanceFrequency() * 1000.0f; @@ -641,7 +534,8 @@ main(int argc, char **argv) devkey = portuxn(&u, 0x05, "key", ppnil); devmouse = portuxn(&u, 0x06, "mouse", ppnil); portuxn(&u, 0x07, "file", file_poke); - devaudio = portuxn(&u, 0x08, "audio", audio_poke); + if(!initapu(&u, 0x08)) + return 1; portuxn(&u, 0x09, "midi", ppnil); portuxn(&u, 0x0a, "datetime", datetime_poke); portuxn(&u, 0x0b, "---", ppnil);