Update minigb_apu to S16 version & add comments
Signed-off-by: Mahyar Koshkouei <mk@deltabeard.com>
This commit is contained in:
+227
-176
@@ -5,19 +5,32 @@
|
|||||||
* project is based on MiniGBS by Alex Baines: https://github.com/baines/MiniGBS
|
* project is based on MiniGBS by Alex Baines: https://github.com/baines/MiniGBS
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <math.h>
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "minigb_apu.h"
|
#include "minigb_apu.h"
|
||||||
|
|
||||||
|
#define DMG_CLOCK_FREQ_U ((unsigned)DMG_CLOCK_FREQ)
|
||||||
|
#define AUDIO_NSAMPLES (AUDIO_SAMPLES * 2u)
|
||||||
|
|
||||||
#define AUDIO_MEM_SIZE (0xFF3F - 0xFF10 + 1)
|
#define AUDIO_MEM_SIZE (0xFF3F - 0xFF10 + 1)
|
||||||
#define AUDIO_ADDR_COMPENSATION 0xFF10
|
#define AUDIO_ADDR_COMPENSATION 0xFF10
|
||||||
|
|
||||||
#define MAX(a, b) ( a > b ? a : b )
|
#define MAX(a, b) ( a > b ? a : b )
|
||||||
#define MIN(a, b) ( a <= b ? a : b )
|
#define MIN(a, b) ( a <= b ? a : b )
|
||||||
|
|
||||||
|
#define VOL_INIT_MAX (INT16_MAX/8)
|
||||||
|
#define VOL_INIT_MIN (INT16_MIN/8)
|
||||||
|
|
||||||
|
/* Handles time keeping for sound generation.
|
||||||
|
* FREQ_INC_REF must be equal to, or larger than AUDIO_SAMPLE_RATE in order
|
||||||
|
* to avoid a division by zero error.
|
||||||
|
* Using a square of 2 simplifies calculations. */
|
||||||
|
#define FREQ_INC_REF (AUDIO_SAMPLE_RATE * 16)
|
||||||
|
|
||||||
|
#define MAX_CHAN_VOLUME 15
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Memory holding audio registers between 0xFF10 and 0xFF3F inclusive.
|
* Memory holding audio registers between 0xFF10 and 0xFF3F inclusive.
|
||||||
*/
|
*/
|
||||||
@@ -25,128 +38,120 @@ static uint8_t audio_mem[AUDIO_MEM_SIZE];
|
|||||||
|
|
||||||
struct chan_len_ctr {
|
struct chan_len_ctr {
|
||||||
uint8_t load;
|
uint8_t load;
|
||||||
bool enabled;
|
unsigned enabled : 1;
|
||||||
float counter;
|
uint32_t counter;
|
||||||
float inc;
|
uint32_t inc;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct chan_vol_env {
|
struct chan_vol_env {
|
||||||
uint8_t step;
|
uint8_t step;
|
||||||
bool up;
|
unsigned up : 1;
|
||||||
float counter;
|
uint32_t counter;
|
||||||
float inc;
|
uint32_t inc;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct chan_freq_sweep {
|
struct chan_freq_sweep {
|
||||||
uint_fast16_t freq;
|
uint16_t freq;
|
||||||
uint8_t rate;
|
uint8_t rate;
|
||||||
uint8_t shift;
|
uint8_t shift;
|
||||||
bool up;
|
unsigned up : 1;
|
||||||
float counter;
|
uint32_t counter;
|
||||||
float inc;
|
uint32_t inc;
|
||||||
};
|
};
|
||||||
|
|
||||||
static struct chan {
|
static struct chan {
|
||||||
bool enabled;
|
unsigned enabled : 1;
|
||||||
bool powered;
|
unsigned powered : 1;
|
||||||
bool on_left;
|
unsigned on_left : 1;
|
||||||
bool on_right;
|
unsigned on_right : 1;
|
||||||
bool muted;
|
unsigned muted : 1;
|
||||||
|
|
||||||
uint8_t volume;
|
uint8_t volume;
|
||||||
uint8_t volume_init;
|
uint8_t volume_init;
|
||||||
|
|
||||||
uint16_t freq;
|
uint16_t freq;
|
||||||
float freq_counter;
|
uint32_t freq_counter;
|
||||||
float freq_inc;
|
uint32_t freq_inc;
|
||||||
|
|
||||||
int_fast8_t val;
|
int_fast16_t val;
|
||||||
|
|
||||||
struct chan_len_ctr len;
|
struct chan_len_ctr len;
|
||||||
struct chan_vol_env env;
|
struct chan_vol_env env;
|
||||||
struct chan_freq_sweep sweep;
|
struct chan_freq_sweep sweep;
|
||||||
|
|
||||||
// square
|
union {
|
||||||
|
struct {
|
||||||
uint8_t duty;
|
uint8_t duty;
|
||||||
uint8_t duty_counter;
|
uint8_t duty_counter;
|
||||||
|
} square;
|
||||||
// noise
|
struct {
|
||||||
uint16_t lfsr_reg;
|
uint16_t lfsr_reg;
|
||||||
uint8_t lfsr_wide;
|
uint8_t lfsr_wide;
|
||||||
uint8_t lfsr_div;
|
uint8_t lfsr_div;
|
||||||
|
} noise;
|
||||||
// wave
|
struct {
|
||||||
uint8_t sample;
|
uint8_t sample;
|
||||||
|
} wave;
|
||||||
#if ENABLE_HIPASS
|
};
|
||||||
float capacitor;
|
|
||||||
#endif
|
|
||||||
} chans[4];
|
} chans[4];
|
||||||
|
|
||||||
static float vol_l, vol_r;
|
static int32_t vol_l, vol_r;
|
||||||
|
|
||||||
static float hipass(struct chan *c, float sample)
|
static void set_note_freq(struct chan *c, const uint32_t freq)
|
||||||
{
|
{
|
||||||
#if ENABLE_HIPASS
|
/* Lowest expected value of freq is 64. */
|
||||||
float out = sample - c->capacitor;
|
c->freq_inc = freq * (uint32_t)(FREQ_INC_REF / AUDIO_SAMPLE_RATE);
|
||||||
c->capacitor = sample - out * 0.996f;
|
|
||||||
return out;
|
|
||||||
#else
|
|
||||||
return sample;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
static void set_note_freq(struct chan *c, const uint_fast16_t freq)
|
|
||||||
{
|
|
||||||
c->freq_inc = freq / AUDIO_SAMPLE_RATE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void chan_enable(const uint_fast8_t i, const bool enable)
|
static void chan_enable(const uint_fast8_t i, const bool enable)
|
||||||
{
|
{
|
||||||
chans[i].enabled = enable;
|
uint8_t val;
|
||||||
|
|
||||||
uint8_t val = (audio_mem[0xFF26 - AUDIO_ADDR_COMPENSATION] & 0x80) |
|
chans[i].enabled = enable;
|
||||||
|
val = (audio_mem[0xFF26 - AUDIO_ADDR_COMPENSATION] & 0x80) |
|
||||||
(chans[3].enabled << 3) | (chans[2].enabled << 2) |
|
(chans[3].enabled << 3) | (chans[2].enabled << 2) |
|
||||||
(chans[1].enabled << 1) | (chans[0].enabled << 0);
|
(chans[1].enabled << 1) | (chans[0].enabled << 0);
|
||||||
|
|
||||||
audio_mem[0xFF26 - AUDIO_ADDR_COMPENSATION] = val;
|
audio_mem[0xFF26 - AUDIO_ADDR_COMPENSATION] = val;
|
||||||
|
//audio_mem[0xFF26 - AUDIO_ADDR_COMPENSATION] |= 0x80 | ((uint8_t)enable) << i;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void update_env(struct chan *c)
|
static void update_env(struct chan *c)
|
||||||
{
|
{
|
||||||
c->env.counter += c->env.inc;
|
c->env.counter += c->env.inc;
|
||||||
|
|
||||||
while (c->env.counter > 1.0f) {
|
while (c->env.counter > FREQ_INC_REF) {
|
||||||
if (c->env.step) {
|
if (c->env.step) {
|
||||||
c->volume += c->env.up ? 1 : -1;
|
c->volume += c->env.up ? 1 : -1;
|
||||||
if (c->volume == 0 || c->volume == 15) {
|
if (c->volume == 0 || c->volume == MAX_CHAN_VOLUME) {
|
||||||
c->env.inc = 0;
|
c->env.inc = 0;
|
||||||
}
|
}
|
||||||
c->volume = MAX(0, MIN(15, c->volume));
|
c->volume = MAX(0, MIN(MAX_CHAN_VOLUME, c->volume));
|
||||||
}
|
}
|
||||||
c->env.counter -= 1.0f;
|
c->env.counter -= FREQ_INC_REF;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void update_len(struct chan *c)
|
static void update_len(struct chan *c)
|
||||||
{
|
{
|
||||||
if (c->len.enabled) {
|
if (!c->len.enabled)
|
||||||
|
return;
|
||||||
|
|
||||||
c->len.counter += c->len.inc;
|
c->len.counter += c->len.inc;
|
||||||
if (c->len.counter > 1.0f) {
|
if (c->len.counter > FREQ_INC_REF) {
|
||||||
chan_enable(c - chans, 0);
|
chan_enable(c - chans, 0);
|
||||||
c->len.counter = 0.0f;
|
c->len.counter = 0;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool update_freq(struct chan *c, float *pos)
|
static bool update_freq(struct chan *c, uint32_t *pos)
|
||||||
{
|
{
|
||||||
float inc = c->freq_inc - *pos;
|
uint32_t inc = c->freq_inc - *pos;
|
||||||
c->freq_counter += inc;
|
c->freq_counter += inc;
|
||||||
|
|
||||||
if (c->freq_counter > 1.0f) {
|
if (c->freq_counter > FREQ_INC_REF) {
|
||||||
*pos = c->freq_inc - (c->freq_counter - 1.0f);
|
*pos = c->freq_inc - (c->freq_counter - FREQ_INC_REF);
|
||||||
c->freq_counter = 0.0f;
|
c->freq_counter = 0;
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
*pos = c->freq_inc;
|
*pos = c->freq_inc;
|
||||||
@@ -158,7 +163,7 @@ static void update_sweep(struct chan *c)
|
|||||||
{
|
{
|
||||||
c->sweep.counter += c->sweep.inc;
|
c->sweep.counter += c->sweep.inc;
|
||||||
|
|
||||||
while (c->sweep.counter > 1.0f) {
|
while (c->sweep.counter > FREQ_INC_REF) {
|
||||||
if (c->sweep.shift) {
|
if (c->sweep.shift) {
|
||||||
uint16_t inc = (c->sweep.freq >> c->sweep.shift);
|
uint16_t inc = (c->sweep.freq >> c->sweep.shift);
|
||||||
if (!c->sweep.up)
|
if (!c->sweep.up)
|
||||||
@@ -169,64 +174,68 @@ static void update_sweep(struct chan *c)
|
|||||||
c->enabled = 0;
|
c->enabled = 0;
|
||||||
} else {
|
} else {
|
||||||
set_note_freq(c,
|
set_note_freq(c,
|
||||||
4194304 / ((2048 - c->freq)<< 5));
|
DMG_CLOCK_FREQ_U / ((2048 - c->freq)<< 5));
|
||||||
c->freq_inc *= 8.0f;
|
c->freq_inc *= 8;
|
||||||
}
|
}
|
||||||
} else if (c->sweep.rate) {
|
} else if (c->sweep.rate) {
|
||||||
c->enabled = 0;
|
c->enabled = 0;
|
||||||
}
|
}
|
||||||
c->sweep.counter -= 1.0f;
|
c->sweep.counter -= FREQ_INC_REF;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void update_square(float *restrict samples, const bool ch2)
|
static void update_square(int16_t* samples, const bool ch2)
|
||||||
{
|
{
|
||||||
|
uint32_t freq;
|
||||||
struct chan* c = chans + ch2;
|
struct chan* c = chans + ch2;
|
||||||
if (!c->powered)
|
|
||||||
|
if (!c->powered || !c->enabled)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
set_note_freq(c, 4194304.0f / ((2048 - c->freq) << 5));
|
freq = DMG_CLOCK_FREQ_U / ((2048 - c->freq) << 5);
|
||||||
c->freq_inc *= 8.0f;
|
set_note_freq(c, freq);
|
||||||
|
c->freq_inc *= 8;
|
||||||
|
|
||||||
for (uint_fast16_t i = 0; i < AUDIO_NSAMPLES; i += 2) {
|
for (uint_fast16_t i = 0; i < AUDIO_NSAMPLES; i += 2) {
|
||||||
update_len(c);
|
update_len(c);
|
||||||
|
|
||||||
if (c->enabled) {
|
if (!c->enabled)
|
||||||
|
continue;
|
||||||
|
|
||||||
update_env(c);
|
update_env(c);
|
||||||
if (!ch2)
|
if (!ch2)
|
||||||
update_sweep(c);
|
update_sweep(c);
|
||||||
|
|
||||||
float pos = 0.0f;
|
uint32_t pos = 0;
|
||||||
float prev_pos = 0.0f;
|
uint32_t prev_pos = 0;
|
||||||
float sample = 0.0f;
|
int32_t sample = 0;
|
||||||
|
|
||||||
while (update_freq(c, &pos)) {
|
while (update_freq(c, &pos)) {
|
||||||
c->duty_counter = (c->duty_counter + 1) & 7;
|
c->square.duty_counter = (c->square.duty_counter + 1) & 7;
|
||||||
sample += ((pos - prev_pos) / c->freq_inc) *
|
sample += ((pos - prev_pos) / c->freq_inc) * c->val;
|
||||||
(float)c->val;
|
c->val = (c->square.duty & (1 << c->square.duty_counter)) ?
|
||||||
c->val = (c->duty & (1 << c->duty_counter)) ?
|
VOL_INIT_MAX / MAX_CHAN_VOLUME :
|
||||||
1 :
|
VOL_INIT_MIN / MAX_CHAN_VOLUME;
|
||||||
-1;
|
|
||||||
prev_pos = pos;
|
prev_pos = pos;
|
||||||
}
|
}
|
||||||
sample += ((pos - prev_pos) / c->freq_inc) *
|
|
||||||
(float)c->val;
|
|
||||||
sample = hipass(c, sample * (c->volume / 15.0f));
|
|
||||||
|
|
||||||
if (!c->muted) {
|
if (c->muted)
|
||||||
samples[i + 0] +=
|
continue;
|
||||||
sample * 0.25f * c->on_left * vol_l;
|
|
||||||
samples[i + 1] +=
|
sample += c->val;
|
||||||
sample * 0.25f * c->on_right * vol_r;
|
sample *= c->volume;
|
||||||
}
|
sample /= 4;
|
||||||
}
|
|
||||||
|
samples[i + 0] += sample * c->on_left * vol_l;
|
||||||
|
samples[i + 1] += sample * c->on_right * vol_r;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint8_t wave_sample(const unsigned int pos, const unsigned int volume)
|
static uint8_t wave_sample(const unsigned int pos, const unsigned int volume)
|
||||||
{
|
{
|
||||||
uint8_t sample =
|
uint8_t sample;
|
||||||
audio_mem[(0xFF30 + pos / 2) - AUDIO_ADDR_COMPENSATION];
|
|
||||||
|
sample = audio_mem[(0xFF30 + pos / 2) - AUDIO_ADDR_COMPENSATION];
|
||||||
if (pos & 1) {
|
if (pos & 1) {
|
||||||
sample &= 0xF;
|
sample &= 0xF;
|
||||||
} else {
|
} else {
|
||||||
@@ -235,63 +244,76 @@ static uint8_t wave_sample(const unsigned int pos, const unsigned int volume)
|
|||||||
return volume ? (sample >> (volume - 1)) : 0;
|
return volume ? (sample >> (volume - 1)) : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void update_wave(float *restrict samples)
|
static void update_wave(int16_t *samples)
|
||||||
{
|
{
|
||||||
|
uint32_t freq;
|
||||||
struct chan *c = chans + 2;
|
struct chan *c = chans + 2;
|
||||||
if (!c->powered)
|
|
||||||
|
if (!c->powered || !c->enabled)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
uint_fast16_t freq = 4194304.0f / ((2048 - c->freq) << 5);
|
freq = (DMG_CLOCK_FREQ_U / 64) / (2048 - c->freq);
|
||||||
set_note_freq(c, freq);
|
set_note_freq(c, freq);
|
||||||
|
|
||||||
c->freq_inc *= 16.0f;
|
c->freq_inc *= 32;
|
||||||
|
|
||||||
for (uint_fast16_t i = 0; i < AUDIO_NSAMPLES; i += 2) {
|
for (uint_fast16_t i = 0; i < AUDIO_NSAMPLES; i += 2) {
|
||||||
update_len(c);
|
update_len(c);
|
||||||
|
|
||||||
if (c->enabled) {
|
if (!c->enabled)
|
||||||
float pos = 0.0f;
|
continue;
|
||||||
float prev_pos = 0.0f;
|
|
||||||
float sample = 0.0f;
|
|
||||||
|
|
||||||
c->sample = wave_sample(c->val, c->volume);
|
uint32_t pos = 0;
|
||||||
|
uint32_t prev_pos = 0;
|
||||||
|
int32_t sample = 0;
|
||||||
|
|
||||||
|
c->wave.sample = wave_sample(c->val, c->volume);
|
||||||
|
|
||||||
while (update_freq(c, &pos)) {
|
while (update_freq(c, &pos)) {
|
||||||
c->val = (c->val + 1) & 31;
|
c->val = (c->val + 1) & 31;
|
||||||
sample += ((pos - prev_pos) / c->freq_inc) *
|
sample += ((pos - prev_pos) / c->freq_inc) *
|
||||||
(float)c->sample;
|
((int)c->wave.sample - 8) * (INT16_MAX/64);
|
||||||
c->sample = wave_sample(c->val, c->volume);
|
c->wave.sample = wave_sample(c->val, c->volume);
|
||||||
prev_pos = pos;
|
prev_pos = pos;
|
||||||
}
|
}
|
||||||
sample += ((pos - prev_pos) / c->freq_inc) *
|
|
||||||
(float)c->sample;
|
|
||||||
|
|
||||||
if (c->volume > 0) {
|
sample += ((int)c->wave.sample - 8) * (int)(INT16_MAX/64);
|
||||||
float diff = (float[]){ 7.5f, 3.75f,
|
|
||||||
1.5f }[c->volume - 1];
|
|
||||||
sample = hipass(c, (sample - diff) / 7.5f);
|
|
||||||
|
|
||||||
if (!c->muted) {
|
if (c->volume == 0)
|
||||||
samples[i + 0] += sample * 0.25f *
|
continue;
|
||||||
c->on_left * vol_l;
|
|
||||||
samples[i + 1] += sample * 0.25f *
|
{
|
||||||
c->on_right * vol_r;
|
/* First element is unused. */
|
||||||
}
|
int16_t div[] = { INT16_MAX, 1, 2, 4 };
|
||||||
}
|
sample = sample / (div[c->volume]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (c->muted)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
sample /= 4;
|
||||||
|
|
||||||
|
samples[i + 0] += sample * c->on_left * vol_l;
|
||||||
|
samples[i + 1] += sample * c->on_right * vol_r;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void update_noise(float *restrict samples)
|
static void update_noise(int16_t *samples)
|
||||||
{
|
{
|
||||||
struct chan *c = chans + 3;
|
struct chan *c = chans + 3;
|
||||||
|
|
||||||
if (!c->powered)
|
if (!c->powered)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
uint_fast16_t freq = 4194304 / ((uint_fast8_t[]){
|
{
|
||||||
|
const uint32_t lfsr_div_lut[] = {
|
||||||
8, 16, 32, 48, 64, 80, 96, 112
|
8, 16, 32, 48, 64, 80, 96, 112
|
||||||
}[c->lfsr_div] << c->freq);
|
};
|
||||||
|
uint32_t freq;
|
||||||
|
|
||||||
|
freq = DMG_CLOCK_FREQ_U / (lfsr_div_lut[c->noise.lfsr_div] << c->freq);
|
||||||
set_note_freq(c, freq);
|
set_note_freq(c, freq);
|
||||||
|
}
|
||||||
|
|
||||||
if (c->freq >= 14)
|
if (c->freq >= 14)
|
||||||
c->enabled = 0;
|
c->enabled = 0;
|
||||||
@@ -299,60 +321,63 @@ static void update_noise(float *restrict samples)
|
|||||||
for (uint_fast16_t i = 0; i < AUDIO_NSAMPLES; i += 2) {
|
for (uint_fast16_t i = 0; i < AUDIO_NSAMPLES; i += 2) {
|
||||||
update_len(c);
|
update_len(c);
|
||||||
|
|
||||||
if (c->enabled) {
|
if (!c->enabled)
|
||||||
|
continue;
|
||||||
|
|
||||||
update_env(c);
|
update_env(c);
|
||||||
|
|
||||||
float pos = 0.0f;
|
uint32_t pos = 0;
|
||||||
float prev_pos = 0.0f;
|
uint32_t prev_pos = 0;
|
||||||
float sample = 0.0f;
|
int32_t sample = 0;
|
||||||
|
|
||||||
while (update_freq(c, &pos)) {
|
while (update_freq(c, &pos)) {
|
||||||
c->lfsr_reg = (c->lfsr_reg << 1) |
|
c->noise.lfsr_reg = (c->noise.lfsr_reg << 1) |
|
||||||
(c->val == 1);
|
(c->val >= VOL_INIT_MAX/MAX_CHAN_VOLUME);
|
||||||
|
|
||||||
if (c->lfsr_wide) {
|
if (c->noise.lfsr_wide) {
|
||||||
c->val = !(((c->lfsr_reg >> 14) & 1) ^
|
c->val = !(((c->noise.lfsr_reg >> 14) & 1) ^
|
||||||
((c->lfsr_reg >> 13) & 1)) ?
|
((c->noise.lfsr_reg >> 13) & 1)) ?
|
||||||
1 :
|
VOL_INIT_MAX / MAX_CHAN_VOLUME :
|
||||||
-1;
|
VOL_INIT_MIN / MAX_CHAN_VOLUME;
|
||||||
} else {
|
} else {
|
||||||
c->val = !(((c->lfsr_reg >> 6) & 1) ^
|
c->val = !(((c->noise.lfsr_reg >> 6) & 1) ^
|
||||||
((c->lfsr_reg >> 5) & 1)) ?
|
((c->noise.lfsr_reg >> 5) & 1)) ?
|
||||||
1 :
|
VOL_INIT_MAX / MAX_CHAN_VOLUME :
|
||||||
-1;
|
VOL_INIT_MIN / MAX_CHAN_VOLUME;
|
||||||
}
|
}
|
||||||
sample += ((pos - prev_pos) / c->freq_inc) *
|
|
||||||
c->val;
|
sample += ((pos - prev_pos) / c->freq_inc) * c->val;
|
||||||
prev_pos = pos;
|
prev_pos = pos;
|
||||||
}
|
}
|
||||||
sample += ((pos - prev_pos) / c->freq_inc) * c->val;
|
|
||||||
sample = hipass(c, sample * (c->volume / 15.0f));
|
|
||||||
|
|
||||||
if (!c->muted) {
|
if (c->muted)
|
||||||
samples[i + 0] +=
|
continue;
|
||||||
sample * 0.25f * c->on_left * vol_l;
|
|
||||||
samples[i + 1] +=
|
sample += c->val;
|
||||||
sample * 0.25f * c->on_right * vol_r;
|
sample *= c->volume;
|
||||||
}
|
sample /= 4;
|
||||||
}
|
|
||||||
|
samples[i + 0] += sample * c->on_left * vol_l;
|
||||||
|
samples[i + 1] += sample * c->on_right * vol_r;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SDL2 style audio callback function.
|
* SDL2 style audio callback function.
|
||||||
*/
|
*/
|
||||||
void audio_callback(void *userdata, void *restrict stream, int len)
|
void audio_callback(void *userdata, uint8_t *stream, int len)
|
||||||
{
|
{
|
||||||
|
int16_t *samples = (int16_t *)stream;
|
||||||
|
|
||||||
/* Appease unused variable warning. */
|
/* Appease unused variable warning. */
|
||||||
(void)userdata;
|
(void)userdata;
|
||||||
|
|
||||||
memset(stream, 0, len);
|
memset(stream, 0, len);
|
||||||
|
|
||||||
update_square(stream, 0);
|
update_square(samples, 0);
|
||||||
update_square(stream, 1);
|
update_square(samples, 1);
|
||||||
/* FIXME: Performance regression when wave is switched on. */
|
update_wave(samples);
|
||||||
//update_wave(stream);
|
update_noise(samples);
|
||||||
update_noise(stream);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void chan_trigger(uint_fast8_t i)
|
static void chan_trigger(uint_fast8_t i)
|
||||||
@@ -369,10 +394,10 @@ static void chan_trigger(uint_fast8_t i)
|
|||||||
|
|
||||||
c->env.step = val & 0x07;
|
c->env.step = val & 0x07;
|
||||||
c->env.up = val & 0x08 ? 1 : 0;
|
c->env.up = val & 0x08 ? 1 : 0;
|
||||||
c->env.inc = c->env.step ? (64.0f / (float)c->env.step) /
|
c->env.inc = c->env.step ?
|
||||||
AUDIO_SAMPLE_RATE :
|
(FREQ_INC_REF * 64ul) / ((uint32_t)c->env.step * AUDIO_SAMPLE_RATE) :
|
||||||
8.0f / AUDIO_SAMPLE_RATE;
|
(8ul * FREQ_INC_REF) / AUDIO_SAMPLE_RATE ;
|
||||||
c->env.counter = 0.0f;
|
c->env.counter = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// freq sweep
|
// freq sweep
|
||||||
@@ -384,10 +409,8 @@ static void chan_trigger(uint_fast8_t i)
|
|||||||
c->sweep.up = !(val & 0x08);
|
c->sweep.up = !(val & 0x08);
|
||||||
c->sweep.shift = (val & 0x07);
|
c->sweep.shift = (val & 0x07);
|
||||||
c->sweep.inc = c->sweep.rate ?
|
c->sweep.inc = c->sweep.rate ?
|
||||||
(128.0f / (float)(c->sweep.rate)) /
|
((128 * FREQ_INC_REF) / (c->sweep.rate * AUDIO_SAMPLE_RATE)) : 0;
|
||||||
AUDIO_SAMPLE_RATE :
|
c->sweep.counter = FREQ_INC_REF;
|
||||||
0;
|
|
||||||
c->sweep.counter = nexttowardf(1.0f, 1.1f);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int len_max = 64;
|
int len_max = 64;
|
||||||
@@ -396,13 +419,12 @@ static void chan_trigger(uint_fast8_t i)
|
|||||||
len_max = 256;
|
len_max = 256;
|
||||||
c->val = 0;
|
c->val = 0;
|
||||||
} else if (i == 3) { // noise
|
} else if (i == 3) { // noise
|
||||||
c->lfsr_reg = 0xFFFF;
|
c->noise.lfsr_reg = 0xFFFF;
|
||||||
c->val = -1;
|
c->val = VOL_INIT_MIN / MAX_CHAN_VOLUME;
|
||||||
}
|
}
|
||||||
|
|
||||||
c->len.inc =
|
c->len.inc = (256 * FREQ_INC_REF) / (AUDIO_SAMPLE_RATE * (len_max - c->len.load));
|
||||||
(256.0f / (float)(len_max - c->len.load)) / AUDIO_SAMPLE_RATE;
|
c->len.counter = 0;
|
||||||
c->len.counter = 0.0f;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -413,15 +435,19 @@ static void chan_trigger(uint_fast8_t i)
|
|||||||
*/
|
*/
|
||||||
uint8_t audio_read(const uint16_t addr)
|
uint8_t audio_read(const uint16_t addr)
|
||||||
{
|
{
|
||||||
static uint8_t ortab[] = { 0x80, 0x3f, 0x00, 0xff, 0xbf, 0xff,
|
static const uint8_t ortab[] = {
|
||||||
0x3f, 0x00, 0xff, 0xbf, 0x7f, 0xff,
|
0x80, 0x3f, 0x00, 0xff, 0xbf,
|
||||||
0x9f, 0xff, 0xbf, 0xff, 0xff, 0x00,
|
0xff, 0x3f, 0x00, 0xff, 0xbf,
|
||||||
0x00, 0xbf, 0x00, 0x00, 0x70 };
|
0x7f, 0xff, 0x9f, 0xff, 0xbf,
|
||||||
|
0xff, 0xff, 0x00, 0x00, 0xbf,
|
||||||
|
0x00, 0x00, 0x70,
|
||||||
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||||
|
};
|
||||||
|
|
||||||
if (addr > 0xFF26)
|
return audio_mem[addr - AUDIO_ADDR_COMPENSATION] |
|
||||||
return audio_mem[addr - AUDIO_ADDR_COMPENSATION];
|
ortab[addr - AUDIO_ADDR_COMPENSATION];
|
||||||
|
|
||||||
return audio_mem[addr - AUDIO_ADDR_COMPENSATION] | ortab[addr - 0xFF10];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -433,8 +459,31 @@ uint8_t audio_read(const uint16_t addr)
|
|||||||
void audio_write(const uint16_t addr, const uint8_t val)
|
void audio_write(const uint16_t addr, const uint8_t val)
|
||||||
{
|
{
|
||||||
/* Find sound channel corresponding to register address. */
|
/* Find sound channel corresponding to register address. */
|
||||||
uint_fast8_t i = (addr - 0xFF10) / 5;
|
uint_fast8_t i;
|
||||||
|
|
||||||
|
if(addr == 0xFF26)
|
||||||
|
{
|
||||||
|
audio_mem[addr - AUDIO_ADDR_COMPENSATION] = val & 0x80;
|
||||||
|
/* On APU power off, clear all registers apart from wave
|
||||||
|
* RAM. */
|
||||||
|
if((val & 0x80) == 0)
|
||||||
|
{
|
||||||
|
memset(audio_mem, 0x00, 0xFF26 - AUDIO_ADDR_COMPENSATION);
|
||||||
|
chans[0].enabled = false;
|
||||||
|
chans[1].enabled = false;
|
||||||
|
chans[2].enabled = false;
|
||||||
|
chans[3].enabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ignore register writes if APU powered off. */
|
||||||
|
if(audio_mem[0xFF26 - AUDIO_ADDR_COMPENSATION] == 0x00)
|
||||||
|
return;
|
||||||
|
|
||||||
audio_mem[addr - AUDIO_ADDR_COMPENSATION] = val;
|
audio_mem[addr - AUDIO_ADDR_COMPENSATION] = val;
|
||||||
|
i = (addr - AUDIO_ADDR_COMPENSATION) / 5;
|
||||||
|
|
||||||
switch (addr) {
|
switch (addr) {
|
||||||
case 0xFF12:
|
case 0xFF12:
|
||||||
@@ -470,7 +519,7 @@ void audio_write(const uint16_t addr, const uint8_t val)
|
|||||||
case 0xFF20: {
|
case 0xFF20: {
|
||||||
const uint8_t duty_lookup[] = { 0x10, 0x30, 0x3C, 0xCF };
|
const uint8_t duty_lookup[] = { 0x10, 0x30, 0x3C, 0xCF };
|
||||||
chans[i].len.load = val & 0x3f;
|
chans[i].len.load = val & 0x3f;
|
||||||
chans[i].duty = duty_lookup[val >> 6];
|
chans[i].square.duty = duty_lookup[val >> 6];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -505,19 +554,21 @@ void audio_write(const uint16_t addr, const uint8_t val)
|
|||||||
|
|
||||||
case 0xFF22:
|
case 0xFF22:
|
||||||
chans[3].freq = val >> 4;
|
chans[3].freq = val >> 4;
|
||||||
chans[3].lfsr_wide = !(val & 0x08);
|
chans[3].noise.lfsr_wide = !(val & 0x08);
|
||||||
chans[3].lfsr_div = val & 0x07;
|
chans[3].noise.lfsr_div = val & 0x07;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0xFF24:
|
case 0xFF24:
|
||||||
vol_l = ((val >> 4) & 0x07) / 7.0f;
|
{
|
||||||
vol_r = (val & 0x07) / 7.0f;
|
vol_l = ((val >> 4) & 0x07);
|
||||||
|
vol_r = (val & 0x07);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case 0xFF25:
|
case 0xFF25:
|
||||||
for (uint_fast8_t i = 0; i < 4; ++i) {
|
for (uint_fast8_t j = 0; j < 4; j++) {
|
||||||
chans[i].on_left = (val >> (4 + i)) & 1;
|
chans[j].on_left = (val >> (4 + j)) & 1;
|
||||||
chans[i].on_right = (val >> i) & 1;
|
chans[j].on_right = (val >> j) & 1;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,28 +9,19 @@
|
|||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#define AUDIO_SAMPLE_RATE 32768
|
||||||
|
|
||||||
#define DMG_CLOCK_FREQ 4194304.0
|
#define DMG_CLOCK_FREQ 4194304.0
|
||||||
#define SCREEN_REFRESH_CYCLES 70224.0
|
#define SCREEN_REFRESH_CYCLES 70224.0
|
||||||
#define VERTICAL_SYNC (DMG_CLOCK_FREQ/SCREEN_REFRESH_CYCLES)
|
#define VERTICAL_SYNC (DMG_CLOCK_FREQ/SCREEN_REFRESH_CYCLES)
|
||||||
|
|
||||||
#ifndef ENABLE_HIPASS
|
#define AUDIO_SAMPLES ((unsigned)(AUDIO_SAMPLE_RATE / VERTICAL_SYNC))
|
||||||
# define ENABLE_HIPASS 1
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef AUDIO_SAMPLE_RATE
|
|
||||||
# define AUDIO_SAMPLE_RATE 8000.0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef AUDIO_NSAMPLES
|
|
||||||
# define AUDIO_NSAMPLES 256
|
|
||||||
//((unsigned)(AUDIO_SAMPLE_RATE / VERTICAL_SYNC) * 2)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fill allocated buffer "data" with "len" number of 32-bit floating point
|
* Fill allocated buffer "data" with "len" number of 32-bit floating point
|
||||||
* samples (native endian order) in stereo interleaved format.
|
* samples (native endian order) in stereo interleaved format.
|
||||||
*/
|
*/
|
||||||
void audio_callback(void *ptr, void *data, int len);
|
void audio_callback(void *ptr, uint8_t *data, int len);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read audio register at given address "addr".
|
* Read audio register at given address "addr".
|
||||||
|
|||||||
+20
-2
@@ -14,8 +14,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#define ENABLE_LCD 1
|
#define ENABLE_LCD 1
|
||||||
#define ENABLE_SOUND 1
|
#define ENABLE_SOUND 0
|
||||||
#define ENABLE_HIPASS 0
|
|
||||||
|
/* Use DMA for all drawing to LCD. Benefits aren't fully realised at the moment
|
||||||
|
* due to busy loops waiting for DMA completion. */
|
||||||
#define USE_DMA 0
|
#define USE_DMA 0
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -23,6 +25,7 @@
|
|||||||
* When setting a clock IRQ to DMG_CLOCK_FREQ_REDUCED, count to
|
* When setting a clock IRQ to DMG_CLOCK_FREQ_REDUCED, count to
|
||||||
* SCREEN_REFRESH_CYCLES_REDUCED to obtain the time required each VSYNC.
|
* SCREEN_REFRESH_CYCLES_REDUCED to obtain the time required each VSYNC.
|
||||||
* DMG_CLOCK_FREQ_REDUCED = 2^18, and SCREEN_REFRESH_CYCLES_REDUCED = 4389.
|
* DMG_CLOCK_FREQ_REDUCED = 2^18, and SCREEN_REFRESH_CYCLES_REDUCED = 4389.
|
||||||
|
* Currently unused.
|
||||||
*/
|
*/
|
||||||
#define VSYNC_REDUCTION_FACTOR 16u
|
#define VSYNC_REDUCTION_FACTOR 16u
|
||||||
#define SCREEN_REFRESH_CYCLES_REDUCED (SCREEN_REFRESH_CYCLES/VSYNC_REDUCTION_FACTOR)
|
#define SCREEN_REFRESH_CYCLES_REDUCED (SCREEN_REFRESH_CYCLES/VSYNC_REDUCTION_FACTOR)
|
||||||
@@ -58,17 +61,29 @@
|
|||||||
#define GPIO_RS 4
|
#define GPIO_RS 4
|
||||||
#define GPIO_RST 5
|
#define GPIO_RST 5
|
||||||
|
|
||||||
|
/* DMA channel for LCD communication. */
|
||||||
static uint dma_lcd;
|
static uint dma_lcd;
|
||||||
|
/* Definition of ROM data variable. Must be declared like:
|
||||||
|
* #include <pico/platform.h>
|
||||||
|
* const unsigned char __in_flash("rom") rom[] = {
|
||||||
|
* ...
|
||||||
|
* };
|
||||||
|
*/
|
||||||
extern const unsigned char rom[];
|
extern const unsigned char rom[];
|
||||||
unsigned char rom_bank0[16384];
|
unsigned char rom_bank0[16384];
|
||||||
static uint8_t ram[32768];
|
static uint8_t ram[32768];
|
||||||
static int lcd_line_busy = 0;
|
static int lcd_line_busy = 0;
|
||||||
|
|
||||||
|
/* Multicore command structure. */
|
||||||
union core_cmd {
|
union core_cmd {
|
||||||
struct {
|
struct {
|
||||||
|
/* Does nothing. */
|
||||||
#define CORE_CMD_NOP 0
|
#define CORE_CMD_NOP 0
|
||||||
|
/* Set line "data" on the LCD. Pixel data is in pixels_buffer. */
|
||||||
#define CORE_CMD_LCD_LINE 1
|
#define CORE_CMD_LCD_LINE 1
|
||||||
|
/* Control idle mode on the LCD. Limits colours to 2 bits. */
|
||||||
#define CORE_CMD_IDLE_SET 2
|
#define CORE_CMD_IDLE_SET 2
|
||||||
|
/* Set a specific pixel. For debugging. */
|
||||||
#define CORE_CMD_SET_PIXEL 3
|
#define CORE_CMD_SET_PIXEL 3
|
||||||
uint8_t cmd;
|
uint8_t cmd;
|
||||||
uint8_t unused1;
|
uint8_t unused1;
|
||||||
@@ -78,10 +93,12 @@ union core_cmd {
|
|||||||
uint32_t full;
|
uint32_t full;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* Pixel data is stored in here. */
|
||||||
static uint8_t pixels_buffer[LCD_WIDTH];
|
static uint8_t pixels_buffer[LCD_WIDTH];
|
||||||
|
|
||||||
#define putstdio(x) write(1, x, strlen(x))
|
#define putstdio(x) write(1, x, strlen(x))
|
||||||
|
|
||||||
|
/* Functions required for communication with the ILI9225. */
|
||||||
void mk_ili9225_set_rst(bool state)
|
void mk_ili9225_set_rst(bool state)
|
||||||
{
|
{
|
||||||
gpio_put(GPIO_RST, state);
|
gpio_put(GPIO_RST, state);
|
||||||
@@ -181,6 +198,7 @@ void core1_lcd_draw_line(const uint_fast8_t line)
|
|||||||
dma_channel_transfer_from_buffer_now(dma_lcd, &fb[0], LCD_WIDTH);
|
dma_channel_transfer_from_buffer_now(dma_lcd, &fb[0], LCD_WIDTH);
|
||||||
dma_channel_wait_for_finish_blocking(dma_lcd);
|
dma_channel_wait_for_finish_blocking(dma_lcd);
|
||||||
mk_ili9225_write_pixels_end();
|
mk_ili9225_write_pixels_end();
|
||||||
|
__atomic_store_n(&lcd_line_busy, 0, __ATOMIC_SEQ_CST);
|
||||||
#else
|
#else
|
||||||
mk_ili9225_write_pixels(fb, LCD_WIDTH);
|
mk_ili9225_write_pixels(fb, LCD_WIDTH);
|
||||||
__atomic_store_n(&lcd_line_busy, 0, __ATOMIC_SEQ_CST);
|
__atomic_store_n(&lcd_line_busy, 0, __ATOMIC_SEQ_CST);
|
||||||
|
|||||||
Reference in New Issue
Block a user