@@ -0,0 +1,20 @@
|
|||||||
|
Copyright (c) 2017 Alex Baines <alex@abaines.me.uk>
|
||||||
|
Copyright (c) 2019 Mahyar Koshkouei <mk@deltabeard.com>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -0,0 +1,553 @@
|
|||||||
|
/**
|
||||||
|
* minigb_apu is released under the terms listed within the LICENSE file.
|
||||||
|
*
|
||||||
|
* minigb_apu emulates the audio processing unit (APU) of the Game Boy. This
|
||||||
|
* project is based on MiniGBS by Alex Baines: https://github.com/baines/MiniGBS
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <math.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "minigb_apu.h"
|
||||||
|
|
||||||
|
#define AUDIO_MEM_SIZE (0xFF3F - 0xFF10 + 1)
|
||||||
|
#define AUDIO_ADDR_COMPENSATION 0xFF10
|
||||||
|
|
||||||
|
#define MAX(a, b) ( a > b ? a : b )
|
||||||
|
#define MIN(a, b) ( a <= b ? a : b )
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Memory holding audio registers between 0xFF10 and 0xFF3F inclusive.
|
||||||
|
*/
|
||||||
|
static uint8_t audio_mem[AUDIO_MEM_SIZE];
|
||||||
|
|
||||||
|
struct chan_len_ctr {
|
||||||
|
uint8_t load;
|
||||||
|
bool enabled;
|
||||||
|
float counter;
|
||||||
|
float inc;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct chan_vol_env {
|
||||||
|
uint8_t step;
|
||||||
|
bool up;
|
||||||
|
float counter;
|
||||||
|
float inc;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct chan_freq_sweep {
|
||||||
|
uint_fast16_t freq;
|
||||||
|
uint8_t rate;
|
||||||
|
uint8_t shift;
|
||||||
|
bool up;
|
||||||
|
float counter;
|
||||||
|
float inc;
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct chan {
|
||||||
|
bool enabled;
|
||||||
|
bool powered;
|
||||||
|
bool on_left;
|
||||||
|
bool on_right;
|
||||||
|
bool muted;
|
||||||
|
|
||||||
|
uint8_t volume;
|
||||||
|
uint8_t volume_init;
|
||||||
|
|
||||||
|
uint16_t freq;
|
||||||
|
float freq_counter;
|
||||||
|
float freq_inc;
|
||||||
|
|
||||||
|
int_fast8_t val;
|
||||||
|
|
||||||
|
struct chan_len_ctr len;
|
||||||
|
struct chan_vol_env env;
|
||||||
|
struct chan_freq_sweep sweep;
|
||||||
|
|
||||||
|
// square
|
||||||
|
uint8_t duty;
|
||||||
|
uint8_t duty_counter;
|
||||||
|
|
||||||
|
// noise
|
||||||
|
uint16_t lfsr_reg;
|
||||||
|
uint8_t lfsr_wide;
|
||||||
|
uint8_t lfsr_div;
|
||||||
|
|
||||||
|
// wave
|
||||||
|
uint8_t sample;
|
||||||
|
|
||||||
|
#if ENABLE_HIPASS
|
||||||
|
float capacitor;
|
||||||
|
#endif
|
||||||
|
} chans[4];
|
||||||
|
|
||||||
|
static float vol_l, vol_r;
|
||||||
|
|
||||||
|
static float hipass(struct chan *c, float sample)
|
||||||
|
{
|
||||||
|
#if ENABLE_HIPASS
|
||||||
|
float out = sample - c->capacitor;
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
chans[i].enabled = enable;
|
||||||
|
|
||||||
|
uint8_t val = (audio_mem[0xFF26 - AUDIO_ADDR_COMPENSATION] & 0x80) |
|
||||||
|
(chans[3].enabled << 3) | (chans[2].enabled << 2) |
|
||||||
|
(chans[1].enabled << 1) | (chans[0].enabled << 0);
|
||||||
|
|
||||||
|
audio_mem[0xFF26 - AUDIO_ADDR_COMPENSATION] = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void update_env(struct chan *c)
|
||||||
|
{
|
||||||
|
c->env.counter += c->env.inc;
|
||||||
|
|
||||||
|
while (c->env.counter > 1.0f) {
|
||||||
|
if (c->env.step) {
|
||||||
|
c->volume += c->env.up ? 1 : -1;
|
||||||
|
if (c->volume == 0 || c->volume == 15) {
|
||||||
|
c->env.inc = 0;
|
||||||
|
}
|
||||||
|
c->volume = MAX(0, MIN(15, c->volume));
|
||||||
|
}
|
||||||
|
c->env.counter -= 1.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void update_len(struct chan *c)
|
||||||
|
{
|
||||||
|
if (c->len.enabled) {
|
||||||
|
c->len.counter += c->len.inc;
|
||||||
|
if (c->len.counter > 1.0f) {
|
||||||
|
chan_enable(c - chans, 0);
|
||||||
|
c->len.counter = 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool update_freq(struct chan *c, float *pos)
|
||||||
|
{
|
||||||
|
float inc = c->freq_inc - *pos;
|
||||||
|
c->freq_counter += inc;
|
||||||
|
|
||||||
|
if (c->freq_counter > 1.0f) {
|
||||||
|
*pos = c->freq_inc - (c->freq_counter - 1.0f);
|
||||||
|
c->freq_counter = 0.0f;
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
*pos = c->freq_inc;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void update_sweep(struct chan *c)
|
||||||
|
{
|
||||||
|
c->sweep.counter += c->sweep.inc;
|
||||||
|
|
||||||
|
while (c->sweep.counter > 1.0f) {
|
||||||
|
if (c->sweep.shift) {
|
||||||
|
uint16_t inc = (c->sweep.freq >> c->sweep.shift);
|
||||||
|
if (!c->sweep.up)
|
||||||
|
inc *= -1;
|
||||||
|
|
||||||
|
c->freq += inc;
|
||||||
|
if (c->freq > 2047) {
|
||||||
|
c->enabled = 0;
|
||||||
|
} else {
|
||||||
|
set_note_freq(c,
|
||||||
|
4194304 / ((2048 - c->freq)<< 5));
|
||||||
|
c->freq_inc *= 8.0f;
|
||||||
|
}
|
||||||
|
} else if (c->sweep.rate) {
|
||||||
|
c->enabled = 0;
|
||||||
|
}
|
||||||
|
c->sweep.counter -= 1.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void update_square(float *restrict samples, const bool ch2)
|
||||||
|
{
|
||||||
|
struct chan *c = chans + ch2;
|
||||||
|
if (!c->powered)
|
||||||
|
return;
|
||||||
|
|
||||||
|
set_note_freq(c, 4194304.0f / ((2048 - c->freq) << 5));
|
||||||
|
c->freq_inc *= 8.0f;
|
||||||
|
|
||||||
|
for (uint_fast16_t i = 0; i < AUDIO_NSAMPLES; i += 2) {
|
||||||
|
update_len(c);
|
||||||
|
|
||||||
|
if (c->enabled) {
|
||||||
|
update_env(c);
|
||||||
|
if (!ch2)
|
||||||
|
update_sweep(c);
|
||||||
|
|
||||||
|
float pos = 0.0f;
|
||||||
|
float prev_pos = 0.0f;
|
||||||
|
float sample = 0.0f;
|
||||||
|
|
||||||
|
while (update_freq(c, &pos)) {
|
||||||
|
c->duty_counter = (c->duty_counter + 1) & 7;
|
||||||
|
sample += ((pos - prev_pos) / c->freq_inc) *
|
||||||
|
(float)c->val;
|
||||||
|
c->val = (c->duty & (1 << c->duty_counter)) ?
|
||||||
|
1 :
|
||||||
|
-1;
|
||||||
|
prev_pos = pos;
|
||||||
|
}
|
||||||
|
sample += ((pos - prev_pos) / c->freq_inc) *
|
||||||
|
(float)c->val;
|
||||||
|
sample = hipass(c, sample * (c->volume / 15.0f));
|
||||||
|
|
||||||
|
if (!c->muted) {
|
||||||
|
samples[i + 0] +=
|
||||||
|
sample * 0.25f * c->on_left * vol_l;
|
||||||
|
samples[i + 1] +=
|
||||||
|
sample * 0.25f * c->on_right * vol_r;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t wave_sample(const unsigned int pos, const unsigned int volume)
|
||||||
|
{
|
||||||
|
uint8_t sample =
|
||||||
|
audio_mem[(0xFF30 + pos / 2) - AUDIO_ADDR_COMPENSATION];
|
||||||
|
if (pos & 1) {
|
||||||
|
sample &= 0xF;
|
||||||
|
} else {
|
||||||
|
sample >>= 4;
|
||||||
|
}
|
||||||
|
return volume ? (sample >> (volume - 1)) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void update_wave(float *restrict samples)
|
||||||
|
{
|
||||||
|
struct chan *c = chans + 2;
|
||||||
|
if (!c->powered)
|
||||||
|
return;
|
||||||
|
|
||||||
|
uint_fast16_t freq = 4194304.0f / ((2048 - c->freq) << 5);
|
||||||
|
set_note_freq(c, freq);
|
||||||
|
|
||||||
|
c->freq_inc *= 16.0f;
|
||||||
|
|
||||||
|
for (uint_fast16_t i = 0; i < AUDIO_NSAMPLES; i += 2) {
|
||||||
|
update_len(c);
|
||||||
|
|
||||||
|
if (c->enabled) {
|
||||||
|
float pos = 0.0f;
|
||||||
|
float prev_pos = 0.0f;
|
||||||
|
float sample = 0.0f;
|
||||||
|
|
||||||
|
c->sample = wave_sample(c->val, c->volume);
|
||||||
|
|
||||||
|
while (update_freq(c, &pos)) {
|
||||||
|
c->val = (c->val + 1) & 31;
|
||||||
|
sample += ((pos - prev_pos) / c->freq_inc) *
|
||||||
|
(float)c->sample;
|
||||||
|
c->sample = wave_sample(c->val, c->volume);
|
||||||
|
prev_pos = pos;
|
||||||
|
}
|
||||||
|
sample += ((pos - prev_pos) / c->freq_inc) *
|
||||||
|
(float)c->sample;
|
||||||
|
|
||||||
|
if (c->volume > 0) {
|
||||||
|
float diff = (float[]){ 7.5f, 3.75f,
|
||||||
|
1.5f }[c->volume - 1];
|
||||||
|
sample = hipass(c, (sample - diff) / 7.5f);
|
||||||
|
|
||||||
|
if (!c->muted) {
|
||||||
|
samples[i + 0] += sample * 0.25f *
|
||||||
|
c->on_left * vol_l;
|
||||||
|
samples[i + 1] += sample * 0.25f *
|
||||||
|
c->on_right * vol_r;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void update_noise(float *restrict samples)
|
||||||
|
{
|
||||||
|
struct chan *c = chans + 3;
|
||||||
|
if (!c->powered)
|
||||||
|
return;
|
||||||
|
|
||||||
|
uint_fast16_t freq = 4194304 / ((uint_fast8_t[]){
|
||||||
|
8, 16, 32, 48, 64, 80, 96, 112
|
||||||
|
}[c->lfsr_div] << c->freq);
|
||||||
|
set_note_freq(c, freq);
|
||||||
|
|
||||||
|
if (c->freq >= 14)
|
||||||
|
c->enabled = 0;
|
||||||
|
|
||||||
|
for (uint_fast16_t i = 0; i < AUDIO_NSAMPLES; i += 2) {
|
||||||
|
update_len(c);
|
||||||
|
|
||||||
|
if (c->enabled) {
|
||||||
|
update_env(c);
|
||||||
|
|
||||||
|
float pos = 0.0f;
|
||||||
|
float prev_pos = 0.0f;
|
||||||
|
float sample = 0.0f;
|
||||||
|
|
||||||
|
while (update_freq(c, &pos)) {
|
||||||
|
c->lfsr_reg = (c->lfsr_reg << 1) |
|
||||||
|
(c->val == 1);
|
||||||
|
|
||||||
|
if (c->lfsr_wide) {
|
||||||
|
c->val = !(((c->lfsr_reg >> 14) & 1) ^
|
||||||
|
((c->lfsr_reg >> 13) & 1)) ?
|
||||||
|
1 :
|
||||||
|
-1;
|
||||||
|
} else {
|
||||||
|
c->val = !(((c->lfsr_reg >> 6) & 1) ^
|
||||||
|
((c->lfsr_reg >> 5) & 1)) ?
|
||||||
|
1 :
|
||||||
|
-1;
|
||||||
|
}
|
||||||
|
sample += ((pos - prev_pos) / c->freq_inc) *
|
||||||
|
c->val;
|
||||||
|
prev_pos = pos;
|
||||||
|
}
|
||||||
|
sample += ((pos - prev_pos) / c->freq_inc) * c->val;
|
||||||
|
sample = hipass(c, sample * (c->volume / 15.0f));
|
||||||
|
|
||||||
|
if (!c->muted) {
|
||||||
|
samples[i + 0] +=
|
||||||
|
sample * 0.25f * c->on_left * vol_l;
|
||||||
|
samples[i + 1] +=
|
||||||
|
sample * 0.25f * c->on_right * vol_r;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SDL2 style audio callback function.
|
||||||
|
*/
|
||||||
|
void audio_callback(void *userdata, void *restrict stream, int len)
|
||||||
|
{
|
||||||
|
/* Appease unused variable warning. */
|
||||||
|
(void)userdata;
|
||||||
|
|
||||||
|
memset(stream, 0, len);
|
||||||
|
|
||||||
|
update_square(stream, 0);
|
||||||
|
update_square(stream, 1);
|
||||||
|
update_wave(stream);
|
||||||
|
update_noise(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void chan_trigger(uint_fast8_t i)
|
||||||
|
{
|
||||||
|
struct chan *c = chans + i;
|
||||||
|
|
||||||
|
chan_enable(i, 1);
|
||||||
|
c->volume = c->volume_init;
|
||||||
|
|
||||||
|
// volume envelope
|
||||||
|
{
|
||||||
|
uint8_t val =
|
||||||
|
audio_mem[(0xFF12 + (i * 5)) - AUDIO_ADDR_COMPENSATION];
|
||||||
|
|
||||||
|
c->env.step = val & 0x07;
|
||||||
|
c->env.up = val & 0x08 ? 1 : 0;
|
||||||
|
c->env.inc = c->env.step ? (64.0f / (float)c->env.step) /
|
||||||
|
AUDIO_SAMPLE_RATE :
|
||||||
|
8.0f / AUDIO_SAMPLE_RATE;
|
||||||
|
c->env.counter = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// freq sweep
|
||||||
|
if (i == 0) {
|
||||||
|
uint8_t val = audio_mem[0xFF10 - AUDIO_ADDR_COMPENSATION];
|
||||||
|
|
||||||
|
c->sweep.freq = c->freq;
|
||||||
|
c->sweep.rate = (val >> 4) & 0x07;
|
||||||
|
c->sweep.up = !(val & 0x08);
|
||||||
|
c->sweep.shift = (val & 0x07);
|
||||||
|
c->sweep.inc = c->sweep.rate ?
|
||||||
|
(128.0f / (float)(c->sweep.rate)) /
|
||||||
|
AUDIO_SAMPLE_RATE :
|
||||||
|
0;
|
||||||
|
c->sweep.counter = nexttowardf(1.0f, 1.1f);
|
||||||
|
}
|
||||||
|
|
||||||
|
int len_max = 64;
|
||||||
|
|
||||||
|
if (i == 2) { // wave
|
||||||
|
len_max = 256;
|
||||||
|
c->val = 0;
|
||||||
|
} else if (i == 3) { // noise
|
||||||
|
c->lfsr_reg = 0xFFFF;
|
||||||
|
c->val = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
c->len.inc =
|
||||||
|
(256.0f / (float)(len_max - c->len.load)) / AUDIO_SAMPLE_RATE;
|
||||||
|
c->len.counter = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read audio register.
|
||||||
|
* \param addr Address of audio register. Must be 0xFF10 <= addr <= 0xFF3F.
|
||||||
|
* This is not checked in this function.
|
||||||
|
* \return Byte at address.
|
||||||
|
*/
|
||||||
|
uint8_t audio_read(const uint16_t addr)
|
||||||
|
{
|
||||||
|
static uint8_t ortab[] = { 0x80, 0x3f, 0x00, 0xff, 0xbf, 0xff,
|
||||||
|
0x3f, 0x00, 0xff, 0xbf, 0x7f, 0xff,
|
||||||
|
0x9f, 0xff, 0xbf, 0xff, 0xff, 0x00,
|
||||||
|
0x00, 0xbf, 0x00, 0x00, 0x70 };
|
||||||
|
|
||||||
|
if (addr > 0xFF26)
|
||||||
|
return audio_mem[addr - AUDIO_ADDR_COMPENSATION];
|
||||||
|
|
||||||
|
return audio_mem[addr - AUDIO_ADDR_COMPENSATION] | ortab[addr - 0xFF10];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write audio register.
|
||||||
|
* \param addr Address of audio register. Must be 0xFF10 <= addr <= 0xFF3F.
|
||||||
|
* This is not checked in this function.
|
||||||
|
* \param val Byte to write at address.
|
||||||
|
*/
|
||||||
|
void audio_write(const uint16_t addr, const uint8_t val)
|
||||||
|
{
|
||||||
|
/* Find sound channel corresponding to register address. */
|
||||||
|
uint_fast8_t i = (addr - 0xFF10) / 5;
|
||||||
|
audio_mem[addr - AUDIO_ADDR_COMPENSATION] = val;
|
||||||
|
|
||||||
|
switch (addr) {
|
||||||
|
case 0xFF12:
|
||||||
|
case 0xFF17:
|
||||||
|
case 0xFF21: {
|
||||||
|
chans[i].volume_init = val >> 4;
|
||||||
|
chans[i].powered = (val >> 3) != 0;
|
||||||
|
|
||||||
|
// "zombie mode" stuff, needed for Prehistorik Man and probably
|
||||||
|
// others
|
||||||
|
if (chans[i].powered && chans[i].enabled) {
|
||||||
|
if ((chans[i].env.step == 0 && chans[i].env.inc != 0)) {
|
||||||
|
if (val & 0x08) {
|
||||||
|
chans[i].volume++;
|
||||||
|
} else {
|
||||||
|
chans[i].volume += 2;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
chans[i].volume = 16 - chans[i].volume;
|
||||||
|
}
|
||||||
|
|
||||||
|
chans[i].volume &= 0x0F;
|
||||||
|
chans[i].env.step = val & 0x07;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case 0xFF1C:
|
||||||
|
chans[i].volume = chans[i].volume_init = (val >> 5) & 0x03;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0xFF11:
|
||||||
|
case 0xFF16:
|
||||||
|
case 0xFF20: {
|
||||||
|
const uint8_t duty_lookup[] = { 0x10, 0x30, 0x3C, 0xCF };
|
||||||
|
chans[i].len.load = val & 0x3f;
|
||||||
|
chans[i].duty = duty_lookup[val >> 6];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 0xFF1B:
|
||||||
|
chans[i].len.load = val;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0xFF13:
|
||||||
|
case 0xFF18:
|
||||||
|
case 0xFF1D:
|
||||||
|
chans[i].freq &= 0xFF00;
|
||||||
|
chans[i].freq |= val;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0xFF1A:
|
||||||
|
chans[i].powered = (val & 0x80) != 0;
|
||||||
|
chan_enable(i, val & 0x80);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0xFF14:
|
||||||
|
case 0xFF19:
|
||||||
|
case 0xFF1E:
|
||||||
|
chans[i].freq &= 0x00FF;
|
||||||
|
chans[i].freq |= ((val & 0x07) << 8);
|
||||||
|
/* Intentional fall-through. */
|
||||||
|
case 0xFF23:
|
||||||
|
chans[i].len.enabled = val & 0x40 ? 1 : 0;
|
||||||
|
if (val & 0x80)
|
||||||
|
chan_trigger(i);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0xFF22:
|
||||||
|
chans[3].freq = val >> 4;
|
||||||
|
chans[3].lfsr_wide = !(val & 0x08);
|
||||||
|
chans[3].lfsr_div = val & 0x07;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0xFF24:
|
||||||
|
vol_l = ((val >> 4) & 0x07) / 7.0f;
|
||||||
|
vol_r = (val & 0x07) / 7.0f;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0xFF25:
|
||||||
|
for (uint_fast8_t i = 0; i < 4; ++i) {
|
||||||
|
chans[i].on_left = (val >> (4 + i)) & 1;
|
||||||
|
chans[i].on_right = (val >> i) & 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void audio_init(void)
|
||||||
|
{
|
||||||
|
/* Initialise channels and samples. */
|
||||||
|
memset(chans, 0, sizeof(chans));
|
||||||
|
chans[0].val = chans[1].val = -1;
|
||||||
|
|
||||||
|
/* Initialise IO registers. */
|
||||||
|
{
|
||||||
|
const uint8_t regs_init[] = { 0x80, 0xBF, 0xF3, 0xFF, 0x3F,
|
||||||
|
0xFF, 0x3F, 0x00, 0xFF, 0x3F,
|
||||||
|
0x7F, 0xFF, 0x9F, 0xFF, 0x3F,
|
||||||
|
0xFF, 0xFF, 0x00, 0x00, 0x3F,
|
||||||
|
0x77, 0xF3, 0xF1 };
|
||||||
|
|
||||||
|
for(uint_fast8_t i = 0; i < sizeof(regs_init); ++i)
|
||||||
|
audio_write(0xFF10 + i, regs_init[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Initialise Wave Pattern RAM. */
|
||||||
|
{
|
||||||
|
const uint8_t wave_init[] = { 0xac, 0xdd, 0xda, 0x48,
|
||||||
|
0x36, 0x02, 0xcf, 0x16,
|
||||||
|
0x2c, 0x04, 0xe5, 0x2c,
|
||||||
|
0xac, 0xdd, 0xda, 0x48 };
|
||||||
|
|
||||||
|
for(uint_fast8_t i = 0; i < sizeof(wave_init); ++i)
|
||||||
|
audio_write(0xFF30 + i, wave_init[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
/**
|
||||||
|
* minigb_apu is released under the terms listed within the LICENSE file.
|
||||||
|
*
|
||||||
|
* minigb_apu emulates the audio processing unit (APU) of the Game Boy. This
|
||||||
|
* project is based on MiniGBS by Alex Baines: https://github.com/baines/MiniGBS
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#define DMG_CLOCK_FREQ 4194304.0
|
||||||
|
#define SCREEN_REFRESH_CYCLES 70224.0
|
||||||
|
#define VERTICAL_SYNC (DMG_CLOCK_FREQ/SCREEN_REFRESH_CYCLES)
|
||||||
|
|
||||||
|
#ifndef ENABLE_HIPASS
|
||||||
|
# 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
|
||||||
|
* samples (native endian order) in stereo interleaved format.
|
||||||
|
*/
|
||||||
|
void audio_callback(void *ptr, void *data, int len);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read audio register at given address "addr".
|
||||||
|
*/
|
||||||
|
uint8_t audio_read(const uint16_t addr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write "val" to audio register at given address "addr".
|
||||||
|
*/
|
||||||
|
void audio_write(const uint16_t addr, const uint8_t val);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialise audio driver.
|
||||||
|
*/
|
||||||
|
void audio_init(void);
|
||||||
Reference in New Issue
Block a user