kopia lustrzana https://github.com/jameshball/osci-render
Support enabling and disabling MIDI
rodzic
596771f625
commit
fca62d009f
|
@ -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());
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -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]);
|
||||
|
|
|
@ -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);
|
||||
|
|
Ładowanie…
Reference in New Issue