Skip to content

Commit 47cca9f

Browse files
committed
BitCrusherProcessor: overhauled to make it kinda neat.
1 parent 2a33842 commit 47cca9f

File tree

2 files changed

+185
-85
lines changed

2 files changed

+185
-85
lines changed
Lines changed: 111 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,113 +1,142 @@
1-
//==============================================================================
2-
#if 0 && ! DOXYGEN
3-
//These are some bits of code that could be useful someday...
4-
5-
/** Obtain the maximum value, pre-negative a number of bits to the */
6-
inline float getMaxValueForNumBits (int numBits) noexcept
7-
{
8-
BigInteger biggy;
9-
biggy.setRange (0, numBits - 1, true);
10-
11-
return (float) biggy.toInteger() * 0.5f; //Halved to offset, making the middle-most number 0 dBFS
12-
}
13-
14-
/** Discrete sample bit-conversion algorithm */
15-
inline float reduceSample (float sample, int bitDepth) noexcept
1+
BitCrusherProcessor::BitCrusherProcessor() :
2+
InternalProcessor (false)
3+
{
4+
presets =
165
{
17-
return (float) std::clamp (getMaxValueForNumBits (bitDepth) * sample, -maxValue, maxValue);
18-
}
19-
20-
inline constexpr float compressSample (float sample, float scale) noexcept
6+
{ NEEDS_TRANS ("CleanPass"), 16.0f, 1.0f, 1.0f },
7+
{ NEEDS_TRANS ("PixelPulse"), 8.0f, 12.0f, 2.5f },
8+
{ NEEDS_TRANS ("PocketConsole"), 5.0f, 16.0f, 3.0f },
9+
{ NEEDS_TRANS ("Lofi"), 10.0f, 4.0f, 6.0f },
10+
{ NEEDS_TRANS ("WoodgrainFury"), 4.0f, 32.0f, 6.0f },
11+
{ NEEDS_TRANS ("BrokenDAC"), 3.0f, 64.0f, 10.0f },
12+
{ NEEDS_TRANS ("AliasBoi"), 4.0f, 72.0f, 10.0f }
13+
};
14+
15+
auto layout = createDefaultParameterLayout();
16+
17+
auto addFloatParam = [&] (StringRef id, StringRef name,
18+
float start, float end, float defaultValue,
19+
float interval, float skew)
2120
{
22-
return sample * scale;
23-
}
21+
auto newParam = std::make_unique<AudioParameterFloat> (id, name,
22+
NormalisableRange<float> (start, end, interval, skew),
23+
defaultValue);
24+
auto* np = newParam.get();
25+
layout.add (std::move (newParam));
26+
return np;
27+
};
2428

25-
// Ye olde...
26-
m = (float) (1 << (localBitDepth - 1));
27-
M = 1.0f / (float) m;
29+
bitDepthParam = addFloatParam ("depth", NEEDS_TRANS ("Depth"), 4.0f, 16.0f, 8.0f, 1.0f, 0.4f);
30+
downsampleFactorParam = addFloatParam ("downsampleFactor", NEEDS_TRANS ("Downsample Factor"), 1.0f, 128.0f, 8.0f, 1.0f, 0.4f);
31+
driveParam = addFloatParam ("drive", NEEDS_TRANS ("Drive"), 1.0f, 20.0f, 2.5f, 0.001f, 0.5f);
2832

29-
for (int i = numChannels; --i >= 0;)
30-
for (int f = numSamples; --f >= 0;)
31-
dest[i][f] = (float) ((float) src[i][f] * m * M);
33+
resetAPVTSWithLayout (std::move (layout));
3234

33-
template<typename FloatType>
34-
inline FloatType decimate (FloatType input, int keepBits)
35-
{
36-
const auto quantum = std::pow (static_cast<FloatType> (2), (FloatType) keepBits);
37-
return std::floor (input * quantum) / quantum;
35+
setCurrentProgramDirectly (1);
3836
}
39-
#endif
4037

