Skip to content

Commit

Permalink
add option to build control message when passing
Browse files Browse the repository at this point in the history
+ more driveby cleanup in voice #144
  • Loading branch information
anokta committed Nov 13, 2024
1 parent 4459a4e commit 89dda58
Show file tree
Hide file tree
Showing 13 changed files with 142 additions and 129 deletions.
1 change: 0 additions & 1 deletion src/dsp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ target_link_libraries(
barelymusician_dsp_sample_data
barelymusician_dsp_envelope
barelymusician_dsp_gain_processor
barelymusician_dsp_one_pole_filter
barelymusician_dsp_voice
barelymusician_headeronly
)
Expand Down
35 changes: 20 additions & 15 deletions src/dsp/envelope.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
#define BARELYMUSICIAN_DSP_ENVELOPE_H_

#include <algorithm>
#include <cassert>

namespace barely::internal {

/// Envelope that generates output samples according to its current state.
Expand All @@ -12,9 +14,8 @@ class Envelope {
public:
/// Constructs new `Adsr`.
///
/// @param sample_rate Sampling rate in hertz.
explicit Adsr(int sample_rate) noexcept
: sample_interval_((sample_rate > 0) ? 1.0 / static_cast<double>(sample_rate) : 0.0) {}
/// @param sample_interval Sample interval in seconds.
explicit Adsr(double sample_interval) noexcept : sample_interval_(sample_interval) {}

/// Sets the attack.
///
Expand Down Expand Up @@ -54,7 +55,7 @@ class Envelope {
private:
friend class Envelope;

// Inverse sampling rate in seconds.
// Sample interval in seconds.
double sample_interval_ = 0.0;

// ADSR values.
Expand All @@ -71,16 +72,16 @@ class Envelope {

/// Generates the next output sample.
///
/// @param adsr Adsr.
/// @return Next output sample.
double Next(const Adsr& adsr) noexcept {
double Next() noexcept {
if (state_ == State::kIdle) {
return 0.0;
}
assert(adsr_ != nullptr);
if (state_ == State::kAttack) {
if (adsr.attack_increment_ > 0.0) {
if (adsr_->attack_increment_ > 0.0) {
output_ = phase_;
phase_ += adsr.attack_increment_;
phase_ += adsr_->attack_increment_;
if (phase_ >= 1.0) {
phase_ = 0.0;
state_ = State::kDecay;
Expand All @@ -91,9 +92,9 @@ class Envelope {
state_ = State::kDecay;
}
if (state_ == State::kDecay) {
if (adsr.decay_increment_ > 0.0) {
output_ = 1.0 - phase_ * (1.0 - adsr.sustain_);
phase_ += adsr.decay_increment_;
if (adsr_->decay_increment_ > 0.0) {
output_ = 1.0 - phase_ * (1.0 - adsr_->sustain_);
phase_ += adsr_->decay_increment_;
if (phase_ >= 1.0) {
phase_ = 0.0;
state_ = State::kSustain;
Expand All @@ -104,13 +105,13 @@ class Envelope {
state_ = State::kSustain;
}
if (state_ == State::kSustain) {
output_ = adsr.sustain_;
output_ = adsr_->sustain_;
return output_;
}
if (state_ == State::kRelease) {
if (adsr.release_increment_ > 0.0) {
if (adsr_->release_increment_ > 0.0) {
output_ = (1.0 - phase_) * release_output_;
phase_ += adsr.release_increment_;
phase_ += adsr_->release_increment_;
if (phase_ >= 1.0) {
phase_ = 0.0;
state_ = State::kIdle;
Expand All @@ -130,9 +131,10 @@ class Envelope {
///
/// @param adsr Adsr.
void Start(const Adsr& adsr) noexcept {
adsr_ = &adsr;
output_ = adsr_->sustain_;
phase_ = 0.0;
state_ = State::kAttack;
output_ = adsr.sustain_;
}

/// Stops the envelope.
Expand All @@ -148,6 +150,9 @@ class Envelope {
// Envelope state.
enum class State { kAttack, kDecay, kSustain, kRelease, kIdle };

// Pointer to adsr.
const Adsr* adsr_ = nullptr;

// Last output value.
double output_ = 0.0;

Expand Down
23 changes: 12 additions & 11 deletions src/dsp/envelope_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
namespace barely::internal {
namespace {

// Sampling rate.
constexpr int kSampleRate = 1000;
constexpr double kSampleInterval = 1.0 / kSampleRate;

// Envelope ADSR.
constexpr double kAttack = 0.02;
Expand All @@ -20,16 +20,16 @@ constexpr double kEpsilon = 1e-3;
// Tests that the envelope generates the expected output samples when initialized with the default
// constructor.
TEST(EnvelopeTest, ProcessDefault) {
const Envelope::Adsr adsr(kSampleRate);
const Envelope::Adsr adsr(kSampleInterval);

Envelope envelope;
EXPECT_DOUBLE_EQ(envelope.Next(adsr), 0.0);
EXPECT_DOUBLE_EQ(envelope.Next(), 0.0);

envelope.Start(adsr);
EXPECT_DOUBLE_EQ(envelope.Next(adsr), 1.0);
EXPECT_DOUBLE_EQ(envelope.Next(), 1.0);

envelope.Stop();
EXPECT_DOUBLE_EQ(envelope.Next(adsr), 0.0);
EXPECT_DOUBLE_EQ(envelope.Next(), 0.0);
}

// Tests that the envelope generates the expected output samples consistently over multiple samples.
Expand All @@ -39,14 +39,15 @@ TEST(EnvelopeTest, ProcessMultiSamples) {
constexpr int kSustainSampleCount = kAttackSampleCount + kDecaySampleCount;
constexpr int kReleaseSampleCount = static_cast<int>(kSampleRate * kRelease);

Envelope::Adsr adsr(kSampleRate);

Envelope envelope;
Envelope::Adsr adsr(kSampleInterval);
adsr.SetAttack(kAttack);
adsr.SetDecay(kDecay);
adsr.SetSustain(kSustain);
adsr.SetRelease(kRelease);
EXPECT_DOUBLE_EQ(envelope.Next(adsr), 0.0);

Envelope envelope;

EXPECT_DOUBLE_EQ(envelope.Next(), 0.0);

double expected_sample = 0.0;

Expand All @@ -63,7 +64,7 @@ TEST(EnvelopeTest, ProcessMultiSamples) {
// Sustain.
expected_sample = kSustain;
}
EXPECT_NEAR(envelope.Next(adsr), expected_sample, kEpsilon);
EXPECT_NEAR(envelope.Next(), expected_sample, kEpsilon);
}

envelope.Stop();
Expand All @@ -76,7 +77,7 @@ TEST(EnvelopeTest, ProcessMultiSamples) {
// Idle.
expected_sample = 0.0;
}
EXPECT_NEAR(envelope.Next(adsr), expected_sample, kEpsilon);
EXPECT_NEAR(envelope.Next(), expected_sample, kEpsilon);
}
}

Expand Down
73 changes: 25 additions & 48 deletions src/dsp/instrument_processor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,27 @@

#include <algorithm>
#include <cassert>
#include <cmath>

#include "barelymusician.h"
#include "dsp/one_pole_filter.h"
#include "dsp/sample_data.h"
#include "dsp/voice.h"

namespace barely::internal {

namespace {

// Returns the frequency ratio of a given `pitch`.
double FrequencyRatioFromPitch(double pitch) { return std::pow(2.0, pitch); }

// Returns the frequency of a given `pitch`.
double FrequencyFromPitch(double pitch, double reference_frequency) noexcept {
return reference_frequency * FrequencyRatioFromPitch(pitch);
}

template <OscillatorShape kOscillatorShape, SamplePlaybackMode kSamplePlaybackMode>
VoiceCallback GetVoiceCallback(FilterType filter_type) {
switch (filter_type) {
case FilterType::kNone:
return Voice::ProcessVoice<FilterType::kNone, kOscillatorShape, kSamplePlaybackMode>;
return Voice::Next<FilterType::kNone, kOscillatorShape, kSamplePlaybackMode>;
case FilterType::kLowPass:
return Voice::ProcessVoice<FilterType::kLowPass, kOscillatorShape, kSamplePlaybackMode>;
return Voice::Next<FilterType::kLowPass, kOscillatorShape, kSamplePlaybackMode>;
case FilterType::kHighPass:
return Voice::ProcessVoice<FilterType::kHighPass, kOscillatorShape, kSamplePlaybackMode>;
return Voice::Next<FilterType::kHighPass, kOscillatorShape, kSamplePlaybackMode>;
default:
assert(!"Invalid filter type");
return Voice::ProcessVoice<FilterType::kNone, kOscillatorShape, kSamplePlaybackMode>;
return Voice::Next<FilterType::kNone, kOscillatorShape, kSamplePlaybackMode>;
}
}

Expand Down Expand Up @@ -80,11 +70,10 @@ VoiceCallback GetVoiceCallback(FilterType filter_type, OscillatorShape oscillato

// NOLINTNEXTLINE(bugprone-exception-escape)
InstrumentProcessor::InstrumentProcessor(int sample_rate, double reference_frequency) noexcept
: adsr_(sample_rate),
: sample_interval_(1.0 / static_cast<double>(sample_rate)),
adsr_(sample_interval_),
gain_processor_(sample_rate),
reference_frequency_(reference_frequency),
sample_rate_(sample_rate),
sample_interval_(1.0 / static_cast<double>(sample_rate)) {
reference_frequency_(reference_frequency) {
assert(sample_rate > 0);
}

Expand Down Expand Up @@ -147,11 +136,8 @@ void InstrumentProcessor::SetControl(ControlType type, double value) noexcept {
if (Voice& voice = voice_states_[i].voice; voice.IsActive()) {
const double shifted_pitch =
voice_states_[i].pitch + pitch_shift_ + voice_states_[i].pitch_shift;
voice.set_oscillator_increment(FrequencyFromPitch(shifted_pitch, reference_frequency_) *
sample_interval_);
voice.set_sample_player_speed(
FrequencyRatioFromPitch(shifted_pitch - voice_states_[i].root_pitch),
sample_interval_);
voice.set_oscillator_increment(shifted_pitch, reference_frequency_, sample_interval_);
voice.set_sample_player_increment(shifted_pitch, sample_interval_);
}
}
break;
Expand All @@ -164,7 +150,7 @@ void InstrumentProcessor::SetControl(ControlType type, double value) noexcept {
GetVoiceCallback(filter_type_, oscillator_shape_, sample_data_, sample_playback_mode_);
break;
case ControlType::kFilterFrequency: {
filter_coefficient_ = GetFilterCoefficient(sample_rate_, value);
filter_coefficient_ = value;
} break;
default:
assert(!"Invalid control type");
Expand All @@ -182,11 +168,8 @@ void InstrumentProcessor::SetNoteControl(double pitch, NoteControlType type,
voice_states_[i].pitch_shift = value;
const double shifted_pitch =
voice_states_[i].pitch + pitch_shift_ + voice_states_[i].pitch_shift;
voice.set_oscillator_increment(FrequencyFromPitch(shifted_pitch, reference_frequency_) *
sample_interval_);
voice.set_sample_player_speed(
FrequencyRatioFromPitch(shifted_pitch - voice_states_[i].root_pitch),
sample_interval_);
voice.set_oscillator_increment(shifted_pitch, reference_frequency_, sample_interval_);
voice.set_sample_player_increment(shifted_pitch, sample_interval_);
break;
}
}
Expand Down Expand Up @@ -214,20 +197,12 @@ void InstrumentProcessor::SetNoteOn(double pitch, double intensity) noexcept {
// No voices available.
return;
}
VoiceState& voice_state = AcquireVoice(pitch);
voice_state.pitch = pitch;
voice_state.pitch_shift = 0.0;
voice_state.timestamp = 0;

Voice& voice = voice_state.voice;
Voice& voice = AcquireVoice(pitch);
const double shifted_pitch = pitch + pitch_shift_;
voice.set_oscillator_increment(FrequencyFromPitch(shifted_pitch, reference_frequency_) *
sample_interval_);
voice.set_oscillator_increment(shifted_pitch, reference_frequency_, sample_interval_);
if (const auto* sample = sample_data_.Select(pitch); sample != nullptr) {
voice_state.root_pitch = sample->root_pitch;
voice.set_sample_player_slice(sample);
voice.set_sample_player_speed(FrequencyRatioFromPitch(shifted_pitch - sample->root_pitch),
sample_interval_);
voice.set_sample_player_increment(shifted_pitch, sample_interval_);
}
voice.Start(adsr_, intensity);
}
Expand All @@ -239,8 +214,7 @@ void InstrumentProcessor::SetReferenceFrequency(double reference_frequency) noex
if (auto& voice = voice_states_[i].voice; voice.IsActive()) {
const double shifted_pitch =
voice_states_[i].pitch + pitch_shift_ + voice_states_[i].pitch_shift;
voice.set_oscillator_increment(FrequencyFromPitch(shifted_pitch, reference_frequency_) *
sample_interval_);
voice.set_oscillator_increment(shifted_pitch, reference_frequency_, sample_interval_);
}
}
}
Expand All @@ -254,16 +228,15 @@ void InstrumentProcessor::SetSampleData(SampleData& sample_data) noexcept {
voice.set_sample_player_slice(nullptr);
} else if (const auto* sample = sample_data_.Select(voice_states_[i].pitch);
sample != nullptr) {
voice_states_[i].root_pitch = sample->root_pitch;
const double shifted_pitch =
voice_states_[i].pitch + pitch_shift_ + voice_states_[i].pitch_shift;
voice.set_sample_player_slice(sample);
voice.set_sample_player_speed(
FrequencyRatioFromPitch(voice_states_[i].pitch + pitch_shift_ - sample->root_pitch),
sample_interval_);
voice.set_sample_player_increment(shifted_pitch, sample_interval_);
}
}
}

InstrumentProcessor::VoiceState& InstrumentProcessor::AcquireVoice(double pitch) noexcept {
Voice& InstrumentProcessor::AcquireVoice(double pitch) noexcept {
int voice_index = -1;
int oldest_voice_index = 0;
for (int i = 0; i < voice_count_; ++i) {
Expand All @@ -287,7 +260,11 @@ InstrumentProcessor::VoiceState& InstrumentProcessor::AcquireVoice(double pitch)
// If no voices are available to acquire, steal the oldest active voice.
voice_index = oldest_voice_index;
}
return voice_states_[voice_index];
VoiceState& voice_state = voice_states_[voice_index];
voice_state.pitch = pitch;
voice_state.pitch_shift = 0.0;
voice_state.timestamp = 0;
return voice_state.voice;
}

} // namespace barely::internal
Loading

0 comments on commit 89dda58

Please sign in to comment.