diff --git a/CMakeLists.txt b/CMakeLists.txt index 87091ef..4a8b77e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,17 +1,17 @@ cmake_minimum_required(VERSION 3.22) -project(MonoSlicer VERSION 1.0.0 LANGUAGES C CXX) +project(Monoslicer VERSION 1.0.0 LANGUAGES C CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) add_subdirectory(lib/JUCE) -juce_add_plugin(MonoSlicer - COMPANY_NAME "MonoSlicer" +juce_add_plugin(Monoslicer + COMPANY_NAME "Monoslicer" PLUGIN_MANUFACTURER_CODE RcSp PLUGIN_CODE MsP1 FORMATS AU VST3 Standalone - PRODUCT_NAME "MonoSlicer" + PRODUCT_NAME "Monoslicer" IS_SYNTH TRUE NEEDS_MIDI_INPUT TRUE NEEDS_MIDI_OUTPUT FALSE @@ -21,7 +21,7 @@ juce_add_plugin(MonoSlicer VST3_CATEGORIES Instrument Sampler ) -target_sources(MonoSlicer PRIVATE +target_sources(Monoslicer PRIVATE Source/PluginProcessor.cpp Source/PluginEditor.cpp Source/SliceManager.cpp @@ -29,13 +29,13 @@ target_sources(MonoSlicer PRIVATE Source/PianoRollDisplay.cpp ) -target_compile_definitions(MonoSlicer PUBLIC +target_compile_definitions(Monoslicer PUBLIC JUCE_WEB_BROWSER=0 JUCE_USE_CURL=0 JUCE_VST3_CAN_REPLACE_VST2=0 ) -target_link_libraries(MonoSlicer +target_link_libraries(Monoslicer PRIVATE juce::juce_audio_utils juce::juce_audio_processors diff --git a/Makefile b/Makefile index 0fa2177..de52ce7 100644 --- a/Makefile +++ b/Makefile @@ -20,18 +20,18 @@ configure: fetch cmake -B $(BUILD_DIR) -S . $(CMAKE_ARGS) build: configure - @echo ">>> Building MonoSlicer..." + @echo ">>> Building Monoslicer..." cmake --build $(BUILD_DIR) -j$$(sysctl -n hw.ncpu) install: build @echo ">>> Installing plugins..." mkdir -p ~/Library/Audio/Plug-Ins/Components mkdir -p ~/Library/Audio/Plug-Ins/VST3 - cp -R $(BUILD_DIR)/MonoSlicer_artefacts/Release/AU/MonoSlicer.component ~/Library/Audio/Plug-Ins/Components/ - cp -R $(BUILD_DIR)/MonoSlicer_artefacts/Release/VST3/MonoSlicer.vst3 ~/Library/Audio/Plug-Ins/VST3/ + cp -R $(BUILD_DIR)/Monoslicer_artefacts/Release/AU/Monoslicer.component ~/Library/Audio/Plug-Ins/Components/ + cp -R $(BUILD_DIR)/Monoslicer_artefacts/Release/VST3/Monoslicer.vst3 ~/Library/Audio/Plug-Ins/VST3/ @echo ">>> Rescanning Audio Units..." killall -9 AudioComponentRegistrar 2>/dev/null; true - @echo ">>> Done. MonoSlicer installed as AU and VST3." + @echo ">>> Done. Monoslicer installed as AU and VST3." clean: rm -rf $(BUILD_DIR) diff --git a/README.md b/README.md index 21c8517..55cfdcb 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# MonoSlicer +# Monoslicer A monophonic sample slicer and sampler instrument built with [JUCE](https://juce.com/) and CMake. @@ -6,7 +6,7 @@ Load an audio file, slice it into regions (automatically via onset detection or ## Screenshot -![MonoSlicer](monoslicershot.png) +![Monoslicer](monoslicershot.png) ## Features @@ -31,7 +31,7 @@ Requires CMake 3.22+ and a C++17 compiler. make # Fetch JUCE, configure, build, and install (AU + VST3) ``` -Running `make` with no arguments will fetch JUCE, build MonoSlicer in Release mode, copy the AU and VST3 plugins to your system directories, and trigger an Audio Unit rescan. +Running `make` with no arguments will fetch JUCE, build Monoslicer in Release mode, copy the AU and VST3 plugins to your system directories, and trigger an Audio Unit rescan. Individual targets are also available: diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index f93a8af..0d8d9b3 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -1,6 +1,6 @@ #include "PluginEditor.h" -MonoSlicerEditor::MonoSlicerEditor(MonoSlicerProcessor& p) +MonoslicerEditor::MonoslicerEditor(MonoslicerProcessor& p) : AudioProcessorEditor(p), processorRef(p), waveformDisplay(p.getSliceManager()) @@ -16,6 +16,7 @@ MonoSlicerEditor::MonoSlicerEditor(MonoSlicerProcessor& p) }; setupTitleBtn(loadSampleButton, "Load audio file"); + setupTitleBtn(clearSampleButton, "Clear loaded sample"); setupTitleBtn(prevSampleButton, "Previous sample in folder"); setupTitleBtn(nextSampleButton, "Next sample in folder"); setupTitleBtn(undoButton, "Undo"); @@ -182,12 +183,13 @@ MonoSlicerEditor::MonoSlicerEditor(MonoSlicerProcessor& p) startTimerHz(30); } -MonoSlicerEditor::~MonoSlicerEditor() +MonoslicerEditor::~MonoslicerEditor() { autoSliceButton.removeListener(this); clearSlicesButton.removeListener(this); scaleComboBox.removeListener(this); loadSampleButton.removeListener(this); + clearSampleButton.removeListener(this); prevSampleButton.removeListener(this); nextSampleButton.removeListener(this); undoButton.removeListener(this); @@ -198,7 +200,7 @@ MonoSlicerEditor::~MonoSlicerEditor() stretchButton.removeListener(this); } -void MonoSlicerEditor::setupSlider(juce::Slider& slider, juce::Label& label, const juce::String& name) +void MonoslicerEditor::setupSlider(juce::Slider& slider, juce::Label& label, const juce::String& name) { addAndMakeVisible(slider); addAndMakeVisible(label); @@ -208,14 +210,14 @@ void MonoSlicerEditor::setupSlider(juce::Slider& slider, juce::Label& label, con label.setFont(juce::Font(juce::FontOptions(13.0f))); } -void MonoSlicerEditor::attachSlider(juce::Slider& slider, juce::Label& label, const juce::String& name, +void MonoslicerEditor::attachSlider(juce::Slider& slider, juce::Label& label, const juce::String& name, const juce::String& paramId, std::unique_ptr& attachment) { setupSlider(slider, label, name); attachment = std::make_unique(processorRef.getAPVTS(), paramId, slider); } -void MonoSlicerEditor::paint(juce::Graphics& g) +void MonoslicerEditor::paint(juce::Graphics& g) { g.fillAll(juce::Colour(0xff0f0f1a)); @@ -232,7 +234,7 @@ void MonoSlicerEditor::paint(juce::Graphics& g) g.drawHorizontalLine(40, 0.0f, static_cast(getWidth())); } -void MonoSlicerEditor::resized() +void MonoslicerEditor::resized() { float scaleX = static_cast(getWidth()) / static_cast(baseWidth); float scaleY = static_cast(getHeight()) / static_cast(baseHeight); @@ -249,6 +251,7 @@ void MonoSlicerEditor::resized() }; placeBtn(loadSampleButton, 44); + placeBtn(clearSampleButton, 44); placeBtn(prevSampleButton, 24); placeBtn(nextSampleButton, 24); @@ -339,7 +342,7 @@ void MonoSlicerEditor::resized() waveformDisplay.setBounds(mainArea); } -void MonoSlicerEditor::timerCallback() +void MonoslicerEditor::timerCallback() { if (processorRef.stateRestored.exchange(false)) { @@ -362,7 +365,7 @@ void MonoSlicerEditor::timerCallback() } } -void MonoSlicerEditor::buttonClicked(juce::Button* button) +void MonoslicerEditor::buttonClicked(juce::Button* button) { if (button == &loadSampleButton) { @@ -378,6 +381,11 @@ void MonoSlicerEditor::buttonClicked(juce::Button* button) } }); } + else if (button == &clearSampleButton) + { + processorRef.clearSample(); + waveformDisplay.setSampleBuffer(&processorRef.getSampleBuffer(), processorRef.getSampleRateLoaded()); + } else if (button == &prevSampleButton) { processorRef.loadPreviousSample(); @@ -440,7 +448,7 @@ void MonoSlicerEditor::buttonClicked(juce::Button* button) } } -void MonoSlicerEditor::comboBoxChanged(juce::ComboBox* comboBoxThatHasChanged) +void MonoslicerEditor::comboBoxChanged(juce::ComboBox* comboBoxThatHasChanged) { if (comboBoxThatHasChanged == &scaleComboBox) { @@ -454,7 +462,7 @@ void MonoSlicerEditor::comboBoxChanged(juce::ComboBox* comboBoxThatHasChanged) } } -bool MonoSlicerEditor::isInterestedInFileDrag(const juce::StringArray& files) +bool MonoslicerEditor::isInterestedInFileDrag(const juce::StringArray& files) { for (const auto& f : files) if (f.endsWithIgnoreCase(".wav") || f.endsWithIgnoreCase(".aiff") || @@ -463,7 +471,7 @@ bool MonoSlicerEditor::isInterestedInFileDrag(const juce::StringArray& files) return false; } -void MonoSlicerEditor::filesDropped(const juce::StringArray& files, int, int) +void MonoslicerEditor::filesDropped(const juce::StringArray& files, int, int) { for (const auto& f : files) { diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h index dae946f..c6e7cb9 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -3,15 +3,15 @@ #include "PluginProcessor.h" #include "WaveformDisplay.h" -class MonoSlicerEditor : public juce::AudioProcessorEditor, +class MonoslicerEditor : public juce::AudioProcessorEditor, private juce::FileDragAndDropTarget, private juce::Button::Listener, private juce::ComboBox::Listener, private juce::Timer { public: - MonoSlicerEditor(MonoSlicerProcessor&); - ~MonoSlicerEditor() override; + MonoslicerEditor(MonoslicerProcessor&); + ~MonoslicerEditor() override; void paint(juce::Graphics&) override; void resized() override; @@ -23,7 +23,7 @@ public: void timerCallback() override; private: - MonoSlicerProcessor& processorRef; + MonoslicerProcessor& processorRef; WaveformDisplay waveformDisplay; @@ -33,6 +33,7 @@ private: // Title bar buttons juce::TextButton loadSampleButton { "Load" }; + juce::TextButton clearSampleButton { "Clear" }; juce::TextButton prevSampleButton { "<" }; juce::TextButton nextSampleButton { ">" }; juce::TextButton undoButton { "Undo" }; @@ -94,5 +95,5 @@ private: int keyClickNote = -1; - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MonoSlicerEditor) + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MonoslicerEditor) }; diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index a5f8df3..ee9d99c 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -1,7 +1,7 @@ #include "PluginProcessor.h" #include "PluginEditor.h" -MonoSlicerProcessor::MonoSlicerProcessor() +MonoslicerProcessor::MonoslicerProcessor() : AudioProcessor(BusesProperties() .withInput("Input", juce::AudioChannelSet::stereo(), true) .withOutput("Output", juce::AudioChannelSet::stereo(), true)), @@ -10,9 +10,9 @@ MonoSlicerProcessor::MonoSlicerProcessor() formatManager.registerBasicFormats(); } -MonoSlicerProcessor::~MonoSlicerProcessor() {} +MonoslicerProcessor::~MonoslicerProcessor() {} -juce::AudioProcessorValueTreeState::ParameterLayout MonoSlicerProcessor::createParameterLayout() +juce::AudioProcessorValueTreeState::ParameterLayout MonoslicerProcessor::createParameterLayout() { juce::AudioProcessorValueTreeState::ParameterLayout layout; @@ -88,15 +88,15 @@ juce::AudioProcessorValueTreeState::ParameterLayout MonoSlicerProcessor::createP return layout; } -void MonoSlicerProcessor::prepareToPlay(double sampleRate, int samplesPerBlock) +void MonoslicerProcessor::prepareToPlay(double sampleRate, int samplesPerBlock) { currentSampleRate = sampleRate; juce::ignoreUnused(samplesPerBlock); } -void MonoSlicerProcessor::releaseResources() {} +void MonoslicerProcessor::releaseResources() {} -void MonoSlicerProcessor::computeBiquadCoeffs(FilterType type, float cutoff, float resonance, +void MonoslicerProcessor::computeBiquadCoeffs(FilterType type, float cutoff, float resonance, float sampleRate, float& b0, float& b1, float& b2, float& a1, float& a2) { @@ -163,7 +163,7 @@ void MonoSlicerProcessor::computeBiquadCoeffs(FilterType type, float cutoff, flo } } -void MonoSlicerProcessor::advanceAmpEnvelope(Voice& voice, int samples) +void MonoslicerProcessor::advanceAmpEnvelope(Voice& voice, int samples) { float attack = apvts.getRawParameterValue("ampAttack")->load(); float decay = apvts.getRawParameterValue("ampDecay")->load(); @@ -221,7 +221,7 @@ void MonoSlicerProcessor::advanceAmpEnvelope(Voice& voice, int samples) } } -void MonoSlicerProcessor::advanceFilterEnvelope(Voice& voice, int samples) +void MonoslicerProcessor::advanceFilterEnvelope(Voice& voice, int samples) { float attack = apvts.getRawParameterValue("filterAttack")->load(); float decay = apvts.getRawParameterValue("filterDecay")->load(); @@ -273,7 +273,7 @@ void MonoSlicerProcessor::advanceFilterEnvelope(Voice& voice, int samples) } } -void MonoSlicerProcessor::processBlock(juce::AudioBuffer& buffer, juce::MidiBuffer& midiMessages) +void MonoslicerProcessor::processBlock(juce::AudioBuffer& buffer, juce::MidiBuffer& midiMessages) { juce::ScopedNoDenormals noDenormals; buffer.clear(); @@ -446,7 +446,7 @@ void MonoSlicerProcessor::processBlock(juce::AudioBuffer& buffer, juce::M currentPlaybackSample.store(-1); } -void MonoSlicerProcessor::handleNoteOn(int noteNumber, float velocity) +void MonoslicerProcessor::handleNoteOn(int noteNumber, float velocity) { int offset = static_cast(apvts.getRawParameterValue("octaveOffset")->load()) * 12; int sliceIdx = noteNumber - 60 + offset; @@ -479,7 +479,7 @@ void MonoSlicerProcessor::handleNoteOn(int noteNumber, float velocity) } } -void MonoSlicerProcessor::handleNoteOff() +void MonoslicerProcessor::handleNoteOff() { for (auto& voice : voices) { @@ -491,7 +491,7 @@ void MonoSlicerProcessor::handleNoteOff() } } -void MonoSlicerProcessor::loadAudioFile(const juce::File& file) +void MonoslicerProcessor::loadAudioFile(const juce::File& file) { std::unique_ptr reader(formatManager.createReaderFor(file)); if (reader) @@ -507,7 +507,17 @@ void MonoSlicerProcessor::loadAudioFile(const juce::File& file) } } -void MonoSlicerProcessor::refreshFolderList() +void MonoslicerProcessor::clearSample() +{ + sampleBuffer.setSize(0, 0); + currentFile = juce::File{}; + folderFiles.clear(); + currentFileIndex = -1; + sliceManager.setSampleBuffer(&sampleBuffer, loadedSampleRate); + selectedSlice.store(-1); +} + +void MonoslicerProcessor::refreshFolderList() { folderFiles.clear(); currentFileIndex = -1; @@ -527,37 +537,37 @@ void MonoSlicerProcessor::refreshFolderList() } } -void MonoSlicerProcessor::loadNextSample() +void MonoslicerProcessor::loadNextSample() { if (folderFiles.isEmpty()) return; int nextIdx = (currentFileIndex + 1) % folderFiles.size(); loadAudioFile(folderFiles[nextIdx]); } -void MonoSlicerProcessor::loadPreviousSample() +void MonoslicerProcessor::loadPreviousSample() { if (folderFiles.isEmpty()) return; int prevIdx = (currentFileIndex - 1 + folderFiles.size()) % folderFiles.size(); loadAudioFile(folderFiles[prevIdx]); } -void MonoSlicerProcessor::triggerSlice(int sliceIndex, float velocity) +void MonoslicerProcessor::triggerSlice(int sliceIndex, float velocity) { int noteNumber = sliceIndex + 60; handleNoteOn(noteNumber, velocity); } -void MonoSlicerProcessor::releaseSlice() +void MonoslicerProcessor::releaseSlice() { handleNoteOff(); } -juce::AudioProcessorEditor* MonoSlicerProcessor::createEditor() +juce::AudioProcessorEditor* MonoslicerProcessor::createEditor() { - return new MonoSlicerEditor(*this); + return new MonoslicerEditor(*this); } -void MonoSlicerProcessor::getStateInformation(juce::MemoryBlock& destData) +void MonoslicerProcessor::getStateInformation(juce::MemoryBlock& destData) { auto state = apvts.copyState(); @@ -592,7 +602,7 @@ void MonoSlicerProcessor::getStateInformation(juce::MemoryBlock& destData) } } -void MonoSlicerProcessor::setStateInformation(const void* data, int sizeInBytes) +void MonoslicerProcessor::setStateInformation(const void* data, int sizeInBytes) { auto stream = juce::MemoryInputStream(data, static_cast(sizeInBytes), false); auto state = juce::ValueTree::readFromStream(stream); @@ -661,10 +671,10 @@ void MonoSlicerProcessor::setStateInformation(const void* data, int sizeInBytes) juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() { - return new MonoSlicerProcessor(); + return new MonoslicerProcessor(); } -void MonoSlicerProcessor::stretchToBeats(int numBeats, float bpm) +void MonoslicerProcessor::stretchToBeats(int numBeats, float bpm) { if (sampleBuffer.getNumSamples() == 0 || bpm <= 0.0f || numBeats <= 0) return; diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index a3ac892..22b364a 100644 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -4,11 +4,11 @@ #include #include "SliceManager.h" -class MonoSlicerProcessor : public juce::AudioProcessor +class MonoslicerProcessor : public juce::AudioProcessor { public: - MonoSlicerProcessor(); - ~MonoSlicerProcessor() override; + MonoslicerProcessor(); + ~MonoslicerProcessor() override; void prepareToPlay(double sampleRate, int samplesPerBlock) override; void releaseResources() override; @@ -34,6 +34,7 @@ public: void setStateInformation(const void* data, int sizeInBytes) override; void loadAudioFile(const juce::File& file); + void clearSample(); void loadNextSample(); void loadPreviousSample(); void triggerSlice(int sliceIndex, float velocity = 0.8f); @@ -123,5 +124,5 @@ private: void computeBiquadCoeffs(FilterType type, float cutoff, float resonance, float sampleRate, float& b0, float& b1, float& b2, float& a1, float& a2); - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MonoSlicerProcessor) + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MonoslicerProcessor) }; diff --git a/Source/WaveformDisplay.cpp b/Source/WaveformDisplay.cpp index 11ff48c..44665f1 100644 --- a/Source/WaveformDisplay.cpp +++ b/Source/WaveformDisplay.cpp @@ -208,7 +208,7 @@ void WaveformDisplay::paint(juce::Graphics& g) auto textArea = bounds.withTrimmedBottom(scrollbarHeight); g.setColour(juce::Colours::grey); g.setFont(14.0f); - g.drawText("Drop or load an audio file", textArea, juce::Justification::centred); + g.drawText("No audio file loaded.", textArea, juce::Justification::centred); hScrollBar.setBounds(bounds.removeFromBottom(scrollbarHeight).toNearestInt()); return; }