... | ... |
@@ -1,5 +1,3 @@ |
1 |
-#include <SDL2/SDL.h> |
|
2 |
- |
|
3 | 1 |
/* |
4 | 2 |
Copyright (c) 2021 Devine Lu Linvega |
5 | 3 |
Copyright (c) 2021 Andrew Alderwick |
... | ... |
@@ -13,11 +11,9 @@ WITH REGARD TO THIS SOFTWARE. |
13 | 11 |
*/ |
14 | 12 |
|
15 | 13 |
#include "uxn.h" |
14 |
+#include "apu.h" |
|
16 | 15 |
|
17 |
-#define SAMPLE_FREQUENCY 48000 |
|
18 |
- |
|
19 |
-extern SDL_AudioDeviceID audio_id; |
|
20 |
-int error(char *msg, const char *err); |
|
16 |
+extern Device *devapu; |
|
21 | 17 |
|
22 | 18 |
static Uint32 note_advances[12] = { |
23 | 19 |
0x82d01286 / (SAMPLE_FREQUENCY / 30), /* C7 */ |
... | ... |
@@ -34,55 +30,33 @@ static Uint32 note_advances[12] = { |
34 | 30 |
0xf6f11003 / (SAMPLE_FREQUENCY / 30) /* B7 */ |
35 | 31 |
}; |
36 | 32 |
|
37 |
-typedef struct { |
|
38 |
- Uint16 *dat; |
|
39 |
- Uint8 i, n, sz, ends; |
|
40 |
-} Queue; |
|
41 |
- |
|
42 |
-typedef struct { |
|
43 |
- Uint32 count, advance, period; |
|
44 |
- Uint16 vector; |
|
45 |
- Sint16 start_value, end_value; |
|
46 |
- Queue queue; |
|
47 |
-} WaveformGenerator; |
|
48 |
- |
|
49 |
-typedef struct { |
|
50 |
- WaveformGenerator wv[2]; |
|
51 |
- Sint8 volume[2], playing; |
|
52 |
-} Note; |
|
53 |
- |
|
54 |
-static Note *notes = NULL; |
|
55 |
-static int n_notes = 0; |
|
56 |
-static Queue *q; |
|
57 |
-static Uint16 id_addr; |
|
58 |
- |
|
59 | 33 |
static void |
60 |
-play_note(Uxn *u, int note_i, Sint16 *samples, int n_samples) |
|
34 |
+render_note(Apu *apu, Uxn *u, int note_i, Sint16 *samples, int n_samples) |
|
61 | 35 |
{ |
62 | 36 |
int i; |
63 |
- Note *note = ¬es[note_i]; |
|
37 |
+ Note *note = &apu->notes[note_i]; |
|
64 | 38 |
while(n_samples--) { |
65 | 39 |
Sint32 sample = 1; |
66 | 40 |
for(i = 0; i < 2; ++i) { |
67 | 41 |
WaveformGenerator *wv = ¬e->wv[i]; |
68 |
- q = &wv->queue; |
|
42 |
+ apu->queue = &wv->queue; |
|
69 | 43 |
wv->count += wv->advance; |
70 | 44 |
while(wv->count > wv->period) { |
71 | 45 |
wv->count -= wv->period; |
72 | 46 |
wv->start_value = wv->end_value; |
73 |
- if(q->i == q->n) { |
|
74 |
- q->i = q->n = 0; |
|
75 |
- if(!q->ends) { |
|
76 |
- u->ram.dat[id_addr] = note_i; |
|
47 |
+ if(apu->queue->i == apu->queue->n) { |
|
48 |
+ apu->queue->i = apu->queue->n = 0; |
|
49 |
+ if(!apu->queue->finishes) { |
|
50 |
+ u->ram.dat[devapu->addr + 0xa] = note_i; |
|
77 | 51 |
evaluxn(u, wv->vector); |
78 | 52 |
} |
79 | 53 |
} |
80 |
- if(!q->n) { |
|
54 |
+ if(!apu->queue->n) { |
|
81 | 55 |
note->playing = 0; |
82 | 56 |
return; |
83 | 57 |
} |
84 |
- wv->end_value = (Sint16)q->dat[q->i++]; |
|
85 |
- wv->period = (30 << 4) * q->dat[q->i++]; |
|
58 |
+ wv->end_value = (Sint16)apu->queue->dat[apu->queue->i++]; |
|
59 |
+ wv->period = (30 << 4) * apu->queue->dat[apu->queue->i++]; |
|
86 | 60 |
} |
87 | 61 |
if(wv->period >> 9) |
88 | 62 |
sample *= wv->start_value + (Sint32)(wv->end_value - wv->start_value) * (Sint32)(wv->count >> 10) / (Sint32)(wv->period >> 10); |
... | ... |
@@ -94,70 +68,31 @@ play_note(Uxn *u, int note_i, Sint16 *samples, int n_samples) |
94 | 68 |
} |
95 | 69 |
} |
96 | 70 |
|
97 |
-static void |
|
98 |
-play_all_notes(void *u, Uint8 *stream, int len) |
|
71 |
+void |
|
72 |
+apu_render(Apu *apu, Uxn *u, Sint16 *samples, int n_samples) |
|
99 | 73 |
{ |
100 | 74 |
int i; |
101 |
- SDL_memset(stream, 0, len); |
|
102 |
- for(i = 0; i < n_notes; ++i) |
|
103 |
- if(notes[i].playing) play_note(u, i, (Sint16 *)stream, len >> 2); |
|
104 |
- q = NULL; |
|
105 |
-} |
|
106 |
- |
|
107 |
-static Note * |
|
108 |
-get_note(Uint8 i) |
|
109 |
-{ |
|
110 |
- if(i >= n_notes) notes = SDL_realloc(notes, (i + 1) * sizeof(Note)); |
|
111 |
- while(i >= n_notes) SDL_zero(notes[n_notes++]); |
|
112 |
- return ¬es[i]; |
|
75 |
+ for(i = 0; i < n_samples * 2; ++i) |
|
76 |
+ samples[i] = 0; |
|
77 |
+ for(i = 0; i < apu->n_notes; ++i) |
|
78 |
+ if(apu->notes[i].playing) render_note(apu, u, i, samples, n_samples); |
|
79 |
+ apu->queue = NULL; |
|
113 | 80 |
} |
114 | 81 |
|
115 |
-static Uint8 |
|
116 |
-audio_poke(Uxn *u, Uint16 ptr, Uint8 b0, Uint8 b1) |
|
82 |
+void |
|
83 |
+apu_play_note(Note *note, Uint16 wave_vector, Uint16 envelope_vector, Uint8 volume, Uint8 pitch) |
|
117 | 84 |
{ |
118 |
- Uint8 *m = u->ram.dat + ptr; |
|
119 | 85 |
int i; |
120 |
- if(b0 == 0xa) { |
|
121 |
- Note *note = get_note(b1); |
|
122 |
- note->playing = 1; |
|
123 |
- for(i = 0; i < 2; ++i) { |
|
124 |
- note->volume[i] = 0xf & (m[0x8] >> 4 * (1 - i)); |
|
125 |
- note->wv[i].vector = (m[0x0 + i * 2] << 8) + m[0x1 + i * 2]; |
|
126 |
- note->wv[i].count = note->wv[i].period = 0; |
|
127 |
- note->wv[i].end_value = 0; |
|
128 |
- note->wv[i].queue.n = note->wv[i].queue.i = 0; |
|
129 |
- note->wv[i].queue.ends = 0; |
|
130 |
- } |
|
131 |
- note->wv[0].advance = note_advances[m[0x9] % 12] >> (8 - m[0x9] / 12); |
|
132 |
- note->wv[1].advance = (30 << 20) / SAMPLE_FREQUENCY; |
|
133 |
- } else if(b0 == 0xe && q != NULL) { |
|
134 |
- if(q->n == q->sz) { |
|
135 |
- q->sz = q->sz < 4 ? 4 : q->sz * 2; |
|
136 |
- q->dat = SDL_realloc(q->dat, q->sz * sizeof(*q->dat)); |
|
137 |
- } |
|
138 |
- q->dat[q->n++] = (m[0xb] << 8) + m[0xc]; |
|
139 |
- q->dat[q->n++] = (m[0xd] << 8) + b1; |
|
140 |
- } else if(b0 == 0xf && q != NULL) { |
|
141 |
- q->ends = 1; |
|
86 |
+ note->playing = 1; |
|
87 |
+ for(i = 0; i < 2; ++i) { |
|
88 |
+ note->volume[i] = 0xf & (volume >> 4 * (1 - i)); |
|
89 |
+ note->wv[i].count = note->wv[i].period = 0; |
|
90 |
+ note->wv[i].end_value = 0; |
|
91 |
+ note->wv[i].queue.n = note->wv[i].queue.i = 0; |
|
92 |
+ note->wv[i].queue.finishes = 0; |
|
142 | 93 |
} |
143 |
- return b1; |
|
144 |
-} |
|
145 |
- |
|
146 |
-int |
|
147 |
-initapu(Uxn *u, Uint8 id) |
|
148 |
-{ |
|
149 |
- SDL_AudioSpec as; |
|
150 |
- SDL_zero(as); |
|
151 |
- as.freq = SAMPLE_FREQUENCY; |
|
152 |
- as.format = AUDIO_S16; |
|
153 |
- as.channels = 2; |
|
154 |
- as.callback = play_all_notes; |
|
155 |
- as.samples = 2048; |
|
156 |
- as.userdata = u; |
|
157 |
- audio_id = SDL_OpenAudioDevice(NULL, 0, &as, NULL, 0); |
|
158 |
- if(!audio_id) |
|
159 |
- return error("Audio", SDL_GetError()); |
|
160 |
- id_addr = portuxn(u, id, "audio", audio_poke)->addr + 0xa; |
|
161 |
- SDL_PauseAudioDevice(audio_id, 0); |
|
162 |
- return 1; |
|
94 |
+ note->wv[0].vector = wave_vector; |
|
95 |
+ note->wv[0].advance = note_advances[pitch % 12] >> (8 - pitch / 12); |
|
96 |
+ note->wv[1].vector = envelope_vector; |
|
97 |
+ note->wv[1].advance = (30 << 20) / SAMPLE_FREQUENCY; |
|
163 | 98 |
} |
164 | 99 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,46 @@ |
1 |
+/* |
|
2 |
+Copyright (c) 2021 Devine Lu Linvega |
|
3 |
+Copyright (c) 2021 Andrew Alderwick |
|
4 |
+ |
|
5 |
+Permission to use, copy, modify, and distribute this software for any |
|
6 |
+purpose with or without fee is hereby granted, provided that the above |
|
7 |
+copyright notice and this permission notice appear in all copies. |
|
8 |
+ |
|
9 |
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
|
10 |
+WITH REGARD TO THIS SOFTWARE. |
|
11 |
+*/ |
|
12 |
+ |
|
13 |
+typedef unsigned char Uint8; |
|
14 |
+typedef signed char Sint8; |
|
15 |
+typedef unsigned short Uint16; |
|
16 |
+typedef signed short Sint16; |
|
17 |
+typedef unsigned int Uint32; |
|
18 |
+typedef signed int Sint32; |
|
19 |
+ |
|
20 |
+#define SAMPLE_FREQUENCY 48000 |
|
21 |
+ |
|
22 |
+typedef struct { |
|
23 |
+ Uint16 *dat; |
|
24 |
+ Uint8 i, n, sz, finishes; |
|
25 |
+} Queue; |
|
26 |
+ |
|
27 |
+typedef struct { |
|
28 |
+ Uint32 count, advance, period; |
|
29 |
+ Uint16 vector; |
|
30 |
+ Sint16 start_value, end_value; |
|
31 |
+ Queue queue; |
|
32 |
+} WaveformGenerator; |
|
33 |
+ |
|
34 |
+typedef struct { |
|
35 |
+ WaveformGenerator wv[2]; |
|
36 |
+ Sint8 volume[2], playing; |
|
37 |
+} Note; |
|
38 |
+ |
|
39 |
+typedef struct { |
|
40 |
+ Queue *queue; |
|
41 |
+ Note *notes; |
|
42 |
+ int n_notes; |
|
43 |
+} Apu; |
|
44 |
+ |
|
45 |
+void apu_render(Apu *apu, Uxn *u, Sint16 *samples, int n_samples); |
|
46 |
+void apu_play_note(Note *note, Uint16 wave_vector, Uint16 envelope_vector, Uint8 volume, Uint8 pitch); |
... | ... |
@@ -15,15 +15,18 @@ WITH REGARD TO THIS SOFTWARE. |
15 | 15 |
|
16 | 16 |
#include "uxn.h" |
17 | 17 |
#include "ppu.h" |
18 |
+#include "apu.h" |
|
18 | 19 |
|
19 | 20 |
int initapu(Uxn *u, Uint8 id); |
20 | 21 |
|
21 |
-SDL_AudioDeviceID audio_id; |
|
22 |
+static SDL_AudioDeviceID audio_id; |
|
22 | 23 |
static SDL_Window *gWindow; |
23 | 24 |
static SDL_Renderer *gRenderer; |
24 | 25 |
static SDL_Texture *gTexture; |
25 | 26 |
static Ppu ppu; |
27 |
+static Apu apu; |
|
26 | 28 |
static Device *devsystem, *devscreen, *devmouse, *devkey, *devctrl; |
29 |
+Device *devapu; |
|
27 | 30 |
|
28 | 31 |
#pragma mark - Helpers |
29 | 32 |
|
... | ... |
@@ -41,6 +44,12 @@ error(char *msg, const char *err) |
41 | 44 |
return 0; |
42 | 45 |
} |
43 | 46 |
|
47 |
+static void |
|
48 |
+audio_callback(void *u, Uint8 *stream, int len) |
|
49 |
+{ |
|
50 |
+ apu_render(&apu, (Uxn *)u, (Sint16 *)stream, len >> 2); |
|
51 |
+} |
|
52 |
+ |
|
44 | 53 |
void |
45 | 54 |
redraw(Uint32 *dst, Uxn *u) |
46 | 55 |
{ |
... | ... |
@@ -86,8 +95,9 @@ quit(void) |
86 | 95 |
} |
87 | 96 |
|
88 | 97 |
int |
89 |
-init(void) |
|
98 |
+init(Uxn *u) |
|
90 | 99 |
{ |
100 |
+ SDL_AudioSpec as; |
|
91 | 101 |
if(!initppu(&ppu, 48, 32, 16)) |
92 | 102 |
return error("PPU", "Init failure"); |
93 | 103 |
if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) |
... | ... |
@@ -103,6 +113,17 @@ init(void) |
103 | 113 |
return error("Texture", SDL_GetError()); |
104 | 114 |
SDL_StartTextInput(); |
105 | 115 |
SDL_ShowCursor(SDL_DISABLE); |
116 |
+ SDL_zero(as); |
|
117 |
+ as.freq = SAMPLE_FREQUENCY; |
|
118 |
+ as.format = AUDIO_S16; |
|
119 |
+ as.channels = 2; |
|
120 |
+ as.callback = audio_callback; |
|
121 |
+ as.samples = 2048; |
|
122 |
+ as.userdata = u; |
|
123 |
+ audio_id = SDL_OpenAudioDevice(NULL, 0, &as, NULL, 0); |
|
124 |
+ if(!audio_id) |
|
125 |
+ return error("Audio", SDL_GetError()); |
|
126 |
+ SDL_PauseAudioDevice(audio_id, 0); |
|
106 | 127 |
return 1; |
107 | 128 |
} |
108 | 129 |
|
... | ... |
@@ -258,6 +279,27 @@ file_poke(Uxn *u, Uint16 ptr, Uint8 b0, Uint8 b1) |
258 | 279 |
return b1; |
259 | 280 |
} |
260 | 281 |
|
282 |
+static Uint8 |
|
283 |
+audio_poke(Uxn *u, Uint16 ptr, Uint8 b0, Uint8 b1) |
|
284 |
+{ |
|
285 |
+ Uint8 *m = u->ram.dat + ptr; |
|
286 |
+ if(b0 == 0xa) { |
|
287 |
+ if(b1 >= apu.n_notes) apu.notes = SDL_realloc(apu.notes, (b1 + 1) * sizeof(Note)); |
|
288 |
+ while(b1 >= apu.n_notes) SDL_zero(apu.notes[apu.n_notes++]); |
|
289 |
+ apu_play_note(&apu.notes[b1], (m[0x0] << 8) + m[0x1], (m[0x2] << 8) + m[0x3], m[0x8], m[0x9]); |
|
290 |
+ } else if(b0 == 0xe && apu.queue != NULL) { |
|
291 |
+ if(apu.queue->n == apu.queue->sz) { |
|
292 |
+ apu.queue->sz = apu.queue->sz < 4 ? 4 : apu.queue->sz * 2; |
|
293 |
+ apu.queue->dat = SDL_realloc(apu.queue->dat, apu.queue->sz * sizeof(*apu.queue->dat)); |
|
294 |
+ } |
|
295 |
+ apu.queue->dat[apu.queue->n++] = (m[0xb] << 8) + m[0xc]; |
|
296 |
+ apu.queue->dat[apu.queue->n++] = (m[0xd] << 8) + b1; |
|
297 |
+ } else if(b0 == 0xf && apu.queue != NULL) { |
|
298 |
+ apu.queue->finishes = 1; |
|
299 |
+ } |
|
300 |
+ return b1; |
|
301 |
+} |
|
302 |
+ |
|
261 | 303 |
Uint8 |
262 | 304 |
midi_poke(Uxn *u, Uint16 ptr, Uint8 b0, Uint8 b1) |
263 | 305 |
{ |
... | ... |
@@ -367,7 +409,7 @@ main(int argc, char **argv) |
367 | 409 |
return error("Boot", "Failed"); |
368 | 410 |
if(!loaduxn(&u, argv[1])) |
369 | 411 |
return error("Load", "Failed"); |
370 |
- if(!init()) |
|
412 |
+ if(!init(&u)) |
|
371 | 413 |
return error("Init", "Failed"); |
372 | 414 |
|
373 | 415 |
devsystem = portuxn(&u, 0x00, "system", system_poke); |
... | ... |
@@ -378,8 +420,7 @@ main(int argc, char **argv) |
378 | 420 |
devkey = portuxn(&u, 0x05, "key", ppnil); |
379 | 421 |
devmouse = portuxn(&u, 0x06, "mouse", ppnil); |
380 | 422 |
portuxn(&u, 0x07, "file", file_poke); |
381 |
- if(!initapu(&u, 0x08)) |
|
382 |
- return 1; |
|
423 |
+ devapu = portuxn(&u, 0x08, "audio", audio_poke); |
|
383 | 424 |
portuxn(&u, 0x09, "midi", ppnil); |
384 | 425 |
portuxn(&u, 0x0a, "datetime", datetime_poke); |
385 | 426 |
portuxn(&u, 0x0b, "---", ppnil); |