Merge branch 'master' (early part) into medusa

This commit is contained in:
Vicki Pfau 2022-08-23 21:06:07 -07:00
commit 7278900032
30 changed files with 290 additions and 173 deletions

View File

@ -115,6 +115,8 @@ Misc:
- Debugger: Save and restore CLI history
- 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
@ -140,6 +142,7 @@ Misc:
- Qt: Add e-Card passing to the command line (closes mgba.io/i/2474)
- Qt: Boot both a multiboot image and ROM with CLI args (closes mgba.io/i/1941)
- Qt: Improve cheat parsing (fixes mgba.io/i/2297)
- Qt: Change lossless setting to use WavPack audio
- SDL: Support exposing an axis directly as the gyro value (closes mgba.io/i/2531)
- Windows: Attach to console if present
- Vita: Add bilinear filtering option (closes mgba.io/i/344)

View File

@ -189,6 +189,7 @@ DECLARE_VECTOR(mCoreCallbacksList, struct mCoreCallbacks);
struct mAVStream {
void (*videoDimensionsChanged)(struct mAVStream*, unsigned width, unsigned height);
void (*audioRateChanged)(struct mAVStream*, unsigned rate);
void (*postVideoFrame)(struct mAVStream*, const color_t* buffer, size_t stride);
void (*postAudioFrame)(struct mAVStream*, int16_t left, int16_t right);
void (*postAudioBuffer)(struct mAVStream*, struct blip_t* left, struct blip_t* right);

View File

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

View File

@ -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];

View File

