commit f3bf1a74db17151610327c134f6241b07bff7fb0
parent f62956426932a1ab8068adfe2fb4a54f6fe1cb73
Author: Andrew Alderwick <andrew@alderwick.co.uk>
Date: Wed, 7 Apr 2021 21:50:35 +0100
Added Uxn-based synth
Diffstat:
6 files changed, 257 insertions(+), 176 deletions(-)
diff --git 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
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,164 @@
+#include <SDL2/SDL.h>
+#include <stdlib.h>
+
+/*
+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
@@ -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;
@@ -505,29 +416,6 @@ file_poke(Uxn *u, Uint16 ptr, Uint8 b0, Uint8 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)
{
(void)u;
@@ -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);