diff --git a/Source/CrossPlatformUtils.h b/Source/CrossPlatformUtils.h new file mode 100644 index 0000000..c0824a2 --- /dev/null +++ b/Source/CrossPlatformUtils.h @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPLv3-or-later WITH Appstore-exception +// Copyright (C) 2020 Jesse Chappell + + +#pragma once + +void getSafeAreaInsets(void * component, float & top, float & bottom, float & left, float & right); + + +#if JUCE_MAC + +void disableAppNap(); + +#endif diff --git a/Source/CrossPlatformUtilsMac.mm b/Source/CrossPlatformUtilsMac.mm new file mode 100644 index 0000000..9423b71 --- /dev/null +++ b/Source/CrossPlatformUtilsMac.mm @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: GPLv3-or-later WITH Appstore-exception +// Copyright (C) 2020 Jesse Chappell + + +#include "CrossPlatformUtils.h" + +//#include "../JuceLibraryCode/JuceHeader.h" + +#define JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED 1 + +#include + + +#if JUCE_MAC + + +//#import + +#import + + +void getSafeAreaInsets(void * component, float & top, float & bottom, float & left, float & right) +{ + top = bottom = left = right = 0; +} + + +void disableAppNap() { + // Does the App Nap API even exist on this Mac? + if ([[NSProcessInfo processInfo] respondsToSelector:@selector(beginActivityWithOptions:reason:)]) { + // If the API exists, then disable App Nap... + + // From NSProcessInfo.h: + // NSActivityIdleSystemSleepDisabled = (1ULL << 20), + // NSActivityUserInitiated = (0x00FFFFFFULL | NSActivityIdleSystemSleepDisabled), + // NSActivityLatencyCritical = 0xFF00000000ULL + + uint64_t options = (0x00FFFFFFULL | (1ULL << 20)) | 0xFF00000000ULL; + + // NSActivityLatencyCritical | NSActivityUserInitiated + [[NSProcessInfo processInfo] beginActivityWithOptions:options + reason:@"avoiding audio hiccups and reducing latency"]; + } +} + +#endif diff --git a/Source/PS_Source/StretchSource.cpp b/Source/PS_Source/StretchSource.cpp index 3e3187a..0d5e333 100644 --- a/Source/PS_Source/StretchSource.cpp +++ b/Source/PS_Source/StretchSource.cpp @@ -157,7 +157,7 @@ void StretchAudioSource::setAudioBufferAsInputSource(AudioBuffer* buf, in ScopedLock locker(m_cs); m_inputfile->setAudioBuffer(buf, sr, len); m_seekpos = 0.0; - + m_audiobuffer_is_source = true; m_curfile = File(); if (m_playrange.isEmpty()) setPlayRange({ 0.0,1.0 }); @@ -512,6 +512,7 @@ String StretchAudioSource::setAudioFile(File file) { m_curfile = file; m_firstbuffer = true; + m_audiobuffer_is_source = false; return String(); } return "Could not open file"; diff --git a/Source/PS_Source/StretchSource.h b/Source/PS_Source/StretchSource.h index 13bb45d..d8d502d 100644 --- a/Source/PS_Source/StretchSource.h +++ b/Source/PS_Source/StretchSource.h @@ -99,6 +99,7 @@ public: void setLoopingEnabled(bool b); void setMaxLoops(int64_t numloops) { m_maxloops = numloops; } void setAudioBufferAsInputSource(AudioBuffer* buf, int sr, int len); + bool isAudioBufferInputSource() const { return m_audiobuffer_is_source; } void setMainVolume(double decibels); double getMainVolume() const { return m_main_volume; } //void setSpectralModulesEnabled(const std::array& params); @@ -148,6 +149,7 @@ private: bool m_stream_end_reached = false; int64_t m_output_silence_counter = 0; File m_curfile; + bool m_audiobuffer_is_source = false; int64_t m_maxloops = 0; std::unique_ptr m_resampler; std::vector m_resampler_outbuf; diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index c91ab30..e63507d 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -21,6 +21,8 @@ www.gnu.org/licenses #include #include "RenderSettingsComponent.h" +#include "CrossPlatformUtils.h" + static void handleSettingsMenuModalCallback(int choice, PaulstretchpluginAudioProcessorEditor* ed) { ed->executeModalMenuAction(0,choice); @@ -242,7 +244,15 @@ PaulstretchpluginAudioProcessorEditor::PaulstretchpluginAudioProcessorEditor(Pau startTimer(2, 1000); startTimer(3, 200); m_wavecomponent.startTimer(100); - + + + setResizeLimits(320, 14*25 + 120, 40000, 4000); + + setResizable(true, !JUCEApplicationBase::isStandaloneApp()); + +#if JUCE_MAC + disableAppNap(); +#endif } PaulstretchpluginAudioProcessorEditor::~PaulstretchpluginAudioProcessorEditor() @@ -277,6 +287,10 @@ void PaulstretchpluginAudioProcessorEditor::executeModalMenuAction(int menuid, i { toggleBool(processor.m_mute_while_capturing); } + if (r == 10) + { + toggleBool(processor.m_mute_processed_while_capturing); + } if (r == 4) { processor.resetParameters(); @@ -537,7 +551,8 @@ void PaulstretchpluginAudioProcessorEditor::showSettingsMenu() m_settings_menu.addItem(5, "Load file with plugin state", true, processor.m_load_file_with_state); m_settings_menu.addItem(1, "Play when host transport running", true, processor.m_play_when_host_plays); m_settings_menu.addItem(2, "Capture when host transport running", true, processor.m_capture_when_host_plays); - m_settings_menu.addItem(8, "Mute audio while capturing", true, processor.m_mute_while_capturing); + m_settings_menu.addItem(8, "Mute passthrough while capturing", true, processor.m_mute_while_capturing); + m_settings_menu.addItem(10, "Mute processed audio output while capturing", true, processor.m_mute_processed_while_capturing); m_settings_menu.addItem(9, "Save captured audio to disk", true, processor.m_save_captured_audio); int capturelen = *processor.getFloatParameter(cpi_max_capture_len); PopupMenu capturelenmenu; @@ -1362,14 +1377,18 @@ void PerfMeterComponent::mouseDown(const MouseEvent & ev) bufferingmenu.addItem(103, "Large", true, curbufamount == 3); bufferingmenu.addItem(104, "Very large", true, curbufamount == 4); bufferingmenu.addItem(105, "Huge", true, curbufamount == 5); - int r = bufferingmenu.show(); - if (r >= 100 && r < 200) - { - if (r == 100) - m_proc->m_use_backgroundbuffering = false; - if (r > 100) - m_proc->setPreBufferAmount(r - 100); - } + + auto opts = PopupMenu::Options(); + + bufferingmenu.showMenuAsync(opts, [this](int r) { + if (r >= 100 && r < 200) + { + if (r == 100) + m_proc->m_use_backgroundbuffering = false; + if (r > 100) + m_proc->setPreBufferAmount(r - 100); + } + }); } void PerfMeterComponent::timerCallback() diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 178ebb5..902c3da 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -58,7 +58,8 @@ inline AudioParameterFloat* make_floatpar(String id, String name, float minv, fl //============================================================================== PaulstretchpluginAudioProcessor::PaulstretchpluginAudioProcessor(bool is_stand_alone_offline) - : m_is_stand_alone_offline(is_stand_alone_offline), m_bufferingthread("pspluginprebufferthread") + : AudioProcessor(PaulstretchpluginAudioProcessor::BusesProperties().withInput("Main In", AudioChannelSet::stereo(), true).withOutput ("Main Out", AudioChannelSet::stereo(), true)), +m_is_stand_alone_offline(is_stand_alone_offline), m_bufferingthread("pspluginprebufferthread") { m_filechoose_callback = [this](const FileChooser& chooser) { @@ -214,6 +215,8 @@ PaulstretchpluginAudioProcessor::PaulstretchpluginAudioProcessor(bool is_stand_a PaulstretchpluginAudioProcessor::~PaulstretchpluginAudioProcessor() { + stopTimer(1); + //Logger::writeToLog("PaulX AudioProcessor destroyed"); if (m_thumb) m_thumb->removeAllChangeListeners(); @@ -281,7 +284,7 @@ ValueTree PaulstretchpluginAudioProcessor::getStateTree(bool ignoreoptions, bool paramtree.setProperty("loadfilewithstate", m_load_file_with_state, nullptr); storeToTreeProperties(paramtree, nullptr, "playwhenhostrunning", m_play_when_host_plays, "capturewhenhostrunning", m_capture_when_host_plays,"savecapturedaudio",m_save_captured_audio, - "mutewhilecapturing",m_mute_while_capturing); + "mutewhilecapturing",m_mute_while_capturing, "muteprocwhilecapturing",m_mute_processed_while_capturing); } storeToTreeProperties(paramtree, nullptr, "tabaindex", m_cur_tab_index); storeToTreeProperties(paramtree, nullptr, "waveviewrange", m_wave_view_range); @@ -301,7 +304,7 @@ void PaulstretchpluginAudioProcessor::setStateFromTree(ValueTree tree) m_load_file_with_state = tree.getProperty("loadfilewithstate", true); getFromTreeProperties(tree, "playwhenhostrunning", m_play_when_host_plays, "capturewhenhostrunning", m_capture_when_host_plays,"mutewhilecapturing",m_mute_while_capturing, - "savecapturedaudio",m_save_captured_audio); + "savecapturedaudio",m_save_captured_audio, "muteprocwhilecapturing",m_mute_processed_while_capturing); getFromTreeProperties(tree, "tabaindex", m_cur_tab_index); if (tree.hasProperty("numspectralstagesb")) { @@ -526,6 +529,8 @@ void PaulstretchpluginAudioProcessor::saveCaptureBuffer() inchans, 32, {}, 0)); if (writer != nullptr) { + outstream.release(); // the writer takes ownership + auto sourcebuffer = getStretchSource()->getSourceAudioBuffer(); jassert(sourcebuffer->getNumChannels() == inchans); jassert(sourcebuffer->getNumSamples() > 0); @@ -596,28 +601,34 @@ String PaulstretchpluginAudioProcessor::offlineRender(OfflineRenderParams render { //delete outstream; jassert(false); - } - AudioBuffer renderbuffer{ numoutchans, blocksize }; - MidiBuffer dummymidi; - double outlensecs = sc->getOutputDurationSecondsForRange(sc->getPlayRange(),sc->getFFTSize()); - int64_t outlenframes = outlensecs * outsr; - int64_t outcounter{ 0 }; - m_offline_render_state = 0; - m_offline_render_cancel_requested = false; - - while (outcounter < outlenframes) - { - if (m_offline_render_cancel_requested == true) - break; - processor->processBlock(renderbuffer, dummymidi); - int64 framesToWrite = std::min(blocksize, outlenframes - outcounter); - writer->writeFromAudioSampleBuffer(renderbuffer, 0, framesToWrite); - outcounter += blocksize; - m_offline_render_state = 100.0 / outlenframes * outcounter; - } - m_offline_render_state = 200; - Logger::writeToLog("Rendered ok!"); - + + m_offline_render_state = 200; + Logger::writeToLog("Render failed, could not open file!"); + return; + } else { + outstream.release(); // the writer takes ownership + + AudioBuffer renderbuffer{ numoutchans, blocksize }; + MidiBuffer dummymidi; + double outlensecs = sc->getOutputDurationSecondsForRange(sc->getPlayRange(),sc->getFFTSize()); + int64_t outlenframes = outlensecs * outsr; + int64_t outcounter{ 0 }; + m_offline_render_state = 0; + m_offline_render_cancel_requested = false; + + while (outcounter < outlenframes) + { + if (m_offline_render_cancel_requested == true) + break; + processor->processBlock(renderbuffer, dummymidi); + int64 framesToWrite = std::min(blocksize, outlenframes - outcounter); + writer->writeFromAudioSampleBuffer(renderbuffer, 0, framesToWrite); + outcounter += blocksize; + m_offline_render_state = 100.0 / outlenframes * outcounter; + } + m_offline_render_state = 200; + Logger::writeToLog("Rendered ok!"); + } }; std::thread th(rendertask); th.detach(); @@ -768,7 +779,9 @@ void PaulstretchpluginAudioProcessor::processBlock (AudioSampleBuffer& buffer, M m_recorded_range = { 0, m_rec_count }; if (m_mute_while_capturing == true) buffer.clear(); - return; + + if (m_mute_processed_while_capturing == true) + return; } jassert(m_buffering_source != nullptr); jassert(m_bufferingthread.isThreadRunning()); diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index a9b1a8d..198b964 100644 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -207,6 +207,7 @@ public: bool m_play_when_host_plays = false; bool m_capture_when_host_plays = false; bool m_mute_while_capturing = false; + bool m_mute_processed_while_capturing = false; bool m_use_backgroundbuffering = true; void resetParameters(); void setPreBufferAmount(int x); diff --git a/Source/RenderSettingsComponent.cpp b/Source/RenderSettingsComponent.cpp index 10b2a79..5cff5ee 100644 --- a/Source/RenderSettingsComponent.cpp +++ b/Source/RenderSettingsComponent.cpp @@ -186,10 +186,9 @@ void RenderSettingsComponent::buttonClicked (Button* buttonThatWasClicked) FileChooser myChooser("Please select audio file to render...", lastexportfolder, "*.wav"); - if (myChooser.browseForFileToSave(true)) - { - outfileNameEditor.setText(myChooser.getResult().getFullPathName(), dontSendNotification); - } + myChooser.launchAsync(FileBrowserComponent::saveMode, [this](const FileChooser &chooser) { + outfileNameEditor.setText(chooser.getResult().getFullPathName(), dontSendNotification); + }); } } diff --git a/Source/envelope_component.cpp b/Source/envelope_component.cpp index cd443bd..4d1401d 100644 --- a/Source/envelope_component.cpp +++ b/Source/envelope_component.cpp @@ -225,34 +225,40 @@ void EnvelopeComponent::mouseDown(const MouseEvent & ev) if (ev.mods.isRightButtonDown() == true) { PopupMenu menu; + PopupMenu::Options opts; menu.addItem(1, "Reset"); menu.addItem(2, "Invert"); menu.addItem(3, "Wrap envelope X transform", true, m_envelope->m_transform_wrap_x); menu.addItem(4, "Envelope Y random linear interpolation", true, m_envelope->m_transform_y_random_linear_interpolation); - int r = menu.show(); - if (r == 1) - { - ScopedLock locker(*m_cs); - m_envelope->ResetEnvelope(); - } - if (r == 2) - { - for (int i = 0; i < m_envelope->GetNumPoints(); ++i) - { - double val = 1.0 - m_envelope->GetNodeAtIndex(i).pt_y; - m_envelope->GetNodeAtIndex(i).pt_y = val; - } - } - if (r == 3) - { - toggleBool(m_envelope->m_transform_wrap_x); - } - if (r == 4) - { - toggleBool(m_envelope->m_transform_y_random_linear_interpolation); - } - repaint(); - return; + + auto callback = [this] (int r) { + if (r == 1) + { + ScopedLock locker(*m_cs); + m_envelope->ResetEnvelope(); + } + if (r == 2) + { + for (int i = 0; i < m_envelope->GetNumPoints(); ++i) + { + double val = 1.0 - m_envelope->GetNodeAtIndex(i).pt_y; + m_envelope->GetNodeAtIndex(i).pt_y = val; + } + } + if (r == 3) + { + toggleBool(m_envelope->m_transform_wrap_x); + } + if (r == 4) + { + toggleBool(m_envelope->m_transform_y_random_linear_interpolation); + } + repaint(); + }; + + menu.showMenuAsync(opts, callback); + + return; } m_node_to_drag = find_hot_envelope_point(ev.x, ev.y); m_mouse_down = true; diff --git a/paulstretchplugin.jucer b/paulstretchplugin.jucer index b9f595d..673fc89 100644 --- a/paulstretchplugin.jucer +++ b/paulstretchplugin.jucer @@ -1,20 +1,23 @@ + + - + - + + macOSDeploymentTarget="10.10"/>