diff --git a/Source/PS_Source/StretchSource.cpp b/Source/PS_Source/StretchSource.cpp index afcca2b..97c5000 100644 --- a/Source/PS_Source/StretchSource.cpp +++ b/Source/PS_Source/StretchSource.cpp @@ -122,6 +122,7 @@ void StretchAudioSource::setAudioBufferAsInputSource(AudioBuffer* buf, in m_inputfile->setAudioBuffer(buf, sr, len); m_seekpos = 0.0; m_lastinpos = 0.0; + m_curfile = File(); } void StretchAudioSource::getNextAudioBlock(const AudioSourceChannelInfo & bufferToFill) @@ -702,7 +703,8 @@ void MultiStretchAudioSource::setFFTSize(int size) double curpos = m_stretchsources[0]->getInfilePositionPercent(); m_stretchsources[1]->setFFTSize(size); m_stretchsources[1]->setNumOutChannels(m_stretchsources[0]->getNumOutChannels()); - m_stretchsources[1]->setAudioFile(m_stretchsources[0]->getAudioFile()); + if (m_stretchsources[0]->getAudioFile()!=File()) + m_stretchsources[1]->setAudioFile(m_stretchsources[0]->getAudioFile()); m_stretchsources[1]->setRate(m_stretchsources[0]->getRate()); m_stretchsources[1]->setPlayRange(m_stretchsources[0]->getPlayRange(), m_stretchsources[0]->isLoopEnabled()); m_stretchsources[1]->seekPercent(curpos); diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index afeb59b..c77ecc2 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -14,9 +14,10 @@ //============================================================================== PaulstretchpluginAudioProcessorEditor::PaulstretchpluginAudioProcessorEditor (PaulstretchpluginAudioProcessor& p) - : AudioProcessorEditor (&p), processor (p) + : AudioProcessorEditor (&p), processor (p), m_wavecomponent(p.m_afm.get()) { addAndMakeVisible(&m_info_label); + addAndMakeVisible(&m_wavecomponent); const auto& pars = processor.getParameters(); for (int i=0;i range, int which) + { + *processor.getFloatParameter(5) = range.getStart(); + *processor.getFloatParameter(6) = range.getEnd(); + }; + m_wavecomponent.ShowFileCacheRange = true; startTimer(1, 100); } @@ -51,9 +58,10 @@ void PaulstretchpluginAudioProcessorEditor::paint (Graphics& g) void PaulstretchpluginAudioProcessorEditor::resized() { - m_rec_enable.setBounds(1, getHeight() - 25, 10, 24); + m_rec_enable.setBounds(1, m_parcomps.back()->getBottom()+1, 10, 24); m_rec_enable.changeWidthToFitText(); - m_info_label.setBounds(m_rec_enable.getRight() + 1, getHeight() - 25, 60, 24); + m_info_label.setBounds(m_rec_enable.getRight() + 1, m_rec_enable.getY(), 60, 24); + m_wavecomponent.setBounds(1, m_info_label.getBottom()+1, getWidth()-2, getHeight()-1-m_info_label.getBottom()); } void PaulstretchpluginAudioProcessorEditor::timerCallback(int id) @@ -67,3 +75,270 @@ void PaulstretchpluginAudioProcessorEditor::timerCallback(int id) m_info_label.setText(String(processor.getRecordingPositionPercent()*100.0, 1),dontSendNotification); } } + +void PaulstretchpluginAudioProcessorEditor::setAudioFile(File f) +{ + m_wavecomponent.setAudioFile(f); +} + +void PaulstretchpluginAudioProcessorEditor::setAudioBuffer(AudioBuffer* buf, int samplerate, int len) +{ + MessageManager::callAsync([this,buf, samplerate, len]() + { + m_wavecomponent.setAudioBuffer(buf, samplerate, len); + }); +} + +WaveformComponent::WaveformComponent(AudioFormatManager* afm) : m_thumbcache(100) +{ + TimeSelectionChangedCallback = [](Range, int) {}; + if (m_use_opengl == true) + m_ogl.attachTo(*this); + // The default priority of 2 is a bit too low in some cases, it seems... + m_thumbcache.getTimeSliceThread().setPriority(3); + m_thumb = std::make_unique(512, *afm, m_thumbcache); + m_thumb->addChangeListener(this); + setOpaque(true); +} + +WaveformComponent::~WaveformComponent() +{ + if (m_use_opengl == true) + m_ogl.detach(); +} + +void WaveformComponent::changeListenerCallback(ChangeBroadcaster * /*cb*/) +{ + m_waveimage = Image(); + repaint(); +} + +void WaveformComponent::paint(Graphics & g) +{ + //Logger::writeToLog("Waveform component paint"); + g.fillAll(Colours::black); + g.setColour(Colours::darkgrey); + g.fillRect(0, 0, getWidth(), m_topmargin); + if (m_thumb == nullptr || m_thumb->getTotalLength() < 0.1) + { + g.setColour(Colours::aqua.darker()); + g.drawText("No file loaded", 2, m_topmargin + 2, getWidth(), 20, Justification::topLeft); + return; + } + g.setColour(Colours::lightslategrey); + double thumblen = m_thumb->getTotalLength(); + double tick_interval = 1.0; + if (thumblen > 60.0) + tick_interval = 5.0; + for (double secs = 0.0; secs < thumblen; secs += tick_interval) + { + float tickxcor = (float)jmap(secs, + thumblen*m_view_range.getStart(), thumblen*m_view_range.getEnd(), 0.0f, (float)getWidth()); + g.drawLine(tickxcor, 0.0, tickxcor, (float)m_topmargin, 1.0f); + } + bool m_use_cached_image = true; + if (m_use_cached_image == true) + { + if (m_waveimage.isValid() == false || m_waveimage.getWidth() != getWidth() + || m_waveimage.getHeight() != getHeight() - m_topmargin) + { + //Logger::writeToLog("updating cached waveform image"); + m_waveimage = Image(Image::ARGB, getWidth(), getHeight() - m_topmargin, true); + Graphics tempg(m_waveimage); + tempg.fillAll(Colours::black); + tempg.setColour(Colours::darkgrey); + m_thumb->drawChannels(tempg, { 0,0,getWidth(),getHeight() - m_topmargin }, + thumblen*m_view_range.getStart(), thumblen*m_view_range.getEnd(), 1.0f); + } + g.drawImage(m_waveimage, 0, m_topmargin, getWidth(), getHeight() - m_topmargin, 0, 0, getWidth(), getHeight() - m_topmargin); + + } + else + { + //g.fillAll(Colours::black); + g.setColour(Colours::darkgrey); + m_thumb->drawChannels(g, { 0,m_topmargin,getWidth(),getHeight() - m_topmargin }, + thumblen*m_view_range.getStart(), thumblen*m_view_range.getEnd(), 1.0f); + } + + //g.setColour(Colours::darkgrey); + //m_thumb->drawChannels(g, { 0,m_topmargin,getWidth(),getHeight()-m_topmargin }, + // 0.0, thumblen, 1.0f); + g.setColour(Colours::white.withAlpha(0.5f)); + int xcorleft = (int)jmap(m_time_sel_start, m_view_range.getStart(), m_view_range.getEnd(), 0, getWidth()); + int xcorright = (int)jmap(m_time_sel_end, m_view_range.getStart(), m_view_range.getEnd(), 0, getWidth()); + g.fillRect(xcorleft, m_topmargin, xcorright - xcorleft, getHeight() - m_topmargin); + if (m_file_cached.first.getLength() > 0.0 && + (bool)ShowFileCacheRange.getValue()) + { + g.setColour(Colours::red.withAlpha(0.2f)); + xcorleft = (int)jmap(m_file_cached.first.getStart(), m_view_range.getStart(), m_view_range.getEnd(), 0, getWidth()); + xcorright = (int)jmap(m_file_cached.first.getEnd(), m_view_range.getStart(), m_view_range.getEnd(), 0, getWidth()); + g.fillRect(xcorleft, 0, xcorright - xcorleft, m_topmargin / 2); + xcorleft = (int)jmap(m_file_cached.second.getStart(), m_view_range.getStart(), m_view_range.getEnd(), 0, getWidth()); + xcorright = (int)jmap(m_file_cached.second.getEnd(), m_view_range.getStart(), m_view_range.getEnd(), 0, getWidth()); + if (xcorright - xcorleft>0) + { + g.setColour(Colours::blue.withAlpha(0.2f)); + g.fillRect(xcorleft, m_topmargin / 2, xcorright - xcorleft, m_topmargin / 2); + } + } + + g.setColour(Colours::white); + if (CursorPosCallback) + { + double pos = jmap(CursorPosCallback(), m_view_range.getStart(), m_view_range.getEnd(), 0, getWidth()); + g.fillRect((int)pos, m_topmargin, 1, getHeight() - m_topmargin); + } + g.setColour(Colours::aqua.darker()); + g.drawText(m_curfile.getFullPathName(), 2, m_topmargin + 2, getWidth(), 20, Justification::topLeft); +} + +void WaveformComponent::setAudioFile(File f) +{ + if (f.existsAsFile()) + { + m_waveimage = Image(); + if (m_thumb != nullptr && f == m_curfile) // reloading same file, might happen that the overview needs to be redone... + m_thumbcache.removeThumb(m_thumb->getHashCode()); + m_thumb->setSource(new FileInputSource(f)); + m_curfile = f; + } + else + { + m_thumb->setSource(nullptr); + } + +} + +void WaveformComponent::setAudioBuffer(AudioBuffer* buf, int samplerate, int len) +{ + m_waveimage = Image(); + m_curfile = File(); + m_thumb->reset(buf->getNumChannels(), samplerate, len); + m_thumb->addBlock(0, *buf, 0, len); +} + +void WaveformComponent::timerCallback() +{ + repaint(); +} + +void WaveformComponent::setFileCachedRange(std::pair, Range> rng) +{ + m_file_cached = rng; + //repaint(); +} + +void WaveformComponent::setTimerEnabled(bool b) +{ + if (b == true) + startTimer(100); + else + stopTimer(); +} + +void WaveformComponent::setViewRange(Range rng) +{ + m_view_range = rng; + m_waveimage = Image(); + repaint(); +} + + +void WaveformComponent::mouseDown(const MouseEvent & e) +{ + m_mousedown = true; + double pos = jmap(e.x, 0, getWidth(), m_view_range.getStart(), m_view_range.getEnd()); + if (e.y < m_topmargin) + { + if (SeekCallback) + SeekCallback(pos); + m_didseek = true; + } + else + { + m_time_sel_drag_target = getTimeSelectionEdge(e.x, e.y); + m_drag_time_start = pos; + if (m_time_sel_drag_target == 0) + { + m_time_sel_start = -1.0; + m_time_sel_end = -1.0; + } + } + + repaint(); +} + +void WaveformComponent::mouseUp(const MouseEvent & /*e*/) +{ + m_mousedown = false; + m_didseek = false; + if (m_didchangetimeselection) + { + TimeSelectionChangedCallback(Range(m_time_sel_start, m_time_sel_end), 1); + m_didchangetimeselection = false; + } +} + +void WaveformComponent::mouseDrag(const MouseEvent & e) +{ + if (m_didseek == true) + return; + if (m_time_sel_drag_target == 0) + { + m_time_sel_start = m_drag_time_start; + m_time_sel_end = jmap(e.x, 0, getWidth(), m_view_range.getStart(), m_view_range.getEnd()); + } + if (m_time_sel_drag_target == 1) + { + m_time_sel_start = jmap(e.x, 0, getWidth(), m_view_range.getStart(), m_view_range.getEnd()); + } + if (m_time_sel_drag_target == 2) + { + m_time_sel_end = jmap(e.x, 0, getWidth(), m_view_range.getStart(), m_view_range.getEnd()); + } + if (m_time_sel_start > m_time_sel_end) + { + std::swap(m_time_sel_start, m_time_sel_end); + if (m_time_sel_drag_target == 1) + m_time_sel_drag_target = 2; + else if (m_time_sel_drag_target == 2) + m_time_sel_drag_target = 1; + } + m_time_sel_start = jlimit(0.0, 1.0, m_time_sel_start); + m_time_sel_end = jlimit(0.0, 1.0, m_time_sel_end); + + if (TimeSelectionChangedCallback) + { + if (m_time_sel_end>m_time_sel_start) + TimeSelectionChangedCallback(Range(m_time_sel_start, m_time_sel_end), 0); + else + TimeSelectionChangedCallback(Range(0.0, 1.0), 0); + } + m_didchangetimeselection = true; + repaint(); +} + +void WaveformComponent::mouseMove(const MouseEvent & e) +{ + m_time_sel_drag_target = getTimeSelectionEdge(e.x, e.y); + if (m_time_sel_drag_target == 0) + setMouseCursor(MouseCursor::NormalCursor); + if (m_time_sel_drag_target == 1) + setMouseCursor(MouseCursor::LeftRightResizeCursor); + if (m_time_sel_drag_target == 2) + setMouseCursor(MouseCursor::LeftRightResizeCursor); + +} + +int WaveformComponent::getTimeSelectionEdge(int x, int y) +{ + int xcorleft = (int)jmap(m_time_sel_start, m_view_range.getStart(), m_view_range.getEnd(), 0, getWidth()); + int xcorright = (int)jmap(m_time_sel_end, m_view_range.getStart(), m_view_range.getEnd(), 0, getWidth()); + if (juce::Rectangle(xcorleft - 5, m_topmargin, 10, getHeight() - m_topmargin).contains(x, y)) + return 1; + if (juce::Rectangle(xcorright - 5, m_topmargin, 10, getHeight() - m_topmargin).contains(x, y)) + return 2; + return 0; +} diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h index 6246d14..3e1e750 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -70,6 +70,63 @@ private: std::unique_ptr m_togglebut; }; +class WaveformComponent : public Component, public ChangeListener, public Timer +{ +public: + WaveformComponent(AudioFormatManager* afm); + ~WaveformComponent(); + void changeListenerCallback(ChangeBroadcaster* cb) override; + void paint(Graphics& g) override; + void setAudioFile(File f); + void setAudioBuffer(AudioBuffer* buf, int samplerate, int len); + void timerCallback() override; + std::function CursorPosCallback; + std::function SeekCallback; + std::function, int)> TimeSelectionChangedCallback; + void mouseDown(const MouseEvent& e) override; + void mouseUp(const MouseEvent& e) override; + void mouseDrag(const MouseEvent& e) override; + void mouseMove(const MouseEvent& e) override; + Range getTimeSelection() + { + if (m_time_sel_start >= 0.0 && m_time_sel_end>m_time_sel_start + 0.001) + return { m_time_sel_start, m_time_sel_end }; + return { 0.0, 1.0 }; + } + void setTimeSelection(Range rng) + { + if (rng.isEmpty()) + rng = { -1.0,1.0 }; + m_time_sel_start = rng.getStart(); + m_time_sel_end = rng.getEnd(); + repaint(); + } + void setFileCachedRange(std::pair, Range> rng); + void setTimerEnabled(bool b); + void setViewRange(Range rng); + Value ShowFileCacheRange; +private: + AudioThumbnailCache m_thumbcache; + + std::unique_ptr m_thumb; + Range m_view_range{ 0.0,1.0 }; + int m_time_sel_drag_target = 0; + double m_time_sel_start = -1.0; + double m_time_sel_end = -1.0; + double m_drag_time_start = 0.0; + bool m_mousedown = false; + bool m_didseek = false; + bool m_didchangetimeselection = false; + int m_topmargin = 0; + int getTimeSelectionEdge(int x, int y); + std::pair, Range> m_file_cached; + File m_curfile; + Image m_waveimage; + OpenGLContext m_ogl; + bool m_use_opengl = false; +}; + + class PaulstretchpluginAudioProcessorEditor : public AudioProcessorEditor, public MultiTimer, public Button::Listener { @@ -81,10 +138,13 @@ public: void paint (Graphics&) override; void resized() override; void timerCallback(int id) override; + void setAudioFile(File f); + void setAudioBuffer(AudioBuffer* buf, int samplerate, int len); private: PaulstretchpluginAudioProcessor& processor; std::vector> m_parcomps; ToggleButton m_rec_enable; Label m_info_label; + WaveformComponent m_wavecomponent; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PaulstretchpluginAudioProcessorEditor) }; diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 0e2ea59..028ebf7 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -110,6 +110,17 @@ void PaulstretchpluginAudioProcessor::changeProgramName (int index, const String //============================================================================== void PaulstretchpluginAudioProcessor::prepareToPlay(double sampleRate, int samplesPerBlock) { + if (m_ready_to_play == false) + { + m_control->update_player_stretch(); + m_control->update_process_parameters(); + String err; + m_control->startplay(false, true, + { *getFloatParameter(5),*getFloatParameter(6) }, + 2, err); + m_ready_to_play = true; + } + return; m_ready_to_play = false; m_control->set_input_file(File("C:/MusicAudio/sourcesamples/sheila.wav"), [this](String cberr) { @@ -120,6 +131,11 @@ void PaulstretchpluginAudioProcessor::prepareToPlay(double sampleRate, int sampl m_control->update_player_stretch(); m_control->update_process_parameters(); m_control->startplay(false, true, { 0.0,1.0 }, 2, err); + auto ed = dynamic_cast(getActiveEditor()); + if (ed) + { + ed->setAudioFile(m_control->getStretchAudioSource()->getAudioFile()); + } } else m_ready_to_play = false; }); @@ -128,7 +144,8 @@ void PaulstretchpluginAudioProcessor::prepareToPlay(double sampleRate, int sampl void PaulstretchpluginAudioProcessor::releaseResources() { - m_control->stopplay(); + //m_control->stopplay(); + //m_ready_to_play = false; } #ifndef JucePlugin_PreferredChannelConfigurations @@ -175,13 +192,12 @@ void PaulstretchpluginAudioProcessor::processBlock (AudioSampleBuffer& buffer, M m_rec_pos += buffer.getNumSamples(); if (m_rec_pos >= m_max_reclen * getSampleRate()) { - m_is_recording = false; - m_control->getStretchAudioSource()->setAudioBufferAsInputSource(&m_recbuffer, getSampleRate(), - m_max_reclen*getSampleRate()); + finishRecording(m_max_reclen*getSampleRate()); } return; } m_control->getStretchAudioSource()->setRate(*getFloatParameter(1)); + m_control->getStretchAudioSource()->val_XFadeLen = 0.1; //m_control->setFFTSize(*getFloatParameter(2)); m_control->ppar.pitch_shift.cents = *getFloatParameter(3) * 100.0; m_control->ppar.freq_shift.Hz = *getFloatParameter(4); @@ -232,8 +248,10 @@ void PaulstretchpluginAudioProcessor::setRecordingEnabled(bool b) } else { - m_is_recording = false; - m_control->getStretchAudioSource()->setAudioBufferAsInputSource(&m_recbuffer, getSampleRate(), m_rec_pos); + if (m_is_recording == true) + { + finishRecording(m_rec_pos); + } } } @@ -244,6 +262,17 @@ double PaulstretchpluginAudioProcessor::getRecordingPositionPercent() return 1.0 / m_recbuffer.getNumSamples()*m_rec_pos; } +void PaulstretchpluginAudioProcessor::finishRecording(int lenrecording) +{ + m_is_recording = false; + m_control->getStretchAudioSource()->setAudioBufferAsInputSource(&m_recbuffer, getSampleRate(), lenrecording); + auto ed = dynamic_cast(getActiveEditor()); + if (ed) + { + ed->setAudioBuffer(&m_recbuffer, getSampleRate(), m_rec_pos); + } +} + //============================================================================== // This creates new instances of the plugin.. AudioProcessor* JUCE_CALLTYPE createPluginFilter() diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index fa4e0f6..0798576 100644 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -62,14 +62,18 @@ public: void setRecordingEnabled(bool b); bool isRecordingEnabled() { return m_is_recording; } double getRecordingPositionPercent(); + std::unique_ptr m_afm; private: std::unique_ptr m_control; - std::unique_ptr m_afm; + bool m_ready_to_play = false; AudioBuffer m_recbuffer; double m_max_reclen = 10.0; bool m_is_recording = false; int m_rec_pos = 0; - //============================================================================== + void finishRecording(int lenrecorded); + bool m_using_memory_buffer = true; + + //============================================================================== JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PaulstretchpluginAudioProcessor) };