-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathHaas.cpp
More file actions
91 lines (74 loc) · 3.12 KB
/
Haas.cpp
File metadata and controls
91 lines (74 loc) · 3.12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
// Copyright (c) 2024 JDSherbert. All rights reserved.
#include "Haas.h"
#include <cassert>
#include <cmath>
// ======================================================================= //
Sherbert::Haas::Haas(int delayInMilliseconds, DelayedChannel delayedChannel, float sampleRate)
: delayInMilliseconds(delayInMilliseconds)
, delayInSamples(static_cast<size_t>(delayInMilliseconds * sampleRate / 1000.0f))
, delayedChannel(delayedChannel)
, sampleRate(sampleRate)
, writeIndex(0)
{
assert(delayInMilliseconds > 0);
assert(sampleRate > 0.0f);
delayBuffer.resize(delayInSamples, 0.0f);
}
// ======================================================================= //
void Sherbert::Haas::ProcessSample(float leftIn, float rightIn,
float& leftOut, float& rightOut)
{
// === HOW THE HAAS EFFECT WORKS =====================================
//
// The leading channel passes through to output immediately — no delay.
// The delayed channel reads from the circular buffer (which holds samples
// from 'delayInSamples' frames ago) and then writes the current input
// into the buffer for future playback.
//
// The result is that both channels carry the same signal, but one lags
// behind by the configured delay time. The brain interprets this timing
// difference as spatial information — the sound appears to originate
// from the side of the leading (undelayed) channel.
//
// The circular buffer works identically to the reverb implementation:
// a fixed-size array with a write index that wraps at the buffer end,
// so no memory is allocated or freed during processing.
// ====================================================================
const float delayedSample = delayBuffer[writeIndex];
if (delayedChannel == DelayedChannel::Right)
{
// Left channel leads — right channel is delayed
leftOut = leftIn;
delayBuffer[writeIndex] = rightIn;
rightOut = delayedSample;
}
else
{
// Right channel leads — left channel is delayed
rightOut = rightIn;
delayBuffer[writeIndex] = leftIn;
leftOut = delayedSample;
}
writeIndex = (writeIndex + 1) % delayInSamples;
}
// ======================================================================= //
void Sherbert::Haas::reset()
{
std::fill(delayBuffer.begin(), delayBuffer.end(), 0.0f);
writeIndex = 0;
}
// ======================================================================= //
void Sherbert::Haas::setDelayInMilliseconds(int newDelayInMilliseconds)
{
assert(newDelayInMilliseconds > 0);
delayInMilliseconds = newDelayInMilliseconds;
delayInSamples = static_cast<size_t>(delayInMilliseconds * sampleRate / 1000.0f);
delayBuffer.resize(delayInSamples, 0.0f);
reset();
}
// ======================================================================= //
void Sherbert::Haas::setDelayedChannel(DelayedChannel newDelayedChannel) noexcept
{
delayedChannel = newDelayedChannel;
}
// ======================================================================= //