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

implements bit crusher effect #153

Merged
merged 2 commits into from
Jan 27, 2025
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
2 changes: 0 additions & 2 deletions examples/demo/metronome_demo.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
#include <algorithm>
#include <cctype>
#include <chrono>
#include <cmath>
#include <span>
#include <thread>

Expand All @@ -14,7 +13,6 @@
namespace {

using ::barely::ControlType;
using ::barely::Instrument;
using ::barely::Musician;
using ::barely::OscillatorShape;
using ::barely::examples::AudioClock;
Expand Down
2 changes: 2 additions & 0 deletions examples/demo/midi_demo.cpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
#include <algorithm>
#include <cassert>
#include <chrono>
#include <cstdint>
#include <functional>
#include <span>
#include <string>
#include <thread>
#include <tuple>
#include <utility>
#include <vector>

Expand Down
1 change: 0 additions & 1 deletion examples/demo/sequencer_demo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
#include <span>
#include <thread>
#include <unordered_map>
#include <utility>
#include <vector>

#include "barelymusician.h"
Expand Down
8 changes: 8 additions & 0 deletions include/barelymusician.h
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,10 @@ typedef enum BarelyControlType {
BarelyControlType_kFilterType,
/// Filter frequency in hertz.
BarelyControlType_kFilterFrequency,
/// Bit crusher depth.
BarelyControlType_kBitCrusherDepth,
/// Normalized bit crusher rate.
BarelyControlType_kBitCrusherRate,
/// Number of control types.
BarelyControlType_kCount,
} BarelyControlType;
Expand Down Expand Up @@ -775,6 +779,10 @@ enum class ControlType {
kFilterType = BarelyControlType_kFilterType,
/// Filter frequency in hertz.
kFilterFrequency = BarelyControlType_kFilterFrequency,
/// Bit crusher depth.
kBitCrusherDepth = BarelyControlType_kBitCrusherDepth,
/// Normalized bit crusher rate.
kBitCrusherRate = BarelyControlType_kBitCrusherRate,
};

/// Filter type enum.
Expand Down
14 changes: 14 additions & 0 deletions platforms/unity/Assets/BarelyMusician/Scripts/Instrument.cs
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,16 @@ public bool HasChanged {
[Range(0.0f, 48000.0f)]
public float FilterFrequency = 0.0f;

[Header("Bit Crusher")]

/// Bit crusher depth.
[Range(1.0f, 16.0f)]
public float BitCrusherDepth = 16.0f;

/// Normalized bit crusher rate.
[Range(0.0f, 1.0f)]
public float BitCrusherRate = 1.0f;

/// Note off callback.
///
/// @param pitch Note pitch.
Expand Down Expand Up @@ -325,6 +335,10 @@ private void Update() {
SetControl(Musician.Internal.ControlType.SAMPLE_PLAYBACK_MODE, (float)SamplePlaybackMode);
SetControl(Musician.Internal.ControlType.FILTER_TYPE, (float)FilterType);
SetControl(Musician.Internal.ControlType.FILTER_FREQUENCY, FilterFrequency);
SetControl(Musician.Internal.ControlType.FILTER_TYPE, (float)FilterType);
SetControl(Musician.Internal.ControlType.FILTER_FREQUENCY, FilterFrequency);
SetControl(Musician.Internal.ControlType.BIT_CRUSHER_DEPTH, BitCrusherDepth);
SetControl(Musician.Internal.ControlType.BIT_CRUSHER_RATE, BitCrusherRate);
}

private void OnAudioFilterRead(float[] data, int channels) {
Expand Down
4 changes: 4 additions & 0 deletions platforms/unity/Assets/BarelyMusician/Scripts/Musician.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ public enum ControlType {
[InspectorName("Filter Type")] FILTER_TYPE,
/// Filter frequency in hertz.
[InspectorName("Filter Frequency")] FILTER_FREQUENCY,
/// Bit crusher depth.
[InspectorName("Bit Crusher Depth")] BIT_CRUSHER_DEPTH,
/// Bit crusher rate.
[InspectorName("Bit Crusher Rate")] BIT_CRUSHER_RATE,
}

/// Creates a new component.
Expand Down
4 changes: 2 additions & 2 deletions src/barelycomposer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

// Arpeggiator.
struct BarelyArpeggiator : public barely::internal::Arpeggiator {
BarelyArpeggiator(barely::internal::Musician& musician) noexcept
explicit BarelyArpeggiator(barely::internal::Musician& musician) noexcept
: barely::internal::Arpeggiator(musician) {}
};

Expand All @@ -24,7 +24,7 @@ struct BarelyRandom : public barely::internal::Random {

// Repeater.
struct BarelyRepeater : public barely::internal::Repeater {
BarelyRepeater(barely::internal::Musician& musician) noexcept
explicit BarelyRepeater(barely::internal::Musician& musician) noexcept
: barely::internal::Repeater(musician) {}
};

Expand Down
20 changes: 19 additions & 1 deletion src/dsp/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
add_library(
barelymusician_dsp_bit_crusher INTERFACE
bit_crusher.h
)

add_library(
barelymusician_dsp_decibels INTERFACE
decibels.h
Expand All @@ -21,9 +26,10 @@ add_library(
)
target_link_libraries(
barelymusician_dsp_instrument_processor
barelymusician_dsp_sample_data
barelymusician_dsp_bit_crusher
barelymusician_dsp_envelope
barelymusician_dsp_gain_processor
barelymusician_dsp_sample_data
barelymusician_dsp_voice
barelymusician_headeronly
)
Expand Down Expand Up @@ -73,6 +79,7 @@ add_library(
)
target_link_libraries(
barelymusician_dsp_voice INTERFACE
barelymusician_dsp_bit_crusher
barelymusician_dsp_envelope
barelymusician_dsp_one_pole_filter
barelymusician_dsp_oscillator
Expand All @@ -81,6 +88,17 @@ target_link_libraries(
)

if(ENABLE_TESTS)
add_executable(
barelymusician_dsp_bit_crusher_test
bit_crusher_test.cpp
)
target_link_libraries(
barelymusician_dsp_bit_crusher_test
barelymusician_dsp_bit_crusher
gtest_main
)
gtest_discover_tests(barelymusician_dsp_bit_crusher_test)

add_executable(
barelymusician_dsp_decibels_test
decibels_test.cpp
Expand Down
48 changes: 48 additions & 0 deletions src/dsp/bit_crusher.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#ifndef BARELYMUSICIAN_DSP_BIT_CRUSHER_H_
#define BARELYMUSICIAN_DSP_BIT_CRUSHER_H_

#include <cassert>
#include <cmath>

#include "barelymusician.h"

namespace barely::internal {

/// Bit crusher effect with bit depth and sample rate reduction.
class BitCrusher {
public:
/// Applies the bit crusher effect to the next input sample.
///
/// @param input Input sample.
/// @param range Sample value range (for bit depth reduction).
/// @param increment Sample step increment (for sample rate reduction).
/// @return Filtered output sample.
[[nodiscard]] float Next(float input, float range, float increment) noexcept {
assert(range >= 0.0f);
assert(increment >= 0.0f);
phase_ += increment;
if (phase_ >= 1.0f) {
// Zero `range` is passed to disable the bit depth reduction.
output_ = (range > 0.0f) ? std::round(input * range) / range : input;
phase_ -= 1.0f;
}
return output_;
}

/// Resets the effect state.
void Reset() noexcept {
output_ = 0.0f;
phase_ = 0.0f;
}

private:
// Last output sample.
float output_ = 0.0f;

// Sample step phase.
float phase_ = 0.0f;
};

} // namespace barely::internal

#endif // BARELYMUSICIAN_DSP_BIT_CRUSHER_H_
53 changes: 53 additions & 0 deletions src/dsp/bit_crusher_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#include "dsp/bit_crusher.h"

#include <array>
#include <cmath>

#include "barelymusician.h"
#include "gtest/gtest.h"

namespace barely::internal {
namespace {

// Test input.
constexpr int kInputLength = 5;
constexpr float kInput[kInputLength] = {0.2f, -0.4f, 0.6f, -0.8f, 1.0f};

// Tests that a bit crusher reduces the bit depth as expected.
TEST(BitCrusherTest, BitDepth) {
constexpr float kIncrement = 1.0f;

BitCrusher bit_crusher;
for (int i = 0; i < kInputLength; ++i) {
EXPECT_FLOAT_EQ(bit_crusher.Next(kInput[i], 0.0f, kIncrement), kInput[i]); // bypass
EXPECT_FLOAT_EQ(bit_crusher.Next(kInput[i], 1.0f, kIncrement), std::round(kInput[i])); // 1-bit
}
}

// Tests that a bit crusher reduces the sample rate as expected.
TEST(BitCrusherTest, SampleRate) {
constexpr float kRange = 0.0f;

BitCrusher bit_crusher;

// Bypass.
for (int i = 0; i < kInputLength; ++i) {
EXPECT_FLOAT_EQ(bit_crusher.Next(kInput[i], kRange, 1.0f), kInput[i]);
}
bit_crusher.Reset();

// Hold every other sample.
for (int i = 0; i < kInputLength; ++i) {
EXPECT_FLOAT_EQ(bit_crusher.Next(kInput[i], kRange, 0.5f),
(i > 0) ? kInput[2 * ((i - 1) / 2) + 1] : 0.0f);
}
bit_crusher.Reset();

// Hold forever.
for (int i = 0; i < kInputLength; ++i) {
EXPECT_FLOAT_EQ(bit_crusher.Next(kInput[i], kRange, 0.0f), 0.0f);
}
}

} // namespace
} // namespace barely::internal
18 changes: 13 additions & 5 deletions src/dsp/instrument_processor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

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

#include "barelymusician.h"
#include "dsp/sample_data.h"
Expand Down Expand Up @@ -175,7 +176,7 @@ void InstrumentProcessor::SetControl(ControlType type, float value) noexcept {
adsr_.SetRelease(value);
break;
case ControlType::kOscillatorMix:
oscillator_mix_ = value;
voice_params_.oscillator_mix = value;
break;
case ControlType::kOscillatorPitchShift:
oscillator_pitch_shift_ = value;
Expand All @@ -201,7 +202,7 @@ void InstrumentProcessor::SetControl(ControlType type, float value) noexcept {
sample_data_, sample_playback_mode_);
break;
case ControlType::kPulseWidth:
pulse_width_ = value;
voice_params_.pulse_width = value;
break;
case ControlType::kSamplePlaybackMode:
sample_playback_mode_ = static_cast<SamplePlaybackMode>(value);
Expand All @@ -213,9 +214,16 @@ void InstrumentProcessor::SetControl(ControlType type, float value) noexcept {
voice_callback_ = GetVoiceCallback(filter_type_, oscillator_mode_, oscillator_shape_,
sample_data_, sample_playback_mode_);
break;
case ControlType::kFilterFrequency: {
filter_coefficient_ = value;
} break;
case ControlType::kFilterFrequency:
voice_params_.filter_coefficient = value;
break;
case ControlType::kBitCrusherDepth:
// Offset the bit depth by 1 to normalize the range.
voice_params_.bit_crusher_range = (value < 16.0f) ? std::pow(2.0f, value - 1.0f) : 0.0f;
break;
case ControlType::kBitCrusherRate:
voice_params_.bit_crusher_increment = value;
break;
default:
assert(!"Invalid control type");
return;
Expand Down
10 changes: 3 additions & 7 deletions src/dsp/instrument_processor.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,9 @@ class InstrumentProcessor {
return;
}
if constexpr (kShouldAccumulate) {
output_samples[i] +=
voice_callback_(voice, filter_coefficient_, oscillator_mix_, pulse_width_);
output_samples[i] += voice_callback_(voice, voice_params_);
} else {
output_samples[i] =
voice_callback_(voice, filter_coefficient_, oscillator_mix_, pulse_width_);
output_samples[i] = voice_callback_(voice, voice_params_);
}
}
}
Expand All @@ -112,9 +110,7 @@ class InstrumentProcessor {
OscillatorShape oscillator_shape_ = OscillatorShape::kNone;
SamplePlaybackMode sample_playback_mode_ = SamplePlaybackMode::kNone;

float filter_coefficient_ = 1.0f;
float oscillator_mix_ = 0.0f;
float pulse_width_ = 0.5f;
Voice::Params voice_params_ = {};

bool should_retrigger_ = false;

Expand Down
Loading
Loading