kopia lustrzana https://github.com/jameshball/osci-render
Merge pull request #32 from jameshball/frequency
Add audio streaming, software oscilloscope, and current frequencypull/170/head
commit
0a922847b4
|
@ -1,5 +1,6 @@
|
|||
#include "EffectsComponent.h"
|
||||
#include "audio/BitCrushEffect.h"
|
||||
#include "PluginEditor.h"
|
||||
|
||||
EffectsComponent::EffectsComponent(OscirenderAudioProcessor& p) : audioProcessor(p), itemData(p), listBoxModel(listBox, itemData) {
|
||||
setText("Audio Effects");
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include "components/DraggableListBox.h"
|
||||
#include "components/EffectsListComponent.h"
|
||||
|
||||
class OscirenderAudioProcessorEditor;
|
||||
class EffectsComponent : public juce::GroupComponent {
|
||||
public:
|
||||
EffectsComponent(OscirenderAudioProcessor&);
|
||||
|
|
|
@ -77,9 +77,18 @@ MainComponent::MainComponent(OscirenderAudioProcessor& p, OscirenderAudioProcess
|
|||
fileName.onReturnKey = [this] {
|
||||
createFile.triggerClick();
|
||||
};
|
||||
|
||||
addAndMakeVisible(visualiser);
|
||||
audioProcessor.audioProducer.registerConsumer(consumer);
|
||||
visualiserProcessor.startThread();
|
||||
|
||||
addAndMakeVisible(frequencyLabel);
|
||||
pitchDetector.startThread();
|
||||
}
|
||||
|
||||
MainComponent::~MainComponent() {
|
||||
audioProcessor.audioProducer.unregisterConsumer(consumer);
|
||||
visualiserProcessor.stopThread(1000);
|
||||
}
|
||||
|
||||
void MainComponent::updateFileLabel() {
|
||||
|
@ -112,4 +121,10 @@ void MainComponent::resized() {
|
|||
fileType.setBounds(row.removeFromLeft(buttonWidth / 2));
|
||||
row.removeFromLeft(rowPadding);
|
||||
createFile.setBounds(row.removeFromLeft(buttonWidth));
|
||||
|
||||
bounds.removeFromTop(padding);
|
||||
frequencyLabel.setBounds(bounds.removeFromTop(20));
|
||||
|
||||
bounds.removeFromTop(padding);
|
||||
visualiser.setBounds(bounds);
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
#include "PluginProcessor.h"
|
||||
#include "parser/FileParser.h"
|
||||
#include "parser/FrameProducer.h"
|
||||
#include "components/VisualiserComponent.h"
|
||||
#include "audio/PitchDetector.h"
|
||||
|
||||
class OscirenderAudioProcessorEditor;
|
||||
class MainComponent : public juce::GroupComponent {
|
||||
|
@ -26,5 +28,20 @@ private:
|
|||
juce::ComboBox fileType;
|
||||
juce::TextButton createFile{"Create File"};
|
||||
|
||||
VisualiserComponent visualiser{2, audioProcessor};
|
||||
std::shared_ptr<BufferConsumer> consumer = std::make_shared<BufferConsumer>(2048);
|
||||
VisualiserProcessor visualiserProcessor{consumer, visualiser};
|
||||
|
||||
juce::Label frequencyLabel;
|
||||
PitchDetector pitchDetector{
|
||||
audioProcessor,
|
||||
[this](float frequency) {
|
||||
// round to nearest integer
|
||||
int roundedFrequency = static_cast<int>(frequency + 0.5f);
|
||||
frequencyLabel.setText(juce::String(roundedFrequency) + "Hz", juce::dontSendNotification);
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MainComponent)
|
||||
};
|
|
@ -87,7 +87,6 @@ ObjComponent::ObjComponent(OscirenderAudioProcessor& p, OscirenderAudioProcessor
|
|||
Util::changeSvgColour(doc.get(), "white");
|
||||
fixedRotateWhite = juce::Drawable::createFromSVG(*doc);
|
||||
Util::changeSvgColour(doc.get(), "red");
|
||||
DBG(doc->toString());
|
||||
fixedRotateRed = juce::Drawable::createFromSVG(*doc);
|
||||
|
||||
// TODO: any way of removing this duplication?
|
||||
|
|
|
@ -1,11 +1,3 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file contains the basic framework code for a JUCE plugin editor.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <JuceHeader.h>
|
||||
|
@ -15,16 +7,12 @@
|
|||
#include "LuaComponent.h"
|
||||
#include "ObjComponent.h"
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
*/
|
||||
class OscirenderAudioProcessorEditor : public juce::AudioProcessorEditor, private juce::CodeDocument::Listener
|
||||
{
|
||||
|
||||
class OscirenderAudioProcessorEditor : public juce::AudioProcessorEditor, private juce::CodeDocument::Listener {
|
||||
public:
|
||||
OscirenderAudioProcessorEditor (OscirenderAudioProcessor&);
|
||||
~OscirenderAudioProcessorEditor() override;
|
||||
|
||||
//==============================================================================
|
||||
void paint (juce::Graphics&) override;
|
||||
void resized() override;
|
||||
|
||||
|
|
|
@ -118,8 +118,7 @@ void OscirenderAudioProcessor::changeProgramName (int index, const juce::String&
|
|||
}
|
||||
|
||||
//==============================================================================
|
||||
void OscirenderAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock)
|
||||
{
|
||||
void OscirenderAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock) {
|
||||
currentSampleRate = sampleRate;
|
||||
updateAngleDelta();
|
||||
}
|
||||
|
@ -449,7 +448,6 @@ void OscirenderAudioProcessor::processBlock (juce::AudioBuffer<float>& buffer, j
|
|||
x = std::max(-1.0, std::min(1.0, x));
|
||||
y = std::max(-1.0, std::min(1.0, y));
|
||||
|
||||
|
||||
if (totalNumOutputChannels >= 2) {
|
||||
channelData[0][sample] = x;
|
||||
channelData[1][sample] = y;
|
||||
|
@ -457,6 +455,8 @@ void OscirenderAudioProcessor::processBlock (juce::AudioBuffer<float>& buffer, j
|
|||
channelData[0][sample] = x;
|
||||
}
|
||||
|
||||
audioProducer.write(x, y);
|
||||
|
||||
actualTraceMax = std::max(actualTraceMin + MIN_TRACE, std::min(traceMax->getValue(), 1.0));
|
||||
actualTraceMin = std::max(MIN_TRACE, std::min(traceMin->getValue(), actualTraceMax - MIN_TRACE));
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include "parser/FrameConsumer.h"
|
||||
#include "audio/Effect.h"
|
||||
#include <numbers>
|
||||
#include "concurrency/BufferProducer.h"
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
|
@ -167,6 +168,8 @@ public:
|
|||
|
||||
std::unique_ptr<FrameProducer> producer;
|
||||
|
||||
BufferProducer audioProducer;
|
||||
|
||||
void addLuaSlider();
|
||||
void updateAngleDelta();
|
||||
void addFrame(std::vector<std::unique_ptr<Shape>> frame, int fileIndex) override;
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
#include "PitchDetector.h"
|
||||
#include "PitchDetector.h"
|
||||
|
||||
PitchDetector::PitchDetector(OscirenderAudioProcessor& p, std::function<void(float)> frequencyCallback) : juce::Thread("PitchDetector"), audioProcessor(p), frequencyCallback(frequencyCallback) {}
|
||||
|
||||
PitchDetector::~PitchDetector() {
|
||||
audioProcessor.audioProducer.unregisterConsumer(consumer);
|
||||
stopThread(1000);
|
||||
}
|
||||
|
||||
void PitchDetector::run() {
|
||||
audioProcessor.audioProducer.registerConsumer(consumer);
|
||||
|
||||
while (!threadShouldExit()) {
|
||||
auto buffer = consumer->startProcessing();
|
||||
|
||||
// buffer is for 2 channels, so we need to only use one
|
||||
for (int i = 0; i < fftSize; i++) {
|
||||
fftData[i] = buffer->at(2 * i);
|
||||
}
|
||||
|
||||
forwardFFT.performFrequencyOnlyForwardTransform(fftData.data());
|
||||
|
||||
// get frequency of the peak
|
||||
int maxIndex = 0;
|
||||
for (int i = 0; i < fftSize / 2; ++i) {
|
||||
if (frequencyFromIndex(i) < 20 || frequencyFromIndex(i) > 20000) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto current = fftData[i];
|
||||
if (current > fftData[maxIndex]) {
|
||||
maxIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
frequency = frequencyFromIndex(maxIndex);
|
||||
|
||||
consumer->finishedProcessing();
|
||||
triggerAsyncUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
void PitchDetector::handleAsyncUpdate() {
|
||||
frequencyCallback(frequency);
|
||||
}
|
||||
|
||||
float PitchDetector::frequencyFromIndex(int index) {
|
||||
auto binWidth = audioProcessor.currentSampleRate / fftSize;
|
||||
return index * binWidth;
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
#pragma once
|
||||
#include <JuceHeader.h>
|
||||
#include "../concurrency/BufferConsumer.h"
|
||||
#include "../PluginProcessor.h"
|
||||
|
||||
class PitchDetector : public juce::Thread, public juce::AsyncUpdater {
|
||||
public:
|
||||
PitchDetector(OscirenderAudioProcessor& p, std::function<void(float)> frequencyCallback);
|
||||
~PitchDetector();
|
||||
|
||||
void run() override;
|
||||
void handleAsyncUpdate() override;
|
||||
|
||||
std::atomic<float> frequency = 0.0f;
|
||||
|
||||
private:
|
||||
static constexpr int fftOrder = 15;
|
||||
static constexpr int fftSize = 1 << fftOrder;
|
||||
|
||||
std::shared_ptr<BufferConsumer> consumer = std::make_shared<BufferConsumer>(fftSize);
|
||||
juce::dsp::FFT forwardFFT{fftOrder};
|
||||
std::array<float, fftSize * 2> fftData;
|
||||
OscirenderAudioProcessor& audioProcessor;
|
||||
std::function<void(float)> frequencyCallback;
|
||||
|
||||
float frequencyFromIndex(int index);
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PitchDetector)
|
||||
};
|
|
@ -0,0 +1,77 @@
|
|||
#include "VisualiserComponent.h"
|
||||
|
||||
VisualiserComponent::VisualiserComponent(int numChannels, OscirenderAudioProcessor& p) : numChannels(numChannels), backgroundColour(juce::Colours::black), waveformColour(juce::Colour(0xff00ff00)), audioProcessor(p) {
|
||||
setOpaque(true);
|
||||
startTimerHz(60);
|
||||
}
|
||||
|
||||
VisualiserComponent::~VisualiserComponent() {}
|
||||
|
||||
void VisualiserComponent::setBuffer(std::vector<float>& newBuffer) {
|
||||
juce::SpinLock::ScopedLockType scope(lock);
|
||||
buffer.clear();
|
||||
for (int i = 0; i < newBuffer.size(); i += precision * numChannels) {
|
||||
buffer.push_back(newBuffer[i]);
|
||||
buffer.push_back(newBuffer[i + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
void VisualiserComponent::setColours(juce::Colour bk, juce::Colour fg) {
|
||||
backgroundColour = bk;
|
||||
waveformColour = fg;
|
||||
}
|
||||
|
||||
void VisualiserComponent::paint(juce::Graphics& g) {
|
||||
g.fillAll(backgroundColour);
|
||||
|
||||
auto r = getLocalBounds().toFloat();
|
||||
auto channelHeight = r.getHeight() / (float)numChannels;
|
||||
|
||||
g.setColour(waveformColour);
|
||||
juce::SpinLock::ScopedLockType scope(lock);
|
||||
paintXY(g, r.removeFromRight(r.getHeight()));
|
||||
|
||||
for (int i = 0; i < numChannels; ++i) {
|
||||
paintChannel(g, r.removeFromTop(channelHeight), i);
|
||||
}
|
||||
}
|
||||
|
||||
void VisualiserComponent::timerCallback() {
|
||||
repaint();
|
||||
}
|
||||
|
||||
void VisualiserComponent::paintChannel(juce::Graphics& g, juce::Rectangle<float> area, int channel) {
|
||||
juce::Path path;
|
||||
|
||||
for (int i = 0; i < buffer.size(); i += numChannels) {
|
||||
auto sample = buffer[i + channel];
|
||||
|
||||
if (i == 0) {
|
||||
path.startNewSubPath(0.0f, sample);
|
||||
} else {
|
||||
path.lineTo((float)i, sample);
|
||||
}
|
||||
}
|
||||
|
||||
// apply affine transform to path to fit in area
|
||||
auto transform = juce::AffineTransform::fromTargetPoints(0.0f, -1.0f, area.getX(), area.getY(), 0.0f, 1.0f, area.getX(), area.getBottom(), buffer.size(), -1.0f, area.getRight(), area.getY());
|
||||
path.applyTransform(transform);
|
||||
|
||||
g.strokePath(path, juce::PathStrokeType(1.0f));
|
||||
}
|
||||
|
||||
void VisualiserComponent::paintXY(juce::Graphics& g, juce::Rectangle<float> area) {
|
||||
juce::Path path;
|
||||
|
||||
path.startNewSubPath(buffer[0], buffer[1]);
|
||||
|
||||
for (int i = 2; i < buffer.size(); i += 2) {
|
||||
path.lineTo(buffer[i + 0], buffer[i + 1]);
|
||||
}
|
||||
|
||||
// apply affine transform to path to fit in area
|
||||
auto transform = juce::AffineTransform::fromTargetPoints(-1.0f, -1.0f, area.getX(), area.getBottom(), 1.0f, 1.0f, area.getRight(), area.getY(), 1.0f, -1.0f, area.getRight(), area.getBottom());
|
||||
path.applyTransform(transform);
|
||||
|
||||
g.strokePath(path, juce::PathStrokeType(1.0f));
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
#pragma once
|
||||
|
||||
#include <JuceHeader.h>
|
||||
#include "../concurrency/BufferConsumer.h"
|
||||
#include "../PluginProcessor.h"
|
||||
|
||||
class VisualiserComponent : public juce::Component, public juce::Timer {
|
||||
public:
|
||||
VisualiserComponent(int numChannels, OscirenderAudioProcessor& p);
|
||||
~VisualiserComponent() override;
|
||||
|
||||
void setBuffer(std::vector<float>& buffer);
|
||||
void setColours(juce::Colour backgroundColour, juce::Colour waveformColour);
|
||||
void paintChannel(juce::Graphics&, juce::Rectangle<float> bounds, int channel);
|
||||
void paintXY(juce::Graphics&, juce::Rectangle<float> bounds);
|
||||
void paint(juce::Graphics&) override;
|
||||
void timerCallback() override;
|
||||
|
||||
private:
|
||||
juce::SpinLock lock;
|
||||
std::vector<float> buffer;
|
||||
int numChannels = 2;
|
||||
juce::Colour backgroundColour, waveformColour;
|
||||
OscirenderAudioProcessor& audioProcessor;
|
||||
int precision = 2;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(VisualiserComponent)
|
||||
};
|
||||
|
||||
class VisualiserProcessor : public juce::Thread {
|
||||
public:
|
||||
VisualiserProcessor(std::shared_ptr<BufferConsumer> consumer, VisualiserComponent& visualiser) : juce::Thread("VisualiserProcessor"), consumer(consumer), visualiser(visualiser) {}
|
||||
~VisualiserProcessor() override {}
|
||||
|
||||
void run() override {
|
||||
while (!threadShouldExit()) {
|
||||
auto buffer = consumer->startProcessing();
|
||||
|
||||
visualiser.setBuffer(*buffer);
|
||||
consumer->finishedProcessing();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<BufferConsumer> consumer;
|
||||
VisualiserComponent& visualiser;
|
||||
};
|
|
@ -0,0 +1,123 @@
|
|||
#pragma once
|
||||
|
||||
#include <JuceHeader.h>
|
||||
|
||||
// This is a helper class for the producer and consumer threads.
|
||||
//
|
||||
// ORDER OF OPERATIONS:
|
||||
// 1. Consumer is created.
|
||||
// 2. The thread that owns the consumers calls registerConsumer() on the producer, which acquires the lock on the first buffer to be written to by calling getBuffer().
|
||||
// LOOP:
|
||||
// 3. The consumer calls startProcessing() to signal that they want to start processing the current buffer.
|
||||
// 4. The producer calls finishedWriting() to signal that they have finished writing to the current buffer, which gives the lock to the consumer.
|
||||
// 5. The consumer calls finishedProcessing() to signal that they have finished processing the current buffer.
|
||||
// 6. The producer calls getBuffer() to acquire the lock on the next buffer to be written to.
|
||||
// GOTO LOOP
|
||||
// 7. The thread that owns the consumer calls unregisterConsumer() on the producer at some point during the loop, which releases the lock on the current buffer.
|
||||
//
|
||||
class BufferConsumer {
|
||||
public:
|
||||
BufferConsumer(int bufferSize) {
|
||||
firstBuffer->resize(2 * bufferSize, 0.0);
|
||||
secondBuffer->resize(2 * bufferSize, 0.0);
|
||||
}
|
||||
|
||||
~BufferConsumer() {}
|
||||
|
||||
// Returns the buffer that is ready to be written to.
|
||||
// This is only called by the producer thread.
|
||||
// force forces the lock to be acquired.
|
||||
// Returns nullptr if the lock can't be acquired when force is false.
|
||||
// It is only called when the global producer lock is held.
|
||||
std::shared_ptr<std::vector<float>> getBuffer(bool force) {
|
||||
auto buffer = firstBufferWriting ? firstBuffer : secondBuffer;
|
||||
if (lockHeldForWriting) {
|
||||
return buffer;
|
||||
}
|
||||
auto bufferLock = firstBufferWriting ? firstBufferLock : secondBufferLock;
|
||||
|
||||
if (force) {
|
||||
bufferLock->enter();
|
||||
lockHeldForWriting = true;
|
||||
return buffer;
|
||||
} else if (bufferLock->tryEnter()) {
|
||||
lockHeldForWriting = true;
|
||||
return buffer;
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// This is only called by the producer thread. It is only called when the global
|
||||
// producer lock is held.
|
||||
void finishedWriting() {
|
||||
auto bufferLock = firstBufferWriting ? firstBufferLock : secondBufferLock;
|
||||
lockHeldForWriting = false;
|
||||
firstBufferWriting = !firstBufferWriting;
|
||||
// Try locking before we unlock the current buffer so that
|
||||
// the consumer doesn't start processing before we
|
||||
// unlock the buffer. Ignore if we can't get the lock
|
||||
// because the consumer is still processing.
|
||||
getBuffer(false);
|
||||
bufferLock->exit();
|
||||
}
|
||||
|
||||
void releaseLock() {
|
||||
if (lockHeldForWriting) {
|
||||
auto bufferLock = firstBufferWriting ? firstBufferLock : secondBufferLock;
|
||||
bufferLock->exit();
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the buffer that has been written to fully and is ready to be processed.
|
||||
// This will lock the buffer so that the producer can't write to it while we're processing.
|
||||
std::shared_ptr<std::vector<float>> startProcessing() {
|
||||
auto buffer = firstBufferProcessing ? firstBuffer : secondBuffer;
|
||||
auto bufferLock = firstBufferProcessing ? firstBufferLock : secondBufferLock;
|
||||
|
||||
bufferLock->enter();
|
||||
return buffer;
|
||||
}
|
||||
|
||||
// This should be called after processing has finished.
|
||||
// It releases the lock on the buffer so that the producer can write to it again.
|
||||
void finishedProcessing() {
|
||||
auto bufferLock = firstBufferProcessing ? firstBufferLock : secondBufferLock;
|
||||
firstBufferProcessing = !firstBufferProcessing;
|
||||
bufferLock->exit();
|
||||
}
|
||||
|
||||
std::shared_ptr<std::vector<float>> firstBuffer = std::make_shared<std::vector<float>>();
|
||||
std::shared_ptr<std::vector<float>> secondBuffer = std::make_shared<std::vector<float>>();
|
||||
std::shared_ptr<juce::SpinLock> firstBufferLock = std::make_shared<juce::SpinLock>();
|
||||
std::shared_ptr<juce::SpinLock> secondBufferLock = std::make_shared<juce::SpinLock>();
|
||||
private:
|
||||
// Indirectly used by the producer to signal whether it holds the lock on the buffer.
|
||||
// This is accurate if the global producer lock is held as the buffer lock is acquired
|
||||
// and this is set to true before the global producer lock is released.
|
||||
bool lockHeldForWriting = false;
|
||||
bool firstBufferWriting = true;
|
||||
bool firstBufferProcessing = true;
|
||||
};
|
||||
|
||||
class DummyConsumer : public juce::Thread {
|
||||
public:
|
||||
DummyConsumer(std::shared_ptr<BufferConsumer> consumer) : juce::Thread("DummyConsumer"), consumer(consumer) {}
|
||||
~DummyConsumer() {}
|
||||
|
||||
void run() override {
|
||||
while (!threadShouldExit()) {
|
||||
auto buffer = consumer->startProcessing();
|
||||
|
||||
float total = 0.0;
|
||||
for (int i = 0; i < buffer->size(); i++) {
|
||||
total += (*buffer)[i];
|
||||
}
|
||||
DBG(total);
|
||||
|
||||
consumer->finishedProcessing();
|
||||
}
|
||||
}
|
||||
private:
|
||||
std::shared_ptr<BufferConsumer> consumer;
|
||||
};
|
|
@ -0,0 +1,63 @@
|
|||
#pragma once
|
||||
|
||||
#include <JuceHeader.h>
|
||||
#include "BufferConsumer.h"
|
||||
|
||||
class BufferProducer {
|
||||
public:
|
||||
BufferProducer() {}
|
||||
~BufferProducer() {}
|
||||
|
||||
// This should add the buffers and locks to the vectors
|
||||
// and then lock the first buffer lock so it can start
|
||||
// being written to.
|
||||
// This is only called by the thread that owns the consumer thread.
|
||||
void registerConsumer(std::shared_ptr<BufferConsumer> consumer) {
|
||||
juce::SpinLock::ScopedLockType scope(lock);
|
||||
consumers.push_back(consumer);
|
||||
bufferPositions.push_back(0);
|
||||
consumer->getBuffer(true);
|
||||
}
|
||||
|
||||
// This is only called by the thread that owns the consumer thread.
|
||||
// This can't happen at the same time as write() it locks the producer lock.
|
||||
void unregisterConsumer(std::shared_ptr<BufferConsumer> consumer) {
|
||||
juce::SpinLock::ScopedLockType scope(lock);
|
||||
for (int i = 0; i < consumers.size(); i++) {
|
||||
if (consumers[i] == consumer) {
|
||||
consumer->releaseLock();
|
||||
consumers.erase(consumers.begin() + i);
|
||||
bufferPositions.erase(bufferPositions.begin() + i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Writes a sample to the current buffer for all consumers.
|
||||
void write(float left, float right) {
|
||||
juce::SpinLock::ScopedLockType scope(lock);
|
||||
for (int i = 0; i < consumers.size(); i++) {
|
||||
std::shared_ptr<std::vector<float>> buffer = consumers[i]->getBuffer(false);
|
||||
if (buffer == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
(*buffer)[bufferPositions[i]] = left;
|
||||
(*buffer)[bufferPositions[i] + 1] = right;
|
||||
bufferPositions[i] += 2;
|
||||
|
||||
// If we've reached the end of the buffer, switch
|
||||
// to the other buffer and unlock it. This signals
|
||||
// to the consumer that it can start processing!
|
||||
if (bufferPositions[i] >= buffer->size()) {
|
||||
bufferPositions[i] = 0;
|
||||
consumers[i]->finishedWriting();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
juce::SpinLock lock;
|
||||
std::vector<std::shared_ptr<BufferConsumer>> consumers;
|
||||
std::vector<int> bufferPositions;
|
||||
};
|
|
@ -24,6 +24,12 @@
|
|||
</GROUP>
|
||||
</GROUP>
|
||||
<GROUP id="{75439074-E50C-362F-1EDF-8B4BE9011259}" name="Source">
|
||||
<GROUP id="{9F5970A9-8094-E7F3-7AC1-812AE5589B9F}" name="concurrency">
|
||||
<FILE id="WQ2W15" name="BufferConsumer.h" compile="0" resource="0"
|
||||
file="Source/concurrency/BufferConsumer.h"/>
|
||||
<FILE id="yWTiQQ" name="BufferProducer.h" compile="0" resource="0"
|
||||
file="Source/concurrency/BufferProducer.h"/>
|
||||
</GROUP>
|
||||
<FILE id="JceyXh" name="Util.h" compile="0" resource="0" file="Source/Util.h"/>
|
||||
<FILE id="RHHuXP" name="ObjComponent.cpp" compile="1" resource="0"
|
||||
file="Source/ObjComponent.cpp"/>
|
||||
|
@ -117,6 +123,10 @@
|
|||
file="Source/components/LuaListComponent.cpp"/>
|
||||
<FILE id="x0Syav" name="LuaListComponent.h" compile="0" resource="0"
|
||||
file="Source/components/LuaListComponent.h"/>
|
||||
<FILE id="y3UiR0" name="VisualiserComponent.cpp" compile="1" resource="0"
|
||||
file="Source/components/VisualiserComponent.cpp"/>
|
||||
<FILE id="ZueyNl" name="VisualiserComponent.h" compile="0" resource="0"
|
||||
file="Source/components/VisualiserComponent.h"/>
|
||||
</GROUP>
|
||||
<GROUP id="{85A33213-D880-BD92-70D8-1901DA6D23F0}" name="audio">
|
||||
<FILE id="NWuowi" name="BitCrushEffect.cpp" compile="1" resource="0"
|
||||
|
@ -136,6 +146,9 @@
|
|||
file="Source/audio/EffectApplication.h"/>
|
||||
<FILE id="uhyh7T" name="LuaEffect.cpp" compile="1" resource="0" file="Source/audio/LuaEffect.cpp"/>
|
||||
<FILE id="jqDcZq" name="LuaEffect.h" compile="0" resource="0" file="Source/audio/LuaEffect.h"/>
|
||||
<FILE id="t2bsR8" name="PitchDetector.cpp" compile="1" resource="0"
|
||||
file="Source/audio/PitchDetector.cpp"/>
|
||||
<FILE id="rQC2gX" name="PitchDetector.h" compile="0" resource="0" file="Source/audio/PitchDetector.h"/>
|
||||
<FILE id="PbbNqz" name="RotateEffect.cpp" compile="1" resource="0"
|
||||
file="Source/audio/RotateEffect.cpp"/>
|
||||
<FILE id="tUwNZV" name="RotateEffect.h" compile="0" resource="0" file="Source/audio/RotateEffect.h"/>
|
||||
|
@ -264,6 +277,7 @@
|
|||
<MODULEPATH id="juce_gui_basics" path="../../../JUCE/modules"/>
|
||||
<MODULEPATH id="juce_gui_extra" path="../../../JUCE/modules"/>
|
||||
<MODULEPATH id="juce_opengl" path="../../../JUCE/modules"/>
|
||||
<MODULEPATH id="juce_dsp" path="../../JUCE/modules"/>
|
||||
</MODULEPATHS>
|
||||
</LINUX_MAKE>
|
||||
<VS2022 targetFolder="Builds/VisualStudio2022">
|
||||
|
@ -285,6 +299,7 @@
|
|||
<MODULEPATH id="juce_gui_basics" path="../../../JUCE/modules"/>
|
||||
<MODULEPATH id="juce_gui_extra" path="../../../JUCE/modules"/>
|
||||
<MODULEPATH id="juce_opengl" path="../../../JUCE/modules"/>
|
||||
<MODULEPATH id="juce_dsp" path="../../JUCE/modules"/>
|
||||
</MODULEPATHS>
|
||||
</VS2022>
|
||||
<XCODE_MAC targetFolder="Builds/MacOSX">
|
||||
|
@ -306,6 +321,7 @@
|
|||
<MODULEPATH id="juce_gui_basics" path="../../../JUCE/modules"/>
|
||||
<MODULEPATH id="juce_gui_extra" path="../../../JUCE/modules"/>
|
||||
<MODULEPATH id="juce_opengl" path="../../../JUCE/modules"/>
|
||||
<MODULEPATH id="juce_dsp" path="../../JUCE/modules"/>
|
||||
</MODULEPATHS>
|
||||
</XCODE_MAC>
|
||||
</EXPORTFORMATS>
|
||||
|
@ -319,6 +335,7 @@
|
|||
<MODULE id="juce_audio_utils" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
|
||||
<MODULE id="juce_core" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
|
||||
<MODULE id="juce_data_structures" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
|
||||
<MODULE id="juce_dsp" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
|
||||
<MODULE id="juce_events" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
|
||||
<MODULE id="juce_graphics" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
|
||||
<MODULE id="juce_gui_basics" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
|
||||
|
|
Ładowanie…
Reference in New Issue