diff --git a/CHANGES b/CHANGES index 96fcce36d..351a0e7f3 100644 --- a/CHANGES +++ b/CHANGES @@ -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) diff --git a/include/mgba/core/interface.h b/include/mgba/core/interface.h index 9e0dfd798..76d723ff8 100644 --- a/include/mgba/core/interface.h +++ b/include/mgba/core/interface.h @@ -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); diff --git a/include/mgba/internal/gb/audio.h b/include/mgba/internal/gb/audio.h index 3667145e1..5b8993ac5 100644 --- a/include/mgba/internal/gb/audio.h +++ b/include/mgba/internal/gb/audio.h @@ -10,8 +10,11 @@ CXX_GUARD_START +#include #include +#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; diff --git a/include/mgba/internal/gb/serialize.h b/include/mgba/internal/gb/serialize.h index 11bdf7dc3..c950af782 100644 --- a/include/mgba/internal/gb/serialize.h +++ b/include/mgba/internal/gb/serialize.h @@ -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]; diff --git a/src/feature/ffmpeg/ffmpeg-encoder.c b/src/feature/ffmpeg/ffmpeg-encoder.c index a2810fdd3..6c578efd5 100644 --- a/src/feature/ffmpeg/ffmpeg-encoder.c +++ b/src/feature/ffmpeg/ffmpeg-encoder.c @@ -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 +} diff --git a/src/feature/ffmpeg/ffmpeg-encoder.h b/src/feature/ffmpeg/ffmpeg-encoder.h index a484a9673..f244745b7 100644 --- a/src/feature/ffmpeg/ffmpeg-encoder.h +++ b/src/feature/ffmpeg/ffmpeg-encoder.h @@ -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); diff --git a/src/gb/audio.c b/src/gb/audio.c index 94a1c40c4..793cceec8 100644 --- a/src/gb/audio.c +++ b/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); diff --git a/src/gb/core.c b/src/gb/core.c index 804c44a63..992276aec 100644 --- a/src/gb/core.c +++ b/src/gb/core.c @@ -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) { diff --git a/src/gba/audio.c b/src/gba/audio.c index c4f989f63..5d7dd347b 100644 --- a/src/gba/audio.c +++ b/src/gba/audio.c @@ -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); diff --git a/src/gba/core.c b/src/gba/core.c index c0570571a..fe11eb38b 100644 --- a/src/gba/core.c +++ b/src/gba/core.c @@ -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) { diff --git a/src/platform/qt/VideoView.cpp b/src/platform/qt/VideoView.cpp index d2a4850cd..3548aa5d7 100644 --- a/src/platform/qt/VideoView.cpp +++ b/src/platform/qt/VideoView.cpp @@ -167,7 +167,7 @@ void VideoView::updatePresets() { addPreset(m_ui.presetLossless, { "MKV", "libx264rgb", - "FLAC", + "WavPack", -1, 0, { m_nativeWidth, m_nativeHeight } diff --git a/src/platform/qt/VideoView.ui b/src/platform/qt/VideoView.ui index 9dce12f56..53523653f 100644 --- a/src/platform/qt/VideoView.ui +++ b/src/platform/qt/VideoView.ui @@ -324,6 +324,11 @@ FLAC + + + WavPack + + Opus diff --git a/src/platform/qt/ts/medusa-emu-de.ts b/src/platform/qt/ts/medusa-emu-de.ts index 53d6bebf4..b825a4de1 100644 --- a/src/platform/qt/ts/medusa-emu-de.ts +++ b/src/platform/qt/ts/medusa-emu-de.ts @@ -6008,11 +6008,6 @@ Download-Größe: %3 Load cheats - - - Periodally autosave state - - Save entered cheats @@ -6148,6 +6143,11 @@ Download-Größe: %3 Save state extra data: Zusätzliche Savestate-Daten: + + + Periodically autosave state + + diff --git a/src/platform/qt/ts/medusa-emu-es.ts b/src/platform/qt/ts/medusa-emu-es.ts index 3801936f0..0e61ebe98 100644 --- a/src/platform/qt/ts/medusa-emu-es.ts +++ b/src/platform/qt/ts/medusa-emu-es.ts @@ -4805,11 +4805,6 @@ Tamaño de la descarga: %3 Load cheats - - - Periodally autosave state - - Save entered cheats @@ -5172,6 +5167,11 @@ Tamaño de la descarga: %3 SGB BIOS file: Archivo BIOS SGB: + + + Periodically autosave state + + Save games diff --git a/src/platform/qt/ts/medusa-emu-it.ts b/src/platform/qt/ts/medusa-emu-it.ts index 2a301ed01..bf310e849 100644 --- a/src/platform/qt/ts/medusa-emu-it.ts +++ b/src/platform/qt/ts/medusa-emu-it.ts @@ -4795,11 +4795,6 @@ Dimensione del download: %3 Load cheats - - - Periodally autosave state - - Save entered cheats @@ -4981,6 +4976,11 @@ Dimensione del download: %3 Show OSD messages Mostra messaggi OSD + + + Periodically autosave state + + Show filename instead of ROM name in title bar diff --git a/src/platform/qt/ts/mgba-en.ts b/src/platform/qt/ts/mgba-en.ts index 52c0cc4ed..8a559316a 100644 --- a/src/platform/qt/ts/mgba-en.ts +++ b/src/platform/qt/ts/mgba-en.ts @@ -4804,11 +4804,6 @@ Download size: %3 Load cheats - - - Periodally autosave state - - Save entered cheats @@ -4964,6 +4959,11 @@ Download size: %3 Enable Discord Rich Presence + + + Periodically autosave state + + Show FPS in title bar diff --git a/src/platform/qt/ts/mgba-fi.ts b/src/platform/qt/ts/mgba-fi.ts index 350dda558..5bce22575 100644 --- a/src/platform/qt/ts/mgba-fi.ts +++ b/src/platform/qt/ts/mgba-fi.ts @@ -4805,11 +4805,6 @@ Download size: %3 Load cheats - - - Periodally autosave state - - Save entered cheats @@ -4965,6 +4960,11 @@ Download size: %3 Enable Discord Rich Presence + + + Periodically autosave state + + Show FPS in title bar diff --git a/src/platform/qt/ts/mgba-fr.ts b/src/platform/qt/ts/mgba-fr.ts index 5e720908b..f8a37311c 100644 --- a/src/platform/qt/ts/mgba-fr.ts +++ b/src/platform/qt/ts/mgba-fr.ts @@ -4814,6 +4814,11 @@ Taille du téléchargement : %3 When inactive: + + + Periodically autosave state + + When minimized: @@ -4889,11 +4894,6 @@ Taille du téléchargement : %3 Load cheats - - - Periodally autosave state - - Save entered cheats diff --git a/src/platform/qt/ts/mgba-hu.ts b/src/platform/qt/ts/mgba-hu.ts index 4789b8604..747a94bd5 100644 --- a/src/platform/qt/ts/mgba-hu.ts +++ b/src/platform/qt/ts/mgba-hu.ts @@ -4772,6 +4772,11 @@ Download size: %3 Bilinear filtering + + + Periodically autosave state + + Show filename instead of ROM name in library view @@ -4918,11 +4923,6 @@ Download size: %3 Load cheats - - - Periodally autosave state - - Save entered cheats diff --git a/src/platform/qt/ts/mgba-ja.ts b/src/platform/qt/ts/mgba-ja.ts index 33639e7a9..09721c90d 100644 --- a/src/platform/qt/ts/mgba-ja.ts +++ b/src/platform/qt/ts/mgba-ja.ts @@ -4798,11 +4798,6 @@ Download size: %3 Load cheats - - - Periodally autosave state - - Save entered cheats @@ -4928,6 +4923,11 @@ Download size: %3 Save state extra data: + + + Periodically autosave state + + diff --git a/src/platform/qt/ts/mgba-ko.ts b/src/platform/qt/ts/mgba-ko.ts index a9b2b8ddb..2416d311b 100644 --- a/src/platform/qt/ts/mgba-ko.ts +++ b/src/platform/qt/ts/mgba-ko.ts @@ -4788,11 +4788,6 @@ Download size: %3 Load cheats - - - Periodally autosave state - - Save entered cheats @@ -4928,6 +4923,11 @@ Download size: %3 Save state extra data: + + + Periodically autosave state + + diff --git a/src/platform/qt/ts/mgba-ms.ts b/src/platform/qt/ts/mgba-ms.ts index 4bc5337b3..a69fa12e5 100644 --- a/src/platform/qt/ts/mgba-ms.ts +++ b/src/platform/qt/ts/mgba-ms.ts @@ -4802,11 +4802,6 @@ Download size: %3 Load cheats - - - Periodally autosave state - - Save entered cheats @@ -4962,6 +4957,11 @@ Download size: %3 Enable Discord Rich Presence Dayakan Discord Rich Presence + + + Periodically autosave state + + Show FPS in title bar diff --git a/src/platform/qt/ts/mgba-nb_NO.ts b/src/platform/qt/ts/mgba-nb_NO.ts index 8e7408fce..146d1bb26 100644 --- a/src/platform/qt/ts/mgba-nb_NO.ts +++ b/src/platform/qt/ts/mgba-nb_NO.ts @@ -4805,11 +4805,6 @@ Download size: %3 Load cheats - - - Periodally autosave state - - Save entered cheats @@ -4965,6 +4960,11 @@ Download size: %3 Enable Discord Rich Presence + + + Periodically autosave state + + Show FPS in title bar diff --git a/src/platform/qt/ts/mgba-nl.ts b/src/platform/qt/ts/mgba-nl.ts index 82733919d..84e027b04 100644 --- a/src/platform/qt/ts/mgba-nl.ts +++ b/src/platform/qt/ts/mgba-nl.ts @@ -4804,11 +4804,6 @@ Download size: %3 Load cheats - - - Periodally autosave state - - Save entered cheats @@ -4949,6 +4944,11 @@ Download size: %3 Interframe blending + + + Periodically autosave state + + Language diff --git a/src/platform/qt/ts/mgba-pl.ts b/src/platform/qt/ts/mgba-pl.ts index a1393a015..8e2d5706c 100644 --- a/src/platform/qt/ts/mgba-pl.ts +++ b/src/platform/qt/ts/mgba-pl.ts @@ -4812,11 +4812,6 @@ Rozmiar pobierania: %3 Load cheats - - - Periodally autosave state - - Save entered cheats @@ -4972,6 +4967,11 @@ Rozmiar pobierania: %3 Enable Discord Rich Presence Włącz Discord Rich Presence + + + Periodically autosave state + + Show FPS in title bar diff --git a/src/platform/qt/ts/mgba-pt_BR.ts b/src/platform/qt/ts/mgba-pt_BR.ts index 0a9b07ec0..411f4ffca 100644 --- a/src/platform/qt/ts/mgba-pt_BR.ts +++ b/src/platform/qt/ts/mgba-pt_BR.ts @@ -4800,11 +4800,6 @@ Download size: %3 Load cheats - - - Periodally autosave state - - Save entered cheats @@ -5167,6 +5162,11 @@ Download size: %3 SGB BIOS file: Arquivo da BIOS do SGB: + + + Periodically autosave state + + Save games diff --git a/src/platform/qt/ts/mgba-ru.ts b/src/platform/qt/ts/mgba-ru.ts index a7e5cb0a8..a18c5d340 100644 --- a/src/platform/qt/ts/mgba-ru.ts +++ b/src/platform/qt/ts/mgba-ru.ts @@ -4811,11 +4811,6 @@ Download size: %3 Load cheats - - - Periodally autosave state - - Save entered cheats @@ -4956,6 +4951,11 @@ Download size: %3 Interframe blending + + + Periodically autosave state + + Language diff --git a/src/platform/qt/ts/mgba-template.ts b/src/platform/qt/ts/mgba-template.ts index d92ee6d3d..67835c67a 100644 --- a/src/platform/qt/ts/mgba-template.ts +++ b/src/platform/qt/ts/mgba-template.ts @@ -4802,11 +4802,6 @@ Download size: %3 Load cheats - - - Periodally autosave state - - Save entered cheats @@ -4962,6 +4957,11 @@ Download size: %3 Enable Discord Rich Presence + + + Periodically autosave state + + Show FPS in title bar diff --git a/src/platform/qt/ts/mgba-tr.ts b/src/platform/qt/ts/mgba-tr.ts index 30d00b9e4..ce9f5bb48 100644 --- a/src/platform/qt/ts/mgba-tr.ts +++ b/src/platform/qt/ts/mgba-tr.ts @@ -4803,11 +4803,6 @@ Download size: %3 Load cheats - - - Periodally autosave state - - Save entered cheats @@ -4948,6 +4943,11 @@ Download size: %3 Interframe blending Kareler-arası Karıştırma + + + Periodically autosave state + + Language diff --git a/src/platform/qt/ts/mgba-zh_CN.ts b/src/platform/qt/ts/mgba-zh_CN.ts index 1162e82c2..52c478e0c 100644 --- a/src/platform/qt/ts/mgba-zh_CN.ts +++ b/src/platform/qt/ts/mgba-zh_CN.ts @@ -117,7 +117,7 @@ Download size: %3 Open in archive... - 在压缩文件中打开... + 打开压缩文件... @@ -170,17 +170,17 @@ Download size: %3 Can't set format of context-less audio device - + 无法设置无上下文音频设备的格式 Audio device is missing its core - + 音频设备缺少其核心 Writing data to read-only audio device - + 将数据写入只读音频设备 @@ -188,7 +188,7 @@ Download size: %3 Can't start an audio processor without input - + 没有输入无法启动音频处理器 @@ -196,7 +196,7 @@ Download size: %3 Can't start an audio processor without input - + 没有输入无法启动音频处理器 @@ -264,28 +264,28 @@ Download size: %3 BattleChip data missing - + 缺失 BattleChip 数据 BattleChip data is missing. BattleChip Gates will still work, but some graphics will be missing. Would you like to download the data now? - + 缺少 BattleChip 数据。BattleChip Gate 仍然可以使用,但一些图形将会丢失。您要立即下载数据吗? Select deck file - + 选择卡座文件 Incompatible deck - + 不兼容的卡座 The selected deck is not compatible with this Chip Gate - + 选定的卡座与此 Chip Gate 不兼容 @@ -494,12 +494,12 @@ Download size: %3 Couldn't Connect - + 无法连接 Could not connect to Dolphin. - + 无法连接到 Dolphin。 @@ -793,7 +793,7 @@ Download size: %3 ROM Only - + 仅 ROM @@ -823,7 +823,7 @@ Download size: %3 MBC5 + Rumble - + MB5 + 震动 @@ -853,7 +853,7 @@ Download size: %3 Pocket Cam - + 口袋摄像机 @@ -868,12 +868,12 @@ Download size: %3 NT (new) - + NT(新) Pokémon Jade/Diamond - + 宝可梦翡翠/钻石 @@ -4808,11 +4808,6 @@ Download size: %3 Load cheats - - - Periodally autosave state - - Save entered cheats @@ -4968,6 +4963,11 @@ Download size: %3 Enable Discord Rich Presence 启用 Discord Rich Presence + + + Periodically autosave state + + Show FPS in title bar