Hello all,
I’m developing a sequencer / plugin host app, which means that I don’t spend much time writing DSP code. I have a basic understanding of the Synthesizer
and Voice
classes, but haven’t invested much time into it as it’s not where the main challenges of my program lie. The only part where I need to delve into DSP code is for a small default sine wave plugin, which is basically just there so that the user can get some sound up and running immediately without having to add their own 3rd party plugins.
For this I’ve built something basic using the juce tutorials as a starting point, but it’s not really working as expected and I can’t figure out why. It’s probably a really trivial problem for a plugin developer, so I wonder if someone could help?
The main problem that I’m facing is with polyphony. It’s essential that the plugin can play many notes at one time (say around 16). I created 16 voices and did setNoteStealingEnabled(true)
, but still the notes seem to max out at 3 or 4 simultaneously playing notes, after which some notes are dropped any everything seems to distort. I’ve played around with it, but I can’t seem to fix this.
The second problem is that it seems to be out of tune! Midi note 60 is not middle C any more, but seems instead to be somewhere around D. This is bound to be something really simple, but I can’t figure it out.
Here is my code:
#pragma once
#include <JuceHeader.h>
class SineWaveSynth : public juce::AudioProcessor
{
public:
SineWaveSynth()
: juce::AudioProcessor(BusesProperties().withOutput("Output", juce::AudioChannelSet::stereo()))
{
synth.setNoteStealingEnabled(true);
static constexpr int numVoices = 16;
// Add some voices...
for (int i = 0; i < numVoices; ++i)
synth.addVoice(new SineWaveVoice());
// ..and give the synth a sound to play
synth.addSound(new SineWaveSound());
}
static String getIdentifier()
{
return "Sine Wave Synth";
}
//==============================================================================
void prepareToPlay(double newSampleRate, int) override
{
synth.setCurrentPlaybackSampleRate(newSampleRate);
}
void releaseResources() override {}
//==============================================================================
void processBlock(juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midiMessages) override
{
processBlockTemplate<float>(buffer, midiMessages);
}
void processBlock(juce::AudioBuffer<double>& buffer, juce::MidiBuffer& midiMessages) override
{
processBlockTemplate<double>(buffer, midiMessages);
}
const juce::String getName() const override { return getIdentifier(); }
double getTailLengthSeconds() const override { return 0.0; }
bool acceptsMidi() const override { return true; }
bool producesMidi() const override { return true; }
juce::AudioProcessorEditor* createEditor() override { return nullptr; }
bool hasEditor() const override { return false; }
int getNumPrograms() override { return 1; }
int getCurrentProgram() override { return 0; }
void setCurrentProgram(int) override {}
const juce::String getProgramName(int) override { return {}; }
void changeProgramName(int, const juce::String&) override {}
void getStateInformation(juce::MemoryBlock&) override {}
void setStateInformation(const void*, int) override {}
bool canAddBus(bool b) const override { return true; }
bool canRemoveBus(bool b) const override { return true; }
private:
//==============================================================================
template<typename T>
void processBlockTemplate(juce::AudioBuffer<T>& buffer, juce::MidiBuffer& midiMessages)
{
const int numSamples = buffer.getNumSamples();
buffer.clear();
synth.renderNextBlock(buffer, midiMessages, 0, numSamples);
buffer.applyGain(0.8f);
}
struct SineWaveSound : public juce::SynthesiserSound
{
SineWaveSound() = default;
bool appliesToNote(int /*midiNoteNumber*/) override { return true; }
bool appliesToChannel(int /*midiChannel*/) override { return true; }
};
struct SineWaveVoice : public juce::SynthesiserVoice
{
SineWaveVoice() = default;
bool canPlaySound(juce::SynthesiserSound* sound) override
{
return dynamic_cast<SineWaveSound*> (sound) != nullptr;
}
void startNote(int midiNoteNumber, float velocity,
juce::SynthesiserSound* /*sound*/,
int /*currentPitchWheelPosition*/) override
{
currentAngle = 0.0;
level = velocity * 0.15;
env.noteOn();
double cyclesPerSecond = MidiMessage::getMidiNoteInHertz(midiNoteNumber);
double cyclesPerSample = cyclesPerSecond / getSampleRate();
angleDelta = cyclesPerSample * 2.0 * MathConstants<double>::pi;
}
void stopNote(float /*velocity*/, bool allowTailOff) override
{
if (allowTailOff)
env.noteOff();
else
{
clearCurrentNote();
env.reset();
}
}
void pitchWheelMoved(int /*newValue*/) override
{
}
void controllerMoved(int /*controllerNumber*/, int /*newValue*/) override
{
}
void setCurrentPlaybackSampleRate(double newRate) override
{
if (newRate > 0.0)
{
env.setSampleRate(newRate);
env.setParameters(juce::ADSR::Parameters(0.001f, 0.1f, 0.5f, 0.6f));
}
}
void renderNextBlock(juce::AudioBuffer<float>& outputBuffer, int startSample, int numSamples) override
{
const int origStart = startSample, origNum = numSamples;
if (angleDelta != 0.0)
{
while (--numSamples >= 0)
{
const float currentSample = (float)(sin(currentAngle) * level * env.getNextSample());
for (int i = outputBuffer.getNumChannels(); --i >= 0;)
outputBuffer.addSample(i, startSample, currentSample);
currentAngle += angleDelta;
++startSample;
}
env.applyEnvelopeToBuffer(outputBuffer, origStart, origNum);
if (!env.isActive())
{
clearCurrentNote();
angleDelta = 0.0;
}
}
}
using juce::SynthesiserVoice::renderNextBlock;
private:
double currentAngle = 0, angleDelta = 0, level = 0;
juce::ADSR env;
};
//==============================================================================
juce::Synthesiser synth;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(SineWaveSynth)
};
Instead of digging through the code and trying to fix things, I’d also be happy just to copy code from any other open source project, if someone here is kind enough to share. My only requirements are that it’s a simple sine wave synth which allows for polyphony, and has basically 0 parameters. It seems like a lot of people must have made this before, so why re-invent the wheel?