mirror of
https://codeberg.org/armin/monoslicer.git
synced 2026-10-11 21:11:53 +02:00
Add drag-and-drop regression test and build targets
This commit is contained in:
parent
ca6f1ff0ee
commit
f28e4d37c5
7 changed files with 329 additions and 39 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1 +1,2 @@
|
||||||
build/
|
build/
|
||||||
|
build-tests/
|
||||||
|
|
@ -90,6 +90,42 @@ target_link_libraries(Monoslicer
|
||||||
juce::juce_recommended_lto_flags
|
juce::juce_recommended_lto_flags
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Drag and drop regression test. Off by default: a console app needs its own copy
|
||||||
|
# of the JUCE modules, so building it by default would roughly double build time.
|
||||||
|
# Enable with -DMONOSLICER_BUILD_TESTS=ON, or just use `make test`.
|
||||||
|
option(MONOSLICER_BUILD_TESTS "Build the test executables" OFF)
|
||||||
|
|
||||||
|
if(MONOSLICER_BUILD_TESTS)
|
||||||
|
juce_add_console_app(MonoslicerDnDTest
|
||||||
|
PRODUCT_NAME "MonoslicerDnDTest"
|
||||||
|
)
|
||||||
|
target_sources(MonoslicerDnDTest PRIVATE
|
||||||
|
Tests/DragAndDropTest.cpp
|
||||||
|
Source/PluginProcessor.cpp
|
||||||
|
Source/PluginEditor.cpp
|
||||||
|
Source/SliceManager.cpp
|
||||||
|
Source/WaveformDisplay.cpp
|
||||||
|
Source/PianoRollDisplay.cpp
|
||||||
|
)
|
||||||
|
target_compile_definitions(MonoslicerDnDTest PUBLIC
|
||||||
|
JUCE_WEB_BROWSER=0
|
||||||
|
JUCE_USE_CURL=0
|
||||||
|
JUCE_VST3_CAN_REPLACE_VST2=0
|
||||||
|
JucePlugin_Name="Monoslicer"
|
||||||
|
GIT_VERSION="${GIT_VERSION}"
|
||||||
|
# handleDragDrop() defers filesDropped() via MessageManager::callAsync, so the
|
||||||
|
# test needs to be able to pump the message loop to observe it.
|
||||||
|
JUCE_MODAL_LOOPS_PERMITTED=1
|
||||||
|
)
|
||||||
|
target_link_libraries(MonoslicerDnDTest
|
||||||
|
PRIVATE
|
||||||
|
juce::juce_audio_utils
|
||||||
|
juce::juce_audio_processors
|
||||||
|
juce::juce_audio_formats
|
||||||
|
juce::juce_dsp
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
# Suppress JUCE / VST3 SDK warnings globally for all targets.
|
# Suppress JUCE / VST3 SDK warnings globally for all targets.
|
||||||
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fcolor-diagnostics")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fcolor-diagnostics")
|
||||||
|
|
|
||||||
69
Makefile
69
Makefile
|
|
@ -2,7 +2,32 @@ JUCE_REPO := https://github.com/juce-framework/JUCE.git
|
||||||
JUCE_DIR := lib/JUCE
|
JUCE_DIR := lib/JUCE
|
||||||
JUCE_TAG := 8.0.9
|
JUCE_TAG := 8.0.9
|
||||||
BUILD_DIR := build
|
BUILD_DIR := build
|
||||||
CMAKE_ARGS := -DCMAKE_BUILD_TYPE=Release
|
TEST_BUILD_DIR := build-tests
|
||||||
|
BUILD_TYPE := Release
|
||||||
|
CMAKE_ARGS := -DCMAKE_BUILD_TYPE=$(BUILD_TYPE)
|
||||||
|
UNAME_S := $(shell uname -s)
|
||||||
|
|
||||||
|
# Platform-aware CPU count
|
||||||
|
ifeq ($(UNAME_S),Darwin)
|
||||||
|
JOBS := $(shell sysctl -n hw.ncpu 2>/dev/null || nproc 2>/dev/null || echo 4)
|
||||||
|
else
|
||||||
|
JOBS := $(shell nproc 2>/dev/null || getconf _NPROCESSORS_ONLN 2>/dev/null || echo 4)
|
||||||
|
endif
|
||||||
|
|
||||||
|
ARTEFACTS := $(BUILD_DIR)/Monoslicer_artefacts/$(BUILD_TYPE)
|
||||||
|
VST3 := $(ARTEFACTS)/VST3/Monoslicer.vst3
|
||||||
|
|
||||||
|
# AU only exists on macOS; Standalone is a .app on macOS, a plain binary elsewhere
|
||||||
|
ifeq ($(UNAME_S),Darwin)
|
||||||
|
AU := $(ARTEFACTS)/AU/Monoslicer.component
|
||||||
|
AU_DIR := ~/Library/Audio/Plug-Ins/Components
|
||||||
|
VST3_DIR := ~/Library/Audio/Plug-Ins/VST3
|
||||||
|
FORMATS := AU and VST3
|
||||||
|
else
|
||||||
|
AU :=
|
||||||
|
VST3_DIR := ~/.vst3
|
||||||
|
FORMATS := VST3
|
||||||
|
endif
|
||||||
|
|
||||||
C_RESET := \033[0m
|
C_RESET := \033[0m
|
||||||
C_BOLD := \033[1m
|
C_BOLD := \033[1m
|
||||||
|
|
@ -25,9 +50,9 @@ COLORIZE = sed -E "$$(printf ' \
|
||||||
s/(.*error:.*)$$/\033[1m\\1\033[0m/i \
|
s/(.*error:.*)$$/\033[1m\\1\033[0m/i \
|
||||||
')"
|
')"
|
||||||
|
|
||||||
.PHONY: all clean fetch configure build install
|
.PHONY: all clean fetch configure build install test
|
||||||
|
|
||||||
all: install
|
all: build
|
||||||
|
|
||||||
$(JUCE_DIR)/CMakeLists.txt:
|
$(JUCE_DIR)/CMakeLists.txt:
|
||||||
$(call STEP,Fetching JUCE $(JUCE_TAG))
|
$(call STEP,Fetching JUCE $(JUCE_TAG))
|
||||||
|
|
@ -38,28 +63,42 @@ fetch: $(JUCE_DIR)/CMakeLists.txt
|
||||||
|
|
||||||
configure: fetch
|
configure: fetch
|
||||||
$(call STEP,Configuring CMake)
|
$(call STEP,Configuring CMake)
|
||||||
$(call INFO,$(C_DIM)cmake -B build -S . -DCMAKE_BUILD_TYPE=Release$(C_RESET))
|
$(call INFO,$(C_DIM)cmake -B $(BUILD_DIR) -S . $(CMAKE_ARGS)$(C_RESET))
|
||||||
cmake -B $(BUILD_DIR) -S . $(CMAKE_ARGS) | $(COLORIZE)
|
cmake -B $(BUILD_DIR) -S . $(CMAKE_ARGS) | $(COLORIZE)
|
||||||
|
|
||||||
build: configure
|
build: configure
|
||||||
$(call STEP,Building Monoslicer)
|
$(call STEP,Building Monoslicer)
|
||||||
$(call INFO,$(C_DIM)$(C_YELLOW)$(shell getconf _NPROCESSORS_ONLN)$(C_RESET)$(C_DIM) parallel jobs$(C_RESET))
|
$(call INFO,$(C_DIM)$(C_YELLOW)$(JOBS)$(C_RESET)$(C_DIM) parallel jobs$(C_RESET))
|
||||||
cmake --build $(BUILD_DIR) -j$(shell getconf _NPROCESSORS_ONLN) | $(COLORIZE)
|
cmake --build $(BUILD_DIR) -j$(JOBS) | $(COLORIZE)
|
||||||
|
|
||||||
install: build
|
install: build
|
||||||
$(call STEP,Installing plugins)
|
$(call STEP,Installing plugins)
|
||||||
$(call INFO,$(C_DIM)AU → ~/Library/Audio/Plug-Ins/Components$(C_RESET))
|
ifeq ($(UNAME_S),Darwin)
|
||||||
mkdir -p ~/Library/Audio/Plug-Ins/Components
|
$(call INFO,AU → $(AU_DIR))
|
||||||
cp -R $(BUILD_DIR)/Monoslicer_artefacts/Release/AU/Monoslicer.component ~/Library/Audio/Plug-Ins/Components/
|
mkdir -p $(AU_DIR)
|
||||||
$(call INFO,$(C_DIM)VST3 → ~/Library/Audio/Plug-Ins/VST3$(C_RESET))
|
cp -R $(AU) $(AU_DIR)/
|
||||||
mkdir -p ~/Library/Audio/Plug-Ins/VST3
|
$(call INFO,VST3 → $(VST3_DIR))
|
||||||
cp -R $(BUILD_DIR)/Monoslicer_artefacts/Release/VST3/Monoslicer.vst3 ~/Library/Audio/Plug-Ins/VST3/
|
mkdir -p $(VST3_DIR)
|
||||||
|
cp -R $(VST3) $(VST3_DIR)/
|
||||||
$(call STEP,Rescanning Audio Units)
|
$(call STEP,Rescanning Audio Units)
|
||||||
killall -9 AudioComponentRegistrar 2>/dev/null; true
|
killall -9 AudioComponentRegistrar 2>/dev/null; true
|
||||||
@printf '\n $(C_GREEN)$(C_BOLD)✓ Done.$(C_RESET) $(C_GREEN)Monoslicer$(C_RESET)$(C_DIM) installed as AU and VST3$(C_RESET)\n\n'
|
else
|
||||||
|
$(call INFO,VST3 → $(VST3_DIR))
|
||||||
|
mkdir -p $(VST3_DIR)
|
||||||
|
cp -R $(VST3) $(VST3_DIR)/
|
||||||
|
endif
|
||||||
|
@printf '\n $(C_GREEN)$(C_BOLD)✓ Done.$(C_RESET) $(C_GREEN)Monoslicer$(C_RESET)$(C_DIM) installed as $(FORMATS)$(C_RESET)\n\n'
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
$(call STEP,Cleaning)
|
$(call STEP,Cleaning)
|
||||||
rm -rf $(BUILD_DIR)
|
rm -rf $(BUILD_DIR) $(TEST_BUILD_DIR)
|
||||||
$(call INFO,$(C_DIM)removed $(BUILD_DIR)/$(C_RESET))
|
$(call INFO,$(C_DIM)removed $(BUILD_DIR)/ and $(TEST_BUILD_DIR)/$(C_RESET))
|
||||||
@printf ' $(C_GREEN)$(C_BOLD)✓ Clean.$(C_RESET)\n'
|
@printf ' $(C_GREEN)$(C_BOLD)✓ Clean.$(C_RESET)\n'
|
||||||
|
|
||||||
|
# Tests live in their own build dir so `make` stays fast.
|
||||||
|
test:
|
||||||
|
$(call STEP,Building and running tests)
|
||||||
|
$(call INFO,$(C_DIM)separate build dir → $(TEST_BUILD_DIR)$(C_RESET))
|
||||||
|
cmake -B $(TEST_BUILD_DIR) -S . $(CMAKE_ARGS) -DMONOSLICER_BUILD_TESTS=ON > /dev/null
|
||||||
|
cmake --build $(TEST_BUILD_DIR) -j$(JOBS) --target MonoslicerDnDTest > /dev/null
|
||||||
|
$(TEST_BUILD_DIR)/MonoslicerDnDTest_artefacts/$(BUILD_TYPE)/MonoslicerDnDTest
|
||||||
|
|
|
||||||
24
README.md
24
README.md
|
|
@ -28,10 +28,15 @@ Load an audio file, slice it into regions (automatically via onset detection or
|
||||||
Requires CMake 3.22+ and a C++17 compiler.
|
Requires CMake 3.22+ and a C++17 compiler.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
make # Fetch JUCE, configure, build, and install (AU + VST3)
|
make # Fetch JUCE, configure, build
|
||||||
|
make install # Build, then copy the plugins to your system directories
|
||||||
```
|
```
|
||||||
|
|
||||||
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.
|
`make` only builds — installing to system plugin folders is opt-in via `make install`.
|
||||||
|
|
||||||
|
On macOS `make install` copies the AU to `~/Library/Audio/Plug-Ins/Components` and the VST3
|
||||||
|
to `~/Library/Audio/Plug-Ins/VST3`, then triggers an Audio Unit rescan. On Linux it copies the
|
||||||
|
VST3 bundle to `~/.vst3`.
|
||||||
|
|
||||||
Individual targets are also available:
|
Individual targets are also available:
|
||||||
|
|
||||||
|
|
@ -39,13 +44,24 @@ Individual targets are also available:
|
||||||
make fetch # Clone JUCE 8.0.9
|
make fetch # Clone JUCE 8.0.9
|
||||||
make configure # CMake configure
|
make configure # CMake configure
|
||||||
make build # Build (Release)
|
make build # Build (Release)
|
||||||
make install # Install AU + VST3 to system directories
|
make install # Build, then install (AU + VST3 on macOS, VST3 on Linux)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Tests
|
||||||
|
|
||||||
|
```sh
|
||||||
|
make test
|
||||||
|
```
|
||||||
|
|
||||||
|
Currently one suite: `Tests/DragAndDropTest.cpp` verifies that dropping an audio file
|
||||||
|
onto the editor is discovered and loaded. It uses a real native peer, so it exercises
|
||||||
|
the same code path a host does. Tests build in a separate `build-tests/` directory, so
|
||||||
|
`make` is unaffected.
|
||||||
|
|
||||||
### Clean
|
### Clean
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
make clean
|
make clean # removes build/ and build-tests/
|
||||||
```
|
```
|
||||||
|
|
||||||
## Plugin Formats
|
## Plugin Formats
|
||||||
|
|
|
||||||
|
|
@ -552,33 +552,18 @@ void MonoslicerEditor::comboBoxChanged(juce::ComboBox* comboBoxThatHasChanged)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// [EDITOR DIAG] temporary
|
|
||||||
static void ediag(const char* what, const juce::StringArray& files, int accept)
|
|
||||||
{
|
|
||||||
FILE* f = fopen("/tmp/monoslicer_drag.log", "a");
|
|
||||||
if (f == nullptr) return;
|
|
||||||
fprintf(f, "[editor] %s accept=%d files=%d", what, accept, (int)files.size());
|
|
||||||
for (const auto& x : files) fprintf(f, " \"%s\"", x.toUTF8().getAddress());
|
|
||||||
fprintf(f, "\n");
|
|
||||||
fclose(f);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool MonoslicerEditor::isInterestedInFileDrag(const juce::StringArray& files)
|
bool MonoslicerEditor::isInterestedInFileDrag(const juce::StringArray& files)
|
||||||
{
|
{
|
||||||
for (const auto& f : files)
|
for (const auto& f : files)
|
||||||
if (f.endsWithIgnoreCase(".wav") || f.endsWithIgnoreCase(".aiff") ||
|
if (f.endsWithIgnoreCase(".wav") || f.endsWithIgnoreCase(".aiff") ||
|
||||||
f.endsWithIgnoreCase(".flac") || f.endsWithIgnoreCase(".ogg"))
|
f.endsWithIgnoreCase(".flac") || f.endsWithIgnoreCase(".ogg"))
|
||||||
{
|
|
||||||
ediag("isInterested", files, 1);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
|
||||||
ediag("isInterested", files, 0);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MonoslicerEditor::fileDragEnter(const juce::StringArray& files, int, int)
|
void MonoslicerEditor::fileDragEnter(const juce::StringArray& files, int, int)
|
||||||
{
|
{
|
||||||
ediag("enter", files, isInterestedInFileDrag(files) ? 1 : 0);
|
|
||||||
if (isInterestedInFileDrag(files))
|
if (isInterestedInFileDrag(files))
|
||||||
{
|
{
|
||||||
dropOverlay.setVisible(true);
|
dropOverlay.setVisible(true);
|
||||||
|
|
@ -595,13 +580,11 @@ void MonoslicerEditor::fileDragMove(const juce::StringArray& files, int, int)
|
||||||
|
|
||||||
void MonoslicerEditor::fileDragExit(const juce::StringArray&)
|
void MonoslicerEditor::fileDragExit(const juce::StringArray&)
|
||||||
{
|
{
|
||||||
ediag("exit", juce::StringArray(), 0);
|
|
||||||
dropOverlay.setVisible(false);
|
dropOverlay.setVisible(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MonoslicerEditor::filesDropped(const juce::StringArray& files, int, int)
|
void MonoslicerEditor::filesDropped(const juce::StringArray& files, int, int)
|
||||||
{
|
{
|
||||||
ediag("drop", files, 1);
|
|
||||||
dropOverlay.setVisible(false);
|
dropOverlay.setVisible(false);
|
||||||
|
|
||||||
for (const auto& f : files)
|
for (const auto& f : files)
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,7 @@ private:
|
||||||
};
|
};
|
||||||
|
|
||||||
class MonoslicerEditor : public juce::AudioProcessorEditor,
|
class MonoslicerEditor : public juce::AudioProcessorEditor,
|
||||||
private juce::FileDragAndDropTarget,
|
public juce::FileDragAndDropTarget,
|
||||||
private juce::Button::Listener,
|
private juce::Button::Listener,
|
||||||
private juce::ComboBox::Listener,
|
private juce::ComboBox::Listener,
|
||||||
private juce::Timer
|
private juce::Timer
|
||||||
|
|
|
||||||
215
Tests/DragAndDropTest.cpp
Normal file
215
Tests/DragAndDropTest.cpp
Normal file
|
|
@ -0,0 +1,215 @@
|
||||||
|
// Regression test: dropping an audio file onto the plugin editor.
|
||||||
|
//
|
||||||
|
// Drag and drop discovery is driven entirely by RTTI. JUCE's
|
||||||
|
// ComponentPeer::DragHelpers::findDragAndDropTarget walks up the component
|
||||||
|
// chain and does dynamic_cast<FileDragAndDropTarget*> on each candidate, so the
|
||||||
|
// interface only has to be visible as a *public* base. Inheriting it privately
|
||||||
|
// makes the cast return null and the drop handlers are never called - silently,
|
||||||
|
// with no warning at build time.
|
||||||
|
//
|
||||||
|
// Build and run:
|
||||||
|
// cmake --build build --target MonoslicerDnDTest
|
||||||
|
// ./build/MonoslicerDnDTest_artefacts/Release/MonoslicerDnDTest
|
||||||
|
|
||||||
|
#include "../Source/PluginEditor.h"
|
||||||
|
#include "../Source/PluginProcessor.h"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
int failures = 0;
|
||||||
|
|
||||||
|
void check (bool condition, const juce::String& what)
|
||||||
|
{
|
||||||
|
std::cout << (condition ? " \033[32mPASS\033[0m " : " \033[1;31mFAIL\033[0m ")
|
||||||
|
<< what << std::endl;
|
||||||
|
|
||||||
|
if (!condition)
|
||||||
|
++failures;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mirrors JUCE's DragHelpers::findDragAndDropTarget (juce_ComponentPeer.cpp).
|
||||||
|
// Returns the winning *component* rather than a FileDragAndDropTarget* so that
|
||||||
|
// this compiles - and fails at runtime rather than at build time - even when the
|
||||||
|
// interface is inherited privately. That is precisely the regression being
|
||||||
|
// guarded against: JUCE casts from Component*, which is always legal.
|
||||||
|
juce::Component* findDragTargetComponent (juce::Component* c,
|
||||||
|
const juce::StringArray& files)
|
||||||
|
{
|
||||||
|
for (; c != nullptr; c = c->getParentComponent())
|
||||||
|
{
|
||||||
|
if (auto* target = dynamic_cast<juce::FileDragAndDropTarget*> (c))
|
||||||
|
if (target->isInterestedInFileDrag (files))
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mirrors the host structure: JUCE's VST3 wrapper puts the plugin editor inside a
|
||||||
|
// ContentWrapperComponent and adds *that* to the desktop
|
||||||
|
// (juce_audio_plugin_client_VST3.cpp:1996). The peer therefore knows nothing about
|
||||||
|
// the editor directly - it can only reach it by walking the component chain.
|
||||||
|
struct HostWrapper final : juce::Component
|
||||||
|
{
|
||||||
|
explicit HostWrapper (juce::Component& child)
|
||||||
|
{
|
||||||
|
addAndMakeVisible (child);
|
||||||
|
setSize (child.getWidth(), child.getHeight());
|
||||||
|
}
|
||||||
|
|
||||||
|
void paint (juce::Graphics& g) override { g.fillAll (juce::Colours::black); }
|
||||||
|
};
|
||||||
|
|
||||||
|
void dumpChain (juce::Component* c)
|
||||||
|
{
|
||||||
|
std::cout << " chain:";
|
||||||
|
|
||||||
|
for (; c != nullptr; c = c->getParentComponent())
|
||||||
|
std::cout << " \"" << juce::String (typeid (*c).name()).upToLastOccurrenceOf ("::", true, false)
|
||||||
|
<< "\""
|
||||||
|
<< (c->isVisible() ? "" : "(hidden)")
|
||||||
|
<< (dynamic_cast<juce::FileDragAndDropTarget*> (c) != nullptr ? "[fdnd]" : "")
|
||||||
|
<< " " << c->getWidth() << "x" << c->getHeight();
|
||||||
|
|
||||||
|
std::cout << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The editor scales itself via setTransform() in resized(); at the base size
|
||||||
|
// that transform is the identity, which keeps pointer maths straightforward.
|
||||||
|
constexpr int baseWidth = 920;
|
||||||
|
constexpr int baseHeight = 606;
|
||||||
|
|
||||||
|
juce::File writeTestWav()
|
||||||
|
{
|
||||||
|
auto file = juce::File::getSpecialLocation (juce::File::tempDirectory)
|
||||||
|
.getChildFile ("monoslicer_dnd_test.wav");
|
||||||
|
|
||||||
|
constexpr double sampleRate = 44100.0;
|
||||||
|
constexpr int numSamples = 4096;
|
||||||
|
|
||||||
|
juce::AudioBuffer<float> buffer (1, numSamples);
|
||||||
|
|
||||||
|
for (int i = 0; i < numSamples; ++i)
|
||||||
|
buffer.setSample (0, i, std::sin (i / 20.0f));
|
||||||
|
|
||||||
|
juce::WavAudioFormat format;
|
||||||
|
std::unique_ptr<juce::FileOutputStream> out (file.createOutputStream());
|
||||||
|
|
||||||
|
if (out == nullptr)
|
||||||
|
{
|
||||||
|
std::cout << " \033[1;31mFAIL\033[0m could not create " << file.getFullPathName() << std::endl;
|
||||||
|
++failures;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// The writer has to be destroyed before the stream it writes to.
|
||||||
|
std::unique_ptr<juce::AudioFormatWriter> writer (
|
||||||
|
format.createWriterFor (out.get(), sampleRate, 1, 16, {}, 0));
|
||||||
|
|
||||||
|
if (writer == nullptr)
|
||||||
|
{
|
||||||
|
std::cout << " \033[1;31mFAIL\033[0m could not create a WAV writer" << std::endl;
|
||||||
|
++failures;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
writer->writeFromAudioSampleBuffer (buffer, 0, numSamples);
|
||||||
|
}
|
||||||
|
|
||||||
|
out.release(); // commits and closes the file
|
||||||
|
|
||||||
|
if (file.getSize() == 0)
|
||||||
|
{
|
||||||
|
std::cout << " \033[1;31mFAIL\033[0m test WAV was written empty" << std::endl;
|
||||||
|
++failures;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
juce::ScopedJuceInitialiser_GUI juceInitialiser;
|
||||||
|
|
||||||
|
std::cout << "\n\033[1mMonoslicer - file drag and drop\033[0m\n\n";
|
||||||
|
|
||||||
|
const auto wavFile = writeTestWav();
|
||||||
|
|
||||||
|
if (!wavFile.existsAsFile())
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
const auto path = wavFile.getFullPathName();
|
||||||
|
const juce::Point<int> pointerPos (400, 300);
|
||||||
|
|
||||||
|
MonoslicerProcessor processor;
|
||||||
|
MonoslicerEditor editor (processor);
|
||||||
|
editor.setSize (baseWidth, baseHeight);
|
||||||
|
|
||||||
|
HostWrapper wrapper (editor);
|
||||||
|
wrapper.addToDesktop (0);
|
||||||
|
wrapper.setVisible (true);
|
||||||
|
|
||||||
|
auto* peer = wrapper.getPeer();
|
||||||
|
|
||||||
|
std::cout << "\n\033[1mRTTI visibility\033[0m\n";
|
||||||
|
check (dynamic_cast<juce::FileDragAndDropTarget*> (static_cast<juce::Component*> (&editor)) != nullptr,
|
||||||
|
"editor exposes FileDragAndDropTarget as a public base");
|
||||||
|
|
||||||
|
std::cout << "\n\033[1mAcceptance\033[0m\n";
|
||||||
|
check (editor.isInterestedInFileDrag ({ path }), "accepts a .wav file");
|
||||||
|
check (editor.isInterestedInFileDrag ({ "/tmp/notes.txt" }) == false, "rejects a .txt file");
|
||||||
|
|
||||||
|
std::cout << "\n\033[1mTarget discovery (as used by JUCE)\033[0m\n";
|
||||||
|
auto* underPointer = peer != nullptr ? peer->getComponent().getComponentAt (pointerPos)
|
||||||
|
: wrapper.getComponentAt (pointerPos);
|
||||||
|
|
||||||
|
if (underPointer == nullptr)
|
||||||
|
std::cout << " (no component under the pointer)" << std::endl;
|
||||||
|
|
||||||
|
dumpChain (underPointer);
|
||||||
|
|
||||||
|
check (findDragTargetComponent (underPointer, { path }) == static_cast<juce::Component*> (&editor),
|
||||||
|
"lookup from the component under the pointer finds the editor");
|
||||||
|
check (findDragTargetComponent (underPointer, { "/tmp/notes.txt" }) == nullptr,
|
||||||
|
"lookup for an unsupported file finds no target");
|
||||||
|
|
||||||
|
std::cout << "\n\033[1mEnd-to-end drop via a real native peer\033[0m\n";
|
||||||
|
|
||||||
|
if (peer != nullptr)
|
||||||
|
{
|
||||||
|
juce::ComponentPeer::DragInfo info;
|
||||||
|
info.files.add (path);
|
||||||
|
info.position = pointerPos;
|
||||||
|
|
||||||
|
check (peer->handleDragMove (info), "peer accepts the drag (handleDragMove)");
|
||||||
|
check (peer->handleDragDrop (info), "peer accepts the drop (handleDragDrop)");
|
||||||
|
|
||||||
|
// handleDragDrop() defers filesDropped() through MessageManager::callAsync
|
||||||
|
juce::MessageManager::getInstance()->runDispatchLoopUntil (250);
|
||||||
|
|
||||||
|
check (processor.getSampleBuffer().getNumSamples() == 4096,
|
||||||
|
"the dropped sample was decoded into the processor");
|
||||||
|
check (processor.getCurrentFile() == wavFile,
|
||||||
|
"the dropped file is recorded as the current file");
|
||||||
|
// Note: dropping does not auto-slice - that is a separate explicit action,
|
||||||
|
// and matches the behaviour of the "Load" button.
|
||||||
|
check (processor.getSampleRateLoaded() == 44100.0,
|
||||||
|
"the reader set the sample rate of the dropped file");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::cout << " \033[33mSKIP\033[0m no native peer available (headless session)"
|
||||||
|
<< std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "\n" << (failures == 0 ? "\033[32;1mAll checks passed.\033[0m"
|
||||||
|
: juce::String (failures) + " check(s) failed.")
|
||||||
|
<< "\n\n";
|
||||||
|
|
||||||
|
return failures == 0 ? 0 : 1;
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue