Support enabling and disabling MIDI

pull/170/head
James Ball 2023-09-07 22:04:08 +01:00
rodzic 596771f625
commit fca62d009f
7 zmienionych plików z 83 dodań i 20 usunięć

Wyświetl plik

@ -4,6 +4,10 @@
MidiComponent::MidiComponent(OscirenderAudioProcessor& p, OscirenderAudioProcessorEditor& editor) : audioProcessor(p), pluginEditor(editor) {
addAndMakeVisible(midiToggle);
addAndMakeVisible(keyboard);
midiToggle.onClick = [this]() {
audioProcessor.midiEnabled->setBoolValueNotifyingHost(midiToggle.getToggleState());
};
}

Wyświetl plik

@ -131,6 +131,7 @@ OscirenderAudioProcessor::OscirenderAudioProcessor()
booleanParameters.push_back(perspectiveEffect->fixedRotateX);
booleanParameters.push_back(perspectiveEffect->fixedRotateY);
booleanParameters.push_back(perspectiveEffect->fixedRotateZ);
booleanParameters.push_back(midiEnabled);
for (auto parameter : booleanParameters) {
addParameter(parameter);
@ -372,22 +373,29 @@ void OscirenderAudioProcessor::openFile(int index) {
// used ONLY for changing the current file to an EXISTING file.
// much faster than openFile(int index) because it doesn't reparse any files.
// parsersLock AND effectsLock must be locked before calling this function
// TODO: This should change whatever the ShapeSound is to the new index
void OscirenderAudioProcessor::changeCurrentFile(int index) {
if (index == -1) {
currentFile = -1;
changeSound(defaultSound);
}
if (index < 0 || index >= fileBlocks.size()) {
return;
}
synth.clearSounds();
synth.addSound(sounds[index]);
changeSound(sounds[index]);
currentFile = index;
updateLuaValues();
updateObjValues();
}
void OscirenderAudioProcessor::changeSound(ShapeSound::Ptr sound) {
synth.clearSounds();
synth.addSound(sound);
for (int i = 0; i < synth.getNumVoices(); i++) {
auto voice = dynamic_cast<ShapeVoice*>(synth.getVoice(i));
voice->updateSound(sound.get());
}
}
int OscirenderAudioProcessor::getCurrentFileIndex() {
return currentFile;
}
@ -418,7 +426,30 @@ void OscirenderAudioProcessor::processBlock(juce::AudioBuffer<float>& buffer, ju
auto totalNumOutputChannels = getTotalNumOutputChannels();
buffer.clear();
synth.renderNextBlock(buffer, midiMessages, 0, buffer.getNumSamples());
bool usingMidi = midiEnabled->getBoolValue();
if (!usingMidi) {
midiMessages.clear();
}
// if midi enabled has changed state
if (prevMidiEnabled != usingMidi) {
for (int i = 1; i <= 16; i++) {
midiMessages.addEvent(juce::MidiMessage::allNotesOff(i), i);
}
}
// if midi has just been disabled
if (prevMidiEnabled && !usingMidi) {
midiMessages.addEvent(juce::MidiMessage::noteOn(1, 60, 1.0f), 17);
}
prevMidiEnabled = usingMidi;
{
juce::SpinLock::ScopedLockType lock1(parsersLock);
juce::SpinLock::ScopedLockType lock2(effectsLock);
synth.renderNextBlock(buffer, midiMessages, 0, buffer.getNumSamples());
}
midiMessages.clear();
auto* channelData = buffer.getArrayOfWritePointers();

Wyświetl plik

@ -61,6 +61,7 @@ public:
std::shared_ptr<BufferConsumer> consumerRegister(std::vector<float>& buffer);
void consumerStop(std::shared_ptr<BufferConsumer> consumer);
void consumerRead(std::shared_ptr<BufferConsumer> consumer);
void setMidiEnabled(bool enabled);
int VERSION_HINT = 1;
@ -175,6 +176,9 @@ public:
std::shared_ptr<DelayEffect> delayEffect = std::make_shared<DelayEffect>();
std::shared_ptr<PerspectiveEffect> perspectiveEffect = std::make_shared<PerspectiveEffect>(VERSION_HINT);
BooleanParameter* midiEnabled = new BooleanParameter("MIDI Enabled", "midiEnabled", VERSION_HINT, false);
std::atomic<float> frequency = 440.0f;
juce::SpinLock parsersLock;
std::vector<std::shared_ptr<FileParser>> parsers;
std::vector<ShapeSound::Ptr> sounds;
@ -184,6 +188,11 @@ public:
juce::ChangeBroadcaster broadcaster;
private:
juce::SpinLock consumerLock;
std::vector<std::shared_ptr<BufferConsumer>> consumers;
public:
PitchDetector pitchDetector{*this};
std::shared_ptr<WobbleEffect> wobbleEffect = std::make_shared<WobbleEffect>(pitchDetector);
@ -210,9 +219,10 @@ public:
juce::String getFileName(int index);
std::shared_ptr<juce::MemoryBlock> getFileBlock(int index);
private:
std::atomic<float> frequency = 440.0f;
std::atomic<double> volume = 1.0;
std::atomic<double> threshold = 1.0;
bool prevMidiEnabled = !midiEnabled->getBoolValue();
std::vector<BooleanParameter*> booleanParameters;
std::vector<std::shared_ptr<Effect>> allEffects;
@ -220,9 +230,6 @@ private:
ShapeSound::Ptr defaultSound = new ShapeSound(std::make_shared<FileParser>());
juce::Synthesiser synth;
juce::SpinLock consumerLock;
std::vector<std::shared_ptr<BufferConsumer>> consumers;
AudioWebSocketServer softwareOscilloscopeServer{*this};
@ -234,6 +241,7 @@ private:
std::pair<std::shared_ptr<Effect>, EffectParameter*> effectFromLegacyId(const juce::String& id, bool updatePrecedence = false);
LfoType lfoTypeFromLegacyAnimationType(const juce::String& type);
double valueFromLegacy(double value, const juce::String& id);
void changeSound(ShapeSound::Ptr sound);
const double MIN_LENGTH_INCREMENT = 0.000001;

Wyświetl plik

@ -13,15 +13,18 @@ bool ShapeVoice::canPlaySound(juce::SynthesiserSound* sound) {
void ShapeVoice::startNote(int midiNoteNumber, float velocity, juce::SynthesiserSound* sound, int currentPitchWheelPosition) {
auto* shapeSound = dynamic_cast<ShapeSound*>(sound);
currentlyPlaying = true;
this->sound = shapeSound;
if (shapeSound != nullptr) {
this->sound = shapeSound;
int tries = 0;
while (frame.empty() && tries < 20) {
while (frame.empty() && tries < 50) {
frameLength = shapeSound->updateFrame(frame);
tries++;
}
tailOff = 0.0;
frequency = juce::MidiMessage::getMidiNoteInHertz(midiNoteNumber);
if (audioProcessor.midiEnabled->getBoolValue()) {
frequency = juce::MidiMessage::getMidiNoteInHertz(midiNoteNumber);
}
}
}
@ -50,10 +53,22 @@ void ShapeVoice::incrementShapeDrawing() {
}
}
// should be called if the current file is changed so that we interrupt
// any currently playing sounds / voices
void ShapeVoice::updateSound(juce::SynthesiserSound* sound) {
if (currentlyPlaying) {
this->sound = dynamic_cast<ShapeSound*>(sound);
}
}
void ShapeVoice::renderNextBlock(juce::AudioSampleBuffer& outputBuffer, int startSample, int numSamples) {
juce::ScopedNoDenormals noDenormals;
int numChannels = outputBuffer.getNumChannels();
if (!audioProcessor.midiEnabled->getBoolValue()) {
frequency = audioProcessor.frequency;
}
for (auto sample = startSample; sample < startSample + numSamples; ++sample) {
bool traceMinEnabled = audioProcessor.traceMin->enabled->getBoolValue();
@ -72,11 +87,11 @@ void ShapeVoice::renderNextBlock(juce::AudioSampleBuffer& outputBuffer, int star
bool renderingSample = true;
if (sound != nullptr) {
renderingSample = sound->parser->isSample();
if (sound.load() != nullptr) {
renderingSample = sound.load()->parser->isSample();
if (renderingSample) {
channels = sound->parser->nextSample();
channels = sound.load()->parser->nextSample();
} else if (currentShape < frame.size()) {
auto& shape = frame[currentShape];
double length = shape->length();
@ -119,8 +134,8 @@ void ShapeVoice::renderNextBlock(juce::AudioSampleBuffer& outputBuffer, int star
double drawnFrameLength = traceMaxEnabled ? actualTraceMax * frameLength : frameLength;
if (!renderingSample && frameDrawn >= drawnFrameLength) {
if (sound != nullptr) {
frameLength = sound->updateFrame(frame);
if (sound.load() != nullptr) {
frameLength = sound.load()->updateFrame(frame);
}
// TODO: updateFrame already iterates over all the shapes,
// so we can improve performance by calculating frameDrawn
@ -137,6 +152,7 @@ void ShapeVoice::renderNextBlock(juce::AudioSampleBuffer& outputBuffer, int star
}
void ShapeVoice::stopNote(float velocity, bool allowTailOff) {
currentlyPlaying = false;
if (allowTailOff) {
if (tailOff == 0.0) {
tailOff = 1.0;

Wyświetl plik

@ -9,6 +9,7 @@ public:
bool canPlaySound(juce::SynthesiserSound* sound) override;
void startNote(int midiNoteNumber, float velocity, juce::SynthesiserSound* sound, int currentPitchWheelPosition) override;
void updateSound(juce::SynthesiserSound* sound);
void renderNextBlock(juce::AudioSampleBuffer& outputBuffer, int startSample, int numSamples) override;
void stopNote(float velocity, bool allowTailOff) override;
void pitchWheelMoved(int newPitchWheelValue) override;
@ -22,7 +23,7 @@ private:
OscirenderAudioProcessor& audioProcessor;
std::vector<std::unique_ptr<Shape>> frame;
ShapeSound* sound = nullptr;
std::atomic<ShapeSound*> sound = nullptr;
double actualTraceMin;
double actualTraceMax;
@ -32,6 +33,7 @@ private:
double frameDrawn = 0.0;
double lengthIncrement = 0.0;
bool currentlyPlaying = false;
double tailOff = 0.0;
double frequency = 1.0;
};
};

Wyświetl plik

@ -27,7 +27,8 @@ int LuaListBoxModel::getNumRows() {
void LuaListBoxModel::paintListBoxItem(int rowNumber, juce::Graphics& g, int width, int height, bool rowIsSelected) {}
juce::Component* LuaListBoxModel::refreshComponentForRow(int rowNum, bool isRowSelected, juce::Component *existingComponentToUpdate) {
juce::SpinLock::ScopedLockType lock(audioProcessor.effectsLock);
// TODO: We should REALLY be locking here but it causes a deadlock :( works fine without.....
// juce::SpinLock::ScopedLockType lock1(audioProcessor.effectsLock);
std::unique_ptr<LuaListComponent> item(dynamic_cast<LuaListComponent*>(existingComponentToUpdate));
if (juce::isPositiveAndBelow(rowNum, getNumRows())) {
item = std::make_unique<LuaListComponent>(audioProcessor, *audioProcessor.luaEffects[rowNum]);

Wyświetl plik

@ -56,6 +56,7 @@ void CircleArc::translate(double x, double y) {
double CircleArc::length() {
if (len < 0) {
len = 0;
// TODO: Replace this, it's stupid. Do a real approximation.
int segments = 5;
for (int i = 0; i < segments; i++) {
Vector2 v1 = nextVector(i / (double) segments);