kopia lustrzana https://github.com/jameshball/osci-render
203 wiersze
7.2 KiB
C++
203 wiersze
7.2 KiB
C++
#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<ShapeSound*> (sound) != nullptr;
|
|
}
|
|
|
|
void ShapeVoice::startNote(int midiNoteNumber, float velocity, juce::SynthesiserSound* sound, int currentPitchWheelPosition) {
|
|
this->velocity = velocity;
|
|
pitchWheelMoved(currentPitchWheelPosition);
|
|
auto* shapeSound = dynamic_cast<ShapeSound*>(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<double> 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<ShapeSound*>(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);
|
|
|
|
OsciPoint 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) {}
|