4138
//==============================================================================
42-
inline double crushToNBit (double sample, int bitDepth)
43-
{
44-
constexpr auto one = 1.0;
45-
constexpr auto two = 2.0;
46-
47-
const auto bd = bitDepth;
48-
auto s = 0.0;
49-
50-
if (sample >= 1.0)
51-
s = std::pow (two, bd - 1) - one;
52-
else if (sample <= -1.0)
53-
s = std::pow (-two, bd - 1);
54-
else
55-
s = std::floor (sample * -std::pow (-two, bd - one));
39+
int BitCrusherProcessor::getNumPrograms() { return std::max (1, (int) presets.size()); }
40+
int BitCrusherProcessor::getCurrentProgram() { return programIndex; }
5641

57-
// NB: Deliberately quantising with casts here!
58-
return (double) (int) s;
42+
void BitCrusherProcessor::setCurrentProgramDirectly (int index)
43+
{
44+
programIndex = index;
45+
const auto& p = presets[index];
46+
setBitDepth ((int) p.bitDepth);
47+
setDownsampleFactor ((int) p.downsample);
48+
setDrive ((float) p.drive);
5949
}
6050

61-
inline double crushBit (double sample, int bitDepth)
51+
void BitCrusherProcessor::setCurrentProgram (int index)
6252
{
63-
/*
64-
auto mixAmount = std::lerp (1.0, 32.0, static_cast<double> (bitDepth) / 32.0) / 32.0;
65-
66-
mixAmount *= 0.08;
67-
68-
mixAmount = 0.0;
69-
70-
sample = (sample * (1.0 - mixAmount))
71-
+ (DistortionFunctions::hyperbolicTangentSoftClipping (sample) * mixAmount);
72-
*/
73-
sample = crushToNBit (sample, bitDepth);
74-
sample /= -std::pow (-2.0, (double) bitDepth - 1.0);
75-
return sample;
53+
// Don't check if programIndex is already set;
54+
// doing this allows "resetting" to the program.
55+
if (isPositiveAndBelow (index, (int) presets.size()))
56+
setCurrentProgramDirectly (index);
7657
}
7758

78-
//==============================================================================
79-
BitCrusherProcessor::BitCrusherProcessor()
59+
const String BitCrusherProcessor::getProgramName (int index)
8060
{
81-
AudioProcessor::addParameter (bitDepth);
61+
if (isPositiveAndBelow (index, (int) presets.size()))
62+
return TRANS (presets[index].name);
63+
64+
return {};
8265
}
8366

8467
//==============================================================================
85-
void BitCrusherProcessor::setBitDepth (int newBitDepth)
68+
void BitCrusherProcessor::prepareToPlay (const double sampleRate, const int estimatedSamplesPerBlock)
8669
{
87-
bitDepth->operator= (newBitDepth);
88-
}
70+
InternalProcessor::prepareToPlay (sampleRate, estimatedSamplesPerBlock);
8971

90-
int BitCrusherProcessor::getBitDepth() const noexcept
91-
{
92-
return bitDepth->get();
72+
const auto tc = jmax (2, getTotalNumInputChannels(), getTotalNumOutputChannels());
73+
floatStates.resize (tc);
74+
doubleStates.resize (tc);
9375
}
9476

77+
void BitCrusherProcessor::setBitDepth (int v) { bitDepthParam->operator= ((float) v); }
78+
int BitCrusherProcessor::getBitDepth() const noexcept { return (int) bitDepthParam->get(); }
79+
void BitCrusherProcessor::setDownsampleFactor (int v) { downsampleFactorParam->operator= ((float) v); }
80+
int BitCrusherProcessor::getDownsampleFactor() const noexcept { return (int) downsampleFactorParam->get(); }
81+
void BitCrusherProcessor::setDrive (float v) { driveParam->operator= (v); }
82+
float BitCrusherProcessor::getDrive() const noexcept { return driveParam->get(); }
83+
9584
//==============================================================================
96-
void BitCrusherProcessor::processBlock (juce::AudioBuffer<float>& buffer, MidiBuffer&) { process (buffer); }
97-
void BitCrusherProcessor::processBlock (juce::AudioBuffer<double>& buffer, MidiBuffer&) { process (buffer); }
85+
void BitCrusherProcessor::processBlock (juce::AudioBuffer<float>& buffer, MidiBuffer&) { process<float> (buffer, floatStates); }
86+
void BitCrusherProcessor::processBlock (juce::AudioBuffer<double>& buffer, MidiBuffer&) { process<double> (buffer, doubleStates); }
9887

9988
template<typename FloatType>
100-
void BitCrusherProcessor::process (juce::AudioBuffer<FloatType>& buffer)
89+
FloatType BitCrusherProcessor::crush (FloatType sample, ChannelState<FloatType>& state,
90+
FloatType levels, FloatType makeup,
91+
FloatType drive, int dsFactor)
10192
{
102-
if (isBypassed())
103-
return;
93+
// Pre-gain + saturation
94+
sample = juce::dsp::FastMathApproximations::tanh (sample * drive);
10495

105-
const auto localBitDepth = bitDepth->get();
96+
// Sample-rate reduction (ZOH)
97+
if (--state.holdCounter <= 0)
98+
{
99+
state.heldSample = sample;
100+
state.holdCounter = dsFactor; // precomputed int >= 1
101+
}
102+
103+
// Bit depth reduction
104+
// levels = 1 << bits
105+
sample = std::round (state.heldSample * levels) / levels;
106106

107-
if (buffer.hasBeenCleared() || localBitDepth >= 16)
107+
// Makeup gain
108+
sample *= makeup; // 1 / sqrt(drive)
109+
110+
return sample;
111+
}
112+
113+
template<typename FloatType>
114+
void BitCrusherProcessor::process (juce::AudioBuffer<FloatType>& buffer, Array<ChannelState<FloatType>>& states)
115+
{
116+
const auto numChannels = buffer.getNumChannels();
117+
const auto numSamples = buffer.getNumSamples();
118+
if (isBypassed()
119+
|| numChannels <= 0
120+
|| numSamples <= 0
121+
|| buffer.hasBeenCleared())
108122
return; // Nothing to do here.
109123

110-
for (auto channel : AudioBufferView (buffer))
111-
for (auto& sample : channel)
112-
sample = (FloatType) crushBit ((double) sample, localBitDepth);
124+
const auto localBitDepth = getBitDepth();
125+
const auto localDrive = (FloatType) getDrive();
126+
const auto localDownsampleFactor = getDownsampleFactor();
127+
const auto levels = FloatType (1 << localBitDepth);
128+
const auto makeup = FloatType (1) / std::sqrt (localDrive);
129+
130+
auto chans = buffer.getArrayOfWritePointers();
131+
132+
for (int i = 0; i < numChannels; ++i)
133+
{
134+
if (auto chan = chans[i])
135+
{
136+
auto& state = states.getReference (i);
137+
for (int f = 0; f < numSamples; ++f)
138+
chan[f] = crush (chan[f], state, levels, makeup, localDrive, localDownsampleFactor);
139+
}
140+
}
113141
}
142+

modules/squarepine_audio/effects/squarepine_BitCrusherProcessor.h

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,23 @@ class BitCrusherProcessor final : public InternalProcessor
77

88
//==============================================================================
99
/** */
10-
void setBitDepth (int newBitDepth);
10+
void setBitDepth (int);
1111

1212
/** */
1313
[[nodiscard]] int getBitDepth() const noexcept;
1414

15+
/** */
16+
void setDownsampleFactor (int);
17+
18+
/** */
19+
[[nodiscard]] int getDownsampleFactor() const noexcept;
20+
21+
/** */
22+
void setDrive (float);
23+
24+
/** */
25+
[[nodiscard]] float getDrive() const noexcept;
26+
1527
//==============================================================================
1628
/** @internal */
1729
const String getName() const override { return NEEDS_TRANS ("Bit Crusher"); }
@@ -20,17 +32,76 @@ class BitCrusherProcessor final : public InternalProcessor
2032
/** @internal */
2133
bool supportsDoublePrecisionProcessing() const override { return true; }
2234
/** @internal */
35+
void prepareToPlay (double, int) override;
36+
/** @internal */
2337
void processBlock (juce::AudioBuffer<float>&, MidiBuffer&) override;
2438
/** @internal */
2539
void processBlock (juce::AudioBuffer<double>&, MidiBuffer&) override;
2640

41+
/** @internal */
42+
int getNumPrograms() override;
43+
/** @internal */
44+
int getCurrentProgram() override;
45+
/** @internal */
46+
void setCurrentProgram (int) override;
47+
/** @internal */
48+
const String getProgramName (int) override;
49+
/** @internal */
50+
void changeProgramName (int, const String&) override {}
51+
2752
private:
2853
//==============================================================================
29-
AudioParameterInt* bitDepth = new AudioParameterInt (ParameterID ("bitDepth", 1), "Bit-Depth", 1, 16, 8);
54+
struct Preset final
55+
{
56+
Preset() = default;
57+
58+
Preset (const String& s, float b, float ds, float d) :
59+
name (s),
60+
bitDepth (b),
61+
downsample (ds),
62+
drive (d)
63+
{
64+
}
65+
66+
Preset (const Preset&) = default;
67+
Preset (Preset&&) = default;
68+
~Preset() = default;
69+
Preset& operator= (const Preset&) = default;
70+
Preset& operator= (Preset&&) = default;
71+
72+
String name;
73+
float bitDepth = {},
74+
downsample = {},
75+
drive = {};
76+
};
77+
78+
template<typename FloatType>
79+
struct ChannelState
80+
{
81+
int holdCounter = 0;
82+
FloatType heldSample = {};
83+
};
84+
85+
int programIndex = 0;
86+
std::vector<Preset> presets;
87+
88+
AudioParameterFloat* bitDepthParam = nullptr;
89+
AudioParameterFloat* downsampleFactorParam = nullptr;
90+
AudioParameterFloat* driveParam = nullptr;
91+
92+
Array<ChannelState<float>> floatStates;
93+
Array<ChannelState<double>> doubleStates;
3094

3195
//==============================================================================
96+
void setCurrentProgramDirectly (int);
97+
98+
template<typename FloatType>
99+
static FloatType crush (FloatType sample, ChannelState<FloatType>& state,
100+
FloatType levels, FloatType makeup,
101+
FloatType drive, int dsFactor);
102+
32103
template<typename FloatType>
33-
void process (juce::AudioBuffer<FloatType>&);
104+
void process (juce::AudioBuffer<FloatType>& buffer, Array<ChannelState<FloatType>>& states);
34105

35106
//==============================================================================
36107
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BitCrusherProcessor)

0 commit comments

Comments
 (0)