From 27ba7629d73055f113a14764ff3f684485239b26 Mon Sep 17 00:00:00 2001 From: Oleg Samarin Date: Fri, 29 Dec 2023 21:42:45 +0300 Subject: [PATCH] Fixed not loading a pipe if some loop was not suitable for crossfade https://github.com/GrandOrgue/grandorgue/issues/1724 --- CHANGELOG.md | 1 + src/grandorgue/loader/GOLoaderFilename.h | 9 ++ src/grandorgue/model/GOCacheObject.h | 10 ++ src/grandorgue/sound/GOSoundAudioSection.cpp | 148 ++++++++++-------- src/grandorgue/sound/GOSoundAudioSection.h | 2 + .../sound/GOSoundProviderSynthedTrem.cpp | 2 + src/grandorgue/sound/GOSoundProviderWave.cpp | 4 +- 7 files changed, 108 insertions(+), 68 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d234f15b..025e0c796 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ +- Fixed not loading a pipe if some loop was not suitable for crossfade https://github.com/GrandOrgue/grandorgue/issues/1724 - Fixed a wrong .wav filename in the log message window https://github.com/GrandOrgue/grandorgue/issues/1724 - Increased the maximum number of Tremulants from 10 to 999 - Fixed setting a reverb file name by default to the current directory https://github.com/GrandOrgue/grandorgue/issues/1741 diff --git a/src/grandorgue/loader/GOLoaderFilename.h b/src/grandorgue/loader/GOLoaderFilename.h index 70ad7a3cf..f3c6c4efe 100644 --- a/src/grandorgue/loader/GOLoaderFilename.h +++ b/src/grandorgue/loader/GOLoaderFilename.h @@ -53,6 +53,15 @@ class GOLoaderFilename { * @return a pointer to the GOFile */ std::unique_ptr Open(const GOFileStore &fileStore) const; + + wxString GenerateMessage(const wxString &srcMsg) const { + return wxString::Format("%s: %s", m_path, srcMsg); + } + + static wxString generateMessage( + const GOLoaderFilename *pFileName, const wxString &srcMsg) { + return pFileName ? pFileName->GenerateMessage(srcMsg) : srcMsg; + } }; #endif diff --git a/src/grandorgue/model/GOCacheObject.h b/src/grandorgue/model/GOCacheObject.h index 8d1b9d239..1055d95ee 100644 --- a/src/grandorgue/model/GOCacheObject.h +++ b/src/grandorgue/model/GOCacheObject.h @@ -10,6 +10,8 @@ #include +#include "loader/GOLoaderFilename.h" + class GOCache; class GOCacheWriter; class GOFileStore; @@ -77,6 +79,14 @@ class GOCacheObject { const GOCacheObject *pObjectFor, const wxString &srcMsg) { return pObjectFor ? pObjectFor->GenerateMessage(srcMsg) : srcMsg; } + + static const wxString generateMessage( + const GOCacheObject *pObjectFor, + const GOLoaderFilename *pFilename, + const wxString &srcMsg) { + return generateMessage( + pObjectFor, GOLoaderFilename::generateMessage(pFilename, srcMsg)); + } }; #endif diff --git a/src/grandorgue/sound/GOSoundAudioSection.cpp b/src/grandorgue/sound/GOSoundAudioSection.cpp index db336552a..7945b9cc4 100644 --- a/src/grandorgue/sound/GOSoundAudioSection.cpp +++ b/src/grandorgue/sound/GOSoundAudioSection.cpp @@ -13,6 +13,9 @@ #include "loader/cache/GOCache.h" #include "loader/cache/GOCacheWriter.h" +#include "loader/GOLoaderFilename.h" +#include "model/GOCacheObject.h" + #include "GOAlloc.h" #include "GOMemoryPool.h" #include "GOSampleStatistic.h" @@ -638,6 +641,7 @@ void GOAudioSection::DoCrossfade( } void GOAudioSection::Setup( + const GOCacheObject *pObjectFor, const GOLoaderFilename *pLoaderFilename, const void *pcm_data, const GOWave::SAMPLE_FORMAT pcm_data_format, @@ -686,75 +690,85 @@ void GOAudioSection::Setup( end_seg.next_start_segment_index = i + 1; const unsigned loop_length = 1 + end_seg.end_offset - start_seg.start_offset; - unsigned end_length; + wxString loopError; if (fade_len > loop_length - 1) - throw(wxString) _("Loop too short for crossfade"); - - if (start_seg.start_offset < fade_len) - throw(wxString) _("Not enough samples for a crossfade"); - - // calculate the fade segment size and offsets - if (end_seg.end_offset - start_seg.start_offset > SHORT_LOOP_LENGTH) { - end_seg.transition_offset - = end_seg.end_offset - MAX_READAHEAD - fade_len + 1; - end_seg.read_end = end_seg.end_offset - fade_len; - end_length = 2 * MAX_READAHEAD + fade_len; - } else { - end_seg.transition_offset = start_seg.start_offset; - end_seg.read_end = end_seg.end_offset; - end_length = SHORT_LOOP_LENGTH + MAX_READAHEAD; - if ( - end_length < MAX_READAHEAD - + (SHORT_LOOP_LENGTH / loop_length) * SHORT_LOOP_LENGTH + fade_len) - end_length = MAX_READAHEAD - + (SHORT_LOOP_LENGTH / loop_length) * SHORT_LOOP_LENGTH + fade_len; - } - end_seg.end_size = end_length * m_BytesPerSample; - - // Allocate the fade segment - end_seg.end_data = (unsigned char *)m_Pool.Alloc(end_seg.end_size, true); - if (!end_seg.end_data) - throw GOOutOfMemory(); - end_seg.end_ptr - = end_seg.end_data - m_BytesPerSample * end_seg.transition_offset; - - const unsigned copy_len - = 1 + end_seg.end_offset - end_seg.transition_offset; - - // Fill the fade seg with transition data, then with the loop start data - memcpy( - end_seg.end_data, - ((const unsigned char *)pcm_data) - + end_seg.transition_offset * m_BytesPerSample, - copy_len * m_BytesPerSample); - loop_memcpy( - ((unsigned char *)end_seg.end_data) + copy_len * m_BytesPerSample, - ((const unsigned char *)pcm_data) - + loop.m_StartPosition * m_BytesPerSample, - loop_length * m_BytesPerSample, - (end_length - copy_len) * m_BytesPerSample); - if (fade_len > 0) - // TODO: Remove the parameter names from the comment and reduce the - // number of parameters of DoCrossfade that the call would be easy - // readable without additional comments - DoCrossfade( - end_seg.end_data, // dest - MAX_READAHEAD, // dest_offset - (const unsigned char *)pcm_data, // src - start_seg.start_offset - fade_len, // src_offset - pcm_data_channels, // channels - m_BitsPerSample, // bits_per_sample - fade_len, // fade_length - loop_length, // loop_length - end_length); // length - - end_seg.end_loop_length = loop_length; - end_seg.end_pos = end_length + end_seg.transition_offset; - assert(end_length >= MAX_READAHEAD); - - m_StartSegments.push_back(start_seg); - m_EndSegments.push_back(end_seg); + loopError + = wxString::Format(_("Loop %u is too short for crossfade"), i + 1); + else if (start_seg.start_offset < fade_len) + loopError = wxString::Format( + _("Not enough samples for crossfade before loop %u "), i + 1); + + if (loopError.IsEmpty()) { + unsigned end_length; + + // calculate the fade segment size and offsets + if (end_seg.end_offset - start_seg.start_offset > SHORT_LOOP_LENGTH) { + end_seg.transition_offset + = end_seg.end_offset - MAX_READAHEAD - fade_len + 1; + end_seg.read_end = end_seg.end_offset - fade_len; + end_length = 2 * MAX_READAHEAD + fade_len; + } else { + end_seg.transition_offset = start_seg.start_offset; + end_seg.read_end = end_seg.end_offset; + end_length = SHORT_LOOP_LENGTH + MAX_READAHEAD; + if ( + end_length < MAX_READAHEAD + + (SHORT_LOOP_LENGTH / loop_length) * SHORT_LOOP_LENGTH + + fade_len) + end_length = MAX_READAHEAD + + (SHORT_LOOP_LENGTH / loop_length) * SHORT_LOOP_LENGTH + + fade_len; + } + end_seg.end_size = end_length * m_BytesPerSample; + + // Allocate the fade segment + end_seg.end_data + = (unsigned char *)m_Pool.Alloc(end_seg.end_size, true); + if (!end_seg.end_data) + throw GOOutOfMemory(); + end_seg.end_ptr + = end_seg.end_data - m_BytesPerSample * end_seg.transition_offset; + + const unsigned copy_len + = 1 + end_seg.end_offset - end_seg.transition_offset; + + // Fill the fade seg with transition data, then with the loop start data + memcpy( + end_seg.end_data, + ((const unsigned char *)pcm_data) + + end_seg.transition_offset * m_BytesPerSample, + copy_len * m_BytesPerSample); + loop_memcpy( + ((unsigned char *)end_seg.end_data) + copy_len * m_BytesPerSample, + ((const unsigned char *)pcm_data) + + loop.m_StartPosition * m_BytesPerSample, + loop_length * m_BytesPerSample, + (end_length - copy_len) * m_BytesPerSample); + if (fade_len > 0) + // TODO: Remove the parameter names from the comment and reduce the + // number of parameters of DoCrossfade that the call would be easy + // readable without additional comments + DoCrossfade( + end_seg.end_data, // dest + MAX_READAHEAD, // dest_offset + (const unsigned char *)pcm_data, // src + start_seg.start_offset - fade_len, // src_offset + pcm_data_channels, // channels + m_BitsPerSample, // bits_per_sample + fade_len, // fade_length + loop_length, // loop_length + end_length); // length + + end_seg.end_loop_length = loop_length; + end_seg.end_pos = end_length + end_seg.transition_offset; + assert(end_length >= MAX_READAHEAD); + + m_StartSegments.push_back(start_seg); + m_EndSegments.push_back(end_seg); + } else + wxLogWarning(GOCacheObject::generateMessage( + pObjectFor, pLoaderFilename, loopError)); } /* There is no need to store any samples after the end of the last loop. */ diff --git a/src/grandorgue/sound/GOSoundAudioSection.h b/src/grandorgue/sound/GOSoundAudioSection.h index c9069b531..e9b7c8902 100644 --- a/src/grandorgue/sound/GOSoundAudioSection.h +++ b/src/grandorgue/sound/GOSoundAudioSection.h @@ -19,6 +19,7 @@ class GOAudioSection; class GOCache; +class GOCacheObject; class GOCacheWriter; class GOLoaderFilename; class GOMemoryPool; @@ -198,6 +199,7 @@ class GOAudioSection { int history[BLOCK_HISTORY][MAX_OUTPUT_CHANNELS]); void Setup( + const GOCacheObject *pObjectFor, const GOLoaderFilename *pLoaderFilename, const void *pcm_data, GOWave::SAMPLE_FORMAT pcm_data_format, diff --git a/src/grandorgue/sound/GOSoundProviderSynthedTrem.cpp b/src/grandorgue/sound/GOSoundProviderSynthedTrem.cpp index 138e95fed..9406f47c3 100644 --- a/src/grandorgue/sound/GOSoundProviderSynthedTrem.cpp +++ b/src/grandorgue/sound/GOSoundProviderSynthedTrem.cpp @@ -89,6 +89,7 @@ void GOSoundProviderSynthedTrem::Create( m_AttackInfo.push_back(attack_info); m_Attack.push_back(new GOAudioSection(pool)); m_Attack[0]->Setup( + nullptr, nullptr, data.get(), GOWave::SF_SIGNEDSHORT_16, @@ -106,6 +107,7 @@ void GOSoundProviderSynthedTrem::Create( m_ReleaseInfo.push_back(release_info); m_Release.push_back(new GOAudioSection(pool)); m_Release[0]->Setup( + nullptr, nullptr, data.get() + attack_samples + loop_samples, GOWave::SF_SIGNEDSHORT_16, diff --git a/src/grandorgue/sound/GOSoundProviderWave.cpp b/src/grandorgue/sound/GOSoundProviderWave.cpp index d4987ac89..49a163d45 100644 --- a/src/grandorgue/sound/GOSoundProviderWave.cpp +++ b/src/grandorgue/sound/GOSoundProviderWave.cpp @@ -121,6 +121,7 @@ void GOSoundProviderWave::CreateAttack( GOAudioSection *section = new GOAudioSection(pool); m_Attack.push_back(section); section->Setup( + p_ObjectFor, &loaderFilename, data + attack_pos * GetBytesPerSample(bits_per_sample) * channels, (GOWave::SAMPLE_FORMAT)bits_per_sample, @@ -166,6 +167,7 @@ void GOSoundProviderWave::CreateRelease( GOAudioSection *section = new GOAudioSection(pool); m_Release.push_back(section); section->Setup( + p_ObjectFor, &loaderFilename, data + release_offset * GetBytesPerSample(bits_per_sample) * channels, (GOWave::SAMPLE_FORMAT)bits_per_sample, @@ -287,7 +289,7 @@ void GOSoundProviderWave::LoadFromOneFile( excText = _("Unknown exception"); } if (!excText.IsEmpty()) - throw wxString::Format("%s: %s", loaderFilename.GetPath(), excText); + throw loaderFilename.GenerateMessage(excText); } unsigned GOSoundProviderWave::GetFaderLength(unsigned MidiKeyNumber) {