added output recording feature. moved settings to a separate popup panel instead of a menu

This commit is contained in:
essej
2022-06-14 19:11:57 -04:00
parent aff633ff39
commit af5efd8b09
21 changed files with 3309 additions and 263 deletions

View File

@ -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
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);
}
else if (r == SettingsShowTechInfoId)
{
toggleBool(processor.m_show_technical_info);
processor.m_propsfile->m_props_file->setValue("showtechnicalinfo", processor.m_show_technical_info);
}
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;
}
// 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;
}
}
}
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)
{