commit 8d5ffc6b56aee1ab0aa9dd47a6068f33251ff554 Author: Armin Date: Sun Jul 19 11:40:36 2026 +0200 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..eb6b14b --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +build/ +Builds/ +.idea/ +*.user +*.suo +*.vcxproj.filters +*.xcworkspace +*.xcuserdata +DerivedData/ +.DS_Store +JUCE + diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..c738a49 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,72 @@ +cmake_minimum_required(VERSION 3.22) +project(Trommelkiste VERSION 1.0.0 LANGUAGES C CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +add_subdirectory(JUCE) + +juce_add_plugin(Trommelkiste + PRODUCT_NAME "Trommelkiste" + COMPANY_NAME "Trommelkiste" + PLUGIN_MANUFACTURER_CODE D909 + PLUGIN_CODE Ds90 + FORMATS VST3 Standalone AU + IS_SYNTH TRUE + NEEDS_MIDI_INPUT TRUE + IS_MIDI_EFFECT FALSE + EDITOR_WANTS_KEYBOARD_FOCUS FALSE + COPY_PLUGIN_AFTER_BUILD TRUE +) + +target_sources(Trommelkiste PRIVATE + Source/PluginProcessor.cpp + Source/PluginEditor.cpp +) + +# Embed all WAV samples into the binary +file(GLOB SAMPLE_WAVS "Samples/*.WAV") +juce_add_binary_data(TrommelkisteBinaryData + HEADER_NAME "BinaryData.h" + NAMESPACE "BinaryData" + SOURCES ${SAMPLE_WAVS} +) + +# Generate sample manifest mapping names -> binary data pointers +set(_manifest "// Auto-generated - do not edit\n#pragma once\n") +set(_manifest "${_manifest}struct EmbeddedSample { const char* name; const char* data; int size; };\n") +set(_manifest "${_manifest}static const EmbeddedSample embeddedSamples[] = {\n") +foreach(_f ${SAMPLE_WAVS}) + get_filename_component(_we ${_f} NAME_WE) + string(REGEX REPLACE "[^A-Za-z0-9]" "_" _var ${_we}) + set(_manifest "${_manifest} { \"${_we}\", BinaryData::${_var}_WAV, BinaryData::${_var}_WAVSize },\n") +endforeach() +set(_manifest "${_manifest}};\n") +set(_manifest "${_manifest}static const int numEmbeddedSamples = sizeof(embeddedSamples) / sizeof(embeddedSamples[0]);\n") +file(WRITE "${CMAKE_BINARY_DIR}/SampleLibrary.h" "${_manifest}") + +target_include_directories(TrommelkisteBinaryData PUBLIC "${CMAKE_BINARY_DIR}") + +target_compile_definitions(Trommelkiste PRIVATE + JUCE_WEB_BROWSER=0 + JUCE_USE_CURL=0 + JUCE_VST3_CAN_REPLACE_VST2=0 + JUCE_DISPLAY_SPLASH_SCREEN=0 +) + +target_link_libraries(Trommelkiste + PRIVATE + TrommelkisteBinaryData + juce::juce_audio_basics + juce::juce_audio_devices + juce::juce_audio_formats + juce::juce_audio_processors + juce::juce_audio_utils + juce::juce_core + juce::juce_data_structures + juce::juce_events + juce::juce_graphics + juce::juce_gui_basics + PUBLIC + juce::juce_recommended_config_flags +) diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f8a8f93 --- /dev/null +++ b/Makefile @@ -0,0 +1,82 @@ +JUCE := JUCE +NPROC := $(shell sysctl -n hw.ncpu 2>/dev/null || nproc 2>/dev/null || echo 4) +UNAME_S := $(shell uname -s) + +ifeq ($(UNAME_S),Darwin) + AU_DIR := $(HOME)/Library/Audio/Plug-Ins/Components + VST3_DIR := $(HOME)/Library/Audio/Plug-Ins/VST3 + APP_DIR := /Applications +else + VST3_DIR := $(HOME)/.vst3 +endif + +BUILD_DIR := build/Trommelkiste_artefacts + +.PHONY: all clean install uninstall + +install: all + @echo "==> Installing plugins..." +ifeq ($(UNAME_S),Darwin) + @mkdir -p "$(AU_DIR)" + @for f in $$(find $(BUILD_DIR) -name "*.component" -type d); do \ + echo " $$f -> $(AU_DIR)/"; \ + rm -rf "$(AU_DIR)/$$(basename "$$f")"; \ + cp -R "$$f" "$(AU_DIR)/"; \ + done + @mkdir -p "$(VST3_DIR)" + @for f in $$(find $(BUILD_DIR) -name "*.vst3" -type d); do \ + echo " $$f -> $(VST3_DIR)/"; \ + rm -rf "$(VST3_DIR)/$$(basename "$$f")"; \ + cp -R "$$f" "$(VST3_DIR)/"; \ + done + @mkdir -p "$(APP_DIR)" + @for f in $$(find $(BUILD_DIR) -name "*.app" -type d); do \ + echo " $$f -> $(APP_DIR)/"; \ + rm -rf "$(APP_DIR)/$$(basename "$$f")"; \ + cp -R "$$f" "$(APP_DIR)/"; \ + done +else + @mkdir -p "$(VST3_DIR)" + @for f in $$(find $(BUILD_DIR) -name "*.vst3" -type d); do \ + echo " $$f -> $(VST3_DIR)/"; \ + rm -rf "$(VST3_DIR)/$$(basename "$$f")"; \ + cp -R "$$f" "$(VST3_DIR)/"; \ + done +endif + @echo "==> Done." + +all: build/Makefile + @$(MAKE) --no-print-directory -C build -j$(NPROC) + @echo "" + @echo "Build complete. Output:" + @find $(BUILD_DIR) -type d \( -name "*.vst3" -o -name "*.component" -o -name "*.app" \) 2>/dev/null | while read f; do echo " $$f"; done + +build/Makefile: CMakeLists.txt + @if [ ! -d "$(JUCE)" ]; then \ + echo "==> Cloning JUCE..."; \ + git clone --depth 1 https://github.com/juce-framework/JUCE.git $(JUCE); \ + fi + @echo "==> Configuring CMake..." + @cmake -B build -DCMAKE_BUILD_TYPE=Release + +uninstall: + @echo "==> Removing installed plugins..." +ifeq ($(UNAME_S),Darwin) + @for f in $$(find $(BUILD_DIR) -name "*.component" -type d 2>/dev/null); do \ + rm -rf "$(AU_DIR)/$$(basename "$$f")"; \ + done + @for f in $$(find $(BUILD_DIR) -name "*.vst3" -type d 2>/dev/null); do \ + rm -rf "$(VST3_DIR)/$$(basename "$$f")"; \ + done + @for f in $$(find $(BUILD_DIR) -name "*.app" -type d 2>/dev/null); do \ + rm -rf "$(APP_DIR)/$$(basename "$$f")"; \ + done +else + @for f in $$(find $(BUILD_DIR) -name "*.vst3" -type d 2>/dev/null); do \ + rm -rf "$(VST3_DIR)/$$(basename "$$f")"; \ + done +endif + @echo "==> Done." + +clean: + rm -rf build diff --git a/Samples/BT0A0A7.WAV b/Samples/BT0A0A7.WAV new file mode 100755 index 0000000..9999593 Binary files /dev/null and b/Samples/BT0A0A7.WAV differ diff --git a/Samples/BT0A0D0.WAV b/Samples/BT0A0D0.WAV new file mode 100755 index 0000000..fe9f0cc Binary files /dev/null and b/Samples/BT0A0D0.WAV differ diff --git a/Samples/BT0A0D3.WAV b/Samples/BT0A0D3.WAV new file mode 100755 index 0000000..a63598b Binary files /dev/null and b/Samples/BT0A0D3.WAV differ diff --git a/Samples/BT0A0DA.WAV b/Samples/BT0A0DA.WAV new file mode 100755 index 0000000..ca93e9c Binary files /dev/null and b/Samples/BT0A0DA.WAV differ diff --git a/Samples/BT0AAD0.WAV b/Samples/BT0AAD0.WAV new file mode 100755 index 0000000..b590548 Binary files /dev/null and b/Samples/BT0AAD0.WAV differ diff --git a/Samples/BT0AADA.WAV b/Samples/BT0AADA.WAV new file mode 100755 index 0000000..c5b407e Binary files /dev/null and b/Samples/BT0AADA.WAV differ diff --git a/Samples/BT3A0D0.WAV b/Samples/BT3A0D0.WAV new file mode 100755 index 0000000..d63d796 Binary files /dev/null and b/Samples/BT3A0D0.WAV differ diff --git a/Samples/BT3A0D3.WAV b/Samples/BT3A0D3.WAV new file mode 100755 index 0000000..08b9a1a Binary files /dev/null and b/Samples/BT3A0D3.WAV differ diff --git a/Samples/BT3A0D7.WAV b/Samples/BT3A0D7.WAV new file mode 100755 index 0000000..68e9e9a Binary files /dev/null and b/Samples/BT3A0D7.WAV differ diff --git a/Samples/BT3A0DA.WAV b/Samples/BT3A0DA.WAV new file mode 100755 index 0000000..1091abb Binary files /dev/null and b/Samples/BT3A0DA.WAV differ diff --git a/Samples/BT3AAD0.WAV b/Samples/BT3AAD0.WAV new file mode 100755 index 0000000..acb0545 Binary files /dev/null and b/Samples/BT3AAD0.WAV differ diff --git a/Samples/BT3AADA.WAV b/Samples/BT3AADA.WAV new file mode 100755 index 0000000..7e5a620 Binary files /dev/null and b/Samples/BT3AADA.WAV differ diff --git a/Samples/BT7A0D0.WAV b/Samples/BT7A0D0.WAV new file mode 100755 index 0000000..b1f3e55 Binary files /dev/null and b/Samples/BT7A0D0.WAV differ diff --git a/Samples/BT7A0D3.WAV b/Samples/BT7A0D3.WAV new file mode 100755 index 0000000..34257c5 Binary files /dev/null and b/Samples/BT7A0D3.WAV differ diff --git a/Samples/BT7A0D7.WAV b/Samples/BT7A0D7.WAV new file mode 100755 index 0000000..f500790 Binary files /dev/null and b/Samples/BT7A0D7.WAV differ diff --git a/Samples/BT7A0DA.WAV b/Samples/BT7A0DA.WAV new file mode 100755 index 0000000..58aca66 Binary files /dev/null and b/Samples/BT7A0DA.WAV differ diff --git a/Samples/BT7AAD0.WAV b/Samples/BT7AAD0.WAV new file mode 100755 index 0000000..d422fee Binary files /dev/null and b/Samples/BT7AAD0.WAV differ diff --git a/Samples/BT7AADA.WAV b/Samples/BT7AADA.WAV new file mode 100755 index 0000000..7120fcb Binary files /dev/null and b/Samples/BT7AADA.WAV differ diff --git a/Samples/BTAA0D0.WAV b/Samples/BTAA0D0.WAV new file mode 100755 index 0000000..9b55f74 Binary files /dev/null and b/Samples/BTAA0D0.WAV differ diff --git a/Samples/BTAA0D3.WAV b/Samples/BTAA0D3.WAV new file mode 100755 index 0000000..817e2a3 Binary files /dev/null and b/Samples/BTAA0D3.WAV differ diff --git a/Samples/BTAA0D7.WAV b/Samples/BTAA0D7.WAV new file mode 100755 index 0000000..0f9e3c9 Binary files /dev/null and b/Samples/BTAA0D7.WAV differ diff --git a/Samples/BTAA0DA.WAV b/Samples/BTAA0DA.WAV new file mode 100755 index 0000000..f08bc5b Binary files /dev/null and b/Samples/BTAA0DA.WAV differ diff --git a/Samples/BTAAAD0.WAV b/Samples/BTAAAD0.WAV new file mode 100755 index 0000000..6d53365 Binary files /dev/null and b/Samples/BTAAAD0.WAV differ diff --git a/Samples/BTAAADA.WAV b/Samples/BTAAADA.WAV new file mode 100755 index 0000000..60f9a80 Binary files /dev/null and b/Samples/BTAAADA.WAV differ diff --git a/Samples/CLOP1.WAV b/Samples/CLOP1.WAV new file mode 100755 index 0000000..183ddcf Binary files /dev/null and b/Samples/CLOP1.WAV differ diff --git a/Samples/CLOP2.WAV b/Samples/CLOP2.WAV new file mode 100755 index 0000000..738b25c Binary files /dev/null and b/Samples/CLOP2.WAV differ diff --git a/Samples/CLOP3.WAV b/Samples/CLOP3.WAV new file mode 100755 index 0000000..0b052cc Binary files /dev/null and b/Samples/CLOP3.WAV differ diff --git a/Samples/CLOP4.WAV b/Samples/CLOP4.WAV new file mode 100755 index 0000000..63bbf16 Binary files /dev/null and b/Samples/CLOP4.WAV differ diff --git a/Samples/CSHD0.WAV b/Samples/CSHD0.WAV new file mode 100755 index 0000000..de907d4 Binary files /dev/null and b/Samples/CSHD0.WAV differ diff --git a/Samples/CSHD2.WAV b/Samples/CSHD2.WAV new file mode 100755 index 0000000..b8a7c2d Binary files /dev/null and b/Samples/CSHD2.WAV differ diff --git a/Samples/CSHD4.WAV b/Samples/CSHD4.WAV new file mode 100755 index 0000000..eb1dcb4 Binary files /dev/null and b/Samples/CSHD4.WAV differ diff --git a/Samples/CSHD6.WAV b/Samples/CSHD6.WAV new file mode 100755 index 0000000..945331c Binary files /dev/null and b/Samples/CSHD6.WAV differ diff --git a/Samples/CSHD8.WAV b/Samples/CSHD8.WAV new file mode 100755 index 0000000..0b6880c Binary files /dev/null and b/Samples/CSHD8.WAV differ diff --git a/Samples/CSHDA.WAV b/Samples/CSHDA.WAV new file mode 100755 index 0000000..c5543cd Binary files /dev/null and b/Samples/CSHDA.WAV differ diff --git a/Samples/HANDCLP1.WAV b/Samples/HANDCLP1.WAV new file mode 100755 index 0000000..dbd729f Binary files /dev/null and b/Samples/HANDCLP1.WAV differ diff --git a/Samples/HANDCLP2.WAV b/Samples/HANDCLP2.WAV new file mode 100755 index 0000000..a5bed4e Binary files /dev/null and b/Samples/HANDCLP2.WAV differ diff --git a/Samples/HHCD0.WAV b/Samples/HHCD0.WAV new file mode 100755 index 0000000..9faa3d1 Binary files /dev/null and b/Samples/HHCD0.WAV differ diff --git a/Samples/HHCD2.WAV b/Samples/HHCD2.WAV new file mode 100755 index 0000000..127ccaa Binary files /dev/null and b/Samples/HHCD2.WAV differ diff --git a/Samples/HHCD4.WAV b/Samples/HHCD4.WAV new file mode 100755 index 0000000..e8e93ed Binary files /dev/null and b/Samples/HHCD4.WAV differ diff --git a/Samples/HHCD6.WAV b/Samples/HHCD6.WAV new file mode 100755 index 0000000..3bbd7ab Binary files /dev/null and b/Samples/HHCD6.WAV differ diff --git a/Samples/HHCD8.WAV b/Samples/HHCD8.WAV new file mode 100755 index 0000000..f83f38d Binary files /dev/null and b/Samples/HHCD8.WAV differ diff --git a/Samples/HHCDA.WAV b/Samples/HHCDA.WAV new file mode 100755 index 0000000..73f55be Binary files /dev/null and b/Samples/HHCDA.WAV differ diff --git a/Samples/HHOD0.WAV b/Samples/HHOD0.WAV new file mode 100755 index 0000000..ea9f4a5 Binary files /dev/null and b/Samples/HHOD0.WAV differ diff --git a/Samples/HHOD2.WAV b/Samples/HHOD2.WAV new file mode 100755 index 0000000..3e0f35e Binary files /dev/null and b/Samples/HHOD2.WAV differ diff --git a/Samples/HHOD4.WAV b/Samples/HHOD4.WAV new file mode 100755 index 0000000..29ed3ec Binary files /dev/null and b/Samples/HHOD4.WAV differ diff --git a/Samples/HHOD6.WAV b/Samples/HHOD6.WAV new file mode 100755 index 0000000..11ec80b Binary files /dev/null and b/Samples/HHOD6.WAV differ diff --git a/Samples/HHOD8.WAV b/Samples/HHOD8.WAV new file mode 100755 index 0000000..3a16d3c Binary files /dev/null and b/Samples/HHOD8.WAV differ diff --git a/Samples/HHODA.WAV b/Samples/HHODA.WAV new file mode 100755 index 0000000..ebf7253 Binary files /dev/null and b/Samples/HHODA.WAV differ diff --git a/Samples/HT0D0.WAV b/Samples/HT0D0.WAV new file mode 100755 index 0000000..1586745 Binary files /dev/null and b/Samples/HT0D0.WAV differ diff --git a/Samples/HT0D3.WAV b/Samples/HT0D3.WAV new file mode 100755 index 0000000..4c5bf65 Binary files /dev/null and b/Samples/HT0D3.WAV differ diff --git a/Samples/HT0D7.WAV b/Samples/HT0D7.WAV new file mode 100755 index 0000000..6015816 Binary files /dev/null and b/Samples/HT0D7.WAV differ diff --git a/Samples/HT0DA.WAV b/Samples/HT0DA.WAV new file mode 100755 index 0000000..6be588c Binary files /dev/null and b/Samples/HT0DA.WAV differ diff --git a/Samples/HT3D0.WAV b/Samples/HT3D0.WAV new file mode 100755 index 0000000..4b071bd Binary files /dev/null and b/Samples/HT3D0.WAV differ diff --git a/Samples/HT3D3.WAV b/Samples/HT3D3.WAV new file mode 100755 index 0000000..bad76e9 Binary files /dev/null and b/Samples/HT3D3.WAV differ diff --git a/Samples/HT3D7.WAV b/Samples/HT3D7.WAV new file mode 100755 index 0000000..1fb5a2e Binary files /dev/null and b/Samples/HT3D7.WAV differ diff --git a/Samples/HT3DA.WAV b/Samples/HT3DA.WAV new file mode 100755 index 0000000..0a06c9d Binary files /dev/null and b/Samples/HT3DA.WAV differ diff --git a/Samples/HT7D0.WAV b/Samples/HT7D0.WAV new file mode 100755 index 0000000..45e3f5a Binary files /dev/null and b/Samples/HT7D0.WAV differ diff --git a/Samples/HT7D3.WAV b/Samples/HT7D3.WAV new file mode 100755 index 0000000..d3c9f1e Binary files /dev/null and b/Samples/HT7D3.WAV differ diff --git a/Samples/HT7D7.WAV b/Samples/HT7D7.WAV new file mode 100755 index 0000000..c3a0cf1 Binary files /dev/null and b/Samples/HT7D7.WAV differ diff --git a/Samples/HT7DA.WAV b/Samples/HT7DA.WAV new file mode 100755 index 0000000..7f84dfe Binary files /dev/null and b/Samples/HT7DA.WAV differ diff --git a/Samples/HTAD0.WAV b/Samples/HTAD0.WAV new file mode 100755 index 0000000..d61845d Binary files /dev/null and b/Samples/HTAD0.WAV differ diff --git a/Samples/HTAD3.WAV b/Samples/HTAD3.WAV new file mode 100755 index 0000000..0bdb92a Binary files /dev/null and b/Samples/HTAD3.WAV differ diff --git a/Samples/HTAD7.WAV b/Samples/HTAD7.WAV new file mode 100755 index 0000000..d787697 Binary files /dev/null and b/Samples/HTAD7.WAV differ diff --git a/Samples/HTADA.WAV b/Samples/HTADA.WAV new file mode 100755 index 0000000..13c125b Binary files /dev/null and b/Samples/HTADA.WAV differ diff --git a/Samples/LT0D0.WAV b/Samples/LT0D0.WAV new file mode 100755 index 0000000..067522f Binary files /dev/null and b/Samples/LT0D0.WAV differ diff --git a/Samples/LT0D3.WAV b/Samples/LT0D3.WAV new file mode 100755 index 0000000..f926b08 Binary files /dev/null and b/Samples/LT0D3.WAV differ diff --git a/Samples/LT0D7.WAV b/Samples/LT0D7.WAV new file mode 100755 index 0000000..5a9ff50 Binary files /dev/null and b/Samples/LT0D7.WAV differ diff --git a/Samples/LT0DA.WAV b/Samples/LT0DA.WAV new file mode 100755 index 0000000..e3e06cf Binary files /dev/null and b/Samples/LT0DA.WAV differ diff --git a/Samples/LT3D0.WAV b/Samples/LT3D0.WAV new file mode 100755 index 0000000..3af253b Binary files /dev/null and b/Samples/LT3D0.WAV differ diff --git a/Samples/LT3D3.WAV b/Samples/LT3D3.WAV new file mode 100755 index 0000000..cae00ab Binary files /dev/null and b/Samples/LT3D3.WAV differ diff --git a/Samples/LT3D7.WAV b/Samples/LT3D7.WAV new file mode 100755 index 0000000..a522350 Binary files /dev/null and b/Samples/LT3D7.WAV differ diff --git a/Samples/LT3DA.WAV b/Samples/LT3DA.WAV new file mode 100755 index 0000000..fd61054 Binary files /dev/null and b/Samples/LT3DA.WAV differ diff --git a/Samples/LT7D0.WAV b/Samples/LT7D0.WAV new file mode 100755 index 0000000..8fa96ad Binary files /dev/null and b/Samples/LT7D0.WAV differ diff --git a/Samples/LT7D3.WAV b/Samples/LT7D3.WAV new file mode 100755 index 0000000..a50c462 Binary files /dev/null and b/Samples/LT7D3.WAV differ diff --git a/Samples/LT7D7.WAV b/Samples/LT7D7.WAV new file mode 100755 index 0000000..627a5ed Binary files /dev/null and b/Samples/LT7D7.WAV differ diff --git a/Samples/LT7DA.WAV b/Samples/LT7DA.WAV new file mode 100755 index 0000000..f2df912 Binary files /dev/null and b/Samples/LT7DA.WAV differ diff --git a/Samples/LTAD0.WAV b/Samples/LTAD0.WAV new file mode 100755 index 0000000..4d0341c Binary files /dev/null and b/Samples/LTAD0.WAV differ diff --git a/Samples/LTAD3.WAV b/Samples/LTAD3.WAV new file mode 100755 index 0000000..6342834 Binary files /dev/null and b/Samples/LTAD3.WAV differ diff --git a/Samples/LTAD7.WAV b/Samples/LTAD7.WAV new file mode 100755 index 0000000..c185a7b Binary files /dev/null and b/Samples/LTAD7.WAV differ diff --git a/Samples/LTADA.WAV b/Samples/LTADA.WAV new file mode 100755 index 0000000..a5712f5 Binary files /dev/null and b/Samples/LTADA.WAV differ diff --git a/Samples/MT0D0.WAV b/Samples/MT0D0.WAV new file mode 100755 index 0000000..98ab1ac Binary files /dev/null and b/Samples/MT0D0.WAV differ diff --git a/Samples/MT0D3.WAV b/Samples/MT0D3.WAV new file mode 100755 index 0000000..962f10e Binary files /dev/null and b/Samples/MT0D3.WAV differ diff --git a/Samples/MT0D7.WAV b/Samples/MT0D7.WAV new file mode 100755 index 0000000..f2f905f Binary files /dev/null and b/Samples/MT0D7.WAV differ diff --git a/Samples/MT0DA.WAV b/Samples/MT0DA.WAV new file mode 100755 index 0000000..942c02f Binary files /dev/null and b/Samples/MT0DA.WAV differ diff --git a/Samples/MT3D0.WAV b/Samples/MT3D0.WAV new file mode 100755 index 0000000..91e06ca Binary files /dev/null and b/Samples/MT3D0.WAV differ diff --git a/Samples/MT3D3.WAV b/Samples/MT3D3.WAV new file mode 100755 index 0000000..2d89751 Binary files /dev/null and b/Samples/MT3D3.WAV differ diff --git a/Samples/MT3D7.WAV b/Samples/MT3D7.WAV new file mode 100755 index 0000000..f11757f Binary files /dev/null and b/Samples/MT3D7.WAV differ diff --git a/Samples/MT3DA.WAV b/Samples/MT3DA.WAV new file mode 100755 index 0000000..87baa41 Binary files /dev/null and b/Samples/MT3DA.WAV differ diff --git a/Samples/MT7D0.WAV b/Samples/MT7D0.WAV new file mode 100755 index 0000000..78dd325 Binary files /dev/null and b/Samples/MT7D0.WAV differ diff --git a/Samples/MT7D3.WAV b/Samples/MT7D3.WAV new file mode 100755 index 0000000..0746167 Binary files /dev/null and b/Samples/MT7D3.WAV differ diff --git a/Samples/MT7D7.WAV b/Samples/MT7D7.WAV new file mode 100755 index 0000000..5b4e364 Binary files /dev/null and b/Samples/MT7D7.WAV differ diff --git a/Samples/MT7DA.WAV b/Samples/MT7DA.WAV new file mode 100755 index 0000000..c75d8f3 Binary files /dev/null and b/Samples/MT7DA.WAV differ diff --git a/Samples/MTAD0.WAV b/Samples/MTAD0.WAV new file mode 100755 index 0000000..1ba650e Binary files /dev/null and b/Samples/MTAD0.WAV differ diff --git a/Samples/MTAD3.WAV b/Samples/MTAD3.WAV new file mode 100755 index 0000000..8a76eee Binary files /dev/null and b/Samples/MTAD3.WAV differ diff --git a/Samples/MTAD7.WAV b/Samples/MTAD7.WAV new file mode 100755 index 0000000..7127e41 Binary files /dev/null and b/Samples/MTAD7.WAV differ diff --git a/Samples/MTADA.WAV b/Samples/MTADA.WAV new file mode 100755 index 0000000..c065f93 Binary files /dev/null and b/Samples/MTADA.WAV differ diff --git a/Samples/OPCL1.WAV b/Samples/OPCL1.WAV new file mode 100755 index 0000000..08d0e7d Binary files /dev/null and b/Samples/OPCL1.WAV differ diff --git a/Samples/OPCL2.WAV b/Samples/OPCL2.WAV new file mode 100755 index 0000000..c02cc17 Binary files /dev/null and b/Samples/OPCL2.WAV differ diff --git a/Samples/OPCL3.WAV b/Samples/OPCL3.WAV new file mode 100755 index 0000000..c105953 Binary files /dev/null and b/Samples/OPCL3.WAV differ diff --git a/Samples/OPCL4.WAV b/Samples/OPCL4.WAV new file mode 100755 index 0000000..56de56c Binary files /dev/null and b/Samples/OPCL4.WAV differ diff --git a/Samples/RIDED0.WAV b/Samples/RIDED0.WAV new file mode 100755 index 0000000..1eb93db Binary files /dev/null and b/Samples/RIDED0.WAV differ diff --git a/Samples/RIDED2.WAV b/Samples/RIDED2.WAV new file mode 100755 index 0000000..ad0d4ea Binary files /dev/null and b/Samples/RIDED2.WAV differ diff --git a/Samples/RIDED4.WAV b/Samples/RIDED4.WAV new file mode 100755 index 0000000..f00b44f Binary files /dev/null and b/Samples/RIDED4.WAV differ diff --git a/Samples/RIDED6.WAV b/Samples/RIDED6.WAV new file mode 100755 index 0000000..f00b44f Binary files /dev/null and b/Samples/RIDED6.WAV differ diff --git a/Samples/RIDED8.WAV b/Samples/RIDED8.WAV new file mode 100755 index 0000000..2f0ca74 Binary files /dev/null and b/Samples/RIDED8.WAV differ diff --git a/Samples/RIDEDA.WAV b/Samples/RIDEDA.WAV new file mode 100755 index 0000000..8e26ee3 Binary files /dev/null and b/Samples/RIDEDA.WAV differ diff --git a/Samples/RIM127.WAV b/Samples/RIM127.WAV new file mode 100755 index 0000000..782743d Binary files /dev/null and b/Samples/RIM127.WAV differ diff --git a/Samples/RIM63.WAV b/Samples/RIM63.WAV new file mode 100755 index 0000000..31f2a96 Binary files /dev/null and b/Samples/RIM63.WAV differ diff --git a/Samples/ST0T0S0.WAV b/Samples/ST0T0S0.WAV new file mode 100755 index 0000000..5b6042c Binary files /dev/null and b/Samples/ST0T0S0.WAV differ diff --git a/Samples/ST0T0S3.WAV b/Samples/ST0T0S3.WAV new file mode 100755 index 0000000..00b3836 Binary files /dev/null and b/Samples/ST0T0S3.WAV differ diff --git a/Samples/ST0T0S7.WAV b/Samples/ST0T0S7.WAV new file mode 100755 index 0000000..f905875 Binary files /dev/null and b/Samples/ST0T0S7.WAV differ diff --git a/Samples/ST0T0SA.WAV b/Samples/ST0T0SA.WAV new file mode 100755 index 0000000..6b8417e Binary files /dev/null and b/Samples/ST0T0SA.WAV differ diff --git a/Samples/ST0T3S3.WAV b/Samples/ST0T3S3.WAV new file mode 100755 index 0000000..7adc3c7 Binary files /dev/null and b/Samples/ST0T3S3.WAV differ diff --git a/Samples/ST0T3S7.WAV b/Samples/ST0T3S7.WAV new file mode 100755 index 0000000..2bc0313 Binary files /dev/null and b/Samples/ST0T3S7.WAV differ diff --git a/Samples/ST0T3SA.WAV b/Samples/ST0T3SA.WAV new file mode 100755 index 0000000..b6ef165 Binary files /dev/null and b/Samples/ST0T3SA.WAV differ diff --git a/Samples/ST0T7S3.WAV b/Samples/ST0T7S3.WAV new file mode 100755 index 0000000..31688ec Binary files /dev/null and b/Samples/ST0T7S3.WAV differ diff --git a/Samples/ST0T7S7.WAV b/Samples/ST0T7S7.WAV new file mode 100755 index 0000000..44aad1b Binary files /dev/null and b/Samples/ST0T7S7.WAV differ diff --git a/Samples/ST0T7SA.WAV b/Samples/ST0T7SA.WAV new file mode 100755 index 0000000..331b556 Binary files /dev/null and b/Samples/ST0T7SA.WAV differ diff --git a/Samples/ST0TAS3.WAV b/Samples/ST0TAS3.WAV new file mode 100755 index 0000000..2e2a02a Binary files /dev/null and b/Samples/ST0TAS3.WAV differ diff --git a/Samples/ST0TAS7.WAV b/Samples/ST0TAS7.WAV new file mode 100755 index 0000000..a903909 Binary files /dev/null and b/Samples/ST0TAS7.WAV differ diff --git a/Samples/ST0TASA.WAV b/Samples/ST0TASA.WAV new file mode 100755 index 0000000..00ab45c Binary files /dev/null and b/Samples/ST0TASA.WAV differ diff --git a/Samples/ST3T0S0.WAV b/Samples/ST3T0S0.WAV new file mode 100755 index 0000000..e429f51 Binary files /dev/null and b/Samples/ST3T0S0.WAV differ diff --git a/Samples/ST3T0S3.WAV b/Samples/ST3T0S3.WAV new file mode 100755 index 0000000..c86bda0 Binary files /dev/null and b/Samples/ST3T0S3.WAV differ diff --git a/Samples/ST3T0S7.WAV b/Samples/ST3T0S7.WAV new file mode 100755 index 0000000..bb5811e Binary files /dev/null and b/Samples/ST3T0S7.WAV differ diff --git a/Samples/ST3T0SA.WAV b/Samples/ST3T0SA.WAV new file mode 100755 index 0000000..1f40e7f Binary files /dev/null and b/Samples/ST3T0SA.WAV differ diff --git a/Samples/ST3T3S3.WAV b/Samples/ST3T3S3.WAV new file mode 100755 index 0000000..5cc25cf Binary files /dev/null and b/Samples/ST3T3S3.WAV differ diff --git a/Samples/ST3T3S7.WAV b/Samples/ST3T3S7.WAV new file mode 100755 index 0000000..6439bf9 Binary files /dev/null and b/Samples/ST3T3S7.WAV differ diff --git a/Samples/ST3T3SA.WAV b/Samples/ST3T3SA.WAV new file mode 100755 index 0000000..5a8a0a6 Binary files /dev/null and b/Samples/ST3T3SA.WAV differ diff --git a/Samples/ST3T7S3.WAV b/Samples/ST3T7S3.WAV new file mode 100755 index 0000000..70a68b3 Binary files /dev/null and b/Samples/ST3T7S3.WAV differ diff --git a/Samples/ST3T7S7.WAV b/Samples/ST3T7S7.WAV new file mode 100755 index 0000000..acd8c80 Binary files /dev/null and b/Samples/ST3T7S7.WAV differ diff --git a/Samples/ST3T7SA.WAV b/Samples/ST3T7SA.WAV new file mode 100755 index 0000000..fe51979 Binary files /dev/null and b/Samples/ST3T7SA.WAV differ diff --git a/Samples/ST3TAS3.WAV b/Samples/ST3TAS3.WAV new file mode 100755 index 0000000..89ceaaa Binary files /dev/null and b/Samples/ST3TAS3.WAV differ diff --git a/Samples/ST3TAS7.WAV b/Samples/ST3TAS7.WAV new file mode 100755 index 0000000..2adb24c Binary files /dev/null and b/Samples/ST3TAS7.WAV differ diff --git a/Samples/ST3TASA.WAV b/Samples/ST3TASA.WAV new file mode 100755 index 0000000..1248718 Binary files /dev/null and b/Samples/ST3TASA.WAV differ diff --git a/Samples/ST7T0S0.WAV b/Samples/ST7T0S0.WAV new file mode 100755 index 0000000..08205cc Binary files /dev/null and b/Samples/ST7T0S0.WAV differ diff --git a/Samples/ST7T0S3.WAV b/Samples/ST7T0S3.WAV new file mode 100755 index 0000000..e57cfc4 Binary files /dev/null and b/Samples/ST7T0S3.WAV differ diff --git a/Samples/ST7T0S7.WAV b/Samples/ST7T0S7.WAV new file mode 100755 index 0000000..b073ee9 Binary files /dev/null and b/Samples/ST7T0S7.WAV differ diff --git a/Samples/ST7T0SA.WAV b/Samples/ST7T0SA.WAV new file mode 100755 index 0000000..5487105 Binary files /dev/null and b/Samples/ST7T0SA.WAV differ diff --git a/Samples/ST7T3S3.WAV b/Samples/ST7T3S3.WAV new file mode 100755 index 0000000..e0d851a Binary files /dev/null and b/Samples/ST7T3S3.WAV differ diff --git a/Samples/ST7T3S7.WAV b/Samples/ST7T3S7.WAV new file mode 100755 index 0000000..be96019 Binary files /dev/null and b/Samples/ST7T3S7.WAV differ diff --git a/Samples/ST7T3SA.WAV b/Samples/ST7T3SA.WAV new file mode 100755 index 0000000..48a6bf9 Binary files /dev/null and b/Samples/ST7T3SA.WAV differ diff --git a/Samples/ST7T7S3.WAV b/Samples/ST7T7S3.WAV new file mode 100755 index 0000000..849bfc2 Binary files /dev/null and b/Samples/ST7T7S3.WAV differ diff --git a/Samples/ST7T7S7.WAV b/Samples/ST7T7S7.WAV new file mode 100755 index 0000000..d90ee6d Binary files /dev/null and b/Samples/ST7T7S7.WAV differ diff --git a/Samples/ST7T7SA.WAV b/Samples/ST7T7SA.WAV new file mode 100755 index 0000000..097c54b Binary files /dev/null and b/Samples/ST7T7SA.WAV differ diff --git a/Samples/ST7TAS3.WAV b/Samples/ST7TAS3.WAV new file mode 100755 index 0000000..2bee4dd Binary files /dev/null and b/Samples/ST7TAS3.WAV differ diff --git a/Samples/ST7TAS7.WAV b/Samples/ST7TAS7.WAV new file mode 100755 index 0000000..5c5947f Binary files /dev/null and b/Samples/ST7TAS7.WAV differ diff --git a/Samples/ST7TASA.WAV b/Samples/ST7TASA.WAV new file mode 100755 index 0000000..2671d6f Binary files /dev/null and b/Samples/ST7TASA.WAV differ diff --git a/Samples/STAT0S0.WAV b/Samples/STAT0S0.WAV new file mode 100755 index 0000000..475026d Binary files /dev/null and b/Samples/STAT0S0.WAV differ diff --git a/Samples/STAT0S3.WAV b/Samples/STAT0S3.WAV new file mode 100755 index 0000000..13091bc Binary files /dev/null and b/Samples/STAT0S3.WAV differ diff --git a/Samples/STAT0S7.WAV b/Samples/STAT0S7.WAV new file mode 100755 index 0000000..e0772cf Binary files /dev/null and b/Samples/STAT0S7.WAV differ diff --git a/Samples/STAT0SA.WAV b/Samples/STAT0SA.WAV new file mode 100755 index 0000000..cb76e24 Binary files /dev/null and b/Samples/STAT0SA.WAV differ diff --git a/Samples/STAT3S3.WAV b/Samples/STAT3S3.WAV new file mode 100755 index 0000000..e61c427 Binary files /dev/null and b/Samples/STAT3S3.WAV differ diff --git a/Samples/STAT3S7.WAV b/Samples/STAT3S7.WAV new file mode 100755 index 0000000..bab11ac Binary files /dev/null and b/Samples/STAT3S7.WAV differ diff --git a/Samples/STAT3SA.WAV b/Samples/STAT3SA.WAV new file mode 100755 index 0000000..065a296 Binary files /dev/null and b/Samples/STAT3SA.WAV differ diff --git a/Samples/STAT7S3.WAV b/Samples/STAT7S3.WAV new file mode 100755 index 0000000..cd84b9d Binary files /dev/null and b/Samples/STAT7S3.WAV differ diff --git a/Samples/STAT7S7.WAV b/Samples/STAT7S7.WAV new file mode 100755 index 0000000..87bac8d Binary files /dev/null and b/Samples/STAT7S7.WAV differ diff --git a/Samples/STAT7SA.WAV b/Samples/STAT7SA.WAV new file mode 100755 index 0000000..022e098 Binary files /dev/null and b/Samples/STAT7SA.WAV differ diff --git a/Samples/STATAS3.WAV b/Samples/STATAS3.WAV new file mode 100755 index 0000000..07f6625 Binary files /dev/null and b/Samples/STATAS3.WAV differ diff --git a/Samples/STATAS7.WAV b/Samples/STATAS7.WAV new file mode 100755 index 0000000..f393142 Binary files /dev/null and b/Samples/STATAS7.WAV differ diff --git a/Samples/STATASA.WAV b/Samples/STATASA.WAV new file mode 100755 index 0000000..88e41f5 Binary files /dev/null and b/Samples/STATASA.WAV differ diff --git a/Samples/TR909SET.TXT b/Samples/TR909SET.TXT new file mode 100755 index 0000000..cccb30b --- /dev/null +++ b/Samples/TR909SET.TXT @@ -0,0 +1,166 @@ +************************************************************************** +** ** +** 909909909 909909909 909909909 ** +** Roland 909 909 909 909 909 909 ** +** 909 909 909 909 909 909 Copyright ** +** TR- 909909909 909 909 909909909 1995 ** +** 909 909 909 909 ** +** 909 909 909 909 Rob Roy ** +** 909 909 909 909 Recordings ** +** 909 909909909 909 ** +** Minneapolis, USA ** +** Rhythm Composer Samples ** +** ** +************************************************************************** + +------------ +Introduction +------------ + +Since it's admittance into the world of music, the Roland TR-909 has been +one of the most sought after drum machines in existence. Every dance, +techno, and hip-hop artist desperately searches for the "magic box", only +to find the 909. Yet, this box is highly coveted by those who worship its +demonic powers. Many have tried to obtain this box through large sums of +money, deceit, and ultimately theft. + +In order to curve the population's insatiable thirst for the 909, I have +created a 'complete' 909 sample set. I'll be surprised if you can tell the +difference. + +For purists, this sample set will not totally replace a real 909. The 909 +sequencer is really natural for many dance and hip-hop artists. But +in terms of sound quality, this set accurately reproduces the various +instrument settings on the 909. In other words, it is the next best +thing to a real 909. How many drum machines in the store have a five meg +909 sample set? + +I hope you find this set useful. Please feel free to copy or distribute +the set in any way. Please DO NOT change samples, add samples, +delete samples, or modify this text file in any way. Respect the work. +This sample set is FREE. You may not distribute these samples for +profit!! If you have any comments please feel free to email me. +Or, if you create a cool tune using the samples feel free to send me a +tape or dat. Have fun! + + -Rob Roy/ April 20 '95 + +Jason Baker +Rob Roy Recordings +818 SE 8th Street #103 +Minneapolis, MN 55414 USA +email: bake0028@gold.tc.umn.edu +web: T.B.A. soon! + +** Special thanks and greets to fEEd at MANNA STUDIOS, Minneapolis, for + providing the TR-909. Check out Manna Studios at the following web + site: http://www.ivi.com/~robw + +__________________________________________________________________________ + +Sample set specifications: +-------------------------- + +160 Samples covering the full 909 kit + -24 bass drum + -52 snare drum + -16 low tom drum + -16 mid tom drum + -16 high tom drum + -2 rim shot + -2 hand clap + -6 high hat close + -6 high hat open + -6 crash cymbal + -6 rim cymbal + -4 open->close high hat combinations + -4 close->open high hat combinations + +Disk space requirement: approx. 4.8 megs unzipped + +__________________________________________________________________________ +Recording process: +------------------ + +All samples were recorded in mono at 44.1k using the following process: + +909 -------> EMU ESI-32 :::::::::::::> 486PC running Sound Forge 3.0 + mono sample dump + +The process maintained the greatest s/n ration by recording all +instruments at maximum level (except where noted). All levels on the +909 were kept at their highest setting. The samples were normalized in +Sound Forge 3.0. The sounds were recorded completely dry. +__________________________________________________________________________ + +Sample Listing Notation +----------------------- + +The 909 affords nearly an infinite number of instrument settings. In +order to make the "infinite" possible, I used a standard system for +recording each instrument's possible settings. Each instrument setting +knob on the 909 has 10 decants. I chose to use the 0, 3, 7 and 10 +settings on each knob to create most instrument variations. The sample +listings use the letter "a" to represent 10 (think of 'a' as meaning +'a'll the way). The high hat and cymbal samples use settings of 0, 2, 4, +6, 8, and 10 for the variations. + +So, for example, when I recorded the bass drum I used the four chosen +decants on three knob settings to create 24 bass drum variations. + +For the bass drum and snare drum I eliminated a few settings to reduce +"sonic redundancy". In the case of the bass drum I only used the +0 and 10 ('a') settings on the attack. For the snare drum I did not +vary the tone control while the 'snappy' setting was at 0. + +After recording and normalizing each sample, I saved the samples in their +respective instrument directories. Each sample name is composed of an +instrument letter, and knob settings specifications. + +--------------------- +Sample Identification +--------------------- + +Instrument (first letter) Settings (in order) Directory + +b bass drum t=tune, a=attack, d=decay \bassdm +s snare drum t=tune, t=tone, s=snappy \snaredm +l low tom t=tune, d=decay \lowtomdm +m mid tom t=tune, d=decay \midtomdm +h high tom t=tune, d=decay \hitomdm +rim rimshot #=velocity level \rimshot +hand handclap #=velocity level \handclap +hhc closed high hat d=decay \closedhh +hho open high hat d=decay \openhh +csh crash cymbal t=tune \crshcym +ride ride cymbal t=tune \ridecym +clop closed->open hh #=combination number \misc +opcl open->closed hh #=combination number \misc + +The best way to figure out what sounds you need is to listen to a variety +of samples. Better yet, try to create your own personalized set of 909 +samples. + +Email me at: bake0028@gold.tc.umn.edu if you have any questions! + +** 909KIT UPDATE 5/12/95 + +I cleaned a bunch of sound file headers. Apparently some .wav file have not +been loading properly on systems. The new headers contain the proper sound +data information. + + + + + + + + + + + + + + + + diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp new file mode 100644 index 0000000..4990c43 --- /dev/null +++ b/Source/PluginEditor.cpp @@ -0,0 +1,356 @@ +#include "PluginEditor.h" + +using namespace juce; + +static Font font(float h, float s = 1.0f) { return Font(FontOptions(h * s)); } +static Font fontBold(float h, float s = 1.0f) { return Font(FontOptions(h * s).withStyle("Bold")); } + +static const char* trackNames[] = {"BD", "RS", "SN", "CL", "CH", "PH", "OH", "RD", "LT", "MT"}; +static const char* noteNames[] = {"C4","C#4","D4","D#4","E4","F4","F#4","G4","G#4","A4"}; + +static constexpr int ROW_H = 52; +static constexpr int TOP = 44; +static constexpr int COL_LBL = 8; +static constexpr int COL_KNOB = 44; +static constexpr int KNOB_W = 44; +static constexpr int KNOB_GAP = 4; +static constexpr int COL_DECAY = COL_KNOB + 6 * (KNOB_W + KNOB_GAP) + 8; +static constexpr int COL_FILE = COL_DECAY + 78; + +// ── LookAndFeel ────────────────────────────────────────────────── + +void TrommelkisteLookAndFeel::drawRotarySlider(Graphics& g, int x, int y, int w, int h, + float sliderPos, float startAngle, float endAngle, + Slider&) +{ + auto bounds = Rectangle(x, y, w, h).toFloat(); + auto radius = jmin(bounds.getWidth(), bounds.getHeight()) / 2.0f - 3.0f; + auto cx = bounds.getCentreX(); + auto cy = bounds.getCentreY(); + auto angle = startAngle + sliderPos * (endAngle - startAngle); + + g.setColour(Colour(0xFF333333)); + g.fillEllipse(cx - radius, cy - radius, radius * 2.0f, radius * 2.0f); + + { + Path arc; + arc.addCentredArc(cx, cy, radius - 1.5f, radius - 1.5f, 0.0f, startAngle, angle, true); + g.setColour(accent); + g.strokePath(arc, PathStrokeType(2.5f, PathStrokeType::curved, PathStrokeType::rounded)); + } + + { + Path ptr; + ptr.addRectangle(-1.25f, -radius + 2.0f, 2.5f, radius * 0.5f); + ptr.applyTransform(AffineTransform::rotation(angle).translated(cx, cy)); + g.setColour(Colours::white); + g.fillPath(ptr); + } +} + +void TrommelkisteLookAndFeel::drawButtonBackground(Graphics& g, Button& btn, const Colour&, + bool over, bool down) +{ + auto bounds = btn.getLocalBounds().toFloat(); + Colour fill; + if (btn.getButtonText() == "X") + fill = down ? Colour(0xFFCC3333) : (over ? Colour(0xFFAA3333) : Colour(0xFF663333)); + else + fill = down ? accent.darker(0.3f) : (over ? accent.brighter(0.2f) : accent); + + g.setColour(fill); + g.fillRoundedRectangle(bounds, 3.0f); +} + +void TrommelkisteLookAndFeel::drawComboBox(Graphics& g, int w, int h, bool, + int, int, int, int, ComboBox& box) +{ + auto bounds = Rectangle(0, 0, (float)w, (float)h); + g.setColour(Colour(0xFF333333)); + g.fillRoundedRectangle(bounds, 3.0f); + g.setColour(Colour(0xFF555555)); + g.drawRoundedRectangle(bounds, 3.0f, 1.0f); + + Path arrow; + arrow.addTriangle((float)w - 13.0f, (float)h * 0.5f - 2.5f, + (float)w - 13.0f, (float)h * 0.5f + 2.5f, + (float)w - 7.0f, (float)h * 0.5f); + g.setColour(Colours::white); + g.fillPath(arrow); +} + +void TrommelkisteLookAndFeel::positionComboBoxText(ComboBox& box, Label& label) +{ + label.setBounds(6, 0, box.getWidth() - 22, box.getHeight()); + label.setFont(font(11.0f, fontScale)); +} + +// ── Editor ─────────────────────────────────────────────────────── + +TrommelkisteEditor::TrommelkisteEditor(TrommelkisteProcessor& p) + : AudioProcessorEditor(p), proc(p) +{ + setLookAndFeel(&lnf); + lnf.fontScale = 1.5f; + uiScale = 1.5f; + setSize(scaled(660), scaled(TOP + NUM_TRACKS * ROW_H + 12)); + setResizable(false, false); + setWantsKeyboardFocus(false); + + for (int i = 0; i < NUM_TRACKS; ++i) + { + auto& t = ui[i]; + const String pfx = "t" + String(i) + "_"; + + auto setupKnob = [&](Slider& s, const String& /*id*/) + { + s.setSliderStyle(Slider::RotaryHorizontalVerticalDrag); + s.setTextBoxStyle(Slider::TextBoxBelow, false, 44, 15); + s.setColour(Slider::textBoxTextColourId, Colours::white); + s.setColour(Slider::textBoxOutlineColourId, Colours::transparentBlack); + s.setColour(Slider::textBoxBackgroundColourId, Colours::transparentBlack); + addAndMakeVisible(s); + }; + + setupKnob(t.level, "level"); + setupKnob(t.length, "length"); + setupKnob(t.velocity, "velocity"); + setupKnob(t.pitch, "pitch"); + setupKnob(t.tone, "tone"); + setupKnob(t.pan, "pan"); + + t.level.formatFn = [](double v) { return String(v, 1); }; + t.length.formatFn = [](double v) { return String(v, 1) + "s"; }; + t.velocity.formatFn = [](double v) { return String(v, 1); }; + t.pitch.formatFn = [](double v) { return String(v, 1); }; + t.tone.formatFn = [](double v) -> String { + return v >= 1000.0 ? String(v / 1000.0, 1) + "k" : String((int) v); + }; + t.pan.formatFn = [](double v) -> String { + if (std::abs(v) < 0.05) return "C"; + int pct = (int) std::round(std::abs(v) * 100.0); + return (v < 0 ? "L" : "R") + String(pct); + }; + + t.levelAtt = std::make_unique(proc.apvts, pfx + "level", t.level); + t.lengthAtt = std::make_unique(proc.apvts, pfx + "length", t.length); + t.velAtt = std::make_unique(proc.apvts, pfx + "velocity", t.velocity); + t.pitchAtt = std::make_unique(proc.apvts, pfx + "pitch", t.pitch); + t.toneAtt = std::make_unique(proc.apvts, pfx + "tone", t.tone); + t.panAtt = std::make_unique(proc.apvts, pfx + "pan", t.pan); + t.panAtt = std::make_unique(proc.apvts, pfx + "pan", t.pan); + + t.decay.addItemList({"Saw", "Gate"}, 1); + t.decay.setColour(ComboBox::backgroundColourId, Colour(0xFF333333)); + t.decay.setColour(ComboBox::textColourId, Colours::white); + t.decay.setColour(ComboBox::outlineColourId, Colour(0xFF555555)); + addAndMakeVisible(t.decay); + t.decayAtt = std::make_unique(proc.apvts, pfx + "decay", t.decay); + + t.prevBtn.onClick = [this, i] { + proc.prevSample(i); + updateFileLabel(i); + }; + addAndMakeVisible(t.prevBtn); + + t.nextBtn.onClick = [this, i] { + proc.nextSample(i); + updateFileLabel(i); + }; + addAndMakeVisible(t.nextBtn); + + t.fileLabel.setFont(font(10.5f, uiScale)); + t.fileLabel.setColour(Label::textColourId, Colour(0xFF888888)); + t.fileLabel.setJustificationType(Justification::centredLeft); + t.fileLabel.setEditable(false); + t.fileLabel.addMouseListener(this, true); + addAndMakeVisible(t.fileLabel); + + updateFileLabel(i); + } + + // Preset selector + refreshPresetCombo(); + presetCombo.setColour(ComboBox::backgroundColourId, Colour(0xFF333333)); + presetCombo.setColour(ComboBox::textColourId, Colours::white); + presetCombo.setColour(ComboBox::outlineColourId, Colour(0xFF555555)); + presetCombo.onChange = [this] { + int id = presetCombo.getSelectedId(); + if (id >= 1) + { + proc.loadKit(id - 1); + for (int i = 0; i < NUM_TRACKS; ++i) + updateFileLabel(i); + } + }; + addAndMakeVisible(presetCombo); + + presetPrevBtn.onClick = [this] { + proc.prevKit(); + refreshPresetCombo(); + for (int i = 0; i < NUM_TRACKS; ++i) + updateFileLabel(i); + }; + addAndMakeVisible(presetPrevBtn); + + presetNextBtn.onClick = [this] { + proc.nextKit(); + refreshPresetCombo(); + for (int i = 0; i < NUM_TRACKS; ++i) + updateFileLabel(i); + }; + addAndMakeVisible(presetNextBtn); + + scaleCombo.addItemList({"100%", "125%", "150%", "175%", "200%"}, 1); + scaleCombo.setSelectedId(3, dontSendNotification); + scaleCombo.setColour(ComboBox::backgroundColourId, Colour(0xFF333333)); + scaleCombo.setColour(ComboBox::textColourId, Colours::white); + scaleCombo.setColour(ComboBox::outlineColourId, Colour(0xFF555555)); + scaleCombo.onChange = [this] { scaleChanged(); }; + addAndMakeVisible(scaleCombo); + + startTimerHz(30); + resized(); +} + +TrommelkisteEditor::~TrommelkisteEditor() +{ + setLookAndFeel(nullptr); +} + +void TrommelkisteEditor::scaleChanged() +{ + static const float scales[] = { 1.0f, 1.25f, 1.5f, 1.75f, 2.0f }; + int selId = scaleCombo.getSelectedId(); + if (selId >= 1 && selId <= 5) + { + uiScale = scales[selId - 1]; + lnf.fontScale = uiScale; + setSize(scaled(660), scaled(TOP + NUM_TRACKS * ROW_H + 12)); + } +} + +void TrommelkisteEditor::updateFileLabel(int idx) +{ + if (idx < 0 || idx >= NUM_TRACKS) return; + const int ci = proc.currentSampleIndex[idx]; + if (ci >= 0 && ci < proc.trackSamples[idx].size()) + ui[idx].fileLabel.setText(proc.trackSamples[idx][ci].name, + dontSendNotification); + else + ui[idx].fileLabel.setText("--", dontSendNotification); +} + +void TrommelkisteEditor::refreshPresetCombo() +{ + presetCombo.clear(dontSendNotification); + const int n = proc.getNumKits(); + for (int i = 0; i < n; ++i) + presetCombo.addItem(proc.getKitList()[i].name, i + 1); + presetCombo.setSelectedId(proc.currentKitIndex + 1, dontSendNotification); +} + +void TrommelkisteEditor::paint(Graphics& g) +{ + g.fillAll(lnf.bg); + + g.setColour(lnf.accent); + g.setFont(fontBold(16.0f, uiScale)); + g.drawText("TROMMELKISTE", scaled(10), scaled(4), scaled(400), scaled(20), Justification::centredLeft); + + g.setColour(lnf.accent.withAlpha(0.3f)); + g.fillRect(scaled(10), scaled(26), scaled(getWidth() - 20), scaled(1)); + + g.setColour(Colours::white.withAlpha(0.9f)); + g.setFont(font(9.0f, uiScale)); + g.drawText("LVL", scaled(COL_KNOB), scaled(30), scaled(KNOB_W), scaled(12), Justification::centred); + g.drawText("LEN", scaled(COL_KNOB + 1*(KNOB_W + KNOB_GAP)), scaled(30), scaled(KNOB_W), scaled(12), Justification::centred); + g.drawText("VEL", scaled(COL_KNOB + 2*(KNOB_W + KNOB_GAP)), scaled(30), scaled(KNOB_W), scaled(12), Justification::centred); + g.drawText("PIT", scaled(COL_KNOB + 3*(KNOB_W + KNOB_GAP)), scaled(30), scaled(KNOB_W), scaled(12), Justification::centred); + g.drawText("TONE", scaled(COL_KNOB + 4*(KNOB_W + KNOB_GAP)), scaled(30), scaled(KNOB_W), scaled(12), Justification::centred); + g.drawText("PAN", scaled(COL_KNOB + 5*(KNOB_W + KNOB_GAP)), scaled(30), scaled(KNOB_W), scaled(12), Justification::centred); + g.drawText("DECAY", scaled(COL_DECAY), scaled(30), scaled(68), scaled(12), Justification::centred); + g.drawText("SAMPLE", scaled(COL_FILE), scaled(30), scaled(100), scaled(12), Justification::centredLeft); + + for (int i = 0; i < NUM_TRACKS; ++i) + { + const int ry = TOP + i * ROW_H; + if (i % 2 == 1) + { + g.setColour(lnf.rowAlt); + g.fillRect(0, scaled(ry), getWidth(), scaled(ROW_H)); + } + + g.setColour(Colours::white); + g.setFont(fontBold(12.0f, uiScale)); + g.drawText(trackNames[i], scaled(COL_LBL), scaled(ry + 6), scaled(32), scaled(16), Justification::centredLeft); + + g.setColour(Colour(0xFF666666)); + g.setFont(font(9.0f, uiScale)); + g.drawText(noteNames[i], scaled(COL_LBL), scaled(ry + 24), scaled(32), scaled(14), Justification::centredLeft); + + if (proc.trackActive[i]) + { + g.setColour(lnf.accent); + g.fillRect(scaled(0), scaled(ry + 4), scaled(3), scaled(ROW_H - 8)); + } + } +} + +void TrommelkisteEditor::resized() +{ + // Header: title is painted; position controls in top row + presetPrevBtn.setBounds(scaled(440), scaled(4), scaled(20), scaled(20)); + presetCombo.setBounds(scaled(462), scaled(4), scaled(110), scaled(20)); + presetNextBtn.setBounds(scaled(574), scaled(4), scaled(20), scaled(20)); + scaleCombo.setBounds(scaled(598), scaled(4), scaled(56), scaled(20)); + + for (int i = 0; i < NUM_TRACKS; ++i) + { + const int ry = TOP + i * ROW_H; + auto& t = ui[i]; + + Slider* knobs[] = {&t.level, &t.length, &t.velocity, &t.pitch, &t.tone, &t.pan}; + for (int k = 0; k < 6; ++k) + { + int kx = COL_KNOB + k * (KNOB_W + KNOB_GAP); + knobs[k]->setBounds(scaled(kx), scaled(ry + 3), scaled(KNOB_W), scaled(ROW_H - 4)); + } + + t.decay.setBounds(scaled(COL_DECAY), scaled(ry + 16), scaled(68), scaled(22)); + + const int fileX = COL_FILE; + const int fileEnd = 660 - 8; + t.prevBtn.setBounds(scaled(fileX), scaled(ry + 16), scaled(18), scaled(22)); + t.nextBtn.setBounds(scaled(fileEnd - 18), scaled(ry + 16), scaled(18), scaled(22)); + t.fileLabel.setBounds(scaled(fileX + 20), scaled(ry + 16), + scaled(fileEnd - fileX - 38), scaled(22)); + } +} + +void TrommelkisteEditor::loadForTrack(int idx) +{ + chooser.launchAsync(FileBrowserComponent::openMode | FileBrowserComponent::canSelectFiles, + [this, idx](const FileChooser& fc) + { + auto file = fc.getResult(); + if (file.existsAsFile()) + { + proc.loadSample(idx, file); + proc.currentSampleIndex[idx] = -1; + ui[idx].fileLabel.setText(file.getFileNameWithoutExtension(), dontSendNotification); + } + }); +} + +void TrommelkisteEditor::mouseDown(const MouseEvent& event) +{ + for (int i = 0; i < NUM_TRACKS; ++i) + { + if (event.eventComponent == &ui[i].fileLabel) + { + loadForTrack(i); + return; + } + } +} diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h new file mode 100644 index 0000000..8841350 --- /dev/null +++ b/Source/PluginEditor.h @@ -0,0 +1,81 @@ +#pragma once +#include "PluginProcessor.h" +#include + +class TrommelkisteLookAndFeel : public juce::LookAndFeel_V4 +{ +public: + juce::Colour bg{0xFF1A1A1A}; + juce::Colour rowAlt{0xFF222222}; + juce::Colour accent{0xFFE8600A}; + float fontScale = 1.5f; + + void drawRotarySlider(juce::Graphics&, int x, int y, int w, int h, + float pos, float start, float end, juce::Slider&) override; + void drawButtonBackground(juce::Graphics&, juce::Button&, const juce::Colour&, + bool, bool) override; + void drawComboBox(juce::Graphics&, int w, int h, bool down, + int bx, int by, int bw, int bh, juce::ComboBox&) override; + void positionComboBoxText(juce::ComboBox&, juce::Label&) override; +}; + +struct KnobSlider : juce::Slider +{ + std::function formatFn; + juce::String getTextFromValue(double v) override + { + if (formatFn) return formatFn(v); + return Slider::getTextFromValue(v); + } +}; + +class TrommelkisteEditor : public juce::AudioProcessorEditor, private juce::Timer +{ +public: + TrommelkisteEditor(TrommelkisteProcessor&); + ~TrommelkisteEditor() override; + + void paint(juce::Graphics&) override; + void resized() override; + void mouseDown(const juce::MouseEvent&) override; + void timerCallback() override { repaint(); } + +private: + TrommelkisteProcessor& proc; + TrommelkisteLookAndFeel lnf; + + juce::ComboBox scaleCombo; + float uiScale = 1.5f; + + int scaled(int v) const { return (int)(v * uiScale + 0.5f); } + void scaleChanged(); + + struct TrackUI + { + using SliderAtt = juce::AudioProcessorValueTreeState::SliderAttachment; + using ComboAtt = juce::AudioProcessorValueTreeState::ComboBoxAttachment; + + juce::TextButton prevBtn{"<"}; + juce::TextButton nextBtn{">"}; + KnobSlider level, length, velocity, pitch, tone, pan; + juce::ComboBox decay; + juce::Label fileLabel; + + std::unique_ptr levelAtt, lengthAtt, velAtt, pitchAtt, toneAtt, panAtt; + std::unique_ptr decayAtt; + }; + + TrackUI ui[NUM_TRACKS]; + juce::FileChooser chooser{"Load Sample", juce::File{}, "*.wav;*.aif;*.aiff;*.flac"}; + + // Preset selector + juce::ComboBox presetCombo; + juce::TextButton presetPrevBtn{"<"}; + juce::TextButton presetNextBtn{">"}; + + void loadForTrack(int idx); + void updateFileLabel(int idx); + void refreshPresetCombo(); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(TrommelkisteEditor) +}; diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp new file mode 100644 index 0000000..41ad0ea --- /dev/null +++ b/Source/PluginProcessor.cpp @@ -0,0 +1,560 @@ +#include "PluginProcessor.h" +#include "PluginEditor.h" +#include +#include + +using namespace juce; + +TrommelkisteProcessor::TrommelkisteProcessor() + : AudioProcessor(BusesProperties() + .withOutput("Output", juce::AudioChannelSet::stereo(), true)), + apvts(*this, nullptr, "Parameters", createParameterLayout()) +{ + formatManager.registerBasicFormats(); + scanSamples(); + loadKit(0); +} + +TrommelkisteProcessor::~TrommelkisteProcessor() {} + +std::optional TrommelkisteProcessor::getNameForMidiNoteNumber(int note, int) +{ + static const char* names[] = {"BD", "RS", "SN", "CL", "CH", "PH", "OH", "RD", "LT", "MT"}; + int t = note - BASE_NOTE; + if (t >= 0 && t < NUM_TRACKS) + return juce::String(names[t]); + return std::nullopt; +} + +void TrommelkisteProcessor::prepareToPlay(double sr, int) +{ + currentSampleRate = sr; +} + +void TrommelkisteProcessor::releaseResources() {} + +void TrommelkisteProcessor::processBlock(juce::AudioBuffer& buffer, juce::MidiBuffer& midi) +{ + juce::ScopedNoDenormals nd; + const int numSamples = buffer.getNumSamples(); + buffer.clear(); + + for (int i = 0; i < NUM_TRACKS; ++i) + trackActive[i] = false; + + for (const auto metadata : midi) + { + auto msg = metadata.getMessage(); + if (msg.isNoteOn()) + { + int t = msg.getNoteNumber() - BASE_NOTE; + if (t >= 0 && t < NUM_TRACKS) + handleNoteOn(t, msg.getFloatVelocity()); + } + else if (msg.isNoteOff()) + { + int t = msg.getNoteNumber() - BASE_NOTE; + if (t >= 0 && t < NUM_TRACKS) + handleNoteOff(t); + } + } + + auto* outL = buffer.getWritePointer(0); + auto* outR = buffer.getNumChannels() > 1 ? buffer.getWritePointer(1) : nullptr; + + for (auto& v : voices) + { + if (!v.active) + continue; + + const int ti = v.trackIndex; + trackActive[ti] = true; + auto& track = tracks[ti]; + const juce::SpinLock::ScopedLockType sl(track.lock); + + const int bufLen = track.buffer.getNumSamples(); + if (bufLen == 0) + { + v.active = false; + continue; + } + + const juce::String p = "t" + juce::String(ti) + "_"; + const float level = apvts.getRawParameterValue(p + "level")->load(); + const float length = apvts.getRawParameterValue(p + "length")->load(); + const float velAmt = apvts.getRawParameterValue(p + "velocity")->load(); + const float pitchSemitones = apvts.getRawParameterValue(p + "pitch")->load(); + const float toneHz = apvts.getRawParameterValue(p + "tone")->load(); + const float pan = apvts.getRawParameterValue(p + "pan")->load(); + const int decayType = (int)apvts.getRawParameterValue(p + "decay")->load(); + + float pitchRatio = std::pow(2.0f, pitchSemitones / 12.0f) + * (float)track.fileSampleRate / (float)currentSampleRate; + + const float maxSamples = length * (float)currentSampleRate; + const float toneA = (toneHz < 19000.0f) + ? std::exp(-2.0f * juce::MathConstants::pi * toneHz / (float)currentSampleRate) + : 0.0f; + + const float effVel = 1.0f - velAmt + velAmt * v.noteVelocity; + const float gain = level * effVel; + + const float panAngle = (pan + 1.0f) * 0.25f * juce::MathConstants::pi; + const float panL = std::cos(panAngle) * juce::Decibels::decibelsToGain(-3.0f); + const float panR = std::sin(panAngle) * juce::Decibels::decibelsToGain(-3.0f); + + const float* sData = track.buffer.getReadPointer(0); + const bool hasR = track.buffer.getNumChannels() > 1; + const float* sDataR = hasR ? track.buffer.getReadPointer(1) : nullptr; + + for (int s = 0; s < numSamples; ++s) + { + if (!v.active) + break; + + float env = 1.0f; + if (decayType == 0) + { + env = 1.0f - (float)v.samplesPlayed / maxSamples; + if (env <= 0.0f) { v.active = false; break; } + } + else + { + if (v.samplesPlayed >= (int)maxSamples) { v.active = false; break; } + } + + int pos0 = (int)v.position; + if (pos0 >= bufLen) { v.active = false; break; } + const float frac = v.position - (float)pos0; + int pos1 = pos0 + 1; + if (pos1 >= bufLen) pos1 = bufLen - 1; + + float smplL = sData[pos0] + (sData[pos1] - sData[pos0]) * frac; + float smplR = hasR ? (sDataR[pos0] + (sDataR[pos1] - sDataR[pos0]) * frac) : smplL; + + if (toneA > 0.0f) + { + v.filterStateL = (1.0f - toneA) * smplL + toneA * v.filterStateL; + smplL = v.filterStateL; + v.filterStateR = (1.0f - toneA) * smplR + toneA * v.filterStateR; + smplR = v.filterStateR; + } + + smplL *= env * gain; + smplR *= env * gain; + + outL[s] += smplL * panL; + if (outR) + outR[s] += smplR * panR; + + v.position += pitchRatio; + ++v.samplesPlayed; + + if (v.position >= (float)bufLen) + v.active = false; + } + } +} + +int TrommelkisteProcessor::findFreeVoice(int trackIndex) +{ + for (int i = 0; i < MAX_VOICES; ++i) + if (!voices[i].active) + return i; + + int oldest = 0, bestAge = -1; + for (int i = 0; i < MAX_VOICES; ++i) + { + if (voices[i].trackIndex == trackIndex && voices[i].samplesPlayed > bestAge) + { + bestAge = voices[i].samplesPlayed; + oldest = i; + } + } + if (bestAge >= 0) + return oldest; + + oldest = 0; + bestAge = -1; + for (int i = 0; i < MAX_VOICES; ++i) + { + if (voices[i].samplesPlayed > bestAge) + { + bestAge = voices[i].samplesPlayed; + oldest = i; + } + } + return oldest; +} + +void TrommelkisteProcessor::handleNoteOn(int trackIndex, float velocity) +{ + int idx = findFreeVoice(trackIndex); + auto& v = voices[idx]; + v.active = true; + v.trackIndex = trackIndex; + v.position = 0.0f; + v.samplesPlayed = 0; + v.filterStateL = 0.0f; + v.filterStateR = 0.0f; + v.noteVelocity = velocity; +} + +void TrommelkisteProcessor::handleNoteOff(int trackIndex) +{ + for (auto& v : voices) + { + if (v.active && v.trackIndex == trackIndex) + { + const int decayType = (int)apvts.getRawParameterValue( + "t" + juce::String(trackIndex) + "_decay")->load(); + if (decayType == 1) + v.active = false; + } + } +} + +void TrommelkisteProcessor::loadSample(int trackIndex, const juce::File& file) +{ + if (trackIndex < 0 || trackIndex >= NUM_TRACKS) + return; + + std::unique_ptr reader(formatManager.createReaderFor(file)); + if (reader == nullptr) + return; + + juce::AudioBuffer newBuf(reader->numChannels, (int)reader->lengthInSamples); + reader->read(&newBuf, 0, (int)reader->lengthInSamples, 0, true, true); + + { + const juce::SpinLock::ScopedLockType sl(tracks[trackIndex].lock); + tracks[trackIndex].buffer = std::move(newBuf); + tracks[trackIndex].fileSampleRate = (int)reader->sampleRate; + tracks[trackIndex].filePath = file.getFullPathName(); + } +} + +void TrommelkisteProcessor::clearSample(int trackIndex) +{ + if (trackIndex < 0 || trackIndex >= NUM_TRACKS) + return; + const juce::SpinLock::ScopedLockType sl(tracks[trackIndex].lock); + tracks[trackIndex].buffer.setSize(1, 0); + tracks[trackIndex].filePath = ""; +} + +static void readAudioFromBuffer(juce::AudioFormatManager& fmtMgr, + juce::AudioBuffer& dest, + int& destSampleRate, + const char* data, int size) +{ + auto memStream = std::make_unique(data, size, false); + std::unique_ptr reader(fmtMgr.createReaderFor(std::move(memStream))); + if (reader == nullptr) + return; + dest.setSize((int)reader->numChannels, (int)reader->lengthInSamples); + reader->read(&dest, 0, (int)reader->lengthInSamples, 0, true, true); + destSampleRate = (int)reader->sampleRate; +} + +// ── Sample library ───────────────────────────────────────────── + +static bool matchesPrefix(const juce::String& name, const juce::String& prefix) +{ + return name.toUpperCase().startsWith(prefix.toUpperCase()); +} + +void TrommelkisteProcessor::scanSamples() +{ + static const char* prefixes[][8] = { + { "BT", nullptr }, // 0 BD + { "RIM", nullptr }, // 1 RS + { "ST", "STAT", nullptr }, // 2 SN + { "HANDCLP", nullptr }, // 3 CL + { "HHCD", nullptr }, // 4 CH + { "CLOP", nullptr }, // 5 PH + { "HHOD", nullptr }, // 6 OH + { "RIDE", nullptr }, // 7 RD + { "LT", nullptr }, // 8 LT + { "MT", nullptr }, // 9 MT + }; + + for (int t = 0; t < NUM_TRACKS; ++t) + { + trackSamples[t].clear(); + currentSampleIndex[t] = -1; + } + + for (int i = 0; i < numEmbeddedSamples; ++i) + { + const auto& es = embeddedSamples[i]; + juce::String name(es.name); + + for (int t = 0; t < NUM_TRACKS; ++t) + { + for (int p = 0; prefixes[t][p] != nullptr; ++p) + { + if (matchesPrefix(name, prefixes[t][p])) + { + SampleRef ref; + ref.name = name; + ref.data = es.data; + ref.dataSize = es.size; + trackSamples[t].add(ref); + break; + } + } + } + } + + struct SampleRefSorter + { + int compareElements(const SampleRef& a, const SampleRef& b) const { return a.name.compare(b.name); } + }; + SampleRefSorter sorter; + for (int t = 0; t < NUM_TRACKS; ++t) + trackSamples[t].sort(sorter); +} + +void TrommelkisteProcessor::loadSampleByIndex(int trackIndex, int sampleIndex) +{ + if (trackIndex < 0 || trackIndex >= NUM_TRACKS) + return; + if (sampleIndex < 0 || sampleIndex >= trackSamples[trackIndex].size()) + return; + + currentSampleIndex[trackIndex] = sampleIndex; + const auto& ref = trackSamples[trackIndex][sampleIndex]; + + if (ref.data != nullptr && ref.dataSize > 0) + { + const juce::SpinLock::ScopedLockType sl(tracks[trackIndex].lock); + readAudioFromBuffer(formatManager, tracks[trackIndex].buffer, + tracks[trackIndex].fileSampleRate, + ref.data, ref.dataSize); + tracks[trackIndex].filePath = ""; + } + else if (ref.file.existsAsFile()) + { + loadSample(trackIndex, ref.file); + } +} + +void TrommelkisteProcessor::nextSample(int trackIndex) +{ + if (trackIndex < 0 || trackIndex >= NUM_TRACKS) + return; + const int n = trackSamples[trackIndex].size(); + if (n == 0) return; + + int idx = currentSampleIndex[trackIndex] + 1; + if (idx >= n) idx = 0; + loadSampleByIndex(trackIndex, idx); +} + +void TrommelkisteProcessor::prevSample(int trackIndex) +{ + if (trackIndex < 0 || trackIndex >= NUM_TRACKS) + return; + const int n = trackSamples[trackIndex].size(); + if (n == 0) return; + + int idx = currentSampleIndex[trackIndex] - 1; + if (idx < 0) idx = n - 1; + loadSampleByIndex(trackIndex, idx); +} + +// ── Kit / Preset system ─────────────────────────────────────── + +static const TrommelkisteProcessor::Kit kits[] = { + { "Classic", { + "BT0AADA", "RIM63", "ST0T0SA", "HANDCLP2", "HHCD6", + "CLOP2", "HHOD6", "RIDED6", "LT0DA", "MT0DA" } }, + { "Punchy", { + "BT7A0D3", "RIM127", "ST7T7S3", "HANDCLP1", "HHCD2", + "CLOP1", "HHOD2", "RIDED2", "LT7D3", "MT7D3" } }, + { "Soft", { + "BT0A0D7", "RIM63", "ST0T0S7", "HANDCLP2", "HHCD8", + "CLOP4", "HHOD8", "RIDED8", "LT0D7", "MT0D7" } }, + { "Raw", { + "BTAAADA", "RIM127", "STATASA", "HANDCLP1", "HHCDA", + "CLOP3", "HHODA", "RIDEDA", "LTADA", "MTADA" } }, +}; + +const TrommelkisteProcessor::Kit* TrommelkisteProcessor::getKitList() { return kits; } +int TrommelkisteProcessor::getNumKits() const { return 4; } +juce::String TrommelkisteProcessor::getCurrentKitName() const +{ + if (currentKitIndex >= 0 && currentKitIndex < getNumKits()) + return kits[currentKitIndex].name; + return {}; +} + +void TrommelkisteProcessor::loadKit(int index) +{ + if (index < 0 || index >= getNumKits()) + return; + + currentKitIndex = index; + const auto& kit = kits[index]; + + for (int t = 0; t < NUM_TRACKS; ++t) + { + if (kit.samples[t] == nullptr || kit.samples[t][0] == '\0') + { + currentSampleIndex[t] = -1; + clearSample(t); + continue; + } + + juce::String target(kit.samples[t]); + int found = -1; + for (int s = 0; s < trackSamples[t].size(); ++s) + { + if (trackSamples[t][s].name.equalsIgnoreCase(target)) + { + found = s; + break; + } + } + + if (found >= 0) + loadSampleByIndex(t, found); + else + clearSample(t); + } +} + +void TrommelkisteProcessor::nextKit() +{ + int idx = currentKitIndex + 1; + if (idx >= getNumKits()) idx = 0; + loadKit(idx); +} + +void TrommelkisteProcessor::prevKit() +{ + int idx = currentKitIndex - 1; + if (idx < 0) idx = getNumKits() - 1; + loadKit(idx); +} + +juce::AudioProcessorEditor* TrommelkisteProcessor::createEditor() +{ + return new TrommelkisteEditor(*this); +} + +void TrommelkisteProcessor::getStateInformation(juce::MemoryBlock& destData) +{ + auto state = apvts.copyState(); + + state.setProperty("kitIndex", currentKitIndex, nullptr); + + juce::ValueTree samples("Samples"); + for (int i = 0; i < NUM_TRACKS; ++i) + { + juce::ValueTree tr("Track" + juce::String(i)); + tr.setProperty("path", tracks[i].filePath, nullptr); + tr.setProperty("sampleIndex", currentSampleIndex[i], nullptr); + if (currentSampleIndex[i] >= 0 && currentSampleIndex[i] < trackSamples[i].size()) + tr.setProperty("sampleName", trackSamples[i][currentSampleIndex[i]].name, nullptr); + samples.appendChild(tr, nullptr); + } + state.appendChild(samples, nullptr); + + std::unique_ptr xml(state.createXml()); + copyXmlToBinary(*xml, destData); +} + +void TrommelkisteProcessor::setStateInformation(const void* data, int sizeInBytes) +{ + std::unique_ptr xml(getXmlFromBinary(data, sizeInBytes)); + if (xml == nullptr) + return; + + auto state = juce::ValueTree::fromXml(*xml); + apvts.replaceState(state); + + currentKitIndex = (int)state.getProperty("kitIndex", 0); + + auto samples = state.getChildWithName("Samples"); + if (samples.isValid()) + { + for (int i = 0; i < NUM_TRACKS; ++i) + { + auto tr = samples.getChildWithName("Track" + juce::String(i)); + if (!tr.isValid()) + continue; + + auto path = tr.getProperty("path", "").toString(); + auto sampleName = tr.getProperty("sampleName", "").toString(); + + if (path.isNotEmpty()) + { + loadSample(i, juce::File(path)); + currentSampleIndex[i] = (int)tr.getProperty("sampleIndex", -1); + } + else if (sampleName.isNotEmpty()) + { + int found = -1; + for (int s = 0; s < trackSamples[i].size(); ++s) + { + if (trackSamples[i][s].name.equalsIgnoreCase(sampleName)) + { + found = s; + break; + } + } + if (found >= 0) + loadSampleByIndex(i, found); + } + } + } +} + +juce::AudioProcessorValueTreeState::ParameterLayout TrommelkisteProcessor::createParameterLayout() +{ + AudioProcessorValueTreeState::ParameterLayout layout; + const StringArray decayTypes = {"Saw", "Gate"}; + + for (int i = 0; i < NUM_TRACKS; ++i) + { + const String p = "t" + String(i) + "_"; + const String tn = "T" + String(i + 1) + " "; + + layout.add(std::make_unique( + p + "level", tn + "Level", + NormalisableRange(0.0f, 1.0f), 0.8f)); + + layout.add(std::make_unique( + p + "length", tn + "Length", + NormalisableRange(0.01f, 2.0f, 0.001f, 0.45f), 0.5f)); + + layout.add(std::make_unique( + p + "velocity", tn + "Velocity", + NormalisableRange(0.0f, 1.0f), 1.0f)); + + layout.add(std::make_unique( + p + "pitch", tn + "Pitch", + NormalisableRange(-12.0f, 12.0f, 0.1f), 0.0f)); + + layout.add(std::make_unique( + p + "tone", tn + "Tone", + NormalisableRange(20.0f, 20000.0f, 1.0f, 0.25f), 20000.0f)); + + layout.add(std::make_unique( + p + "pan", tn + "Pan", + NormalisableRange(-1.0f, 1.0f), 0.0f)); + + layout.add(std::make_unique( + p + "decay", tn + "Decay", + decayTypes, 0)); + } + + return layout; +} + +AudioProcessor* JUCE_CALLTYPE createPluginFilter() +{ + return new TrommelkisteProcessor(); +} diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h new file mode 100644 index 0000000..1eb0c67 --- /dev/null +++ b/Source/PluginProcessor.h @@ -0,0 +1,107 @@ +#pragma once +#include +#include + +static constexpr int NUM_TRACKS = 10; +static constexpr int MAX_VOICES = 32; +static constexpr int BASE_NOTE = 60; + +class TrommelkisteProcessor : public juce::AudioProcessor +{ +public: + TrommelkisteProcessor(); + ~TrommelkisteProcessor() override; + + void prepareToPlay(double sampleRate, int samplesPerBlock) override; + void releaseResources() override; + void processBlock(juce::AudioBuffer&, juce::MidiBuffer&) override; + + juce::AudioProcessorEditor* createEditor() override; + bool hasEditor() const override { return true; } + + const juce::String getName() const override { return "Trommelkiste"; } + bool acceptsMidi() const override { return true; } + bool producesMidi() const override { return false; } + bool isMidiEffect() const override { return false; } + double getTailLengthSeconds() const override { return 0.0; } + + int getNumPrograms() override { return 1; } + int getCurrentProgram() override { return 0; } + void setCurrentProgram(int) override {} + const juce::String getProgramName(int) override { return {}; } + void changeProgramName(int, const juce::String&) override {} + + void getStateInformation(juce::MemoryBlock& destData) override; + void setStateInformation(const void* data, int sizeInBytes) override; + + std::optional getNameForMidiNoteNumber(int note, int) override; + + void loadSample(int trackIndex, const juce::File& file); + void clearSample(int trackIndex); + + struct Track + { + juce::AudioBuffer buffer; + int fileSampleRate = 44100; + juce::SpinLock lock; + juce::String filePath; + }; + + Track tracks[NUM_TRACKS]; + bool trackActive[NUM_TRACKS] = {}; + juce::AudioProcessorValueTreeState apvts; + + // Sample library + struct SampleRef + { + juce::String name; + juce::File file; + const char* data = nullptr; + int dataSize = 0; + }; + + juce::Array trackSamples[NUM_TRACKS]; + int currentSampleIndex[NUM_TRACKS] = {}; + void scanSamples(); + void loadSampleByIndex(int trackIndex, int sampleIndex); + void nextSample(int trackIndex); + void prevSample(int trackIndex); + + // Kit / Preset system + struct Kit + { + juce::String name; + const char* samples[NUM_TRACKS]; + }; + + int currentKitIndex = 0; + void loadKit(int index); + void nextKit(); + void prevKit(); + int getNumKits() const; + juce::String getCurrentKitName() const; + static const Kit* getKitList(); + +private: + struct Voice + { + bool active = false; + int trackIndex = -1; + float position = 0.0f; + int samplesPlayed = 0; + float filterStateL = 0.0f; + float filterStateR = 0.0f; + float noteVelocity = 1.0f; + }; + + Voice voices[MAX_VOICES]; + juce::AudioFormatManager formatManager; + double currentSampleRate = 44100.0; + + int findFreeVoice(int trackIndex); + void handleNoteOn(int trackIndex, float velocity); + void handleNoteOff(int trackIndex); + juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout(); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(TrommelkisteProcessor) +};