osci-render/Source/audio/ShapeVoice.cpp

212 wiersze
7.4 KiB
C++
Czysty Zwykły widok Historia

2023-08-28 21:06:21 +00:00
#include "ShapeVoice.h"
#include "../PluginProcessor.h"
ShapeVoice::ShapeVoice(OscirenderAudioProcessor& p) : audioProcessor(p) {
2025-01-19 18:35:46 +00:00
actualTraceStart = audioProcessor.trace->getValue(0);
actualTraceLength = audioProcessor.trace->getValue(1);
2023-08-28 21:06:21 +00:00
}
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;
2024-01-21 18:50:46 +00:00
pitchWheelMoved(currentPitchWheelPosition);
2023-08-28 21:06:21 +00:00
auto* shapeSound = dynamic_cast<ShapeSound*>(sound);
2023-09-07 21:04:08 +00:00
currentlyPlaying = true;
this->sound = shapeSound;
2023-08-28 21:06:21 +00:00
if (shapeSound != nullptr) {
int tries = 0;
2023-09-07 21:04:08 +00:00
while (frame.empty() && tries < 50) {
2023-08-28 21:06:21 +00:00
frameLength = shapeSound->updateFrame(frame);
tries++;
2023-08-28 21:06:21 +00:00
}
2023-11-25 17:57:35 +00:00
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];
}
2023-09-07 21:04:08 +00:00
if (audioProcessor.midiEnabled->getBoolValue()) {
frequency = juce::MidiMessage::getMidiNoteInHertz(midiNoteNumber);
}
2023-08-28 21:06:21 +00:00
}
}
// TODO this is the slowest part of the program - any way to improve this would help!
void ShapeVoice::incrementShapeDrawing() {
2025-01-21 21:34:25 +00:00
if (frame.size() <= 0) return;
2023-08-28 21:06:21 +00:00
double length = currentShape < frame.size() ? frame[currentShape]->len : 0.0;
frameDrawn += lengthIncrement;
shapeDrawn += lengthIncrement;
2023-08-28 21:06:21 +00:00
// 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;
}
// 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;
}
2023-09-07 21:04:08 +00:00
// 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);
}
}
2023-08-28 21:06:21 +00:00
void ShapeVoice::renderNextBlock(juce::AudioSampleBuffer& outputBuffer, int startSample, int numSamples) {
juce::ScopedNoDenormals noDenormals;
int numChannels = outputBuffer.getNumChannels();
2024-01-07 16:00:43 +00:00
if (audioProcessor.midiEnabled->getBoolValue()) {
actualFrequency = frequency * pitchWheelAdjustment;
} else {
2024-01-21 18:50:46 +00:00
actualFrequency = audioProcessor.frequency;
2023-09-07 21:04:08 +00:00
}
2023-08-28 21:06:21 +00:00
for (auto sample = startSample; sample < startSample + numSamples; ++sample) {
2025-01-19 18:35:46 +00:00
bool traceEnabled = audioProcessor.trace->enabled->getBoolValue();
2023-08-28 21:06:21 +00:00
// update length increment
2025-01-19 18:35:46 +00:00
double traceLen = traceEnabled ? actualTraceLength : 1.0;
double traceMin = traceEnabled ? actualTraceStart : 0.0;
2025-01-17 15:19:55 +00:00
double proportionalLength = std::max(0.001, traceLen) * frameLength;
2024-01-21 18:50:46 +00:00
lengthIncrement = juce::jmax(proportionalLength / (audioProcessor.currentSampleRate / actualFrequency), MIN_LENGTH_INCREMENT);
2023-08-28 21:06:21 +00:00
OsciPoint channels;
2023-08-28 21:06:21 +00:00
double x = 0.0;
double y = 0.0;
double z = 0.0;
2023-08-28 21:06:21 +00:00
bool renderingSample = true;
2023-09-07 21:04:08 +00:00
if (sound.load() != nullptr) {
2023-09-10 16:43:37 +00:00
auto parser = sound.load()->parser;
renderingSample = parser != nullptr && parser->isSample();
2023-08-28 21:06:21 +00:00
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);
2023-08-28 21:06:21 +00:00
} 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;
2023-08-28 21:06:21 +00:00
2023-11-25 17:57:35 +00:00
time += 1.0 / audioProcessor.currentSampleRate;
2023-08-28 21:06:21 +00:00
2023-11-25 17:57:35 +00:00
if (waitingForRelease) {
time = juce::jmin(time, releaseTime);
} else if (time >= endTime) {
2024-01-21 18:50:46 +00:00
noteStopped();
2023-11-25 17:57:35 +00:00
break;
2023-08-28 21:06:21 +00:00
}
2023-11-25 17:57:35 +00:00
double gain = audioProcessor.midiEnabled->getBoolValue() ? adsr.lookup(time) : 1.0;
gain *= velocity;
2023-08-28 21:06:21 +00:00
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) {
2023-08-28 21:06:21 +00:00
outputBuffer.addSample(0, sample, x * gain);
outputBuffer.addSample(1, sample, y * gain);
} else if (numChannels == 1) {
outputBuffer.addSample(0, sample, x * gain);
}
2025-01-19 18:35:46 +00:00
double traceStartValue = audioProcessor.trace->getActualValue(0);
double traceLengthValue = audioProcessor.trace->getActualValue(1);
traceLengthValue = traceEnabled ? traceLengthValue : 1.0;
traceStartValue = traceEnabled ? traceStartValue : 0.0;
actualTraceLength = std::max(0.01, traceLengthValue);
actualTraceStart = traceStartValue;
if (actualTraceStart < 0) {
actualTraceStart = 0;
}
2023-08-28 21:06:21 +00:00
if (!renderingSample) {
incrementShapeDrawing();
}
double drawnFrameLength = frameLength;
bool willLoopOver = false;
2025-01-19 18:35:46 +00:00
if (traceEnabled) {
drawnFrameLength *= actualTraceLength + actualTraceStart;
}
2023-08-28 21:06:21 +00:00
if (!renderingSample && frameDrawn >= drawnFrameLength) {
if (sound.load() != nullptr && currentlyPlaying) {
2023-09-07 21:04:08 +00:00
frameLength = sound.load()->updateFrame(frame);
2023-08-28 21:06:21 +00:00
}
2025-01-19 10:42:16 +00:00
frameDrawn -= drawnFrameLength;
2025-01-19 18:35:46 +00:00
if (traceEnabled) {
shapeDrawn = juce::jlimit(0.0, frame[currentShape]->len, frameDrawn);
2025-01-19 10:42:16 +00:00
}
2024-01-07 16:00:43 +00:00
currentShape = 0;
2023-08-28 21:06:21 +00:00
// TODO: updateFrame already iterates over all the shapes,
// so we can improve performance by calculating frameDrawn
// and shapeDrawn directly. frameDrawn is simply actualTraceStart * frameLength
2023-08-28 21:06:21 +00:00
// 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.
2025-01-19 18:35:46 +00:00
if (traceEnabled) {
while (frameDrawn < actualTraceStart * frameLength) {
2023-08-28 21:06:21 +00:00
incrementShapeDrawing();
}
}
}
}
}
void ShapeVoice::stopNote(float velocity, bool allowTailOff) {
2023-09-07 21:04:08 +00:00
currentlyPlaying = false;
2023-11-25 17:57:35 +00:00
waitingForRelease = false;
if (!allowTailOff) {
2024-01-21 18:50:46 +00:00
noteStopped();
2023-08-28 21:06:21 +00:00
}
}
2024-01-21 18:50:46 +00:00
void ShapeVoice::noteStopped() {
clearCurrentNote();
sound = nullptr;
}
void ShapeVoice::pitchWheelMoved(int newPitchWheelValue) {
pitchWheelAdjustment = 1.0 + (newPitchWheelValue - 8192.0) / 65536.0;
}
2023-08-28 21:06:21 +00:00
void ShapeVoice::controllerMoved(int controllerNumber, int newControllerValue) {}