diff --git a/Source/CrossPlatformUtils.h b/Source/CrossPlatformUtils.h index aaf56a5..3e68afe 100644 --- a/Source/CrossPlatformUtils.h +++ b/Source/CrossPlatformUtils.h @@ -13,4 +13,12 @@ void getSafeAreaInsets(void * component, float & top, float & bottom, float & le void disableAppNap(); +#endif + +#if JUCE_IOS +bool urlBookmarkToBinaryData(void * bookmark, const void * & retdata, size_t & retsize); + +void *binaryDataToUrlBookmark(const void * data, size_t size); + + #endif diff --git a/Source/CrossPlatformUtilsIOS.mm b/Source/CrossPlatformUtilsIOS.mm index e4b3367..d33654a 100644 --- a/Source/CrossPlatformUtilsIOS.mm +++ b/Source/CrossPlatformUtilsIOS.mm @@ -59,4 +59,23 @@ void getSafeAreaInsets(void * component, float & top, float & bottom, float & le } } +bool urlBookmarkToBinaryData(void * bookmark, const void * & retdata, size_t & retsize) +{ + NSData * data = (NSData*) bookmark; + if (data && [data isKindOfClass:NSData.class]) { + retdata = [data bytes]; + retsize = [data length]; + return true; + } + return false; +} + +void * binaryDataToUrlBookmark(const void * data, size_t size) +{ + NSData * nsdata = [[NSData alloc] initWithBytes:data length:size]; + + return nsdata; +} + + #endif diff --git a/Source/PS_Source/Input/AInputS.h b/Source/PS_Source/Input/AInputS.h index abd8b69..4d3067a 100644 --- a/Source/PS_Source/Input/AInputS.h +++ b/Source/PS_Source/Input/AInputS.h @@ -71,10 +71,10 @@ public: return &m_readbuf; return nullptr; } - bool openAudioFile(File file) override + bool openAudioFile(const URL & url) override { m_silenceoutputted = 0; - AudioFormatReader* reader = m_manager->createReaderFor(file); + AudioFormatReader* reader = m_manager->createReaderFor(url.getLocalFile()); if (reader) { ScopedLock locker(m_mutex); diff --git a/Source/PS_Source/Input/InputS.h b/Source/PS_Source/Input/InputS.h index 95dd01e..9e486a3 100644 --- a/Source/PS_Source/Input/InputS.h +++ b/Source/PS_Source/Input/InputS.h @@ -35,7 +35,7 @@ public: { }; - [[nodiscard]] virtual bool openAudioFile(File file)=0; + [[nodiscard]] virtual bool openAudioFile(const URL & url)=0; virtual void close()=0; virtual int readNextBlock(AudioBuffer& abuf, int smps, int numchans)=0; diff --git a/Source/PS_Source/StretchSource.cpp b/Source/PS_Source/StretchSource.cpp index 0d5e333..6a365e3 100644 --- a/Source/PS_Source/StretchSource.cpp +++ b/Source/PS_Source/StretchSource.cpp @@ -158,7 +158,7 @@ void StretchAudioSource::setAudioBufferAsInputSource(AudioBuffer* buf, in m_inputfile->setAudioBuffer(buf, sr, len); m_seekpos = 0.0; m_audiobuffer_is_source = true; - m_curfile = File(); + m_curfile = URL(); if (m_playrange.isEmpty()) setPlayRange({ 0.0,1.0 }); ++m_param_change_count; @@ -505,12 +505,12 @@ bool StretchAudioSource::isLooping() const return false; } -String StretchAudioSource::setAudioFile(File file) +String StretchAudioSource::setAudioFile(const URL & url) { ScopedLock locker(m_cs); - if (m_inputfile->openAudioFile(file)) + if (m_inputfile->openAudioFile(url)) { - m_curfile = file; + m_curfile = url; m_firstbuffer = true; m_audiobuffer_is_source = false; return String(); @@ -518,7 +518,7 @@ String StretchAudioSource::setAudioFile(File file) return "Could not open file"; } -File StretchAudioSource::getAudioFile() +URL StretchAudioSource::getAudioFile() { return m_curfile; } diff --git a/Source/PS_Source/StretchSource.h b/Source/PS_Source/StretchSource.h index d8d502d..943d108 100644 --- a/Source/PS_Source/StretchSource.h +++ b/Source/PS_Source/StretchSource.h @@ -46,8 +46,8 @@ public: bool isLooping() const override; - String setAudioFile(File file); - File getAudioFile(); + String setAudioFile(const URL & file); + URL getAudioFile(); AudioBuffer* getSourceAudioBuffer(); @@ -148,7 +148,7 @@ private: bool m_stream_end_reached = false; int64_t m_output_silence_counter = 0; - File m_curfile; + URL m_curfile; bool m_audiobuffer_is_source = false; int64_t m_maxloops = 0; std::unique_ptr m_resampler; diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index 20ce4fd..89895ed 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -160,11 +160,20 @@ PaulstretchpluginAudioProcessorEditor::PaulstretchpluginAudioProcessorEditor(Pau } } + m_parcomps[cpi_num_inchans]->getSlider()->setSliderStyle(Slider::SliderStyle::IncDecButtons); m_parcomps[cpi_num_inchans]->getSlider()->setTextBoxStyle(Slider::TextEntryBoxPosition::TextBoxLeft, false, 30, 34); m_parcomps[cpi_num_outchans]->getSlider()->setSliderStyle(Slider::SliderStyle::IncDecButtons); m_parcomps[cpi_num_outchans]->getSlider()->setTextBoxStyle(Slider::TextEntryBoxPosition::TextBoxLeft, false, 30, 34); + +#if JUCE_IOS + // just don't include chan counts on ios for now + removeChildComponent(m_parcomps[cpi_num_inchans].get()); + removeChildComponent(m_parcomps[cpi_num_outchans].get()); +#endif + + m_groupviewport = std::make_unique(); m_groupcontainer = std::make_unique(); m_groupviewport->setViewedComponent(m_groupcontainer.get(), false); @@ -581,6 +590,7 @@ void PaulstretchpluginAudioProcessorEditor::resized() togglesbox.items.add(FlexItem(toggleminw, togglerowheight, *m_parcomps[cpi_capture_trigger]).withMargin(margin).withFlex(1).withMaxWidth(200)); togglesbox.items.add(FlexItem(toggleminw, togglerowheight, *m_parcomps[cpi_passthrough]).withMargin(margin).withFlex(1.5).withMaxWidth(200)); + togglesbox.items.add(FlexItem(toggleminw, togglerowheight, *m_parcomps[cpi_pause_enabled]).withMargin(margin).withFlex(1).withMaxWidth(200)); togglesbox.items.add(FlexItem(toggleminw, togglerowheight, *m_parcomps[cpi_freeze]).withMargin(margin).withFlex(1).withMaxWidth(200)); togglesbox.items.add(FlexItem(toggleminw, togglerowheight, *m_parcomps[cpi_bypass_stretch]).withMargin(margin).withFlex(1).withMaxWidth(200)); @@ -596,6 +606,8 @@ void PaulstretchpluginAudioProcessorEditor::resized() volbox.alignContent = FlexBox::AlignContent::flexStart; volbox.items.add(FlexItem(minitemw*0.75f, rowheight, *m_parcomps[cpi_main_volume]).withMargin(margin).withFlex(1)); + +#if !JUCE_IOS FlexBox inoutbox; int inoutminw = 170; int inoutmaxw = 200; @@ -605,6 +617,7 @@ void PaulstretchpluginAudioProcessorEditor::resized() inoutbox.items.add(FlexItem(inoutminw, rowheight, *m_parcomps[cpi_num_outchans]).withMargin(margin).withFlex(0.5).withMaxWidth(inoutmaxw)); volbox.items.add(FlexItem(2*inoutminw, rowheight, inoutbox).withMargin(margin).withFlex(1.5).withMaxWidth(2*inoutmaxw + 10)); +#endif volbox.performLayout(Rectangle(0,0,w - 2*margin,400)); // test run to calculate actual used height int volh = volbox.items.getLast().currentBounds.getBottom() + volbox.items.getLast().margin.bottom; @@ -888,8 +901,9 @@ void PaulstretchpluginAudioProcessorEditor::filesDropped(const StringArray & fil { if (files.size() > 0) { - File f(files[0]); - processor.setAudioFile(f); + File file(files[0]); + URL url = URL(file); + processor.setAudioFile(url); toFront(true); } } @@ -900,14 +914,9 @@ void PaulstretchpluginAudioProcessorEditor::urlOpened(const URL& url) std::unique_ptr wi (url.createInputStream (false)); if (wi != nullptr) { - File file = url.getLocalFile(); - DBG("Attempting to load after input stream create: " << file.getFullPathName()); - processor.setAudioFile(file); - } else { - File file = url.getLocalFile(); - DBG("Attempting to load after no input stream create: " << file.getFullPathName()); - processor.setAudioFile(file); - } + DBG("Attempting to load after input stream create: " << url.toString(false)); + processor.setAudioFile(url); + } toFront(true); } @@ -1048,7 +1057,7 @@ void PaulstretchpluginAudioProcessorEditor::toggleFileBrowser() DBG("Attempting to load from: " << file.getFullPathName()); //curropendir = file.getParentDirectory(); - processor.setAudioFile(file); + processor.setAudioFile(url); processor.m_propsfile->m_props_file->setValue("importfilefolder", file.getParentDirectory().getFullPathName()); } } @@ -1223,7 +1232,7 @@ void WaveformComponent::paint(Graphics & g) g.fillRect(normalizedToViewX(m_rec_pos), m_topmargin, 1, getHeight() - m_topmargin); } g.setColour(Colours::aqua); - g.drawText(GetFileCallback().getFileName(), 2, m_topmargin + 2, getWidth(), 20, Justification::topLeft); + g.drawText(URL::removeEscapeChars(GetFileCallback().getFileName()), 2, m_topmargin + 2, getWidth(), 20, Justification::topLeft); g.drawText(secondsToString2(thumblen), getWidth() - 200, m_topmargin + 2, 200, 20, Justification::topRight); } @@ -2366,7 +2375,7 @@ void MyFileBrowserComponent::fileClicked(const File & file, const MouseEvent & e void MyFileBrowserComponent::fileDoubleClicked(const File & file) { - m_proc.setAudioFile(file); + m_proc.setAudioFile(URL(file)); m_proc.m_propsfile->m_props_file->setValue("importfilefolder", file.getParentDirectory().getFullPathName()); } diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h index 9fd176b..6fde057 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -189,7 +189,7 @@ public: std::function CursorPosCallback; std::function SeekCallback; std::function, int)> TimeSelectionChangedCallback; - std::function GetFileCallback; + std::function GetFileCallback; std::function)> ViewRangeChangedCallback; void mouseDown(const MouseEvent& e) override; void mouseUp(const MouseEvent& e) override; diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index a5b5776..6de8e64 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -20,6 +20,8 @@ www.gnu.org/licenses #include #include +#import "CrossPlatformUtils.h" + #ifdef WIN32 #undef min #undef max @@ -66,20 +68,21 @@ m_bufferingthread("pspluginprebufferthread"), m_is_stand_alone_offline(is_stand_ { m_filechoose_callback = [this](const FileChooser& chooser) { - File resu = chooser.getResult(); - String pathname = resu.getFullPathName(); - if (pathname.startsWith("/localhost")) - { - pathname = pathname.substring(10); - resu = File(pathname); - } - m_propsfile->m_props_file->setValue("importfilefolder", resu.getParentDirectory().getFullPathName()); - String loaderr = setAudioFile(resu); - if (auto ed = dynamic_cast(getActiveEditor()); ed != nullptr) - { - ed->m_last_err = loaderr; - } - + URL resu = chooser.getURLResult(); + //String pathname = resu.getFullPathName(); + //if (pathname.startsWith("/localhost")) + //{ + // pathname = pathname.substring(10); + // resu = File(pathname); + //} + if (!resu.isEmpty()) { + m_propsfile->m_props_file->setValue("importfilefolder", resu.getLocalFile().getParentDirectory().getFullPathName()); + String loaderr = setAudioFile(resu); + if (auto ed = dynamic_cast(getActiveEditor()); ed != nullptr) + { + ed->m_last_err = loaderr; + } + } }; m_playposinfo.timeInSeconds = 0.0; @@ -268,9 +271,22 @@ ValueTree PaulstretchpluginAudioProcessor::getStateTree(bool ignoreoptions, bool { ValueTree paramtree("paulstretch3pluginstate"); storeToTreeProperties(paramtree, nullptr, getParameters(), { getBoolParameter(cpi_capture_trigger) }); - if (m_current_file != File() && ignorefile == false) + if (m_current_file != URL() && ignorefile == false) { - paramtree.setProperty("importedfile", m_current_file.getFullPathName(), nullptr); + paramtree.setProperty("importedfile", m_current_file.toString(false), nullptr); +#if JUCE_IOS + // store bookmark data if necessary + if (void * bookmark = getURLBookmark(m_current_file)) { + const void * data = nullptr; + size_t datasize = 0; + if (urlBookmarkToBinaryData(bookmark, data, datasize)) { + DBG("Audio file has bookmark, storing it in state, size: " << datasize); + paramtree.setProperty("importedfile_bookmark", var(data, datasize), nullptr); + } else { + DBG("Bookmark is not valid!"); + } + } +#endif } auto specorder = m_stretch_source->getSpectrumProcessOrder(); paramtree.setProperty("numspectralstagesb", (int)specorder.size(), nullptr); @@ -342,10 +358,31 @@ void PaulstretchpluginAudioProcessor::setStateFromTree(ValueTree tree) setPreBufferAmount(prebufamt); if (m_load_file_with_state == true) { - String fn = tree.getProperty("importedfile"); - if (fn.isEmpty() == false) + String fn = tree.getProperty("importedfile"); + if (fn.isNotEmpty()) { - setAudioFile(File(fn)); + URL url(fn); + + if (!url.isLocalFile()) { + // reconstruct just in case imported file string was not a URL + url = URL(File(fn)); + } + +#if JUCE_IOS + // check for bookmark + auto bptr = tree.getPropertyPointer("importedfile_bookmark"); + if (bptr) { + if (auto * block = bptr->getBinaryData()) { + DBG("Has file bookmark"); + void * bookmark = binaryDataToUrlBookmark(block->getData(), block->getSize()); + setURLBookmark(url, bookmark); + } + } + else { + DBG("no url bookmark found"); + } +#endif + setAudioFile(url); } } m_state_dirty = true; @@ -546,7 +583,7 @@ void PaulstretchpluginAudioProcessor::saveCaptureBuffer() jassert(sourcebuffer->getNumSamples() > 0); writer->writeFromAudioSampleBuffer(*sourcebuffer, 0, sourcebuffer->getNumSamples()); - m_current_file = outfile; + m_current_file = URL(outfile); } else { @@ -982,7 +1019,7 @@ void PaulstretchpluginAudioProcessor::setRecordingEnabled(bool b) if (b == true) { m_using_memory_buffer = true; - m_current_file = File(); + m_current_file = URL(); int numchans = *m_inchansparam; m_recbuffer.setSize(numchans, m_max_reclen*getSampleRateChecked()+4096,false,false,true); m_recbuffer.clear(); @@ -1008,33 +1045,44 @@ double PaulstretchpluginAudioProcessor::getRecordingPositionPercent() return 1.0 / m_recbuffer.getNumSamples()*m_rec_pos; } -String PaulstretchpluginAudioProcessor::setAudioFile(File f) +String PaulstretchpluginAudioProcessor::setAudioFile(const URL & url) { - auto ai = unique_from_raw(m_afm->createReaderFor(f)); + // this handles any permissions stuff (needed on ios) + std::unique_ptr wi (url.createInputStream (false)); + File file = url.getLocalFile(); + + auto ai = unique_from_raw(m_afm->createReaderFor(file)); if (ai != nullptr) { if (ai->numChannels > 8) { - return "Too many channels in file "+f.getFullPathName(); + return "Too many channels in file "+ file.getFullPathName(); } if (ai->bitsPerSample>32) { - return "Too high bit depth in file " + f.getFullPathName(); + return "Too high bit depth in file " + file.getFullPathName(); } if (m_thumb) - m_thumb->setSource(new FileInputSource(f)); + m_thumb->setSource(new FileInputSource(file)); ScopedLock locker(m_cs); - m_stretch_source->setAudioFile(f); + m_stretch_source->setAudioFile(url); //Range currange{ *getFloatParameter(cpi_soundstart),*getFloatParameter(cpi_soundend) }; //if (currange.contains(m_stretch_source->getInfilePositionPercent())==false) m_stretch_source->seekPercent(*getFloatParameter(cpi_soundstart)); - m_current_file = f; - m_current_file_date = m_current_file.getLastModificationTime(); + m_current_file = url; + +#if JUCE_IOS + if (void * bookmark = getURLBookmark(m_current_file)) { + DBG("Loaded audio file has bookmark"); + } +#endif + + m_current_file_date = file.getLastModificationTime(); m_using_memory_buffer = false; setDirty(); return String(); } - return "Could not open file " + f.getFullPathName(); + return "Could not open file " + file.getFullPathName(); } Range PaulstretchpluginAudioProcessor::getTimeSelection() @@ -1106,7 +1154,7 @@ pointer_sized_int PaulstretchpluginAudioProcessor::handleVstPluginCanDo(int32 in { String fn(CharPointer_UTF8((char*)value)); //std::cout << "host requested to set audio file " << fn << "\n"; - auto err = setAudioFile(File(fn)); + auto err = setAudioFile(URL(fn)); if (err.isEmpty()==false) std::cout << err << "\n"; } @@ -1124,7 +1172,7 @@ pointer_sized_int PaulstretchpluginAudioProcessor::handleVstManufacturerSpecific void PaulstretchpluginAudioProcessor::finishRecording(int lenrecording) { m_is_recording = false; - m_current_file = File(); + m_current_file = URL(); m_stretch_source->setAudioBufferAsInputSource(&m_recbuffer, getSampleRateChecked(), lenrecording); *getFloatParameter(cpi_soundstart) = 0.0f; *getFloatParameter(cpi_soundend) = jlimit(0.01, 1.0, 1.0 / lenrecording * m_rec_count); diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index 9d38e18..014a7d5 100644 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -200,8 +200,8 @@ public: void setRecordingEnabled(bool b); bool isRecordingEnabled() { return m_is_recording; } double getRecordingPositionPercent(); - String setAudioFile(File f); - File getAudioFile() { return m_current_file; } + String setAudioFile(const URL& url); + URL getAudioFile() { return m_current_file; } Range getTimeSelection(); SharedResourcePointer m_afm; SharedResourcePointer m_propsfile; @@ -261,7 +261,7 @@ private: bool m_using_memory_buffer = true; int m_cur_num_out_chans = 2; CriticalSection m_cs; - File m_current_file; + URL m_current_file; Time m_current_file_date; bool m_is_recording = false; TimeSliceThread m_bufferingthread; diff --git a/images/paulxstretch_icon_1024.png b/images/paulxstretch_icon_1024.png new file mode 100644 index 0000000..eb7d358 Binary files /dev/null and b/images/paulxstretch_icon_1024.png differ diff --git a/images/paulxstretch_icon_1024_rounded.png b/images/paulxstretch_icon_1024_rounded.png new file mode 100644 index 0000000..8b7a9e4 Binary files /dev/null and b/images/paulxstretch_icon_1024_rounded.png differ diff --git a/images/paulxstretch_icon_256.png b/images/paulxstretch_icon_256.png new file mode 100644 index 0000000..41d5b99 Binary files /dev/null and b/images/paulxstretch_icon_256.png differ diff --git a/images/paulxstretch_icon_256_rounded.png b/images/paulxstretch_icon_256_rounded.png new file mode 100644 index 0000000..70ba192 Binary files /dev/null and b/images/paulxstretch_icon_256_rounded.png differ diff --git a/Source/power.svg b/images/power.svg similarity index 100% rename from Source/power.svg rename to images/power.svg diff --git a/Source/power_sel.svg b/images/power_sel.svg similarity index 100% rename from Source/power_sel.svg rename to images/power_sel.svg diff --git a/paulstretchplugin.jucer b/paulstretchplugin.jucer index 01d53f7..2aeb2a3 100644 --- a/paulstretchplugin.jucer +++ b/paulstretchplugin.jucer @@ -13,6 +13,14 @@ headerPath=" " pluginFormats="buildAU,buildStandalone,buildVST3" jucerFormatVersion="1" useAppConfig="0" defines="JUCE_USE_CUSTOM_PLUGIN_STANDALONE_APP=1"> + + + + + + @@ -52,8 +60,6 @@ file="Source/PluginProcessor.cpp"/> - - + xcodeValidArchs="arm64,x86_64" buildNumber="100" applicationCategory="public.app-category.music" + smallIcon="Yny1oQ" bigIcon="hG1CYd"> + extraDefs="" smallIcon="Yny1oQ" bigIcon="hG1CYd"> - + @@ -156,7 +164,7 @@ + extraDefs="JUCE_MODAL_LOOPS_PERMITTED=1" smallIcon="Yny1oQ" bigIcon="hG1CYd"> diff --git a/paulstretchplugin_ios.jucer b/paulstretchplugin_ios.jucer index 6672c9a..0d10e77 100644 --- a/paulstretchplugin_ios.jucer +++ b/paulstretchplugin_ios.jucer @@ -14,6 +14,14 @@ headerPath=" " pluginFormats="buildAUv3,buildStandalone,enableIAA" jucerFormatVersion="1" useAppConfig="0" defines="JUCE_USE_CUSTOM_PLUGIN_STANDALONE_APP=1"> + + + + + + @@ -55,8 +63,6 @@ file="Source/PluginProcessor.cpp"/> - - + UIFileSharingEnabled="1" UISupportsDocumentBrowser="1" extraDefs="PS_USE_VDSP_FFT=1" + smallIcon="yM4pfJ" bigIcon="eZYcsl" iosBluetoothPermissionNeeded="1" + iosBackgroundBle="1">