Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reworked the resampler code https://github.com/GrandOrgue/grandorgue/issues/710 #1919

Merged
merged 21 commits into from
Jun 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 31 additions & 49 deletions src/grandorgue/sound/GOSoundAudioSection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
#define M_PI 3.14159265358979323846
#endif

static constexpr unsigned DEFAULT_END_SEG_LENGTH = MAX_READAHEAD * 2;

GOSoundAudioSection::GOSoundAudioSection(GOMemoryPool &pool)
: m_data(NULL),
m_ReleaseAligner(NULL),
Expand Down Expand Up @@ -105,19 +107,12 @@ bool GOSoundAudioSection::LoadCache(GOCache &cache) {
return false;
for (unsigned i = 0; i < temp; i++) {
EndSegment s;
if (!cache.Read(&s.end_offset, sizeof(s.end_offset)))
return false;

if (!cache.Read(
&s.next_start_segment_index, sizeof(s.next_start_segment_index)))
return false;
if (!cache.Read(&s.transition_offset, sizeof(s.transition_offset)))
return false;
if (!cache.Read(&s.end_loop_length, sizeof(s.end_loop_length)))
return false;
if (!cache.Read(&s.read_end, sizeof(s.read_end)))
return false;
if (!cache.Read(&s.end_pos, sizeof(s.end_pos)))
return false;
if (!cache.Read(&s.end_size, sizeof(s.end_size)))
return false;
s.end_data = (unsigned char *)cache.ReadBlock(s.end_size);
Expand Down Expand Up @@ -185,19 +180,12 @@ bool GOSoundAudioSection::SaveCache(GOCacheWriter &cache) const {
return false;
for (unsigned i = 0; i < temp; i++) {
const EndSegment *s = &m_EndSegments[i];
if (!cache.Write(&s->end_offset, sizeof(s->end_offset)))
return false;

if (!cache.Write(
&s->next_start_segment_index, sizeof(s->next_start_segment_index)))
return false;
if (!cache.Write(&s->transition_offset, sizeof(s->transition_offset)))
return false;
if (!cache.Write(&s->end_loop_length, sizeof(s->end_loop_length)))
return false;
if (!cache.Write(&s->read_end, sizeof(s->read_end)))
return false;
if (!cache.Write(&s->end_pos, sizeof(s->end_pos)))
return false;
if (!cache.Write(&s->end_size, sizeof(s->end_size)))
return false;
if (!cache.WriteBlock(s->end_data, s->end_size))
Expand Down Expand Up @@ -373,10 +361,9 @@ void GOSoundAudioSection::Setup(
min_reqd_samples = loop.m_EndPosition + 1;

start_seg.start_offset = loop.m_StartPosition;
end_seg.end_offset = loop.m_EndPosition;
end_seg.end_pos = loop.m_EndPosition;
end_seg.next_start_segment_index = i + 1;
const unsigned loop_length
= 1 + end_seg.end_offset - start_seg.start_offset;
const unsigned loop_length = end_seg.end_pos - start_seg.start_offset;
wxString loopError;

if (fade_len > loop_length - 1)
Expand All @@ -392,14 +379,12 @@ void GOSoundAudioSection::Setup(
unsigned end_length;

// calculate the fade segment size and offsets
if (end_seg.end_offset - start_seg.start_offset > SHORT_LOOP_LENGTH) {
if (loop_length >= 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;
= end_seg.end_pos - MAX_READAHEAD - fade_len;
end_length = DEFAULT_END_SEG_LENGTH + 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
Expand All @@ -416,24 +401,27 @@ void GOSoundAudioSection::Setup(
= (unsigned char *)m_Pool.Alloc(end_seg.end_size, true);
if (!end_seg.end_data)
throw GOOutOfMemory();

// make a virtual pointer for reading the end segment with the same
// offset as the main data
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;
const unsigned nBytesToCopy
= (end_seg.end_pos - end_seg.transition_offset) * m_BytesPerSample;

// 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);
nBytesToCopy);
loop_memcpy(
((unsigned char *)end_seg.end_data) + copy_len * m_BytesPerSample,
((unsigned char *)end_seg.end_data) + nBytesToCopy,
((const unsigned char *)pcm_data)
+ loop.m_StartPosition * m_BytesPerSample,
loop_length * m_BytesPerSample,
(end_length - copy_len) * m_BytesPerSample);
end_seg.end_size - nBytesToCopy);
if (fade_len > 0)
// TODO: reduce the number of parameters of DoCrossfade that the call
// would be easy readable without additional comments
Expand All @@ -446,8 +434,6 @@ void GOSoundAudioSection::Setup(
loop_length,
end_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);
Expand All @@ -465,35 +451,31 @@ void GOSoundAudioSection::Setup(
} else {
/* Create a default end segment */
EndSegment end_seg;
end_seg.end_offset = pcm_data_nb_samples - 1;
end_seg.read_end = end_seg.end_offset + 1;

end_seg.end_pos = pcm_data_nb_samples;
end_seg.next_start_segment_index = -1;
unsigned end_length = 2 * MAX_READAHEAD;
end_seg.end_size = end_length * m_BytesPerSample;
end_seg.end_size = m_BytesPerSample * DEFAULT_END_SEG_LENGTH;
end_seg.end_data = (unsigned char *)m_Pool.Alloc(end_seg.end_size, true);
end_seg.transition_offset = limitedDiff(end_seg.end_offset, MAX_READAHEAD);
end_seg.end_loop_length = end_length * 2;
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;

if (!end_seg.end_data)
throw GOOutOfMemory();

end_seg.transition_offset = limitedDiff(end_seg.end_pos, MAX_READAHEAD);
end_seg.end_ptr
= end_seg.end_data - m_BytesPerSample * end_seg.transition_offset;

const unsigned nBytesToCopy
= (end_seg.end_pos - end_seg.transition_offset) * m_BytesPerSample;

memcpy(
end_seg.end_data,
((const unsigned char *)pcm_data)
+ end_seg.transition_offset * m_BytesPerSample,
copy_len * m_BytesPerSample);
+ m_BytesPerSample * end_seg.transition_offset,
nBytesToCopy);
memset(
((unsigned char *)end_seg.end_data) + copy_len * m_BytesPerSample,
((unsigned char *)end_seg.end_data) + nBytesToCopy,
0,
(end_length - copy_len) * m_BytesPerSample);

end_seg.end_pos = end_length + end_seg.transition_offset;
assert(end_length >= MAX_READAHEAD);
end_seg.end_size - nBytesToCopy);

m_EndSegments.push_back(end_seg);
}
Expand Down
22 changes: 15 additions & 7 deletions src/grandorgue/sound/GOSoundAudioSection.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,23 +36,31 @@ class GOSoundAudioSection {
DecompressionCache cache;
};

/**
* This segment contains copy of samples from a loop end and then from the
* loop start. It is necessary for read-ahead when resampling
*/
struct EndSegment {
/* Sample offset where the loop ends and needs to jump into the next
* start segment. */
unsigned end_offset;
/* A position after the last copied sample in the end segment.
* An end segment has more MAX_READAHEAD samples after end_pos:
* - A loop end segment contains a copy of samples from the loop start
* - A release end segment contains zero samples
*/
unsigned end_pos;

/* Sample offset where the uncompressed end data blob begins (must be less
* than end_offset). */
* than end_pos). */
unsigned transition_offset;

/* Uncompressed ending data blob. This data must start before
* sample_offset*/
unsigned char *end_data;

// A virtual pointer to end_data. end_data has transition_offset to end_ptr
unsigned char *end_ptr;
unsigned read_end;
unsigned end_pos;

// The size of the end data in bytes
unsigned end_size;
unsigned end_loop_length;

/* Index of the next section segment to be played (-1 indicates the
* end of the blob. */
Expand Down
6 changes: 1 addition & 5 deletions src/grandorgue/sound/GOSoundDefs.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* Copyright 2006 Milan Digital Audio LLC
* Copyright 2009-2023 GrandOrgue contributors (see AUTHORS)
* Copyright 2009-2024 GrandOrgue contributors (see AUTHORS)
* License GPL-2.0 or later
* (https://www.gnu.org/licenses/old-licenses/gpl-2.0.html).
*/
Expand All @@ -11,10 +11,6 @@
/* Number of samples to match for release alignment. */
#define BLOCK_HISTORY (2)

/* Read-Ahead of various playback modes */
#define POLYPHASE_READAHEAD (8)
#define LINEAR_COMPRESSED_READAHEAD (2)
#define LINEAR_READAHEAD (1)
/* Maximum of the above values */
#define MAX_READAHEAD (8)
/* Minimum remaining loop length after a crossfade */
Expand Down
17 changes: 9 additions & 8 deletions src/grandorgue/sound/GOSoundEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ GOSoundEngine::GOSoundEngine()
m_AudioRecorder(NULL),
m_TouchTask(),
m_HasBeenSetup(false) {
memset(&m_ResamplerCoefs, 0, sizeof(m_ResamplerCoefs));
m_SamplerPool.SetUsageLimit(2048);
m_PolyphonySoftLimit = (m_SamplerPool.GetUsageLimit() * 3) / 4;
m_ReleaseProcessor = new GOSoundReleaseTask(*this, m_AudioGroupTasks);
Expand Down Expand Up @@ -101,12 +100,10 @@ void GOSoundEngine::SetSamplesPerBuffer(unsigned samples_per_buffer) {

void GOSoundEngine::SetSampleRate(unsigned sample_rate) {
m_SampleRate = sample_rate;
resampler_coefs_init(
&m_ResamplerCoefs, m_SampleRate, m_ResamplerCoefs.interpolation);
}

void GOSoundEngine::SetInterpolationType(unsigned type) {
m_ResamplerCoefs.interpolation = (interpolation_type)type;
m_interpolation = (GOSoundResample::InterpolationType)type;
}

unsigned GOSoundEngine::GetSampleRate() { return m_SampleRate; }
Expand Down Expand Up @@ -415,8 +412,9 @@ GOSoundSampler *GOSoundEngine::CreateTaskSample(
sampler->m_WaveTremulantStateFor = section->GetWaveTremulantStateFor();
sampler->velocity = velocity;
sampler->stream.InitStream(
&m_resample,
section,
&m_ResamplerCoefs,
m_interpolation,
GetRandomFactor() * pSoundProvider->GetTuning() / (float)m_SampleRate);

const float playback_gain
Expand Down Expand Up @@ -462,7 +460,8 @@ void GOSoundEngine::SwitchToAnotherAttack(GOSoundSampler *pSampler) {

// start new section stream in the old sampler
pSampler->m_WaveTremulantStateFor = section->GetWaveTremulantStateFor();
pSampler->stream.InitAlignedStream(section, &new_sampler->stream);
pSampler->stream.InitAlignedStream(
section, m_interpolation, &new_sampler->stream);
pSampler->p_SoundProvider = pProvider;
pSampler->time = m_CurrentTime + 1;

Expand Down Expand Up @@ -601,11 +600,13 @@ void GOSoundEngine::CreateReleaseSampler(GOSoundSampler *handle) {
if (
m_ReleaseAlignmentEnabled
&& release_section->SupportsStreamAlignment()) {
new_sampler->stream.InitAlignedStream(release_section, &handle->stream);
new_sampler->stream.InitAlignedStream(
release_section, m_interpolation, &handle->stream);
} else {
new_sampler->stream.InitStream(
&m_resample,
release_section,
&m_ResamplerCoefs,
m_interpolation,
this_pipe->GetTuning() / (float)m_SampleRate);
}
new_sampler->is_release = true;
Expand Down
3 changes: 2 additions & 1 deletion src/grandorgue/sound/GOSoundEngine.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ class GOSoundEngine {
std::unique_ptr<GOSoundTouchTask> m_TouchTask;
GOSoundScheduler m_Scheduler;

struct resampler_coefs_s m_ResamplerCoefs;
GOSoundResample m_resample;
GOSoundResample::InterpolationType m_interpolation;

std::atomic_bool m_HasBeenSetup;

Expand Down
Loading
Loading