mirror of https://github.com/mgba-emu/mgba.git
GB Audio: Increase sample rate
This commit is contained in:
parent
137f3e5804
commit
1fb7d7a4a3
1
CHANGES
1
CHANGES
|
@ -75,6 +75,7 @@ Misc:
|
|||
- Debugger: GDB now works while the game is paused
|
||||
- Debugger: Add command to load external symbol file (fixes mgba.io/i/2480)
|
||||
- FFmpeg: Support dynamic audio sample rate
|
||||
- GB Audio: Increase sample rate
|
||||
- GB MBC: Filter out MBC errors when cartridge is yanked (fixes mgba.io/i/2488)
|
||||
- GB Video: Add default SGB border
|
||||
- GBA: Automatically skip BIOS if ROM has invalid logo
|
||||
|
|
|
@ -10,8 +10,11 @@
|
|||
|
||||
CXX_GUARD_START
|
||||
|
||||
#include <mgba/core/interface.h>
|
||||
#include <mgba/core/timing.h>
|
||||
|
||||
#define GB_MAX_SAMPLES 32
|
||||
|
||||
DECL_BITFIELD(GBAudioRegisterDuty, uint8_t);
|
||||
DECL_BITS(GBAudioRegisterDuty, Length, 0, 6);
|
||||
DECL_BITS(GBAudioRegisterDuty, Duty, 6, 2);
|
||||
|
@ -195,6 +198,10 @@ struct GBAudio {
|
|||
int32_t sampleInterval;
|
||||
enum GBAudioStyle style;
|
||||
|
||||
int32_t lastSample;
|
||||
int sampleIndex;
|
||||
struct mStereoSample currentSamples[GB_MAX_SAMPLES];
|
||||
|
||||
struct mTimingEvent frameEvent;
|
||||
struct mTimingEvent sampleEvent;
|
||||
bool enable;
|
||||
|
|
|
@ -164,7 +164,12 @@ mLOG_DECLARE_CATEGORY(GB_STATE);
|
|||
* | 0x00197: Reserved (leave zero)
|
||||
* 0x00198 - 0x0019F: Global cycle counter
|
||||
* 0x001A0 - 0x001A1: Program counter for last cartridge read
|
||||
* 0x001A2 - 0x0025F: Reserved (leave zero)
|
||||
* 0x001A2 - 0x00247: Reserved (leave zero)
|
||||
* 0x00248 - 0x0025F: Additional audio state
|
||||
* | 0x00248 - 0x0024B: Last sample timestamp
|
||||
* | 0x0024C: Current audio sample index
|
||||
* | 0x0024D - 0x0024F: Reserved (leave zero)
|
||||
* | 0x00250 - 0x0025F: Audio rendered samples
|
||||
* 0x00260 - 0x002FF: OAM
|
||||
* 0x00300 - 0x0037F: I/O memory
|
||||
* 0x00380 - 0x003FE: HRAM
|
||||
|
@ -430,7 +435,15 @@ struct GBSerializedState {
|
|||
uint64_t globalCycles;
|
||||
|
||||
uint16_t cartBusPc;
|
||||
uint16_t reserved[95];
|
||||
|
||||
uint16_t reserved[27];
|
||||
|
||||
struct {
|
||||
int32_t lastSample;
|
||||
uint8_t sampleIndex;
|
||||
uint8_t reserved[3];
|
||||
struct mStereoSample currentSamples[32];
|
||||
} audio2;
|
||||
|
||||
uint8_t oam[GB_SIZE_OAM];
|
||||
|
||||
|
|
109
src/gb/audio.c
109
src/gb/audio.c
|
@ -24,6 +24,8 @@
|
|||
const uint32_t DMG_SM83_FREQUENCY = 0x400000;
|
||||
static const int CLOCKS_PER_BLIP_FRAME = 0x1000;
|
||||
static const unsigned BLIP_BUFFER_SIZE = 0x4000;
|
||||
static const int SAMPLE_INTERVAL = 32;
|
||||
static const int FILTER = 65368;
|
||||
const int GB_AUDIO_VOLUME_MAX = 0x100;
|
||||
|
||||
static bool _writeSweep(struct GBAudioSweep* sweep, uint8_t value);
|
||||
|
@ -44,6 +46,8 @@ static int16_t _coalesceNoiseChannel(struct GBAudioNoiseChannel* ch);
|
|||
static void _updateFrame(struct mTiming* timing, void* user, uint32_t cyclesLate);
|
||||
static void _sample(struct mTiming* timing, void* user, uint32_t cyclesLate);
|
||||
|
||||
static void GBAudioSample(struct GBAudio* audio, int32_t timestamp);
|
||||
|
||||
static const int _squareChannelDuty[4][8] = {
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 1 },
|
||||
{ 1, 0, 0, 0, 0, 0, 0, 1 },
|
||||
|
@ -114,7 +118,9 @@ void GBAudioReset(struct GBAudio* audio) {
|
|||
audio->ch3.wavedata8[15] = 0xFF;
|
||||
audio->ch4 = (struct GBAudioNoiseChannel) { .envelope = { .dead = 2 } };
|
||||
audio->frame = 0;
|
||||
audio->sampleInterval = 128;
|
||||
audio->sampleInterval = SAMPLE_INTERVAL * GB_MAX_SAMPLES;
|
||||
audio->lastSample = 0;
|
||||
audio->sampleIndex = 0;
|
||||
audio->lastLeft = 0;
|
||||
audio->lastRight = 0;
|
||||
audio->capLeft = 0;
|
||||
|
@ -468,6 +474,11 @@ void GBAudioRun(struct GBAudio* audio, int32_t timestamp, int channels) {
|
|||
if (!audio->enable) {
|
||||
return;
|
||||
}
|
||||
if (audio->p && channels != 0xF && timestamp - audio->lastSample > SAMPLE_INTERVAL) {
|
||||
GBAudioSample(audio, timestamp);
|
||||
return;
|
||||
}
|
||||
|
||||
if (audio->playingCh1 && (channels & 0x1)) {
|
||||
int period = 4 * (2048 - audio->ch1.control.frequency) * audio->timingFactor;
|
||||
int32_t diff = timestamp - audio->ch1.lastUpdate;
|
||||
|
@ -735,41 +746,65 @@ void GBAudioSamplePSG(struct GBAudio* audio, int16_t* left, int16_t* right) {
|
|||
*right = sampleRight * (1 + audio->volumeRight);
|
||||
}
|
||||
|
||||
void GBAudioSample(struct GBAudio* audio, int32_t timestamp) {
|
||||
timestamp -= audio->lastSample;
|
||||
timestamp -= audio->sampleIndex * SAMPLE_INTERVAL;
|
||||
|
||||
int interval = SAMPLE_INTERVAL * audio->timingFactor;
|
||||
|
||||
int sample;
|
||||
for (sample = audio->sampleIndex; timestamp >= interval && sample < GB_MAX_SAMPLES; ++sample, timestamp -= interval) {
|
||||
int16_t sampleLeft = 0;
|
||||
int16_t sampleRight = 0;
|
||||
GBAudioRun(audio, sample * interval + audio->lastSample, 0xF);
|
||||
GBAudioSamplePSG(audio, &sampleLeft, &sampleRight);
|
||||
sampleLeft = (sampleLeft * audio->masterVolume * 6) >> 7;
|
||||
sampleRight = (sampleRight * audio->masterVolume * 6) >> 7;
|
||||
|
||||
int16_t degradedLeft = sampleLeft - (audio->capLeft >> 16);
|
||||
int16_t degradedRight = sampleRight - (audio->capRight >> 16);
|
||||
audio->capLeft = (sampleLeft << 16) - degradedLeft * FILTER;
|
||||
audio->capRight = (sampleRight << 16) - degradedRight * FILTER;
|
||||
|
||||
audio->currentSamples[sample].left = degradedLeft;
|
||||
audio->currentSamples[sample].right = degradedRight;
|
||||
}
|
||||
|
||||
audio->sampleIndex = sample;
|
||||
if (sample == GB_MAX_SAMPLES) {
|
||||
audio->lastSample += interval * GB_MAX_SAMPLES;
|
||||
audio->sampleIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void _sample(struct mTiming* timing, void* user, uint32_t cyclesLate) {
|
||||
struct GBAudio* audio = user;
|
||||
int16_t sampleLeft = 0;
|
||||
int16_t sampleRight = 0;
|
||||
GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0xF);
|
||||
GBAudioSamplePSG(audio, &sampleLeft, &sampleRight);
|
||||
sampleLeft = (sampleLeft * audio->masterVolume * 6) >> 7;
|
||||
sampleRight = (sampleRight * audio->masterVolume * 6) >> 7;
|
||||
GBAudioSample(audio, mTimingCurrentTime(audio->timing));
|
||||
|
||||
mCoreSyncLockAudio(audio->p->sync);
|
||||
unsigned produced;
|
||||
|
||||
int16_t degradedLeft = sampleLeft - (audio->capLeft >> 16);
|
||||
int16_t degradedRight = sampleRight - (audio->capRight >> 16);
|
||||
audio->capLeft = (sampleLeft << 16) - degradedLeft * 65184;
|
||||
audio->capRight = (sampleRight << 16) - degradedRight * 65184;
|
||||
sampleLeft = degradedLeft;
|
||||
sampleRight = degradedRight;
|
||||
|
||||
if ((size_t) blip_samples_avail(audio->left) < audio->samples) {
|
||||
blip_add_delta(audio->left, audio->clock, sampleLeft - audio->lastLeft);
|
||||
blip_add_delta(audio->right, audio->clock, sampleRight - audio->lastRight);
|
||||
audio->lastLeft = sampleLeft;
|
||||
audio->lastRight = sampleRight;
|
||||
audio->clock += audio->sampleInterval;
|
||||
if (audio->clock >= CLOCKS_PER_BLIP_FRAME) {
|
||||
blip_end_frame(audio->left, CLOCKS_PER_BLIP_FRAME);
|
||||
blip_end_frame(audio->right, CLOCKS_PER_BLIP_FRAME);
|
||||
audio->clock -= CLOCKS_PER_BLIP_FRAME;
|
||||
int i;
|
||||
for (i = 0; i < GB_MAX_SAMPLES; ++i) {
|
||||
int16_t sampleLeft = audio->currentSamples[i].left;
|
||||
int16_t sampleRight = audio->currentSamples[i].right;
|
||||
if ((size_t) blip_samples_avail(audio->left) < audio->samples) {
|
||||
blip_add_delta(audio->left, audio->clock, sampleLeft - audio->lastLeft);
|
||||
blip_add_delta(audio->right, audio->clock, sampleRight - audio->lastRight);
|
||||
audio->lastLeft = sampleLeft;
|
||||
audio->lastRight = sampleRight;
|
||||
audio->clock += SAMPLE_INTERVAL;
|
||||
if (audio->clock >= CLOCKS_PER_BLIP_FRAME) {
|
||||
blip_end_frame(audio->left, CLOCKS_PER_BLIP_FRAME);
|
||||
blip_end_frame(audio->right, CLOCKS_PER_BLIP_FRAME);
|
||||
audio->clock -= CLOCKS_PER_BLIP_FRAME;
|
||||
}
|
||||
}
|
||||
if (audio->p->stream && audio->p->stream->postAudioFrame) {
|
||||
audio->p->stream->postAudioFrame(audio->p->stream, sampleLeft, sampleRight);
|
||||
}
|
||||
}
|
||||
|
||||
produced = blip_samples_avail(audio->left);
|
||||
if (audio->p->stream && audio->p->stream->postAudioFrame) {
|
||||
audio->p->stream->postAudioFrame(audio->p->stream, sampleLeft, sampleRight);
|
||||
}
|
||||
bool wait = produced >= audio->samples;
|
||||
if (!mCoreSyncProduceAudio(audio->p->sync, audio->left, audio->samples)) {
|
||||
// Interrupted
|
||||
|
@ -1035,6 +1070,15 @@ void GBAudioPSGDeserialize(struct GBAudio* audio, const struct GBSerializedPSGSt
|
|||
|
||||
void GBAudioSerialize(const struct GBAudio* audio, struct GBSerializedState* state) {
|
||||
GBAudioPSGSerialize(audio, &state->audio.psg, &state->audio.flags);
|
||||
|
||||
size_t i;
|
||||
for (i = 0; i < GB_MAX_SAMPLES; ++i) {
|
||||
STORE_16LE(audio->currentSamples[i].left, 0, &state->audio2.currentSamples[i].left);
|
||||
STORE_16LE(audio->currentSamples[i].right, 0, &state->audio2.currentSamples[i].right);
|
||||
}
|
||||
STORE_32LE(audio->lastSample, 0, &state->audio2.lastSample);
|
||||
state->audio2.sampleIndex = audio->sampleIndex;
|
||||
|
||||
STORE_32LE(audio->capLeft, 0, &state->audio.capLeft);
|
||||
STORE_32LE(audio->capRight, 0, &state->audio.capRight);
|
||||
STORE_32LE(audio->sampleEvent.when - mTimingCurrentTime(audio->timing), 0, &state->audio.nextSample);
|
||||
|
@ -1044,6 +1088,15 @@ void GBAudioDeserialize(struct GBAudio* audio, const struct GBSerializedState* s
|
|||
GBAudioPSGDeserialize(audio, &state->audio.psg, &state->audio.flags);
|
||||
LOAD_32LE(audio->capLeft, 0, &state->audio.capLeft);
|
||||
LOAD_32LE(audio->capRight, 0, &state->audio.capRight);
|
||||
|
||||
size_t i;
|
||||
for (i = 0; i < GB_MAX_SAMPLES; ++i) {
|
||||
LOAD_16LE(audio->currentSamples[i].left, 0, &state->audio2.currentSamples[i].left);
|
||||
LOAD_16LE(audio->currentSamples[i].right, 0, &state->audio2.currentSamples[i].right);
|
||||
}
|
||||
LOAD_32LE(audio->lastSample, 0, &state->audio2.lastSample);
|
||||
audio->sampleIndex = state->audio2.sampleIndex;
|
||||
|
||||
uint32_t when;
|
||||
LOAD_32LE(when, 0, &state->audio.nextSample);
|
||||
mTimingSchedule(audio->timing, &audio->sampleEvent, when);
|
||||
|
|
|
@ -428,7 +428,7 @@ static void _GBCoreSetAVStream(struct mCore* core, struct mAVStream* stream) {
|
|||
stream->videoDimensionsChanged(stream, width, height);
|
||||
}
|
||||
if (stream && stream->audioRateChanged) {
|
||||
stream->audioRateChanged(stream, DMG_SM83_FREQUENCY / gb->audio.sampleInterval);
|
||||
stream->audioRateChanged(stream, DMG_SM83_FREQUENCY / 32);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue