#include "ShapeVoice.h" #include "../PluginProcessor.h" ShapeVoice::ShapeVoice(OscirenderAudioProcessor& p) : audioProcessor(p) { actualTraceMin = audioProcessor.traceMin->getValue(); actualTraceMax = audioProcessor.traceMax->getValue(); } bool ShapeVoice::canPlaySound(juce::SynthesiserSound* sound) { return dynamic_cast (sound) != nullptr; } void ShapeVoice::startNote(int midiNoteNumber, float velocity, juce::SynthesiserSound* sound, int currentPitchWheelPosition) { this->velocity = velocity; pitchWheelMoved(currentPitchWheelPosition); auto* shapeSound = dynamic_cast(sound); currentlyPlaying = true; this->sound = shapeSound; if (shapeSound != nullptr) { int tries = 0; while (frame.empty() && tries < 50) { frameLength = shapeSound->updateFrame(frame); tries++; } adsr = audioProcessor.adsrEnv; time = 0.0; releaseTime = 0.0; endTime = 0.0; waitingForRelease = true; std::vector times = adsr.getTimes(); for (int i = 0; i < times.size(); i++) { if (i < adsr.getReleaseNode()) { releaseTime += times[i]; } endTime += times[i]; } if (audioProcessor.midiEnabled->getBoolValue()) { frequency = juce::MidiMessage::getMidiNoteInHertz(midiNoteNumber); } } } // TODO this is the slowest part of the program - any way to improve this would help! void ShapeVoice::incrementShapeDrawing() { double length = currentShape < frame.size() ? frame[currentShape]->len : 0.0; frameDrawn += lengthIncrement; shapeDrawn += lengthIncrement; // Need to skip all shapes that the lengthIncrement draws over. // This is especially an issue when there are lots of small lines being // drawn. while (shapeDrawn > length) { shapeDrawn -= length; currentShape++; if (currentShape >= frame.size()) { currentShape = 0; break; } // POTENTIAL TODO: Think of a way to make this more efficient when iterating // this loop many times length = frame[currentShape]->len; } } double ShapeVoice::getFrequency() { return actualFrequency; } // 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(sound); } } void ShapeVoice::renderNextBlock(juce::AudioSampleBuffer& outputBuffer, int startSample, int numSamples) { juce::ScopedNoDenormals noDenormals; int numChannels = outputBuffer.getNumChannels(); if (audioProcessor.midiEnabled->getBoolValue()) { actualFrequency = frequency * pitchWheelAdjustment; } else { actualFrequency = audioProcessor.frequency; } for (auto sample = startSample; sample < startSample + numSamples; ++sample) { bool traceMinEnabled = audioProcessor.traceMin->enabled->getBoolValue(); bool traceMaxEnabled = audioProcessor.traceMax->enabled->getBoolValue(); // update length increment double traceMax = traceMaxEnabled ? actualTraceMax : 1.0; double traceMin = traceMinEnabled ? actualTraceMin : 0.0; double proportionalLength = (traceMax - traceMin) * frameLength; lengthIncrement = juce::jmax(proportionalLength / (audioProcessor.currentSampleRate / actualFrequency), MIN_LENGTH_INCREMENT); Point channels; double x = 0.0; double y = 0.0; double z = 0.0; bool renderingSample = true; if (sound.load() != nullptr) { auto parser = sound.load()->parser; renderingSample = parser != nullptr && parser->isSample(); if (renderingSample) { vars.sampleRate = audioProcessor.currentSampleRate; vars.frequency = actualFrequency; std::copy(std::begin(audioProcessor.luaValues), std::end(audioProcessor.luaValues), std::begin(vars.sliders)); channels = parser->nextSample(L, vars); } else if (currentShape < frame.size()) { auto& shape = frame[currentShape]; double length = shape->length(); double drawingProgress = length == 0.0 ? 1 : shapeDrawn / length; channels = shape->nextVector(drawingProgress); } } x = channels.x; y = channels.y; z = channels.z; time += 1.0 / audioProcessor.currentSampleRate; if (waitingForRelease) { time = juce::jmin(time, releaseTime); } else if (time >= endTime) { noteStopped(); break; } double gain = audioProcessor.midiEnabled->getBoolValue() ? adsr.lookup(time) : 1.0; gain *= velocity; if (numChannels >= 3) { outputBuffer.addSample(0, sample, x * gain); outputBuffer.addSample(1, sample, y * gain); outputBuffer.addSample(2, sample, z * gain); } else if (numChannels == 2) { outputBuffer.addSample(0, sample, x * gain); outputBuffer.addSample(1, sample, y * gain); } else if (numChannels == 1) { outputBuffer.addSample(0, sample, x * gain); } double traceMinValue = audioProcessor.traceMin->getActualValue(); double traceMaxValue = audioProcessor.traceMax->getActualValue(); traceMaxValue = traceMaxEnabled ? traceMaxValue : 1.0; traceMinValue = traceMinEnabled ? traceMinValue : 0.0; actualTraceMax = juce::jmax(actualTraceMin, juce::jmin(traceMaxValue, 1.0)); actualTraceMin = juce::jmax(MIN_TRACE, juce::jmin(traceMinValue, actualTraceMax - MIN_TRACE)); if (!renderingSample) { incrementShapeDrawing(); } double drawnFrameLength = traceMaxEnabled ? actualTraceMax * frameLength : frameLength; if (!renderingSample && frameDrawn >= drawnFrameLength) { if (sound.load() != nullptr && currentlyPlaying) { frameLength = sound.load()->updateFrame(frame); } frameDrawn -= drawnFrameLength; currentShape = 0; // TODO: updateFrame already iterates over all the shapes, // so we can improve performance by calculating frameDrawn // and shapeDrawn directly. frameDrawn is simply actualTraceMin * frameLength // but shapeDrawn is the amount of the current shape that has been drawn so // we need to iterate over all the shapes to calculate it. if (traceMinEnabled) { while (frameDrawn < actualTraceMin * frameLength) { incrementShapeDrawing(); } } } } } void ShapeVoice::stopNote(float velocity, bool allowTailOff) { currentlyPlaying = false; waitingForRelease = false; if (!allowTailOff) { noteStopped(); } } void ShapeVoice::noteStopped() { clearCurrentNote(); sound = nullptr; } void ShapeVoice::pitchWheelMoved(int newPitchWheelValue) { pitchWheelAdjustment = 1.0 + (newPitchWheelValue - 8192.0) / 65536.0; } void ShapeVoice::controllerMoved(int controllerNumber, int newControllerValue) {}