kopia lustrzana https://github.com/jameshball/osci-render
Resolve conflicts
commit
bfc70d71dc
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M12,9A3,3 0 0,1 15,12A3,3 0 0,1 12,15A3,3 0 0,1 9,12A3,3 0 0,1 12,9Z" /></svg>
|
Po Szerokość: | Wysokość: | Rozmiar: 222 B |
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M6,2H18V8H18V8L14,12L18,16V16H18V22H6V16H6V16L10,12L6,8V8H6V2M16,16.5L12,12.5L8,16.5V20H16V16.5M12,11.5L16,7.5V4H8V7.5L12,11.5M10,6H14V6.75L12,8.75L10,6.75V6Z" /></svg>
|
Po Szerokość: | Wysokość: | Rozmiar: 237 B |
|
@ -130,6 +130,8 @@ MainComponent::MainComponent(OscirenderAudioProcessor& p, OscirenderAudioProcess
|
||||||
frequencyLabel.setText(juce::String(roundedFrequency) + "Hz", juce::dontSendNotification);
|
frequencyLabel.setText(juce::String(roundedFrequency) + "Hz", juce::dontSendNotification);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
addAndMakeVisible(recorder);
|
||||||
}
|
}
|
||||||
|
|
||||||
MainComponent::~MainComponent() {
|
MainComponent::~MainComponent() {
|
||||||
|
@ -152,6 +154,9 @@ void MainComponent::resized() {
|
||||||
auto buttonHeight = 30;
|
auto buttonHeight = 30;
|
||||||
auto padding = 10;
|
auto padding = 10;
|
||||||
auto rowPadding = 10;
|
auto rowPadding = 10;
|
||||||
|
|
||||||
|
recorder.setBounds(bounds.removeFromBottom(30));
|
||||||
|
bounds.removeFromBottom(padding);
|
||||||
|
|
||||||
auto row = bounds.removeFromTop(buttonHeight);
|
auto row = bounds.removeFromTop(buttonHeight);
|
||||||
fileButton.setBounds(row.removeFromLeft(buttonWidth));
|
fileButton.setBounds(row.removeFromLeft(buttonWidth));
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include "audio/PitchDetector.h"
|
#include "audio/PitchDetector.h"
|
||||||
#include "UGen/ugen_JuceEnvelopeComponent.h"
|
#include "UGen/ugen_JuceEnvelopeComponent.h"
|
||||||
#include "components/SvgButton.h"
|
#include "components/SvgButton.h"
|
||||||
|
#include "components/AudioRecordingComponent.h"
|
||||||
|
|
||||||
class OscirenderAudioProcessorEditor;
|
class OscirenderAudioProcessorEditor;
|
||||||
class MainComponent : public juce::GroupComponent {
|
class MainComponent : public juce::GroupComponent {
|
||||||
|
@ -25,7 +26,7 @@ private:
|
||||||
std::unique_ptr<juce::FileChooser> chooser;
|
std::unique_ptr<juce::FileChooser> chooser;
|
||||||
juce::TextButton fileButton;
|
juce::TextButton fileButton;
|
||||||
juce::TextButton closeFileButton;
|
juce::TextButton closeFileButton;
|
||||||
SvgButton inputEnabled{"inputEnabled", juce::String(BinaryData::microphone_svg), "white", "red", audioProcessor.inputEnabled};
|
SvgButton inputEnabled{"inputEnabled", juce::String(BinaryData::microphone_svg), juce::Colours::white, juce::Colours::red, audioProcessor.inputEnabled};
|
||||||
juce::Label fileLabel;
|
juce::Label fileLabel;
|
||||||
|
|
||||||
juce::TextEditor fileName;
|
juce::TextEditor fileName;
|
||||||
|
@ -37,5 +38,7 @@ private:
|
||||||
juce::Label frequencyLabel;
|
juce::Label frequencyLabel;
|
||||||
int callbackIndex = -1;
|
int callbackIndex = -1;
|
||||||
|
|
||||||
|
AudioRecordingComponent recorder{audioProcessor};
|
||||||
|
|
||||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MainComponent)
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MainComponent)
|
||||||
};
|
};
|
|
@ -28,10 +28,10 @@ private:
|
||||||
|
|
||||||
juce::TextButton resetRotation{"Reset Rotation"};
|
juce::TextButton resetRotation{"Reset Rotation"};
|
||||||
juce::ToggleButton mouseRotate{"Rotate with Mouse (Esc to disable)"};
|
juce::ToggleButton mouseRotate{"Rotate with Mouse (Esc to disable)"};
|
||||||
|
|
||||||
std::shared_ptr<SvgButton> fixedRotateX = std::make_shared<SvgButton>("fixedRotateX", juce::String(BinaryData::fixed_rotate_svg), "white", "red", audioProcessor.perspectiveEffect->fixedRotateX);
|
std::shared_ptr<SvgButton> fixedRotateX = std::make_shared<SvgButton>("fixedRotateX", juce::String(BinaryData::fixed_rotate_svg), juce::Colours::white, juce::Colours::red, audioProcessor.perspectiveEffect->fixedRotateX);
|
||||||
std::shared_ptr<SvgButton> fixedRotateY = std::make_shared<SvgButton>("fixedRotateY", juce::String(BinaryData::fixed_rotate_svg), "white", "red", audioProcessor.perspectiveEffect->fixedRotateY);
|
std::shared_ptr<SvgButton> fixedRotateY = std::make_shared<SvgButton>("fixedRotateY", juce::String(BinaryData::fixed_rotate_svg), juce::Colours::white, juce::Colours::red, audioProcessor.perspectiveEffect->fixedRotateY);
|
||||||
std::shared_ptr<SvgButton> fixedRotateZ = std::make_shared<SvgButton>("fixedRotateZ", juce::String(BinaryData::fixed_rotate_svg), "white", "red", audioProcessor.perspectiveEffect->fixedRotateZ);
|
std::shared_ptr<SvgButton> fixedRotateZ = std::make_shared<SvgButton>("fixedRotateZ", juce::String(BinaryData::fixed_rotate_svg), juce::Colours::white, juce::Colours::red, audioProcessor.perspectiveEffect->fixedRotateZ);
|
||||||
|
|
||||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PerspectiveComponent)
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PerspectiveComponent)
|
||||||
};
|
};
|
||||||
|
|
|
@ -168,6 +168,11 @@ const juce::String OscirenderAudioProcessor::getName() const {
|
||||||
return JucePlugin_Name;
|
return JucePlugin_Name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void OscirenderAudioProcessor::setAudioThreadCallback(std::function<void(const juce::AudioBuffer<float>&)> callback) {
|
||||||
|
juce::SpinLock::ScopedLockType lock(audioThreadCallbackLock);
|
||||||
|
audioThreadCallback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
bool OscirenderAudioProcessor::acceptsMidi() const {
|
bool OscirenderAudioProcessor::acceptsMidi() const {
|
||||||
#if JucePlugin_WantsMidiInput
|
#if JucePlugin_WantsMidiInput
|
||||||
return true;
|
return true;
|
||||||
|
@ -631,6 +636,10 @@ void OscirenderAudioProcessor::processBlock(juce::AudioBuffer<float>& buffer, ju
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// used for any callback that must guarantee all audio is recieved (e.g. when recording to a file)
|
||||||
|
juce::SpinLock::ScopedLockType lock(audioThreadCallbackLock);
|
||||||
|
audioThreadCallback(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
|
|
|
@ -52,6 +52,8 @@ public:
|
||||||
|
|
||||||
const juce::String getName() const override;
|
const juce::String getName() const override;
|
||||||
|
|
||||||
|
void setAudioThreadCallback(std::function<void(const juce::AudioBuffer<float>&)> callback);
|
||||||
|
|
||||||
bool acceptsMidi() const override;
|
bool acceptsMidi() const override;
|
||||||
bool producesMidi() const override;
|
bool producesMidi() const override;
|
||||||
bool isMidiEffect() const override;
|
bool isMidiEffect() const override;
|
||||||
|
@ -237,6 +239,9 @@ private:
|
||||||
|
|
||||||
bool prevMidiEnabled = !midiEnabled->getBoolValue();
|
bool prevMidiEnabled = !midiEnabled->getBoolValue();
|
||||||
|
|
||||||
|
juce::SpinLock audioThreadCallbackLock;
|
||||||
|
std::function<void(const juce::AudioBuffer<float>&)> audioThreadCallback;
|
||||||
|
|
||||||
std::vector<BooleanParameter*> booleanParameters;
|
std::vector<BooleanParameter*> booleanParameters;
|
||||||
std::vector<FloatParameter*> floatParameters;
|
std::vector<FloatParameter*> floatParameters;
|
||||||
std::vector<IntParameter*> intParameters;
|
std::vector<IntParameter*> intParameters;
|
||||||
|
|
|
@ -0,0 +1,321 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
This file is part of the JUCE examples.
|
||||||
|
Copyright (c) 2022 - Raw Material Software Limited
|
||||||
|
|
||||||
|
The code included in this file is provided under the terms of the ISC license
|
||||||
|
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||||
|
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||||
|
without fee is hereby granted provided that the above copyright notice and
|
||||||
|
this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
|
||||||
|
WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
|
||||||
|
PURPOSE, ARE DISCLAIMED.
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
The block below describes the properties of this PIP. A PIP is a short snippet
|
||||||
|
of code that can be read by the Projucer and used to generate a JUCE project.
|
||||||
|
|
||||||
|
BEGIN_JUCE_PIP_METADATA
|
||||||
|
|
||||||
|
name: AudioRecordingComponent
|
||||||
|
version: 1.0.0
|
||||||
|
vendor: JUCE
|
||||||
|
website: http://juce.com
|
||||||
|
description: Records audio to a file.
|
||||||
|
|
||||||
|
dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats,
|
||||||
|
juce_audio_processors, juce_audio_utils, juce_core,
|
||||||
|
juce_data_structures, juce_events, juce_graphics,
|
||||||
|
juce_gui_basics, juce_gui_extra
|
||||||
|
exporters: xcode_mac, vs2022, linux_make, androidstudio, xcode_iphone
|
||||||
|
|
||||||
|
moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
|
||||||
|
|
||||||
|
type: Component
|
||||||
|
mainClass: AudioRecordingComponent
|
||||||
|
|
||||||
|
useLocalCopy: 1
|
||||||
|
|
||||||
|
END_JUCE_PIP_METADATA
|
||||||
|
|
||||||
|
*******************************************************************************/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "DoubleTextBox.h"
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
class AudioRecorder final {
|
||||||
|
public:
|
||||||
|
AudioRecorder(OscirenderAudioProcessor& p, juce::AudioThumbnail& thumbnailToUpdate)
|
||||||
|
: audioProcessor(p), thumbnail(thumbnailToUpdate) {
|
||||||
|
backgroundThread.startThread();
|
||||||
|
audioProcessor.setAudioThreadCallback([this](const juce::AudioBuffer<float>& buffer) { audioThreadCallback(buffer); });
|
||||||
|
}
|
||||||
|
|
||||||
|
~AudioRecorder() {
|
||||||
|
audioProcessor.setAudioThreadCallback(nullptr);
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
void startRecording(const juce::File& file) {
|
||||||
|
stop();
|
||||||
|
|
||||||
|
if (audioProcessor.currentSampleRate > 0) {
|
||||||
|
// Create an OutputStream to write to our destination file...
|
||||||
|
file.deleteFile();
|
||||||
|
|
||||||
|
if (auto fileStream = std::unique_ptr<juce::FileOutputStream>(file.createOutputStream())) {
|
||||||
|
// Now create a WAV writer object that writes to our output stream...
|
||||||
|
juce::WavAudioFormat wavFormat;
|
||||||
|
|
||||||
|
if (auto writer = wavFormat.createWriterFor(fileStream.get(), audioProcessor.currentSampleRate, 2, 32, {}, 0)) {
|
||||||
|
fileStream.release(); // (passes responsibility for deleting the stream to the writer object that is now using it)
|
||||||
|
|
||||||
|
// Now we'll create one of these helper objects which will act as a FIFO buffer, and will
|
||||||
|
// write the data to disk on our background thread.
|
||||||
|
threadedWriter.reset(new juce::AudioFormatWriter::ThreadedWriter(writer, backgroundThread, 32768));
|
||||||
|
|
||||||
|
// Reset our recording thumbnail
|
||||||
|
thumbnail.reset(writer->getNumChannels(), writer->getSampleRate());
|
||||||
|
nextSampleNum = 0;
|
||||||
|
|
||||||
|
// And now, swap over our active writer pointer so that the audio callback will start using it..
|
||||||
|
const juce::ScopedLock sl(writerLock);
|
||||||
|
activeWriter = threadedWriter.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void stop() {
|
||||||
|
// First, clear this pointer to stop the audio callback from using our writer object..
|
||||||
|
{
|
||||||
|
const juce::ScopedLock sl(writerLock);
|
||||||
|
activeWriter = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we can delete the writer object. It's done in this order because the deletion could
|
||||||
|
// take a little time while remaining data gets flushed to disk, so it's best to avoid blocking
|
||||||
|
// the audio callback while this happens.
|
||||||
|
threadedWriter.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isRecording() const {
|
||||||
|
return activeWriter.load() != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void audioThreadCallback(const juce::AudioBuffer<float>& buffer) {
|
||||||
|
if (nextSampleNum >= recordingLength * audioProcessor.currentSampleRate) {
|
||||||
|
stop();
|
||||||
|
stopCallback();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const juce::ScopedLock sl(writerLock);
|
||||||
|
int numSamples = buffer.getNumSamples();
|
||||||
|
|
||||||
|
if (activeWriter.load() != nullptr) {
|
||||||
|
activeWriter.load()->write(buffer.getArrayOfReadPointers(), numSamples);
|
||||||
|
thumbnail.addBlock(nextSampleNum, buffer, 0, numSamples);
|
||||||
|
nextSampleNum += numSamples;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setRecordLength(double recordLength) {
|
||||||
|
recordingLength = recordLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::function<void()> stopCallback;
|
||||||
|
|
||||||
|
private:
|
||||||
|
OscirenderAudioProcessor& audioProcessor;
|
||||||
|
|
||||||
|
juce::AudioThumbnail& thumbnail;
|
||||||
|
juce::TimeSliceThread backgroundThread { "Audio Recorder Thread" }; // the thread that will write our audio data to disk
|
||||||
|
std::unique_ptr<juce::AudioFormatWriter::ThreadedWriter> threadedWriter; // the FIFO used to buffer the incoming data
|
||||||
|
juce::int64 nextSampleNum = 0;
|
||||||
|
|
||||||
|
double recordingLength = 99999999999.0;
|
||||||
|
|
||||||
|
juce::CriticalSection writerLock;
|
||||||
|
std::atomic<juce::AudioFormatWriter::ThreadedWriter*> activeWriter { nullptr };
|
||||||
|
};
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
class RecordingThumbnail final : public juce::Component,
|
||||||
|
private juce::ChangeListener {
|
||||||
|
public:
|
||||||
|
RecordingThumbnail() {
|
||||||
|
formatManager.registerBasicFormats();
|
||||||
|
thumbnail.addChangeListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
~RecordingThumbnail() override {
|
||||||
|
thumbnail.removeChangeListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
juce::AudioThumbnail& getAudioThumbnail() { return thumbnail; }
|
||||||
|
|
||||||
|
void setDisplayFullThumbnail(bool displayFull) {
|
||||||
|
displayFullThumb = displayFull;
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
void paint(juce::Graphics& g) override {
|
||||||
|
g.setColour(juce::Colours::white);
|
||||||
|
|
||||||
|
if (thumbnail.getTotalLength() > 0.0) {
|
||||||
|
auto endTime = displayFullThumb ? thumbnail.getTotalLength()
|
||||||
|
: juce::jmax(30.0, thumbnail.getTotalLength());
|
||||||
|
|
||||||
|
auto thumbArea = getLocalBounds();
|
||||||
|
thumbnail.drawChannels(g, thumbArea.reduced(2), 0.0, endTime, 1.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
juce::AudioFormatManager formatManager;
|
||||||
|
juce::AudioThumbnailCache thumbnailCache { 10 };
|
||||||
|
juce::AudioThumbnail thumbnail { 128, formatManager, thumbnailCache };
|
||||||
|
|
||||||
|
bool displayFullThumb = false;
|
||||||
|
|
||||||
|
void changeListenerCallback(juce::ChangeBroadcaster* source) override {
|
||||||
|
if (source == &thumbnail)
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(RecordingThumbnail)
|
||||||
|
};
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
class AudioRecordingComponent final : public juce::Component {
|
||||||
|
public:
|
||||||
|
AudioRecordingComponent(OscirenderAudioProcessor& p) : audioProcessor(p) {
|
||||||
|
addAndMakeVisible(recordButton);
|
||||||
|
addAndMakeVisible(timedRecord);
|
||||||
|
addAndMakeVisible(recordLength);
|
||||||
|
|
||||||
|
recordButton.setTooltip("Start recording audio to a WAV file. Press again to stop and save the recording.");
|
||||||
|
timedRecord.setTooltip("Record for a set amount of time in seconds. When enabled, the recording will automatically stop once the time is reached.");
|
||||||
|
|
||||||
|
recordLength.setValue(1);
|
||||||
|
|
||||||
|
recordButton.onClick = [this] {
|
||||||
|
if (recordButton.getToggleState()) {
|
||||||
|
startRecording();
|
||||||
|
} else {
|
||||||
|
stopRecording();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
timedRecord.onClick = [this] {
|
||||||
|
if (timedRecord.getToggleState()) {
|
||||||
|
addAndMakeVisible(recordLength);
|
||||||
|
} else {
|
||||||
|
removeChildComponent(&recordLength);
|
||||||
|
}
|
||||||
|
resized();
|
||||||
|
};
|
||||||
|
|
||||||
|
recorder.stopCallback = [this] {
|
||||||
|
juce::MessageManager::callAsync([this] {
|
||||||
|
recordButton.setToggleState(false, juce::sendNotification);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
addAndMakeVisible(recordingThumbnail);
|
||||||
|
recordingThumbnail.setDisplayFullThumbnail(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void resized() override {
|
||||||
|
double iconSize = 25;
|
||||||
|
|
||||||
|
auto area = getLocalBounds();
|
||||||
|
recordButton.setBounds(area.removeFromLeft(iconSize).withSizeKeepingCentre(iconSize, iconSize));
|
||||||
|
area.removeFromLeft(5);
|
||||||
|
timedRecord.setBounds(area.removeFromLeft(iconSize).withSizeKeepingCentre(iconSize, iconSize));
|
||||||
|
if (timedRecord.getToggleState()) {
|
||||||
|
recordLength.setBounds(area.removeFromLeft(80).withSizeKeepingCentre(60, 25));
|
||||||
|
}
|
||||||
|
area.removeFromLeft(5);
|
||||||
|
recordingThumbnail.setBounds(area);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
OscirenderAudioProcessor& audioProcessor;
|
||||||
|
|
||||||
|
RecordingThumbnail recordingThumbnail;
|
||||||
|
AudioRecorder recorder{ audioProcessor, recordingThumbnail.getAudioThumbnail() };
|
||||||
|
|
||||||
|
SvgButton recordButton{ "record", BinaryData::record_svg, juce::Colours::white, juce::Colours::red };
|
||||||
|
juce::File lastRecording;
|
||||||
|
juce::FileChooser chooser { "Output file...", juce::File::getCurrentWorkingDirectory().getChildFile("recording.wav"), "*.wav" };
|
||||||
|
SvgButton timedRecord{ "timedRecord", BinaryData::timer_svg, juce::Colours::white, juce::Colours::red };
|
||||||
|
DoubleTextBox recordLength{ 0, 60 * 60 * 24 };
|
||||||
|
|
||||||
|
void startRecording() {
|
||||||
|
auto parentDir = juce::File::getSpecialLocation(juce::File::tempDirectory);
|
||||||
|
|
||||||
|
lastRecording = parentDir.getNonexistentChildFile("osci-render-recording", ".wav");
|
||||||
|
if (timedRecord.getToggleState()) {
|
||||||
|
recorder.setRecordLength(recordLength.getValue());
|
||||||
|
} else {
|
||||||
|
recorder.setRecordLength(99999999999.0);
|
||||||
|
}
|
||||||
|
recorder.startRecording(lastRecording);
|
||||||
|
|
||||||
|
recordButton.setColour(juce::TextButton::buttonColourId, juce::Colours::red);
|
||||||
|
recordButton.setColour(juce::TextButton::textColourOnId, juce::Colours::black);
|
||||||
|
}
|
||||||
|
|
||||||
|
void stopRecording() {
|
||||||
|
recorder.stop();
|
||||||
|
|
||||||
|
recordButton.setColour(juce::TextButton::buttonColourId, findColour(juce::TextButton::buttonColourId));
|
||||||
|
recordButton.setColour(juce::TextButton::textColourOnId, findColour(juce::TextButton::textColourOnId));
|
||||||
|
|
||||||
|
chooser.launchAsync(juce::FileBrowserComponent::saveMode
|
||||||
|
| juce::FileBrowserComponent::canSelectFiles
|
||||||
|
| juce::FileBrowserComponent::warnAboutOverwriting,
|
||||||
|
[this](const juce::FileChooser& c) {
|
||||||
|
if (juce::FileInputStream inputStream(lastRecording); inputStream.openedOk()) {
|
||||||
|
if (const auto outputStream = c.getURLResult().getLocalFile().createOutputStream()) {
|
||||||
|
outputStream->setPosition(0);
|
||||||
|
outputStream->truncate();
|
||||||
|
outputStream->writeFromInputStream(inputStream, -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lastRecording.deleteFile();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
inline juce::Colour getUIColourIfAvailable(juce::LookAndFeel_V4::ColourScheme::UIColour uiColour, juce::Colour fallback = juce::Colour(0xff4d4d4d)) noexcept {
|
||||||
|
if (auto* v4 = dynamic_cast<juce::LookAndFeel_V4*> (&juce::LookAndFeel::getDefaultLookAndFeel()))
|
||||||
|
return v4->getCurrentColourScheme().getUIColour(uiColour);
|
||||||
|
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::unique_ptr<juce::OutputStream> makeOutputStream(const juce::URL& url) {
|
||||||
|
if (const auto doc = juce::AndroidDocument::fromDocument(url))
|
||||||
|
return doc.createOutputStream();
|
||||||
|
|
||||||
|
#if ! JUCE_IOS
|
||||||
|
if (url.isLocalFile())
|
||||||
|
return url.getLocalFile().createOutputStream();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return url.createOutputStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(AudioRecordingComponent)
|
||||||
|
};
|
|
@ -0,0 +1,59 @@
|
||||||
|
#pragma once
|
||||||
|
#include <JuceHeader.h>
|
||||||
|
|
||||||
|
class DoubleTextBox : public juce::TextEditor {
|
||||||
|
public:
|
||||||
|
DoubleTextBox(double minValue, double maxValue) : minValue(minValue), maxValue(maxValue) {
|
||||||
|
setText(juce::String(minValue, 2), false);
|
||||||
|
setMultiLine(false);
|
||||||
|
setJustification(juce::Justification::centred);
|
||||||
|
setFont(juce::Font(15.0f, juce::Font::plain));
|
||||||
|
onTextChange = [this]() {
|
||||||
|
setText(getText(), false);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
double getValue() {
|
||||||
|
return getText().getDoubleValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setValue(double value, bool sendChangeMessage = true) {
|
||||||
|
setText(juce::String(value, 2), sendChangeMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setText(const juce::String& newText, bool sendChangeMessage = true) {
|
||||||
|
// remove all non-digits
|
||||||
|
juce::String text = newText.retainCharacters("0123456789.-");
|
||||||
|
|
||||||
|
// only keep first decimal point
|
||||||
|
int firstDecimal = text.indexOfChar('.');
|
||||||
|
if (firstDecimal != -1) {
|
||||||
|
juce::String remainder = text.substring(firstDecimal + 1);
|
||||||
|
remainder = remainder.retainCharacters("0123456789");
|
||||||
|
text = text.substring(0, firstDecimal + 1) + remainder;
|
||||||
|
}
|
||||||
|
|
||||||
|
// only keep negative sign at beginning
|
||||||
|
if (text.contains("-")) {
|
||||||
|
juce::String remainder = text.substring(1);
|
||||||
|
remainder = remainder.retainCharacters("0123456789");
|
||||||
|
text = text.substring(0, 1) + remainder;
|
||||||
|
}
|
||||||
|
|
||||||
|
double value = text.getDoubleValue();
|
||||||
|
if (value < minValue || value > maxValue) {
|
||||||
|
text = juce::String(prevValue);
|
||||||
|
} else {
|
||||||
|
prevValue = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
juce::TextEditor::setText(text, sendChangeMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
~DoubleTextBox() override {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
double minValue;
|
||||||
|
double maxValue;
|
||||||
|
double prevValue = minValue;
|
||||||
|
};
|
|
@ -9,7 +9,7 @@ EffectComponent::EffectComponent(OscirenderAudioProcessor& p, Effect& effect, in
|
||||||
|
|
||||||
sidechainEnabled = effect.parameters[0]->sidechain != nullptr;
|
sidechainEnabled = effect.parameters[0]->sidechain != nullptr;
|
||||||
if (sidechainEnabled) {
|
if (sidechainEnabled) {
|
||||||
sidechainButton = std::make_unique<SvgButton>(effect.parameters[0]->name, BinaryData::microphone_svg, "white", "red", effect.parameters[0]->sidechain);
|
sidechainButton = std::make_unique<SvgButton>(effect.parameters[0]->name, BinaryData::microphone_svg, juce::Colours::white, juce::Colours::red, effect.parameters[0]->sidechain);
|
||||||
sidechainButton->setTooltip("When enabled, the volume of the input audio controls the value of the slider, acting like a sidechain effect.");
|
sidechainButton->setTooltip("When enabled, the volume of the input audio controls the value of the slider, acting like a sidechain effect.");
|
||||||
addAndMakeVisible(*sidechainButton);
|
addAndMakeVisible(*sidechainButton);
|
||||||
}
|
}
|
||||||
|
|
|
@ -97,14 +97,14 @@ std::shared_ptr<juce::Component> EffectsListComponent::createComponent(EffectPar
|
||||||
toggle = audioProcessor.perspectiveEffect->fixedRotateZ;
|
toggle = audioProcessor.perspectiveEffect->fixedRotateZ;
|
||||||
axis = "Z";
|
axis = "Z";
|
||||||
}
|
}
|
||||||
std::shared_ptr<SvgButton> button = std::make_shared<SvgButton>(parameter->name, BinaryData::fixed_rotate_svg, "white", "red", toggle);
|
std::shared_ptr<SvgButton> button = std::make_shared<SvgButton>(parameter->name, BinaryData::fixed_rotate_svg, juce::Colours::white, juce::Colours::red, toggle);
|
||||||
button->setTooltip("Toggles whether the rotation around the " + axis + " axis is fixed, or changes according to the rotation speed.");
|
button->setTooltip("Toggles whether the rotation around the " + axis + " axis is fixed, or changes according to the rotation speed.");
|
||||||
button->onClick = [this, toggle] {
|
button->onClick = [this, toggle] {
|
||||||
toggle->setBoolValueNotifyingHost(!toggle->getBoolValue());
|
toggle->setBoolValueNotifyingHost(!toggle->getBoolValue());
|
||||||
};
|
};
|
||||||
return button;
|
return button;
|
||||||
} else if (parameter->paramID == "customEffectStrength") {
|
} else if (parameter->paramID == "customEffectStrength") {
|
||||||
std::shared_ptr<SvgButton> button = std::make_shared<SvgButton>(parameter->name, BinaryData::pencil_svg, "white", "red");
|
std::shared_ptr<SvgButton> button = std::make_shared<SvgButton>(parameter->name, BinaryData::pencil_svg, juce::Colours::white, juce::Colours::red);
|
||||||
std::weak_ptr<SvgButton> weakButton = button;
|
std::weak_ptr<SvgButton> weakButton = button;
|
||||||
button->setEdgeIndent(5);
|
button->setEdgeIndent(5);
|
||||||
button->setToggleState(editor.editingCustomFunction, juce::dontSendNotification);
|
button->setToggleState(editor.editingCustomFunction, juce::dontSendNotification);
|
||||||
|
|
|
@ -3,19 +3,33 @@
|
||||||
|
|
||||||
class SvgButton : public juce::DrawableButton, public juce::AudioProcessorParameter::Listener, public juce::AsyncUpdater {
|
class SvgButton : public juce::DrawableButton, public juce::AudioProcessorParameter::Listener, public juce::AsyncUpdater {
|
||||||
public:
|
public:
|
||||||
SvgButton(juce::String name, juce::String svg, juce::String colour, juce::String colourOn, BooleanParameter* toggle = nullptr) : juce::DrawableButton(name, juce::DrawableButton::ButtonStyle::ImageFitted), toggle(toggle) {
|
SvgButton(juce::String name, juce::String svg, juce::Colour colour, juce::Colour colourOn, BooleanParameter* toggle = nullptr) : juce::DrawableButton(name, juce::DrawableButton::ButtonStyle::ImageFitted), toggle(toggle) {
|
||||||
auto doc = juce::XmlDocument::parse(svg);
|
auto doc = juce::XmlDocument::parse(svg);
|
||||||
|
|
||||||
changeSvgColour(doc.get(), colour);
|
changeSvgColour(doc.get(), colour);
|
||||||
normalImage = juce::Drawable::createFromSVG(*doc);
|
normalImage = juce::Drawable::createFromSVG(*doc);
|
||||||
|
changeSvgColour(doc.get(), colour.withBrightness(0.7f));
|
||||||
|
overImage = juce::Drawable::createFromSVG(*doc);
|
||||||
|
changeSvgColour(doc.get(), colour.withBrightness(0.5f));
|
||||||
|
downImage = juce::Drawable::createFromSVG(*doc);
|
||||||
|
changeSvgColour(doc.get(), colour.withBrightness(0.3f));
|
||||||
|
disabledImage = juce::Drawable::createFromSVG(*doc);
|
||||||
|
|
||||||
changeSvgColour(doc.get(), colourOn);
|
changeSvgColour(doc.get(), colourOn);
|
||||||
normalImageOn = juce::Drawable::createFromSVG(*doc);
|
normalImageOn = juce::Drawable::createFromSVG(*doc);
|
||||||
|
changeSvgColour(doc.get(), colourOn.withBrightness(0.7f));
|
||||||
|
overImageOn = juce::Drawable::createFromSVG(*doc);
|
||||||
|
changeSvgColour(doc.get(), colourOn.withBrightness(0.5f));
|
||||||
|
downImageOn = juce::Drawable::createFromSVG(*doc);
|
||||||
|
changeSvgColour(doc.get(), colourOn.withBrightness(0.3f));
|
||||||
|
disabledImageOn = juce::Drawable::createFromSVG(*doc);
|
||||||
|
|
||||||
getLookAndFeel().setColour(juce::DrawableButton::backgroundOnColourId, juce::Colours::transparentWhite);
|
getLookAndFeel().setColour(juce::DrawableButton::backgroundOnColourId, juce::Colours::transparentWhite);
|
||||||
|
|
||||||
if (colour != colourOn) {
|
if (colour != colourOn) {
|
||||||
setClickingTogglesState(true);
|
setClickingTogglesState(true);
|
||||||
}
|
}
|
||||||
setImages(normalImage.get(), nullptr, nullptr, nullptr, normalImageOn.get());
|
setImages(normalImage.get(), overImage.get(), downImage.get(), disabledImage.get(), normalImageOn.get(), overImageOn.get(), downImageOn.get(), disabledImageOn.get());
|
||||||
|
|
||||||
if (toggle != nullptr) {
|
if (toggle != nullptr) {
|
||||||
toggle->addListener(this);
|
toggle->addListener(this);
|
||||||
|
@ -23,7 +37,7 @@ class SvgButton : public juce::DrawableButton, public juce::AudioProcessorParame
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SvgButton(juce::String name, juce::String svg, juce::String colour) : SvgButton(name, svg, colour, colour) {}
|
SvgButton(juce::String name, juce::String svg, juce::Colour colour) : SvgButton(name, svg, colour, colour) {}
|
||||||
|
|
||||||
~SvgButton() override {
|
~SvgButton() override {
|
||||||
if (toggle != nullptr) {
|
if (toggle != nullptr) {
|
||||||
|
@ -42,12 +56,20 @@ class SvgButton : public juce::DrawableButton, public juce::AudioProcessorParame
|
||||||
}
|
}
|
||||||
private:
|
private:
|
||||||
std::unique_ptr<juce::Drawable> normalImage;
|
std::unique_ptr<juce::Drawable> normalImage;
|
||||||
|
std::unique_ptr<juce::Drawable> overImage;
|
||||||
|
std::unique_ptr<juce::Drawable> downImage;
|
||||||
|
std::unique_ptr<juce::Drawable> disabledImage;
|
||||||
|
|
||||||
std::unique_ptr<juce::Drawable> normalImageOn;
|
std::unique_ptr<juce::Drawable> normalImageOn;
|
||||||
|
std::unique_ptr<juce::Drawable> overImageOn;
|
||||||
|
std::unique_ptr<juce::Drawable> downImageOn;
|
||||||
|
std::unique_ptr<juce::Drawable> disabledImageOn;
|
||||||
|
|
||||||
BooleanParameter* toggle;
|
BooleanParameter* toggle;
|
||||||
|
|
||||||
void changeSvgColour(juce::XmlElement* xml, juce::String colour) {
|
void changeSvgColour(juce::XmlElement* xml, juce::Colour colour) {
|
||||||
forEachXmlChildElement(*xml, xmlnode) {
|
forEachXmlChildElement(*xml, xmlnode) {
|
||||||
xmlnode->setAttribute("fill", colour);
|
xmlnode->setAttribute("fill", '#' + colour.toDisplayString(false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -98,6 +98,10 @@ void VolumeComponent::run() {
|
||||||
if (sampleRate != (int) audioProcessor.currentSampleRate) {
|
if (sampleRate != (int) audioProcessor.currentSampleRate) {
|
||||||
resetBuffer();
|
resetBuffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (buffer.size() == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
consumer = audioProcessor.consumerRegister(buffer);
|
consumer = audioProcessor.consumerRegister(buffer);
|
||||||
audioProcessor.consumerRead(consumer);
|
audioProcessor.consumerRead(consumer);
|
||||||
|
|
|
@ -76,7 +76,7 @@ private:
|
||||||
const double BUFFER_DURATION_SECS = 0.02;
|
const double BUFFER_DURATION_SECS = 0.02;
|
||||||
|
|
||||||
int sampleRate = DEFAULT_SAMPLE_RATE;
|
int sampleRate = DEFAULT_SAMPLE_RATE;
|
||||||
std::vector<float> buffer;
|
std::vector<float> buffer = std::vector<float>(BUFFER_DURATION_SECS * DEFAULT_SAMPLE_RATE);
|
||||||
|
|
||||||
std::atomic<float> leftVolume = 0;
|
std::atomic<float> leftVolume = 0;
|
||||||
std::atomic<float> rightVolume = 0;
|
std::atomic<float> rightVolume = 0;
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
pluginCharacteristicsValue="pluginProducesMidiOut,pluginWantsMidiIn"
|
pluginCharacteristicsValue="pluginProducesMidiOut,pluginWantsMidiIn"
|
||||||
pluginManufacturer="jameshball" aaxIdentifier="sh.ball.oscirender"
|
pluginManufacturer="jameshball" aaxIdentifier="sh.ball.oscirender"
|
||||||
cppLanguageStandard="20" projectLineFeed=" " headerPath="./include"
|
cppLanguageStandard="20" projectLineFeed=" " headerPath="./include"
|
||||||
version="2.0.6" companyName="James H Ball" companyWebsite="https://osci-render.com"
|
version="2.0.7" companyName="James H Ball" companyWebsite="https://osci-render.com"
|
||||||
companyEmail="james@ball.sh">
|
companyEmail="james@ball.sh">
|
||||||
<MAINGROUP id="j5Ge2T" name="osci-render">
|
<MAINGROUP id="j5Ge2T" name="osci-render">
|
||||||
<GROUP id="{5ABCED88-0059-A7AF-9596-DBF91DDB0292}" name="Resources">
|
<GROUP id="{5ABCED88-0059-A7AF-9596-DBF91DDB0292}" name="Resources">
|
||||||
|
@ -22,7 +22,9 @@
|
||||||
<FILE id="PxYKbt" name="microphone.svg" compile="0" resource="1" file="Resources/svg/microphone.svg"/>
|
<FILE id="PxYKbt" name="microphone.svg" compile="0" resource="1" file="Resources/svg/microphone.svg"/>
|
||||||
<FILE id="pSc1mq" name="osci.svg" compile="0" resource="1" file="Resources/svg/osci.svg"/>
|
<FILE id="pSc1mq" name="osci.svg" compile="0" resource="1" file="Resources/svg/osci.svg"/>
|
||||||
<FILE id="D2AI1b" name="pencil.svg" compile="0" resource="1" file="Resources/svg/pencil.svg"/>
|
<FILE id="D2AI1b" name="pencil.svg" compile="0" resource="1" file="Resources/svg/pencil.svg"/>
|
||||||
|
<FILE id="n79IAy" name="record.svg" compile="0" resource="1" file="Resources/svg/record.svg"/>
|
||||||
<FILE id="rXjNlx" name="threshold.svg" compile="0" resource="1" file="Resources/svg/threshold.svg"/>
|
<FILE id="rXjNlx" name="threshold.svg" compile="0" resource="1" file="Resources/svg/threshold.svg"/>
|
||||||
|
<FILE id="rFYmV8" name="timer.svg" compile="0" resource="1" file="Resources/svg/timer.svg"/>
|
||||||
<FILE id="qC6QiP" name="volume.svg" compile="0" resource="1" file="Resources/svg/volume.svg"/>
|
<FILE id="qC6QiP" name="volume.svg" compile="0" resource="1" file="Resources/svg/volume.svg"/>
|
||||||
</GROUP>
|
</GROUP>
|
||||||
<GROUP id="{F8A3D32C-4187-9A2F-5D78-040259957E9B}" name="text">
|
<GROUP id="{F8A3D32C-4187-9A2F-5D78-040259957E9B}" name="text">
|
||||||
|
@ -101,9 +103,12 @@
|
||||||
<FILE id="CdKZCg" name="Matching.h" compile="0" resource="0" file="Source/chinese_postman/Matching.h"/>
|
<FILE id="CdKZCg" name="Matching.h" compile="0" resource="0" file="Source/chinese_postman/Matching.h"/>
|
||||||
</GROUP>
|
</GROUP>
|
||||||
<GROUP id="{CD81913A-7F0E-5898-DA77-5EBEB369DEB1}" name="components">
|
<GROUP id="{CD81913A-7F0E-5898-DA77-5EBEB369DEB1}" name="components">
|
||||||
|
<FILE id="M7wyTt" name="AudioRecordingComponent.h" compile="0" resource="0"
|
||||||
|
file="Source/components/AudioRecordingComponent.h"/>
|
||||||
<FILE id="kUinTt" name="ComponentList.cpp" compile="1" resource="0"
|
<FILE id="kUinTt" name="ComponentList.cpp" compile="1" resource="0"
|
||||||
file="Source/components/ComponentList.cpp"/>
|
file="Source/components/ComponentList.cpp"/>
|
||||||
<FILE id="HGTPEW" name="ComponentList.h" compile="0" resource="0" file="Source/components/ComponentList.h"/>
|
<FILE id="HGTPEW" name="ComponentList.h" compile="0" resource="0" file="Source/components/ComponentList.h"/>
|
||||||
|
<FILE id="IvySRY" name="DoubleTextBox.h" compile="0" resource="0" file="Source/components/DoubleTextBox.h"/>
|
||||||
<FILE id="poPVxL" name="DraggableListBox.cpp" compile="1" resource="0"
|
<FILE id="poPVxL" name="DraggableListBox.cpp" compile="1" resource="0"
|
||||||
file="Source/components/DraggableListBox.cpp"/>
|
file="Source/components/DraggableListBox.cpp"/>
|
||||||
<FILE id="Y9NEGn" name="DraggableListBox.h" compile="0" resource="0"
|
<FILE id="Y9NEGn" name="DraggableListBox.h" compile="0" resource="0"
|
||||||
|
|
Ładowanie…
Reference in New Issue