diff --git a/UI/BackgroundAudio.cpp b/UI/BackgroundAudio.cpp index 3b84dc1063..87dc6c2789 100644 --- a/UI/BackgroundAudio.cpp +++ b/UI/BackgroundAudio.cpp @@ -369,6 +369,11 @@ void BackgroundAudio::Update() { } } +inline int16_t ConvertU8ToI16(uint8_t value) { + int ivalue = value - 128; + return ivalue * 255; +} + Sample *Sample::Load(const std::string &path) { size_t bytes; uint8_t *data = g_VFS.ReadFile(path.c_str(), &bytes); @@ -384,14 +389,22 @@ Sample *Sample::Load(const std::string &path) { delete[] data; - if (wave.num_channels != 2 || wave.raw_bytes_per_frame != 4) { - ERROR_LOG(AUDIO, "Wave format not supported for mixer playback. Must be 16-bit raw stereo. '%s'", path.c_str()); + if (wave.num_channels > 2 || wave.raw_bytes_per_frame > sizeof(int16_t) * wave.num_channels) { + ERROR_LOG(AUDIO, "Wave format not supported for mixer playback. Must be 8-bit or 16-bit raw mono or stereo. '%s'", path.c_str()); return nullptr; } - int16_t *samples = new int16_t[2 * wave.numFrames]; - memcpy(samples, wave.raw_data, wave.numFrames * wave.raw_bytes_per_frame); - return new Sample(samples, wave.numFrames, wave.sample_rate); + int16_t *samples = new int16_t[wave.num_channels * wave.numFrames]; + if (wave.raw_bytes_per_frame == wave.num_channels * 2) { + // 16-bit + memcpy(samples, wave.raw_data, wave.numFrames * wave.raw_bytes_per_frame); + } else if (wave.raw_bytes_per_frame == wave.num_channels) { + // 8-bit. Convert. + for (int i = 0; i < wave.num_channels * wave.numFrames; i++) { + samples[i] = ConvertU8ToI16(wave.raw_data[i]); + } + } + return new Sample(samples, wave.num_channels, wave.numFrames, wave.sample_rate); } static inline int16_t Clamp16(int32_t sample) { @@ -434,15 +447,25 @@ void SoundEffectMixer::Mix(int16_t *buffer, int sz, int sampleRateHz) { int wholeOffset = iter->offset >> 32; int frac = (iter->offset >> 20) & 0xFFF; // Use a 12 bit fraction to get away with 32-bit multiplies - int interpolatedLeft = (sample->data_[wholeOffset * 2] * (0x1000 - frac) + sample->data_[(wholeOffset + 1) * 2] * frac) >> 12; - int interpolatedRight = (sample->data_[wholeOffset * 2 + 1] * (0x1000 - frac) + sample->data_[(wholeOffset + 1) * 2 + 1] * frac) >> 12; + if (sample->channels_ == 2) { + int interpolatedLeft = (sample->data_[wholeOffset * 2] * (0x1000 - frac) + sample->data_[(wholeOffset + 1) * 2] * frac) >> 12; + int interpolatedRight = (sample->data_[wholeOffset * 2 + 1] * (0x1000 - frac) + sample->data_[(wholeOffset + 1) * 2 + 1] * frac) >> 12; - // Clamping add on top per sample. Not great, we should be mixing at higher bitrate instead. Oh well. - int left = Clamp16(buffer[i] + (interpolatedLeft * iter->volume >> 8)); - int right = Clamp16(buffer[i + 1] + (interpolatedRight * iter->volume >> 8)); + // Clamping add on top per sample. Not great, we should be mixing at higher bitrate instead. Oh well. + int left = Clamp16(buffer[i] + (interpolatedLeft * iter->volume >> 8)); + int right = Clamp16(buffer[i + 1] + (interpolatedRight * iter->volume >> 8)); - buffer[i] = left; - buffer[i + 1] = right; + buffer[i] = left; + buffer[i + 1] = right; + } else if (sample->channels_ == 1) { + int interpolated = (sample->data_[wholeOffset] * (0x1000 - frac) + sample->data_[wholeOffset + 1] * frac) >> 12; + + // Clamping add on top per sample. Not great, we should be mixing at higher bitrate instead. Oh well. + int value = Clamp16(buffer[i] + (interpolated * iter->volume >> 8)); + + buffer[i] = value; + buffer[i + 1] = value; + } iter->offset += stride; } @@ -462,6 +485,7 @@ void SoundEffectMixer::Play(UI::UISound sfx, float volume) { void SoundEffectMixer::UpdateSample(UI::UISound sound, Sample *sample) { if (sample) { + std::lock_guard guard(mutex_); samples_[(size_t)sound] = std::unique_ptr(sample); } else { LoadDefaultSample(sound); @@ -485,6 +509,7 @@ void SoundEffectMixer::LoadDefaultSample(UI::UISound sound) { if (!sample) { ERROR_LOG(SYSTEM, "Failed to load the default sample for UI sound %d", (int)sound); } + std::lock_guard guard(mutex_); samples_[(size_t)sound] = std::unique_ptr(sample); } diff --git a/UI/BackgroundAudio.h b/UI/BackgroundAudio.h index 322e0b3f28..178ea2e1d6 100644 --- a/UI/BackgroundAudio.h +++ b/UI/BackgroundAudio.h @@ -12,13 +12,14 @@ class AT3PlusReader; struct Sample { // data must be new-ed. - Sample(int16_t *data, int length, int rateInHz) : data_(data), length_(length), rateInHz_(rateInHz) {} + Sample(int16_t *data, int channels, int length, int rateInHz) : channels_(channels), data_(data), length_(length), rateInHz_(rateInHz) {} ~Sample() { delete[] data_; } int16_t *data_; - int length_; // stereo samples. + int length_; // stereo or mono samples. int rateInHz_; // sampleRate + int channels_; static Sample *Load(const std::string &path); }; diff --git a/UI/RetroAchievementScreens.cpp b/UI/RetroAchievementScreens.cpp index 67e2920fe8..8d2ae7d7ec 100644 --- a/UI/RetroAchievementScreens.cpp +++ b/UI/RetroAchievementScreens.cpp @@ -54,6 +54,7 @@ AudioFileChooser::AudioFileChooser(std::string *value, const std::string &title, return UI::EVENT_DONE; }); Add(new Choice(ImageID("I_TRASHCAN"), new LinearLayoutParams(ITEM_HEIGHT, ITEM_HEIGHT)))->OnClick.Add([=](UI::EventParams &) { + g_BackgroundAudio.SFX().UpdateSample(sound, nullptr); value->clear(); return UI::EVENT_DONE; }); @@ -239,7 +240,10 @@ void RetroAchievementsSettingsScreen::CreateTabs() { using namespace UI; CreateAccountTab(AddTab("AchievementsAccount", ac->T("Account"))); - CreateCustomizeTab(AddTab("AchievementsCustomize", ac->T("Customize"))); + if (System_GetPropertyBool(SYSPROP_HAS_FILE_BROWSER)) { + // Don't bother creating this tab if we don't have a file browser. + CreateCustomizeTab(AddTab("AchievementsCustomize", ac->T("Customize"))); + } CreateDeveloperToolsTab(AddTab("AchievementsDeveloperTools", sy->T("Developer Tools"))); }