@ -38,12 +38,15 @@ static void _ffmpegPostVideoFrame(struct mAVStream*, const color_t* pixels, size
static void _ffmpegPostAudioFrame(struct mAVStream*, int16_t left, int16_t right);
static void _ffmpegSetVideoDimensions(struct mAVStream*, unsigned width, unsigned height);
static void _ffmpegSetVideoFrameRate(struct mAVStream*, unsigned numerator, unsigned denominator);
static void _ffmpegSetAudioRate(struct mAVStream*, unsigned rate);
static bool _ffmpegWriteAudioFrame(struct FFmpegEncoder* encoder, struct AVFrame* audioFrame);
static bool _ffmpegWriteVideoFrame(struct FFmpegEncoder* encoder, struct AVFrame* videoFrame);
static void _ffmpegOpenResampleContext(struct FFmpegEncoder* encoder);
enum {
PREFERRED_SAMPLE_RATE = 0x8000
PREFERRED_SAMPLE_RATE = 0x10000
};
void FFmpegEncoderInit(struct FFmpegEncoder* encoder) {
@ -52,9 +55,10 @@ void FFmpegEncoderInit(struct FFmpegEncoder* encoder) {
#endif
encoder->d.videoDimensionsChanged = _ffmpegSetVideoDimensions;
encoder->d.audioRateChanged = _ffmpegSetAudioRate;
encoder->d.postVideoFrame = _ffmpegPostVideoFrame;
encoder->d.postAudioFrame = _ffmpegPostAudioFrame;
encoder->d.postAudioBuffer = 0;
encoder->d.postAudioBuffer = NULL;
encoder->d.videoFrameRateChanged = _ffmpegSetVideoFrameRate;
encoder->audioCodec = NULL;
@ -68,6 +72,7 @@ void FFmpegEncoderInit(struct FFmpegEncoder* encoder) {
encoder->iheight = GBA_VIDEO_VERTICAL_PIXELS;
encoder->frameCycles = VIDEO_TOTAL_LENGTH;
encoder->cycles = GBA_ARM7TDMI_FREQUENCY;
encoder->isampleRate = PREFERRED_SAMPLE_RATE;
encoder->frameskip = 1;
encoder->skipResidue = 0;
encoder->loop = false;
@ -151,19 +156,24 @@ bool FFmpegEncoderSetAudio(struct FFmpegEncoder* encoder, const char* acodec, un
if (encoder->sampleFormat == AV_SAMPLE_FMT_NONE) {
return false;
}
encoder->sampleRate = PREFERRED_SAMPLE_RATE;
encoder->sampleRate = encoder->isampleRate;
if (codec->supported_samplerates) {
for (i = 0; codec->supported_samplerates[i]; ++i) {
if (codec->supported_samplerates[i] < PREFERRED_SAMPLE_RATE) {
if (codec->supported_samplerates[i] < encoder->isampleRate) {
continue;
}
if (encoder->sampleRate == PREFERRED_SAMPLE_RATE || encoder->sampleRate > codec->supported_samplerates[i]) {
if (encoder->sampleRate == encoder->isampleRate || encoder->sampleRate > codec->supported_samplerates[i]) {
encoder->sampleRate = codec->supported_samplerates[i];
}
}
} else if (codec->id == AV_CODEC_ID_FLAC) {
// HACK: FLAC doesn't support > 65535Hz unless it's divisible by 10
if (encoder->sampleRate >= 65535) {
encoder->sampleRate -= encoder->isampleRate % 10;
}
} else if (codec->id == AV_CODEC_ID_AAC) {
// HACK: AAC doesn't support 32768Hz (it rounds to 32000), but libfaac doesn't tell us that
encoder->sampleRate = 44100;
encoder->sampleRate = 48000;
}
encoder->audioCodec = acodec;
encoder->audioBitrate = abr;
@ -325,22 +335,7 @@ bool FFmpegEncoderOpen(struct FFmpegEncoder* encoder, const char* outfile) {
encoder->audioFrame->format = encoder->audio->sample_fmt;
encoder->audioFrame->pts = 0;
encoder->audioFrame->channel_layout = AV_CH_LAYOUT_STEREO;
#ifdef USE_LIBAVRESAMPLE
encoder->resampleContext = avresample_alloc_context();
av_opt_set_int(encoder->resampleContext, "in_channel_layout", AV_CH_LAYOUT_STEREO, 0);
av_opt_set_int(encoder->resampleContext, "out_channel_layout", AV_CH_LAYOUT_STEREO, 0);
av_opt_set_int(encoder->resampleContext, "in_sample_rate", PREFERRED_SAMPLE_RATE, 0);
av_opt_set_int(encoder->resampleContext, "out_sample_rate", encoder->sampleRate, 0);
av_opt_set_int(encoder->resampleContext, "in_sample_fmt", AV_SAMPLE_FMT_S16, 0);
av_opt_set_int(encoder->resampleContext, "out_sample_fmt", encoder->sampleFormat, 0);
avresample_open(encoder->resampleContext);
#else
encoder->resampleContext = swr_alloc_set_opts(NULL, AV_CH_LAYOUT_STEREO, encoder->sampleFormat, encoder->sampleRate,
AV_CH_LAYOUT_STEREO, AV_SAMPLE_FMT_S16, PREFERRED_SAMPLE_RATE, 0, NULL);
swr_init(encoder->resampleContext);
#endif
encoder->audioBufferSize = (encoder->audioFrame->nb_samples * PREFERRED_SAMPLE_RATE / encoder->sampleRate) * 4;
encoder->audioBuffer = av_malloc(encoder->audioBufferSize);
_ffmpegOpenResampleContext(encoder);
av_frame_get_buffer(encoder->audioFrame, 0);
if (encoder->audio->codec->id == AV_CODEC_ID_AAC &&
@ -867,6 +862,11 @@ static void _ffmpegSetVideoFrameRate(struct mAVStream* stream, unsigned numerato
FFmpegEncoderSetInputFrameRate(encoder, numerator, denominator);
}
static void _ffmpegSetAudioRate(struct mAVStream* stream, unsigned rate) {
struct FFmpegEncoder* encoder = (struct FFmpegEncoder*) stream;
FFmpegEncoderSetInputSampleRate(encoder, rate);
}
void FFmpegEncoderSetInputFrameRate(struct FFmpegEncoder* encoder, int numerator, int denominator) {
reduceFraction(&numerator, &denominator);
encoder->frameCycles = numerator;
@ -875,3 +875,35 @@ void FFmpegEncoderSetInputFrameRate(struct FFmpegEncoder* encoder, int numerator
encoder->video->framerate = (AVRational) { denominator, numerator * encoder->frameskip };
}
}
void FFmpegEncoderSetInputSampleRate(struct FFmpegEncoder* encoder, int sampleRate) {
encoder->isampleRate = sampleRate;
if (encoder->resampleContext) {
av_freep(&encoder->audioBuffer);
#ifdef USE_LIBAVRESAMPLE
avresample_close(encoder->resampleContext);
#else
swr_free(&encoder->resampleContext);
#endif
_ffmpegOpenResampleContext(encoder);
}
}
void _ffmpegOpenResampleContext(struct FFmpegEncoder* encoder) {
encoder->audioBufferSize = av_rescale_q(encoder->audioFrame->nb_samples, (AVRational) { 4, encoder->sampleRate }, (AVRational) { 1, encoder->isampleRate });
encoder->audioBuffer = av_malloc(encoder->audioBufferSize);
#ifdef USE_LIBAVRESAMPLE
encoder->resampleContext = avresample_alloc_context();
av_opt_set_int(encoder->resampleContext, "in_channel_layout", AV_CH_LAYOUT_STEREO, 0);
av_opt_set_int(encoder->resampleContext, "out_channel_layout", AV_CH_LAYOUT_STEREO, 0);
av_opt_set_int(encoder->resampleContext, "in_sample_rate", encoder->isampleRate, 0);
av_opt_set_int(encoder->resampleContext, "out_sample_rate", encoder->sampleRate, 0);
av_opt_set_int(encoder->resampleContext, "in_sample_fmt", AV_SAMPLE_FMT_S16, 0);
av_opt_set_int(encoder->resampleContext, "out_sample_fmt", encoder->sampleFormat, 0);
avresample_open(encoder->resampleContext);
#else
encoder->resampleContext = swr_alloc_set_opts(NULL, AV_CH_LAYOUT_STEREO, encoder->sampleFormat, encoder->sampleRate,
AV_CH_LAYOUT_STEREO, AV_SAMPLE_FMT_S16, encoder->isampleRate, 0, NULL);
swr_init(encoder->resampleContext);
#endif
}

View File

@ -56,6 +56,7 @@ struct FFmpegEncoder {
int height;
int iwidth;
int iheight;
int isampleRate;
int frameCycles;
int cycles;
int frameskip;
@ -78,6 +79,7 @@ bool FFmpegEncoderSetVideo(struct FFmpegEncoder*, const char* vcodec, int vbr, i
bool FFmpegEncoderSetContainer(struct FFmpegEncoder*, const char* container);
void FFmpegEncoderSetDimensions(struct FFmpegEncoder*, int width, int height);
void FFmpegEncoderSetInputFrameRate(struct FFmpegEncoder*, int numerator, int denominator);
void FFmpegEncoderSetInputSampleRate(struct FFmpegEncoder*, int sampleRate);
void FFmpegEncoderSetLooping(struct FFmpegEncoder*, bool loop);
bool FFmpegEncoderVerifyContainer(struct FFmpegEncoder*);
bool FFmpegEncoderOpen(struct FFmpegEncoder*, const char* outfile);

View File

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

View File

@ -439,6 +439,9 @@ static void _GBCoreSetAVStream(struct mCore* core, struct mAVStream* stream) {
if (stream && stream->videoFrameRateChanged) {
stream->videoFrameRateChanged(stream, core->frameCycles(core), core->frequency(core));
}
if (stream && stream->audioRateChanged) {
stream->audioRateChanged(stream, DMG_SM83_FREQUENCY / 32);
}
}
static bool _GBCoreLoadROM(struct mCore* core, struct VFile* vf) {

View File

@ -237,7 +237,11 @@ void GBAAudioWriteSOUNDCNT_X(struct GBAAudio* audio, uint16_t value) {
void GBAAudioWriteSOUNDBIAS(struct GBAAudio* audio, uint16_t value) {
audio->soundbias = value;
int32_t oldSampleInterval = audio->sampleInterval;
audio->sampleInterval = 0x200 >> GBARegisterSOUNDBIASGetResolution(value);
if (oldSampleInterval != audio->sampleInterval && audio->p->stream && audio->p->stream->audioRateChanged) {
audio->p->stream->audioRateChanged(audio->p->stream, GBA_ARM7TDMI_FREQUENCY / audio->sampleInterval);
}
}
void GBAAudioWriteWaveRAM(struct GBAAudio* audio, int address, uint32_t value) {
@ -401,20 +405,15 @@ static void _sample(struct mTiming* timing, void* user, uint32_t cyclesLate) {
GBAAudioSample(audio, mTimingCurrentTime(&audio->p->timing) - cyclesLate);
int samples = 2 << GBARegisterSOUNDBIASGetResolution(audio->soundbias);
int sampleMask = 1 << GBARegisterSOUNDBIASGetResolution(audio->soundbias);
memset(audio->chA.samples, audio->chA.samples[samples - 1], sizeof(audio->chA.samples));
memset(audio->chB.samples, audio->chB.samples[samples - 1], sizeof(audio->chB.samples));
mCoreSyncLockAudio(audio->p->sync);
unsigned produced;
int32_t sampleSumLeft = 0;
int32_t sampleSumRight = 0;
int i;
for (i = 0; i < samples; ++i) {
int16_t sampleLeft = audio->currentSamples[i].left;
int16_t sampleRight = audio->currentSamples[i].right;
sampleSumLeft += sampleLeft;
sampleSumRight += sampleRight;
if ((size_t) blip_samples_avail(audio->psg.left) < audio->samples) {
blip_add_delta(audio->psg.left, audio->clock, sampleLeft - audio->lastLeft);
blip_add_delta(audio->psg.right, audio->clock, sampleRight - audio->lastRight);
@ -427,13 +426,9 @@ static void _sample(struct mTiming* timing, void* user, uint32_t cyclesLate) {
audio->clock -= CLOCKS_PER_FRAME;
}
}
// TODO: Post all frames
if (audio->p->stream && audio->p->stream->postAudioFrame && (i & (sampleMask - 1)) == sampleMask - 1) {
sampleSumLeft /= sampleMask;
sampleSumRight /= sampleMask;
audio->p->stream->postAudioFrame(audio->p->stream, sampleSumLeft, sampleSumRight);
sampleSumLeft = 0;
sampleSumRight = 0;
if (audio->p->stream && audio->p->stream->postAudioFrame) {
audio->p->stream->postAudioFrame(audio->p->stream, sampleLeft, sampleRight);
}
}
produced = blip_samples_avail(audio->psg.left);

View File

@ -514,6 +514,9 @@ static void _GBACoreSetAVStream(struct mCore* core, struct mAVStream* stream) {
if (stream && stream->videoFrameRateChanged) {
stream->videoFrameRateChanged(stream, core->frameCycles(core), core->frequency(core));
}
if (stream && stream->audioRateChanged) {
stream->audioRateChanged(stream, GBA_ARM7TDMI_FREQUENCY / gba->audio.sampleInterval);
}
}
static bool _GBACoreLoadROM(struct mCore* core, struct VFile* vf) {

View File

@ -167,7 +167,7 @@ void VideoView::updatePresets() {
addPreset(m_ui.presetLossless, {
"MKV",
"libx264rgb",
"FLAC",
"WavPack",
-1,
0,
{ m_nativeWidth, m_nativeHeight }

View File

@ -324,6 +324,11 @@
<string>FLAC</string>
</property>
</item>
<item>
<property name="text">
<string>WavPack</string>
</property>
</item>
<item>
<property name="text">
<string>Opus</string>

View File

@ -6008,11 +6008,6 @@ Download-Größe: %3</translation>
<source>Load cheats</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="582"/>
<source>Periodally autosave state</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="592"/>
<source>Save entered cheats</source>
@ -6148,6 +6143,11 @@ Download-Größe: %3</translation>
<source>Save state extra data:</source>
<translation>Zusätzliche Savestate-Daten:</translation>
</message>
<message>
<location filename="../SettingsView.ui" line="582"/>
<source>Periodically autosave state</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="626"/>
<location filename="../SettingsView.ui" line="670"/>

View File

@ -4805,11 +4805,6 @@ Tamaño de la descarga: %3</translation>
<source>Load cheats</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="582"/>
<source>Periodally autosave state</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="592"/>
<source>Save entered cheats</source>
@ -5172,6 +5167,11 @@ Tamaño de la descarga: %3</translation>
<source>SGB BIOS file:</source>
<translation>Archivo BIOS SGB:</translation>
</message>
<message>
<location filename="../SettingsView.ui" line="582"/>
<source>Periodically autosave state</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="1464"/>
<source>Save games</source>

View File

@ -4795,11 +4795,6 @@ Dimensione del download: %3</translation>
<source>Load cheats</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="582"/>
<source>Periodally autosave state</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="592"/>
<source>Save entered cheats</source>
@ -4981,6 +4976,11 @@ Dimensione del download: %3</translation>
<source>Show OSD messages</source>
<translation>Mostra messaggi OSD</translation>
</message>
<message>
<location filename="../SettingsView.ui" line="582"/>
<source>Periodically autosave state</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="865"/>
<source>Show filename instead of ROM name in title bar</source>

View File

@ -4804,11 +4804,6 @@ Download size: %3</source>
<source>Load cheats</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="582"/>
<source>Periodally autosave state</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="592"/>
<source>Save entered cheats</source>
@ -4964,6 +4959,11 @@ Download size: %3</source>
<source>Enable Discord Rich Presence</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="582"/>
<source>Periodically autosave state</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="855"/>
<source>Show FPS in title bar</source>

View File

@ -4805,11 +4805,6 @@ Download size: %3</source>
<source>Load cheats</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="582"/>
<source>Periodally autosave state</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="592"/>
<source>Save entered cheats</source>
@ -4965,6 +4960,11 @@ Download size: %3</source>
<source>Enable Discord Rich Presence</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="582"/>
<source>Periodically autosave state</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="855"/>
<source>Show FPS in title bar</source>

View File

@ -4814,6 +4814,11 @@ Taille du téléchargement&#xa0;:&#xa0;%3</translation>
<source>When inactive:</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="582"/>
<source>Periodically autosave state</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="813"/>
<source>When minimized:</source>
@ -4889,11 +4894,6 @@ Taille du téléchargement&#xa0;:&#xa0;%3</translation>
<source>Load cheats</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="582"/>
<source>Periodally autosave state</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="592"/>
<source>Save entered cheats</source>

View File

@ -4772,6 +4772,11 @@ Download size: %3</source>
<source>Bilinear filtering</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="582"/>
<source>Periodically autosave state</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="750"/>
<source>Show filename instead of ROM name in library view</source>
@ -4918,11 +4923,6 @@ Download size: %3</source>
<source>Load cheats</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="582"/>
<source>Periodally autosave state</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="592"/>
<source>Save entered cheats</source>

View File

@ -4798,11 +4798,6 @@ Download size: %3</source>
<source>Load cheats</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="582"/>
<source>Periodally autosave state</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="592"/>
<source>Save entered cheats</source>
@ -4928,6 +4923,11 @@ Download size: %3</source>
<source>Save state extra data:</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="582"/>
<source>Periodically autosave state</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="626"/>
<location filename="../SettingsView.ui" line="670"/>

View File

@ -4788,11 +4788,6 @@ Download size: %3</source>
<source>Load cheats</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="582"/>
<source>Periodally autosave state</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="592"/>
<source>Save entered cheats</source>
@ -4928,6 +4923,11 @@ Download size: %3</source>
<source>Save state extra data:</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="582"/>
<source>Periodically autosave state</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="626"/>
<location filename="../SettingsView.ui" line="670"/>

View File

@ -4802,11 +4802,6 @@ Download size: %3</source>
<source>Load cheats</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="582"/>
<source>Periodally autosave state</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="592"/>
<source>Save entered cheats</source>
@ -4962,6 +4957,11 @@ Download size: %3</source>
<source>Enable Discord Rich Presence</source>
<translation>Dayakan Discord Rich Presence</translation>
</message>
<message>
<location filename="../SettingsView.ui" line="582"/>
<source>Periodically autosave state</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="855"/>
<source>Show FPS in title bar</source>

View File

@ -4805,11 +4805,6 @@ Download size: %3</source>
<source>Load cheats</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="582"/>
<source>Periodally autosave state</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="592"/>
<source>Save entered cheats</source>
@ -4965,6 +4960,11 @@ Download size: %3</source>
<source>Enable Discord Rich Presence</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="582"/>
<source>Periodically autosave state</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="855"/>
<source>Show FPS in title bar</source>

View File

@ -4804,11 +4804,6 @@ Download size: %3</source>
<source>Load cheats</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="582"/>
<source>Periodally autosave state</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="592"/>
<source>Save entered cheats</source>
@ -4949,6 +4944,11 @@ Download size: %3</source>
<source>Interframe blending</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="582"/>
<source>Periodically autosave state</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="702"/>
<source>Language</source>

View File

@ -4812,11 +4812,6 @@ Rozmiar pobierania: %3</translation>
<source>Load cheats</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="582"/>
<source>Periodally autosave state</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="592"/>
<source>Save entered cheats</source>
@ -4972,6 +4967,11 @@ Rozmiar pobierania: %3</translation>
<source>Enable Discord Rich Presence</source>
<translation>Włącz Discord Rich Presence</translation>
</message>
<message>
<location filename="../SettingsView.ui" line="582"/>
<source>Periodically autosave state</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="855"/>
<source>Show FPS in title bar</source>

View File

@ -4800,11 +4800,6 @@ Download size: %3</source>
<source>Load cheats</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="582"/>
<source>Periodally autosave state</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="592"/>
<source>Save entered cheats</source>
@ -5167,6 +5162,11 @@ Download size: %3</source>
<source>SGB BIOS file:</source>
<translation>Arquivo da BIOS do SGB:</translation>
</message>
<message>
<location filename="../SettingsView.ui" line="582"/>
<source>Periodically autosave state</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="1464"/>
<source>Save games</source>

View File

@ -4811,11 +4811,6 @@ Download size: %3</source>
<source>Load cheats</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="582"/>
<source>Periodally autosave state</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="592"/>
<source>Save entered cheats</source>
@ -4956,6 +4951,11 @@ Download size: %3</source>
<source>Interframe blending</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="582"/>
<source>Periodically autosave state</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="702"/>
<source>Language</source>

View File

@ -4802,11 +4802,6 @@ Download size: %3</source>
<source>Load cheats</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="582"/>
<source>Periodally autosave state</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="592"/>
<source>Save entered cheats</source>
@ -4962,6 +4957,11 @@ Download size: %3</source>
<source>Enable Discord Rich Presence</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="582"/>
<source>Periodically autosave state</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="855"/>
<source>Show FPS in title bar</source>

View File

@ -4803,11 +4803,6 @@ Download size: %3</source>
<source>Load cheats</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="582"/>
<source>Periodally autosave state</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="592"/>
<source>Save entered cheats</source>
@ -4948,6 +4943,11 @@ Download size: %3</source>
<source>Interframe blending</source>
<translation>Kareler-arası Karıştırma</translation>
</message>
<message>
<location filename="../SettingsView.ui" line="582"/>
<source>Periodically autosave state</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="702"/>
<source>Language</source>

View File

@ -117,7 +117,7 @@ Download size: %3</source>
<message>
<location filename="../ArchiveInspector.ui" line="14"/>
<source>Open in archive...</source>
<translation>...</translation>
<translation>...</translation>
</message>
<message>
<location filename="../ArchiveInspector.ui" line="20"/>
@ -170,17 +170,17 @@ Download size: %3</source>
<message>
<location filename="../AudioDevice.cpp" line="26"/>
<source>Can&apos;t set format of context-less audio device</source>
<translation type="unfinished"></translation>
<translation></translation>
</message>
<message>
<location filename="../AudioDevice.cpp" line="44"/>
<source>Audio device is missing its core</source>
<translation type="unfinished"></translation>
<translation></translation>
</message>
<message>
<location filename="../AudioDevice.cpp" line="62"/>
<source>Writing data to read-only audio device</source>
<translation type="unfinished"></translation>
<translation></translation>
</message>
</context>
<context>
@ -188,7 +188,7 @@ Download size: %3</source>
<message>
<location filename="../AudioProcessorQt.cpp" line="43"/>
<source>Can&apos;t start an audio processor without input</source>
<translation type="unfinished"></translation>
<translation></translation>
</message>
</context>
<context>
@ -196,7 +196,7 @@ Download size: %3</source>
<message>
<location filename="../AudioProcessorSDL.cpp" line="34"/>
<source>Can&apos;t start an audio processor without input</source>
<translation type="unfinished"></translation>
<translation></translation>
</message>
</context>
<context>
@ -264,28 +264,28 @@ Download size: %3</source>
<message>
<location filename="../BattleChipView.cpp" line="117"/>
<source>BattleChip data missing</source>
<translation type="unfinished"></translation>
<translation> BattleChip </translation>
</message>
<message>
<location filename="../BattleChipView.cpp" line="118"/>
<source>BattleChip data is missing. BattleChip Gates will still work, but some graphics will be missing. Would you like to download the data now?</source>
<translation type="unfinished"></translation>
<translation> BattleChip BattleChip Gate 使,</translation>
</message>
<message>
<location filename="../BattleChipView.cpp" line="184"/>
<location filename="../BattleChipView.cpp" line="203"/>
<source>Select deck file</source>
<translation type="unfinished"></translation>
<translation></translation>
</message>
<message>
<location filename="../BattleChipView.cpp" line="215"/>
<source>Incompatible deck</source>
<translation type="unfinished"></translation>
<translation></translation>
</message>
<message>
<location filename="../BattleChipView.cpp" line="216"/>
<source>The selected deck is not compatible with this Chip Gate</source>
<translation type="unfinished"></translation>
<translation> Chip Gate </translation>
</message>
</context>
<context>
@ -494,12 +494,12 @@ Download size: %3</source>
<message>
<location filename="../DolphinConnector.cpp" line="57"/>
<source>Couldn&apos;t Connect</source>
<translation type="unfinished"></translation>
<translation></translation>
</message>
<message>
<location filename="../DolphinConnector.cpp" line="58"/>
<source>Could not connect to Dolphin.</source>
<translation type="unfinished"></translation>
<translation> Dolphin</translation>
</message>
</context>
<context>
@ -793,7 +793,7 @@ Download size: %3</source>
<message>
<location filename="../GameBoy.cpp" line="75"/>
<source>ROM Only</source>
<translation type="unfinished"></translation>
<translation> ROM</translation>
</message>
<message>
<location filename="../GameBoy.cpp" line="76"/>
@ -823,7 +823,7 @@ Download size: %3</source>
<message>
<location filename="../GameBoy.cpp" line="81"/>
<source>MBC5 + Rumble</source>
<translation type="unfinished"></translation>
<translation>MB5 + </translation>
</message>
<message>
<location filename="../GameBoy.cpp" line="82"/>
@ -853,7 +853,7 @@ Download size: %3</source>
<message>
<location filename="../GameBoy.cpp" line="87"/>
<source>Pocket Cam</source>
<translation type="unfinished"></translation>
<translation></translation>
</message>
<message>
<location filename="../GameBoy.cpp" line="88"/>
@ -868,12 +868,12 @@ Download size: %3</source>
<message>
<location filename="../GameBoy.cpp" line="90"/>
<source>NT (new)</source>
<translation type="unfinished"></translation>
<translation>NT()</translation>
</message>
<message>
<location filename="../GameBoy.cpp" line="91"/>
<source>Pokémon Jade/Diamond</source>
<translation type="unfinished"></translation>
<translation>/</translation>
</message>
<message>
<location filename="../GameBoy.cpp" line="92"/>
@ -4808,11 +4808,6 @@ Download size: %3</source>
<source>Load cheats</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="582"/>
<source>Periodally autosave state</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="592"/>
<source>Save entered cheats</source>
@ -4968,6 +4963,11 @@ Download size: %3</source>
<source>Enable Discord Rich Presence</source>
<translation> Discord Rich Presence</translation>
</message>
<message>
<location filename="../SettingsView.ui" line="582"/>
<source>Periodically autosave state</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../SettingsView.ui" line="855"/>
<source>Show FPS in title bar</source>