Skip to content

Commit d976fdc

Browse files
committed
Implement Gain parameter for Sampler and Synth
* Add Gain parameter (-30 dB to +30 dB) to SamplerDevice and SynthDevice * Add Gain knobs to Synth and Sampler dialogs * Update Knob component to support dB formatting * Add unit tests for gain functionality and XML serialization
1 parent 73ca152 commit d976fdc

18 files changed

Lines changed: 260 additions & 20 deletions

CHANGELOG

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ Release date:
55

66
New features:
77

8+
* Implement a Gain parameter for Noteahead Sampler and Synth
9+
- Range: -30 dB to +30 dB
10+
- Allows proper gain staging when recording alongside hardware instruments
11+
812
* Fix GitHub Issue #45: Implement a simple built-in sampler
913

1014
* Implements a virtual device rack

src/application/service/sampler_controller.cpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,38 @@ void SamplerController::setSelectedPadHpfCutoff(double cutoff)
154154
}
155155
}
156156

157+
double SamplerController::globalVolume() const
158+
{
159+
if (!m_sampler) {
160+
return 1.0;
161+
}
162+
return static_cast<double>(m_sampler->globalVolume());
163+
}
164+
165+
void SamplerController::setGlobalVolume(double volume)
166+
{
167+
if (m_sampler) {
168+
m_sampler->setGlobalVolume(static_cast<float>(volume));
169+
emit globalVolumeChanged();
170+
}
171+
}
172+
173+
double SamplerController::gain() const
174+
{
175+
if (!m_sampler) {
176+
return 0.5;
177+
}
178+
return static_cast<double>(m_sampler->gain());
179+
}
180+
181+
void SamplerController::setGain(double gain)
182+
{
183+
if (m_sampler) {
184+
m_sampler->setGain(static_cast<float>(gain));
185+
emit gainChanged();
186+
}
187+
}
188+
157189
int SamplerController::selectedPadStartOffsetSeconds() const
158190
{
159191
if (!m_sampler || m_selectedPad < 0) {
@@ -243,6 +275,8 @@ void SamplerController::initialize()
243275
emit selectedPadStartOffsetChanged();
244276
emit selectedPadDurationChanged();
245277
}
278+
emit globalVolumeChanged();
279+
emit gainChanged();
246280
emit channelModeChanged();
247281
}
248282

@@ -255,6 +289,8 @@ void SamplerController::reset()
255289
emit selectedPadCutoffChanged();
256290
emit selectedPadHpfCutoffChanged();
257291
emit selectedPadStartOffsetChanged();
292+
emit globalVolumeChanged();
293+
emit gainChanged();
258294
emit channelModeChanged();
259295
}
260296
}

src/application/service/sampler_controller.hpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ class SamplerController : public QObject
3737
Q_PROPERTY(double selectedPadVolume READ selectedPadVolume WRITE setSelectedPadVolume NOTIFY selectedPadVolumeChanged)
3838
Q_PROPERTY(double selectedPadCutoff READ selectedPadCutoff WRITE setSelectedPadCutoff NOTIFY selectedPadCutoffChanged)
3939
Q_PROPERTY(double selectedPadHpfCutoff READ selectedPadHpfCutoff WRITE setSelectedPadHpfCutoff NOTIFY selectedPadHpfCutoffChanged)
40+
Q_PROPERTY(double globalVolume READ globalVolume WRITE setGlobalVolume NOTIFY globalVolumeChanged)
41+
Q_PROPERTY(double gain READ gain WRITE setGain NOTIFY gainChanged)
4042
Q_PROPERTY(int selectedPadStartOffsetSeconds READ selectedPadStartOffsetSeconds WRITE setSelectedPadStartOffsetSeconds NOTIFY selectedPadStartOffsetChanged)
4143
Q_PROPERTY(int selectedPadStartOffsetMilliseconds READ selectedPadStartOffsetMilliseconds WRITE setSelectedPadStartOffsetMilliseconds NOTIFY selectedPadStartOffsetChanged)
4244
Q_PROPERTY(double selectedPadDuration READ selectedPadDuration NOTIFY selectedPadDurationChanged)
@@ -72,6 +74,12 @@ class SamplerController : public QObject
7274
double selectedPadHpfCutoff() const;
7375
void setSelectedPadHpfCutoff(double cutoff);
7476

77+
double globalVolume() const;
78+
void setGlobalVolume(double volume);
79+
80+
double gain() const;
81+
void setGain(double gain);
82+
7583
int selectedPadStartOffsetSeconds() const;
7684
void setSelectedPadStartOffsetSeconds(int seconds);
7785

@@ -104,6 +112,8 @@ class SamplerController : public QObject
104112
void selectedPadVolumeChanged();
105113
void selectedPadCutoffChanged();
106114
void selectedPadHpfCutoffChanged();
115+
void globalVolumeChanged();
116+
void gainChanged();
107117
void selectedPadStartOffsetChanged();
108118
void selectedPadDurationChanged();
109119
void channelModeChanged();

src/application/service/synth_controller.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,8 @@ int SynthController::masterPan() const { return m_synth ? static_cast<int>(std::
137137
void SynthController::setMasterPan(int p) { if (m_synth) { m_synth->setMasterPan(p / 1000.0f); emit masterPanChanged(); } }
138138
int SynthController::masterVolume() const { return m_synth ? static_cast<int>(std::round(m_synth->masterVolume() * 1000.0f)) : 0; }
139139
void SynthController::setMasterVolume(int v) { if (m_synth) { m_synth->setMasterVolume(v / 1000.0f); emit masterVolumeChanged(); } }
140+
int SynthController::gain() const { return m_synth ? static_cast<int>(std::round(m_synth->gain() * 1000.0f)) : 0; }
141+
void SynthController::setGain(int g) { if (m_synth) { m_synth->setGain(g / 1000.0f); emit gainChanged(); } }
140142

141143
uint32_t SynthController::sampleRate() const { return m_synth ? m_synth->sampleRate() : static_cast<uint32_t>(Constants::defaultSampleRate()); }
142144

@@ -232,7 +234,7 @@ void SynthController::requestSettings() {
232234
emit ampAttackChanged(); emit ampDecayChanged(); emit ampSustainChanged(); emit ampReleaseChanged();
233235
emit modAttackChanged(); emit modDecayChanged(); emit modIntChanged(); emit modTargetChanged();
234236
emit lfoWaveformChanged(); emit lfoModeChanged(); emit lfoRateChanged(); emit lfoIntChanged(); emit lfoTargetChanged();
235-
emit voiceModeChanged(); emit voiceDepthChanged(); emit portamentoChanged(); emit panSpreadChanged(); emit masterPanChanged(); emit masterVolumeChanged();
237+
emit voiceModeChanged(); emit voiceDepthChanged(); emit portamentoChanged(); emit panSpreadChanged(); emit masterPanChanged(); emit masterVolumeChanged(); emit gainChanged();
236238
emit delayTypeChanged(); emit delayTimeChanged(); emit delayFeedbackChanged(); emit delayDepthChanged(); emit delayMixChanged(); emit delaySyncChanged(); emit delaySyncDivisionChanged(); emit delayFeedbackLpfChanged(); emit delayFeedbackHpfChanged();
237239
}
238240
void SynthController::accept() {}

src/application/service/synth_controller.hpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ class SynthController : public QObject
8686
Q_PROPERTY(int panSpread READ panSpread WRITE setPanSpread NOTIFY panSpreadChanged)
8787
Q_PROPERTY(int masterPan READ masterPan WRITE setMasterPan NOTIFY masterPanChanged)
8888
Q_PROPERTY(int masterVolume READ masterVolume WRITE setMasterVolume NOTIFY masterVolumeChanged)
89+
Q_PROPERTY(int gain READ gain WRITE setGain NOTIFY gainChanged)
8990
Q_PROPERTY(uint32_t sampleRate READ sampleRate NOTIFY sampleRateChanged)
9091
Q_PROPERTY(QStringList presetNames READ presetNames CONSTANT)
9192
Q_PROPERTY(int currentBank READ currentBank WRITE setCurrentBank NOTIFY currentBankChanged)
@@ -157,6 +158,7 @@ class SynthController : public QObject
157158
int panSpread() const; void setPanSpread(int s);
158159
int masterPan() const; void setMasterPan(int p);
159160
int masterVolume() const; void setMasterVolume(int v);
161+
int gain() const; void setGain(int g);
160162

161163
uint32_t sampleRate() const;
162164
Q_INVOKABLE float cutoffToHz(float cutoff) const;
@@ -201,7 +203,7 @@ class SynthController : public QObject
201203
void ampAttackChanged(); void ampDecayChanged(); void ampSustainChanged(); void ampReleaseChanged();
202204
void modAttackChanged(); void modDecayChanged(); void modIntChanged(); void modTargetChanged();
203205
void lfoWaveformChanged(); void lfoModeChanged(); void lfoRateChanged(); void lfoIntChanged(); void lfoTargetChanged();
204-
void voiceModeChanged(); void voiceDepthChanged(); void portamentoChanged(); void panSpreadChanged(); void masterPanChanged(); void masterVolumeChanged();
206+
void voiceModeChanged(); void voiceDepthChanged(); void portamentoChanged(); void panSpreadChanged(); void masterPanChanged(); void masterVolumeChanged(); void gainChanged();
205207
void sampleRateChanged();
206208
void delayTypeChanged(); void delayTimeChanged(); void delayFeedbackChanged(); void delayDepthChanged(); void delayMixChanged(); void delaySyncChanged(); void delaySyncDivisionChanged(); void delayFeedbackLpfChanged(); void delayFeedbackHpfChanged();
207209

src/common/constants.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -649,6 +649,11 @@ QString xmlKeyVolume()
649649
return "volume";
650650
}
651651

652+
QString xmlKeyGain()
653+
{
654+
return "gain";
655+
}
656+
652657
QString xmlKeyProject()
653658
{
654659
return "Project";

src/common/constants.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ QString xmlKeyValue();
195195

196196
QString xmlKeyVelocity();
197197
QString xmlKeyVolume();
198+
QString xmlKeyGain();
198199

199200
QString xmlKeyProject();
200201
QString xmlKeySong();

src/domain/devices/sampler_device.cpp

Lines changed: 73 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,17 @@ SamplerDevice::SamplerDevice(std::string name, AudioFileReaderU audioFileReader)
4444
, m_audioFileReader { audioFileReader ? std::move(audioFileReader) : std::make_unique<SndFileReader>() }
4545
{
4646
addParameter(Parameter { Constants::NahdXml::xmlKeyChannelMode().toStdString(), 0.0f, 0, 1, 0, 1 });
47+
addParameter(Parameter { Constants::NahdXml::xmlKeyVolume().toStdString(), 1.0f, 0, 100, 100, 1 });
48+
addParameter(Parameter { Constants::NahdXml::xmlKeyGain().toStdString(), 0.5f, -30, 30, 0, 1, false });
4749

4850
m_voices.resize(m_maxVoices);
4951
for (auto && sample : m_samples) {
5052
sample = nullptr;
5153
}
54+
55+
m_manualGain = m_gain;
56+
m_manualGlobalVolume = m_globalVolume;
57+
syncParameters();
5258
}
5359

5460
SamplerDevice::~SamplerDevice() = default;
@@ -153,6 +159,7 @@ void SamplerDevice::processMidiCc(uint8_t controller, uint8_t value, uint8_t cha
153159
if (controller == 121) { // Reset All Controllers
154160
m_globalPan = m_manualGlobalPan;
155161
m_globalVolume = m_manualGlobalVolume;
162+
m_gain = m_manualGain;
156163
m_globalCutoff = m_manualGlobalCutoff;
157164
m_globalHpfCutoff = m_manualGlobalHpfCutoff;
158165

@@ -170,6 +177,9 @@ void SamplerDevice::processMidiCc(uint8_t controller, uint8_t value, uint8_t cha
170177
}
171178
}
172179

180+
if (auto p = parameter(Constants::NahdXml::xmlKeyVolume().toStdString()); p) p->get().setValue(m_globalVolume);
181+
if (auto p = parameter(Constants::NahdXml::xmlKeyGain().toStdString()); p) p->get().setValue(m_gain);
182+
173183
for (auto && voice : m_voices) {
174184
if (voice.active && voice.sample) {
175185
voice.pan = m_globalPan;
@@ -226,6 +236,7 @@ void SamplerDevice::processMidiCc(uint8_t controller, uint8_t value, uint8_t cha
226236
}
227237
} else if (controller == 7) { // Volume
228238
m_globalVolume = static_cast<float>(value) / 127.0f;
239+
if (auto p = parameter(Constants::NahdXml::xmlKeyVolume().toStdString()); p) p->get().setValue(m_globalVolume);
229240
// Update all active voices' volume
230241
for (auto && voice : m_voices) {
231242
if (voice.active) {
@@ -360,8 +371,8 @@ void SamplerDevice::processAudio(float * output, uint32_t nFrames, uint32_t samp
360371
}
361372
}
362373

363-
output[i * 2] += left;
364-
output[i * 2 + 1] += right;
374+
output[i * 2] += left * m_linearGain;
375+
output[i * 2 + 1] += right * m_linearGain;
365376

366377
voice.position += pitchScale;
367378
}
@@ -380,9 +391,7 @@ void SamplerDevice::reset()
380391
voice.active = false;
381392
}
382393

383-
if (auto p = parameter(Constants::NahdXml::xmlKeyChannelMode().toStdString()); p) {
384-
m_channelMode = p->get().value() > 0.5f;
385-
}
394+
syncParameters();
386395
}
387396

388397
emit dataChanged();
@@ -770,9 +779,7 @@ void SamplerDevice::deserializeFromXml(QXmlStreamReader & reader)
770779
{
771780
std::lock_guard<std::mutex> lock { m_mutex };
772781
// Sync global fields
773-
if (auto p = parameter(Constants::NahdXml::xmlKeyChannelMode().toStdString()); p) {
774-
m_channelMode = p->get().value() > 0.5f;
775-
}
782+
syncParameters();
776783
}
777784

778785
emit dataChanged();
@@ -808,4 +815,62 @@ void SamplerDevice::setProjectPath(const std::string & projectPath)
808815
m_projectPath = projectPath;
809816
}
810817

818+
float SamplerDevice::globalVolume() const
819+
{
820+
return m_globalVolume;
821+
}
822+
823+
void SamplerDevice::setGlobalVolume(float volume)
824+
{
825+
bool changed = false;
826+
{
827+
std::lock_guard<std::mutex> lock { m_mutex };
828+
if (auto p = parameter(Constants::NahdXml::xmlKeyVolume().toStdString()); p) {
829+
p->get().setValue(volume);
830+
m_manualGlobalVolume = p->get().value();
831+
syncParameters();
832+
changed = true;
833+
}
834+
}
835+
if (changed) {
836+
emit dataChanged();
837+
}
838+
}
839+
840+
float SamplerDevice::gain() const
841+
{
842+
return m_gain;
843+
}
844+
845+
void SamplerDevice::setGain(float gain)
846+
{
847+
bool changed = false;
848+
{
849+
std::lock_guard<std::mutex> lock { m_mutex };
850+
if (auto p = parameter(Constants::NahdXml::xmlKeyGain().toStdString()); p) {
851+
p->get().setValue(gain);
852+
m_manualGain = p->get().value();
853+
syncParameters();
854+
changed = true;
855+
}
856+
}
857+
if (changed) {
858+
emit dataChanged();
859+
}
860+
}
861+
862+
void SamplerDevice::syncParameters()
863+
{
864+
if (auto p = parameter(Constants::NahdXml::xmlKeyChannelMode().toStdString()); p) {
865+
m_channelMode = p->get().value() > 0.5f;
866+
}
867+
if (auto p = parameter(Constants::NahdXml::xmlKeyVolume().toStdString()); p) {
868+
m_globalVolume = p->get().value();
869+
}
870+
if (auto p = parameter(Constants::NahdXml::xmlKeyGain().toStdString()); p) {
871+
m_gain = p->get().value();
872+
m_linearGain = std::pow(10.0f, ((m_gain - 0.5f) * 60.0f) / 20.0f);
873+
}
874+
}
875+
811876
} // namespace noteahead

src/domain/devices/sampler_device.hpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,12 @@ class SamplerDevice : public Device
108108
bool channelMode() const;
109109
void setChannelMode(bool enabled);
110110

111+
float globalVolume() const;
112+
void setGlobalVolume(float volume);
113+
114+
float gain() const;
115+
void setGain(float gain);
116+
111117
double playbackPosition(uint8_t note) const;
112118
bool isFinished(uint8_t note) const;
113119

@@ -119,6 +125,7 @@ class SamplerDevice : public Device
119125
private:
120126
struct Voice;
121127
void updateVoiceEffects(Voice & voice);
128+
void syncParameters();
122129

123130
struct Voice
124131
{
@@ -152,10 +159,13 @@ class SamplerDevice : public Device
152159
std::string m_name;
153160
float m_globalPan = 0.5f;
154161
float m_globalVolume = 1.0f;
162+
float m_gain = 0.5f;
163+
float m_linearGain = 1.0f;
155164
float m_globalCutoff = 1.0f;
156165
float m_globalHpfCutoff = 0.0f;
157166
float m_manualGlobalPan = 0.5f;
158167
float m_manualGlobalVolume = 1.0f;
168+
float m_manualGain = 0.5f;
159169
float m_manualGlobalCutoff = 1.0f;
160170
float m_manualGlobalHpfCutoff = 0.0f;
161171
bool m_channelMode = false;

src/domain/devices/synth_device.cpp

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ SynthDevice::SynthDevice(std::string name)
118118
addParameter(Parameter { Constants::NahdXml::xmlKeyPanSpread().toStdString(), 0.0f, 0, 100, 0 });
119119
addParameter(Parameter { Constants::NahdXml::xmlKeyPan().toStdString(), 0.5f, 0, 100, 50 });
120120
addParameter(Parameter { Constants::NahdXml::xmlKeyVolume().toStdString(), 1.0f, 0, 100, 100 });
121+
addParameter(Parameter { Constants::NahdXml::xmlKeyGain().toStdString(), 0.5f, -30, 30, 0, 1, false });
121122

122123
addParameter(Parameter { Constants::NahdXml::xmlKeyDelayType().toStdString(), 0.0f, 0, 5, 0, 1, true });
123124
addParameter(Parameter { Constants::NahdXml::xmlKeyDelayTime().toStdString(), 0.5f, 0, 10000, 500 }); // 0..10 seconds in ms
@@ -137,6 +138,7 @@ SynthDevice::SynthDevice(std::string name)
137138
m_manualPanSpread = m_panSpread;
138139
m_manualMasterPan = m_masterPan;
139140
m_manualMasterVolume = m_masterVolume;
141+
m_manualGain = m_gain;
140142
m_manualLpfCutoff = m_lpfCutoff;
141143
m_manualHpfCutoff = m_hpfCutoff;
142144

@@ -246,9 +248,9 @@ void SynthDevice::processAudio(float * output, uint32_t nFrames, uint32_t sample
246248
voice.hpf.setCutoff(m_hpfCutoff);
247249

248250
const float filtered { voice.hpf.process(voice.lpf.process(static_cast<float>(mixHeadroom))) };
249-
const float finalSample { filtered * static_cast<float>(ampEnv) * m_masterVolume * (1.0f / static_cast<float>(MaxVoices)) };
251+
const float finalSample { filtered * static_cast<float>(ampEnv) * m_masterVolume * (1.0f / static_cast<float>(MaxVoices)) * m_linearGain };
250252

251-
localBuffer[i * 2] += finalSample * (1.0f - voice.pan) * (1.0f - m_masterPan) * 2.0f;
253+
localBuffer[i * 2] += finalSample * (1.0f - voice.pan);
252254
localBuffer[i * 2 + 1] += finalSample * voice.pan * m_masterPan * 2.0f;
253255
}
254256

@@ -291,12 +293,14 @@ void SynthDevice::processMidiCc(uint8_t controller, uint8_t value, uint8_t)
291293
m_panSpread = m_manualPanSpread;
292294
m_masterPan = m_manualMasterPan;
293295
m_masterVolume = m_manualMasterVolume;
296+
m_gain = m_manualGain;
294297
m_lpfCutoff = m_manualLpfCutoff;
295298
m_hpfCutoff = m_manualHpfCutoff;
296299

297300
if (auto p = parameter(Constants::NahdXml::xmlKeyPanSpread().toStdString()); p) p->get().setValue(m_panSpread);
298301
if (auto p = parameter(Constants::NahdXml::xmlKeyPan().toStdString()); p) p->get().setValue(m_masterPan);
299302
if (auto p = parameter(Constants::NahdXml::xmlKeyVolume().toStdString()); p) p->get().setValue(m_masterVolume);
303+
if (auto p = parameter(Constants::NahdXml::xmlKeyGain().toStdString()); p) p->get().setValue(m_gain);
300304
if (auto p = parameter(Constants::NahdXml::xmlKeySynthLpfCutoff().toStdString()); p) p->get().setValue(m_lpfCutoff);
301305
if (auto p = parameter(Constants::NahdXml::xmlKeySynthHpfCutoff().toStdString()); p) p->get().setValue(m_hpfCutoff);
302306

@@ -515,6 +519,10 @@ void SynthDevice::syncParameters()
515519
if (auto p = parameter(Constants::NahdXml::xmlKeyPanSpread().toStdString()); p) m_panSpread = p->get().value();
516520
if (auto p = parameter(Constants::NahdXml::xmlKeyPan().toStdString()); p) m_masterPan = p->get().value();
517521
if (auto p = parameter(Constants::NahdXml::xmlKeyVolume().toStdString()); p) m_masterVolume = p->get().value();
522+
if (auto p = parameter(Constants::NahdXml::xmlKeyGain().toStdString()); p) {
523+
m_gain = p->get().value();
524+
m_linearGain = std::pow(10.0f, ((m_gain - 0.5f) * 60.0f) / 20.0f);
525+
}
518526

519527
if (auto p = parameter(Constants::NahdXml::xmlKeyDelayType().toStdString()); p) m_delayType = static_cast<DelayEffect::Type>(p->get().xmlValue());
520528
if (auto p = parameter(Constants::NahdXml::xmlKeyDelayTime().toStdString()); p) m_delayTime = p->get().value() * 10.0f;
@@ -751,6 +759,8 @@ float SynthDevice::masterPan() const { return m_masterPan; }
751759
void SynthDevice::setMasterPan(float pan) { bool changed = false; { std::lock_guard<std::mutex> lock { m_mutex }; if (auto p = parameter(Constants::NahdXml::xmlKeyPan().toStdString()); p) { p->get().setValue(pan); m_manualMasterPan = p->get().value(); syncParameters(); changed = true; } } if (changed) emit dataChanged(); }
752760
float SynthDevice::masterVolume() const { return m_masterVolume; }
753761
void SynthDevice::setMasterVolume(float vol) { bool changed = false; { std::lock_guard<std::mutex> lock { m_mutex }; if (auto p = parameter(Constants::NahdXml::xmlKeyVolume().toStdString()); p) { p->get().setValue(vol); m_manualMasterVolume = p->get().value(); syncParameters(); changed = true; } } if (changed) emit dataChanged(); }
762+
float SynthDevice::gain() const { return m_gain; }
763+
void SynthDevice::setGain(float val) { bool changed = false; { std::lock_guard<std::mutex> lock { m_mutex }; if (auto p = parameter(Constants::NahdXml::xmlKeyGain().toStdString()); p) { p->get().setValue(val); m_manualGain = p->get().value(); syncParameters(); changed = true; } } if (changed) emit dataChanged(); }
754764

755765
// Delay Accessors
756766
DelayEffect::Type SynthDevice::delayType() const { return m_delayType; }

0 commit comments

Comments
 (0)