diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 87e6cb5..78b9f32 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -3,7 +3,7 @@ on: push: branches: - '**' - + concurrency: group: build-${{ github.ref || github.run_id }} @@ -25,6 +25,11 @@ jobs: - uses: actions/checkout@v4 with: submodules: recursive + + - name: Install Inno Setup (Windows) + if: runner.os == 'Windows' + run: choco install innosetup -y + - name: "Run script" run: | ./ci/build.sh @@ -35,9 +40,14 @@ jobs: APPLE_PASS: ${{ secrets.APPLE_PASS }} APPLE_USER: ${{ secrets.APPLE_USER }} APIKEY: ${{ secrets.APIKEY }} + - name: Upload Artifact uses: actions/upload-artifact@v4 with: name: Binaries ${{ matrix.name }} - path: ci/bin/*.zip - retention-days: 30 \ No newline at end of file + path: | + ci/bin/*.zip + ci/bin/*.pkg + ci/bin/*.deb + ci/bin/*.exe + retention-days: 30 diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 3862267..201c6ea 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -22,6 +22,11 @@ jobs: - uses: actions/checkout@v4 with: submodules: recursive + + - name: Install Inno Setup (Windows) + if: runner.os == 'Windows' + run: choco install innosetup -y + - name: "Build" run: | ./ci/build.sh @@ -32,6 +37,7 @@ jobs: APPLE_PASS: ${{ secrets.APPLE_PASS }} APPLE_USER: ${{ secrets.APPLE_USER }} APIKEY: ${{ secrets.APIKEY }} + - name: "Upload" run: | ./ci/upload.sh @@ -42,11 +48,16 @@ jobs: APPLE_PASS: ${{ secrets.APPLE_PASS }} APPLE_USER: ${{ secrets.APPLE_USER }} APIKEY: ${{ secrets.APIKEY }} + - name: Upload Artifact uses: actions/upload-artifact@v4 with: name: Binaries ${{ matrix.name }} - path: ci/bin/*.zip + path: | + ci/bin/*.zip + ci/bin/*.pkg + ci/bin/*.deb + ci/bin/*.exe retention-days: 30 release: diff --git a/CMakeLists.txt b/CMakeLists.txt index 14eac0b..28c74a3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -90,19 +90,15 @@ endforeach() juce_add_module ("${CMAKE_CURRENT_LIST_DIR}/modules/melatonin_inspector") set_property (TARGET melatonin_inspector APPEND PROPERTY LABELS melatonin) -# Binary Data +# Binary Data (only layout.json - wavetables and presets are loaded from disk) set_property (DIRECTORY APPEND PROPERTY LABELS Assets) file (GLOB_RECURSE binary_files CONFIGURE_DEPENDS - "./plugin/Resources/layout.json" - "./plugin/Resources/WavetablesFLAC/*.wt2048" - "./plugin/Resources/Presets/*.xml") + "./plugin/Resources/layout.json") juce_add_binary_data (${PLUGIN_NAME}_Assets SOURCES ${binary_files}) -set_target_properties(${PLUGIN_NAME}_Assets PROPERTIES UNITY_BUILD ON UNITY_BUILD_MODE BATCH UNITY_BUILD_BATCH_SIZE 10) - set_property(GLOBAL PROPERTY USE_FOLDERS ON) juce_set_vst2_sdk_path (${CMAKE_SOURCE_DIR}/modules/plugin_sdk/vstsdk2.4) diff --git a/Installer/linux/build_deb.sh b/Installer/linux/build_deb.sh new file mode 100755 index 0000000..d3f98bb --- /dev/null +++ b/Installer/linux/build_deb.sh @@ -0,0 +1,97 @@ +#!/bin/bash +# Build Linux .deb package for Wavetable + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)" +VERSION="${VERSION:-1.0.0}" +PLUGIN_NAME="Wavetable" +PACKAGE_NAME="wavetable" +ARCH="amd64" + +# Paths +BIN_DIR="$ROOT_DIR/ci/bin" +DEB_ROOT="$SCRIPT_DIR/deb_root" +OUTPUT_DIR="$SCRIPT_DIR/output" + +echo "Building Wavetable $VERSION .deb package..." + +# Clean and create directories +rm -rf "$DEB_ROOT" "$OUTPUT_DIR" +mkdir -p "$DEB_ROOT" +mkdir -p "$OUTPUT_DIR" + +# Create package structure +mkdir -p "$DEB_ROOT/DEBIAN" +mkdir -p "$DEB_ROOT/usr/lib/vst" +mkdir -p "$DEB_ROOT/usr/lib/vst3" +mkdir -p "$DEB_ROOT/usr/lib/lv2" +mkdir -p "$DEB_ROOT/usr/share/SocaLabs/Wavetable/Wavetables" +mkdir -p "$DEB_ROOT/usr/share/SocaLabs/Wavetable/Presets" + +# Copy plugins +if [ -f "$BIN_DIR/vst/$PLUGIN_NAME.so" ]; then + cp "$BIN_DIR/vst/$PLUGIN_NAME.so" "$DEB_ROOT/usr/lib/vst/" +fi + +if [ -d "$BIN_DIR/vst3/$PLUGIN_NAME.vst3" ]; then + cp -R "$BIN_DIR/vst3/$PLUGIN_NAME.vst3" "$DEB_ROOT/usr/lib/vst3/" +fi + +if [ -d "$BIN_DIR/lv2/$PLUGIN_NAME.lv2" ]; then + cp -R "$BIN_DIR/lv2/$PLUGIN_NAME.lv2" "$DEB_ROOT/usr/lib/lv2/" +fi + +# Copy wavetables and presets +cp -R "$ROOT_DIR/plugin/Resources/WavetablesFLAC/"* "$DEB_ROOT/usr/share/SocaLabs/Wavetable/Wavetables/" +cp -R "$ROOT_DIR/plugin/Resources/Presets/"* "$DEB_ROOT/usr/share/SocaLabs/Wavetable/Presets/" + +# Calculate installed size (in KB) +INSTALLED_SIZE=$(du -sk "$DEB_ROOT" | cut -f1) + +# Create control file +cat > "$DEB_ROOT/DEBIAN/control" << EOF +Package: $PACKAGE_NAME +Version: $VERSION +Section: sound +Priority: optional +Architecture: $ARCH +Installed-Size: $INSTALLED_SIZE +Maintainer: SocaLabs +Homepage: https://socalabs.com +Description: Wavetable Synthesizer Plugin + Wavetable is a powerful wavetable synthesizer available as + VST2, VST3, and LV2 plugins for Linux. + . + Features: + - Dual wavetable oscillators + - Multiple filter types + - Extensive modulation matrix + - Built-in effects +EOF + +# Create postinst script +cat > "$DEB_ROOT/DEBIAN/postinst" << 'EOF' +#!/bin/bash +# Update plugin caches if available +if command -v update-mime-database &> /dev/null; then + update-mime-database /usr/share/mime || true +fi +exit 0 +EOF +chmod 755 "$DEB_ROOT/DEBIAN/postinst" + +# Set permissions +find "$DEB_ROOT" -type d -exec chmod 755 {} \; +find "$DEB_ROOT/usr" -type f -exec chmod 644 {} \; +find "$DEB_ROOT/usr/lib" -name "*.so" -exec chmod 755 {} \; + +# Build the package +DEB_FILE="$OUTPUT_DIR/${PACKAGE_NAME}_${VERSION}_${ARCH}.deb" +dpkg-deb --build "$DEB_ROOT" "$DEB_FILE" + +# Cleanup +rm -rf "$DEB_ROOT" + +echo "Done! Package created: $DEB_FILE" diff --git a/Installer/macOS/build_pkg.sh b/Installer/macOS/build_pkg.sh new file mode 100755 index 0000000..d58731c --- /dev/null +++ b/Installer/macOS/build_pkg.sh @@ -0,0 +1,124 @@ +#!/bin/bash +# Build macOS installer package for Wavetable + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)" +VERSION="${VERSION:-1.0.0}" +PLUGIN_NAME="Wavetable" + +# Paths +BIN_DIR="$ROOT_DIR/ci/bin" +PKG_ROOT="$SCRIPT_DIR/pkg_root" +OUTPUT_DIR="$SCRIPT_DIR/output" + +# Signing identities (set via environment or leave empty for unsigned) +DEV_APP_ID="${DEV_APP_ID:-}" +DEV_INST_ID="${DEV_INST_ID:-}" + +echo "Building Wavetable $VERSION installer..." + +# Clean and create directories +rm -rf "$PKG_ROOT" "$OUTPUT_DIR" +mkdir -p "$PKG_ROOT" +mkdir -p "$OUTPUT_DIR" + +# Create package root structure +mkdir -p "$PKG_ROOT/Library/Audio/Plug-Ins/VST" +mkdir -p "$PKG_ROOT/Library/Audio/Plug-Ins/VST3" +mkdir -p "$PKG_ROOT/Library/Audio/Plug-Ins/Components" +mkdir -p "$PKG_ROOT/Library/Application Support/SocaLabs/Wavetable/Wavetables" +mkdir -p "$PKG_ROOT/Library/Application Support/SocaLabs/Wavetable/Presets" + +# Copy plugins +if [ -d "$BIN_DIR/vst/$PLUGIN_NAME.vst" ]; then + cp -R "$BIN_DIR/vst/$PLUGIN_NAME.vst" "$PKG_ROOT/Library/Audio/Plug-Ins/VST/" +fi + +if [ -d "$BIN_DIR/vst3/$PLUGIN_NAME.vst3" ]; then + cp -R "$BIN_DIR/vst3/$PLUGIN_NAME.vst3" "$PKG_ROOT/Library/Audio/Plug-Ins/VST3/" +fi + +if [ -d "$BIN_DIR/au/$PLUGIN_NAME.component" ]; then + cp -R "$BIN_DIR/au/$PLUGIN_NAME.component" "$PKG_ROOT/Library/Audio/Plug-Ins/Components/" +fi + +# Copy wavetables and presets +cp -R "$ROOT_DIR/plugin/Resources/WavetablesFLAC/"* "$PKG_ROOT/Library/Application Support/SocaLabs/Wavetable/Wavetables/" +cp -R "$ROOT_DIR/plugin/Resources/Presets/"* "$PKG_ROOT/Library/Application Support/SocaLabs/Wavetable/Presets/" + +# Set permissions +chmod -R 755 "$PKG_ROOT/Library/Audio" +chmod -R 755 "$PKG_ROOT/Library/Application Support" + +# Build component package +pkgbuild --root "$PKG_ROOT" \ + --identifier "com.socalabs.wavetable.pkg" \ + --version "$VERSION" \ + --install-location "/" \ + "$OUTPUT_DIR/Wavetable-component.pkg" + +# Create distribution.xml +cat > "$OUTPUT_DIR/distribution.xml" << EOF + + + Wavetable $VERSION + com.socalabs + + + + + + + + + + + + + Wavetable-component.pkg + +EOF + +# Create welcome text +cat > "$OUTPUT_DIR/welcome.txt" << EOF +Welcome to the Wavetable $VERSION installer. + +This will install: +- Wavetable VST2 plugin +- Wavetable VST3 plugin +- Wavetable AU plugin +- Factory wavetables +- Factory presets + +Click Continue to proceed with the installation. +EOF + +# Build product archive +UNSIGNED_PKG="$OUTPUT_DIR/${PLUGIN_NAME}_${VERSION}_Mac_unsigned.pkg" +SIGNED_PKG="$OUTPUT_DIR/${PLUGIN_NAME}_${VERSION}_Mac.pkg" + +productbuild --distribution "$OUTPUT_DIR/distribution.xml" \ + --package-path "$OUTPUT_DIR" \ + --resources "$OUTPUT_DIR" \ + "$UNSIGNED_PKG" + +# Sign if identity is available +if [ -n "$DEV_INST_ID" ]; then + echo "Signing installer..." + productsign --sign "$DEV_INST_ID" "$UNSIGNED_PKG" "$SIGNED_PKG" + rm "$UNSIGNED_PKG" + echo "Signed installer: $SIGNED_PKG" +else + mv "$UNSIGNED_PKG" "$SIGNED_PKG" + echo "Unsigned installer: $SIGNED_PKG" +fi + +# Cleanup +rm -rf "$PKG_ROOT" +rm -f "$OUTPUT_DIR/Wavetable-component.pkg" +rm -f "$OUTPUT_DIR/distribution.xml" +rm -f "$OUTPUT_DIR/welcome.txt" + +echo "Done! Installer created: $SIGNED_PKG" diff --git a/Installer/win/Wavetable.iss b/Installer/win/Wavetable.iss new file mode 100644 index 0000000..112cab1 --- /dev/null +++ b/Installer/win/Wavetable.iss @@ -0,0 +1,74 @@ +; Wavetable Installer Script for Inno Setup + +#define MyAppName "Wavetable" +#define MyAppCompany "SocaLabs" +#define MyAppPublisher "SocaLabs" +#define MyAppURL "https://socalabs.com" +#define MyAppVersion GetEnv('VERSION') +#if MyAppVersion == "" +#define MyAppVersion "1.0.0" +#endif + +[Setup] +AppID={{B8F4E3A2-9C7D-4B5E-8A1F-6D2E3C4B5A6F} +AppName={#MyAppCompany} {#MyAppName} +AppVerName={#MyAppCompany} {#MyAppName} {#MyAppVersion} +AppVersion={#MyAppVersion} +AppPublisher={#MyAppPublisher} +AppPublisherURL={#MyAppURL} +AppSupportURL={#MyAppURL} +AppUpdatesURL={#MyAppURL} +DefaultDirName={commonpf64}\VstPlugins +DisableProgramGroupPage=yes +OutputDir=.\output +OutputBaseFilename=Wavetable_{#MyAppVersion}_Win +Compression=lzma/ultra +SolidCompression=true +ShowLanguageDialog=auto +InternalCompressLevel=ultra +MinVersion=6.1.7600 +FlatComponentsList=false +AppendDefaultDirName=false +AlwaysShowDirOnReadyPage=yes +DirExistsWarning=no +DisableDirPage=no +DisableWelcomePage=False +DisableReadyPage=no +ArchitecturesAllowed=x64compatible +ArchitecturesInstallIn64BitMode=x64compatible +VersionInfoVersion={#MyAppVersion} +VersionInfoCompany={#MyAppPublisher} +VersionInfoProductName={#MyAppCompany} {#MyAppName} +VersionInfoProductVersion={#MyAppVersion} +UsePreviousGroup=False +Uninstallable=yes +UninstallDisplayName={#MyAppCompany} {#MyAppName} + +[Languages] +Name: english; MessagesFile: compiler:Default.isl + +[Types] +Name: "full"; Description: "Full installation" +Name: "custom"; Description: "Custom installation"; Flags: iscustom + +[Components] +Name: "vst2"; Description: "VST2 Plugin"; Types: full +Name: "vst3"; Description: "VST3 Plugin"; Types: full +Name: "data"; Description: "Wavetables and Presets"; Types: full; Flags: fixed + +[Files] +; VST2 Plugin +Source: "bin\vst\Wavetable.dll"; DestDir: "{app}"; Components: vst2; Flags: ignoreversion + +; VST3 Plugin +Source: "bin\vst3\Wavetable.vst3\*"; DestDir: "{commoncf64}\VST3\Wavetable.vst3"; Components: vst3; Flags: ignoreversion recursesubdirs + +; Wavetables +Source: "bin\Wavetables\*"; DestDir: "{commonappdata}\SocaLabs\Wavetable\Wavetables"; Components: data; Flags: ignoreversion recursesubdirs + +; Presets +Source: "bin\Presets\*"; DestDir: "{commonappdata}\SocaLabs\Wavetable\Presets"; Components: data; Flags: ignoreversion recursesubdirs + +[InstallDelete] +; Clean up old VST3 installation +Type: filesandordirs; Name: "{commoncf64}\VST3\Wavetable.vst3" diff --git a/ci/build.sh b/ci/build.sh index c6c5221..6bb0653 100755 --- a/ci/build.sh +++ b/ci/build.sh @@ -2,6 +2,7 @@ set +x PLUGIN="Wavetable" +VERSION=$(cat VERSION) # linux specific stiff if [ "$(expr substr $(uname -s) 1 5)" == "Linux" ]; then @@ -83,10 +84,22 @@ if [ "$(uname)" == "Darwin" ]; then echo "Not notarizing" fi + # Build installer package + echo "Building macOS installer..." + export VERSION + export DEV_INST_ID + "$ROOT/Installer/macOS/build_pkg.sh" + + # Copy installer to bin + cp "$ROOT/Installer/macOS/output/${PLUGIN}_${VERSION}_Mac.pkg" "$ROOT/ci/bin/" + + # Also create zip of plugins for backwards compatibility + cd "$ROOT/ci/bin" zip -r ${PLUGIN}_Mac.zip vst/$PLUGIN.vst vst3/$PLUGIN.vst3 au/$PLUGIN.component if [ "$BRANCH" = "release" ]; then curl -F "files=@${PLUGIN}_Mac.zip" "https://socalabs.com/files/set.php?key=$APIKEY" + curl -F "files=@${PLUGIN}_${VERSION}_Mac.pkg" "https://socalabs.com/files/set.php?key=$APIKEY" fi # Build linux version elif [ "$(expr substr $(uname -s) 1 5)" == "Linux" ]; then @@ -110,12 +123,21 @@ elif [ "$(expr substr $(uname -s) 1 5)" == "Linux" ]; then strip vst3/$PLUGIN.vst3/Contents/x86_64-linux/$PLUGIN.so strip lv2/$PLUGIN.lv2/lib$PLUGIN.so - # Upload + # Build .deb package + echo "Building Linux .deb package..." + export VERSION + "$ROOT/Installer/linux/build_deb.sh" + + # Copy deb to bin + cp "$ROOT/Installer/linux/output/"*.deb "$ROOT/ci/bin/" + + # Also create zip for backwards compatibility cd "$ROOT/ci/bin" zip -r ${PLUGIN}_Linux.zip vst/$PLUGIN.so vst3/$PLUGIN.vst3 lv2/$PLUGIN.lv2 if [ "$BRANCH" = "release" ]; then curl -F "files=@${PLUGIN}_Linux.zip" "https://socalabs.com/files/set.php?key=$APIKEY" + curl -F "files=@wavetable_${VERSION}_amd64.deb" "https://socalabs.com/files/set.php?key=$APIKEY" fi # Build Win version elif [ "$(expr substr $(uname -s) 1 10)" == "MINGW64_NT" ]; then @@ -126,14 +148,44 @@ elif [ "$(expr substr $(uname -s) 1 10)" == "MINGW64_NT" ]; then mkdir -p "$ROOT/ci/bin/vst" mkdir -p "$ROOT/ci/bin/vst3" + mkdir -p "$ROOT/ci/bin/Wavetables" + mkdir -p "$ROOT/ci/bin/Presets" cp -R "$ROOT/Builds/vs/${PLUGIN}_artefacts/Release/VST/$PLUGIN.dll" "$ROOT/ci/bin/vst" cp -R "$ROOT/Builds/vs/${PLUGIN}_artefacts/Release/VST3/$PLUGIN.vst3" "$ROOT/ci/bin/vst3" + # Copy assets for installer + cp -R "$ROOT/plugin/Resources/WavetablesFLAC/"* "$ROOT/ci/bin/Wavetables/" + cp -R "$ROOT/plugin/Resources/Presets/"* "$ROOT/ci/bin/Presets/" + + # Build installer with Inno Setup + echo "Building Windows installer..." + cd "$ROOT/Installer/win" + mkdir -p bin + cp -R "$ROOT/ci/bin/vst" bin/ + cp -R "$ROOT/ci/bin/vst3" bin/ + cp -R "$ROOT/ci/bin/Wavetables" bin/ + cp -R "$ROOT/ci/bin/Presets" bin/ + mkdir -p output + + # Set version for Inno Setup + export VERSION + + # Run Inno Setup (iscc is the command-line compiler) + iscc "Wavetable.iss" + + # Copy installer to ci/bin + cp output/*.exe "$ROOT/ci/bin/" + + # Cleanup + rm -rf bin output + + # Also create zip for backwards compatibility cd "$ROOT/ci/bin" 7z a ${PLUGIN}_Win.zip vst/$PLUGIN.dll vst3/$PLUGIN.vst3 if [ "$BRANCH" = "release" ]; then curl -F "files=@${PLUGIN}_Win.zip" "https://socalabs.com/files/set.php?key=$APIKEY" + curl -F "files=@${PLUGIN}_${VERSION}_Win.exe" "https://socalabs.com/files/set.php?key=$APIKEY" fi fi diff --git a/copy_assets.sh b/copy_assets.sh new file mode 100755 index 0000000..05c99f6 --- /dev/null +++ b/copy_assets.sh @@ -0,0 +1,43 @@ +#!/bin/bash + +# Copy Wavetable assets to system locations for local testing +# Run with: sudo ./copy_assets.sh + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +if [[ "$OSTYPE" == "darwin"* ]]; then + # macOS + DEST_DIR="/Library/Application Support/SocaLabs/Wavetable" +elif [[ "$OSTYPE" == "linux-gnu"* ]]; then + # Linux + DEST_DIR="/usr/share/SocaLabs/Wavetable" +elif [[ "$OSTYPE" == "msys" || "$OSTYPE" == "cygwin" ]]; then + # Windows (Git Bash / Cygwin) + DEST_DIR="/c/ProgramData/SocaLabs/Wavetable" +else + echo "Unsupported OS: $OSTYPE" + exit 1 +fi + +echo "Installing assets to: $DEST_DIR" + +# Create directories +mkdir -p "$DEST_DIR/Wavetables" +mkdir -p "$DEST_DIR/Presets" + +# Copy wavetables (preserving subdirectory structure) +echo "Copying wavetables..." +cp -R "$SCRIPT_DIR/plugin/Resources/WavetablesFLAC/"* "$DEST_DIR/Wavetables/" + +# Copy presets (preserving subdirectory structure) +echo "Copying presets..." +cp -R "$SCRIPT_DIR/plugin/Resources/Presets/"* "$DEST_DIR/Presets/" + +# Set permissions so all users can read +chmod -R a+rX "$DEST_DIR" + +echo "Done! Assets installed to:" +echo " Wavetables: $DEST_DIR/Wavetables" +echo " Presets: $DEST_DIR/Presets" diff --git a/plugin/Source/Panels.h b/plugin/Source/Panels.h index 30c91ab..d4bdd1c 100644 --- a/plugin/Source/Panels.h +++ b/plugin/Source/Panels.h @@ -143,12 +143,7 @@ public: } else if (e.originalComponent == &h && e.mouseWasClicked() && e.x >= prevButton.getRight() && e.x <= nextButton.getX()) { - juce::StringArray tables; - for (auto i = 0; i < BinaryData::namedResourceListSize; i++) - if (juce::String (BinaryData::originalFilenames[i]).endsWith (".wt2048")) - tables.add (juce::String (BinaryData::originalFilenames[i]).upToLastOccurrenceOf (".wt2048", false, false)); - - tables.sortNatural(); + juce::StringArray tables = proc.getWavetableList(); std::map menus; diff --git a/plugin/Source/PluginProcessor.cpp b/plugin/Source/PluginProcessor.cpp index 5432539..3ca06b5 100644 --- a/plugin/Source/PluginProcessor.cpp +++ b/plugin/Source/PluginProcessor.cpp @@ -520,14 +520,6 @@ WavetableAudioProcessor::WavetableAudioProcessor() fireAmp (FXBaseCallback ([this] { return gin::Processor::getSampleRate(); })), grindAmp (FXBaseCallback ([this] { return gin::Processor::getSampleRate(); })) { - { - auto sz = 0; - for (auto i = 0; i < BinaryData::namedResourceListSize; i++) - if (juce::String (BinaryData::originalFilenames[i]).endsWith (".xml")) - if (auto data = BinaryData::getNamedResource (BinaryData::namedResourceList[i], sz)) - extractProgram (BinaryData::originalFilenames[i], data, sz); - } - mtsClient = MTS_RegisterClient(); enableLegacyMode(); setVoiceStealingEnabled (true); @@ -592,13 +584,18 @@ void WavetableAudioProcessor::reloadWavetables() { auto loadMemory = [&] (const juce::String& name) -> juce::MemoryBlock { - for (auto i = 0; i < BinaryData::namedResourceListSize; i++) + // Load wavetable from file system + juce::File wavetableDir = getFactoryWavetableDirectory(); + juce::Array files; + wavetableDir.findChildFiles (files, juce::File::findFiles, true, "*.wt2048"); + + for (auto& f : files) { - if ((name + ".wt2048").equalsIgnoreCase (BinaryData::originalFilenames[i])) + if (f.getFileNameWithoutExtension().equalsIgnoreCase (name)) { - int sz = 0; - if (auto data = BinaryData::getNamedResource (BinaryData::namedResourceList[i], sz)) - return juce::MemoryBlock (data, sz); + juce::MemoryBlock mb; + f.loadFileAsData (mb); + return mb; } } return {}; @@ -651,12 +648,7 @@ void WavetableAudioProcessor::incWavetable (int osc, int delta) else userTable2.reset(); - juce::StringArray tables; - for (auto i = 0; i < BinaryData::namedResourceListSize; i++) - if (juce::String (BinaryData::originalFilenames[i]).endsWith (".wt2048")) - tables.add (juce::String (BinaryData::originalFilenames[i]).upToLastOccurrenceOf (".wt2048", false, false)); - - tables.sortNatural(); + juce::StringArray tables = getWavetableList(); auto idx = tables.indexOf (table.toString()); @@ -689,6 +681,159 @@ bool WavetableAudioProcessor::loadUserWavetable (int osc, const juce::File& f, i return false; } +//============================================================================== +juce::File WavetableAudioProcessor::getFactoryWavetableDirectory() +{ + #if JUCE_MAC + juce::File dir = juce::File::getSpecialLocation (juce::File::commonApplicationDataDirectory).getChildFile ("Application Support/SocaLabs/Wavetable/Wavetables"); + #else + juce::File dir = juce::File::getSpecialLocation (juce::File::commonApplicationDataDirectory).getChildFile ("SocaLabs/Wavetable/Wavetables"); + #endif + return dir; +} + +juce::File WavetableAudioProcessor::getFactoryPresetDirectory() +{ + #if JUCE_MAC + juce::File dir = juce::File::getSpecialLocation (juce::File::commonApplicationDataDirectory).getChildFile ("Application Support/SocaLabs/Wavetable/Presets"); + #else + juce::File dir = juce::File::getSpecialLocation (juce::File::commonApplicationDataDirectory).getChildFile ("SocaLabs/Wavetable/Presets"); + #endif + return dir; +} + +juce::File WavetableAudioProcessor::getProgramDirectory() +{ + #if JUCE_MAC + juce::File dir = juce::File::getSpecialLocation (juce::File::userApplicationDataDirectory).getChildFile ("Application Support/SocaLabs/Wavetable/UserPresets"); + #else + juce::File dir = juce::File::getSpecialLocation (juce::File::userApplicationDataDirectory).getChildFile ("SocaLabs/Wavetable/UserPresets"); + #endif + + if (! dir.isDirectory()) + dir.createDirectory(); + + return dir; +} + +juce::File WavetableAudioProcessor::getLegacyProgramDirectory() +{ + #if JUCE_MAC + juce::File dir = juce::File::getSpecialLocation (juce::File::userApplicationDataDirectory).getChildFile ("Application Support/" + processorOptions.devId + "/" + processorOptions.pluginName + "/programs"); + #else + juce::File dir = juce::File::getSpecialLocation (juce::File::userApplicationDataDirectory).getChildFile (processorOptions.devId + "/" + processorOptions.pluginName + "/programs"); + #endif + return dir; +} + +void WavetableAudioProcessor::migrateUserPresets() +{ + juce::File legacyDir = getLegacyProgramDirectory(); + if (! legacyDir.isDirectory()) + return; + + // Build a set of factory preset filenames to ignore + juce::StringArray factoryPresetNames; + juce::File factoryDir = getFactoryPresetDirectory(); + if (factoryDir.isDirectory()) + { + juce::Array factoryFiles; + factoryDir.findChildFiles (factoryFiles, juce::File::findFiles, true, "*.xml"); + for (auto& f : factoryFiles) + factoryPresetNames.add (f.getFileName().toLowerCase()); + } + + // Find all presets in legacy directory + juce::Array legacyFiles; + legacyDir.findChildFiles (legacyFiles, juce::File::findFiles, false, "*.xml"); + + juce::File userDir = getProgramDirectory(); + + for (auto& f : legacyFiles) + { + // Skip if this is a factory preset + if (factoryPresetNames.contains (f.getFileName().toLowerCase())) + continue; + + // Skip if already exists in new location + juce::File destFile = userDir.getChildFile (f.getFileName()); + if (destFile.existsAsFile()) + continue; + + // Copy to new location + f.copyFileTo (destFile); + } +} + +void WavetableAudioProcessor::loadAllPrograms() +{ + lastProgramsUpdated = juce::Time::getCurrentTime(); + + updateState(); + + programs.clear(); + + // Migrate user presets from legacy location (one-time operation) + migrateUserPresets(); + + // Load factory presets from commonAppData + juce::File factoryDir = getFactoryPresetDirectory(); + if (factoryDir.isDirectory()) + { + juce::Array factoryFiles; + factoryDir.findChildFiles (factoryFiles, juce::File::findFiles, true, "*.xml"); + + for (auto f : factoryFiles) + { + auto program = createProgram(); + program->loadFromFile (f, false); + programs.add (program); + } + } + + // Load user presets from userAppData + juce::File userDir = getProgramDirectory(); + if (userDir.isDirectory()) + { + juce::Array userFiles; + userDir.findChildFiles (userFiles, juce::File::findFiles, true, "*.xml"); + + for (auto f : userFiles) + { + auto program = createProgram(); + program->loadFromFile (f, false); + programs.add (program); + } + } + + std::sort (programs.begin(), programs.end(), [](const auto& a, const auto& b) { return a->name.compareIgnoreCase (b->name) < 0; }); + + // Create the default program + auto defaultProgram = createProgram(); + defaultProgram->name = "Default"; + defaultProgram->saveProcessor (*this); + + programs.insert (0, defaultProgram); +} + +juce::StringArray WavetableAudioProcessor::getWavetableList() +{ + juce::StringArray tables; + + juce::File wavetableDir = getFactoryWavetableDirectory(); + if (wavetableDir.isDirectory()) + { + juce::Array files; + wavetableDir.findChildFiles (files, juce::File::findFiles, true, "*.wt2048"); + + for (auto& f : files) + tables.add (f.getFileNameWithoutExtension()); + } + + tables.sortNatural(); + return tables; +} + //============================================================================== void WavetableAudioProcessor::stateUpdated() { diff --git a/plugin/Source/PluginProcessor.h b/plugin/Source/PluginProcessor.h index f23d7a6..a0688f8 100644 --- a/plugin/Source/PluginProcessor.h +++ b/plugin/Source/PluginProcessor.h @@ -53,6 +53,14 @@ public: void incWavetable (int osc, int delta); bool loadUserWavetable (int osc, const juce::File& f, int sz); + juce::File getFactoryWavetableDirectory(); + juce::File getFactoryPresetDirectory(); + juce::File getProgramDirectory() override; + juce::File getLegacyProgramDirectory(); + void loadAllPrograms() override; + void migrateUserPresets(); + juce::StringArray getWavetableList(); + void applyEffects (juce::AudioSampleBuffer& buffer); void applyEffect (juce::AudioSampleBuffer& buffer, int fxId);