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