added output recording feature. moved settings to a separate popup panel instead of a menu
This commit is contained in:
parent
aff633ff39
commit
af5efd8b09
@ -36,9 +36,9 @@ endif()
|
||||
# `project()` command. `project()` sets up some helpful variables that describe source/binary
|
||||
# directories, and the current project version. This is a standard CMake command.
|
||||
|
||||
project(PaulXStretch VERSION 1.5.4)
|
||||
project(PaulXStretch VERSION 1.6.0)
|
||||
|
||||
set(BUILDVERSION 111)
|
||||
set(BUILDVERSION 112)
|
||||
|
||||
|
||||
# If you've installed JUCE somehow (via a package manager, or directly using the CMake install
|
||||
@ -277,12 +277,20 @@ function(sono_add_custom_plugin_target target_name product_name formats is_instr
|
||||
Source/CustomLookAndFeel.h
|
||||
Source/CustomStandaloneFilterApp.cpp
|
||||
Source/CustomStandaloneFilterWindow.h
|
||||
Source/GenericItemChooser.cpp
|
||||
Source/GenericItemChooser.h
|
||||
Source/SonoChoiceButton.cpp
|
||||
Source/SonoChoiceButton.h
|
||||
Source/SonoTextButton.cpp
|
||||
Source/SonoTextButton.h
|
||||
Source/PluginEditor.cpp
|
||||
Source/PluginEditor.h
|
||||
Source/PluginProcessor.cpp
|
||||
Source/PluginProcessor.h
|
||||
Source/RenderSettingsComponent.cpp
|
||||
Source/RenderSettingsComponent.h
|
||||
Source/OptionsView.cpp
|
||||
Source/OptionsView.h
|
||||
Source/envelope_component.cpp
|
||||
Source/envelope_component.h
|
||||
Source/jcdp_envelope.h
|
||||
@ -401,8 +409,10 @@ function(sono_add_custom_plugin_target target_name product_name formats is_instr
|
||||
images/play_icon.svg
|
||||
images/power.svg
|
||||
images/power_sel.svg
|
||||
images/record.svg
|
||||
images/record_active.svg
|
||||
images/record_input.svg
|
||||
images/record_input_active.svg
|
||||
images/record_output.svg
|
||||
images/record_output_active.svg
|
||||
images/skipback_icon.svg
|
||||
)
|
||||
|
||||
|
@ -243,10 +243,12 @@ void CustomLookAndFeel::createTabTextLayout (const TabBarButton& button, float l
|
||||
Colour colour, TextLayout& textLayout)
|
||||
{
|
||||
float hscale = 0.6f;
|
||||
float xhscale = 0.6f;
|
||||
#if JUCE_IOS
|
||||
hscale = 0.5f;
|
||||
xhscale = 0.5f;
|
||||
#endif
|
||||
float fontsize = button.getExtraComponent() != nullptr ? jmin(depth, 32.0f) * hscale : jmin(depth, 32.0f) * hscale;
|
||||
float fontsize = button.getExtraComponent() != nullptr ? jmin(depth, 32.0f) * xhscale : jmin(depth, 32.0f) * hscale;
|
||||
Font font = myFont.withHeight(fontsize * fontScale);
|
||||
font.setUnderline (button.hasKeyboardFocus (false));
|
||||
|
||||
@ -1174,7 +1176,7 @@ void CustomLookAndFeel::drawDrawableButton (Graphics& g, DrawableButton& button,
|
||||
|
||||
int textH = 0;
|
||||
int textW = 0;
|
||||
float imageratio = 0.75f;
|
||||
float imageratio = 0.85f;
|
||||
|
||||
//if (SonoDrawableButton* const sonobutt = dynamic_cast<SonoDrawableButton*> (&button)) {
|
||||
// imageratio = sonobutt->getForegroundImageRatio();
|
||||
|
@ -131,8 +131,8 @@ public:
|
||||
mainWindow->pluginHolder->saveAudioDeviceState();
|
||||
|
||||
if (auto * sonoproc = dynamic_cast<PaulstretchpluginAudioProcessor*>(mainWindow->pluginHolder->processor.get())) {
|
||||
if (sonoproc->getBoolParameter(cpi_pause_enabled)->get() && !sonoproc->isRecordingEnabled()
|
||||
&& !mainWindow->pluginHolder->isInterAppAudioConnected()) {
|
||||
if (sonoproc->getBoolParameter(cpi_pause_enabled)->get() && !sonoproc->isInputRecordingEnabled()
|
||||
&& !sonoproc->isRecordingToFile() && !mainWindow->pluginHolder->isInterAppAudioConnected()) {
|
||||
// shutdown audio engine
|
||||
DBG("not active, shutting down audio");
|
||||
mainWindow->getDeviceManager().closeAudioDevice();
|
||||
|
@ -327,7 +327,7 @@ public:
|
||||
settings->setValue ("audioSetup", xml.get());
|
||||
|
||||
#if ! (JUCE_IOS || JUCE_ANDROID)
|
||||
settings->setValue ("shouldMuteInput", (bool) shouldMuteInput.getValue());
|
||||
// settings->setValue ("shouldMuteInput", (bool) shouldMuteInput.getValue());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@ -343,7 +343,7 @@ public:
|
||||
savedState = settings->getXmlValue ("audioSetup");
|
||||
|
||||
#if ! (JUCE_IOS || JUCE_ANDROID)
|
||||
shouldMuteInput.setValue (settings->getBoolValue ("shouldMuteInput", true));
|
||||
// shouldMuteInput.setValue (settings->getBoolValue ("shouldMuteInput", true));
|
||||
#endif
|
||||
}
|
||||
|
||||
|
354
Source/GenericItemChooser.cpp
Normal file
354
Source/GenericItemChooser.cpp
Normal file
@ -0,0 +1,354 @@
|
||||
// SPDX-License-Identifier: GPLv3-or-later WITH Appstore-exception
|
||||
// Copyright (C) 2020 Jesse Chappell
|
||||
|
||||
|
||||
#include "GenericItemChooser.h"
|
||||
|
||||
enum {
|
||||
nameTextColourId = 0x1002830,
|
||||
currentNameTextColourId = 0x1002850,
|
||||
selectedColourId = 0x1002840,
|
||||
separatorColourId = 0x1002860,
|
||||
disabledColourId = 0x1002870
|
||||
};
|
||||
|
||||
|
||||
CallOutBox& GenericItemChooser::launchPopupChooser(const Array<GenericItemChooserItem> & items, Rectangle<int> targetBounds, Component * targetComponent, GenericItemChooser::Listener * listener, int tag, int selectedIndex, int maxheight, bool dismissSel)
|
||||
{
|
||||
|
||||
auto chooser = std::make_unique<GenericItemChooser>(items, tag);
|
||||
chooser->dismissOnSelected = dismissSel;
|
||||
if (selectedIndex >= 0) {
|
||||
chooser->setCurrentRow(selectedIndex);
|
||||
}
|
||||
|
||||
if (listener) {
|
||||
chooser->addListener(listener);
|
||||
}
|
||||
if (maxheight > 0) {
|
||||
chooser->setMaxHeight(maxheight);
|
||||
}
|
||||
|
||||
|
||||
CallOutBox & box = CallOutBox::launchAsynchronously (std::move(chooser), targetBounds, targetComponent);
|
||||
box.setDismissalMouseClicksAreAlwaysConsumed(true);
|
||||
// box.setArrowSize(0);
|
||||
box.grabKeyboardFocus();
|
||||
|
||||
return box;
|
||||
}
|
||||
|
||||
CallOutBox& GenericItemChooser::launchPopupChooser(const Array<GenericItemChooserItem> & items, juce::Rectangle<int> targetBounds, Component * targetComponent, std::function<void (GenericItemChooser* chooser,int index)> onSelectedFunction, int selectedIndex, int maxheight, bool dismissSel)
|
||||
{
|
||||
auto chooser = std::make_unique<GenericItemChooser>(items, 0);
|
||||
chooser->dismissOnSelected = dismissSel;
|
||||
|
||||
if (selectedIndex >= 0) {
|
||||
chooser->setCurrentRow(selectedIndex);
|
||||
}
|
||||
|
||||
chooser->onSelected = onSelectedFunction;
|
||||
|
||||
if (maxheight > 0) {
|
||||
chooser->setMaxHeight(maxheight);
|
||||
}
|
||||
|
||||
CallOutBox & box = CallOutBox::launchAsynchronously (std::move(chooser), targetBounds, targetComponent);
|
||||
box.setDismissalMouseClicksAreAlwaysConsumed(true);
|
||||
// box.setArrowSize(0);
|
||||
box.grabKeyboardFocus();
|
||||
|
||||
return box;
|
||||
}
|
||||
|
||||
|
||||
GenericItemChooser::GenericItemChooser(const Array<GenericItemChooserItem> & items_, int tag_) : font (16.0, Font::plain), catFont(15.0, Font::plain), items(items_), tag(tag_)
|
||||
|
||||
{
|
||||
currentIndex = -1;
|
||||
#if JUCE_IOS || JUCE_ANDROID
|
||||
rowHeight = 42;
|
||||
#else
|
||||
rowHeight = 32;
|
||||
#endif
|
||||
numRows = items.size();
|
||||
|
||||
// Create our table component and add it to this component..
|
||||
addAndMakeVisible (table);
|
||||
table.setModel (this);
|
||||
|
||||
// give it a border
|
||||
|
||||
table.setColour (ListBox::outlineColourId, Colour::fromFloatRGBA(0.0, 0.0, 0.0, 0.0));
|
||||
table.setColour (ListBox::backgroundColourId, Colour::fromFloatRGBA(0.1, 0.1, 0.1, 1.0f));
|
||||
table.setColour (ListBox::textColourId, Colours::whitesmoke.withAlpha(0.8f));
|
||||
|
||||
setColour (nameTextColourId, Colour::fromFloatRGBA(1.0f, 1.0f, 1.0f, 0.8f));
|
||||
setColour (currentNameTextColourId, Colour::fromFloatRGBA(0.4f, 0.8f, 1.0f, 0.9f));
|
||||
setColour (selectedColourId, Colour (0xff3d70c8).withAlpha(0.5f));
|
||||
setColour (separatorColourId, Colour::fromFloatRGBA(1.0f, 1.0f, 1.0f, 0.5f));
|
||||
setColour (disabledColourId, Colour::fromFloatRGBA(0.7f, 0.7f, 0.7f, 0.7f));
|
||||
|
||||
|
||||
table.setOutlineThickness (0);
|
||||
|
||||
|
||||
|
||||
//table.getViewport()->setScrollOnDragEnabled(true);
|
||||
table.getViewport()->setScrollBarsShown(true, false);
|
||||
table.getViewport()->setScrollOnDragEnabled(true);
|
||||
table.setRowSelectedOnMouseDown(true);
|
||||
table.setRowClickedOnMouseDown(false);
|
||||
table.setMultipleSelectionEnabled (false);
|
||||
table.setRowHeight(rowHeight);
|
||||
|
||||
int newh = (rowHeight) * numRows + 4;
|
||||
setSize(getAutoWidth(), newh);
|
||||
|
||||
}
|
||||
|
||||
GenericItemChooser::~GenericItemChooser()
|
||||
{
|
||||
}
|
||||
|
||||
void GenericItemChooser::setCurrentRow(int index)
|
||||
{
|
||||
currentIndex = index;
|
||||
SparseSet<int> selrows;
|
||||
selrows.addRange(Range<int>(index,index+1));
|
||||
table.setSelectedRows(selrows);
|
||||
table.updateContent();
|
||||
}
|
||||
|
||||
|
||||
void GenericItemChooser::setItems(const Array<GenericItemChooserItem> & items_)
|
||||
{
|
||||
items = items_;
|
||||
numRows = items.size();
|
||||
table.updateContent();
|
||||
|
||||
int newh = (rowHeight) * numRows;
|
||||
|
||||
setSize(getAutoWidth(), newh);
|
||||
|
||||
}
|
||||
|
||||
int GenericItemChooser::getAutoWidth()
|
||||
{
|
||||
int targw = 60;
|
||||
|
||||
for (int i=0; i < items.size(); ++i) {
|
||||
int tsize = font.getStringWidth(items[i].name);
|
||||
if (items[i].image.isValid()) {
|
||||
tsize += rowHeight - 8;
|
||||
}
|
||||
targw = jmax(targw, tsize);
|
||||
}
|
||||
|
||||
return targw + 30;
|
||||
}
|
||||
|
||||
|
||||
void GenericItemChooser::setRowHeight(int ht)
|
||||
{
|
||||
rowHeight = ht;
|
||||
table.setRowHeight(rowHeight);
|
||||
int newh = (rowHeight+2) * numRows;
|
||||
if (maxHeight > 0) {
|
||||
newh = jmin(newh, maxHeight);
|
||||
}
|
||||
setSize(getAutoWidth(), newh);
|
||||
}
|
||||
|
||||
void GenericItemChooser::setMaxHeight(int ht)
|
||||
{
|
||||
maxHeight = ht;
|
||||
|
||||
int newh = (rowHeight+2) * numRows;
|
||||
if (maxHeight > 0) {
|
||||
newh = jmin(newh, maxHeight);
|
||||
}
|
||||
setSize(getAutoWidth(), newh);
|
||||
}
|
||||
|
||||
|
||||
// This is overloaded from TableListBoxModel, and must return the total number of rows in our table
|
||||
int GenericItemChooser::getNumRows()
|
||||
{
|
||||
return numRows;
|
||||
}
|
||||
|
||||
String GenericItemChooser::getNameForRow (int rowNumber)
|
||||
{
|
||||
if (rowNumber< items.size()) {
|
||||
return items[rowNumber].name;
|
||||
}
|
||||
return ListBoxModel::getNameForRow(rowNumber);
|
||||
}
|
||||
|
||||
|
||||
void GenericItemChooser::listBoxItemClicked (int rowNumber, const MouseEvent& e)
|
||||
{
|
||||
|
||||
DBG("listbox clicked");
|
||||
|
||||
if (items[rowNumber].disabled) {
|
||||
// not selectable
|
||||
return;
|
||||
}
|
||||
|
||||
listeners.call (&GenericItemChooser::Listener::genericItemChooserSelected, this, rowNumber);
|
||||
|
||||
if (onSelected) {
|
||||
onSelected(this, rowNumber);
|
||||
}
|
||||
|
||||
if (dismissOnSelected) {
|
||||
if (CallOutBox* const cb = findParentComponentOfClass<CallOutBox>()) {
|
||||
cb->dismiss();
|
||||
}
|
||||
} else {
|
||||
setCurrentRow(rowNumber);
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
void GenericItemChooser::selectedRowsChanged(int lastRowSelected)
|
||||
{
|
||||
// notify listeners
|
||||
DBG("Selected rows changed");
|
||||
}
|
||||
|
||||
void GenericItemChooser::deleteKeyPressed (int)
|
||||
{
|
||||
DBG("delete key pressed");
|
||||
|
||||
}
|
||||
|
||||
void GenericItemChooser::returnKeyPressed (int rowNumber)
|
||||
{
|
||||
DBG("return key pressed: " << rowNumber);
|
||||
|
||||
listeners.call (&GenericItemChooser::Listener::genericItemChooserSelected, this, rowNumber);
|
||||
|
||||
if (rowNumber < items.size() && items[rowNumber].disabled) {
|
||||
// not selectable
|
||||
return;
|
||||
}
|
||||
|
||||
if (onSelected) {
|
||||
onSelected(this, rowNumber);
|
||||
}
|
||||
|
||||
if (dismissOnSelected) {
|
||||
|
||||
if (CallOutBox* const cb = findParentComponentOfClass<CallOutBox>()) {
|
||||
cb->giveAwayKeyboardFocus();
|
||||
cb->dismiss();
|
||||
}
|
||||
} else {
|
||||
setCurrentRow(rowNumber);
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void GenericItemChooser::paintListBoxItem (int rowNumber, Graphics &g, int width, int height, bool rowIsSelected)
|
||||
{
|
||||
|
||||
if (items[rowNumber].separator) {
|
||||
g.setColour (findColour(separatorColourId));
|
||||
g.drawLine(0, 0, width, 0);
|
||||
}
|
||||
|
||||
if (rowIsSelected && !items[rowNumber].disabled) {
|
||||
g.setColour (findColour(selectedColourId));
|
||||
g.fillRect(Rectangle<int>(0,0,width,height));
|
||||
}
|
||||
|
||||
if (items[rowNumber].disabled) {
|
||||
g.setColour (findColour(disabledColourId));
|
||||
}
|
||||
else if (rowNumber == currentIndex) {
|
||||
g.setColour (findColour(currentNameTextColourId));
|
||||
}
|
||||
else {
|
||||
g.setColour (findColour(nameTextColourId));
|
||||
}
|
||||
|
||||
g.setFont (font);
|
||||
|
||||
int imagewidth = 0;
|
||||
|
||||
|
||||
|
||||
if (rowNumber < items.size()) {
|
||||
if (items[rowNumber].image.isValid()) {
|
||||
imagewidth = height-8;
|
||||
//g.drawImage(items[rowNumber].image, Rectangle<float>(2, 2, imagewidth, height - 4));
|
||||
g.drawImageWithin(items[rowNumber].image, 2, 4, imagewidth, imagewidth, RectanglePlacement(RectanglePlacement::centred|RectanglePlacement::onlyReduceInSize));
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
|
||||
int imagewidth = height * 0.75;
|
||||
|
||||
if (rowNumber == 0) {
|
||||
g.drawImageWithin(wheelImage, 2, 2, imagewidth, height - 4, RectanglePlacement::centred|RectanglePlacement::onlyReduceInSize);
|
||||
}
|
||||
else if (rowNumber == 1) {
|
||||
g.drawImageWithin(stringImage, 2, 2, imagewidth, height - 4, RectanglePlacement::centred|RectanglePlacement::onlyReduceInSize);
|
||||
}
|
||||
else {
|
||||
g.drawImageWithin(keyboardImage, 2, 2, imagewidth, height - 4, RectanglePlacement::centred|RectanglePlacement::onlyReduceInSize);
|
||||
}
|
||||
*/
|
||||
|
||||
String text = items[rowNumber].name;
|
||||
//DBG("Paint " << text);
|
||||
|
||||
|
||||
//g.drawFittedText (text, imagewidth + 10, 0, width - (imagewidth+8), height, Justification::centredLeft, 1, 0.5);
|
||||
g.drawFittedText (text, imagewidth + 8, 0, width - (imagewidth+8), height, Justification::centredLeft, 1, 0.5);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void GenericItemChooser::buttonClicked (Button* buttonThatWasClicked)
|
||||
{
|
||||
//AppState * app = AppState::getInstance();
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
void GenericItemChooser::paint (Graphics& g)
|
||||
{
|
||||
//g.fillAll (Colour (0xff000000));
|
||||
}
|
||||
|
||||
|
||||
//==============================================================================
|
||||
void GenericItemChooser::resized()
|
||||
{
|
||||
// position our table with a gap around its edge
|
||||
int keywidth = 50;
|
||||
int bottomButtHeight = 40;
|
||||
|
||||
table.setBoundsInset (BorderSize<int>(2,2,2,2));
|
||||
|
||||
// table.setTouchScrollScale(1.0f / AppState::getInstance()->scaleFactor);
|
||||
|
||||
//int bottbuttwidth = (int) (0.3333f * (getWidth()-keywidth));
|
||||
//int addbuttwidth = (getWidth()-keywidth) - 2*bottbuttwidth - 8;
|
||||
|
||||
//allButton->setBounds(keywidth+2, getHeight()-bottomButtHeight-4, bottbuttwidth, bottomButtHeight);
|
||||
//favsButton->setBounds(keywidth+2+bottbuttwidth, getHeight()-bottomButtHeight-4, bottbuttwidth, bottomButtHeight);
|
||||
//addFavButton->setBounds(getWidth()-addbuttwidth-4, getHeight()-bottomButtHeight-4, addbuttwidth, bottomButtHeight);
|
||||
|
||||
}
|
||||
|
110
Source/GenericItemChooser.h
Normal file
110
Source/GenericItemChooser.h
Normal file
@ -0,0 +1,110 @@
|
||||
// SPDX-License-Identifier: GPLv3-or-later WITH Appstore-exception
|
||||
// Copyright (C) 2020 Jesse Chappell
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "JuceHeader.h"
|
||||
|
||||
|
||||
struct GenericItemChooserItem
|
||||
{
|
||||
struct UserData {
|
||||
virtual ~UserData() {}
|
||||
};
|
||||
|
||||
GenericItemChooserItem() : image(Image()) {}
|
||||
GenericItemChooserItem(const String & name_, const Image & image_=Image(), std::shared_ptr<UserData> udata = nullptr, bool withSeparator=false, bool disabled_=false) : name(name_), image(image_), userdata(udata), separator(withSeparator), disabled(disabled_) {}
|
||||
String name;
|
||||
Image image;
|
||||
std::shared_ptr<UserData> userdata;
|
||||
bool separator = false;
|
||||
bool disabled = false;
|
||||
};
|
||||
|
||||
class GenericItemChooser : public Component, public ListBoxModel, public Button::Listener
|
||||
{
|
||||
public:
|
||||
GenericItemChooser(const Array<GenericItemChooserItem> & items_, int tag=0);
|
||||
virtual ~GenericItemChooser();
|
||||
|
||||
class Listener {
|
||||
public:
|
||||
virtual ~Listener() {}
|
||||
virtual void genericItemChooserSelected(GenericItemChooser *comp, int index) {}
|
||||
};
|
||||
|
||||
void setItems(const Array<GenericItemChooserItem> & items);
|
||||
const Array<GenericItemChooserItem> & getItems() const { return items; }
|
||||
|
||||
// This is overloaded from TableListBoxModel, and must return the total number of rows in our table
|
||||
int getNumRows() override;
|
||||
String getNameForRow (int rowNumber) override;
|
||||
void deleteKeyPressed (int) override;
|
||||
void returnKeyPressed (int) override;
|
||||
|
||||
void paintListBoxItem (int rowNumber, Graphics &g, int width, int height, bool rowIsSelected) override;
|
||||
|
||||
|
||||
// This is overloaded from TableListBoxModel, and must update any custom components that we're using
|
||||
//Component* refreshComponentForCell (int rowNumber, int columnId, bool /*isRowSelected*/,
|
||||
// Component* existingComponentToUpdate) override;
|
||||
|
||||
|
||||
void setCurrentRow(int index);
|
||||
int getCurrentRow() const { return currentIndex; }
|
||||
|
||||
//==============================================================================
|
||||
void resized() override;
|
||||
|
||||
void listBoxItemClicked (int rowNumber, const MouseEvent& e) override;
|
||||
void selectedRowsChanged(int lastRowSelected) override;
|
||||
|
||||
void buttonClicked (Button* buttonThatWasClicked) override;
|
||||
void paint (Graphics& g) override;
|
||||
|
||||
void addListener(Listener * listener) { listeners.add(listener); }
|
||||
void removeListener(Listener * listener) { listeners.remove(listener); }
|
||||
|
||||
void setRowHeight(int ht);
|
||||
int getRowHeight() const { return rowHeight; }
|
||||
|
||||
void setMaxHeight(int ht);
|
||||
int getMaxHeight() const { return maxHeight; }
|
||||
|
||||
void setTag(int tag_) { tag = tag_;}
|
||||
int getTag() const { return tag; }
|
||||
|
||||
static CallOutBox& launchPopupChooser(const Array<GenericItemChooserItem> & items, juce::Rectangle<int> targetBounds, Component * targetComponent, GenericItemChooser::Listener * listener, int tag = 0, int selectedIndex=-1, int maxheight=0, bool dismissSel=true);
|
||||
|
||||
static CallOutBox& launchPopupChooser(const Array<GenericItemChooserItem> & items, juce::Rectangle<int> targetBounds, Component * targetComponent, std::function<void (GenericItemChooser* chooser,int index)> onSelectedFunction, int selectedIndex=-1, int maxheight=0, bool dismissSel=true);
|
||||
|
||||
|
||||
std::function<void (GenericItemChooser* chooser,int index)> onSelected;
|
||||
|
||||
bool dismissOnSelected = true;
|
||||
|
||||
private:
|
||||
|
||||
int getAutoWidth();
|
||||
|
||||
ListenerList<Listener> listeners;
|
||||
|
||||
ListBox table; // the table component itself
|
||||
Font font;
|
||||
Font catFont;
|
||||
|
||||
int numRows; // The number of rows of data we've got
|
||||
bool sortDirection;
|
||||
int selectedRow;
|
||||
int rowHeight;
|
||||
int maxHeight = 0;
|
||||
|
||||
//StringArray items;
|
||||
Array<GenericItemChooserItem> items;
|
||||
|
||||
int currentIndex;
|
||||
int tag;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GenericItemChooser)
|
||||
};
|
843
Source/OptionsView.cpp
Normal file
843
Source/OptionsView.cpp
Normal file
@ -0,0 +1,843 @@
|
||||
// SPDX-License-Identifier: GPLv3-or-later WITH Appstore-exception
|
||||
// Copyright (C) 2021 Jesse Chappell
|
||||
|
||||
#include "OptionsView.h"
|
||||
|
||||
|
||||
|
||||
class PaulxstretchOptionsTabbedComponent : public TabbedComponent
|
||||
{
|
||||
public:
|
||||
PaulxstretchOptionsTabbedComponent(TabbedButtonBar::Orientation orientation, OptionsView & editor_) : TabbedComponent(orientation), editor(editor_) {
|
||||
|
||||
}
|
||||
|
||||
void currentTabChanged (int newCurrentTabIndex, const String& newCurrentTabName) override {
|
||||
|
||||
editor.optionsTabChanged(newCurrentTabIndex);
|
||||
}
|
||||
|
||||
protected:
|
||||
OptionsView & editor;
|
||||
|
||||
};
|
||||
|
||||
|
||||
enum {
|
||||
nameTextColourId = 0x1002830,
|
||||
selectedColourId = 0x1002840,
|
||||
separatorColourId = 0x1002850,
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
OptionsView::OptionsView(PaulstretchpluginAudioProcessor& proc, std::function<AudioDeviceManager*()> getaudiodevicemanager)
|
||||
: Component(), getAudioDeviceManager(getaudiodevicemanager), processor(proc), smallLNF(14), sonoSliderLNF(13)
|
||||
{
|
||||
setColour (nameTextColourId, Colour::fromFloatRGBA(1.0f, 1.0f, 1.0f, 0.9f));
|
||||
setColour (selectedColourId, Colour::fromFloatRGBA(0.0f, 0.4f, 0.8f, 0.5f));
|
||||
setColour (separatorColourId, Colour::fromFloatRGBA(0.3f, 0.3f, 0.3f, 0.3f));
|
||||
|
||||
sonoSliderLNF.textJustification = Justification::centredRight;
|
||||
sonoSliderLNF.sliderTextJustification = Justification::centredRight;
|
||||
|
||||
mOptionsComponent = std::make_unique<Component>();
|
||||
|
||||
mSettingsTab = std::make_unique<TabbedComponent>(TabbedButtonBar::Orientation::TabsAtTop);
|
||||
mSettingsTab->setTabBarDepth(36);
|
||||
mSettingsTab->setOutline(0);
|
||||
mSettingsTab->getTabbedButtonBar().setMinimumTabScaleFactor(0.1f);
|
||||
//mSettingsTab->addComponentListener(this);
|
||||
|
||||
|
||||
mOptionsLoadFileWithPluginButton = std::make_unique<ToggleButton>(TRANS("Load file with plugin state"));
|
||||
mOptionsLoadFileWithPluginButton->addListener(this);
|
||||
mOptionsLoadFileWithPluginButton->onClick = [this] () {
|
||||
toggleBool(processor.m_load_file_with_state);
|
||||
};
|
||||
|
||||
mOptionsPlayWithTransportButton = std::make_unique<ToggleButton>(TRANS("Play when host transport running"));
|
||||
mOptionsPlayWithTransportButton->onClick = [this] () {
|
||||
toggleBool(processor.m_play_when_host_plays);
|
||||
};
|
||||
|
||||
mOptionsCaptureWithTransportButton = std::make_unique<ToggleButton>(TRANS("Capture when host transport running"));
|
||||
mOptionsCaptureWithTransportButton->onClick = [this] () {
|
||||
toggleBool(processor.m_capture_when_host_plays);
|
||||
};
|
||||
|
||||
mOptionsRestorePlayStateButton = std::make_unique<ToggleButton>(TRANS("Restore playing state"));
|
||||
mOptionsRestorePlayStateButton->onClick = [this] () {
|
||||
toggleBool(processor.m_restore_playstate);
|
||||
};
|
||||
|
||||
mOptionsMutePassthroughWhenCaptureButton = std::make_unique<ToggleButton>(TRANS("Mute passthrough while capturing"));
|
||||
mOptionsMutePassthroughWhenCaptureButton->onClick = [this] () {
|
||||
toggleBool(processor.m_mute_while_capturing);
|
||||
};
|
||||
|
||||
mOptionsMuteProcessedWhenCaptureButton = std::make_unique<ToggleButton>(TRANS("Mute processed audio output while capturing"));
|
||||
mOptionsMuteProcessedWhenCaptureButton->onClick = [this] () {
|
||||
toggleBool(processor.m_mute_processed_while_capturing);
|
||||
};
|
||||
|
||||
mOptionsSaveCaptureToDiskButton = std::make_unique<ToggleButton>(TRANS("Save captured audio to disk"));
|
||||
mOptionsSaveCaptureToDiskButton->onClick = [this] () {
|
||||
toggleBool(processor.m_save_captured_audio);
|
||||
};
|
||||
|
||||
mOptionsEndRecordingAfterMaxButton = std::make_unique<ToggleButton>(TRANS("End recording after capturing max length"));
|
||||
mOptionsEndRecordingAfterMaxButton->onClick = [this] () {
|
||||
toggleBool(processor.m_auto_finish_record);
|
||||
};
|
||||
|
||||
mOptionsSliderSnapToMouseButton = std::make_unique<ToggleButton>(TRANS("Sliders jump to position"));
|
||||
mOptionsSliderSnapToMouseButton->onClick = [this] () {
|
||||
toggleBool(processor.m_use_jumpsliders);
|
||||
if (updateSliderSnap)
|
||||
updateSliderSnap();
|
||||
};
|
||||
|
||||
|
||||
mOptionsShowTechnicalInfoButton = std::make_unique<ToggleButton>(TRANS("Show technical info in waveform"));
|
||||
mOptionsShowTechnicalInfoButton->onClick = [this] () {
|
||||
toggleBool(processor.m_show_technical_info);
|
||||
};
|
||||
|
||||
|
||||
|
||||
mRecFormatChoice = std::make_unique<SonoChoiceButton>();
|
||||
mRecFormatChoice->addChoiceListener(this);
|
||||
mRecFormatChoice->addItem(TRANS("FLAC"), PaulstretchpluginAudioProcessor::FileFormatFLAC);
|
||||
mRecFormatChoice->addItem(TRANS("WAV"), PaulstretchpluginAudioProcessor::FileFormatWAV);
|
||||
mRecFormatChoice->addItem(TRANS("OGG"), PaulstretchpluginAudioProcessor::FileFormatOGG);
|
||||
|
||||
mRecBitsChoice = std::make_unique<SonoChoiceButton>();
|
||||
mRecBitsChoice->addChoiceListener(this);
|
||||
mRecBitsChoice->addItem(TRANS("16 bit"), 16);
|
||||
mRecBitsChoice->addItem(TRANS("24 bit"), 24);
|
||||
mRecBitsChoice->addItem(TRANS("32 bit"), 32);
|
||||
|
||||
|
||||
mRecFormatStaticLabel = std::make_unique<Label>("", TRANS("Recorded File Format:"));
|
||||
configLabel(mRecFormatStaticLabel.get(), false);
|
||||
mRecFormatStaticLabel->setJustificationType(Justification::centredRight);
|
||||
|
||||
|
||||
mRecLocationStaticLabel = std::make_unique<Label>("", TRANS("Record Location:"));
|
||||
configLabel(mRecLocationStaticLabel.get(), false);
|
||||
mRecLocationStaticLabel->setJustificationType(Justification::centredRight);
|
||||
|
||||
mRecLocationButton = std::make_unique<TextButton>("fileloc");
|
||||
mRecLocationButton->setButtonText("");
|
||||
mRecLocationButton->setLookAndFeel(&smallLNF);
|
||||
mRecLocationButton->addListener(this);
|
||||
|
||||
mOptionsCaptureBufferStaticLabel = std::make_unique<Label>("", TRANS("Capture Buffer Length:"));
|
||||
configLabel(mOptionsCaptureBufferStaticLabel.get(), false);
|
||||
mOptionsCaptureBufferStaticLabel->setJustificationType(Justification::centredRight);
|
||||
|
||||
mCaptureBufferChoice = std::make_unique<SonoChoiceButton>();
|
||||
mCaptureBufferChoice->addChoiceListener(this);
|
||||
mCaptureBufferChoice->addItem(TRANS("2 seconds"), 2);
|
||||
mCaptureBufferChoice->addItem(TRANS("5 seconds"), 5);
|
||||
mCaptureBufferChoice->addItem(TRANS("10 seconds"), 10);
|
||||
mCaptureBufferChoice->addItem(TRANS("30 seconds"), 30);
|
||||
mCaptureBufferChoice->addItem(TRANS("60 seconds"), 60);
|
||||
mCaptureBufferChoice->addItem(TRANS("120 seconds"), 120);
|
||||
|
||||
|
||||
mOptionsDumpPresetToClipboardButton = std::make_unique<TextButton>("dump");
|
||||
mOptionsDumpPresetToClipboardButton->setButtonText(TRANS("Dump Preset to clipboard"));
|
||||
mOptionsDumpPresetToClipboardButton->setLookAndFeel(&smallLNF);
|
||||
mOptionsDumpPresetToClipboardButton->addListener(this);
|
||||
|
||||
mOptionsResetParamsButton = std::make_unique<TextButton>("reset");
|
||||
mOptionsResetParamsButton->setButtonText(TRANS("Reset Parameters"));
|
||||
mOptionsResetParamsButton->setLookAndFeel(&smallLNF);
|
||||
mOptionsResetParamsButton->onClick = [this] () {
|
||||
processor.resetParameters();
|
||||
};
|
||||
|
||||
|
||||
|
||||
addAndMakeVisible(mSettingsTab.get());
|
||||
|
||||
mOptionsComponent->addAndMakeVisible(mCaptureBufferChoice.get());
|
||||
mOptionsComponent->addAndMakeVisible(mOptionsCaptureBufferStaticLabel.get());
|
||||
mOptionsComponent->addAndMakeVisible(mOptionsLoadFileWithPluginButton.get());
|
||||
mOptionsComponent->addAndMakeVisible(mOptionsPlayWithTransportButton.get());
|
||||
mOptionsComponent->addAndMakeVisible(mOptionsCaptureWithTransportButton.get());
|
||||
mOptionsComponent->addAndMakeVisible(mOptionsRestorePlayStateButton.get());
|
||||
|
||||
mOptionsComponent->addAndMakeVisible(mOptionsMutePassthroughWhenCaptureButton.get());
|
||||
mOptionsComponent->addAndMakeVisible(mOptionsMuteProcessedWhenCaptureButton.get());
|
||||
mOptionsComponent->addAndMakeVisible(mOptionsSaveCaptureToDiskButton.get());
|
||||
mOptionsComponent->addAndMakeVisible(mOptionsEndRecordingAfterMaxButton.get());
|
||||
mOptionsComponent->addAndMakeVisible(mOptionsSliderSnapToMouseButton.get());
|
||||
#if JUCE_DEBUG
|
||||
mOptionsComponent->addAndMakeVisible(mOptionsDumpPresetToClipboardButton.get());
|
||||
#endif
|
||||
mOptionsComponent->addAndMakeVisible(mOptionsShowTechnicalInfoButton.get());
|
||||
mOptionsComponent->addAndMakeVisible(mOptionsResetParamsButton.get());
|
||||
|
||||
mOptionsComponent->addAndMakeVisible(mOptionsSliderSnapToMouseButton.get());
|
||||
|
||||
mOptionsComponent->addAndMakeVisible(mRecFormatChoice.get());
|
||||
mOptionsComponent->addAndMakeVisible(mRecFormatChoice.get());
|
||||
mOptionsComponent->addAndMakeVisible(mRecBitsChoice.get());
|
||||
mOptionsComponent->addAndMakeVisible(mRecFormatStaticLabel.get());
|
||||
mOptionsComponent->addAndMakeVisible(mRecLocationButton.get());
|
||||
mOptionsComponent->addAndMakeVisible(mRecLocationStaticLabel.get());
|
||||
|
||||
|
||||
if (JUCEApplicationBase::isStandaloneApp() && getAudioDeviceManager && getAudioDeviceManager())
|
||||
{
|
||||
if (!mAudioDeviceSelector) {
|
||||
int minNumInputs = std::numeric_limits<int>::max(), maxNumInputs = 0,
|
||||
minNumOutputs = std::numeric_limits<int>::max(), maxNumOutputs = 0;
|
||||
|
||||
auto updateMinAndMax = [] (int newValue, int& minValue, int& maxValue)
|
||||
{
|
||||
minValue = jmin (minValue, newValue);
|
||||
maxValue = jmax (maxValue, newValue);
|
||||
};
|
||||
|
||||
/*
|
||||
if (channelConfiguration.size() > 0)
|
||||
{
|
||||
auto defaultConfig = channelConfiguration.getReference (0);
|
||||
updateMinAndMax ((int) defaultConfig.numIns, minNumInputs, maxNumInputs);
|
||||
updateMinAndMax ((int) defaultConfig.numOuts, minNumOutputs, maxNumOutputs);
|
||||
}
|
||||
*/
|
||||
|
||||
if (auto* bus = processor.getBus (true, 0)) {
|
||||
auto maxsup = bus->getMaxSupportedChannels(128);
|
||||
updateMinAndMax (maxsup, minNumInputs, maxNumInputs);
|
||||
updateMinAndMax (bus->getDefaultLayout().size(), minNumInputs, maxNumInputs);
|
||||
if (bus->isNumberOfChannelsSupported(1)) {
|
||||
updateMinAndMax (1, minNumInputs, maxNumInputs);
|
||||
}
|
||||
if (bus->isNumberOfChannelsSupported(0)) {
|
||||
updateMinAndMax (0, minNumInputs, maxNumInputs);
|
||||
}
|
||||
}
|
||||
|
||||
if (auto* bus = processor.getBus (false, 0)) {
|
||||
auto maxsup = bus->getMaxSupportedChannels(128);
|
||||
updateMinAndMax (maxsup, minNumOutputs, maxNumOutputs);
|
||||
updateMinAndMax (bus->getDefaultLayout().size(), minNumOutputs, maxNumOutputs);
|
||||
if (bus->isNumberOfChannelsSupported(1)) {
|
||||
updateMinAndMax (1, minNumOutputs, maxNumOutputs);
|
||||
}
|
||||
if (bus->isNumberOfChannelsSupported(0)) {
|
||||
updateMinAndMax (0, minNumOutputs, maxNumOutputs);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
minNumInputs = jmin (minNumInputs, maxNumInputs);
|
||||
minNumOutputs = jmin (minNumOutputs, maxNumOutputs);
|
||||
|
||||
|
||||
|
||||
mAudioDeviceSelector = std::make_unique<AudioDeviceSelectorComponent>(*getAudioDeviceManager(),
|
||||
minNumInputs, maxNumInputs,
|
||||
minNumOutputs, maxNumOutputs,
|
||||
false, // show MIDI input
|
||||
false,
|
||||
false, false);
|
||||
|
||||
#if JUCE_IOS || JUCE_ANDROID
|
||||
mAudioDeviceSelector->setItemHeight(44);
|
||||
#endif
|
||||
|
||||
mAudioOptionsViewport = std::make_unique<Viewport>();
|
||||
mAudioOptionsViewport->setViewedComponent(mAudioDeviceSelector.get(), false);
|
||||
|
||||
|
||||
}
|
||||
|
||||
if (firsttime) {
|
||||
mSettingsTab->addTab(TRANS("AUDIO"), Colour::fromFloatRGBA(0.1, 0.1, 0.1, 1.0), mAudioOptionsViewport.get(), false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
createAbout();
|
||||
|
||||
mOtherOptionsViewport = std::make_unique<Viewport>();
|
||||
mOtherOptionsViewport->setViewedComponent(mOptionsComponent.get(), false);
|
||||
|
||||
mSettingsTab->addTab(TRANS("OPTIONS"),Colour::fromFloatRGBA(0.1, 0.1, 0.1, 1.0), mOtherOptionsViewport.get(), false);
|
||||
mSettingsTab->addTab(TRANS("ABOUT"), Colour::fromFloatRGBA(0.1, 0.1, 0.1, 1.0), mAboutViewport.get(), false);
|
||||
|
||||
setFocusContainerType(FocusContainerType::keyboardFocusContainer);
|
||||
|
||||
mSettingsTab->setFocusContainerType(FocusContainerType::none);
|
||||
mSettingsTab->getTabbedButtonBar().setFocusContainerType(FocusContainerType::none);
|
||||
mSettingsTab->getTabbedButtonBar().setWantsKeyboardFocus(true);
|
||||
mSettingsTab->setWantsKeyboardFocus(true);
|
||||
for (int i=0; i < mSettingsTab->getTabbedButtonBar().getNumTabs(); ++i) {
|
||||
if (auto tabbut = mSettingsTab->getTabbedButtonBar().getTabButton(i)) {
|
||||
tabbut->setRadioGroupId(3);
|
||||
tabbut->setWantsKeyboardFocus(true);
|
||||
}
|
||||
if (auto tabcomp = mSettingsTab->getTabContentComponent(i)) {
|
||||
tabcomp->setFocusContainerType(FocusContainerType::focusContainer);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
OptionsView::~OptionsView() {}
|
||||
|
||||
juce::Rectangle<int> OptionsView::getMinimumContentBounds() const {
|
||||
int defWidth = 200;
|
||||
int defHeight = 100;
|
||||
return Rectangle<int>(0,0,defWidth,defHeight);
|
||||
}
|
||||
|
||||
juce::Rectangle<int> OptionsView::getPreferredContentBounds() const
|
||||
{
|
||||
return Rectangle<int> (0, 0, 300, prefHeight);
|
||||
}
|
||||
|
||||
|
||||
void OptionsView::timerCallback(int timerid)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void OptionsView::grabInitialFocus()
|
||||
{
|
||||
if (auto * butt = mSettingsTab->getTabbedButtonBar().getTabButton(mSettingsTab->getCurrentTabIndex())) {
|
||||
butt->setWantsKeyboardFocus(true);
|
||||
butt->grabKeyboardFocus();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void OptionsView::configLabel(Label *label, bool val)
|
||||
{
|
||||
if (val) {
|
||||
label->setFont(12);
|
||||
label->setColour(Label::textColourId, Colour(0x90eeeeee));
|
||||
label->setJustificationType(Justification::centred);
|
||||
}
|
||||
else {
|
||||
label->setFont(14);
|
||||
//label->setColour(Label::textColourId, Colour(0xaaeeeeee));
|
||||
label->setJustificationType(Justification::centredLeft);
|
||||
}
|
||||
}
|
||||
|
||||
void OptionsView::configLevelSlider(Slider * slider)
|
||||
{
|
||||
//slider->setVelocityBasedMode(true);
|
||||
//slider->setVelocityModeParameters(2.5, 1, 0.05);
|
||||
//slider->setTextBoxStyle(Slider::NoTextBox, true, 40, 18);
|
||||
slider->setSliderStyle(Slider::SliderStyle::LinearHorizontal);
|
||||
slider->setTextBoxStyle(Slider::TextBoxAbove, true, 50, 14);
|
||||
slider->setMouseDragSensitivity(128);
|
||||
slider->setScrollWheelEnabled(false);
|
||||
//slider->setPopupDisplayEnabled(true, false, this);
|
||||
slider->setColour(Slider::textBoxBackgroundColourId, Colours::transparentBlack);
|
||||
slider->setColour(Slider::textBoxOutlineColourId, Colours::transparentBlack);
|
||||
slider->setColour(Slider::textBoxTextColourId, Colour(0x90eeeeee));
|
||||
slider->setColour(TooltipWindow::textColourId, Colour(0xf0eeeeee));
|
||||
|
||||
slider->setLookAndFeel(&sonoSliderLNF);
|
||||
}
|
||||
|
||||
void OptionsView::configEditor(TextEditor *editor, bool passwd)
|
||||
{
|
||||
editor->addListener(this);
|
||||
if (passwd) {
|
||||
editor->setIndents(8, 6);
|
||||
} else {
|
||||
editor->setIndents(8, 8);
|
||||
}
|
||||
}
|
||||
|
||||
void OptionsView::updateState(bool ignorecheck)
|
||||
{
|
||||
mRecFormatChoice->setSelectedId((int)processor.getDefaultRecordingFormat(), dontSendNotification);
|
||||
mRecBitsChoice->setSelectedId((int)processor.getDefaultRecordingBitsPerSample(), dontSendNotification);
|
||||
|
||||
File recdir = File(processor.getDefaultRecordingDirectory());
|
||||
String dispath = recdir.getRelativePathFrom(File::getSpecialLocation (File::userHomeDirectory));
|
||||
if (dispath.startsWith(".")) dispath = processor.getDefaultRecordingDirectory();
|
||||
mRecLocationButton->setButtonText(dispath);
|
||||
|
||||
mOptionsLoadFileWithPluginButton->setToggleState(processor.m_load_file_with_state, dontSendNotification);
|
||||
mOptionsPlayWithTransportButton->setToggleState(processor.m_play_when_host_plays, dontSendNotification);
|
||||
mOptionsCaptureWithTransportButton->setToggleState(processor.m_capture_when_host_plays, dontSendNotification);
|
||||
mOptionsRestorePlayStateButton->setToggleState(processor.m_restore_playstate, dontSendNotification);
|
||||
mOptionsMutePassthroughWhenCaptureButton->setToggleState(processor.m_mute_while_capturing, dontSendNotification);
|
||||
mOptionsMuteProcessedWhenCaptureButton->setToggleState(processor.m_mute_processed_while_capturing, dontSendNotification);
|
||||
mOptionsSaveCaptureToDiskButton->setToggleState(processor.m_save_captured_audio, dontSendNotification);
|
||||
mOptionsEndRecordingAfterMaxButton->setToggleState(processor.m_auto_finish_record, dontSendNotification);
|
||||
mOptionsSliderSnapToMouseButton->setToggleState(processor.m_use_jumpsliders, dontSendNotification);
|
||||
mOptionsShowTechnicalInfoButton->setToggleState(processor.m_show_technical_info, dontSendNotification);
|
||||
|
||||
auto caplen = processor.getFloatParameter(cpi_max_capture_len)->get();
|
||||
mCaptureBufferChoice->setSelectedId((int)caplen, dontSendNotification);
|
||||
}
|
||||
|
||||
void OptionsView::createAbout()
|
||||
{
|
||||
mAboutLabel = std::make_unique<Label>();
|
||||
|
||||
String fftlib;
|
||||
#if PS_USE_VDSP_FFT
|
||||
fftlib = "vDSP";
|
||||
#elif PS_USE_PFFFT
|
||||
fftlib = "pffft";
|
||||
#else
|
||||
fftlib = fftwf_version;
|
||||
#endif
|
||||
String juceversiontxt = String("JUCE ") + String(JUCE_MAJOR_VERSION) + "." + String(JUCE_MINOR_VERSION);
|
||||
String title = String(JucePlugin_Name) + " " + String(JucePlugin_VersionString);
|
||||
#ifdef JUCE_DEBUG
|
||||
title += " (DEBUG)";
|
||||
#endif
|
||||
String vstInfo;
|
||||
if (processor.wrapperType == AudioProcessor::wrapperType_VST ||
|
||||
processor.wrapperType == AudioProcessor::wrapperType_VST3)
|
||||
vstInfo = "VST Plug-In Technology by Steinberg.\n\n";
|
||||
PluginHostType host;
|
||||
|
||||
String text = title + "\n\n" +
|
||||
"Plugin/Application for extreme time stretching and other sound processing\nBuilt on " + String(__DATE__) + " " + String(__TIME__) + "\n"
|
||||
"Copyright (C) 2006-2011 Nasca Octavian Paul, Tg. Mures, Romania\n"
|
||||
"(C) 2017-2021 Xenakios\n"
|
||||
"(C) 2022 Jesse Chappell\n\n"
|
||||
+vstInfo;
|
||||
|
||||
if (fftlib.isNotEmpty())
|
||||
text += String("Using ") + fftlib + String(" for FFT\n\n");
|
||||
|
||||
|
||||
#if !JUCE_IOS
|
||||
if (PluginHostType::getPluginLoadedAs() == AudioProcessor::wrapperType_AAX) {
|
||||
text += juceversiontxt + String("\n\n");
|
||||
}
|
||||
else {
|
||||
text += juceversiontxt + String(" used under the GPL license.\n\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
text += String("GPL licensed source code at : https://github.com/essej/paulxstretch\n");
|
||||
|
||||
if (host.type != juce::PluginHostType::UnknownHost) {
|
||||
text += String("Running in : ") + host.getHostDescription()+ String("\n");
|
||||
}
|
||||
|
||||
mAboutLabel->setJustificationType(Justification::centred);
|
||||
mAboutLabel->setText(text, dontSendNotification);
|
||||
|
||||
mAboutViewport = std::make_unique<Viewport>();
|
||||
mAboutViewport->setViewedComponent(mAboutLabel.get(), false);
|
||||
|
||||
//std::unique_ptr<SettingsComponent> contptr(content);
|
||||
int defWidth = 450;
|
||||
int defHeight = 350;
|
||||
#if JUCE_IOS
|
||||
defWidth = 320;
|
||||
defHeight = 350;
|
||||
#endif
|
||||
|
||||
mAboutLabel->setSize (defWidth, defHeight);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
void OptionsView::resized()
|
||||
{
|
||||
int leftmargin = 10;
|
||||
int minw = 100;
|
||||
int minKnobWidth = 50;
|
||||
int minSliderWidth = 50;
|
||||
int minPannerWidth = 40;
|
||||
int maxPannerWidth = 100;
|
||||
int minitemheight = 36;
|
||||
int knobitemheight = 80;
|
||||
int minpassheight = 30;
|
||||
int setitemheight = 36;
|
||||
int minButtonWidth = 90;
|
||||
int sliderheight = 44;
|
||||
int inmeterwidth = 22 ;
|
||||
int outmeterwidth = 22 ;
|
||||
int servLabelWidth = 72;
|
||||
int iconheight = 24;
|
||||
int iconwidth = iconheight;
|
||||
int knoblabelheight = 18;
|
||||
int panbuttwidth = 26;
|
||||
|
||||
#if JUCE_IOS || JUCE_ANDROID
|
||||
// make the button heights a bit more for touchscreen purposes
|
||||
minitemheight = 44;
|
||||
knobitemheight = 90;
|
||||
minpassheight = 38;
|
||||
panbuttwidth = 32;
|
||||
#endif
|
||||
|
||||
FlexBox mainBox;
|
||||
mainBox.flexDirection = FlexBox::Direction::column;
|
||||
mainBox.items.add(FlexItem(100, minitemheight, *mSettingsTab).withMargin(0).withFlex(1));
|
||||
|
||||
mainBox.performLayout(getLocalBounds().reduced(2, 2));
|
||||
|
||||
auto innerbounds = mSettingsTab->getLocalBounds();
|
||||
|
||||
if (mAudioDeviceSelector) {
|
||||
mAudioDeviceSelector->setBounds(Rectangle<int>(0,0,innerbounds.getWidth() - 10,mAudioDeviceSelector->getHeight()));
|
||||
}
|
||||
mOptionsComponent->setBounds(Rectangle<int>(0,0,innerbounds.getWidth() - 10, minOptionsHeight));
|
||||
|
||||
if (mAboutLabel) {
|
||||
mAboutLabel->setBounds(Rectangle<int>(0,0,innerbounds.getWidth() - 10, mAboutLabel->getHeight()));
|
||||
}
|
||||
|
||||
|
||||
FlexBox lfwpBox;
|
||||
lfwpBox.flexDirection = FlexBox::Direction::row;
|
||||
lfwpBox.items.add(FlexItem(leftmargin, 12).withFlex(0));
|
||||
lfwpBox.items.add(FlexItem(minw, minpassheight, *mOptionsLoadFileWithPluginButton).withMargin(0).withFlex(1));
|
||||
|
||||
FlexBox pwtBox;
|
||||
pwtBox.flexDirection = FlexBox::Direction::row;
|
||||
pwtBox.items.add(FlexItem(leftmargin, 12).withFlex(0));
|
||||
pwtBox.items.add(FlexItem(minw, minpassheight, *mOptionsPlayWithTransportButton).withMargin(0).withFlex(1));
|
||||
|
||||
FlexBox cwtBox;
|
||||
cwtBox.flexDirection = FlexBox::Direction::row;
|
||||
cwtBox.items.add(FlexItem(leftmargin, 12).withFlex(0));
|
||||
cwtBox.items.add(FlexItem(minw, minpassheight, *mOptionsCaptureWithTransportButton).withMargin(0).withFlex(1));
|
||||
|
||||
FlexBox capbufBox;
|
||||
capbufBox.flexDirection = FlexBox::Direction::row;
|
||||
capbufBox.items.add(FlexItem(leftmargin, 12).withFlex(0));
|
||||
capbufBox.items.add(FlexItem(130, minitemheight, *mOptionsCaptureBufferStaticLabel).withMargin(0).withFlex(0));
|
||||
capbufBox.items.add(FlexItem(5, 12).withFlex(0));
|
||||
capbufBox.items.add(FlexItem(minw, minitemheight, *mCaptureBufferChoice).withMargin(0).withFlex(1));
|
||||
|
||||
FlexBox rpsBox;
|
||||
rpsBox.flexDirection = FlexBox::Direction::row;
|
||||
rpsBox.items.add(FlexItem(leftmargin, 12).withFlex(0));
|
||||
rpsBox.items.add(FlexItem(minw, minpassheight, *mOptionsRestorePlayStateButton).withMargin(0).withFlex(1));
|
||||
|
||||
|
||||
FlexBox mpwcBox;
|
||||
mpwcBox.flexDirection = FlexBox::Direction::row;
|
||||
mpwcBox.items.add(FlexItem(leftmargin, 12).withFlex(0));
|
||||
mpwcBox.items.add(FlexItem(minw, minpassheight, *mOptionsMutePassthroughWhenCaptureButton).withMargin(0).withFlex(1));
|
||||
|
||||
FlexBox mprwcBox;
|
||||
mprwcBox.flexDirection = FlexBox::Direction::row;
|
||||
mprwcBox.items.add(FlexItem(leftmargin, 12).withFlex(0));
|
||||
mprwcBox.items.add(FlexItem(minw, minpassheight, *mOptionsMuteProcessedWhenCaptureButton).withMargin(0).withFlex(1));
|
||||
|
||||
FlexBox sctdBox;
|
||||
sctdBox.flexDirection = FlexBox::Direction::row;
|
||||
sctdBox.items.add(FlexItem(leftmargin, 12).withFlex(0));
|
||||
sctdBox.items.add(FlexItem(minw, minpassheight, *mOptionsSaveCaptureToDiskButton).withMargin(0).withFlex(1));
|
||||
|
||||
FlexBox eramBox;
|
||||
eramBox.flexDirection = FlexBox::Direction::row;
|
||||
eramBox.items.add(FlexItem(leftmargin, 12).withFlex(0));
|
||||
eramBox.items.add(FlexItem(minw, minpassheight, *mOptionsEndRecordingAfterMaxButton).withMargin(0).withFlex(1));
|
||||
|
||||
|
||||
FlexBox ssnapBox;
|
||||
ssnapBox.flexDirection = FlexBox::Direction::row;
|
||||
ssnapBox.items.add(FlexItem(leftmargin, 12).withFlex(0));
|
||||
ssnapBox.items.add(FlexItem(minw, minpassheight, *mOptionsSliderSnapToMouseButton).withMargin(0).withFlex(1));
|
||||
|
||||
FlexBox dumpBox;
|
||||
dumpBox.flexDirection = FlexBox::Direction::row;
|
||||
dumpBox.items.add(FlexItem(leftmargin, 12).withFlex(0));
|
||||
dumpBox.items.add(FlexItem(minw, minpassheight, *mOptionsDumpPresetToClipboardButton).withMargin(0).withFlex(1));
|
||||
|
||||
FlexBox resetBox;
|
||||
resetBox.flexDirection = FlexBox::Direction::row;
|
||||
resetBox.items.add(FlexItem(leftmargin, 12).withFlex(0));
|
||||
resetBox.items.add(FlexItem(minw, minpassheight, *mOptionsResetParamsButton).withMargin(0).withFlex(1));
|
||||
|
||||
FlexBox showtiBox;
|
||||
showtiBox.flexDirection = FlexBox::Direction::row;
|
||||
showtiBox.items.add(FlexItem(leftmargin, 12).withFlex(0));
|
||||
showtiBox.items.add(FlexItem(minw, minpassheight, *mOptionsShowTechnicalInfoButton).withMargin(0).withFlex(1));
|
||||
|
||||
FlexBox optionsRecordDirBox;
|
||||
optionsRecordDirBox.flexDirection = FlexBox::Direction::row;
|
||||
optionsRecordDirBox.items.add(FlexItem(115, minitemheight, *mRecLocationStaticLabel).withMargin(0).withFlex(0));
|
||||
optionsRecordDirBox.items.add(FlexItem(minButtonWidth, minitemheight, *mRecLocationButton).withMargin(0).withFlex(3));
|
||||
|
||||
FlexBox optionsRecordFormatBox;
|
||||
optionsRecordFormatBox.flexDirection = FlexBox::Direction::row;
|
||||
optionsRecordFormatBox.items.add(FlexItem(115, minitemheight, *mRecFormatStaticLabel).withMargin(0).withFlex(0));
|
||||
optionsRecordFormatBox.items.add(FlexItem(minButtonWidth, minitemheight, *mRecFormatChoice).withMargin(0).withFlex(1));
|
||||
optionsRecordFormatBox.items.add(FlexItem(2, 4));
|
||||
optionsRecordFormatBox.items.add(FlexItem(80, minitemheight, *mRecBitsChoice).withMargin(0).withFlex(0.25));
|
||||
|
||||
int vgap = 0;
|
||||
|
||||
FlexBox optionsBox;
|
||||
optionsBox.flexDirection = FlexBox::Direction::column;
|
||||
optionsBox.items.add(FlexItem(4, 6));
|
||||
optionsBox.items.add(FlexItem(minw, minpassheight, lfwpBox).withMargin(2).withFlex(0));
|
||||
optionsBox.items.add(FlexItem(4, vgap));
|
||||
optionsBox.items.add(FlexItem(minw, minpassheight, pwtBox).withMargin(2).withFlex(0));
|
||||
optionsBox.items.add(FlexItem(4, vgap));
|
||||
optionsBox.items.add(FlexItem(minw, minpassheight, cwtBox).withMargin(2).withFlex(0));
|
||||
optionsBox.items.add(FlexItem(4, vgap));
|
||||
optionsBox.items.add(FlexItem(minw, minpassheight, rpsBox).withMargin(2).withFlex(0));
|
||||
optionsBox.items.add(FlexItem(4, vgap + 6));
|
||||
|
||||
optionsBox.items.add(FlexItem(minw, minitemheight, capbufBox).withMargin(2).withFlex(0));
|
||||
optionsBox.items.add(FlexItem(4, vgap));
|
||||
optionsBox.items.add(FlexItem(minw, minpassheight, mpwcBox).withMargin(2).withFlex(0));
|
||||
optionsBox.items.add(FlexItem(4, vgap));
|
||||
optionsBox.items.add(FlexItem(minw, minpassheight, mprwcBox).withMargin(2).withFlex(0));
|
||||
optionsBox.items.add(FlexItem(4, vgap));
|
||||
optionsBox.items.add(FlexItem(minw, minpassheight, sctdBox).withMargin(2).withFlex(0));
|
||||
optionsBox.items.add(FlexItem(4, vgap));
|
||||
optionsBox.items.add(FlexItem(minw, minpassheight, eramBox).withMargin(2).withFlex(0));
|
||||
optionsBox.items.add(FlexItem(4, vgap + 6));
|
||||
|
||||
optionsBox.items.add(FlexItem(minw, minpassheight, ssnapBox).withMargin(2).withFlex(0));
|
||||
optionsBox.items.add(FlexItem(4, vgap));
|
||||
optionsBox.items.add(FlexItem(minw, minpassheight, showtiBox).withMargin(2).withFlex(0));
|
||||
optionsBox.items.add(FlexItem(4, vgap + 6));
|
||||
|
||||
#if !(JUCE_IOS || JUCE_ANDROID)
|
||||
optionsBox.items.add(FlexItem(minw, minitemheight, optionsRecordDirBox).withMargin(2).withFlex(0));
|
||||
optionsBox.items.add(FlexItem(4, vgap + 2));
|
||||
#endif
|
||||
optionsBox.items.add(FlexItem(minw, minitemheight, optionsRecordFormatBox).withMargin(2).withFlex(0));
|
||||
optionsBox.items.add(FlexItem(4, vgap));
|
||||
|
||||
optionsBox.items.add(FlexItem(4, vgap + 4));
|
||||
optionsBox.items.add(FlexItem(minw, minpassheight, resetBox).withMargin(2).withFlex(0));
|
||||
optionsBox.items.add(FlexItem(4, vgap));
|
||||
|
||||
#if JUCE_DEBUG
|
||||
optionsBox.items.add(FlexItem(4, vgap + 4));
|
||||
optionsBox.items.add(FlexItem(minw, minpassheight, dumpBox).withMargin(2).withFlex(0));
|
||||
optionsBox.items.add(FlexItem(4, vgap));
|
||||
#endif
|
||||
|
||||
minOptionsHeight = 0;
|
||||
for (auto & item : optionsBox.items) {
|
||||
minOptionsHeight += item.minHeight + item.margin.top + item.margin.bottom;
|
||||
}
|
||||
|
||||
optionsBox.performLayout(mOptionsComponent->getLocalBounds());
|
||||
|
||||
prefHeight = minOptionsHeight + mSettingsTab->getTabBarDepth();
|
||||
}
|
||||
|
||||
void OptionsView::showAudioTab()
|
||||
{
|
||||
if (mSettingsTab->getNumTabs() == 3) {
|
||||
mSettingsTab->setCurrentTabIndex(0);
|
||||
}
|
||||
}
|
||||
|
||||
void OptionsView::showOptionsTab()
|
||||
{
|
||||
mSettingsTab->setCurrentTabIndex(mSettingsTab->getNumTabs() == 3 ? 1 : 0);
|
||||
}
|
||||
|
||||
void OptionsView::showAboutTab()
|
||||
{
|
||||
mSettingsTab->setCurrentTabIndex(mSettingsTab->getNumTabs() == 3 ? 2 : 1);
|
||||
}
|
||||
|
||||
|
||||
void OptionsView::optionsTabChanged (int newCurrentTabIndex)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void OptionsView::showWarnings()
|
||||
{
|
||||
}
|
||||
|
||||
void OptionsView::textEditorReturnKeyPressed (TextEditor& ed)
|
||||
{
|
||||
DBG("Return pressed");
|
||||
// if (&ed == mOptionsUdpPortEditor.get()) {
|
||||
// int port = mOptionsUdpPortEditor->getText().getIntValue();
|
||||
// //changeUdpPort(port);
|
||||
// }
|
||||
}
|
||||
|
||||
void OptionsView::textEditorEscapeKeyPressed (TextEditor& ed)
|
||||
{
|
||||
DBG("escape pressed");
|
||||
}
|
||||
|
||||
void OptionsView::textEditorTextChanged (TextEditor& ed)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
void OptionsView::textEditorFocusLost (TextEditor& ed)
|
||||
{
|
||||
// only one we care about live is udp port
|
||||
//if (&ed == mOptionsUdpPortEditor.get()) {
|
||||
// int port = mOptionsUdpPortEditor->getText().getIntValue();
|
||||
// //changeUdpPort(port);
|
||||
//}
|
||||
}
|
||||
|
||||
|
||||
void OptionsView::buttonClicked (Button* buttonThatWasClicked)
|
||||
{
|
||||
if (buttonThatWasClicked == mRecLocationButton.get()) {
|
||||
// browse folder chooser
|
||||
SafePointer<OptionsView> safeThis (this);
|
||||
|
||||
if (! RuntimePermissions::isGranted (RuntimePermissions::readExternalStorage))
|
||||
{
|
||||
RuntimePermissions::request (RuntimePermissions::readExternalStorage,
|
||||
[safeThis] (bool granted) mutable
|
||||
{
|
||||
if (granted)
|
||||
safeThis->buttonClicked (safeThis->mRecLocationButton.get());
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
chooseRecDirBrowser();
|
||||
}
|
||||
else if (buttonThatWasClicked == mOptionsDumpPresetToClipboardButton.get()) {
|
||||
ValueTree tree = processor.getStateTree(true, true);
|
||||
MemoryBlock destData;
|
||||
MemoryOutputStream stream(destData, true);
|
||||
tree.writeToStream(stream);
|
||||
String txt = Base64::toBase64(destData.getData(), destData.getSize());
|
||||
SystemClipboard::copyTextToClipboard(txt);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void OptionsView::choiceButtonSelected(SonoChoiceButton *comp, int index, int ident)
|
||||
{
|
||||
if (comp == mRecFormatChoice.get()) {
|
||||
processor.setDefaultRecordingFormat((PaulstretchpluginAudioProcessor::RecordFileFormat) ident);
|
||||
}
|
||||
else if (comp == mRecBitsChoice.get()) {
|
||||
processor.setDefaultRecordingBitsPerSample(ident);
|
||||
}
|
||||
else if (comp == mCaptureBufferChoice.get()) {
|
||||
*processor.getFloatParameter(cpi_max_capture_len) = (float) ident;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void OptionsView::chooseRecDirBrowser()
|
||||
{
|
||||
SafePointer<OptionsView> safeThis (this);
|
||||
|
||||
if (FileChooser::isPlatformDialogAvailable())
|
||||
{
|
||||
File recdir = File(processor.getDefaultRecordingDirectory());
|
||||
|
||||
mFileChooser.reset(new FileChooser(TRANS("Choose the folder for new recordings"),
|
||||
recdir,
|
||||
"",
|
||||
true, false, getTopLevelComponent()));
|
||||
|
||||
|
||||
|
||||
mFileChooser->launchAsync (FileBrowserComponent::openMode | FileBrowserComponent::canSelectDirectories,
|
||||
[safeThis] (const FileChooser& chooser) mutable
|
||||
{
|
||||
auto results = chooser.getURLResults();
|
||||
if (safeThis != nullptr && results.size() > 0)
|
||||
{
|
||||
auto url = results.getReference (0);
|
||||
|
||||
DBG("Chose directory: " << url.toString(false));
|
||||
|
||||
if (url.isLocalFile()) {
|
||||
File lfile = url.getLocalFile();
|
||||
if (lfile.isDirectory()) {
|
||||
safeThis->processor.setDefaultRecordingDirectory(lfile.getFullPathName());
|
||||
} else {
|
||||
safeThis->processor.setDefaultRecordingDirectory(lfile.getParentDirectory().getFullPathName());
|
||||
}
|
||||
|
||||
safeThis->updateState();
|
||||
}
|
||||
}
|
||||
|
||||
if (safeThis) {
|
||||
safeThis->mFileChooser.reset();
|
||||
}
|
||||
|
||||
}, nullptr);
|
||||
|
||||
}
|
||||
else {
|
||||
DBG("Need to enable code signing");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void OptionsView::showPopTip(const String & message, int timeoutMs, Component * target, int maxwidth)
|
||||
{
|
||||
popTip.reset(new BubbleMessageComponent());
|
||||
popTip->setAllowedPlacement(BubbleComponent::above);
|
||||
|
||||
if (target) {
|
||||
if (auto * parent = target->findParentComponentOfClass<AudioProcessorEditor>()) {
|
||||
parent->addChildComponent (popTip.get());
|
||||
} else {
|
||||
addChildComponent(popTip.get());
|
||||
}
|
||||
}
|
||||
else {
|
||||
addChildComponent(popTip.get());
|
||||
}
|
||||
|
||||
AttributedString text(message);
|
||||
text.setJustification (Justification::centred);
|
||||
text.setColour (findColour (TextButton::textColourOffId));
|
||||
text.setFont(Font(12));
|
||||
if (target) {
|
||||
popTip->showAt(target, text, timeoutMs);
|
||||
}
|
||||
else {
|
||||
Rectangle<int> topbox(getWidth()/2 - maxwidth/2, 0, maxwidth, 2);
|
||||
popTip->showAt(topbox, text, timeoutMs);
|
||||
}
|
||||
popTip->toFront(false);
|
||||
//AccessibilityHandler::postAnnouncement(message, AccessibilityHandler::AnnouncementPriority::high);
|
||||
}
|
||||
|
||||
void OptionsView::paint(Graphics & g)
|
||||
{
|
||||
/*
|
||||
//g.fillAll (Colours::black);
|
||||
Rectangle<int> bounds = getLocalBounds();
|
||||
|
||||
bounds.reduce(1, 1);
|
||||
bounds.removeFromLeft(3);
|
||||
|
||||
g.setColour(bgColor);
|
||||
g.fillRoundedRectangle(bounds.toFloat(), 6.0f);
|
||||
g.setColour(outlineColor);
|
||||
g.drawRoundedRectangle(bounds.toFloat(), 6.0f, 0.5f);
|
||||
*/
|
||||
}
|
||||
|
148
Source/OptionsView.h
Normal file
148
Source/OptionsView.h
Normal file
@ -0,0 +1,148 @@
|
||||
// SPDX-License-Identifier: GPLv3-or-later WITH Appstore-exception
|
||||
// Copyright (C) 2021 Jesse Chappell
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "JuceHeader.h"
|
||||
|
||||
#include "PluginProcessor.h"
|
||||
#include "CustomLookAndFeel.h"
|
||||
#include "SonoChoiceButton.h"
|
||||
#include "GenericItemChooser.h"
|
||||
|
||||
|
||||
class OptionsView :
|
||||
public Component,
|
||||
public Button::Listener,
|
||||
public SonoChoiceButton::Listener,
|
||||
public GenericItemChooser::Listener,
|
||||
public TextEditor::Listener,
|
||||
public MultiTimer
|
||||
{
|
||||
public:
|
||||
OptionsView(PaulstretchpluginAudioProcessor& proc, std::function<AudioDeviceManager*()> getaudiodevicemanager);
|
||||
virtual ~OptionsView();
|
||||
|
||||
|
||||
class Listener {
|
||||
public:
|
||||
virtual ~Listener() {}
|
||||
virtual void optionsChanged(OptionsView *comp) {}
|
||||
};
|
||||
|
||||
void addListener(Listener * listener) { listeners.add(listener); }
|
||||
void removeListener(Listener * listener) { listeners.remove(listener); }
|
||||
|
||||
void timerCallback(int timerid) override;
|
||||
|
||||
void buttonClicked (Button* buttonThatWasClicked) override;
|
||||
|
||||
void choiceButtonSelected(SonoChoiceButton *comp, int index, int ident) override;
|
||||
|
||||
|
||||
void textEditorReturnKeyPressed (TextEditor&) override;
|
||||
void textEditorEscapeKeyPressed (TextEditor&) override;
|
||||
void textEditorTextChanged (TextEditor&) override;
|
||||
void textEditorFocusLost (TextEditor&) override;
|
||||
|
||||
juce::Rectangle<int> getMinimumContentBounds() const;
|
||||
juce::Rectangle<int> getPreferredContentBounds() const;
|
||||
|
||||
void grabInitialFocus();
|
||||
|
||||
void updateState(bool ignorecheck=false);
|
||||
|
||||
void paint(Graphics & g) override;
|
||||
void resized() override;
|
||||
|
||||
void showPopTip(const String & message, int timeoutMs, Component * target, int maxwidth=100);
|
||||
|
||||
void optionsTabChanged (int newCurrentTabIndex);
|
||||
|
||||
void showAudioTab();
|
||||
void showOptionsTab();
|
||||
void showAboutTab();
|
||||
|
||||
void showWarnings();
|
||||
|
||||
|
||||
std::function<AudioDeviceManager*()> getAudioDeviceManager; // = []() { return 0; };
|
||||
std::function<void()> updateSliderSnap; // = []() { return 0; };
|
||||
std::function<void()> updateKeybindings; // = []() { return 0; };
|
||||
std::function<void()> saveSettingsIfNeeded; // = []() { return 0; };
|
||||
|
||||
|
||||
protected:
|
||||
|
||||
|
||||
void configEditor(TextEditor *editor, bool passwd = false);
|
||||
void configLabel(Label *label, bool val=false);
|
||||
void configLevelSlider(Slider *);
|
||||
|
||||
void chooseRecDirBrowser();
|
||||
void createAbout();
|
||||
|
||||
|
||||
PaulstretchpluginAudioProcessor& processor;
|
||||
|
||||
CustomBigTextLookAndFeel smallLNF;
|
||||
CustomBigTextLookAndFeel sonoSliderLNF;
|
||||
|
||||
|
||||
ListenerList<Listener> listeners;
|
||||
|
||||
std::unique_ptr<AudioDeviceSelectorComponent> mAudioDeviceSelector;
|
||||
std::unique_ptr<Viewport> mAudioOptionsViewport;
|
||||
std::unique_ptr<Viewport> mOtherOptionsViewport;
|
||||
std::unique_ptr<Viewport> mRecordOptionsViewport;
|
||||
|
||||
|
||||
std::unique_ptr<Component> mOptionsComponent;
|
||||
std::unique_ptr<Viewport> mAboutViewport;
|
||||
std::unique_ptr<Label> mAboutLabel;
|
||||
|
||||
int minOptionsHeight = 0;
|
||||
int minRecOptionsHeight = 0;
|
||||
|
||||
uint32 settingsClosedTimestamp = 0;
|
||||
|
||||
std::unique_ptr<FileChooser> mFileChooser;
|
||||
|
||||
std::unique_ptr<SonoChoiceButton> mCaptureBufferChoice;
|
||||
std::unique_ptr<Label> mOptionsCaptureBufferStaticLabel;
|
||||
|
||||
std::unique_ptr<ToggleButton> mOptionsLoadFileWithPluginButton;
|
||||
std::unique_ptr<ToggleButton> mOptionsPlayWithTransportButton;
|
||||
std::unique_ptr<ToggleButton> mOptionsCaptureWithTransportButton;
|
||||
std::unique_ptr<ToggleButton> mOptionsRestorePlayStateButton;
|
||||
|
||||
std::unique_ptr<ToggleButton> mOptionsMutePassthroughWhenCaptureButton;
|
||||
std::unique_ptr<ToggleButton> mOptionsMuteProcessedWhenCaptureButton;
|
||||
std::unique_ptr<ToggleButton> mOptionsSaveCaptureToDiskButton;
|
||||
std::unique_ptr<ToggleButton> mOptionsEndRecordingAfterMaxButton;
|
||||
|
||||
std::unique_ptr<ToggleButton> mOptionsSliderSnapToMouseButton;
|
||||
std::unique_ptr<TextButton> mOptionsDumpPresetToClipboardButton;
|
||||
std::unique_ptr<ToggleButton> mOptionsShowTechnicalInfoButton;
|
||||
std::unique_ptr<TextButton> mOptionsResetParamsButton;
|
||||
|
||||
std::unique_ptr<SonoChoiceButton> mRecFormatChoice;
|
||||
std::unique_ptr<SonoChoiceButton> mRecBitsChoice;
|
||||
std::unique_ptr<Label> mRecFormatStaticLabel;
|
||||
std::unique_ptr<Label> mRecLocationStaticLabel;
|
||||
std::unique_ptr<TextButton> mRecLocationButton;
|
||||
|
||||
std::unique_ptr<TabbedComponent> mSettingsTab;
|
||||
|
||||
int minHeight = 0;
|
||||
int prefHeight = 0;
|
||||
bool firsttime = true;
|
||||
|
||||
// keep this down here, so it gets destroyed early
|
||||
std::unique_ptr<BubbleMessageComponent> popTip;
|
||||
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OptionsView)
|
||||
|
||||
};
|
@ -281,7 +281,7 @@ inline void callGUI(T* ap, F&& f, bool async)
|
||||
}
|
||||
}
|
||||
|
||||
inline String secondsToString2(double secs)
|
||||
inline String secondsToString2(double secs, bool fractional=true)
|
||||
{
|
||||
RelativeTime rt(secs);
|
||||
String result;
|
||||
@ -292,7 +292,7 @@ inline String secondsToString2(double secs)
|
||||
result << String((int)rt.inMinutes() % 60).paddedLeft('0', 2) << ':';
|
||||
result << String((int)rt.inSeconds() % 60).paddedLeft('0', 2);
|
||||
auto millis = (int)rt.inMilliseconds() % 1000;
|
||||
if (millis > 0)
|
||||
if (fractional && millis > 0)
|
||||
result << '.' << String(millis).paddedLeft('0', 3);
|
||||
return result.trimEnd();
|
||||
}
|
||||
|
@ -8,11 +8,7 @@
|
||||
#include "RenderSettingsComponent.h"
|
||||
|
||||
#include "CrossPlatformUtils.h"
|
||||
|
||||
static void handleSettingsMenuModalCallback(int choice, PaulstretchpluginAudioProcessorEditor* ed)
|
||||
{
|
||||
ed->executeModalMenuAction(0,choice);
|
||||
}
|
||||
#include "OptionsView.h"
|
||||
|
||||
enum ParameterGroupIds
|
||||
{
|
||||
@ -27,24 +23,6 @@ enum ParameterGroupIds
|
||||
CompressGroup = 8
|
||||
};
|
||||
|
||||
enum SettingsMenuIds
|
||||
{
|
||||
SettingsPlayHostTransId = 1,
|
||||
SettingsCaptureHostTransId = 2,
|
||||
SettingsAboutId = 3,
|
||||
SettingsResetParametersId = 4,
|
||||
SettingsLoadFileWithStateId = 5,
|
||||
SettingsDumpPresetClipboardId = 6,
|
||||
SettingsShowTechInfoId = 7,
|
||||
SettingsMutePassthruCaptureId = 8,
|
||||
SettingsSaveCaptureDiskId = 9,
|
||||
SettingsMuteProcessedCaptureId = 10,
|
||||
SettingsAudioSettingsId = 11,
|
||||
SettingsSliderSnapId = 12,
|
||||
SettingsRestorePlayStateId = 13,
|
||||
SettingsAutoEndCaptureId = 14,
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
PaulstretchpluginAudioProcessorEditor::PaulstretchpluginAudioProcessorEditor(PaulstretchpluginAudioProcessor& p)
|
||||
@ -94,7 +72,9 @@ PaulstretchpluginAudioProcessorEditor::PaulstretchpluginAudioProcessorEditor(Pau
|
||||
|
||||
addAndMakeVisible(&m_settings_button);
|
||||
m_settings_button.setButtonText("Settings...");
|
||||
m_settings_button.onClick = [this]() { showSettingsMenu(); };
|
||||
m_settings_button.onClick = [this]() {
|
||||
showSettings(true);
|
||||
};
|
||||
|
||||
if (JUCEApplicationBase::isStandaloneApp())
|
||||
{
|
||||
@ -125,6 +105,27 @@ PaulstretchpluginAudioProcessorEditor::PaulstretchpluginAudioProcessorEditor(Pau
|
||||
m_info_label.setJustificationType(Justification::centredRight);
|
||||
m_info_label.setFont(14.0f);
|
||||
|
||||
|
||||
m_recordingButton = std::make_unique<DrawableButton>("rewind", DrawableButton::ButtonStyle::ImageFitted);
|
||||
std::unique_ptr<Drawable> reconimg(Drawable::createFromImageData(BinaryData::record_output_active_svg, BinaryData::record_output_active_svgSize));
|
||||
std::unique_ptr<Drawable> recoffimg(Drawable::createFromImageData(BinaryData::record_output_svg, BinaryData::record_output_svgSize));
|
||||
m_recordingButton->setImages(recoffimg.get(), nullptr, nullptr, nullptr, reconimg.get());
|
||||
m_recordingButton->setColour(DrawableButton::backgroundOnColourId, Colour::fromFloatRGBA(0.6, 0.3, 0.3, 0.5));
|
||||
addAndMakeVisible(m_recordingButton.get());
|
||||
//m_recordingButton->setColour(DrawableButton::backgroundOnColourId, Colours::transparentBlack);
|
||||
m_recordingButton->setTooltip(TRANS("Start/Stop recording output to file"));
|
||||
m_recordingButton->setTitle(TRANS("Record Output"));
|
||||
m_recordingButton->setClickingTogglesState(true);
|
||||
m_recordingButton->onClick = [this]() { toggleOutputRecording(); };
|
||||
|
||||
m_fileRecordingLabel = std::make_unique<Label>("rectime", "");
|
||||
m_fileRecordingLabel->setJustificationType(Justification::centredBottom);
|
||||
m_fileRecordingLabel->setFont(12);
|
||||
m_fileRecordingLabel->setColour(Label::textColourId, Colour(0x88ffbbbb));
|
||||
m_fileRecordingLabel->setAccessible(false);
|
||||
|
||||
addAndMakeVisible(m_fileRecordingLabel.get());
|
||||
|
||||
m_wavecomponent.GetFileCallback = [this]() { return processor.getAudioFile(); };
|
||||
|
||||
const auto& pars = processor.getParameters();
|
||||
@ -206,9 +207,9 @@ PaulstretchpluginAudioProcessorEditor::PaulstretchpluginAudioProcessorEditor(Pau
|
||||
}
|
||||
|
||||
if (auto * recbut = m_parcomps[cpi_capture_trigger]->getDrawableButton()) {
|
||||
std::unique_ptr<Drawable> reconimg(Drawable::createFromImageData(BinaryData::record_active_svg, BinaryData::record_active_svgSize));
|
||||
std::unique_ptr<Drawable> recoffimg(Drawable::createFromImageData(BinaryData::record_svg, BinaryData::record_svgSize));
|
||||
recbut->setImages(recoffimg.get(), nullptr, nullptr, nullptr, reconimg.get());
|
||||
std::unique_ptr<Drawable> ireconimg(Drawable::createFromImageData(BinaryData::record_input_active_svg, BinaryData::record_input_active_svgSize));
|
||||
std::unique_ptr<Drawable> irecoffimg(Drawable::createFromImageData(BinaryData::record_input_svg, BinaryData::record_input_svgSize));
|
||||
recbut->setImages(irecoffimg.get(), nullptr, nullptr, nullptr, ireconimg.get());
|
||||
recbut->setColour(DrawableButton::backgroundOnColourId, Colour::fromFloatRGBA(0.6, 0.3, 0.3, 0.5));
|
||||
}
|
||||
|
||||
@ -586,92 +587,88 @@ void PaulstretchpluginAudioProcessorEditor::showAudioSetup()
|
||||
}
|
||||
}
|
||||
|
||||
void PaulstretchpluginAudioProcessorEditor::executeModalMenuAction(int menuid, int r)
|
||||
void PaulstretchpluginAudioProcessorEditor::showSettings(bool flag)
|
||||
{
|
||||
enum SettingsMenuIds
|
||||
{
|
||||
SettingsPlayHostTransId = 1,
|
||||
SettingsCaptureHostTransId = 2,
|
||||
SettingsAboutId = 3,
|
||||
SettingsResetParametersId = 4,
|
||||
SettingsLoadFileWithStateId = 5,
|
||||
SettingsDumpPresetClipboardId = 6,
|
||||
SettingsShowTechInfoId = 7,
|
||||
SettingsMutePassthruCaptureId = 8,
|
||||
SettingsSaveCaptureDiskId = 9,
|
||||
SettingsMuteProcessedCaptureId = 10,
|
||||
};
|
||||
DBG("Got settings click");
|
||||
|
||||
if (r >= 200 && r < 210)
|
||||
{
|
||||
int caplen = m_capturelens[r - 200];
|
||||
*processor.getFloatParameter(cpi_max_capture_len) = (float)caplen;
|
||||
}
|
||||
else if (r == SettingsPlayHostTransId)
|
||||
{
|
||||
toggleBool(processor.m_play_when_host_plays);
|
||||
}
|
||||
else if (r == SettingsCaptureHostTransId)
|
||||
{
|
||||
toggleBool(processor.m_capture_when_host_plays);
|
||||
}
|
||||
else if (r == SettingsMutePassthruCaptureId)
|
||||
{
|
||||
toggleBool(processor.m_mute_while_capturing);
|
||||
}
|
||||
else if (r == SettingsMuteProcessedCaptureId)
|
||||
{
|
||||
toggleBool(processor.m_mute_processed_while_capturing);
|
||||
}
|
||||
else if (r == SettingsResetParametersId)
|
||||
{
|
||||
processor.resetParameters();
|
||||
}
|
||||
else if (r == SettingsLoadFileWithStateId)
|
||||
{
|
||||
toggleBool(processor.m_load_file_with_state);
|
||||
}
|
||||
else if (r == SettingsSaveCaptureDiskId)
|
||||
{
|
||||
toggleBool(processor.m_save_captured_audio);
|
||||
}
|
||||
else if (r == SettingsSliderSnapId)
|
||||
{
|
||||
toggleBool(processor.m_use_jumpsliders);
|
||||
}
|
||||
else if (r == SettingsAutoEndCaptureId)
|
||||
{
|
||||
toggleBool(processor.m_auto_finish_record);
|
||||
}
|
||||
else if (r == SettingsRestorePlayStateId)
|
||||
{
|
||||
toggleBool(processor.m_restore_playstate);
|
||||
}
|
||||
else if (r == SettingsAboutId)
|
||||
{
|
||||
showAbout();
|
||||
}
|
||||
else if (r == SettingsAudioSettingsId)
|
||||
{
|
||||
showAudioSetup();
|
||||
if (flag && settingsCalloutBox == nullptr) {
|
||||
|
||||
//Viewport * wrap = new Viewport();
|
||||
|
||||
Component* dw = this;
|
||||
|
||||
#if JUCE_IOS || JUCE_ANDROID
|
||||
int defWidth = 320;
|
||||
int defHeight = 420;
|
||||
#else
|
||||
int defWidth = 340;
|
||||
int defHeight = 400;
|
||||
#endif
|
||||
|
||||
|
||||
bool firsttime = false;
|
||||
if (!m_optionsView) {
|
||||
m_optionsView = std::make_unique<OptionsView>(processor, getAudioDeviceManager);
|
||||
m_optionsView->updateSliderSnap = [this]() { updateAllSliders(); };
|
||||
//m_optionsView->saveSettingsIfNeeded = [this]() { if (saveSettingsIfNeeded) saveSettingsIfNeeded(); };
|
||||
m_optionsView->addComponentListener(this);
|
||||
firsttime = true;
|
||||
}
|
||||
|
||||
else if (r == SettingsDumpPresetClipboardId)
|
||||
{
|
||||
ValueTree tree = processor.getStateTree(true, true);
|
||||
MemoryBlock destData;
|
||||
MemoryOutputStream stream(destData, true);
|
||||
tree.writeToStream(stream);
|
||||
String txt = Base64::toBase64(destData.getData(), destData.getSize());
|
||||
SystemClipboard::copyTextToClipboard(txt);
|
||||
// presize it so the preferred gets calculated
|
||||
m_optionsView->setBounds(Rectangle<int>(0,0,defWidth,defHeight));
|
||||
|
||||
auto prefbounds = m_optionsView->getPreferredContentBounds();
|
||||
|
||||
defHeight = prefbounds.getHeight();
|
||||
|
||||
defWidth = jmin(defWidth + 8, dw->getWidth() - 30);
|
||||
defHeight = jmin(defHeight + 8, dw->getHeight() - 90); // 24
|
||||
|
||||
|
||||
auto wrap = std::make_unique<Component>();
|
||||
|
||||
wrap->addAndMakeVisible(m_optionsView.get());
|
||||
|
||||
m_optionsView->setBounds(Rectangle<int>(0,0,defWidth,defHeight));
|
||||
|
||||
wrap->setSize(defWidth,defHeight);
|
||||
|
||||
m_optionsView->updateState();
|
||||
|
||||
Rectangle<int> bounds = dw->getLocalArea(nullptr, m_settings_button.getScreenBounds().reduced(10));
|
||||
DBG("callout bounds: " << bounds.toString());
|
||||
settingsCalloutBox = & CallOutBox::launchAsynchronously (std::move(wrap), bounds , dw, false);
|
||||
if (CallOutBox * box = dynamic_cast<CallOutBox*>(settingsCalloutBox.get())) {
|
||||
box->setDismissalMouseClicksAreAlwaysConsumed(true);
|
||||
}
|
||||
|
||||
settingsClosedTimestamp = 0;
|
||||
|
||||
m_optionsView->grabInitialFocus();
|
||||
|
||||
}
|
||||
else {
|
||||
// dismiss it
|
||||
if (CallOutBox * box = dynamic_cast<CallOutBox*>(settingsCalloutBox.get())) {
|
||||
box->dismiss();
|
||||
settingsCalloutBox = nullptr;
|
||||
}
|
||||
else if (r == SettingsShowTechInfoId)
|
||||
{
|
||||
toggleBool(processor.m_show_technical_info);
|
||||
processor.m_propsfile->m_props_file->setValue("showtechnicalinfo", processor.m_show_technical_info);
|
||||
}
|
||||
}
|
||||
|
||||
void PaulstretchpluginAudioProcessorEditor::componentParentHierarchyChanged (Component& component)
|
||||
{
|
||||
if (&component == m_optionsView.get()) {
|
||||
if (component.getParentComponent() == nullptr) {
|
||||
DBG("setting parent changed: " << (uint64) component.getParentComponent());
|
||||
settingsClosedTimestamp = Time::getMillisecondCounter();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void PaulstretchpluginAudioProcessorEditor::updateAllSliders()
|
||||
{
|
||||
for (auto& e : m_parcomps) {
|
||||
@ -765,6 +762,10 @@ void PaulstretchpluginAudioProcessorEditor::resized()
|
||||
|
||||
//togglesbox.items.add(FlexItem(toggleminw, togglerowheight, *m_parcomps[cpi_bypass_stretch]).withMargin(margin).withFlex(1).withMaxWidth(150));
|
||||
togglesbox.items.add(FlexItem(buttminw + 15, buttonrowheight, *m_parcomps[cpi_passthrough]).withMargin(margin).withFlex(1.5).withMaxWidth(85));
|
||||
if (m_recordingButton) {
|
||||
togglesbox.items.add(FlexItem(0, buttonrowheight).withFlex(0.1).withMaxWidth(10));
|
||||
togglesbox.items.add(FlexItem(buttminw, buttonrowheight, *m_recordingButton).withMargin(1).withFlex(1).withMaxWidth(65));
|
||||
}
|
||||
|
||||
togglesbox.items.add(FlexItem(2, buttonrowheight));
|
||||
|
||||
@ -1057,6 +1058,9 @@ void PaulstretchpluginAudioProcessorEditor::resized()
|
||||
m_wavecomponent.setBounds(m_wave_container->getX(), 0, m_wave_container->getWidth(),
|
||||
m_wave_container->getHeight()-zscrollh-1);
|
||||
|
||||
if (m_recordingButton) {
|
||||
m_fileRecordingLabel->setBounds(m_recordingButton->getBounds().removeFromTop(14).translated(0, -9));
|
||||
}
|
||||
|
||||
|
||||
m_zs.setBounds(m_wave_container->getX(), m_wavecomponent.getBottom(), m_wave_container->getWidth(), zscrollh);
|
||||
@ -1072,6 +1076,10 @@ void PaulstretchpluginAudioProcessorEditor::resized()
|
||||
m_groupcontainer->repaint();
|
||||
}
|
||||
|
||||
if (settingsCalloutBox && settingsCalloutBox->isVisible()) {
|
||||
settingsCalloutBox->toFront(false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void PaulstretchpluginAudioProcessorEditor::timerCallback(int id)
|
||||
@ -1091,9 +1099,9 @@ void PaulstretchpluginAudioProcessorEditor::timerCallback(int id)
|
||||
m_parcomps[i]->updateComponent();
|
||||
}
|
||||
m_free_filter_component.updateParameterComponents();
|
||||
if (processor.isRecordingEnabled())
|
||||
if (processor.isInputRecordingEnabled())
|
||||
{
|
||||
m_wavecomponent.setRecordingPosition(processor.getRecordingPositionPercent());
|
||||
m_wavecomponent.setRecordingPosition(processor.getInputRecordingPositionPercent());
|
||||
} else
|
||||
m_wavecomponent.setRecordingPosition(-1.0);
|
||||
m_wavecomponent.setAudioInfo(processor.getSampleRateChecked(), processor.getStretchSource()->getLastSeekPos(),
|
||||
@ -1143,6 +1151,10 @@ void PaulstretchpluginAudioProcessorEditor::timerCallback(int id)
|
||||
m_perfmeter.enabled = !enablepar->get();
|
||||
}
|
||||
|
||||
if (m_recordingButton) {
|
||||
m_recordingButton->setToggleState(processor.isRecordingToFile(), dontSendNotification);
|
||||
}
|
||||
|
||||
}
|
||||
if (id == 2)
|
||||
{
|
||||
@ -1174,6 +1186,10 @@ void PaulstretchpluginAudioProcessorEditor::timerCallback(int id)
|
||||
|
||||
updateAllSliders();
|
||||
|
||||
if (processor.isRecordingToFile() && m_fileRecordingLabel) {
|
||||
m_fileRecordingLabel->setText(secondsToString2(processor.getElapsedRecordTime(), false), dontSendNotification);
|
||||
}
|
||||
|
||||
//m_parcomps[cpi_dryplayrate]->setVisible(*processor.getBoolParameter(cpi_bypass_stretch));
|
||||
//m_parcomps[cpi_stretchamount]->setVisible(!*processor.getBoolParameter(cpi_bypass_stretch));
|
||||
|
||||
@ -1228,123 +1244,6 @@ bool PaulstretchpluginAudioProcessorEditor::keyPressed(const KeyPress & press)
|
||||
return action && action();
|
||||
}
|
||||
|
||||
void PaulstretchpluginAudioProcessorEditor::showSettingsMenu()
|
||||
{
|
||||
|
||||
|
||||
PopupMenu m_settings_menu;
|
||||
if (JUCEApplicationBase::isStandaloneApp()) {
|
||||
m_settings_menu.addItem(11, "Audio Setup...", true, false);
|
||||
}
|
||||
m_settings_menu.addItem(SettingsResetParametersId, "Reset parameters", true, false);
|
||||
m_settings_menu.addSeparator();
|
||||
m_settings_menu.addItem(SettingsLoadFileWithStateId, "Load file with plugin state", true, processor.m_load_file_with_state);
|
||||
m_settings_menu.addItem(SettingsPlayHostTransId, "Play when host transport running", true, processor.m_play_when_host_plays);
|
||||
m_settings_menu.addItem(SettingsCaptureHostTransId, "Capture when host transport running", true, processor.m_capture_when_host_plays);
|
||||
m_settings_menu.addItem(SettingsRestorePlayStateId, "Restore playing state", true, processor.m_restore_playstate);
|
||||
m_settings_menu.addSeparator();
|
||||
m_settings_menu.addItem(SettingsMutePassthruCaptureId, "Mute passthrough while capturing", true, processor.m_mute_while_capturing);
|
||||
m_settings_menu.addItem(SettingsMuteProcessedCaptureId, "Mute processed audio output while capturing", true, processor.m_mute_processed_while_capturing);
|
||||
m_settings_menu.addItem(SettingsSaveCaptureDiskId, "Save captured audio to disk", true, processor.m_save_captured_audio);
|
||||
int capturelen = *processor.getFloatParameter(cpi_max_capture_len);
|
||||
PopupMenu capturelenmenu;
|
||||
|
||||
for (int i=0;i<m_capturelens.size();++i)
|
||||
capturelenmenu.addItem(200+i, String(m_capturelens[i])+" seconds", true, capturelen == m_capturelens[i]);
|
||||
m_settings_menu.addSubMenu("Capture buffer length", capturelenmenu);
|
||||
m_settings_menu.addItem(SettingsAutoEndCaptureId, "End recording after capturing max length", true, processor.m_auto_finish_record);
|
||||
|
||||
m_settings_menu.addSeparator();
|
||||
m_settings_menu.addItem(SettingsSliderSnapId, "Sliders jump to position", true, processor.m_use_jumpsliders);
|
||||
#ifdef JUCE_DEBUG
|
||||
m_settings_menu.addItem(SettingsDumpPresetClipboardId, "Dump preset to clipboard", true, false);
|
||||
#endif
|
||||
m_settings_menu.addItem(SettingsShowTechInfoId, "Show technical info in waveform", true, processor.m_show_technical_info);
|
||||
m_settings_menu.addSeparator();
|
||||
m_settings_menu.addItem(SettingsAboutId, "About...", true, false);
|
||||
|
||||
auto options = PopupMenu::Options().withTargetComponent(&m_settings_button);
|
||||
#if JUCE_IOS
|
||||
options = options.withStandardItemHeight(34);
|
||||
#endif
|
||||
if (!JUCEApplicationBase::isStandaloneApp()) {
|
||||
options = options.withParentComponent(this);
|
||||
}
|
||||
m_settings_menu.showMenuAsync(options,
|
||||
ModalCallbackFunction::forComponent(handleSettingsMenuModalCallback, this));
|
||||
}
|
||||
|
||||
void PaulstretchpluginAudioProcessorEditor::showAbout()
|
||||
{
|
||||
String fftlib;
|
||||
#if PS_USE_VDSP_FFT
|
||||
fftlib = "vDSP";
|
||||
#elif PS_USE_PFFFT
|
||||
fftlib = "pffft";
|
||||
#else
|
||||
fftlib = fftwf_version;
|
||||
#endif
|
||||
String juceversiontxt = String("JUCE ") + String(JUCE_MAJOR_VERSION) + "." + String(JUCE_MINOR_VERSION);
|
||||
String title = String(JucePlugin_Name) + " " + String(JucePlugin_VersionString);
|
||||
#ifdef JUCE_DEBUG
|
||||
title += " (DEBUG)";
|
||||
#endif
|
||||
String vstInfo;
|
||||
if (processor.wrapperType == AudioProcessor::wrapperType_VST ||
|
||||
processor.wrapperType == AudioProcessor::wrapperType_VST3)
|
||||
vstInfo = "VST Plug-In Technology by Steinberg.\n\n";
|
||||
PluginHostType host;
|
||||
|
||||
auto * content = new Label();
|
||||
String text = title + "\n\n" +
|
||||
"Plugin/Application for extreme time stretching and other sound processing\nBuilt on " + String(__DATE__) + " " + String(__TIME__) + "\n"
|
||||
"Copyright (C) 2006-2011 Nasca Octavian Paul, Tg. Mures, Romania\n"
|
||||
"(C) 2017-2021 Xenakios\n"
|
||||
"(C) 2022 Jesse Chappell\n\n"
|
||||
+vstInfo;
|
||||
|
||||
if (fftlib.isNotEmpty())
|
||||
text += String("Using ") + fftlib + String(" for FFT\n\n");
|
||||
|
||||
|
||||
#if !JUCE_IOS
|
||||
if (PluginHostType::getPluginLoadedAs() == AudioProcessor::wrapperType_AAX) {
|
||||
text += juceversiontxt + String("\n\n");
|
||||
}
|
||||
else {
|
||||
text += juceversiontxt + String(" used under the GPL license.\n\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
text += String("GPL licensed source code at : https://github.com/essej/paulxstretch\n");
|
||||
|
||||
if (host.type != juce::PluginHostType::UnknownHost) {
|
||||
text += String("Running in : ") + host.getHostDescription()+ String("\n");
|
||||
}
|
||||
|
||||
content->setJustificationType(Justification::centred);
|
||||
content->setText(text, dontSendNotification);
|
||||
|
||||
auto wrap = std::make_unique<Viewport>();
|
||||
wrap->setViewedComponent(content, true); // takes ownership of content
|
||||
|
||||
//std::unique_ptr<SettingsComponent> contptr(content);
|
||||
int defWidth = 450;
|
||||
int defHeight = 350;
|
||||
#if JUCE_IOS
|
||||
defWidth = 320;
|
||||
defHeight = 350;
|
||||
#endif
|
||||
|
||||
content->setSize (defWidth, defHeight);
|
||||
wrap->setSize(jmin(defWidth, getWidth() - 20), jmin(defHeight, getHeight() - 24));
|
||||
|
||||
auto bounds = getLocalArea(nullptr, m_settings_button.getScreenBounds());
|
||||
auto & cb = CallOutBox::launchAsynchronously(std::move(wrap), bounds, this, false);
|
||||
cb.setDismissalMouseClicksAreAlwaysConsumed(true);
|
||||
|
||||
}
|
||||
|
||||
void PaulstretchpluginAudioProcessorEditor::toggleFileBrowser()
|
||||
{
|
||||
#if JUCE_IOS
|
||||
@ -1398,6 +1297,93 @@ void PaulstretchpluginAudioProcessorEditor::toggleFileBrowser()
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
void PaulstretchpluginAudioProcessorEditor::toggleOutputRecording()
|
||||
{
|
||||
if (processor.isRecordingToFile()) {
|
||||
processor.stopRecordingToFile();
|
||||
|
||||
m_recordingButton->setToggleState(false, dontSendNotification);
|
||||
//updateServerStatusLabel("Stopped Recording");
|
||||
|
||||
String filepath;
|
||||
#if (JUCE_IOS || JUCE_ANDROID)
|
||||
filepath = m_lastRecordedFile.getRelativePathFrom(File::getSpecialLocation (File::userDocumentsDirectory));
|
||||
//showPopTip(TRANS("Finished recording to ") + filepath, 4000, mRecordingButton.get(), 130);
|
||||
#else
|
||||
filepath = m_lastRecordedFile.getRelativePathFrom(File::getSpecialLocation (File::userHomeDirectory));
|
||||
#endif
|
||||
|
||||
m_recordingButton->setTooltip(TRANS("Last recorded file: ") + filepath);
|
||||
|
||||
//mFileRecordingLabel->setText("Total: " + SonoUtility::durationToString(processor.getElapsedRecordTime(), true), dontSendNotification);
|
||||
m_fileRecordingLabel->setText("", dontSendNotification);
|
||||
|
||||
//Timer::callAfterDelay(200, []() {
|
||||
// AccessibilityHandler::postAnnouncement(TRANS("Recording finished"), AccessibilityHandler::AnnouncementPriority::high);
|
||||
//});
|
||||
|
||||
} else {
|
||||
|
||||
SafePointer<PaulstretchpluginAudioProcessorEditor> safeThis (this);
|
||||
|
||||
if (! RuntimePermissions::isGranted (RuntimePermissions::writeExternalStorage))
|
||||
{
|
||||
RuntimePermissions::request (RuntimePermissions::writeExternalStorage,
|
||||
[safeThis] (bool granted) mutable
|
||||
{
|
||||
if (granted)
|
||||
safeThis->toggleOutputRecording();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// create new timestamped filename
|
||||
String filename = "PaulXStretchSession" + String("_") + Time::getCurrentTime().formatted("%Y-%m-%d_%H.%M.%S");
|
||||
|
||||
filename = File::createLegalFileName(filename);
|
||||
|
||||
auto parentDir = File(processor.getDefaultRecordingDirectory());
|
||||
parentDir.createDirectory();
|
||||
|
||||
File file (parentDir.getNonexistentChildFile (filename, ".flac"));
|
||||
|
||||
if (processor.startRecordingToFile(file)) {
|
||||
//updateServerStatusLabel("Started recording...");
|
||||
m_lastRecordedFile = file;
|
||||
String filepath;
|
||||
|
||||
#if (JUCE_IOS || JUCE_ANDROID)
|
||||
//showPopTip(TRANS("Started recording output"), 2000, mRecordingButton.get());
|
||||
#else
|
||||
//Timer::callAfterDelay(200, []() {
|
||||
// AccessibilityHandler::postAnnouncement(TRANS("Started recording output"), AccessibilityHandler::AnnouncementPriority::high);
|
||||
//});
|
||||
#endif
|
||||
|
||||
|
||||
#if (JUCE_IOS || JUCE_ANDROID)
|
||||
filepath = m_lastRecordedFile.getRelativePathFrom(File::getSpecialLocation (File::userDocumentsDirectory));
|
||||
#else
|
||||
filepath = m_lastRecordedFile.getRelativePathFrom(File::getSpecialLocation (File::userHomeDirectory));
|
||||
#endif
|
||||
|
||||
m_recordingButton->setTooltip(TRANS("Recording audio to: ") + filepath);
|
||||
}
|
||||
else {
|
||||
// show error starting record
|
||||
String lasterr = processor.getLastErrorMessage();
|
||||
//showPopTip(lasterr, 0, mRecordingButton.get());
|
||||
}
|
||||
|
||||
m_fileRecordingLabel->setText("", dontSendNotification);
|
||||
m_recordingButton->setToggleState(true, dontSendNotification);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
///============================
|
||||
|
||||
WaveformComponent::WaveformComponent(AudioFormatManager* afm, AudioThumbnail* thumb, StretchAudioSource* sas)
|
||||
: m_sas(sas)
|
||||
{
|
||||
|
@ -12,6 +12,8 @@
|
||||
#include "envelope_component.h"
|
||||
#include "CustomLookAndFeel.h"
|
||||
|
||||
class OptionsView;
|
||||
|
||||
class zoom_scrollbar : public Component
|
||||
{
|
||||
public:
|
||||
@ -510,7 +512,8 @@ private:
|
||||
};
|
||||
|
||||
class PaulstretchpluginAudioProcessorEditor : public AudioProcessorEditor,
|
||||
public MultiTimer, public FileDragAndDropTarget, public DragAndDropContainer
|
||||
public MultiTimer, public FileDragAndDropTarget, public DragAndDropContainer, public ComponentListener
|
||||
|
||||
{
|
||||
public:
|
||||
PaulstretchpluginAudioProcessorEditor (PaulstretchpluginAudioProcessor&);
|
||||
@ -538,10 +541,12 @@ public:
|
||||
|
||||
bool keyPressed(const KeyPress& press) override;
|
||||
|
||||
void componentParentHierarchyChanged (Component& component) override;
|
||||
|
||||
|
||||
WaveformComponent m_wavecomponent;
|
||||
|
||||
void showRenderDialog();
|
||||
void executeModalMenuAction(int menuid, int actionid);
|
||||
//SimpleFFTComponent m_sonogram;
|
||||
String m_last_err;
|
||||
|
||||
@ -557,6 +562,8 @@ private:
|
||||
|
||||
void updateAllSliders();
|
||||
|
||||
void toggleOutputRecording();
|
||||
|
||||
CustomLookAndFeel m_lookandfeel;
|
||||
|
||||
PaulstretchpluginAudioProcessor& processor;
|
||||
@ -581,8 +588,15 @@ private:
|
||||
double m_lastspec_select_time = 0.0;
|
||||
int m_lastspec_select_group = -1;
|
||||
bool m_shortMode = false;
|
||||
File m_lastRecordedFile;
|
||||
std::unique_ptr<Label> m_fileRecordingLabel;
|
||||
std::unique_ptr<DrawableButton> m_recordingButton;
|
||||
|
||||
bool settingsWasShownOnDown = false;
|
||||
uint32 settingsClosedTimestamp = 0;
|
||||
WeakReference<Component> settingsCalloutBox;
|
||||
std::unique_ptr<OptionsView> m_optionsView;
|
||||
|
||||
void showSettingsMenu();
|
||||
|
||||
zoom_scrollbar m_zs;
|
||||
RatioMixerEditor m_ratiomixeditor{ 8 };
|
||||
@ -591,7 +605,7 @@ private:
|
||||
MyTabComponent m_wavefilter_tab;
|
||||
Component* m_wave_container=nullptr;
|
||||
void showAudioSetup();
|
||||
void showAbout();
|
||||
void showSettings(bool flag);
|
||||
void toggleFileBrowser();
|
||||
std::vector<int> m_capturelens{ 2,5,10,30,60,120 };
|
||||
|
||||
|
@ -294,6 +294,20 @@ m_bufferingthread("pspluginprebufferthread"), m_is_stand_alone_offline(is_stand_
|
||||
startTimer(1, 40);
|
||||
}
|
||||
|
||||
#if (JUCE_IOS)
|
||||
m_defaultRecordDir = File::getSpecialLocation (File::userDocumentsDirectory).getFullPathName();
|
||||
#elif (JUCE_ANDROID)
|
||||
auto parentDir = File::getSpecialLocation (File::userApplicationDataDirectory);
|
||||
parentDir = parentDir.getChildFile("Recordings");
|
||||
m_defaultRecordDir = parentDir.getFullPathName();
|
||||
#else
|
||||
auto parentDir = File::getSpecialLocation (File::userMusicDirectory);
|
||||
parentDir = parentDir.getChildFile("PaulXStretch");
|
||||
m_defaultRecordDir = parentDir.getFullPathName();
|
||||
#endif
|
||||
|
||||
//m_defaultCaptureDir = parentDir.getChildFile("Captures").getFullPathName();
|
||||
|
||||
m_show_technical_info = m_propsfile->m_props_file->getBoolValue("showtechnicalinfo", false);
|
||||
|
||||
DBG("Constructed PS plugin");
|
||||
@ -398,6 +412,9 @@ ValueTree PaulstretchpluginAudioProcessor::getStateTree(bool ignoreoptions, bool
|
||||
storeToTreeProperties(paramtree, nullptr, "restoreplaystate", m_restore_playstate);
|
||||
storeToTreeProperties(paramtree, nullptr, "autofinishrecord", m_auto_finish_record);
|
||||
|
||||
paramtree.setProperty("defRecordDir", m_defaultRecordDir, nullptr);
|
||||
|
||||
|
||||
return paramtree;
|
||||
}
|
||||
|
||||
@ -440,6 +457,10 @@ void PaulstretchpluginAudioProcessor::setStateFromTree(ValueTree tree)
|
||||
getFromTreeProperties(tree, "waveviewrange", m_wave_view_range);
|
||||
getFromTreeProperties(tree, getParameters());
|
||||
|
||||
#if !(JUCE_IOS || JUCE_ANDROID)
|
||||
setDefaultRecordingDirectory(tree.getProperty("defRecordDir", m_defaultRecordDir));
|
||||
#endif
|
||||
|
||||
}
|
||||
int prebufamt = tree.getProperty("prebufamount", 2);
|
||||
if (prebufamt == -1)
|
||||
@ -676,23 +697,32 @@ void PaulstretchpluginAudioProcessor::saveCaptureBuffer()
|
||||
int inchans = jmin(getMainBusNumInputChannels(), getIntParameter(cpi_num_inchans)->get());
|
||||
if (inchans < 1)
|
||||
return;
|
||||
WavAudioFormat wavformat;
|
||||
|
||||
std::unique_ptr<AudioFormat> audioFormat;
|
||||
String fextension;
|
||||
int bitsPerSample = std::min(32, m_defaultRecordingBitsPerSample);
|
||||
|
||||
if (m_defaultRecordingFormat == FileFormatWAV) {
|
||||
audioFormat = std::make_unique<WavAudioFormat>();
|
||||
fextension = ".wav";
|
||||
}
|
||||
else {
|
||||
audioFormat = std::make_unique<FlacAudioFormat>();
|
||||
fextension = ".flac";
|
||||
bitsPerSample = std::min(24, bitsPerSample);
|
||||
}
|
||||
|
||||
|
||||
String outfn;
|
||||
String filename = String("pxs_") + Time::getCurrentTime().formatted("%Y-%m-%d_%H.%M.%S");
|
||||
filename = File::createLegalFileName(filename);
|
||||
|
||||
if (m_capture_location.isEmpty()) {
|
||||
File capdir;
|
||||
#if JUCE_IOS
|
||||
capdir = File::getSpecialLocation(File::SpecialLocationType::userDocumentsDirectory);
|
||||
outfn = capdir.getChildFile("Captures").getNonexistentChildFile(filename, ".wav").getFullPathName();
|
||||
#else
|
||||
capdir = m_propsfile->m_props_file->getFile().getParentDirectory();
|
||||
outfn = capdir.getChildFile("Captures").getNonexistentChildFile(filename, ".wav").getFullPathName();
|
||||
#endif
|
||||
File capdir(m_defaultRecordDir);
|
||||
outfn = capdir.getChildFile("Captures").getNonexistentChildFile(filename, fextension).getFullPathName();
|
||||
}
|
||||
else {
|
||||
outfn = File(m_capture_location).getNonexistentChildFile(filename, ".wav").getFullPathName();
|
||||
outfn = File(m_capture_location).getNonexistentChildFile(filename, fextension).getFullPathName();
|
||||
}
|
||||
File outfile(outfn);
|
||||
outfile.create();
|
||||
@ -700,8 +730,8 @@ void PaulstretchpluginAudioProcessor::saveCaptureBuffer()
|
||||
{
|
||||
m_capture_save_state = 1;
|
||||
auto outstream = outfile.createOutputStream();
|
||||
auto writer = unique_from_raw(wavformat.createWriterFor(outstream.get(), getSampleRateChecked(),
|
||||
inchans, 32, {}, 0));
|
||||
auto writer = unique_from_raw(audioFormat->createWriterFor(outstream.get(), getSampleRateChecked(),
|
||||
inchans, bitsPerSample, {}, 0));
|
||||
if (writer != nullptr)
|
||||
{
|
||||
outstream.release(); // the writer takes ownership
|
||||
@ -1274,6 +1304,19 @@ void PaulstretchpluginAudioProcessor::processBlock (AudioSampleBuffer& buffer, M
|
||||
ed->m_sonogram.addAudioBlock(buffer);
|
||||
}
|
||||
*/
|
||||
|
||||
// output to file writer if necessary
|
||||
if (m_writingPossible.load()) {
|
||||
const ScopedTryLock sl (m_writerLock);
|
||||
if (sl.isLocked())
|
||||
{
|
||||
if (m_activeMixWriter.load() != nullptr) {
|
||||
m_activeMixWriter.load()->write (buffer.getArrayOfReadPointers(), buffer.getNumSamples());
|
||||
}
|
||||
|
||||
m_elapsedRecordSamples += buffer.getNumSamples();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
@ -1306,7 +1349,7 @@ void PaulstretchpluginAudioProcessor::setDirty()
|
||||
toggleBool(getBoolParameter(cpi_markdirty));
|
||||
}
|
||||
|
||||
void PaulstretchpluginAudioProcessor::setRecordingEnabled(bool b)
|
||||
void PaulstretchpluginAudioProcessor::setInputRecordingEnabled(bool b)
|
||||
{
|
||||
ScopedLock locker(m_cs);
|
||||
int lenbufframes = getSampleRateChecked()*m_max_reclen;
|
||||
@ -1334,7 +1377,7 @@ void PaulstretchpluginAudioProcessor::setRecordingEnabled(bool b)
|
||||
}
|
||||
}
|
||||
|
||||
double PaulstretchpluginAudioProcessor::getRecordingPositionPercent()
|
||||
double PaulstretchpluginAudioProcessor::getInputRecordingPositionPercent()
|
||||
{
|
||||
if (m_is_recording_pending==false)
|
||||
return 0.0;
|
||||
@ -1411,13 +1454,13 @@ void PaulstretchpluginAudioProcessor::timerCallback(int id)
|
||||
if (capture == true && m_is_recording_pending == false && !m_is_recording_finished)
|
||||
{
|
||||
DBG("start recording");
|
||||
setRecordingEnabled(true);
|
||||
setInputRecordingEnabled(true);
|
||||
return;
|
||||
}
|
||||
if (capture == false && m_is_recording_pending == true && !m_is_recording_finished)
|
||||
{
|
||||
DBG("stop recording");
|
||||
setRecordingEnabled(false);
|
||||
setInputRecordingEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1517,6 +1560,145 @@ void PaulstretchpluginAudioProcessor::finishRecording(int lenrecording, bool nos
|
||||
}
|
||||
}
|
||||
|
||||
bool PaulstretchpluginAudioProcessor::startRecordingToFile(File & file, RecordFileFormat fileformat)
|
||||
{
|
||||
if (!m_recordingThread) {
|
||||
m_recordingThread = std::make_unique<TimeSliceThread>("Recording Thread");
|
||||
m_recordingThread->startThread();
|
||||
}
|
||||
|
||||
stopRecordingToFile();
|
||||
|
||||
bool ret = false;
|
||||
|
||||
// Now create a WAV writer object that writes to our output stream...
|
||||
//WavAudioFormat audioFormat;
|
||||
std::unique_ptr<AudioFormat> audioFormat;
|
||||
std::unique_ptr<AudioFormat> wavAudioFormat;
|
||||
|
||||
int qualindex = 0;
|
||||
|
||||
int bitsPerSample = std::min(32, m_defaultRecordingBitsPerSample);
|
||||
|
||||
if (getSampleRate() <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
File usefile = file;
|
||||
|
||||
if (fileformat == FileFormatDefault) {
|
||||
fileformat = m_defaultRecordingFormat;
|
||||
}
|
||||
|
||||
|
||||
m_totalRecordingChannels = getMainBusNumOutputChannels();
|
||||
if (m_totalRecordingChannels == 0) {
|
||||
m_totalRecordingChannels = 2;
|
||||
}
|
||||
|
||||
if (fileformat == FileFormatFLAC && m_totalRecordingChannels > 8) {
|
||||
// flac doesn't support > 8 channels
|
||||
fileformat = FileFormatWAV;
|
||||
}
|
||||
|
||||
if (fileformat == FileFormatFLAC || (fileformat == FileFormatAuto && file.getFileExtension().toLowerCase() == ".flac")) {
|
||||
audioFormat = std::make_unique<FlacAudioFormat>();
|
||||
bitsPerSample = std::min(24, bitsPerSample);
|
||||
usefile = file.withFileExtension(".flac");
|
||||
}
|
||||
else if (fileformat == FileFormatWAV || (fileformat == FileFormatAuto && file.getFileExtension().toLowerCase() == ".wav")) {
|
||||
audioFormat = std::make_unique<WavAudioFormat>();
|
||||
usefile = file.withFileExtension(".wav");
|
||||
}
|
||||
else if (fileformat == FileFormatOGG || (fileformat == FileFormatAuto && file.getFileExtension().toLowerCase() == ".ogg")) {
|
||||
audioFormat = std::make_unique<OggVorbisAudioFormat>();
|
||||
qualindex = 8; // 256k
|
||||
usefile = file.withFileExtension(".ogg");
|
||||
}
|
||||
else {
|
||||
m_lastError = TRANS("Could not find format for filename");
|
||||
DBG(m_lastError);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool userwriting = false;
|
||||
|
||||
// Create an OutputStream to write to our destination file...
|
||||
usefile.deleteFile();
|
||||
|
||||
if (auto fileStream = std::unique_ptr<FileOutputStream> (usefile.createOutputStream()))
|
||||
{
|
||||
if (auto writer = audioFormat->createWriterFor (fileStream.get(), getSampleRate(), m_totalRecordingChannels, bitsPerSample, {}, qualindex))
|
||||
{
|
||||
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.
|
||||
m_threadedMixWriter.reset (new AudioFormatWriter::ThreadedWriter (writer, *m_recordingThread, 65536));
|
||||
|
||||
DBG("Started recording only mix file " << usefile.getFullPathName());
|
||||
|
||||
file = usefile;
|
||||
ret = true;
|
||||
} else {
|
||||
m_lastError.clear();
|
||||
m_lastError << TRANS("Error creating writer for ") << usefile.getFullPathName();
|
||||
DBG(m_lastError);
|
||||
}
|
||||
} else {
|
||||
m_lastError.clear();
|
||||
m_lastError << TRANS("Error creating output file: ") << usefile.getFullPathName();
|
||||
DBG(m_lastError);
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
// And now, swap over our active writer pointers so that the audio callback will start using it..
|
||||
const ScopedLock sl (m_writerLock);
|
||||
m_elapsedRecordSamples = 0;
|
||||
m_activeMixWriter = m_threadedMixWriter.get();
|
||||
|
||||
m_writingPossible.store(m_activeMixWriter);
|
||||
|
||||
//DBG("Started recording file " << usefile.getFullPathName());
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool PaulstretchpluginAudioProcessor::stopRecordingToFile()
|
||||
{
|
||||
// First, clear this pointer to stop the audio callback from using our writer object..
|
||||
|
||||
{
|
||||
const ScopedLock sl (m_writerLock);
|
||||
m_activeMixWriter = nullptr;
|
||||
m_writingPossible.store(false);
|
||||
}
|
||||
|
||||
bool didit = false;
|
||||
|
||||
if (m_threadedMixWriter) {
|
||||
|
||||
// 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, and we can't be blocking
|
||||
// the audio callback while this happens.
|
||||
m_threadedMixWriter.reset();
|
||||
|
||||
DBG("Stopped recording mix file");
|
||||
didit = true;
|
||||
}
|
||||
|
||||
return didit;
|
||||
}
|
||||
|
||||
bool PaulstretchpluginAudioProcessor::isRecordingToFile()
|
||||
{
|
||||
return (m_activeMixWriter.load() != nullptr);
|
||||
}
|
||||
|
||||
|
||||
|
||||
AudioProcessor* JUCE_CALLTYPE createPluginFilter()
|
||||
{
|
||||
return new PaulstretchpluginAudioProcessor();
|
||||
|
@ -193,9 +193,33 @@ public:
|
||||
|
||||
|
||||
void setDirty();
|
||||
void setRecordingEnabled(bool b);
|
||||
bool isRecordingEnabled() { return m_is_recording_pending; }
|
||||
double getRecordingPositionPercent();
|
||||
|
||||
void setInputRecordingEnabled(bool b);
|
||||
bool isInputRecordingEnabled() { return m_is_recording_pending; }
|
||||
double getInputRecordingPositionPercent();
|
||||
|
||||
enum RecordFileFormat {
|
||||
FileFormatDefault = 0,
|
||||
FileFormatAuto,
|
||||
FileFormatFLAC,
|
||||
FileFormatWAV,
|
||||
FileFormatOGG
|
||||
};
|
||||
|
||||
bool startRecordingToFile(File & file, RecordFileFormat fileformat=FileFormatDefault);
|
||||
bool stopRecordingToFile();
|
||||
bool isRecordingToFile();
|
||||
double getElapsedRecordTime() const { return m_elapsedRecordSamples / getSampleRate(); }
|
||||
String getLastErrorMessage() const { return m_lastError; }
|
||||
void setDefaultRecordingDirectory(String recdir) {
|
||||
m_defaultRecordDir = recdir;
|
||||
}
|
||||
String getDefaultRecordingDirectory() const { return m_defaultRecordDir; }
|
||||
RecordFileFormat getDefaultRecordingFormat() const { return m_defaultRecordingFormat; }
|
||||
void setDefaultRecordingFormat(RecordFileFormat fmt) { m_defaultRecordingFormat = fmt; }
|
||||
int getDefaultRecordingBitsPerSample() const { return m_defaultRecordingBitsPerSample; }
|
||||
void setDefaultRecordingBitsPerSample(int fmt) { m_defaultRecordingBitsPerSample = fmt; }
|
||||
|
||||
String setAudioFile(const URL& url);
|
||||
URL getAudioFile() { return m_current_file; }
|
||||
Range<double> getTimeSelection();
|
||||
@ -316,6 +340,23 @@ private:
|
||||
int m_midinote_to_use = -1;
|
||||
ADSR m_adsr;
|
||||
bool m_is_stand_alone_offline = false;
|
||||
|
||||
// recording stuff
|
||||
|
||||
RecordFileFormat m_defaultRecordingFormat = FileFormatFLAC;
|
||||
int m_defaultRecordingBitsPerSample = 24;
|
||||
String m_defaultRecordDir;
|
||||
String m_defaultCaptureDir;
|
||||
String m_lastError;
|
||||
|
||||
std::atomic<bool> m_writingPossible = { false };
|
||||
int m_totalRecordingChannels = 2;
|
||||
int64 m_elapsedRecordSamples = 0;
|
||||
CriticalSection m_writerLock;
|
||||
std::unique_ptr<TimeSliceThread> m_recordingThread;
|
||||
std::unique_ptr<AudioFormatWriter::ThreadedWriter> m_threadedMixWriter;
|
||||
std::atomic<AudioFormatWriter::ThreadedWriter*> m_activeMixWriter { nullptr };
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PaulstretchpluginAudioProcessor)
|
||||
};
|
||||
|
245
Source/SonoChoiceButton.cpp
Normal file
245
Source/SonoChoiceButton.cpp
Normal file
@ -0,0 +1,245 @@
|
||||
// SPDX-License-Identifier: GPLv3-or-later WITH Appstore-exception
|
||||
// Copyright (C) 2020 Jesse Chappell
|
||||
|
||||
|
||||
#include "SonoChoiceButton.h"
|
||||
|
||||
SonoChoiceButton::SonoChoiceButton()
|
||||
{
|
||||
textLabel = std::make_unique<Label>();
|
||||
addAndMakeVisible(textLabel.get());
|
||||
textLabel->setJustificationType(Justification::centredLeft);
|
||||
textLabel->setAccessible(false);
|
||||
|
||||
selIndex = 0;
|
||||
addListener(this);
|
||||
|
||||
}
|
||||
|
||||
SonoChoiceButton::~SonoChoiceButton()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void SonoChoiceButton::clearItems()
|
||||
{
|
||||
items.clear();
|
||||
idList.clear();
|
||||
textLabel->setText("", dontSendNotification);
|
||||
}
|
||||
|
||||
void SonoChoiceButton::addItem(const String & name, int ident, bool separator, bool disabled)
|
||||
{
|
||||
items.add(GenericItemChooserItem(name, Image(), nullptr, separator, disabled));
|
||||
idList.add(ident);
|
||||
}
|
||||
|
||||
void SonoChoiceButton::addItem(const String & name, int ident, const Image & newItemImage, bool separator, bool disabled)
|
||||
{
|
||||
items.add(GenericItemChooserItem(name, newItemImage, nullptr, separator, disabled));
|
||||
idList.add(ident);
|
||||
}
|
||||
|
||||
|
||||
void SonoChoiceButton::setSelectedItemIndex(int index, NotificationType notification)
|
||||
{
|
||||
selIndex = index;
|
||||
if (selIndex < items.size()) {
|
||||
textLabel->setText(items[selIndex].name, dontSendNotification);
|
||||
}
|
||||
|
||||
if (notification != dontSendNotification) {
|
||||
int ident = index < idList.size() ? idList[index] : 0;
|
||||
listeners.call (&SonoChoiceButton::Listener::choiceButtonSelected, this, index, ident);
|
||||
}
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
void SonoChoiceButton::setSelectedId(int ident, NotificationType notification)
|
||||
{
|
||||
for (int i=0; i < idList.size(); ++i) {
|
||||
if (idList[i] == ident) {
|
||||
setSelectedItemIndex(i, notification);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
String SonoChoiceButton::getItemText(int index) const
|
||||
{
|
||||
if (selIndex < items.size()) {
|
||||
return items[selIndex].name;
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
String SonoChoiceButton::getCurrentText() const
|
||||
{
|
||||
return textLabel->getText();
|
||||
}
|
||||
|
||||
|
||||
void SonoChoiceButton::genericItemChooserSelected(GenericItemChooser *comp, int index)
|
||||
{
|
||||
setSelectedItemIndex(index);
|
||||
|
||||
int ident = index < idList.size() ? idList[index] : 0;
|
||||
|
||||
listeners.call (&SonoChoiceButton::Listener::choiceButtonSelected, this, index, ident);
|
||||
|
||||
if (CallOutBox* const dw = comp->findParentComponentOfClass<CallOutBox>()) {
|
||||
dw->dismiss();
|
||||
}
|
||||
|
||||
setWantsKeyboardFocus(true);
|
||||
|
||||
Timer::callAfterDelay(200, [this](){
|
||||
if (isShowing()) grabKeyboardFocus();
|
||||
});
|
||||
}
|
||||
|
||||
void SonoChoiceButton::resized()
|
||||
{
|
||||
SonoTextButton::resized();
|
||||
|
||||
int xoff = 0;
|
||||
bool validImage = false;
|
||||
if (selIndex < items.size()) {
|
||||
if (items[selIndex].image.isValid()) {
|
||||
float imagesize = getHeight() - 8;
|
||||
xoff = imagesize;
|
||||
validImage = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (showArrow && getWidth()-(20 + xoff + 4) > 40) {
|
||||
textLabel->setBounds(4 + xoff, 2, getWidth() - 22, getHeight()-4 - xoff);
|
||||
}
|
||||
else if (!showArrow && getWidth()-(xoff + 4) > 40) {
|
||||
textLabel->setBounds(4 + xoff, 2, getWidth() - 8 - xoff, getHeight()-4 - xoff);
|
||||
}
|
||||
else {
|
||||
textLabel->setSize(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void SonoChoiceButton::paint(Graphics & g)
|
||||
{
|
||||
int width = getWidth();
|
||||
int height = getHeight();
|
||||
|
||||
SonoTextButton::paint(g);
|
||||
|
||||
if (showArrow) {
|
||||
Rectangle<int> arrowZone (width - 20, 0, 16, height);
|
||||
Path path;
|
||||
path.startNewSubPath (arrowZone.getX() + 3.0f, arrowZone.getCentreY() - 2.0f);
|
||||
path.lineTo (static_cast<float> (arrowZone.getCentreX()), arrowZone.getCentreY() + 3.0f);
|
||||
path.lineTo (arrowZone.getRight() - 3.0f, arrowZone.getCentreY() - 2.0f);
|
||||
|
||||
g.setColour (findColour (ComboBox::arrowColourId).withAlpha ((isEnabled() ? 0.9f : 0.2f)));
|
||||
g.strokePath (path, PathStrokeType (2.0f));
|
||||
}
|
||||
|
||||
if (selIndex < items.size()) {
|
||||
if (items[selIndex].image.isValid()) {
|
||||
float imagesize = height - 8;
|
||||
//g.drawImage(items[selIndex].image, Rectangle<float>(2, 4, imagesize, imagesize));
|
||||
g.drawImageWithin(items[selIndex].image, 2, 4, imagesize, imagesize, RectanglePlacement(RectanglePlacement::centred|RectanglePlacement::onlyReduceInSize));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void SonoChoiceButton::buttonClicked (Button* buttonThatWasClicked)
|
||||
{
|
||||
if (items.isEmpty()) {
|
||||
listeners.call (&SonoChoiceButton::Listener::choiceButtonEmptyClick, this);
|
||||
}
|
||||
else {
|
||||
showPopup();
|
||||
}
|
||||
}
|
||||
|
||||
void SonoChoiceButton::showPopup()
|
||||
{
|
||||
auto chooser = std::make_unique<GenericItemChooser>(items);
|
||||
|
||||
chooser->setRowHeight(std::min(getHeight(), 40));
|
||||
chooser->addListener(this);
|
||||
chooser->setCurrentRow(selIndex);
|
||||
//DocumentWindow* const dw = this->findParentComponentOfClass<DocumentWindow>();
|
||||
Component* dw = this->findParentComponentOfClass<DocumentWindow>();
|
||||
|
||||
if (!dw) {
|
||||
dw = this->findParentComponentOfClass<AudioProcessorEditor>();
|
||||
}
|
||||
if (!dw) {
|
||||
dw = this->findParentComponentOfClass<Component>();
|
||||
}
|
||||
|
||||
chooser->setSize(jmin(chooser->getWidth(), dw->getWidth() - 16), jmin(chooser->getHeight(), dw->getHeight() - 20));
|
||||
|
||||
|
||||
Rectangle<int> bounds = dw->getLocalArea(nullptr, getScreenBounds());
|
||||
|
||||
|
||||
CallOutBox & box = CallOutBox::launchAsynchronously (std::move(chooser), bounds , dw);
|
||||
box.setDismissalMouseClicksAreAlwaysConsumed(true);
|
||||
//box.setArrowSize(0);
|
||||
|
||||
box.grabKeyboardFocus();
|
||||
|
||||
activeCalloutBox = &box;
|
||||
}
|
||||
|
||||
bool SonoChoiceButton::isPopupActive() const
|
||||
{
|
||||
return activeCalloutBox.get() != nullptr;
|
||||
}
|
||||
|
||||
|
||||
//==============================================================================
|
||||
class SonoChoiceButtonAccessibilityHandler : public AccessibilityHandler
|
||||
{
|
||||
public:
|
||||
explicit SonoChoiceButtonAccessibilityHandler (SonoChoiceButton& comboBoxToWrap)
|
||||
: AccessibilityHandler (comboBoxToWrap,
|
||||
AccessibilityRole::comboBox,
|
||||
getAccessibilityActions (comboBoxToWrap)),
|
||||
comboBox (comboBoxToWrap)
|
||||
{
|
||||
}
|
||||
|
||||
AccessibleState getCurrentState() const override
|
||||
{
|
||||
auto state = AccessibilityHandler::getCurrentState().withExpandable();
|
||||
|
||||
return comboBox.isPopupActive() ? state.withExpanded() : state.withCollapsed();
|
||||
}
|
||||
|
||||
String getTitle() const override { return comboBox.getTitle() + ", " + comboBox.getCurrentText(); }
|
||||
|
||||
private:
|
||||
static AccessibilityActions getAccessibilityActions (SonoChoiceButton& comboBox)
|
||||
{
|
||||
return AccessibilityActions().addAction (AccessibilityActionType::press, [&comboBox] { comboBox.showPopup(); })
|
||||
.addAction (AccessibilityActionType::showMenu, [&comboBox] { comboBox.showPopup(); });
|
||||
}
|
||||
|
||||
SonoChoiceButton& comboBox;
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SonoChoiceButtonAccessibilityHandler)
|
||||
};
|
||||
|
||||
std::unique_ptr<AccessibilityHandler> SonoChoiceButton::createAccessibilityHandler()
|
||||
{
|
||||
return std::make_unique<SonoChoiceButtonAccessibilityHandler> (*this);
|
||||
}
|
90
Source/SonoChoiceButton.h
Normal file
90
Source/SonoChoiceButton.h
Normal file
@ -0,0 +1,90 @@
|
||||
// SPDX-License-Identifier: GPLv3-or-later WITH Appstore-exception
|
||||
// Copyright (C) 2020 Jesse Chappell
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "JuceHeader.h"
|
||||
|
||||
#include "SonoTextButton.h"
|
||||
#include "GenericItemChooser.h"
|
||||
#include "CustomLookAndFeel.h"
|
||||
|
||||
class SonoChoiceLookAndFeel : public CustomLookAndFeel
|
||||
{
|
||||
public:
|
||||
int getCallOutBoxBorderSize (const CallOutBox&) override {
|
||||
return 40;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
class SonoChoiceButton : public SonoTextButton, public GenericItemChooser::Listener, public Button::Listener
|
||||
{
|
||||
public:
|
||||
SonoChoiceButton();
|
||||
virtual ~SonoChoiceButton();
|
||||
|
||||
class Listener {
|
||||
public:
|
||||
virtual ~Listener() {}
|
||||
virtual void choiceButtonEmptyClick(SonoChoiceButton *comp) {}
|
||||
virtual void choiceButtonSelected(SonoChoiceButton *comp, int index, int ident) {}
|
||||
};
|
||||
|
||||
void paint(Graphics & g) override;
|
||||
void resized() override;
|
||||
|
||||
void genericItemChooserSelected(GenericItemChooser *comp, int index) override;
|
||||
|
||||
void clearItems();
|
||||
void addItem(const String & name, int ident, bool separator=false, bool disabled=false);
|
||||
void addItem(const String & name, int ident, const Image & newItemImage, bool separator=false, bool disabled=false);
|
||||
|
||||
//void setItems(const StringArray & items);
|
||||
//const StringArray & getItems() const { return items; }
|
||||
|
||||
void setSelectedItemIndex(int index, NotificationType notification = dontSendNotification);
|
||||
int getSelectedItemIndex() const { return selIndex; }
|
||||
|
||||
void setSelectedId(int ident, NotificationType notification = dontSendNotification);
|
||||
|
||||
void setShowArrow(bool flag) { showArrow = flag; }
|
||||
bool getShowArrow() const { return showArrow; }
|
||||
|
||||
String getItemText(int index) const;
|
||||
|
||||
String getCurrentText() const;
|
||||
|
||||
void buttonClicked (Button* buttonThatWasClicked) override;
|
||||
|
||||
void addChoiceListener(Listener * listener) { listeners.add(listener); }
|
||||
void removeChoiceListener(Listener * listener) { listeners.remove(listener); }
|
||||
|
||||
void showPopup();
|
||||
bool isPopupActive() const;
|
||||
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
|
||||
|
||||
protected:
|
||||
ListenerList<Listener> listeners;
|
||||
|
||||
std::unique_ptr<Label> textLabel;
|
||||
|
||||
Array<GenericItemChooserItem> items;
|
||||
Array<int> idList;
|
||||
|
||||
int selIndex;
|
||||
bool showArrow = true;
|
||||
|
||||
WeakReference<Component> activeCalloutBox;
|
||||
|
||||
SonoChoiceLookAndFeel lnf;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SonoChoiceButton)
|
||||
|
||||
};
|
||||
|
||||
|
409
Source/SonoTextButton.cpp
Normal file
409
Source/SonoTextButton.cpp
Normal file
@ -0,0 +1,409 @@
|
||||
// SPDX-License-Identifier: GPLv3-or-later WITH Appstore-exception
|
||||
// Copyright (C) 2020 Jesse Chappell
|
||||
|
||||
|
||||
#include "SonoTextButton.h"
|
||||
#include "CustomLookAndFeel.h"
|
||||
#include <math.h>
|
||||
|
||||
SonoTextButton::SonoTextButton(const String & name)
|
||||
: TextButton(name), _buttonStyle(SonoButtonStyleNormal)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
SonoTextButton::~SonoTextButton()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void SonoTextButton::setButtonStyle(SonoButtonStyle style)
|
||||
{
|
||||
_buttonStyle = style;
|
||||
}
|
||||
|
||||
|
||||
void SonoTextButton::drawButtonBackground(Graphics& g, bool isMouseOverButton, bool isButtonDown)
|
||||
{
|
||||
Colour bgcolor = findColour (getToggleState() ? buttonOnColourId : buttonColourId);
|
||||
Colour bordcolor = isColourSpecified(outlineColourId) ? findColour (outlineColourId) : Colour::fromFloatRGBA(0.3, 0.3, 0.3, 0.5);
|
||||
|
||||
if (isButtonDown) {
|
||||
bgcolor = bgcolor.withMultipliedBrightness(1.8);
|
||||
} else if (isMouseOverButton) {
|
||||
bgcolor = bgcolor.withMultipliedBrightness(1.3);
|
||||
}
|
||||
|
||||
g.setColour(bgcolor);
|
||||
g.fillPath(fillPath);
|
||||
|
||||
g.setColour(bordcolor);
|
||||
g.strokePath(borderPath, PathStrokeType(1.0));
|
||||
|
||||
}
|
||||
|
||||
|
||||
void SonoTextButton::paintButton (Graphics& g, bool isMouseOverButton, bool isButtonDown)
|
||||
{
|
||||
LookAndFeel& lf = getLookAndFeel();
|
||||
|
||||
drawButtonBackground(g, isMouseOverButton, isButtonDown);
|
||||
|
||||
if (auto * tetlf = dynamic_cast<CustomLookAndFeel*>(&lf)) {
|
||||
Justification just = textJustification;
|
||||
switch (_buttonStyle) {
|
||||
case SonoButtonStyleLowerLeftCorner: just = Justification::centredLeft; break;
|
||||
case SonoButtonStyleLowerRightCorner: just = Justification::centredRight; break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
tetlf->drawButtonTextWithAlignment (g, *this, isMouseOverButton, isButtonDown, just);
|
||||
}
|
||||
else {
|
||||
lf.drawButtonText (g, *this, isMouseOverButton, isButtonDown);
|
||||
}
|
||||
}
|
||||
|
||||
bool SonoTextButton::hitTest (int x, int y)
|
||||
{
|
||||
return fillPath.contains(x, y);
|
||||
}
|
||||
|
||||
|
||||
void SonoTextButton::resized()
|
||||
{
|
||||
TextButton::resized();
|
||||
|
||||
setupPath();
|
||||
}
|
||||
|
||||
void SonoTextButton::setupPath() {
|
||||
|
||||
float borderOffset = 0.5; // _borderWidth*1.5f;
|
||||
float width = getWidth();
|
||||
float height = getHeight();
|
||||
fillPath.clear();
|
||||
borderPath.clear();
|
||||
|
||||
|
||||
if (_buttonStyle == SonoButtonStyleTop) {
|
||||
fillPath.startNewSubPath(borderOffset, borderOffset);
|
||||
fillPath.quadraticTo(width/2.0, height-borderOffset, width - borderOffset, borderOffset);
|
||||
fillPath.lineTo(borderOffset, borderOffset);
|
||||
|
||||
borderPath.startNewSubPath(borderOffset, borderOffset);
|
||||
borderPath.quadraticTo(width/2.0, height-borderOffset, width - borderOffset, borderOffset);
|
||||
|
||||
//[fillPath moveToPoint:CGPointMake(borderOffset, borderOffset)];
|
||||
//[fillPath addQuadCurveToPoint:CGPointMake(width - borderOffset, borderOffset) controlPoint:CGPointMake(width/2.0, height-borderOffset) ];
|
||||
//[fillPath addLineToPoint:CGPointMake(borderOffset, borderOffset)];
|
||||
|
||||
//[borderPath moveToPoint:CGPointMake(borderOffset, borderOffset)];
|
||||
//[borderPath addQuadCurveToPoint:CGPointMake(width - borderOffset, borderOffset) controlPoint:CGPointMake(width/2.0, height-borderOffset) ];
|
||||
|
||||
|
||||
}
|
||||
else if (_buttonStyle == SonoButtonStyleBottom) {
|
||||
// H /2 + W^2 / 8H
|
||||
float maxdim = jmax(width, height);
|
||||
float radius = circleRadius > 0.0f ? circleRadius : height * 0.5 + (width*width)/(8.0f * height);
|
||||
float startAng = atan2(height - radius, maxdim/2 - width);
|
||||
float endAng = atan2(height - radius, width - maxdim/2);
|
||||
|
||||
startAng += M_PI_2;
|
||||
endAng += M_PI_2;
|
||||
|
||||
DBG("Start ang " << radiansToDegrees(startAng) << " end " << radiansToDegrees(endAng) << " h: " << height << " rad: " << radius);
|
||||
|
||||
fillPath.startNewSubPath(borderOffset, height - borderOffset);
|
||||
fillPath.addCentredArc(maxdim/2, radius, radius, radius, 0.0f, startAng, endAng);
|
||||
fillPath.lineTo(borderOffset, height - borderOffset);
|
||||
|
||||
borderPath.startNewSubPath(borderOffset, height - borderOffset);
|
||||
borderPath.addCentredArc(maxdim/2, radius, radius, radius, 0.0f, startAng, endAng);
|
||||
borderPath.lineTo(borderOffset, height - borderOffset);
|
||||
|
||||
|
||||
//[fillPath moveToPoint:CGPointMake(borderOffset, height - borderOffset)];
|
||||
//[fillPath addArcWithCenter:CGPointMake(maxdim/2, radius) radius:radius startAngle:startAng endAngle:endAng clockwise:YES];
|
||||
//[fillPath addLineToPoint:CGPointMake(borderOffset, height - borderOffset)];
|
||||
|
||||
//borderPath = [UIBezierPath bezierPath];
|
||||
//[borderPath moveToPoint:CGPointMake(borderOffset, height -borderOffset)];
|
||||
//[borderPath addArcWithCenter:CGPointMake(maxdim/2, radius) radius:radius startAngle:startAng endAngle:endAng clockwise:YES];
|
||||
//[borderPath addLineToPoint:CGPointMake(borderOffset, height - borderOffset)];
|
||||
}
|
||||
else if (_buttonStyle == SonoButtonStyleLeft) {
|
||||
|
||||
fillPath.startNewSubPath(borderOffset, borderOffset);
|
||||
fillPath.quadraticTo(width - borderOffset, borderOffset, width - borderOffset, height/2);
|
||||
fillPath.quadraticTo(width - borderOffset, height-borderOffset, borderOffset, height-borderOffset);
|
||||
fillPath.lineTo(borderOffset, borderOffset);
|
||||
|
||||
borderPath.startNewSubPath(borderOffset, borderOffset);
|
||||
borderPath.quadraticTo(width - borderOffset, borderOffset, width - borderOffset, height/2);
|
||||
borderPath.quadraticTo(width - borderOffset, height-borderOffset, borderOffset, height-borderOffset);
|
||||
|
||||
//fillPath = [UIBezierPath bezierPath];
|
||||
//[fillPath moveToPoint:CGPointMake(borderOffset, borderOffset)];
|
||||
//[fillPath addQuadCurveToPoint:CGPointMake(width - borderOffset, height/2) controlPoint:CGPointMake(width - borderOffset , borderOffset) ];
|
||||
//[fillPath addQuadCurveToPoint:CGPointMake(borderOffset, height-borderOffset) controlPoint:CGPointMake(width - borderOffset , height-borderOffset) ];
|
||||
//[fillPath addLineToPoint:CGPointMake(borderOffset, borderOffset)];
|
||||
|
||||
//borderPath = [UIBezierPath bezierPath];
|
||||
//[borderPath moveToPoint:CGPointMake(borderOffset, borderOffset)];
|
||||
//[borderPath addQuadCurveToPoint:CGPointMake(width - borderOffset, height/2) controlPoint:CGPointMake(width - borderOffset , borderOffset) ];
|
||||
//[borderPath addQuadCurveToPoint:CGPointMake(borderOffset, height-borderOffset) controlPoint:CGPointMake(width - borderOffset , height-borderOffset) ];
|
||||
}
|
||||
else if (_buttonStyle == SonoButtonStyleRight) {
|
||||
|
||||
fillPath.startNewSubPath(width - borderOffset, borderOffset);
|
||||
fillPath.quadraticTo(borderOffset, borderOffset, borderOffset, height/2);
|
||||
fillPath.quadraticTo(borderOffset, height-borderOffset, width - borderOffset, height-borderOffset);
|
||||
fillPath.lineTo(width - borderOffset, borderOffset);
|
||||
|
||||
borderPath.startNewSubPath(width - borderOffset, borderOffset);
|
||||
borderPath.quadraticTo(borderOffset, borderOffset, borderOffset, height/2);
|
||||
borderPath.quadraticTo(borderOffset, height-borderOffset, width - borderOffset, height-borderOffset);
|
||||
|
||||
|
||||
//fillPath = [UIBezierPath bezierPath];
|
||||
//[fillPath moveToPoint:CGPointMake(width - borderOffset, borderOffset)];
|
||||
//[fillPath addQuadCurveToPoint:CGPointMake(borderOffset, height/2) controlPoint:CGPointMake(borderOffset, borderOffset) ];
|
||||
//[fillPath addQuadCurveToPoint:CGPointMake(width - borderOffset, height - borderOffset) controlPoint:CGPointMake(borderOffset, height - borderOffset)];
|
||||
//[fillPath addLineToPoint:CGPointMake(width - borderOffset, borderOffset)];
|
||||
|
||||
//borderPath = [UIBezierPath bezierPath];
|
||||
//[borderPath moveToPoint:CGPointMake(width -borderOffset, borderOffset)];
|
||||
//[borderPath addQuadCurveToPoint:CGPointMake(borderOffset, height/2) controlPoint:CGPointMake(borderOffset, borderOffset) ];
|
||||
//[borderPath addQuadCurveToPoint:CGPointMake(width - borderOffset, height - borderOffset) controlPoint:CGPointMake(borderOffset, height - borderOffset)];
|
||||
|
||||
}
|
||||
else if (_buttonStyle == SonoButtonStyleUpperLeftCorner) {
|
||||
|
||||
fillPath.startNewSubPath(borderOffset, borderOffset);
|
||||
fillPath.lineTo(width - borderOffset, borderOffset);
|
||||
fillPath.quadraticTo(width - borderOffset, height - borderOffset, borderOffset, height - borderOffset);
|
||||
fillPath.lineTo(borderOffset, borderOffset);
|
||||
|
||||
borderPath.startNewSubPath(width - borderOffset, borderOffset);
|
||||
borderPath.quadraticTo(width - borderOffset, height - borderOffset, borderOffset, height - borderOffset);
|
||||
|
||||
|
||||
//fillPath = [UIBezierPath bezierPath];
|
||||
//[fillPath moveToPoint:CGPointMake(borderOffset, borderOffset)];
|
||||
//[fillPath addLineToPoint:CGPointMake(width-borderOffset, borderOffset)];
|
||||
//[fillPath addQuadCurveToPoint:CGPointMake(borderOffset, height-borderOffset) controlPoint:CGPointMake(width-borderOffset, height-borderOffset) ];
|
||||
//[fillPath addLineToPoint:CGPointMake(borderOffset, borderOffset)];
|
||||
|
||||
//borderPath = [UIBezierPath bezierPath];
|
||||
//[borderPath moveToPoint:CGPointMake(width-borderOffset, borderOffset)];
|
||||
//[borderPath addQuadCurveToPoint:CGPointMake(borderOffset, height-borderOffset) controlPoint:CGPointMake(width-borderOffset, height-borderOffset) ];
|
||||
|
||||
}
|
||||
else if (_buttonStyle == SonoButtonStyleUpperRightCorner) {
|
||||
|
||||
fillPath.startNewSubPath(width - borderOffset, borderOffset);
|
||||
fillPath.lineTo(width - borderOffset, height - borderOffset);
|
||||
fillPath.quadraticTo(borderOffset, height - borderOffset, borderOffset, borderOffset);
|
||||
fillPath.lineTo(width - borderOffset, borderOffset);
|
||||
|
||||
borderPath.startNewSubPath(width - borderOffset, height - borderOffset);
|
||||
borderPath.quadraticTo(borderOffset, height - borderOffset, borderOffset, borderOffset);
|
||||
|
||||
|
||||
//fillPath = [UIBezierPath bezierPath];
|
||||
//[fillPath moveToPoint:CGPointMake(width - borderOffset, borderOffset)];
|
||||
//[fillPath addLineToPoint:CGPointMake(width - borderOffset, height - borderOffset)];
|
||||
//[fillPath addQuadCurveToPoint:CGPointMake(borderOffset, borderOffset) controlPoint:CGPointMake(borderOffset, height-borderOffset) ];
|
||||
//[fillPath addLineToPoint:CGPointMake(width - borderOffset, borderOffset)];
|
||||
|
||||
//borderPath = [UIBezierPath bezierPath];
|
||||
//[borderPath moveToPoint:CGPointMake(width - borderOffset, height - borderOffset)];
|
||||
//[borderPath addQuadCurveToPoint:CGPointMake(borderOffset, borderOffset) controlPoint:CGPointMake(borderOffset, height-borderOffset) ];
|
||||
|
||||
}
|
||||
else if (_buttonStyle == SonoButtonStyleLowerLeftCorner) {
|
||||
|
||||
fillPath.startNewSubPath(borderOffset, height - borderOffset);
|
||||
fillPath.lineTo(borderOffset, borderOffset);
|
||||
fillPath.quadraticTo(width-borderOffset, borderOffset, width-borderOffset, height-borderOffset);
|
||||
fillPath.lineTo(borderOffset, height-borderOffset);
|
||||
|
||||
borderPath.startNewSubPath(borderOffset, borderOffset);
|
||||
borderPath.quadraticTo(width-borderOffset, borderOffset, width-borderOffset, height-borderOffset);
|
||||
|
||||
//fillPath = [UIBezierPath bezierPath];
|
||||
//[fillPath moveToPoint:CGPointMake(borderOffset, height-borderOffset)];
|
||||
//[fillPath addLineToPoint:CGPointMake(borderOffset, borderOffset)];
|
||||
//[fillPath addQuadCurveToPoint:CGPointMake(width-borderOffset, height-borderOffset) controlPoint:CGPointMake(width-borderOffset, borderOffset) ];
|
||||
//[fillPath addLineToPoint:CGPointMake(borderOffset, height-borderOffset)];
|
||||
|
||||
//borderPath = [UIBezierPath bezierPath];
|
||||
//[borderPath moveToPoint:CGPointMake(borderOffset, borderOffset)];
|
||||
//[borderPath addQuadCurveToPoint:CGPointMake(width-borderOffset, height-borderOffset) controlPoint:CGPointMake(width-borderOffset, borderOffset) ];
|
||||
}
|
||||
else if (_buttonStyle == SonoButtonStyleLowerRightCorner) {
|
||||
|
||||
fillPath.startNewSubPath(width-borderOffset, height - borderOffset);
|
||||
fillPath.lineTo(borderOffset, height-borderOffset);
|
||||
fillPath.quadraticTo(borderOffset, borderOffset, width-borderOffset, borderOffset);
|
||||
fillPath.lineTo(width - borderOffset, height-borderOffset);
|
||||
|
||||
borderPath.startNewSubPath(borderOffset, height - borderOffset);
|
||||
borderPath.quadraticTo(borderOffset, borderOffset, width-borderOffset, borderOffset);
|
||||
|
||||
|
||||
//fillPath = [UIBezierPath bezierPath];
|
||||
//[fillPath moveToPoint:CGPointMake(width-borderOffset, height-borderOffset)];
|
||||
//[fillPath addLineToPoint:CGPointMake(borderOffset, height-borderOffset)];
|
||||
//[fillPath addQuadCurveToPoint:CGPointMake(width-borderOffset, borderOffset) controlPoint:CGPointMake(borderOffset, borderOffset) ];
|
||||
//[fillPath addLineToPoint:CGPointMake(width-borderOffset, height-borderOffset)];
|
||||
|
||||
//borderPath = [UIBezierPath bezierPath];
|
||||
//[borderPath moveToPoint:CGPointMake(borderOffset, height-borderOffset)];
|
||||
//[borderPath addQuadCurveToPoint:CGPointMake(width-borderOffset, borderOffset) controlPoint:CGPointMake(borderOffset, borderOffset) ];
|
||||
}
|
||||
else if (_buttonStyle == SonoButtonStyleLowerRightCornerRound) {
|
||||
float maxdim = jmax(width*2, height);
|
||||
// H /2 + W^2 / 8H (but double the width here)
|
||||
float radius = circleRadius > 0.0f ? circleRadius : height * 0.5 + (width*width*4.0)/(8.0f * height);
|
||||
float startAng = atan2(height - radius, maxdim/2 - width*2);
|
||||
float endAng = atan2(height - radius, width - maxdim/2) ;
|
||||
|
||||
startAng += M_PI_2;
|
||||
endAng += M_PI_2;
|
||||
|
||||
fillPath.startNewSubPath(borderOffset, height - borderOffset);
|
||||
fillPath.addCentredArc(maxdim/2, radius, radius, radius, 0.0f, startAng, endAng);
|
||||
fillPath.lineTo(width - borderOffset, height - borderOffset);
|
||||
fillPath.lineTo(borderOffset, height - borderOffset);
|
||||
|
||||
borderPath.startNewSubPath(borderOffset, height - borderOffset);
|
||||
borderPath.addCentredArc(maxdim/2, radius, radius, radius, 0.0f, startAng, endAng);
|
||||
borderPath.lineTo(width - borderOffset, height - borderOffset);
|
||||
borderPath.lineTo(borderOffset, height - borderOffset);
|
||||
|
||||
|
||||
//fillPath = [UIBezierPath bezierPath];
|
||||
//[fillPath moveToPoint:CGPointMake(borderOffset, height - borderOffset)];
|
||||
//[fillPath addArcWithCenter:CGPointMake(maxdim/2, radius) radius:radius startAngle:startAng endAngle:endAng clockwise:YES];
|
||||
//[fillPath addLineToPoint:CGPointMake(width - borderOffset, height - borderOffset)];
|
||||
//[fillPath addLineToPoint:CGPointMake(borderOffset, height - borderOffset)];
|
||||
|
||||
//borderPath = [UIBezierPath bezierPath];
|
||||
//[borderPath moveToPoint:CGPointMake(borderOffset, height -borderOffset)];
|
||||
//[borderPath addArcWithCenter:CGPointMake(maxdim/2, radius) radius:radius startAngle:startAng endAngle:endAng clockwise:YES];
|
||||
//[borderPath addLineToPoint:CGPointMake(width - borderOffset, height - borderOffset)];
|
||||
//[borderPath addLineToPoint:CGPointMake(borderOffset, height - borderOffset)];
|
||||
}
|
||||
else if (_buttonStyle == SonoButtonStyleLowerLeftCornerRound) {
|
||||
float maxdim = jmax(width*2, height);
|
||||
// H /2 + W^2 / 8H (but double the width here)
|
||||
float radius = circleRadius > 0.0f ? circleRadius : height * 0.5 + (width*width*4.0)/(8.0f * height);
|
||||
float startAng = atan2(height - radius, width - maxdim/2) ;
|
||||
float endAng = atan2(height - radius, width*2 - maxdim/2);
|
||||
startAng += M_PI_2;
|
||||
endAng += M_PI_2;
|
||||
|
||||
fillPath.startNewSubPath(borderOffset, height - borderOffset);
|
||||
fillPath.lineTo(borderOffset, borderOffset);
|
||||
fillPath.addCentredArc(maxdim/2, radius, radius, radius, 0.0f, startAng, endAng);
|
||||
fillPath.lineTo(borderOffset, height - borderOffset);
|
||||
|
||||
borderPath.startNewSubPath(borderOffset, height - borderOffset);
|
||||
borderPath.lineTo(borderOffset, borderOffset);
|
||||
borderPath.addCentredArc(maxdim/2, radius, radius, radius, 0.0f, startAng, endAng);
|
||||
borderPath.lineTo(borderOffset, height - borderOffset);
|
||||
|
||||
//fillPath = [UIBezierPath bezierPath];
|
||||
//[fillPath moveToPoint:CGPointMake(borderOffset, height - borderOffset)];
|
||||
//[fillPath addLineToPoint:CGPointMake(borderOffset, borderOffset)];
|
||||
//[fillPath addArcWithCenter:CGPointMake(maxdim/2 - width, radius) radius:radius startAngle:startAng endAngle:endAng clockwise:YES];
|
||||
//[fillPath addLineToPoint:CGPointMake(borderOffset, height - borderOffset)];
|
||||
|
||||
//borderPath = [UIBezierPath bezierPath];
|
||||
//[borderPath moveToPoint:CGPointMake(borderOffset, height -borderOffset)];
|
||||
//[borderPath addLineToPoint:CGPointMake(borderOffset, borderOffset)];
|
||||
//[borderPath addArcWithCenter:CGPointMake(maxdim/2 - width, radius) radius:radius startAngle:startAng endAngle:endAng clockwise:YES];
|
||||
//[borderPath addLineToPoint:CGPointMake(borderOffset, height - borderOffset)];
|
||||
}
|
||||
else if (_buttonStyle == SonoButtonStyleUpperRightCornerRound) {
|
||||
float maxdim = jmax(width*2, height);
|
||||
// H /2 + W^2 / 8H (but double the width here)
|
||||
float radius = circleRadius > 0.0f ? circleRadius : height * 0.5 + (width*width*4.0)/(8.0f * height);
|
||||
float startAng = atan2(radius - height, maxdim/2 - width*2);
|
||||
float endAng = atan2(radius - height , width - maxdim/2) ;
|
||||
startAng += M_PI_2;
|
||||
endAng += M_PI_2;
|
||||
|
||||
DBG("URC Start ang " << radiansToDegrees(startAng) << " end " << radiansToDegrees(endAng) << " h: " << height << " rad: " << radius);
|
||||
|
||||
fillPath.startNewSubPath(borderOffset, borderOffset);
|
||||
fillPath.addCentredArc(maxdim/2, height-radius, radius, radius, 0.0f, startAng, endAng);
|
||||
fillPath.lineTo(width - borderOffset, borderOffset);
|
||||
fillPath.lineTo(borderOffset, borderOffset);
|
||||
|
||||
borderPath.startNewSubPath(borderOffset, borderOffset);
|
||||
borderPath.addCentredArc(maxdim/2, height-radius, radius, radius, 0.0f, startAng, endAng);
|
||||
borderPath.startNewSubPath(width - borderOffset, borderOffset);
|
||||
borderPath.lineTo(borderOffset, borderOffset);
|
||||
|
||||
//fillPath = [UIBezierPath bezierPath];
|
||||
//[fillPath moveToPoint:CGPointMake(borderOffset, borderOffset)];
|
||||
//[fillPath addArcWithCenter:CGPointMake(maxdim/2, height -radius) radius:radius startAngle:startAng endAngle:endAng clockwise:NO];
|
||||
//[fillPath addLineToPoint:CGPointMake(width - borderOffset, borderOffset)];
|
||||
//[fillPath addLineToPoint:CGPointMake(borderOffset, borderOffset)];
|
||||
|
||||
//borderPath = [UIBezierPath bezierPath];
|
||||
//[borderPath moveToPoint:CGPointMake(borderOffset, borderOffset)];
|
||||
//[borderPath addArcWithCenter:CGPointMake(maxdim/2, height -radius) radius:radius startAngle:startAng endAngle:endAng clockwise:NO];
|
||||
//[borderPath addLineToPoint:CGPointMake(width - borderOffset, borderOffset)];
|
||||
//[borderPath moveToPoint:CGPointMake(width - borderOffset, borderOffset)];
|
||||
//[borderPath addLineToPoint:CGPointMake(borderOffset, borderOffset)];
|
||||
}
|
||||
else if (_buttonStyle == SonoButtonStyleUpperLeftCornerRound) {
|
||||
float maxdim = jmax(width*2, height);
|
||||
// H /2 + W^2 / 8H (but double the width here)
|
||||
float radius = circleRadius > 0.0f ? circleRadius : height * 0.5 + (width*width*4.0)/(8.0f * height);
|
||||
float startAng = atan2(radius - height, width - maxdim/2) ;
|
||||
float endAng = atan2(radius-height, width*2 - maxdim/2);
|
||||
startAng += M_PI_2;
|
||||
endAng += M_PI_2;
|
||||
|
||||
DBG("URC Start ang " << radiansToDegrees(startAng) << " end " << radiansToDegrees(endAng) << " h: " << height << " rad: " << radius);
|
||||
|
||||
|
||||
fillPath.startNewSubPath(borderOffset, borderOffset);
|
||||
fillPath.lineTo(borderOffset, height - borderOffset);
|
||||
fillPath.addCentredArc(maxdim/2 - width, height-radius, radius, radius, 0.0f, startAng, endAng);
|
||||
fillPath.lineTo(borderOffset, borderOffset);
|
||||
|
||||
borderPath.startNewSubPath(borderOffset, height - borderOffset);
|
||||
borderPath.lineTo(borderOffset, height - borderOffset);
|
||||
borderPath.addCentredArc(maxdim/2 - width, height-radius, radius, radius, 0.0f, startAng, endAng);
|
||||
borderPath.lineTo(borderOffset, borderOffset);
|
||||
|
||||
//fillPath = [UIBezierPath bezierPath];
|
||||
//[fillPath moveToPoint:CGPointMake(borderOffset, borderOffset)];
|
||||
//[fillPath addLineToPoint:CGPointMake(borderOffset, height - borderOffset)];
|
||||
////[fillPath moveToPoint:CGPointMake(borderOffset, height - borderOffset)];
|
||||
//[fillPath addArcWithCenter:CGPointMake(maxdim/2 - width, height -radius) radius:radius startAngle:startAng endAngle:endAng clockwise:NO];
|
||||
//[fillPath addLineToPoint:CGPointMake(borderOffset, borderOffset)];
|
||||
|
||||
//borderPath = [UIBezierPath bezierPath];
|
||||
////[borderPath moveToPoint:CGPointMake(borderOffset, borderOffset)];
|
||||
////[borderPath addLineToPoint:CGPointMake(borderOffset, height - borderOffset)];
|
||||
//[borderPath moveToPoint:CGPointMake(borderOffset, height - borderOffset)];
|
||||
//[borderPath addArcWithCenter:CGPointMake(maxdim/2 - width, height -radius) radius:radius startAngle:startAng endAngle:endAng clockwise:NO];
|
||||
//[borderPath addLineToPoint:CGPointMake(borderOffset, borderOffset)];
|
||||
}
|
||||
else
|
||||
{
|
||||
auto bounds = getLocalBounds().toFloat().reduced(borderOffset);
|
||||
fillPath.addRoundedRectangle(bounds, cornerRadius);
|
||||
borderPath.addRoundedRectangle(bounds, cornerRadius);
|
||||
}
|
||||
|
||||
|
||||
}
|
74
Source/SonoTextButton.h
Normal file
74
Source/SonoTextButton.h
Normal file
@ -0,0 +1,74 @@
|
||||
// SPDX-License-Identifier: GPLv3-or-later WITH Appstore-exception
|
||||
// Copyright (C) 2020 Jesse Chappell
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "JuceHeader.h"
|
||||
|
||||
enum SonoButtonStyle {
|
||||
SonoButtonStyleNormal=0,
|
||||
SonoButtonStyleUpperLeftCorner,
|
||||
SonoButtonStyleUpperRightCorner,
|
||||
SonoButtonStyleLowerLeftCorner,
|
||||
SonoButtonStyleLowerRightCorner,
|
||||
SonoButtonStyleUpperLeftCornerRound,
|
||||
SonoButtonStyleUpperRightCornerRound,
|
||||
SonoButtonStyleLowerLeftCornerRound,
|
||||
SonoButtonStyleLowerRightCornerRound,
|
||||
SonoButtonStyleLeft,
|
||||
SonoButtonStyleRight,
|
||||
SonoButtonStyleTop,
|
||||
SonoButtonStyleBottom
|
||||
};
|
||||
|
||||
|
||||
|
||||
class SonoTextButton : public TextButton
|
||||
{
|
||||
public:
|
||||
SonoTextButton(const String & name="");
|
||||
virtual ~SonoTextButton();
|
||||
|
||||
enum SonoColourIds
|
||||
{
|
||||
outlineColourId = 0x1008015,
|
||||
};
|
||||
|
||||
|
||||
void paintButton (Graphics&, bool isMouseOverButton, bool isButtonDown) override;
|
||||
void resized() override;
|
||||
|
||||
void setCornerRadius(float rad) { cornerRadius = rad; }
|
||||
float getCornerRadius() const { return cornerRadius; }
|
||||
|
||||
void setButtonStyle(SonoButtonStyle style);
|
||||
SonoButtonStyle getButtonStyle() const { return _buttonStyle; }
|
||||
|
||||
void setCircleRadius(float rad) { circleRadius = rad; }
|
||||
float getCircleRadius() const { return circleRadius; }
|
||||
|
||||
float getTextHeightRatio() const { return textHeightRatio; }
|
||||
void setTextHeightRatio(float ratio) { textHeightRatio = ratio; }
|
||||
|
||||
void setTextJustification(Justification just) { textJustification = just; }
|
||||
Justification getTextJustification() const { return textJustification; }
|
||||
|
||||
|
||||
protected:
|
||||
|
||||
bool hitTest (int x, int y) override;
|
||||
|
||||
void setupPath();
|
||||
|
||||
void drawButtonBackground(Graphics& g, bool isMouseOverButton, bool isButtonDown);
|
||||
|
||||
SonoButtonStyle _buttonStyle;
|
||||
float cornerRadius = 6.0f;
|
||||
Path borderPath;
|
||||
Path fillPath;
|
||||
|
||||
float circleRadius = 0.0f;
|
||||
float textHeightRatio = 0.8f;
|
||||
Justification textJustification = Justification::centred;
|
||||
};
|
132
images/record_input.svg
Normal file
132
images/record_input.svg
Normal file
@ -0,0 +1,132 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
version="1.0"
|
||||
width="26"
|
||||
height="26"
|
||||
id="svg1437"
|
||||
sodipodi:docname="record_input.svg"
|
||||
inkscape:version="1.1 (c4e8f9e, 2021-05-24)"
|
||||
inkscape:export-filename="/Users/jesse/src/sonoaudio/tonalenergy/images/circleslash_icon.png"
|
||||
inkscape:export-xdpi="102.4"
|
||||
inkscape:export-ydpi="102.4"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<metadata
|
||||
id="metadata4595">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<sodipodi:namedview
|
||||
inkscape:snap-bbox="false"
|
||||
pagecolor="#000000"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1124"
|
||||
inkscape:window-height="686"
|
||||
id="namedview4593"
|
||||
showgrid="false"
|
||||
inkscape:snap-nodes="true"
|
||||
inkscape:snap-global="true"
|
||||
inkscape:zoom="5.5056667"
|
||||
inkscape:cx="-3.7234364"
|
||||
inkscape:cy="-0.36326209"
|
||||
inkscape:window-x="2807"
|
||||
inkscape:window-y="192"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg1437"
|
||||
inkscape:pagecheckerboard="true"
|
||||
inkscape:document-rotation="0"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0">
|
||||
<inkscape:grid
|
||||
id="grid11889"
|
||||
type="xygrid"
|
||||
originx="0"
|
||||
originy="0" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs1440">
|
||||
<marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible"
|
||||
id="EmptyTriangleInS"
|
||||
refX="0"
|
||||
refY="0"
|
||||
orient="auto"
|
||||
inkscape:stockid="EmptyTriangleInS">
|
||||
<path
|
||||
transform="matrix(-0.2,0,0,-0.2,0.6,0)"
|
||||
style="fill:#cccccc;fill-opacity:1;fill-rule:evenodd;stroke:#cccccc;stroke-width:1pt;stroke-opacity:1"
|
||||
d="M 5.77,0 -2.88,5 V -5 Z"
|
||||
id="path1125" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible"
|
||||
id="Arrow1Send"
|
||||
refX="0"
|
||||
refY="0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow1Send">
|
||||
<path
|
||||
transform="matrix(-0.2,0,0,-0.2,-1.2,0)"
|
||||
style="fill:#cccccc;fill-opacity:1;fill-rule:evenodd;stroke:#cccccc;stroke-width:1pt;stroke-opacity:1"
|
||||
d="M 0,0 5,-5 -12.5,0 5,5 Z"
|
||||
id="path983" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible"
|
||||
id="marker2864"
|
||||
refX="0"
|
||||
refY="0"
|
||||
orient="auto"
|
||||
inkscape:stockid="TriangleInM">
|
||||
<path
|
||||
transform="scale(-0.4)"
|
||||
style="fill:#cccccc;fill-opacity:1;fill-rule:evenodd;stroke:#cccccc;stroke-width:1pt;stroke-opacity:1"
|
||||
d="M 5.77,0 -2.88,5 V -5 Z"
|
||||
id="path2862" />
|
||||
</marker>
|
||||
</defs>
|
||||
<rect
|
||||
y="0"
|
||||
x="0"
|
||||
height="26"
|
||||
width="26"
|
||||
id="rect9748"
|
||||
style="opacity:1;fill:#000000;fill-opacity:0;stroke:none;stroke-width:0.185546;stroke-linecap:round;stroke-opacity:1" />
|
||||
<g
|
||||
id="g1193"
|
||||
transform="translate(-2,-2)">
|
||||
<circle
|
||||
style="fill:#cd2828;fill-opacity:0.495968;stroke:#cccccc;stroke-width:1.2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path886"
|
||||
cx="15"
|
||||
cy="15"
|
||||
r="10.826572" />
|
||||
<path
|
||||
style="fill:#e6e6e6;fill-opacity:1;stroke:#d4d4d4;stroke-width:0.304;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 15.459071,19.707728 v 3.337127 c 0,0.256023 -0.207467,0.459082 -0.459071,0.459082 -0.256024,0 -0.459081,-0.207469 -0.459081,-0.459082 V 19.707728 C 13.132798,19.597377 11.865921,18.974975 10.921291,18.030335 9.8751216,16.984175 9.22624,15.540738 9.22624,13.951625 c 0,-0.256022 0.2074688,-0.459072 0.4590716,-0.459072 0.2560236,0 0.4590824,0.207469 0.4590824,0.459072 0,1.333089 0.547361,2.546992 1.42578,3.429828 0.878426,0.878427 2.092326,1.42578 3.429826,1.42578 1.333089,0 2.54699,-0.547353 3.429827,-1.42578 0.878418,-0.878426 1.42578,-2.092329 1.42578,-3.429828 0,-0.256022 0.207468,-0.459072 0.459073,-0.459072 0.256031,0 0.45908,0.207469 0.45908,0.459072 0,1.589113 -0.64889,3.03255 -1.69505,4.07871 -0.944631,0.94464 -2.211508,1.567044 -3.619639,1.677393 z M 15.030896,6.496063 c 0.882838,0 1.686223,0.361962 2.2689,0.94464 0.582669,0.582669 0.944631,1.386054 0.944631,2.268891 v 4.15375 c 0,0.882838 -0.361962,1.686223 -0.944631,2.268891 -0.582677,0.582678 -1.386062,0.94464 -2.2689,0.94464 -0.882836,0 -1.68622,-0.361962 -2.26889,-0.94464 -0.582677,-0.582668 -0.94464,-1.386053 -0.94464,-2.268891 v -4.15375 c 0,-0.882837 0.361963,-1.686222 0.94464,-2.268891 0.58267,-0.582678 1.386054,-0.94464 2.26889,-0.94464 z m 1.615599,1.593522 C 16.23156,7.674649 15.65771,7.418626 15.030896,7.418626 c -0.631223,0 -1.200653,0.256023 -1.615588,0.670959 -0.414937,0.414936 -0.670959,0.988775 -0.670959,1.61559 v 4.153759 c 0,0.631224 0.256022,1.200654 0.670959,1.61559 0.414935,0.414936 0.988775,0.670959 1.615588,0.670959 0.631234,0 1.200664,-0.256023 1.615599,-0.670959 0.414927,-0.414936 0.67095,-0.988776 0.67095,-1.61559 V 9.705175 c 0,-0.631225 -0.256023,-1.200654 -0.67095,-1.61559 z"
|
||||
fill-rule="nonzero"
|
||||
id="path7563" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 5.6 KiB |
132
images/record_input_active.svg
Normal file
132
images/record_input_active.svg
Normal file
@ -0,0 +1,132 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
version="1.0"
|
||||
width="26"
|
||||
height="26"
|
||||
id="svg1437"
|
||||
sodipodi:docname="record_input_active.svg"
|
||||
inkscape:version="1.1 (c4e8f9e, 2021-05-24)"
|
||||
inkscape:export-filename="/Users/jesse/src/sonoaudio/tonalenergy/images/circleslash_icon.png"
|
||||
inkscape:export-xdpi="102.4"
|
||||
inkscape:export-ydpi="102.4"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<metadata
|
||||
id="metadata4595">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<sodipodi:namedview
|
||||
inkscape:snap-bbox="false"
|
||||
pagecolor="#616161"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1145"
|
||||
inkscape:window-height="686"
|
||||
id="namedview4593"
|
||||
showgrid="false"
|
||||
inkscape:snap-nodes="true"
|
||||
inkscape:snap-global="true"
|
||||
inkscape:zoom="8.2114431"
|
||||
inkscape:cx="11.569221"
|
||||
inkscape:cy="16.440472"
|
||||
inkscape:window-x="1576"
|
||||
inkscape:window-y="160"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg1437"
|
||||
inkscape:pagecheckerboard="true"
|
||||
inkscape:document-rotation="0"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0">
|
||||
<inkscape:grid
|
||||
id="grid11889"
|
||||
type="xygrid"
|
||||
originx="0"
|
||||
originy="0" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs1440">
|
||||
<marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible"
|
||||
id="EmptyTriangleInS"
|
||||
refX="0"
|
||||
refY="0"
|
||||
orient="auto"
|
||||
inkscape:stockid="EmptyTriangleInS">
|
||||
<path
|
||||
transform="matrix(-0.2,0,0,-0.2,0.6,0)"
|
||||
style="fill:#cccccc;fill-opacity:1;fill-rule:evenodd;stroke:#cccccc;stroke-width:1pt;stroke-opacity:1"
|
||||
d="M 5.77,0 -2.88,5 V -5 Z"
|
||||
id="path1125" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible"
|
||||
id="Arrow1Send"
|
||||
refX="0"
|
||||
refY="0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow1Send">
|
||||
<path
|
||||
transform="matrix(-0.2,0,0,-0.2,-1.2,0)"
|
||||
style="fill:#cccccc;fill-opacity:1;fill-rule:evenodd;stroke:#cccccc;stroke-width:1pt;stroke-opacity:1"
|
||||
d="M 0,0 5,-5 -12.5,0 5,5 Z"
|
||||
id="path983" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible"
|
||||
id="marker2864"
|
||||
refX="0"
|
||||
refY="0"
|
||||
orient="auto"
|
||||
inkscape:stockid="TriangleInM">
|
||||
<path
|
||||
transform="scale(-0.4)"
|
||||
style="fill:#cccccc;fill-opacity:1;fill-rule:evenodd;stroke:#cccccc;stroke-width:1pt;stroke-opacity:1"
|
||||
d="M 5.77,0 -2.88,5 V -5 Z"
|
||||
id="path2862" />
|
||||
</marker>
|
||||
</defs>
|
||||
<rect
|
||||
y="0"
|
||||
x="0"
|
||||
height="26"
|
||||
width="26"
|
||||
id="rect9748"
|
||||
style="opacity:1;fill:#000000;fill-opacity:0;stroke:none;stroke-width:0.185546;stroke-linecap:round;stroke-opacity:1" />
|
||||
<g
|
||||
id="g1189"
|
||||
transform="translate(-2,-2)">
|
||||
<circle
|
||||
style="fill:#f74848;fill-opacity:1;stroke:#cccccc;stroke-width:1.2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path886"
|
||||
cx="15"
|
||||
cy="15"
|
||||
r="10.826573" />
|
||||
<path
|
||||
style="fill:#e6e6e6;fill-opacity:1;stroke:#d4d4d4;stroke-width:0.304;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 15.459071,19.707728 v 3.337127 c 0,0.256023 -0.207467,0.459082 -0.459071,0.459082 -0.256024,0 -0.459081,-0.207469 -0.459081,-0.459082 v -3.337127 c -1.408121,-0.110351 -2.674998,-0.732753 -3.619628,-1.677393 -1.0461695,-1.04616 -1.6950511,-2.489597 -1.6950511,-4.07871 0,-0.256022 0.2074688,-0.459072 0.4590716,-0.459072 0.2560236,0 0.4590825,0.207469 0.4590825,0.459072 0,1.333089 0.547361,2.546992 1.42578,3.429828 0.878426,0.878427 2.092326,1.42578 3.429826,1.42578 1.333089,0 2.54699,-0.547353 3.429827,-1.42578 0.878418,-0.878426 1.42578,-2.092329 1.42578,-3.429828 0,-0.256022 0.207468,-0.459072 0.459073,-0.459072 0.256031,0 0.45908,0.207469 0.45908,0.459072 0,1.589113 -0.64889,3.03255 -1.69505,4.07871 -0.944631,0.94464 -2.211508,1.567044 -3.619639,1.677393 z M 15.030896,6.4960629 c 0.882838,0 1.686223,0.361962 2.2689,0.9446399 0.582669,0.5826688 0.944631,1.3860537 0.944631,2.2688914 v 4.1537498 c 0,0.882838 -0.361962,1.686223 -0.944631,2.268891 -0.582677,0.582678 -1.386062,0.94464 -2.2689,0.94464 -0.882836,0 -1.68622,-0.361962 -2.26889,-0.94464 -0.582677,-0.582668 -0.94464,-1.386053 -0.94464,-2.268891 V 9.7095942 c 0,-0.8828377 0.361963,-1.6862226 0.94464,-2.2688914 0.58267,-0.5826779 1.386054,-0.9446399 2.26889,-0.9446399 z m 1.615599,1.5935217 C 16.23156,7.6746488 15.65771,7.4186258 15.030896,7.4186258 c -0.631223,0 -1.200653,0.256023 -1.615588,0.6709588 -0.414937,0.414936 -0.670959,0.9887751 -0.670959,1.6155898 v 4.1537596 c 0,0.631224 0.256022,1.200654 0.670959,1.61559 0.414935,0.414936 0.988775,0.670959 1.615588,0.670959 0.631234,0 1.200664,-0.256023 1.615599,-0.670959 0.414927,-0.414936 0.67095,-0.988776 0.67095,-1.61559 V 9.7051744 c 0,-0.6312247 -0.256023,-1.2006538 -0.67095,-1.6155898 z"
|
||||
fill-rule="nonzero"
|
||||
id="path7563" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 5.6 KiB |
137
images/record_output.svg
Normal file
137
images/record_output.svg
Normal file
@ -0,0 +1,137 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
version="1.0"
|
||||
width="25.998236"
|
||||
height="25.998236"
|
||||
id="svg1437"
|
||||
sodipodi:docname="record_output.svg"
|
||||
inkscape:version="1.1 (c4e8f9e, 2021-05-24)"
|
||||
inkscape:export-filename="/Users/jesse/src/sonoaudio/tonalenergy/images/circleslash_icon.png"
|
||||
inkscape:export-xdpi="102.4"
|
||||
inkscape:export-ydpi="102.4"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<metadata
|
||||
id="metadata4595">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<sodipodi:namedview
|
||||
inkscape:snap-bbox="false"
|
||||
pagecolor="#000000"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1124"
|
||||
inkscape:window-height="686"
|
||||
id="namedview4593"
|
||||
showgrid="false"
|
||||
inkscape:snap-nodes="true"
|
||||
inkscape:snap-global="true"
|
||||
inkscape:zoom="8.9964123"
|
||||
inkscape:cx="13.060762"
|
||||
inkscape:cy="17.451401"
|
||||
inkscape:window-x="2640"
|
||||
inkscape:window-y="144"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg1437"
|
||||
inkscape:pagecheckerboard="true"
|
||||
inkscape:document-rotation="0"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0">
|
||||
<inkscape:grid
|
||||
id="grid11889"
|
||||
type="xygrid"
|
||||
originx="0"
|
||||
originy="0" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs1440">
|
||||
<marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible"
|
||||
id="EmptyTriangleInS"
|
||||
refX="0"
|
||||
refY="0"
|
||||
orient="auto"
|
||||
inkscape:stockid="EmptyTriangleInS">
|
||||
<path
|
||||
transform="matrix(-0.2,0,0,-0.2,0.6,0)"
|
||||
style="fill:#cccccc;fill-opacity:1;fill-rule:evenodd;stroke:#cccccc;stroke-width:1pt;stroke-opacity:1"
|
||||
d="M 5.77,0 -2.88,5 V -5 Z"
|
||||
id="path1125" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible"
|
||||
id="Arrow1Send"
|
||||
refX="0"
|
||||
refY="0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow1Send">
|
||||
<path
|
||||
transform="matrix(-0.2,0,0,-0.2,-1.2,0)"
|
||||
style="fill:#cccccc;fill-opacity:1;fill-rule:evenodd;stroke:#cccccc;stroke-width:1pt;stroke-opacity:1"
|
||||
d="M 0,0 5,-5 -12.5,0 5,5 Z"
|
||||
id="path983" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible"
|
||||
id="marker2864"
|
||||
refX="0"
|
||||
refY="0"
|
||||
orient="auto"
|
||||
inkscape:stockid="TriangleInM">
|
||||
<path
|
||||
transform="scale(-0.4)"
|
||||
style="fill:#cccccc;fill-opacity:1;fill-rule:evenodd;stroke:#cccccc;stroke-width:1pt;stroke-opacity:1"
|
||||
d="M 5.77,0 -2.88,5 V -5 Z"
|
||||
id="path2862" />
|
||||
</marker>
|
||||
</defs>
|
||||
<rect
|
||||
y="0"
|
||||
x="0"
|
||||
height="25.998236"
|
||||
width="25.998236"
|
||||
id="rect9748"
|
||||
style="opacity:1;fill:#000000;fill-opacity:0;stroke:none;stroke-width:0.185534;stroke-linecap:round;stroke-opacity:1" />
|
||||
<g
|
||||
id="g1203"
|
||||
transform="translate(-2.0008821,-2.0008821)">
|
||||
<circle
|
||||
style="fill:#cd2828;fill-opacity:0.495968;stroke:#cccccc;stroke-width:1.2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path886"
|
||||
cx="15"
|
||||
cy="15"
|
||||
r="10.826572" />
|
||||
<g
|
||||
id="Layer_x0020_1"
|
||||
transform="matrix(0.00449817,0,0,0.00449817,7.1242974,9.0587762)"
|
||||
style="fill:#e6e6e6;fill-opacity:1;stroke:#d4d4d4;stroke-width:67.583;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1">
|
||||
<path
|
||||
id="path7296"
|
||||
fill-rule="nonzero"
|
||||
d="m 2268,282 c -53,0 -95,-43 -95,-95 0,-53 43,-95 95,-95 356,0 678,144 911,377 233,233 377,555 377,911 0,356 -144,678 -377,911 -233,233 -555,377 -911,377 -53,0 -95,-43 -95,-95 0,-53 43,-95 95,-95 303,0 578,-123 776,-322 199,-199 322,-473 322,-776 0,-303 -123,-578 -322,-776 C 2845,405 2571,282 2268,282 Z M 303,630 h 702 L 1603,28 c 37,-37 97,-37 134,0 19,19 28,43 28,67 v 2474 c 0,53 -43,95 -95,95 -27,0 -51,-11 -68,-29 L 1010,2136 H 302 C 219,2136 143,2102 88,2047 33,1992 -1,1916 -1,1833 V 932 c 0,-83 34,-159 89,-214 55,-55 131,-89 214,-89 z m 742,190 H 303 c -31,0 -59,13 -80,33 -20,20 -33,49 -33,80 v 901 c 0,31 13,59 33,80 20,20 49,33 80,33 h 742 c 22,0 43,7 61,22 l 470,396 V 325 l -459,462 c -17,20 -43,33 -72,33 z m 1176,5 c -43,0 -79,-35 -79,-79 0,-43 35,-79 79,-79 202,0 385,82 517,214 132,132 214,315 214,517 0,202 -82,385 -214,517 -132,132 -315,214 -517,214 -43,0 -79,-35 -79,-79 0,-43 35,-79 79,-79 159,0 302,-64 406,-168 104,-104 168,-247 168,-406 0,-159 -64,-302 -168,-406 C 2523,887 2380,823 2221,823 Z"
|
||||
style="fill:#e6e6e6;fill-opacity:1;stroke:#d4d4d4;stroke-width:67.583;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 5.2 KiB |
137
images/record_output_active.svg
Normal file
137
images/record_output_active.svg
Normal file
@ -0,0 +1,137 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
version="1.0"
|
||||
width="26"
|
||||
height="26"
|
||||
id="svg1437"
|
||||
sodipodi:docname="record_output_active.svg"
|
||||
inkscape:version="1.1 (c4e8f9e, 2021-05-24)"
|
||||
inkscape:export-filename="/Users/jesse/src/sonoaudio/tonalenergy/images/circleslash_icon.png"
|
||||
inkscape:export-xdpi="102.4"
|
||||
inkscape:export-ydpi="102.4"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<metadata
|
||||
id="metadata4595">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<sodipodi:namedview
|
||||
inkscape:snap-bbox="false"
|
||||
pagecolor="#616161"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1145"
|
||||
inkscape:window-height="686"
|
||||
id="namedview4593"
|
||||
showgrid="false"
|
||||
inkscape:snap-nodes="true"
|
||||
inkscape:snap-global="true"
|
||||
inkscape:zoom="11.275529"
|
||||
inkscape:cx="9.6669523"
|
||||
inkscape:cy="18.447028"
|
||||
inkscape:window-x="2002"
|
||||
inkscape:window-y="770"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg1437"
|
||||
inkscape:pagecheckerboard="true"
|
||||
inkscape:document-rotation="0"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0">
|
||||
<inkscape:grid
|
||||
id="grid11889"
|
||||
type="xygrid"
|
||||
originx="0"
|
||||
originy="0" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs1440">
|
||||
<marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible"
|
||||
id="EmptyTriangleInS"
|
||||
refX="0"
|
||||
refY="0"
|
||||
orient="auto"
|
||||
inkscape:stockid="EmptyTriangleInS">
|
||||
<path
|
||||
transform="matrix(-0.2,0,0,-0.2,0.6,0)"
|
||||
style="fill:#cccccc;fill-opacity:1;fill-rule:evenodd;stroke:#cccccc;stroke-width:1pt;stroke-opacity:1"
|
||||
d="M 5.77,0 -2.88,5 V -5 Z"
|
||||
id="path1125" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible"
|
||||
id="Arrow1Send"
|
||||
refX="0"
|
||||
refY="0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow1Send">
|
||||
<path
|
||||
transform="matrix(-0.2,0,0,-0.2,-1.2,0)"
|
||||
style="fill:#cccccc;fill-opacity:1;fill-rule:evenodd;stroke:#cccccc;stroke-width:1pt;stroke-opacity:1"
|
||||
d="M 0,0 5,-5 -12.5,0 5,5 Z"
|
||||
id="path983" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible"
|
||||
id="marker2864"
|
||||
refX="0"
|
||||
refY="0"
|
||||
orient="auto"
|
||||
inkscape:stockid="TriangleInM">
|
||||
<path
|
||||
transform="scale(-0.4)"
|
||||
style="fill:#cccccc;fill-opacity:1;fill-rule:evenodd;stroke:#cccccc;stroke-width:1pt;stroke-opacity:1"
|
||||
d="M 5.77,0 -2.88,5 V -5 Z"
|
||||
id="path2862" />
|
||||
</marker>
|
||||
</defs>
|
||||
<rect
|
||||
y="0"
|
||||
x="0"
|
||||
height="26"
|
||||
width="26"
|
||||
id="rect9748"
|
||||
style="opacity:1;fill:#000000;fill-opacity:0;stroke:none;stroke-width:0.185546;stroke-linecap:round;stroke-opacity:1" />
|
||||
<g
|
||||
id="g1198"
|
||||
transform="translate(-2,-2)">
|
||||
<circle
|
||||
style="fill:#f74848;fill-opacity:1;stroke:#cccccc;stroke-width:1.2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path886"
|
||||
cx="15"
|
||||
cy="15"
|
||||
r="10.826573" />
|
||||
<g
|
||||
id="Layer_x0020_1"
|
||||
transform="matrix(0.00449817,0,0,0.00449817,7.0045028,8.9984055)"
|
||||
style="fill:#e6e6e6;fill-opacity:1;stroke:#d4d4d4;stroke-width:67.583;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1">
|
||||
<path
|
||||
id="path7296"
|
||||
fill-rule="nonzero"
|
||||
d="m 2268,282 c -53,0 -95,-43 -95,-95 0,-53 43,-95 95,-95 356,0 678,144 911,377 233,233 377,555 377,911 0,356 -144,678 -377,911 -233,233 -555,377 -911,377 -53,0 -95,-43 -95,-95 0,-53 43,-95 95,-95 303,0 578,-123 776,-322 199,-199 322,-473 322,-776 0,-303 -123,-578 -322,-776 C 2845,405 2571,282 2268,282 Z M 303,630 h 702 L 1603,28 c 37,-37 97,-37 134,0 19,19 28,43 28,67 v 2474 c 0,53 -43,95 -95,95 -27,0 -51,-11 -68,-29 L 1010,2136 H 302 C 219,2136 143,2102 88,2047 33,1992 -1,1916 -1,1833 V 932 c 0,-83 34,-159 89,-214 55,-55 131,-89 214,-89 z m 742,190 H 303 c -31,0 -59,13 -80,33 -20,20 -33,49 -33,80 v 901 c 0,31 13,59 33,80 20,20 49,33 80,33 h 742 c 22,0 43,7 61,22 l 470,396 V 325 l -459,462 c -17,20 -43,33 -72,33 z m 1176,5 c -43,0 -79,-35 -79,-79 0,-43 35,-79 79,-79 202,0 385,82 517,214 132,132 214,315 214,517 0,202 -82,385 -214,517 -132,132 -315,214 -517,214 -43,0 -79,-35 -79,-79 0,-43 35,-79 79,-79 159,0 302,-64 406,-168 104,-104 168,-247 168,-406 0,-159 -64,-302 -168,-406 C 2523,887 2380,823 2221,823 Z"
|
||||
style="fill:#e6e6e6;fill-opacity:1;stroke:#d4d4d4;stroke-width:67.583;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 5.2 KiB |
Loading…
Reference in New Issue
Block a user