mirror of
https://codeberg.org/armin/monoslicer.git
synced 2026-10-11 13:01:54 +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-tests/
|
||||
|
|
@ -90,6 +90,42 @@ target_link_libraries(Monoslicer
|
|||
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.
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
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_TAG := 8.0.9
|
||||
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_BOLD := \033[1m
|
||||
|
|
@ -25,9 +50,9 @@ COLORIZE = sed -E "$$(printf ' \
|
|||
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:
|
||||
$(call STEP,Fetching JUCE $(JUCE_TAG))
|
||||
|
|
@ -38,28 +63,42 @@ fetch: $(JUCE_DIR)/CMakeLists.txt
|
|||
|
||||
configure: fetch
|
||||
$(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)
|
||||
|
||||
build: configure
|
||||
$(call STEP,Building Monoslicer)
|
||||
$(call INFO,$(C_DIM)$(C_YELLOW)$(shell getconf _NPROCESSORS_ONLN)$(C_RESET)$(C_DIM) parallel jobs$(C_RESET))
|
||||
cmake --build $(BUILD_DIR) -j$(shell getconf _NPROCESSORS_ONLN) | $(COLORIZE)
|
||||
$(call INFO,$(C_DIM)$(C_YELLOW)$(JOBS)$(C_RESET)$(C_DIM) parallel jobs$(C_RESET))
|
||||
cmake --build $(BUILD_DIR) -j$(JOBS) | $(COLORIZE)
|
||||
|
||||
install: build
|
||||
$(call STEP,Installing plugins)
|
||||
$(call INFO,$(C_DIM)AU → ~/Library/Audio/Plug-Ins/Components$(C_RESET))
|
||||
mkdir -p ~/Library/Audio/Plug-Ins/Components
|
||||
cp -R $(BUILD_DIR)/Monoslicer_artefacts/Release/AU/Monoslicer.component ~/Library/Audio/Plug-Ins/Components/
|
||||
$(call INFO,$(C_DIM)VST3 → ~/Library/Audio/Plug-Ins/VST3$(C_RESET))
|
||||
mkdir -p ~/Library/Audio/Plug-Ins/VST3
|
||||
cp -R $(BUILD_DIR)/Monoslicer_artefacts/Release/VST3/Monoslicer.vst3 ~/Library/Audio/Plug-Ins/VST3/
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
$(call INFO,AU → $(AU_DIR))
|
||||
mkdir -p $(AU_DIR)
|
||||
cp -R $(AU) $(AU_DIR)/
|
||||
$(call INFO,VST3 → $(VST3_DIR))
|
||||
mkdir -p $(VST3_DIR)
|
||||
cp -R $(VST3) $(VST3_DIR)/
|
||||
$(call STEP,Rescanning Audio Units)
|
||||
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:
|
||||
$(call STEP,Cleaning)
|
||||
rm -rf $(BUILD_DIR)
|
||||
$(call INFO,$(C_DIM)removed $(BUILD_DIR)/$(C_RESET))
|
||||
rm -rf $(BUILD_DIR) $(TEST_BUILD_DIR)
|
||||
$(call INFO,$(C_DIM)removed $(BUILD_DIR)/ and $(TEST_BUILD_DIR)/$(C_RESET))
|
||||
@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.
|
||||
|
||||
```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:
|
||||
|
||||
|
|
@ -39,13 +44,24 @@ Individual targets are also available:
|
|||
make fetch # Clone JUCE 8.0.9
|
||||
make configure # CMake configure
|
||||
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
|
||||
|
||||
```sh
|
||||
make clean
|
||||
make clean # removes build/ and build-tests/
|
||||
```
|
||||
|
||||
## 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)
|
||||
{
|
||||
for (const auto& f : files)
|
||||
if (f.endsWithIgnoreCase(".wav") || f.endsWithIgnoreCase(".aiff") ||
|
||||
f.endsWithIgnoreCase(".flac") || f.endsWithIgnoreCase(".ogg"))
|
||||
{
|
||||
ediag("isInterested", files, 1);
|
||||
return true;
|
||||
}
|
||||
ediag("isInterested", files, 0);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void MonoslicerEditor::fileDragEnter(const juce::StringArray& files, int, int)
|
||||
{
|
||||
ediag("enter", files, isInterestedInFileDrag(files) ? 1 : 0);
|
||||
if (isInterestedInFileDrag(files))
|
||||
{
|
||||
dropOverlay.setVisible(true);
|
||||
|
|
@ -595,13 +580,11 @@ void MonoslicerEditor::fileDragMove(const juce::StringArray& files, int, int)
|
|||
|
||||
void MonoslicerEditor::fileDragExit(const juce::StringArray&)
|
||||
{
|
||||
ediag("exit", juce::StringArray(), 0);
|
||||
dropOverlay.setVisible(false);
|
||||
}
|
||||
|
||||
void MonoslicerEditor::filesDropped(const juce::StringArray& files, int, int)
|
||||
{
|
||||
ediag("drop", files, 1);
|
||||
dropOverlay.setVisible(false);
|
||||
|
||||
for (const auto& f : files)
|
||||
|
|
@ -611,7 +594,7 @@ void MonoslicerEditor::filesDropped(const juce::StringArray& files, int, int)
|
|||
{
|
||||
processorRef.loadAudioFile(file);
|
||||
waveformDisplay.setSampleBuffer(&processorRef.getSampleBuffer(), processorRef.getSampleRateLoaded());
|
||||
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ private:
|
|||
};
|
||||
|
||||
class MonoslicerEditor : public juce::AudioProcessorEditor,
|
||||
private juce::FileDragAndDropTarget,
|
||||
public juce::FileDragAndDropTarget,
|
||||
private juce::Button::Listener,
|
||||
private juce::ComboBox::Listener,
|
||||
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