Added Uxn-based synth

This commit is contained in:
Andrew Alderwick 2021-04-07 21:50:35 +01:00
parent f629564269
commit f3bf1a74db
6 changed files with 257 additions and 176 deletions

View File

@ -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.."

View File

@ -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 )

View File

@ -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 )

View File

@ -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

164
src/apu.c Normal file
View File

@ -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 = &notes[note_i];
while(n_samples--) {
Sint32 sample = 1;
for(i = 0; i < 2; ++i) {
WaveformGenerator *wv = &note->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 &notes[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;
}

View File

@ -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);