diff --git a/.gitignore b/.gitignore index d12fdd1..a4db58b 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,8 @@ Dependencies/libsamplerate/ # used by cmake build/ +builds/ +Source/config.h # private config (see Config.cmake template) MyConfig.cmake diff --git a/Assets/CMakeLists.txt b/Assets/CMakeLists.txt index 598e331..0c0270f 100644 --- a/Assets/CMakeLists.txt +++ b/Assets/CMakeLists.txt @@ -3,4 +3,9 @@ juce_add_binary_data(${BaseTargetName}BinaryData Picto_Siren_40x37.png ) +# Fix pour ARM64/aarch64 Linux : forcer -fPIC pour BinaryData +set_target_properties(${BaseTargetName}BinaryData PROPERTIES + POSITION_INDEPENDENT_CODE ON +) + target_link_libraries(${BaseTargetName} PRIVATE ${BaseTargetName}BinaryData) \ No newline at end of file diff --git a/Assets/ComposeSiren.icns b/Assets/ComposeSiren.icns new file mode 100644 index 0000000..d00c492 Binary files /dev/null and b/Assets/ComposeSiren.icns differ diff --git a/Assets/Icon_1024.png b/Assets/Icon_1024.png new file mode 100644 index 0000000..af8e708 Binary files /dev/null and b/Assets/Icon_1024.png differ diff --git a/CMakeLists.txt b/CMakeLists.txt index 7f58e06..0404191 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,8 +39,8 @@ juce_add_plugin(${BaseTargetName} PRODUCT_NAME "${BaseTargetName}" COMPANY_NAME "${VendorName}" VERSION ${CMAKE_PROJECT_VERSION} - ICON_BIG "${CMAKE_SOURCE_DIR}/Assets/Icon.png" - ICON_SMALL "${CMAKE_SOURCE_DIR}/Assets/Icon.png" + ICON_BIG "${CMAKE_SOURCE_DIR}/Assets/Icon_1024.png" + ICON_SMALL "${CMAKE_SOURCE_DIR}/Assets/Icon_1024.png" FORMATS ${FORMATS} # A four-character manufacturer id with at least one upper-case character PLUGIN_MANUFACTURER_CODE McVv @@ -122,4 +122,6 @@ if(APPLE) # we generate a cmake subproject to build the installer include(Packaging/Apple/MakePackage.cmake) elseif(WIN32) # we just proceed the regular way include(Packaging/Windows/MakePackage.cmake) +elseif(LINUX) # Debian package for Raspberry Pi and other Linux systems + include(Packaging/Linux/MakePackage.cmake) endif() diff --git a/ComposeSiren.jucer b/ComposeSiren.jucer index 057e215..1bb3ddc 100644 --- a/ComposeSiren.jucer +++ b/ComposeSiren.jucer @@ -1,158 +1,165 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Packaging/Linux/MakePackage.cmake b/Packaging/Linux/MakePackage.cmake new file mode 100644 index 0000000..714661c --- /dev/null +++ b/Packaging/Linux/MakePackage.cmake @@ -0,0 +1,101 @@ +################################################################################ +# Linux Debian Package Configuration +# Based on Packaging/Windows/MakePackage.cmake +################################################################################ + +################################################################################ +# Install targets + +# Install Standalone executable +install( + TARGETS ${BaseTargetName}_Standalone + RUNTIME DESTINATION bin + COMPONENT Standalone +) + +# Install Resources +install( + DIRECTORY "${CMAKE_SOURCE_DIR}/Resources/" + DESTINATION "share/${BaseTargetName}/Resources" + COMPONENT Standalone + FILES_MATCHING PATTERN "data*" +) + +################################################################################ +# Configure CPack for Debian + +set(PACKAGING_RESOURCES_DIR "${CMAKE_SOURCE_DIR}/Packaging") + +# Define components to package - Only Standalone (exclude JUCE) +set(CPACK_COMPONENTS_ALL Standalone) +set(CPACK_DEB_COMPONENT_INSTALL ON) + +# Only install the Standalone component, not JUCE +set(CPACK_INSTALL_CMAKE_PROJECTS "${CMAKE_BINARY_DIR};${PROJECT_NAME};Standalone;/") + +# Script to clean JUCE files before packaging +set(CPACK_PRE_BUILD_SCRIPTS "${CMAKE_CURRENT_BINARY_DIR}/CleanJUCEFiles.cmake") +file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/CleanJUCEFiles.cmake" " +# Remove JUCE installation files that shouldn't be in the package +file(REMOVE_RECURSE + \"\${CPACK_TEMPORARY_INSTALL_DIRECTORY}/usr/include/JUCE-7.0.3\" + \"\${CPACK_TEMPORARY_INSTALL_DIRECTORY}/usr/lib/cmake/JUCE-7.0.3\" + \"\${CPACK_TEMPORARY_INSTALL_DIRECTORY}/usr/bin/JUCE-7.0.3\" +) +") + +# Basic package info +set(CPACK_PACKAGE_NAME ${BaseTargetName}) +set(CPACK_PACKAGE_VENDOR ${VendorName}) +set(CPACK_VERBATIM_VARIABLES TRUE) +set(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR}) +set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR}) +set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH}) +set(CPACK_PACKAGE_DIRECTORY "${PROJECT_BINARY_DIR}/Packaging/${BaseTargetName}_Installer_artefacts") + +# Debian-specific settings +set(CPACK_GENERATOR "DEB") +set(CPACK_DEBIAN_PACKAGE_NAME "${BaseTargetName}") +set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Mecanique Vivante ") +set(CPACK_DEBIAN_PACKAGE_DESCRIPTION "ComposeSiren audio synthesizer + ComposeSiren is an audio plugin that synthesizes sounds of sirens + made by Mécanique Vivante. The plugin allows to handle the seven-piece + Siren Orchestra: two altos (S1 and S2), a bass (S3), a tenor (S4), + two sopranos (S5 and S6), and a piccolo (S7).") +set(CPACK_DEBIAN_PACKAGE_SECTION "sound") +set(CPACK_DEBIAN_PACKAGE_PRIORITY "optional") +set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "https://www.mecanique-vivante.com") + +# Architecture detection +execute_process( + COMMAND dpkg --print-architecture + OUTPUT_VARIABLE CPACK_DEBIAN_PACKAGE_ARCHITECTURE + OUTPUT_STRIP_TRAILING_WHITESPACE +) + +# Suggested dependencies (not required, but recommended) +set(CPACK_DEBIAN_PACKAGE_SUGGESTS "jackd2, qjackctl, a2jmidid") + +# Package file name format: composesiren_1.5.0_arm64.deb +set(CPACK_DEBIAN_FILE_NAME "DEB-DEFAULT") + +# License and documentation +if(EXISTS "${PACKAGING_RESOURCES_DIR}/License.txt") + set(CPACK_RESOURCE_FILE_LICENSE "${PACKAGING_RESOURCES_DIR}/License.txt") +endif() + +if(EXISTS "${PACKAGING_RESOURCES_DIR}/ReadMe.txt") + set(CPACK_RESOURCE_FILE_README "${PACKAGING_RESOURCES_DIR}/ReadMe.txt") +endif() + +################################################################################ +# Include CPack and add components + +include(CPack) + +cpack_add_component(Standalone + DISPLAY_NAME "ComposeSiren Standalone Application" + DESCRIPTION "Standalone audio synthesizer application with siren sample data" + REQUIRED +) + diff --git a/README.md b/README.md index 6b3f810..802fbd5 100644 --- a/README.md +++ b/README.md @@ -26,8 +26,72 @@ You can download the vst3 plugin as well as the Audio Unit plugin directly from You'll find [here][3] more info on how to use the plugins with Ableton Live. +### Mixer & Reverb (v1.5.0) + +#### Interface graphique + +L'interface comprend : +- **7 canaux mixer** (S1-S7) avec pour chacun : + - Slider de **Volume** (bleu pour Master, gris pour les autres) + - Bouton rotatif de **Pan** (panoramique L/R) + - Bouton **Reset** (réinitialise tous les paramètres du canal) + - Indicateur **MIDI Note On** (vert = note active) +- **Section Reverb** (Canal 16) : + - **Room Size** : Taille de la réverbération + - **Damping** : Amortissement des hautes fréquences + - **Dry/Wet** : Balance signal sec/effet + - **Width** : Largeur stéréo de la reverb + +#### Contrôle MIDI + +##### Canaux 1-7 (Sirènes S1-S7) + +| CC | Paramètre | Plage | Description | +|-----|-----------|-------|-------------| +| 7 | Volume | 0-127 | Volume individuel de la sirène | +| 10 | Pan | 0-127 | Panoramique (0=gauche, 64=centre, 127=droite) | +| 70 | Master Volume | 0-127 | Volume master indépendant du CC7 | +| 121 | Reset | - | Réinitialise tous les paramètres du canal | + +##### Canal 16 (Reverb globale) + +| CC | Paramètre | Plage | Description | +|-----|-----------|-------|-------------| +| 64 | Enable | 0-127 | Active/désactive la reverb (≥64 = ON) | +| 65 | Room Size | 0-127 | Taille de la réverbération | +| 66 | Dry/Wet | 0-127 | Balance signal sec/effet | +| 67 | Damping | 0-127 | Amortissement des hautes fréquences | +| 68 | Width | 0-127 | Largeur stéréo (0=mono, 127=stéréo large) | +| 121 | Reset All | - | Réinitialise TOUTES les sirènes (canaux 1-7) | + +#### Sauvegarde d'état + +Tous les paramètres du mixer et de la reverb sont sauvegardés automatiquement dans l'état du plugin (DAW preset/project). Cela inclut : +- Volumes et pans de chaque canal +- Tous les paramètres de la reverb +- État ON/OFF de la reverb + + +### Installation v1.5.0 + +**Téléchargement :** +- macOS : `Releases/ComposeSiren-v1.5.0-custom-mix-macOS.dmg` (47 MB) + - Inclus : Standalone + Audio Unit + - Note : VST3 non disponible (bug JUCE avec macOS 15, en attente d'un fix) + +**Instructions d'installation** : Voir `INSTALLATION.txt` dans le DMG + + ### Version history: +- 1.5.0 - **Mixer + Reverb intégré** (branche custom-mix) + - Mixer 7 canaux avec contrôles Volume + Pan individuels + - Reverb globale avec Room Size, Damping, Dry/Wet et Width + - Interface graphique moderne (fond gris foncé, sliders horizontaux) + - Contrôle MIDI complet via CC (canaux 1-7 pour sirènes, canal 16 pour reverb) + - Reset MIDI via CC121 (par canal ou global) + - Indicateurs d'activité MIDI Note On/Off temps réel + - Sauvegarde de l'état (tous les paramètres mixer + reverb) - 1.3.0 - Change default panning and volume - 1.2.0 - Audio Unit format added - 1.1.0 - Improved GUI @@ -55,19 +119,59 @@ linux: * if at some point the `Dependencies/JUCE` submodule is altered by some IDE, you can reset it using `git submodule deinit -f .` then `git submodule update --init` +⚠️ **Note importante** : Le build CMake échoue actuellement sur macOS 15 à cause d'APIs obsolètes dans JUCE (`CGWindowListCreateImage`, `CVDisplayLink*`). En attendant un correctif JUCE, utilisez **Xcode** pour compiler : + At the moment the plugin is built : -* on Mac OS 11.6.4 using Ninja (Xcode works too) - * `cmake -B build -G Ninja -C Config.cmake -DCMAKE_BUILD_TYPE=Release` to setup the build system - * `cmake --build build --config Release` to build the plugins and generate the installer +* on Mac OS 11.6.4+ using **Xcode** (CMake ne fonctionne pas avec macOS 15) + * Ouvrir `Builds/MacOSX/ComposeSiren.xcodeproj` + * Compiler les targets : `ComposeSiren - Standalone Plugin` et `ComposeSiren - AU` + * Note : VST3 échoue aussi à cause du même bug JUCE * on Windows 10 using Visual Studio (couldn't get Ninja to work on windows yet) * `cmake -B build -G "Visual Studio 17 2022" -C Config.cmake` * `cmake --build build --config Release` * `cpack --config build/CPackConfig.cmake` -* on Linux +* on Linux (including Raspberry Pi ARM64) * `cmake -B builds/linux -G "Unix Makefiles"` * `cmake --build builds/linux --config Release` - * no instruction for installer for now + * `cd builds/linux && cpack -G DEB` to create Debian package + * The .deb package is created in `builds/linux/Packaging/ComposeSiren_Installer_artefacts/` + * Install with: `sudo dpkg -i ComposeSiren_*.deb` -The resulting installer (built with `productbuild` on mac and `NSIS` on windows) +The resulting installer (built with `productbuild` on mac, `NSIS` on windows, and `DEB` on Linux) is created in `build/Packaging/ComposeSiren_Installer_artefacts` + +### Linux Installation Notes + +The Linux builds install: +- Standalone binary: `/usr/bin/ComposeSiren` +- Resources (audio data): `/usr/share/ComposeSiren/Resources/` + +For Raspberry Pi or other ARM systems, ensure you have the dependencies installed: +```bash +sudo apt-get install libx11-dev libxrandr-dev libxinerama-dev libxcursor-dev libfreetype-dev libasound2-dev +``` + +### Publishing Releases on GitHub + +To create and publish a new release with the Debian package: + +1. **Create and push a version tag:** + ```bash + git tag -a v1.5.0 -m "Release version 1.5.0" + git push origin v1.5.0 + ``` + +2. **Create a GitHub Release:** + - Go to the repository on GitHub + - Click "Releases" → "Create a new release" + - Select the tag you just created (v1.5.0) + - Add release notes + - Attach the .deb package from `builds/linux/Packaging/ComposeSiren_Installer_artefacts/ComposeSiren_1.5.0_arm64.deb` + - Publish the release + +Users can then download and install with: +```bash +wget https://github.com/patricecolet/ComposeSiren/releases/download/v1.5.0/ComposeSiren_1.5.0_arm64.deb +sudo dpkg -i ComposeSiren_1.5.0_arm64.deb +``` diff --git a/Releases/ComposeSiren-v1.5.0-custom-mix-macOS.dmg b/Releases/ComposeSiren-v1.5.0-custom-mix-macOS.dmg new file mode 100644 index 0000000..d1bce1b Binary files /dev/null and b/Releases/ComposeSiren-v1.5.0-custom-mix-macOS.dmg differ diff --git a/Releases/RELEASE_NOTES_v1.5.0.md b/Releases/RELEASE_NOTES_v1.5.0.md new file mode 100644 index 0000000..464c7cb --- /dev/null +++ b/Releases/RELEASE_NOTES_v1.5.0.md @@ -0,0 +1,128 @@ +# ComposeSiren v1.5.0 - Release Notes + +![ComposeSiren Icon](https://github.com/patricecolet/ComposeSiren/releases/download/v1.5.0/Icon_1024.png) + +**Branche** : `custom-mix` +**Date** : 10 octobre 2025 +**Commit** : `c80dca1` + +## 🎛️ Nouveautés principales + +### Mixer 7 canaux intégré +- Contrôle individuel de **Volume** et **Pan** pour chaque sirène (S1-S7) +- Bouton **Reset** par canal (réinitialise tous les paramètres) +- Indicateurs **MIDI Note On/Off** en temps réel (voyants verts) +- Slider de volume **Master** avec couleur distinctive (bleu) + +### Reverb globale intégrée +- **Room Size** : Taille de la réverbération +- **Damping** : Amortissement des hautes fréquences +- **Dry/Wet** : Balance signal sec/effet +- **Width** : Largeur stéréo de la reverb +- Activation/désactivation par bouton ou MIDI CC64 + +### Interface graphique modernisée +- Fond **gris foncé** élégant +- **En-tête** : "COMPOSE SIREN v1.5.0" + nom de branche "custom-mix" +- Sliders de reverb **horizontaux** pour une meilleure ergonomie +- Boutons de pan **plus grands** pour une manipulation précise +- Label simplifié pour la section reverb : **"Canal 16"** +- **Icône de l'app** : Picto Siren haute résolution + +### Contrôle MIDI complet +- **Canaux 1-7** : Contrôle individuel des sirènes + - CC7 : Volume + - CC10 : Pan + - CC70 : Master Volume indépendant + - CC121 : Reset du canal +- **Canal 16** : Contrôle de la reverb globale + - CC64 : Enable (≥64 = ON) + - CC65 : Room Size + - CC66 : Dry/Wet + - CC67 : Damping + - CC68 : Width + - CC121 : **Reset TOUTES les sirènes** 🔄 + +### Sauvegarde d'état +- Tous les paramètres du mixer et de la reverb sont sauvegardés +- Persistance dans les presets DAW et les projets +- Restauration automatique à l'ouverture + +## 📦 Contenu du package + +### macOS (47 MB) +- **ComposeSiren-v1.5.0-custom-mix-macOS.dmg** : Package complet + - ComposeSiren.app : Application standalone + - ComposeSiren.component : Plugin Audio Unit + - INSTALLATION.txt : Instructions d'installation + +### Icônes haute résolution +- **ComposeSiren.icns** (518 KB) : Format macOS natif multi-résolutions +- **Icon_1024.png** (8.9 MB) : Version PNG 1024x1024 pour développeurs + +### ⚠️ Limitations connues +- **VST3 non disponible** : Le plugin VST3 ne compile pas à cause d'un bug JUCE avec macOS 15 + - APIs obsolètes : `CGWindowListCreateImage`, `CVDisplayLink*` + - Correctif attendu dans une future version de JUCE +- **Build CMake** : Ne fonctionne pas avec macOS 15 (même bug) + - Solution temporaire : Utiliser Xcode pour compiler + +## 🔧 Installation + +### Application Standalone +```bash +/Applications/ComposeSiren.app +``` + +### Plugin Audio Unit +```bash +# Utilisateur +~/Library/Audio/Plug-Ins/Components/ComposeSiren.component + +# Système (tous les utilisateurs) +/Library/Audio/Plug-Ins/Components/ComposeSiren.component +``` + +Voir `INSTALLATION.txt` dans le DMG pour plus de détails. + +## 🎯 Compatibilité + +- **macOS** : 11.6.4 ou supérieur +- **Architecture** : Universal (Intel x86_64 + Apple Silicon arm64) +- **Testé avec** : Ableton Live + +## 📝 Historique des commits + +- `c80dca1` : Add automation scripts for app icon integration +- `9b90bf7` : Add high-resolution app icon based on Picto_Siren +- `878311c` : Add release notes for v1.5.0 +- `078635d` : Add v1.5.0 macOS release package (Standalone + AU) +- `bb86d72` : Document mixer/reverb features and MIDI CC mapping (v1.5.0) +- `500010f` : Add MIDI reset via CC121 (per channel or global on ch16) +- `c7bc15d` : Reduce Linux output gain to x50 +- Plus d'une dizaine de commits pour l'interface, mixer et reverb... + +## 🐛 Bugs corrigés + +- ✅ Fuite mémoire `IIRFilter` résolue +- ✅ Problème de lancement de l'app résolu +- ✅ Gain de sortie Linux réduit (x50 au lieu de x100) +- ✅ Sauvegarde d'état des paramètres mixer/reverb + +## 🚀 Prochaines étapes + +1. **Attendre fix JUCE** pour macOS 15 (VST3 + CMake) +2. **Créer installeur Windows** (VST3 + Standalone) +3. **Créer installeur Linux** (Standalone) +4. **Tests approfondis** avec différentes DAW + +## 📧 Support + +- **GitHub** : https://github.com/patricecolet/ComposeSiren +- **Branche** : `custom-mix` +- **Issues** : https://github.com/patricecolet/ComposeSiren/issues + +--- + +© 2025 Mécanique Vivante + diff --git a/Releases/v1.5.0-custom-mix/ComposeSiren.app/Contents/Info.plist b/Releases/v1.5.0-custom-mix/ComposeSiren.app/Contents/Info.plist new file mode 100644 index 0000000..582897c --- /dev/null +++ b/Releases/v1.5.0-custom-mix/ComposeSiren.app/Contents/Info.plist @@ -0,0 +1,48 @@ + + + + + BuildMachineOSBuild + 24C101 + CFBundleDisplayName + ComposeSiren + CFBundleExecutable + ComposeSiren + CFBundleIdentifier + com.MecaniqueVivante.ComposeSiren + CFBundleName + ComposeSiren + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.5.0 + CFBundleSignature + ???? + CFBundleSupportedPlatforms + + MacOSX + + CFBundleVersion + 1.5.0 + DTCompiler + com.apple.compilers.llvm.clang.1_0 + DTPlatformBuild + 24E241 + DTPlatformName + macosx + DTPlatformVersion + 15.4 + DTSDKBuild + 24E241 + DTSDKName + macosx15.4 + DTXcode + 1630 + DTXcodeBuild + 16E140 + LSMinimumSystemVersion + 10.13 + NSHighResolutionCapable + + + diff --git a/Releases/v1.5.0-custom-mix/ComposeSiren.app/Contents/MacOS/ComposeSiren b/Releases/v1.5.0-custom-mix/ComposeSiren.app/Contents/MacOS/ComposeSiren new file mode 100755 index 0000000..d98ca76 Binary files /dev/null and b/Releases/v1.5.0-custom-mix/ComposeSiren.app/Contents/MacOS/ComposeSiren differ diff --git a/Releases/v1.5.0-custom-mix/ComposeSiren.app/Contents/PkgInfo b/Releases/v1.5.0-custom-mix/ComposeSiren.app/Contents/PkgInfo new file mode 100644 index 0000000..bd04210 --- /dev/null +++ b/Releases/v1.5.0-custom-mix/ComposeSiren.app/Contents/PkgInfo @@ -0,0 +1 @@ +APPL???? \ No newline at end of file diff --git a/Releases/v1.5.0-custom-mix/ComposeSiren.app/Contents/Resources/RecentFilesMenuTemplate.nib b/Releases/v1.5.0-custom-mix/ComposeSiren.app/Contents/Resources/RecentFilesMenuTemplate.nib new file mode 100644 index 0000000..cec7f7c Binary files /dev/null and b/Releases/v1.5.0-custom-mix/ComposeSiren.app/Contents/Resources/RecentFilesMenuTemplate.nib differ diff --git a/Releases/v1.5.0-custom-mix/ComposeSiren.app/Contents/Resources/dataAmpS1 b/Releases/v1.5.0-custom-mix/ComposeSiren.app/Contents/Resources/dataAmpS1 new file mode 100755 index 0000000..d8104df Binary files /dev/null and b/Releases/v1.5.0-custom-mix/ComposeSiren.app/Contents/Resources/dataAmpS1 differ diff --git a/Releases/v1.5.0-custom-mix/ComposeSiren.app/Contents/Resources/dataAmpS3 b/Releases/v1.5.0-custom-mix/ComposeSiren.app/Contents/Resources/dataAmpS3 new file mode 100755 index 0000000..30e3594 Binary files /dev/null and b/Releases/v1.5.0-custom-mix/ComposeSiren.app/Contents/Resources/dataAmpS3 differ diff --git a/Releases/v1.5.0-custom-mix/ComposeSiren.app/Contents/Resources/dataAmpS4 b/Releases/v1.5.0-custom-mix/ComposeSiren.app/Contents/Resources/dataAmpS4 new file mode 100755 index 0000000..b8eec30 Binary files /dev/null and b/Releases/v1.5.0-custom-mix/ComposeSiren.app/Contents/Resources/dataAmpS4 differ diff --git a/Releases/v1.5.0-custom-mix/ComposeSiren.app/Contents/Resources/dataAmpS5 b/Releases/v1.5.0-custom-mix/ComposeSiren.app/Contents/Resources/dataAmpS5 new file mode 100755 index 0000000..237e651 Binary files /dev/null and b/Releases/v1.5.0-custom-mix/ComposeSiren.app/Contents/Resources/dataAmpS5 differ diff --git a/Releases/v1.5.0-custom-mix/ComposeSiren.app/Contents/Resources/dataAmpS7 b/Releases/v1.5.0-custom-mix/ComposeSiren.app/Contents/Resources/dataAmpS7 new file mode 100755 index 0000000..9ebf777 Binary files /dev/null and b/Releases/v1.5.0-custom-mix/ComposeSiren.app/Contents/Resources/dataAmpS7 differ diff --git a/Releases/v1.5.0-custom-mix/ComposeSiren.app/Contents/Resources/dataFreqS1 b/Releases/v1.5.0-custom-mix/ComposeSiren.app/Contents/Resources/dataFreqS1 new file mode 100755 index 0000000..c3480e3 Binary files /dev/null and b/Releases/v1.5.0-custom-mix/ComposeSiren.app/Contents/Resources/dataFreqS1 differ diff --git a/Releases/v1.5.0-custom-mix/ComposeSiren.app/Contents/Resources/dataFreqS3 b/Releases/v1.5.0-custom-mix/ComposeSiren.app/Contents/Resources/dataFreqS3 new file mode 100755 index 0000000..7b194aa Binary files /dev/null and b/Releases/v1.5.0-custom-mix/ComposeSiren.app/Contents/Resources/dataFreqS3 differ diff --git a/Releases/v1.5.0-custom-mix/ComposeSiren.app/Contents/Resources/dataFreqS4 b/Releases/v1.5.0-custom-mix/ComposeSiren.app/Contents/Resources/dataFreqS4 new file mode 100755 index 0000000..2d4e80b Binary files /dev/null and b/Releases/v1.5.0-custom-mix/ComposeSiren.app/Contents/Resources/dataFreqS4 differ diff --git a/Releases/v1.5.0-custom-mix/ComposeSiren.app/Contents/Resources/dataFreqS5 b/Releases/v1.5.0-custom-mix/ComposeSiren.app/Contents/Resources/dataFreqS5 new file mode 100755 index 0000000..7224120 Binary files /dev/null and b/Releases/v1.5.0-custom-mix/ComposeSiren.app/Contents/Resources/dataFreqS5 differ diff --git a/Releases/v1.5.0-custom-mix/ComposeSiren.app/Contents/Resources/dataFreqS7 b/Releases/v1.5.0-custom-mix/ComposeSiren.app/Contents/Resources/dataFreqS7 new file mode 100755 index 0000000..a9e022e Binary files /dev/null and b/Releases/v1.5.0-custom-mix/ComposeSiren.app/Contents/Resources/dataFreqS7 differ diff --git a/Releases/v1.5.0-custom-mix/ComposeSiren.app/Contents/Resources/dataVectorIntervalS1 b/Releases/v1.5.0-custom-mix/ComposeSiren.app/Contents/Resources/dataVectorIntervalS1 new file mode 100644 index 0000000..103ba3b Binary files /dev/null and b/Releases/v1.5.0-custom-mix/ComposeSiren.app/Contents/Resources/dataVectorIntervalS1 differ diff --git a/Releases/v1.5.0-custom-mix/ComposeSiren.app/Contents/Resources/dataVectorIntervalS2 b/Releases/v1.5.0-custom-mix/ComposeSiren.app/Contents/Resources/dataVectorIntervalS2 new file mode 100644 index 0000000..6f9ccd6 Binary files /dev/null and b/Releases/v1.5.0-custom-mix/ComposeSiren.app/Contents/Resources/dataVectorIntervalS2 differ diff --git a/Releases/v1.5.0-custom-mix/ComposeSiren.app/Contents/Resources/dataVectorIntervalS3 b/Releases/v1.5.0-custom-mix/ComposeSiren.app/Contents/Resources/dataVectorIntervalS3 new file mode 100644 index 0000000..1e707e2 Binary files /dev/null and b/Releases/v1.5.0-custom-mix/ComposeSiren.app/Contents/Resources/dataVectorIntervalS3 differ diff --git a/Releases/v1.5.0-custom-mix/ComposeSiren.app/Contents/Resources/dataVectorIntervalS4 b/Releases/v1.5.0-custom-mix/ComposeSiren.app/Contents/Resources/dataVectorIntervalS4 new file mode 100644 index 0000000..445e503 Binary files /dev/null and b/Releases/v1.5.0-custom-mix/ComposeSiren.app/Contents/Resources/dataVectorIntervalS4 differ diff --git a/Releases/v1.5.0-custom-mix/ComposeSiren.app/Contents/Resources/dataVectorIntervalS5 b/Releases/v1.5.0-custom-mix/ComposeSiren.app/Contents/Resources/dataVectorIntervalS5 new file mode 100644 index 0000000..d423d02 --- /dev/null +++ b/Releases/v1.5.0-custom-mix/ComposeSiren.app/Contents/Resources/dataVectorIntervalS5 @@ -0,0 +1 @@ +Ag@@YAgAYAAg@@YAgAYAAg@@YAgAYAAg@@YAgAYAAg@@YAgAYAAg@@YAgAYAAg@@YAgAYAAg@@YAgAYAAg@@YAgAYAAg@@YAgAYAAg@Ag5AgAYAAg@AAgAYAA@A@gAYAA@A@gAYAA@A@gAYAA@A@gAYAA@Ag@gAYAA@A@gAYAA@A@gAYAA@A}@@YAA@A3h@@YAA@AY@@YAA@@Y@@YAA@@Y@@YAA@@@@YAA@@@@YAA@@=A@YAA@@U A@g5AA@@A@g5AAg@@A@AA@@A@AA@AA@@A@AA@@A AAA@@AAAA@@AAP&.AA@@AAg5AAA@Ag5Ag5AAA@ANJ@A + + + + files + + Resources/RecentFilesMenuTemplate.nib + + 4cEN55flubpq9bxeGSBG3zMPYQw= + + Resources/dataAmpS1 + + uT2L3+owhQ4Lu2wxxqAA8ka5tyg= + + Resources/dataAmpS3 + + 2px/of8IVGOhp8sS2GKLaP2QrWw= + + Resources/dataAmpS4 + + Lry2Ca6ftKQdnawBDmuyIaIsF/I= + + Resources/dataAmpS5 + + iRL+IcyVAcDWflYWa2+irAy/QM0= + + Resources/dataAmpS7 + + 9bsnAjBcDfA5QYUFUX/dhdkdWjk= + + Resources/dataFreqS1 + + SDHJNZ0JsWD9sRs7KQBHCKHpj3c= + + Resources/dataFreqS3 + + ZF/vDgPDoZ++egbgXJ8V/fFWILw= + + Resources/dataFreqS4 + + T/8Ktt1RpbAu5wEZWSnUuL7S1BU= + + Resources/dataFreqS5 + + Zb7WVL8MhqcaHENUfz6F4plzDRs= + + Resources/dataFreqS7 + + R8vZXErjJx9Ir6xbAXHmI+LwaIo= + + Resources/dataVectorIntervalS1 + + 1q5xcJch671yFKJ1sU9XFXv6riU= + + Resources/dataVectorIntervalS2 + + vaTYbw5BE1VxbjVaPDl9uDanV8c= + + Resources/dataVectorIntervalS3 + + bLy4rZMk/b66u10DTAZF/GS1wbI= + + Resources/dataVectorIntervalS4 + + OYTh1T0e/oZ6xUJa5Xek96NVNx8= + + Resources/dataVectorIntervalS5 + + u4aBVGV2iPAM1nbxNhn2Hi0yCSw= + + Resources/dataVectorIntervalS7 + + 1q5xcJch671yFKJ1sU9XFXv6riU= + + Resources/datadureTabsS1 + + 7LuvS2PWoujTdrBgJjancHaOV1g= + + Resources/datadureTabsS3 + + aoTv/RiI2cuocLFQkhbnemgqFdc= + + Resources/datadureTabsS4 + + ifOR3sysP73pWFoPile8eGmOluo= + + Resources/datadureTabsS5 + + maPzUC9kyIdI3uLszevZeKPWLRo= + + Resources/datadureTabsS7 + + +lq2gBXnIod3Z0kCNv2EHzbSLcI= + + + files2 + + Resources/RecentFilesMenuTemplate.nib + + hash2 + + cY60xaOlVk3YMq6aNs9knz7yV86JbdkhaT7rYQG9AIo= + + + Resources/dataAmpS1 + + hash2 + + gNnGA+T7GL60V8RINwxQMMSbrJ0C46yHqaYv3lY6ldE= + + + Resources/dataAmpS3 + + hash2 + + kvkirdeFb2STmQZ4VcVDnHsOmJ0L0xlmLutV9rXrc2g= + + + Resources/dataAmpS4 + + hash2 + + j0lKdVm7D4E3SIYPUOGWZHLIf9YdybzXh2kg6ioFjeg= + + + Resources/dataAmpS5 + + hash2 + + OoWfA2AXhvnTZcNdFXgjiACIvYI46A5t3GCBA2TwcHI= + + + Resources/dataAmpS7 + + hash2 + + ZVacyaPQ5fmmGN4wdPj/Vubn+AitsiuV1LZnwmxQj+M= + + + Resources/dataFreqS1 + + hash2 + + WrSELDCTlZH7ONon9fGkYp1clWALx/oOHun90eXcObw= + + + Resources/dataFreqS3 + + hash2 + + 1i+29P66gLj280XzlnZIB3l6++dbPZU4n43T1kJLMS8= + + + Resources/dataFreqS4 + + hash2 + + kjakTyah+Xr42W+OFkKVCquhgsUSP7rRSWisM3M/QT8= + + + Resources/dataFreqS5 + + hash2 + + 7Wyno0vt2LVCQGwi2mTkqEgN0CmYJC9rv94beTIUxtU= + + + Resources/dataFreqS7 + + hash2 + + 1EU0BiPPkD5mAkx7Kz0KBACQxfEqWyhF2+sci8EK3N0= + + + Resources/dataVectorIntervalS1 + + hash2 + + a6Yif7Dk+u95CGkb+jNVULMQNdQH8cZQ2NDSHhwS9xE= + + + Resources/dataVectorIntervalS2 + + hash2 + + Rqze0o95OISYENczuqVaQ+8380ED48/0AuADASFv5r8= + + + Resources/dataVectorIntervalS3 + + hash2 + + XJdHm2IBMoHELDgbLidPFgxvtNDEj9ibQj+a3s6YxcY= + + + Resources/dataVectorIntervalS4 + + hash2 + + ZnD7TW9R/fBaUEqOyu/+67MrorKbIQThtlGmFkAQrKk= + + + Resources/dataVectorIntervalS5 + + hash2 + + hHt83gtSYYh4oleX7S5rW8nCovCj+h3YTVOxrKhcC8o= + + + Resources/dataVectorIntervalS7 + + hash2 + + a6Yif7Dk+u95CGkb+jNVULMQNdQH8cZQ2NDSHhwS9xE= + + + Resources/datadureTabsS1 + + hash2 + + qXPrRGsSvjcgTXmzv729nMOjH5CSbvh3PGcgQZuEe5Y= + + + Resources/datadureTabsS3 + + hash2 + + TxCw4TUwoecd3/V0hdZdjWcNLT+LQrMY3r82fArvtYA= + + + Resources/datadureTabsS4 + + hash2 + + 48uv/22Rxq6kMfbeFzWref/mKavVpXXnTJcG7UKv1Jg= + + + Resources/datadureTabsS5 + + hash2 + + qv0uAMAi6t/P3TQIwBTNmLurH3UC1CYjkXQJ+tvK3Oc= + + + Resources/datadureTabsS7 + + hash2 + + o5lK7Lt1mI+CY1mIi4Pka8lfcU1cyHQYi9JcJSeUm+g= + + + + rules + + ^Resources/ + + ^Resources/.*\.lproj/ + + optional + + weight + 1000 + + ^Resources/.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Resources/Base\.lproj/ + + weight + 1010 + + ^version.plist$ + + + rules2 + + .*\.dSYM($|/) + + weight + 11 + + ^(.*/)?\.DS_Store$ + + omit + + weight + 2000 + + ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/ + + nested + + weight + 10 + + ^.* + + ^Info\.plist$ + + omit + + weight + 20 + + ^PkgInfo$ + + omit + + weight + 20 + + ^Resources/ + + weight + 20 + + ^Resources/.*\.lproj/ + + optional + + weight + 1000 + + ^Resources/.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Resources/Base\.lproj/ + + weight + 1010 + + ^[^/]+$ + + nested + + weight + 10 + + ^embedded\.provisionprofile$ + + weight + 20 + + ^version\.plist$ + + weight + 20 + + + + diff --git a/Releases/v1.5.0-custom-mix/ComposeSiren.component/Contents/Info.plist b/Releases/v1.5.0-custom-mix/ComposeSiren.component/Contents/Info.plist new file mode 100644 index 0000000..2434352 --- /dev/null +++ b/Releases/v1.5.0-custom-mix/ComposeSiren.component/Contents/Info.plist @@ -0,0 +1,74 @@ + + + + + AudioComponents + + + description + ComposeSiren + factoryFunction + ComposeSirenAUFactory + manufacturer + McVv + name + Mecanique Vivante: ComposeSiren + resourceUsage + + network.client + + temporary-exception.files.all.read-write + + + subtype + MvCs + type + aumu + version + 66816 + + + BuildMachineOSBuild + 24C101 + CFBundleDisplayName + ComposeSiren + CFBundleExecutable + ComposeSiren + CFBundleIdentifier + com.MecaniqueVivante.ComposeSiren + CFBundleName + ComposeSiren + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.5.0 + CFBundleSignature + ???? + CFBundleSupportedPlatforms + + MacOSX + + CFBundleVersion + 1.5.0 + DTCompiler + com.apple.compilers.llvm.clang.1_0 + DTPlatformBuild + 24E241 + DTPlatformName + macosx + DTPlatformVersion + 15.4 + DTSDKBuild + 24E241 + DTSDKName + macosx15.4 + DTXcode + 1630 + DTXcodeBuild + 16E140 + LSMinimumSystemVersion + 10.13 + NSHighResolutionCapable + + + diff --git a/Releases/v1.5.0-custom-mix/ComposeSiren.component/Contents/MacOS/ComposeSiren b/Releases/v1.5.0-custom-mix/ComposeSiren.component/Contents/MacOS/ComposeSiren new file mode 100755 index 0000000..017eb45 Binary files /dev/null and b/Releases/v1.5.0-custom-mix/ComposeSiren.component/Contents/MacOS/ComposeSiren differ diff --git a/Releases/v1.5.0-custom-mix/ComposeSiren.component/Contents/PkgInfo b/Releases/v1.5.0-custom-mix/ComposeSiren.component/Contents/PkgInfo new file mode 100644 index 0000000..19a9cf6 --- /dev/null +++ b/Releases/v1.5.0-custom-mix/ComposeSiren.component/Contents/PkgInfo @@ -0,0 +1 @@ +BNDL???? \ No newline at end of file diff --git a/Releases/v1.5.0-custom-mix/ComposeSiren.component/Contents/Resources/RecentFilesMenuTemplate.nib b/Releases/v1.5.0-custom-mix/ComposeSiren.component/Contents/Resources/RecentFilesMenuTemplate.nib new file mode 100644 index 0000000..cec7f7c Binary files /dev/null and b/Releases/v1.5.0-custom-mix/ComposeSiren.component/Contents/Resources/RecentFilesMenuTemplate.nib differ diff --git a/Releases/v1.5.0-custom-mix/ComposeSiren.component/Contents/Resources/dataAmpS1 b/Releases/v1.5.0-custom-mix/ComposeSiren.component/Contents/Resources/dataAmpS1 new file mode 100755 index 0000000..d8104df Binary files /dev/null and b/Releases/v1.5.0-custom-mix/ComposeSiren.component/Contents/Resources/dataAmpS1 differ diff --git a/Releases/v1.5.0-custom-mix/ComposeSiren.component/Contents/Resources/dataAmpS3 b/Releases/v1.5.0-custom-mix/ComposeSiren.component/Contents/Resources/dataAmpS3 new file mode 100755 index 0000000..30e3594 Binary files /dev/null and b/Releases/v1.5.0-custom-mix/ComposeSiren.component/Contents/Resources/dataAmpS3 differ diff --git a/Releases/v1.5.0-custom-mix/ComposeSiren.component/Contents/Resources/dataAmpS4 b/Releases/v1.5.0-custom-mix/ComposeSiren.component/Contents/Resources/dataAmpS4 new file mode 100755 index 0000000..b8eec30 Binary files /dev/null and b/Releases/v1.5.0-custom-mix/ComposeSiren.component/Contents/Resources/dataAmpS4 differ diff --git a/Releases/v1.5.0-custom-mix/ComposeSiren.component/Contents/Resources/dataAmpS5 b/Releases/v1.5.0-custom-mix/ComposeSiren.component/Contents/Resources/dataAmpS5 new file mode 100755 index 0000000..237e651 Binary files /dev/null and b/Releases/v1.5.0-custom-mix/ComposeSiren.component/Contents/Resources/dataAmpS5 differ diff --git a/Releases/v1.5.0-custom-mix/ComposeSiren.component/Contents/Resources/dataAmpS7 b/Releases/v1.5.0-custom-mix/ComposeSiren.component/Contents/Resources/dataAmpS7 new file mode 100755 index 0000000..9ebf777 Binary files /dev/null and b/Releases/v1.5.0-custom-mix/ComposeSiren.component/Contents/Resources/dataAmpS7 differ diff --git a/Releases/v1.5.0-custom-mix/ComposeSiren.component/Contents/Resources/dataFreqS1 b/Releases/v1.5.0-custom-mix/ComposeSiren.component/Contents/Resources/dataFreqS1 new file mode 100755 index 0000000..c3480e3 Binary files /dev/null and b/Releases/v1.5.0-custom-mix/ComposeSiren.component/Contents/Resources/dataFreqS1 differ diff --git a/Releases/v1.5.0-custom-mix/ComposeSiren.component/Contents/Resources/dataFreqS3 b/Releases/v1.5.0-custom-mix/ComposeSiren.component/Contents/Resources/dataFreqS3 new file mode 100755 index 0000000..7b194aa Binary files /dev/null and b/Releases/v1.5.0-custom-mix/ComposeSiren.component/Contents/Resources/dataFreqS3 differ diff --git a/Releases/v1.5.0-custom-mix/ComposeSiren.component/Contents/Resources/dataFreqS4 b/Releases/v1.5.0-custom-mix/ComposeSiren.component/Contents/Resources/dataFreqS4 new file mode 100755 index 0000000..2d4e80b Binary files /dev/null and b/Releases/v1.5.0-custom-mix/ComposeSiren.component/Contents/Resources/dataFreqS4 differ diff --git a/Releases/v1.5.0-custom-mix/ComposeSiren.component/Contents/Resources/dataFreqS5 b/Releases/v1.5.0-custom-mix/ComposeSiren.component/Contents/Resources/dataFreqS5 new file mode 100755 index 0000000..7224120 Binary files /dev/null and b/Releases/v1.5.0-custom-mix/ComposeSiren.component/Contents/Resources/dataFreqS5 differ diff --git a/Releases/v1.5.0-custom-mix/ComposeSiren.component/Contents/Resources/dataFreqS7 b/Releases/v1.5.0-custom-mix/ComposeSiren.component/Contents/Resources/dataFreqS7 new file mode 100755 index 0000000..a9e022e Binary files /dev/null and b/Releases/v1.5.0-custom-mix/ComposeSiren.component/Contents/Resources/dataFreqS7 differ diff --git a/Releases/v1.5.0-custom-mix/ComposeSiren.component/Contents/Resources/dataVectorIntervalS1 b/Releases/v1.5.0-custom-mix/ComposeSiren.component/Contents/Resources/dataVectorIntervalS1 new file mode 100644 index 0000000..103ba3b Binary files /dev/null and b/Releases/v1.5.0-custom-mix/ComposeSiren.component/Contents/Resources/dataVectorIntervalS1 differ diff --git a/Releases/v1.5.0-custom-mix/ComposeSiren.component/Contents/Resources/dataVectorIntervalS2 b/Releases/v1.5.0-custom-mix/ComposeSiren.component/Contents/Resources/dataVectorIntervalS2 new file mode 100644 index 0000000..6f9ccd6 Binary files /dev/null and b/Releases/v1.5.0-custom-mix/ComposeSiren.component/Contents/Resources/dataVectorIntervalS2 differ diff --git a/Releases/v1.5.0-custom-mix/ComposeSiren.component/Contents/Resources/dataVectorIntervalS3 b/Releases/v1.5.0-custom-mix/ComposeSiren.component/Contents/Resources/dataVectorIntervalS3 new file mode 100644 index 0000000..1e707e2 Binary files /dev/null and b/Releases/v1.5.0-custom-mix/ComposeSiren.component/Contents/Resources/dataVectorIntervalS3 differ diff --git a/Releases/v1.5.0-custom-mix/ComposeSiren.component/Contents/Resources/dataVectorIntervalS4 b/Releases/v1.5.0-custom-mix/ComposeSiren.component/Contents/Resources/dataVectorIntervalS4 new file mode 100644 index 0000000..445e503 Binary files /dev/null and b/Releases/v1.5.0-custom-mix/ComposeSiren.component/Contents/Resources/dataVectorIntervalS4 differ diff --git a/Releases/v1.5.0-custom-mix/ComposeSiren.component/Contents/Resources/dataVectorIntervalS5 b/Releases/v1.5.0-custom-mix/ComposeSiren.component/Contents/Resources/dataVectorIntervalS5 new file mode 100644 index 0000000..d423d02 --- /dev/null +++ b/Releases/v1.5.0-custom-mix/ComposeSiren.component/Contents/Resources/dataVectorIntervalS5 @@ -0,0 +1 @@ +Ag@@YAgAYAAg@@YAgAYAAg@@YAgAYAAg@@YAgAYAAg@@YAgAYAAg@@YAgAYAAg@@YAgAYAAg@@YAgAYAAg@@YAgAYAAg@@YAgAYAAg@Ag5AgAYAAg@AAgAYAA@A@gAYAA@A@gAYAA@A@gAYAA@A@gAYAA@Ag@gAYAA@A@gAYAA@A@gAYAA@A}@@YAA@A3h@@YAA@AY@@YAA@@Y@@YAA@@Y@@YAA@@@@YAA@@@@YAA@@=A@YAA@@U A@g5AA@@A@g5AAg@@A@AA@@A@AA@AA@@A@AA@@A AAA@@AAAA@@AAP&.AA@@AAg5AAA@Ag5Ag5AAA@ANJ@A + + + + files + + Resources/RecentFilesMenuTemplate.nib + + 4cEN55flubpq9bxeGSBG3zMPYQw= + + Resources/dataAmpS1 + + uT2L3+owhQ4Lu2wxxqAA8ka5tyg= + + Resources/dataAmpS3 + + 2px/of8IVGOhp8sS2GKLaP2QrWw= + + Resources/dataAmpS4 + + Lry2Ca6ftKQdnawBDmuyIaIsF/I= + + Resources/dataAmpS5 + + iRL+IcyVAcDWflYWa2+irAy/QM0= + + Resources/dataAmpS7 + + 9bsnAjBcDfA5QYUFUX/dhdkdWjk= + + Resources/dataFreqS1 + + SDHJNZ0JsWD9sRs7KQBHCKHpj3c= + + Resources/dataFreqS3 + + ZF/vDgPDoZ++egbgXJ8V/fFWILw= + + Resources/dataFreqS4 + + T/8Ktt1RpbAu5wEZWSnUuL7S1BU= + + Resources/dataFreqS5 + + Zb7WVL8MhqcaHENUfz6F4plzDRs= + + Resources/dataFreqS7 + + R8vZXErjJx9Ir6xbAXHmI+LwaIo= + + Resources/dataVectorIntervalS1 + + 1q5xcJch671yFKJ1sU9XFXv6riU= + + Resources/dataVectorIntervalS2 + + vaTYbw5BE1VxbjVaPDl9uDanV8c= + + Resources/dataVectorIntervalS3 + + bLy4rZMk/b66u10DTAZF/GS1wbI= + + Resources/dataVectorIntervalS4 + + OYTh1T0e/oZ6xUJa5Xek96NVNx8= + + Resources/dataVectorIntervalS5 + + u4aBVGV2iPAM1nbxNhn2Hi0yCSw= + + Resources/dataVectorIntervalS7 + + 1q5xcJch671yFKJ1sU9XFXv6riU= + + Resources/datadureTabsS1 + + 7LuvS2PWoujTdrBgJjancHaOV1g= + + Resources/datadureTabsS3 + + aoTv/RiI2cuocLFQkhbnemgqFdc= + + Resources/datadureTabsS4 + + ifOR3sysP73pWFoPile8eGmOluo= + + Resources/datadureTabsS5 + + maPzUC9kyIdI3uLszevZeKPWLRo= + + Resources/datadureTabsS7 + + +lq2gBXnIod3Z0kCNv2EHzbSLcI= + + + files2 + + Resources/RecentFilesMenuTemplate.nib + + hash2 + + cY60xaOlVk3YMq6aNs9knz7yV86JbdkhaT7rYQG9AIo= + + + Resources/dataAmpS1 + + hash2 + + gNnGA+T7GL60V8RINwxQMMSbrJ0C46yHqaYv3lY6ldE= + + + Resources/dataAmpS3 + + hash2 + + kvkirdeFb2STmQZ4VcVDnHsOmJ0L0xlmLutV9rXrc2g= + + + Resources/dataAmpS4 + + hash2 + + j0lKdVm7D4E3SIYPUOGWZHLIf9YdybzXh2kg6ioFjeg= + + + Resources/dataAmpS5 + + hash2 + + OoWfA2AXhvnTZcNdFXgjiACIvYI46A5t3GCBA2TwcHI= + + + Resources/dataAmpS7 + + hash2 + + ZVacyaPQ5fmmGN4wdPj/Vubn+AitsiuV1LZnwmxQj+M= + + + Resources/dataFreqS1 + + hash2 + + WrSELDCTlZH7ONon9fGkYp1clWALx/oOHun90eXcObw= + + + Resources/dataFreqS3 + + hash2 + + 1i+29P66gLj280XzlnZIB3l6++dbPZU4n43T1kJLMS8= + + + Resources/dataFreqS4 + + hash2 + + kjakTyah+Xr42W+OFkKVCquhgsUSP7rRSWisM3M/QT8= + + + Resources/dataFreqS5 + + hash2 + + 7Wyno0vt2LVCQGwi2mTkqEgN0CmYJC9rv94beTIUxtU= + + + Resources/dataFreqS7 + + hash2 + + 1EU0BiPPkD5mAkx7Kz0KBACQxfEqWyhF2+sci8EK3N0= + + + Resources/dataVectorIntervalS1 + + hash2 + + a6Yif7Dk+u95CGkb+jNVULMQNdQH8cZQ2NDSHhwS9xE= + + + Resources/dataVectorIntervalS2 + + hash2 + + Rqze0o95OISYENczuqVaQ+8380ED48/0AuADASFv5r8= + + + Resources/dataVectorIntervalS3 + + hash2 + + XJdHm2IBMoHELDgbLidPFgxvtNDEj9ibQj+a3s6YxcY= + + + Resources/dataVectorIntervalS4 + + hash2 + + ZnD7TW9R/fBaUEqOyu/+67MrorKbIQThtlGmFkAQrKk= + + + Resources/dataVectorIntervalS5 + + hash2 + + hHt83gtSYYh4oleX7S5rW8nCovCj+h3YTVOxrKhcC8o= + + + Resources/dataVectorIntervalS7 + + hash2 + + a6Yif7Dk+u95CGkb+jNVULMQNdQH8cZQ2NDSHhwS9xE= + + + Resources/datadureTabsS1 + + hash2 + + qXPrRGsSvjcgTXmzv729nMOjH5CSbvh3PGcgQZuEe5Y= + + + Resources/datadureTabsS3 + + hash2 + + TxCw4TUwoecd3/V0hdZdjWcNLT+LQrMY3r82fArvtYA= + + + Resources/datadureTabsS4 + + hash2 + + 48uv/22Rxq6kMfbeFzWref/mKavVpXXnTJcG7UKv1Jg= + + + Resources/datadureTabsS5 + + hash2 + + qv0uAMAi6t/P3TQIwBTNmLurH3UC1CYjkXQJ+tvK3Oc= + + + Resources/datadureTabsS7 + + hash2 + + o5lK7Lt1mI+CY1mIi4Pka8lfcU1cyHQYi9JcJSeUm+g= + + + + rules + + ^Resources/ + + ^Resources/.*\.lproj/ + + optional + + weight + 1000 + + ^Resources/.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Resources/Base\.lproj/ + + weight + 1010 + + ^version.plist$ + + + rules2 + + .*\.dSYM($|/) + + weight + 11 + + ^(.*/)?\.DS_Store$ + + omit + + weight + 2000 + + ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/ + + nested + + weight + 10 + + ^.* + + ^Info\.plist$ + + omit + + weight + 20 + + ^PkgInfo$ + + omit + + weight + 20 + + ^Resources/ + + weight + 20 + + ^Resources/.*\.lproj/ + + optional + + weight + 1000 + + ^Resources/.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Resources/Base\.lproj/ + + weight + 1010 + + ^[^/]+$ + + nested + + weight + 10 + + ^embedded\.provisionprofile$ + + weight + 20 + + ^version\.plist$ + + weight + 20 + + + + diff --git a/Releases/v1.5.0-custom-mix/INSTALLATION.txt b/Releases/v1.5.0-custom-mix/INSTALLATION.txt new file mode 100644 index 0000000..9165b34 --- /dev/null +++ b/Releases/v1.5.0-custom-mix/INSTALLATION.txt @@ -0,0 +1,89 @@ +ComposeSiren v1.5.0 - Mixer + Reverb Edition +============================================= + +CONTENU DU PACKAGE +------------------ +- ComposeSiren.app : Application standalone +- ComposeSiren.component : Plugin Audio Unit (macOS) + +NOTE: Le plugin VST3 n'est pas inclus dans cette version à cause d'un bug +de compilation avec macOS 15 et JUCE. Il sera disponible quand JUCE +publiera un correctif. + + +INSTALLATION +------------ + +1. APPLICATION STANDALONE + Copiez ComposeSiren.app dans votre dossier Applications : + + /Applications/ComposeSiren.app + +2. PLUGIN AUDIO UNIT + Copiez ComposeSiren.component dans le dossier Audio Units : + + ~/Library/Audio/Plug-Ins/Components/ComposeSiren.component + + ou pour une installation système (tous les utilisateurs) : + + /Library/Audio/Plug-Ins/Components/ComposeSiren.component + + +UTILISATION +----------- + +### Interface graphique + +L'interface comprend : +- 7 canaux mixer (S1-S7) avec Volume, Pan, Reset et indicateur MIDI +- Section Reverb (Canal 16) avec Room Size, Damping, Dry/Wet, Width + +### Contrôle MIDI + +CANAUX 1-7 (Sirènes S1-S7) : + CC7 : Volume individuel + CC10 : Pan (0=gauche, 64=centre, 127=droite) + CC70 : Master Volume indépendant + CC121 : Reset du canal + +CANAL 16 (Reverb globale) : + CC64 : Enable (≥64 = ON) + CC65 : Room Size + CC66 : Dry/Wet + CC67 : Damping + CC68 : Width + CC121 : Reset TOUTES les sirènes + + +COMPATIBILITÉ +------------- +- macOS 11.6.4 ou supérieur +- Architecture : Universal (Intel + Apple Silicon) +- Testé avec Ableton Live + + +SUPPORT +------- +https://github.com/patriceguyot/ComposeSiren +Branche : custom-mix + + +VERSION HISTORY +--------------- +v1.5.0 (2025-10-10) +- Mixer 7 canaux avec contrôles Volume + Pan individuels +- Reverb globale intégrée +- Interface graphique moderne +- Contrôle MIDI complet via CC +- Reset MIDI via CC121 +- Indicateurs d'activité MIDI temps réel +- Sauvegarde d'état complète + + +LICENCE +------- +Voir LICENSE dans le dépôt GitHub + + +© 2025 Mécanique Vivante + diff --git a/Resources/dataVectorIntervalS1 b/Resources/dataVectorIntervalS1 new file mode 100644 index 0000000..103ba3b Binary files /dev/null and b/Resources/dataVectorIntervalS1 differ diff --git a/Resources/dataVectorIntervalS2 b/Resources/dataVectorIntervalS2 new file mode 100644 index 0000000..6f9ccd6 Binary files /dev/null and b/Resources/dataVectorIntervalS2 differ diff --git a/Resources/dataVectorIntervalS3 b/Resources/dataVectorIntervalS3 new file mode 100644 index 0000000..1e707e2 Binary files /dev/null and b/Resources/dataVectorIntervalS3 differ diff --git a/Resources/dataVectorIntervalS4 b/Resources/dataVectorIntervalS4 new file mode 100644 index 0000000..445e503 Binary files /dev/null and b/Resources/dataVectorIntervalS4 differ diff --git a/Resources/dataVectorIntervalS5 b/Resources/dataVectorIntervalS5 new file mode 100644 index 0000000..d423d02 --- /dev/null +++ b/Resources/dataVectorIntervalS5 @@ -0,0 +1 @@ +Ag@@YAgAYAAg@@YAgAYAAg@@YAgAYAAg@@YAgAYAAg@@YAgAYAAg@@YAgAYAAg@@YAgAYAAg@@YAgAYAAg@@YAgAYAAg@@YAgAYAAg@Ag5AgAYAAg@AAgAYAA@A@gAYAA@A@gAYAA@A@gAYAA@A@gAYAA@Ag@gAYAA@A@gAYAA@A@gAYAA@A}@@YAA@A3h@@YAA@AY@@YAA@@Y@@YAA@@Y@@YAA@@@@YAA@@@@YAA@@=A@YAA@@U A@g5AA@@A@g5AAg@@A@AA@@A@AA@AA@@A@AA@@A AAA@@AAAA@@AAP&.AA@@AAg5AAA@Ag5Ag5AAA@ANJ@A -#include - -using namespace std; - -MidiIn::MidiIn(const std::function onVelocityChanged, - const std::function onEnginePitchChanged - ) : onVelocityChanged(onVelocityChanged) - , onEngineSpeedChanged(onEnginePitchChanged) - -{ - for (int i =0; i<17; i++) - { - ChangevolumegeneralCh[i]=1.0; - ControlCh[7][i]=127.; - ControlCh[12][i]=127.; - ControlCh[13][i]=127.; - ControlCh[6][i]=60; - volumefinalCh[i]=500.; - MSBCh[i]=64; - isEnVeilleCh[i]=1; - ancienVeloCh[i]=-1; - AncienVolFinalCh[i]=-1; - veloFinal[i]=500; - } - isEnVeilleCh[8]=0; - velociteCh[8]=127; -} - -MidiIn::~MidiIn(){ -} - - - -void MidiIn::timerAudio(){ - if(isWithSynth){ - for (int Ch=1; Ch<9; Ch++) { - sendVariaCh(Ch); - if (isRampeCh[Ch]) { - createRampeCh(Ch); - } - if (isReleaseCh[Ch]) { - createReleaseCh(Ch); - } - if (isAttacVibrato[Ch] && countTimerAudio==0 ) { - incrementeVibrato(Ch); - } - } - countTimerAudio++; - if (countTimerAudio>=9)countTimerAudio=0; - } -} - -void MidiIn::JouerClic(int value){ - switch (value%2) { - case 0: - //[appDelegate.MonSynth jouerclic1]; //todo - break; - case 1: - //[appDelegate.MonSynth jouerclic2]; //todo - break; - default: - break; - } -} - - -void MidiIn::handleMIDIMessage2(int Ch, int value1, int value2){ - std::cout << "Message MIDI reçu: " << Ch << value1 << value2 << std::endl; - if (Ch >= 144 && Ch < 160 ) { - if (value2 != 0) { - if(value2==200) {RealTimeStartNote(Ch-143, value1, 0);} - else {RealTimeStartNote(Ch-143, value1, value2);} - - }else { - RealTimeStopNote(Ch-143, value1); - } - } - else if (Ch >= 128 && Ch < 144 ) { - RealTimeStopNote(Ch-127, value1); - } - else if (Ch >= 176 && Ch < 192) { - HandleControlChange(Ch - 175, value1, value2); - }else if (Ch >=224 && Ch <240){ - HandlePitchWheel(Ch-223, value1, value2); - } -} - -void MidiIn::RealTimeStartNote(int Ch, int value1, int value2){ - std::cout << "RealTimeStartNote: " << Ch << "-" << value1 << "-" << value2 << std::endl; - if (Ch >=1 && Ch <8) { - if(Ch ==1)countvibra = 0; - if((ControlCh[1][Ch] != 0 && ControlCh[9][Ch] != 0 && ControlCh[11][Ch] != 0 && value2>0 && velociteCh[Ch]==0) ||(ControlCh[1][Ch] != 0 && ControlCh[9][Ch] != 0 && ControlCh[11][Ch] != 0 && value2>0 && value1 !=noteonCh[Ch] ) ){ - Control1FinalCh[Ch]=0; - isAttacVibrato[Ch]=1; - } - noteonCh[Ch] = value1; - velociteCh[Ch] = value2; - noteonfinalCh[Ch] = ((noteonCh[Ch]) + pitchbendCh[Ch]) ; - volumefinalCh[Ch] =(velociteCh[Ch] *(ControlCh[7][Ch]/127.0))*(500./127.) ; - if(volumefinalCh[Ch] < 0.0)volumefinalCh[Ch]=0.0; - if(volumefinalCh[Ch] > 500.0)volumefinalCh[Ch]=500.0; - tourmoteurCh[Ch] = tabledecorresponcanceMidinote(noteonfinalCh[Ch], Ch); - sendVariaCh(Ch); - sendVolCh((int) (volumefinalCh[Ch]*ChangevolumegeneralCh[Ch]), Ch); - }else if(Ch==10){ - if(value1 > 1 && value2 > 1) JouerClic(value1); - } -} - -void MidiIn::RealTimeStopNote(int Ch, int note){ - if (Ch < 8) { - if(note == noteonCh[Ch]) - { - sendVariaCh(Ch); - sendVolCh(0, Ch); - velociteCh[Ch] = 0.0; - volumefinalCh[Ch]=0.0; - - } - } -} - -void MidiIn::HandleControlChange(int Ch, int value1, int value2){ - - if (Ch < 9) { - switch (value1) - { - case 1 : - ControlCh[1][Ch] = value2 ; - if(ControlCh[11][Ch]==0)Control1FinalCh[Ch] =ControlCh[1][Ch]; - if(ControlCh[1][Ch]==0 && isAttacVibrato[Ch]==1){ - isAttacVibrato[Ch]=0; - } - break; - case 5 : - ControlCh[5][Ch] =value2; - break; - case 6 : - ControlCh[6][Ch] =value2; - break; - case 7 : - ControlCh[7][Ch] = value2 ; - volumefinalCh[Ch] =(velociteCh[Ch] *(ControlCh[7][Ch]/127.0))*(500./127.) ; - if(volumefinalCh[Ch] < 0.0)volumefinalCh[Ch]=0.0; - if(volumefinalCh[Ch] > 500.0)volumefinalCh[Ch]=500.0; - sendVolCh((int)(volumefinalCh[Ch]*ChangevolumegeneralCh[Ch]), Ch); - break; - case 9 : - ControlCh[9][Ch] = value2 ; - if(ControlCh[9][Ch] < 0.0)ControlCh[9][Ch]=0.0; - if(ControlCh[9][Ch] > 127.0)ControlCh[9][Ch]=127.0; - if(ControlCh[9][Ch]==0 && isAttacVibrato[Ch]==1){ - isAttacVibrato[Ch]=0; - } - break; - case 11 : - ControlCh[11][Ch] = value2 ; - if(ControlCh[11][Ch]==0 && isAttacVibrato[Ch]==1){ - isAttacVibrato[Ch]=0; - - } - break; - case 15 : - ControlCh[15][Ch] = value2 ; - if(value2==0)vartremoloCh[Ch]=0; - break; - case 72 : - ControlCh[72][Ch] = value2; - break; - case 73 : - ControlCh[73][Ch] = value2; - break; - case 92 : - ControlCh[92][Ch]= value2 ; - break; - default: - break; - } - } -} - -void MidiIn::HandlePitchWheel(int Ch, int value1, int value2){ - if (Ch < 8) { - pitchbendCh[Ch] = ((value2 << 7) | value1); - pitchbendCh[Ch] = (float)((pitchbendCh[Ch]) - 8192) / 8192.; - noteonfinalCh[Ch] = (noteonCh[Ch] + pitchbendCh[Ch] ) ; - tourmoteurCh[Ch] = tabledecorresponcanceMidinote(noteonfinalCh[Ch], Ch); - } -} - -float MidiIn::tabledecorresponcanceMidinote(float note, int Ch){ - float Midinote=note; - float tourmoteur=0.; - float multiplicateur=0.; - if (Ch ==1 || Ch==2) { - multiplicateur=5.; - }else if (Ch ==3 || Ch==5 || Ch==6 || Ch==7) { - multiplicateur=7.5; - }else if (Ch ==4){ - multiplicateur=(20./3.); - } - if(Midinote < 0.)Midinote = 0.; - float freq2 = 440. * powf(2., (Midinote-81.)/12.); - if(freq2 > 8.)tourmoteur= freq2 * multiplicateur;// DO 24 - return tourmoteur; -} - -void MidiIn::sendVariaCh(int Ch){ - - float vibrato=0.; - if ((varvfoCh[Ch] <=628) && (ControlCh[9][Ch]!=0) && (ControlCh[1][Ch]!=0)) // <=valeur 2 * PI (pour le calcul du sinus - { // et frequence de modulation diffÈrent de 0 - varvfoCh[Ch]=varvfoCh[Ch]+ incrementationVibrato*ControlCh[9][Ch]; // valeur frÈquence = f*10 12,7Hz = 127 pÈriode = 1/frÈq - vibrato=(((tourmoteurCh[Ch]*constescursion*Control1FinalCh[Ch] )/12700.)*sin(varvfoCh[Ch]/100.)); - } - else - { - varvfoCh[Ch]=0; - vibrato=0.; - } - if ((vartremoloCh[Ch] <=628)&&(ControlCh[15][Ch]!=0)&&(ControlCh[92][Ch]!=0)) // <=valeur 2 * PI (pour le calcul du sinus - { // et frequence de tremolo diffÈrent de 0 - vartremoloCh[Ch]=vartremoloCh[Ch]+incrementationVibrato*ControlCh[15][Ch]; // valeur frÈquence = f*10 12,7Hz = 127 pÈriode = 1/frÈq - } - else - { - vartremoloCh[Ch]=0; - } - if (ControlCh[15][Ch]!=0 && ControlCh[92][Ch]!=0 && isReleaseCh[Ch]==0 && isRampeCh[Ch]==0 && !isEnVeilleCh[Ch]) { - int volume=(int)(volumefinalCh[Ch]*ChangevolumegeneralCh[Ch]); - tremoloCh[Ch] =volume-(((volume*sin(vartremoloCh[Ch]/100.))/(256./ControlCh[92][Ch]))+(volume/(256./ControlCh[92][Ch]))); - sendVolCh(volume, Ch); - } - //***************************** Portamento***************************** - if (ControlCh[5][Ch]==0.0)vitesseCh[Ch]=tourmoteurCh[Ch]; - else { - float nbr=((ControlCh[5][Ch]/127.)/5.)+0.80; - vitesseCh[Ch]=(((1.-nbr) * tourmoteurCh[Ch]) + (nbr*vitesseCh[Ch])); - //***** end Portamento - } - if(isWithSynth){ - onEngineSpeedChanged(Ch, vitesseCh[Ch]+vibrato); - } -} - -void MidiIn::sendVolCh(int message, int Ch){ - if( (ControlCh[73][Ch] > 0.0) && (message >=2) && (ancienVeloCh[Ch]<=1 )){// ATTAC - if(isRampeCh[Ch]==1){ - isRampeCh[Ch]=0; - countcreateattac[Ch]--; - } - if(isReleaseCh[Ch]==1){ - isReleaseCh[Ch]=0; - countcreaterelease[Ch]--; - } - countcreateattac[Ch]++; - if (countcreateattac[Ch]==1) { - isRampeCh[Ch]=1; - }else countcreateattac[Ch]--; - - } - else if( (ControlCh[72][Ch] > 0.0) && (message <=1) && (ancienVeloCh[Ch] >=2)){//Release - if(isReleaseCh[Ch]==1){ - isReleaseCh[Ch]=0; - countcreaterelease[Ch]--; - } - if(isRampeCh[Ch]){ - isRampeCh[Ch]=0; - countcreateattac[Ch]--; - } - isReleaseCh[Ch]=1; - } - else { - if(isRampeCh[Ch]==1 && (message<=1 ) ){ - isRampeCh[Ch]=0; - countcreateattac[Ch]--; - } - if(isReleaseCh[Ch]==1 && message>1 ){ - isReleaseCh[Ch]=0; - countcreaterelease[Ch]--; - } - if((isRampeCh[Ch]==0) && (isReleaseCh[Ch]==0) ){ - if (ControlCh[15][Ch]>0. && ControlCh[92][Ch]>0.){ - vitesseClapetCh[Ch]=veloFinal[Ch]=(int)tremoloCh[Ch]; - } - else vitesseClapetCh[Ch]=veloFinal[Ch]=message; - - if(isWithSynth && Ch !=8){ - onVelocityChanged(Ch, veloFinal[Ch]); - } - } - - } - ancienVeloCh[Ch]=message; -} - - -void MidiIn::createReleaseCh(int Ch){ - float nbr=128-ControlCh[72][Ch]; - if (vitesseClapetCh[Ch] >=250)nbr=nbr/7.62; - else if (vitesseClapetCh[Ch] < 250 && vitesseClapetCh[Ch] >= 200)nbr=nbr/10.; - else if (vitesseClapetCh[Ch] < 200 && vitesseClapetCh[Ch] >= 150)nbr=nbr/15.; - else if (vitesseClapetCh[Ch] < 150 && vitesseClapetCh[Ch] >= 100)nbr=nbr/20; - else if (vitesseClapetCh[Ch] < 100 && vitesseClapetCh[Ch] >= 50)nbr=nbr/25.; - else if (vitesseClapetCh[Ch] < 50 )nbr=nbr/30.; - vitesseClapetCh[Ch]=vitesseClapetCh[Ch]-nbr; - int around=(int)vitesseClapetCh[Ch]; - if (around <= 1 ) { - if(isReleaseCh[Ch]==1){ - isReleaseCh[Ch]=0; - } - countcreaterelease[Ch]--; - tremoloCh[Ch]=veloFinal[Ch]=around=0; - }else if (ControlCh[15][Ch]!=0. && ControlCh[92][Ch]!=0.) { - tremoloCh[Ch] =around-(((around*sin(vartremoloCh[Ch]/100.))/(256./ControlCh[92][Ch]))+(around/(256./ControlCh[92][Ch]))); - veloFinal[Ch]=(int)tremoloCh[Ch]; - }else veloFinal[Ch]=around; - if(isWithSynth && Ch !=8){ - onVelocityChanged(Ch, veloFinal[Ch]); - } -} - -void MidiIn::createRampeCh(int Ch){ - int vitesseVoulue=volumefinalCh[Ch]*ChangevolumegeneralCh[Ch]; - float nbr=(128-ControlCh[73][Ch])/7.62; - vitesseClapetCh[Ch]=vitesseClapetCh[Ch]+nbr; - int around=(int)vitesseClapetCh[Ch]; - if (around >= vitesseVoulue ) { - if(isRampeCh[Ch]==1){ - isRampeCh[Ch]=0; - countcreateattac[Ch]--; - } - veloFinal[Ch]=vitesseClapetCh[Ch]=around=vitesseVoulue; - } - if (ControlCh[15][Ch]!=0. && ControlCh[92][Ch]!=0.) { - tremoloCh[Ch] =around-(((around*sin(vartremoloCh[Ch]/100.))/(256./ControlCh[92][Ch]))+(around/(256./ControlCh[92][Ch]))); - veloFinal[Ch]=(int)tremoloCh[Ch]; - }else veloFinal[Ch]=around; - - if(isWithSynth && Ch !=8){ - onVelocityChanged(Ch, veloFinal[Ch]); - } -} - -void MidiIn::isWithSound(bool is){ - isWithSynth=is; -} - -void MidiIn::changingvolumeclic(int VolumeClic){ - VolumeDuClic=VolumeClic; -} - - - -void MidiIn::incrementeVibrato(int Ch){ - if (Control1FinalCh[Ch] < ControlCh[1][Ch]) { - Control1FinalCh[Ch]=Control1FinalCh[Ch]+(ControlCh[11][Ch]/12.7); - }else { - Control1FinalCh[Ch]=ControlCh[1][Ch]; - isAttacVibrato[Ch]=0; - } -} - -void MidiIn::definiMuteEthernet(bool ismuted, int Ch){ - if (ismuted) { - tourmoteurCh[Ch] =0.0; - noteonfinalCh[Ch]=0.0; - volumefinalCh[Ch]=0.0; - sendVariaCh(Ch); - sendVolCh(0, Ch); - } - isMuteEthernetCh[Ch]=ismuted; -} - -void MidiIn::STOffVariateurCh(int Ch){ - isEnVeilleCh[Ch]=1; -} - -void MidiIn::STOnVariateurCh(int Ch){ - isEnVeilleCh[Ch]=0; -} - -void MidiIn::resetSireneCh(int Ch){ - - std::cout << "Reset: "<< Ch << std::endl; - - noteonfinalCh[Ch]=0.0; - ///////////////////////////////////////////////////****** Ferme les volets - - AncienVolFinalCh[Ch]=-1; - ControlCh[1][Ch]=0; - ControlCh[5][Ch]=0; - ControlCh[9][Ch]=0; - ControlCh[11][Ch]=0; - ControlCh[15][Ch]=0; - ControlCh[17][Ch]=0; - ControlCh[18][Ch]=0; - ControlCh[92][Ch]=0; - ControlCh[72][Ch]=0; - ControlCh[73][Ch]=0; - LSBCh[Ch]=0; - MSBCh[Ch]=64; - pitchbendCh[Ch]=0.; - noteonCh[Ch]=0.; - velociteCh[Ch]=0; - tourmoteurCh[Ch] =0.0; - noteonfinalCh[Ch]=0.0; - volumefinalCh[Ch]=0.0; - ControlCh[12][Ch]=127.; - ControlCh[13][Ch]=127.; - Control1FinalCh[16]=0.; - varvfoCh[Ch]=0; - vartremoloCh[Ch]=0; - vitesseCh[Ch]=0; - tremoloCh[Ch]=0; - - - isAttacVibrato[Ch]=0; - - if (isRampeCh[Ch]==1){ - isRampeCh[Ch]=0; - countcreateattac[Ch]--; - } - if (isReleaseCh[Ch]==1){ - isReleaseCh[Ch]=0; - countcreaterelease[Ch]--; - isReleaseCh[Ch]=0; - } - - ControlCh[7][Ch]=127; - veloFinal[Ch]=500; - volumefinalCh[Ch]=500.; - vitesseClapetCh[Ch]=0; - ancienVeloCh[Ch]=-1; - AncienVolFinalCh[Ch]=-1; -} +/* + ============================================================================== + + CS_midiIN.cpp + Created: 6 May 2020 10:48:16am + Author: guyot + + ============================================================================== +*/ + +#include "CS_midiIN.h" + +#include +#include + +using namespace std; + +MidiIn::MidiIn(const std::function onVelocityChanged, + const std::function onEnginePitchChanged + ) : onVelocityChanged(onVelocityChanged) + , onEngineSpeedChanged(onEnginePitchChanged) + +{ + // Initialiser le sample rate par défaut à 44.1kHz + sampleRate = 44100.0; + incrementationVibrato = (512.0 / sampleRate) / 0.025; + + for (int i =0; i<17; i++) + { + ChangevolumegeneralCh[i]=1.0; + ControlCh[7][i]=127.; + ControlCh[12][i]=127.; + ControlCh[13][i]=127.; + ControlCh[6][i]=60; + volumefinalCh[i]=500.; + MSBCh[i]=64; + isEnVeilleCh[i]=1; + ancienVeloCh[i]=-1; + AncienVolFinalCh[i]=-1; + veloFinal[i]=500; + } + isEnVeilleCh[8]=0; + velociteCh[8]=127; +} + +MidiIn::~MidiIn(){ +} + +void MidiIn::setSampleRate(double newSampleRate) { + sampleRate = newSampleRate; + incrementationVibrato = (512.0 / sampleRate) / 0.025; +} + + +void MidiIn::timerAudio(){ + if(isWithSynth){ + for (int Ch=1; Ch<9; Ch++) { + sendVariaCh(Ch); + if (isRampeCh[Ch]) { + createRampeCh(Ch); + } + if (isReleaseCh[Ch]) { + createReleaseCh(Ch); + } + if (isAttacVibrato[Ch] && countTimerAudio==0 ) { + incrementeVibrato(Ch); + } + } + countTimerAudio++; + if (countTimerAudio>=9)countTimerAudio=0; + } +} + +void MidiIn::JouerClic(int value){ + switch (value%2) { + case 0: + //[appDelegate.MonSynth jouerclic1]; //todo + break; + case 1: + //[appDelegate.MonSynth jouerclic2]; //todo + break; + default: + break; + } +} + + +void MidiIn::handleMIDIMessage2(int Ch, int value1, int value2){ + // Supprimer le debug trop fréquent + if (Ch >= 144 && Ch < 160 ) { + if (value2 != 0) { + if(value2==200) {RealTimeStartNote(Ch-143, value1, 0);} + else {RealTimeStartNote(Ch-143, value1, value2);} + + }else { + RealTimeStopNote(Ch-143, value1); + } + } + else if (Ch >= 128 && Ch < 144 ) { + RealTimeStopNote(Ch-127, value1); + } + else if (Ch >= 176 && Ch < 192) { + HandleControlChange(Ch - 175, value1, value2); + }else if (Ch >=224 && Ch <240){ + HandlePitchWheel(Ch-223, value1, value2); + } +} + +void MidiIn::RealTimeStartNote(int Ch, int value1, int value2){ + if (Ch >=1 && Ch <8) { + if(Ch ==1)countvibra = 0; + if((ControlCh[1][Ch] != 0 && ControlCh[9][Ch] != 0 && ControlCh[11][Ch] != 0 && value2>0 && velociteCh[Ch]==0) ||(ControlCh[1][Ch] != 0 && ControlCh[9][Ch] != 0 && ControlCh[11][Ch] != 0 && value2>0 && value1 !=noteonCh[Ch] ) ){ + Control1FinalCh[Ch]=0; + isAttacVibrato[Ch]=1; + } + noteonCh[Ch] = value1; + velociteCh[Ch] = value2; + noteonfinalCh[Ch] = ((noteonCh[Ch]) + pitchbendCh[Ch]) ; + volumefinalCh[Ch] =(velociteCh[Ch] *(ControlCh[7][Ch]/127.0))*(500./127.) ; + if(volumefinalCh[Ch] < 0.0)volumefinalCh[Ch]=0.0; + if(volumefinalCh[Ch] > 500.0)volumefinalCh[Ch]=500.0; + tourmoteurCh[Ch] = tabledecorresponcanceMidinote(noteonfinalCh[Ch], Ch); + sendVariaCh(Ch); + sendVolCh((int) (volumefinalCh[Ch]*ChangevolumegeneralCh[Ch]), Ch); + }else if(Ch==10){ + if(value1 > 1 && value2 > 1) JouerClic(value1); + } +} + +void MidiIn::RealTimeStopNote(int Ch, int note){ + if (Ch < 8) { + if(note == noteonCh[Ch]) + { + sendVariaCh(Ch); + sendVolCh(0, Ch); + velociteCh[Ch] = 0.0; + volumefinalCh[Ch]=0.0; + + } + } +} + +void MidiIn::HandleControlChange(int Ch, int value1, int value2){ + + if (Ch < 9) { + switch (value1) + { + case 121: // Reset All Controllers (standard MIDI) + resetSireneCh(Ch); + break; + case 1 : + ControlCh[1][Ch] = value2 ; + if(ControlCh[11][Ch]==0)Control1FinalCh[Ch] =ControlCh[1][Ch]; + if(ControlCh[1][Ch]==0 && isAttacVibrato[Ch]==1){ + isAttacVibrato[Ch]=0; + } + break; + case 5 : + ControlCh[5][Ch] =value2; + break; + case 6 : + ControlCh[6][Ch] =value2; + break; + case 7 : + ControlCh[7][Ch] = value2 ; + volumefinalCh[Ch] =(velociteCh[Ch] *(ControlCh[7][Ch]/127.0))*(500./127.) ; + if(volumefinalCh[Ch] < 0.0)volumefinalCh[Ch]=0.0; + if(volumefinalCh[Ch] > 500.0)volumefinalCh[Ch]=500.0; + sendVolCh((int)(volumefinalCh[Ch]*ChangevolumegeneralCh[Ch]), Ch); + break; + case 9 : + ControlCh[9][Ch] = value2 ; + if(ControlCh[9][Ch] < 0.0)ControlCh[9][Ch]=0.0; + if(ControlCh[9][Ch] > 127.0)ControlCh[9][Ch]=127.0; + if(ControlCh[9][Ch]==0 && isAttacVibrato[Ch]==1){ + isAttacVibrato[Ch]=0; + } + break; + case 11 : + ControlCh[11][Ch] = value2 ; + if(ControlCh[11][Ch]==0 && isAttacVibrato[Ch]==1){ + isAttacVibrato[Ch]=0; + + } + break; + case 15 : + ControlCh[15][Ch] = value2 ; + if(value2==0)vartremoloCh[Ch]=0; + break; + case 72 : + ControlCh[72][Ch] = value2; + break; + case 73 : + ControlCh[73][Ch] = value2; + break; + case 92 : + ControlCh[92][Ch]= value2 ; + break; + default: + break; + } + } +} + +void MidiIn::HandlePitchWheel(int Ch, int value1, int value2){ + if (Ch < 8) { + pitchbendCh[Ch] = ((value2 << 7) | value1); + pitchbendCh[Ch] = (float)((pitchbendCh[Ch]) - 8192) / 8192.; + noteonfinalCh[Ch] = (noteonCh[Ch] + pitchbendCh[Ch] ) ; + tourmoteurCh[Ch] = tabledecorresponcanceMidinote(noteonfinalCh[Ch], Ch); + } +} + +float MidiIn::tabledecorresponcanceMidinote(float note, int Ch){ + float Midinote=note; + float tourmoteur=0.; + float multiplicateur=0.; + if (Ch ==1 || Ch==2) { + multiplicateur=5.; + }else if (Ch ==3 || Ch==5 || Ch==6 || Ch==7) { + multiplicateur=7.5; + }else if (Ch ==4){ + multiplicateur=(20./3.); + } + if(Midinote < 0.)Midinote = 0.; + float freq2 = 440. * powf(2., (Midinote-81.)/12.); + if(freq2 > 8.)tourmoteur= freq2 * multiplicateur;// DO 24 + return tourmoteur; +} + +void MidiIn::sendVariaCh(int Ch){ + + float vibrato=0.; + if ((varvfoCh[Ch] <=628) && (ControlCh[9][Ch]!=0) && (ControlCh[1][Ch]!=0)) // <=valeur 2 * PI (pour le calcul du sinus + { // et frequence de modulation diffÈrent de 0 + varvfoCh[Ch]=varvfoCh[Ch]+ incrementationVibrato*ControlCh[9][Ch]; // valeur frÈquence = f*10 12,7Hz = 127 pÈriode = 1/frÈq + vibrato=(((tourmoteurCh[Ch]*constescursion*Control1FinalCh[Ch] )/12700.)*sin(varvfoCh[Ch]/100.)); + + + } + else + { + varvfoCh[Ch]=0; + vibrato=0.; + } + if ((vartremoloCh[Ch] <=628)&&(ControlCh[15][Ch]!=0)&&(ControlCh[92][Ch]!=0)) // <=valeur 2 * PI (pour le calcul du sinus + { // et frequence de tremolo diffÈrent de 0 + vartremoloCh[Ch]=vartremoloCh[Ch]+incrementationVibrato*ControlCh[15][Ch]; // valeur frÈquence = f*10 12,7Hz = 127 pÈriode = 1/frÈq + } + else + { + vartremoloCh[Ch]=0; + } + if (ControlCh[15][Ch]!=0 && ControlCh[92][Ch]!=0 && isReleaseCh[Ch]==0 && isRampeCh[Ch]==0 && !isEnVeilleCh[Ch]) { + int volume=(int)(volumefinalCh[Ch]*ChangevolumegeneralCh[Ch]); + tremoloCh[Ch] =volume-(((volume*sin(vartremoloCh[Ch]/100.))/(256./ControlCh[92][Ch]))+(volume/(256./ControlCh[92][Ch]))); + sendVolCh(volume, Ch); + } + //***************************** Portamento***************************** + if (ControlCh[5][Ch]==0.0)vitesseCh[Ch]=tourmoteurCh[Ch]; + else { + float nbr=((ControlCh[5][Ch]/127.)/5.)+0.80; + vitesseCh[Ch]=(((1.-nbr) * tourmoteurCh[Ch]) + (nbr*vitesseCh[Ch])); + //***** end Portamento + } + if(isWithSynth){ + onEngineSpeedChanged(Ch, vitesseCh[Ch]+vibrato); + } +} + +void MidiIn::sendVolCh(int message, int Ch){ + if( (ControlCh[73][Ch] > 0.0) && (message >=2) && (ancienVeloCh[Ch]<=1 )){// ATTAC + if(isRampeCh[Ch]==1){ + isRampeCh[Ch]=0; + countcreateattac[Ch]--; + } + if(isReleaseCh[Ch]==1){ + isReleaseCh[Ch]=0; + countcreaterelease[Ch]--; + } + countcreateattac[Ch]++; + if (countcreateattac[Ch]==1) { + isRampeCh[Ch]=1; + }else countcreateattac[Ch]--; + + } + else if( (ControlCh[72][Ch] > 0.0) && (message <=1) && (ancienVeloCh[Ch] >=2)){//Release + if(isReleaseCh[Ch]==1){ + isReleaseCh[Ch]=0; + countcreaterelease[Ch]--; + } + if(isRampeCh[Ch]){ + isRampeCh[Ch]=0; + countcreateattac[Ch]--; + } + isReleaseCh[Ch]=1; + } + else { + if(isRampeCh[Ch]==1 && (message<=1 ) ){ + isRampeCh[Ch]=0; + countcreateattac[Ch]--; + } + if(isReleaseCh[Ch]==1 && message>1 ){ + isReleaseCh[Ch]=0; + countcreaterelease[Ch]--; + } + if((isRampeCh[Ch]==0) && (isReleaseCh[Ch]==0) ){ + if (ControlCh[15][Ch]>0. && ControlCh[92][Ch]>0.){ + vitesseClapetCh[Ch]=veloFinal[Ch]=(int)tremoloCh[Ch]; + } + else vitesseClapetCh[Ch]=veloFinal[Ch]=message; + + if(isWithSynth && Ch !=8){ + onVelocityChanged(Ch, veloFinal[Ch]); + } + } + + } + ancienVeloCh[Ch]=message; +} + + +void MidiIn::createReleaseCh(int Ch){ + float nbr=128-ControlCh[72][Ch]; + if (vitesseClapetCh[Ch] >=250)nbr=nbr/7.62; + else if (vitesseClapetCh[Ch] < 250 && vitesseClapetCh[Ch] >= 200)nbr=nbr/10.; + else if (vitesseClapetCh[Ch] < 200 && vitesseClapetCh[Ch] >= 150)nbr=nbr/15.; + else if (vitesseClapetCh[Ch] < 150 && vitesseClapetCh[Ch] >= 100)nbr=nbr/20; + else if (vitesseClapetCh[Ch] < 100 && vitesseClapetCh[Ch] >= 50)nbr=nbr/25.; + else if (vitesseClapetCh[Ch] < 50 )nbr=nbr/30.; + vitesseClapetCh[Ch]=vitesseClapetCh[Ch]-nbr; + int around=(int)vitesseClapetCh[Ch]; + if (around <= 1 ) { + if(isReleaseCh[Ch]==1){ + isReleaseCh[Ch]=0; + } + countcreaterelease[Ch]--; + tremoloCh[Ch]=veloFinal[Ch]=around=0; + }else if (ControlCh[15][Ch]!=0. && ControlCh[92][Ch]!=0.) { + tremoloCh[Ch] =around-(((around*sin(vartremoloCh[Ch]/100.))/(256./ControlCh[92][Ch]))+(around/(256./ControlCh[92][Ch]))); + veloFinal[Ch]=(int)tremoloCh[Ch]; + }else veloFinal[Ch]=around; + if(isWithSynth && Ch !=8){ + onVelocityChanged(Ch, veloFinal[Ch]); + } +} + +void MidiIn::createRampeCh(int Ch){ + int vitesseVoulue=volumefinalCh[Ch]*ChangevolumegeneralCh[Ch]; + float nbr=(128-ControlCh[73][Ch])/7.62; + vitesseClapetCh[Ch]=vitesseClapetCh[Ch]+nbr; + int around=(int)vitesseClapetCh[Ch]; + if (around >= vitesseVoulue ) { + if(isRampeCh[Ch]==1){ + isRampeCh[Ch]=0; + countcreateattac[Ch]--; + } + veloFinal[Ch]=vitesseClapetCh[Ch]=around=vitesseVoulue; + } + if (ControlCh[15][Ch]!=0. && ControlCh[92][Ch]!=0.) { + tremoloCh[Ch] =around-(((around*sin(vartremoloCh[Ch]/100.))/(256./ControlCh[92][Ch]))+(around/(256./ControlCh[92][Ch]))); + veloFinal[Ch]=(int)tremoloCh[Ch]; + }else veloFinal[Ch]=around; + + if(isWithSynth && Ch !=8){ + onVelocityChanged(Ch, veloFinal[Ch]); + } +} + +void MidiIn::isWithSound(bool is){ + isWithSynth=is; +} + +void MidiIn::changingvolumeclic(int VolumeClic){ + VolumeDuClic=VolumeClic; +} + + + +void MidiIn::incrementeVibrato(int Ch){ + if (Control1FinalCh[Ch] < ControlCh[1][Ch]) { + Control1FinalCh[Ch]=Control1FinalCh[Ch]+(ControlCh[11][Ch]/12.7); + }else { + Control1FinalCh[Ch]=ControlCh[1][Ch]; + isAttacVibrato[Ch]=0; + } +} + +void MidiIn::definiMuteEthernet(bool ismuted, int Ch){ + if (ismuted) { + tourmoteurCh[Ch] =0.0; + noteonfinalCh[Ch]=0.0; + volumefinalCh[Ch]=0.0; + sendVariaCh(Ch); + sendVolCh(0, Ch); + } + isMuteEthernetCh[Ch]=ismuted; +} + +void MidiIn::STOffVariateurCh(int Ch){ + isEnVeilleCh[Ch]=1; +} + +void MidiIn::STOnVariateurCh(int Ch){ + isEnVeilleCh[Ch]=0; +} + +void MidiIn::resetSireneCh(int Ch){ + + // Supprimer le debug trop fréquent + + noteonfinalCh[Ch]=0.0; + ///////////////////////////////////////////////////****** Ferme les volets + + AncienVolFinalCh[Ch]=-1; + ControlCh[1][Ch]=0; + ControlCh[5][Ch]=0; + ControlCh[9][Ch]=0; + ControlCh[11][Ch]=0; + ControlCh[15][Ch]=0; + ControlCh[17][Ch]=0; + ControlCh[18][Ch]=0; + ControlCh[92][Ch]=0; + ControlCh[72][Ch]=0; + ControlCh[73][Ch]=0; + LSBCh[Ch]=0; + MSBCh[Ch]=64; + pitchbendCh[Ch]=0.; + noteonCh[Ch]=0.; + velociteCh[Ch]=0; + tourmoteurCh[Ch] =0.0; + noteonfinalCh[Ch]=0.0; + volumefinalCh[Ch]=0.0; + ControlCh[12][Ch]=127.; + ControlCh[13][Ch]=127.; + Control1FinalCh[16]=0.; + varvfoCh[Ch]=0; + vartremoloCh[Ch]=0; + vitesseCh[Ch]=0; + tremoloCh[Ch]=0; + + + isAttacVibrato[Ch]=0; + + if (isRampeCh[Ch]==1){ + isRampeCh[Ch]=0; + countcreateattac[Ch]--; + } + if (isReleaseCh[Ch]==1){ + isReleaseCh[Ch]=0; + countcreaterelease[Ch]--; + isReleaseCh[Ch]=0; + } + + ControlCh[7][Ch]=127; + veloFinal[Ch]=500; + volumefinalCh[Ch]=500.; + vitesseClapetCh[Ch]=0; + ancienVeloCh[Ch]=-1; + AncienVolFinalCh[Ch]=-1; +} + +float MidiIn::getVolumeFinal(int channel) { + if (channel < 1 || channel > 16) return 0.0f; + // Retourner ControlCh[7] (0-127) converti en 0.0-1.0 + return ControlCh[7][channel] / 127.0f; +} + +void MidiIn::setVolumeFinal(int channel, float volume) { + if (channel < 1 || channel > 16) return; + // Convertir 0.0-1.0 en 0-127 et mettre à jour ControlCh[7] + ControlCh[7][channel] = volume * 127.0f; + if(ControlCh[7][channel] < 0.0f) ControlCh[7][channel] = 0.0f; + if(ControlCh[7][channel] > 127.0f) ControlCh[7][channel] = 127.0f; + + // Recalculer volumefinalCh avec la formule originale + volumefinalCh[channel] = (velociteCh[channel] * (ControlCh[7][channel]/127.0)) * (500./127.); + if(volumefinalCh[channel] < 0.0) volumefinalCh[channel] = 0.0; + if(volumefinalCh[channel] > 500.0) volumefinalCh[channel] = 500.0; + + // Appliquer le nouveau volume à la sirène + sendVolCh((int)(volumefinalCh[channel] * ChangevolumegeneralCh[channel]), channel); +} + +bool MidiIn::isNoteOn(int channel) { + if (channel < 1 || channel > 16) return false; + return velociteCh[channel] > 0; +} + + diff --git a/Source/CS_midiIN.h b/Source/CS_midiIN.h index 35ed7ed..afcd9fe 100644 --- a/Source/CS_midiIN.h +++ b/Source/CS_midiIN.h @@ -46,6 +46,14 @@ class MidiIn void timerAudio(); void sirenium_in(unsigned char *buf); + + // Nouvelle méthode pour mettre à jour le sample rate + void setSampleRate(double newSampleRate); + + // Méthodes pour accéder au volume et état Note On/Off pour l'interface + float getVolumeFinal(int channel); // Retourne 0.0-1.0 + void setVolumeFinal(int channel, float volume); // 0.0-1.0 + bool isNoteOn(int channel); // true si une note est active private: @@ -67,7 +75,10 @@ class MidiIn float vitesseCh[17] = {0}; float tremoloCh[17] = {0}; int veloFinal[17]; - float incrementationVibrato=(512./44100.)/0.025; + + // Variables pour le sample rate dynamique + double sampleRate; + float incrementationVibrato; bool isWithSoundON; int VolumeDuClic = 100; @@ -92,6 +103,5 @@ class MidiIn int pitch_bend; const std::function onVelocityChanged; - const std::function onEngineSpeedChanged; }; diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index 8341df0..4e78bc9 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -8,7 +8,15 @@ #include "PluginProcessor.h" #include "PluginEditor.h" -#include "config.h" +#ifdef CMS_BUILD_WITH_PROJUCER + // Projucer ne génère pas config.h, on définit les valeurs en dur + #define PROJECT_VERSION_MAJOR 1 + #define PROJECT_VERSION_MINOR 5 + #define PROJECT_VERSION_PATCH 0 + #define PROJECT_DESCRIPTION "Compose Siren - Mecanique Vivante" +#else + #include "config.h" +#endif #include #include @@ -19,7 +27,7 @@ headComponent::headComponent() addAndMakeVisible (labelPluginTitle); addAndMakeVisible (labelPluginSubTitle); - labelPluginTitle.setColour (juce::Label::textColourId, juce::Colours::black); + labelPluginTitle.setColour (juce::Label::textColourId, juce::Colours::white); std::stringstream versionText; versionText @@ -33,11 +41,14 @@ headComponent::headComponent() auto label = "COMPOSE SIREN " + concatenatedString; labelPluginTitle.setText(label, juce::dontSendNotification); - labelPluginTitle.setFont (juce::Font (24.0f, juce::Font::italic)); - labelPluginTitle.setJustificationType (juce::Justification::centred); - labelPluginSubTitle.setText(PROJECT_DESCRIPTION, juce::dontSendNotification); - labelPluginSubTitle.setFont (juce::Font (12.0f, juce::Font::italic)); - labelPluginSubTitle.setJustificationType (juce::Justification::left); + labelPluginTitle.setFont (juce::Font (24.0f, juce::Font::bold)); + labelPluginTitle.setJustificationType (juce::Justification::centredLeft); + + // Afficher le nom de la branche au lieu de la description + labelPluginSubTitle.setText("custom-mix", juce::dontSendNotification); + labelPluginSubTitle.setFont (juce::Font (14.0f, juce::Font::italic)); + labelPluginSubTitle.setJustificationType (juce::Justification::centredLeft); + labelPluginSubTitle.setColour (juce::Label::textColourId, juce::Colours::lightgrey); imgLogo = juce::ImageFileFormat::loadFrom(BinaryData::Picto_Siren_40x37_png, BinaryData::Picto_Siren_40x37_pngSize); } @@ -54,8 +65,8 @@ void headComponent::paint (juce::Graphics& g) void headComponent::resized() { - auto area = getLocalBounds(); - labelPluginTitle.setBounds(area.removeFromTop(30)); + auto area = getLocalBounds().reduced(10, 2); + labelPluginTitle.setBounds(area.removeFromTop(28)); labelPluginSubTitle.setBounds(area.removeFromTop(20)); } @@ -113,20 +124,441 @@ void MainCommandsComponent::resized() } //============================================================================== +//============================================================================== +// MixerStripComponent - Strip de mixage pour une sirène +MixerStripComponent::MixerStripComponent(SirenePlugAudioProcessor& p, int sireneNum) + : audioProcessor(p), sireneNumber(sireneNum) +{ + // Label du nom de la sirène + nameLabel.setText("S" + juce::String(sireneNumber), juce::dontSendNotification); + nameLabel.setJustificationType(juce::Justification::centred); + nameLabel.setColour(juce::Label::textColourId, juce::Colours::white); + addAndMakeVisible(nameLabel); + + // Master Volume (CC70) - volume indépendant pour mixer + masterVolumeLabel.setText("Master (CC70)", juce::dontSendNotification); + masterVolumeLabel.setJustificationType(juce::Justification::centred); + masterVolumeLabel.setColour(juce::Label::textColourId, juce::Colours::white); + masterVolumeLabel.setFont(juce::Font(9.0f)); + addAndMakeVisible(masterVolumeLabel); + + masterVolumeSlider.setSliderStyle(juce::Slider::LinearVertical); + masterVolumeSlider.setRange(0.0, 1.0, 0.01); + masterVolumeSlider.setValue(audioProcessor.mySynth->getMasterVolume(sireneNumber)); + masterVolumeSlider.setTextBoxStyle(juce::Slider::TextBoxBelow, false, 50, 20); + masterVolumeSlider.setColour(juce::Slider::thumbColourId, juce::Colours::lightblue); + masterVolumeSlider.setColour(juce::Slider::trackColourId, juce::Colours::blue); + masterVolumeSlider.addListener(this); + addAndMakeVisible(masterVolumeSlider); + + // Knob de pan rotatif avec couleur pour meilleure visibilité + panKnob.setSliderStyle(juce::Slider::RotaryHorizontalVerticalDrag); + panKnob.setRange(-0.5, 0.5, 0.01); + panKnob.setValue(audioProcessor.mySynth->getPan(sireneNumber, 0) - 0.5); // Convertir de 0-1 à -0.5-0.5 + panKnob.setTextBoxStyle(juce::Slider::TextBoxBelow, false, 50, 20); + panKnob.setColour(juce::Slider::rotarySliderFillColourId, juce::Colours::orange); + panKnob.setColour(juce::Slider::thumbColourId, juce::Colours::yellow); + panKnob.setColour(juce::Slider::rotarySliderOutlineColourId, juce::Colour(80, 80, 80)); + panKnob.addListener(this); + addAndMakeVisible(panKnob); + + // Label pan avec CC + panLabel.setText("Pan (CC10)", juce::dontSendNotification); + panLabel.setJustificationType(juce::Justification::centred); + panLabel.setColour(juce::Label::textColourId, juce::Colours::white); + panLabel.setFont(juce::Font(9.0f)); + addAndMakeVisible(panLabel); + + // Démarrer le timer pour mettre à jour la LED (30 Hz) + startTimer(33); +} + +MixerStripComponent::~MixerStripComponent() +{ + stopTimer(); +} + +void MixerStripComponent::timerCallback() +{ + // Mettre à jour l'état de la LED + bool newLedState = audioProcessor.myMidiInHandler->isNoteOn(sireneNumber); + if (newLedState != ledState) + { + ledState = newLedState; + repaint(); + } + + // Mettre à jour le master volume CC70 si changé par MIDI + float currentMasterVolume = audioProcessor.mySynth->getMasterVolume(sireneNumber); + if (std::abs(masterVolumeSlider.getValue() - currentMasterVolume) > 0.01) + { + masterVolumeSlider.setValue(currentMasterVolume, juce::dontSendNotification); + } +} + +void MixerStripComponent::paint(juce::Graphics& g) +{ + g.setColour(juce::Colour(50, 50, 50)); + g.fillRoundedRectangle(getLocalBounds().toFloat().reduced(2), 5); + g.setColour(juce::Colours::grey); + g.drawRoundedRectangle(getLocalBounds().toFloat().reduced(2), 5, 1); + + // Dessiner la LED Note On/Off en haut à droite + auto ledArea = getLocalBounds().reduced(5); + int ledSize = 10; + int ledX = ledArea.getRight() - ledSize - 2; + int ledY = ledArea.getY() + 2; + + // Ombre/contour de la LED + g.setColour(juce::Colours::black.withAlpha(0.5f)); + g.fillEllipse((float)ledX, (float)ledY, (float)ledSize, (float)ledSize); + + // LED elle-même + if (ledState) + { + // LED allumée - vert brillant avec effet glow + g.setColour(juce::Colours::lime); + g.fillEllipse((float)(ledX + 1), (float)(ledY + 1), (float)(ledSize - 2), (float)(ledSize - 2)); + g.setColour(juce::Colours::green.brighter()); + g.fillEllipse((float)(ledX + 2), (float)(ledY + 2), (float)(ledSize - 4), (float)(ledSize - 4)); + } + else + { + // LED éteinte - gris foncé + g.setColour(juce::Colour(60, 60, 60)); + g.fillEllipse((float)(ledX + 1), (float)(ledY + 1), (float)(ledSize - 2), (float)(ledSize - 2)); + } + + // Dessiner les marqueurs L/C/R pour le pan sous le knob + auto panBounds = panKnob.getBounds(); + g.setColour(juce::Colours::white); + g.setFont(juce::Font(9.0f)); + g.drawText("L", panBounds.getX() - 10, panBounds.getY() + panBounds.getHeight()/2 - 5, 10, 10, juce::Justification::right); + g.drawText("C", panBounds.getX() + panBounds.getWidth()/2 - 5, panBounds.getBottom() + 2, 10, 10, juce::Justification::centred); + g.drawText("R", panBounds.getRight(), panBounds.getY() + panBounds.getHeight()/2 - 5, 10, 10, juce::Justification::left); +} + +void MixerStripComponent::resized() +{ + auto area = getLocalBounds().reduced(5); + nameLabel.setBounds(area.removeFromTop(20)); + area.removeFromTop(5); + + // Master Volume CC70 + masterVolumeLabel.setBounds(area.removeFromTop(15)); + masterVolumeSlider.setBounds(area.removeFromTop(130)); + + area.removeFromTop(5); + panLabel.setBounds(area.removeFromTop(15)); + // Knob de pan - plus grand + panKnob.setBounds(area.removeFromTop(110)); +} + +void MixerStripComponent::sliderValueChanged(juce::Slider* slider) +{ + if (slider == &masterVolumeSlider) + { + // Master Volume CC70 - volume indépendant + audioProcessor.mySynth->setMasterVolume(sireneNumber, (float)masterVolumeSlider.getValue()); + } + else if (slider == &panKnob) + { + audioProcessor.mySynth->setPan(sireneNumber, (float)panKnob.getValue()); + } +} + +//============================================================================== +// ReverbComponent - Section de reverb +ReverbComponent::ReverbComponent(SirenePlugAudioProcessor& p) + : audioProcessor(p) +{ + // Titre + titleLabel.setText("REVERB", juce::dontSendNotification); + titleLabel.setJustificationType(juce::Justification::centred); + titleLabel.setColour(juce::Label::textColourId, juce::Colours::white); + titleLabel.setFont(juce::Font(16.0f, juce::Font::bold)); + addAndMakeVisible(titleLabel); + + // Bouton d'activation + enableButton.setButtonText("Enable (CC64 ch16)"); + enableButton.setToggleState(audioProcessor.mySynth->isReverbEnabled(), juce::dontSendNotification); + enableButton.addListener(this); + addAndMakeVisible(enableButton); + + // Room Size + roomSizeLabel.setText("Room (CC65)", juce::dontSendNotification); + roomSizeLabel.setJustificationType(juce::Justification::centredLeft); + roomSizeLabel.setColour(juce::Label::textColourId, juce::Colours::white); + roomSizeLabel.setFont(juce::Font(10.0f)); + addAndMakeVisible(roomSizeLabel); + + roomSizeSlider.setSliderStyle(juce::Slider::LinearHorizontal); + roomSizeSlider.setRange(0.0, 1.0, 0.01); + roomSizeSlider.setValue(audioProcessor.mySynth->reverb->getroomsize()); + roomSizeSlider.setTextBoxStyle(juce::Slider::TextBoxRight, false, 40, 18); + roomSizeSlider.setColour(juce::Slider::thumbColourId, juce::Colours::cyan); + roomSizeSlider.setColour(juce::Slider::trackColourId, juce::Colours::darkblue); + roomSizeSlider.addListener(this); + addAndMakeVisible(roomSizeSlider); + + // Dry/Wet - 0=100% dry, 0.5=50/50, 1=100% wet + wetLabel.setText("Dry/Wet (CC66)", juce::dontSendNotification); + wetLabel.setJustificationType(juce::Justification::centredLeft); + wetLabel.setColour(juce::Label::textColourId, juce::Colours::white); + wetLabel.setFont(juce::Font(10.0f)); + addAndMakeVisible(wetLabel); + + wetSlider.setSliderStyle(juce::Slider::LinearHorizontal); + wetSlider.setRange(0.0, 1.0, 0.01); + // Calculer la valeur dry/wet actuelle depuis wet et dry + float currentWet = audioProcessor.mySynth->reverb->getwet(); + float currentDry = audioProcessor.mySynth->reverb->getdry(); + float dryWetValue = currentWet / (currentWet + currentDry + 0.001f); // Éviter division par zéro + wetSlider.setValue(dryWetValue); + wetSlider.setTextBoxStyle(juce::Slider::TextBoxRight, false, 40, 18); + wetSlider.setColour(juce::Slider::thumbColourId, juce::Colours::green); + wetSlider.setColour(juce::Slider::trackColourId, juce::Colours::darkgreen); + wetSlider.addListener(this); + addAndMakeVisible(wetSlider); + + // Damp + dampLabel.setText("Damp (CC67)", juce::dontSendNotification); + dampLabel.setJustificationType(juce::Justification::centredLeft); + dampLabel.setColour(juce::Label::textColourId, juce::Colours::white); + dampLabel.setFont(juce::Font(10.0f)); + addAndMakeVisible(dampLabel); + + dampSlider.setSliderStyle(juce::Slider::LinearHorizontal); + dampSlider.setRange(0.0, 1.0, 0.01); + dampSlider.setValue(audioProcessor.mySynth->reverb->getdamp()); + dampSlider.setTextBoxStyle(juce::Slider::TextBoxRight, false, 40, 18); + dampSlider.setColour(juce::Slider::thumbColourId, juce::Colours::orange); + dampSlider.setColour(juce::Slider::trackColourId, juce::Colours::darkorange); + dampSlider.addListener(this); + addAndMakeVisible(dampSlider); + + // Width + widthLabel.setText("Width (CC70)", juce::dontSendNotification); + widthLabel.setJustificationType(juce::Justification::centredLeft); + widthLabel.setColour(juce::Label::textColourId, juce::Colours::white); + widthLabel.setFont(juce::Font(10.0f)); + addAndMakeVisible(widthLabel); + + widthSlider.setSliderStyle(juce::Slider::LinearHorizontal); + widthSlider.setRange(0.0, 1.0, 0.01); + widthSlider.setValue(audioProcessor.mySynth->reverb->getwidth()); + widthSlider.setTextBoxStyle(juce::Slider::TextBoxRight, false, 40, 18); + widthSlider.setColour(juce::Slider::thumbColourId, juce::Colours::violet); + widthSlider.setColour(juce::Slider::trackColourId, juce::Colours::purple); + widthSlider.addListener(this); + addAndMakeVisible(widthSlider); + + // Highpass Filter (CC68 sur canal 16) + highpassLabel.setText("HPF (CC68)", juce::dontSendNotification); + highpassLabel.setJustificationType(juce::Justification::centredLeft); + highpassLabel.setColour(juce::Label::textColourId, juce::Colours::white); + highpassLabel.setFont(juce::Font(10.0f)); + addAndMakeVisible(highpassLabel); + + highpassSlider.setSliderStyle(juce::Slider::LinearHorizontal); + highpassSlider.setRange(20.0, 2000.0, 1.0); + highpassSlider.setValue(audioProcessor.mySynth->getReverbHighpass()); + highpassSlider.setTextBoxStyle(juce::Slider::TextBoxRight, false, 50, 18); + highpassSlider.setTextValueSuffix(" Hz"); + highpassSlider.setColour(juce::Slider::thumbColourId, juce::Colours::gold); + highpassSlider.setColour(juce::Slider::trackColourId, juce::Colour(100, 100, 0)); + highpassSlider.setSkewFactorFromMidPoint(200.0); // Logarithmique + highpassSlider.addListener(this); + addAndMakeVisible(highpassSlider); + + // Lowpass Filter (CC69 sur canal 16) + lowpassLabel.setText("LPF (CC69)", juce::dontSendNotification); + lowpassLabel.setJustificationType(juce::Justification::centredLeft); + lowpassLabel.setColour(juce::Label::textColourId, juce::Colours::white); + lowpassLabel.setFont(juce::Font(10.0f)); + addAndMakeVisible(lowpassLabel); + + lowpassSlider.setSliderStyle(juce::Slider::LinearHorizontal); + lowpassSlider.setRange(2000.0, 20000.0, 1.0); + lowpassSlider.setValue(audioProcessor.mySynth->getReverbLowpass()); + lowpassSlider.setTextBoxStyle(juce::Slider::TextBoxRight, false, 50, 18); + lowpassSlider.setTextValueSuffix(" Hz"); + lowpassSlider.setColour(juce::Slider::thumbColourId, juce::Colours::hotpink); + lowpassSlider.setColour(juce::Slider::trackColourId, juce::Colour(139, 0, 139)); + lowpassSlider.setSkewFactorFromMidPoint(8000.0); // Logarithmique + lowpassSlider.addListener(this); + addAndMakeVisible(lowpassSlider); + + // Info CC sur canal 16 + ccInfoLabel.setText("Canal 16", juce::dontSendNotification); + ccInfoLabel.setJustificationType(juce::Justification::centred); + ccInfoLabel.setColour(juce::Label::textColourId, juce::Colours::yellow); + ccInfoLabel.setFont(juce::Font(14.0f, juce::Font::bold)); + addAndMakeVisible(ccInfoLabel); +} + +ReverbComponent::~ReverbComponent() +{ +} + +void ReverbComponent::paint(juce::Graphics& g) +{ + g.setColour(juce::Colour(40, 40, 60)); + g.fillRoundedRectangle(getLocalBounds().toFloat().reduced(2), 5); + g.setColour(juce::Colours::lightblue); + g.drawRoundedRectangle(getLocalBounds().toFloat().reduced(2), 5, 2); +} + +void ReverbComponent::resized() +{ + auto area = getLocalBounds().reduced(8); + titleLabel.setBounds(area.removeFromTop(22)); + area.removeFromTop(2); + enableButton.setBounds(area.removeFromTop(22)); + area.removeFromTop(5); + + // Sliders horizontaux empilés verticalement + auto sliderHeight = 25; + + // Room Size + auto roomRow = area.removeFromTop(sliderHeight); + roomSizeLabel.setBounds(roomRow.removeFromLeft(80)); + roomSizeSlider.setBounds(roomRow); + area.removeFromTop(3); + + // Dry/Wet + auto wetRow = area.removeFromTop(sliderHeight); + wetLabel.setBounds(wetRow.removeFromLeft(80)); + wetSlider.setBounds(wetRow); + area.removeFromTop(3); + + // Damp + auto dampRow = area.removeFromTop(sliderHeight); + dampLabel.setBounds(dampRow.removeFromLeft(80)); + dampSlider.setBounds(dampRow); + area.removeFromTop(3); + + // Width + auto widthRow = area.removeFromTop(sliderHeight); + widthLabel.setBounds(widthRow.removeFromLeft(80)); + widthSlider.setBounds(widthRow); + area.removeFromTop(3); + + // Highpass + auto hpfRow = area.removeFromTop(sliderHeight); + highpassLabel.setBounds(hpfRow.removeFromLeft(80)); + highpassSlider.setBounds(hpfRow); + area.removeFromTop(3); + + // Lowpass + auto lpfRow = area.removeFromTop(sliderHeight); + lowpassLabel.setBounds(lpfRow.removeFromLeft(80)); + lowpassSlider.setBounds(lpfRow); + + // Info CC en bas + area.removeFromTop(5); + ccInfoLabel.setBounds(area.removeFromTop(20)); +} + +void ReverbComponent::sliderValueChanged(juce::Slider* slider) +{ + if (slider == &roomSizeSlider) + { + audioProcessor.mySynth->reverb->setroomsize((float)roomSizeSlider.getValue()); + } + else if (slider == &wetSlider) + { + // Dry/Wet : 0=100% dry, 0.5=50/50, 1=100% wet + float dryWetValue = (float)wetSlider.getValue(); + audioProcessor.mySynth->reverb->setwet(dryWetValue); + audioProcessor.mySynth->reverb->setdry(1.0f - dryWetValue); + } + else if (slider == &dampSlider) + { + audioProcessor.mySynth->reverb->setdamp((float)dampSlider.getValue()); + } + else if (slider == &widthSlider) + { + audioProcessor.mySynth->reverb->setwidth((float)widthSlider.getValue()); + } + else if (slider == &highpassSlider) + { + audioProcessor.mySynth->setReverbHighpass((float)highpassSlider.getValue()); + } + else if (slider == &lowpassSlider) + { + audioProcessor.mySynth->setReverbLowpass((float)lowpassSlider.getValue()); + } +} + +void ReverbComponent::buttonClicked(juce::Button* button) +{ + if (button == &enableButton) + { + audioProcessor.mySynth->setReverbEnabled(enableButton.getToggleState()); + } +} + +//============================================================================== +// MixerComponent - Mixeur complet +MixerComponent::MixerComponent(SirenePlugAudioProcessor& p) + : audioProcessor(p) +{ + // Créer les 7 strips + for (int i = 0; i < 7; i++) + { + strips[i] = std::make_unique(audioProcessor, i + 1); + addAndMakeVisible(strips[i].get()); + } + + // Créer la section reverb + reverb = std::make_unique(audioProcessor); + addAndMakeVisible(reverb.get()); +} + +MixerComponent::~MixerComponent() +{ +} + +void MixerComponent::paint(juce::Graphics& g) +{ + g.setColour(juce::Colour(30, 30, 30)); + g.fillRoundedRectangle(getLocalBounds().toFloat(), 10); +} + +void MixerComponent::resized() +{ + auto area = getLocalBounds().reduced(10); + int stripWidth = 70; // Réduit - un seul fader maintenant + int reverbWidth = 210; // Pour 6 knobs + + // Positionner les 7 strips + for (int i = 0; i < 7; i++) + { + strips[i]->setBounds(area.removeFromLeft(stripWidth)); + area.removeFromLeft(3); // Espacement + } + + // Positionner la reverb + reverb->setBounds(area); +} + +//============================================================================== //============================================================================== SirenePlugAudioProcessorEditor::SirenePlugAudioProcessorEditor (SirenePlugAudioProcessor& p) - : AudioProcessorEditor (&p), audioProcessor (p), mainCommands(audioProcessor) + : AudioProcessorEditor (&p), audioProcessor (p), mainCommands(audioProcessor), mixer(audioProcessor) { //mainCommands = new MainCommandsComponent(audioProcessor); - setSize (300, 200); + setSize (750, 400); addAndMakeVisible (head); addAndMakeVisible (mainCommands); + addAndMakeVisible (mixer); //mainCommands.resetButton.addListener(this); @@ -143,7 +575,7 @@ SirenePlugAudioProcessorEditor::~SirenePlugAudioProcessorEditor() void SirenePlugAudioProcessorEditor::paint (juce::Graphics& g) { - g.fillAll (juce::Colour (255, 153, 0)); // background color (orange) + g.fillAll (juce::Colour (45, 45, 45)); // background color (gris foncé) } @@ -153,7 +585,8 @@ void SirenePlugAudioProcessorEditor::resized() // void Component::setBounds (int x, int y, int width, int height) - top left head.setBounds(0, 0, getWidth(), 50); - mainCommands.setBounds (10, 50, 100, 40); + mainCommands.setBounds (10, 55, 100, 40); + mixer.setBounds(10, 100, getWidth() - 20, 290); } diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h index e2fabbd..5049d26 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -54,6 +54,92 @@ class MainCommandsComponent : public juce::Component //============================================================================== +//============================================================================== +// Strip de mixage individuelle pour une sirène +class MixerStripComponent : public juce::Component, + public juce::Slider::Listener, + private juce::Timer +{ +public: + MixerStripComponent(SirenePlugAudioProcessor& p, int sireneNum); + ~MixerStripComponent(); + + void paint (juce::Graphics&) override; + void resized() override; + void sliderValueChanged(juce::Slider* slider) override; + +private: + SirenePlugAudioProcessor& audioProcessor; + int sireneNumber; + + juce::Label nameLabel; + juce::Slider panKnob; + juce::Label panLabel; + juce::Label masterVolumeLabel; // Pour le master volume CC70 + juce::Slider masterVolumeSlider; // Volume indépendant CC70 + + // LED Note On/Off + bool ledState = false; + + void timerCallback(); +}; + +//============================================================================== +// Section de reverb +class ReverbComponent : public juce::Component, + public juce::Slider::Listener, + public juce::Button::Listener +{ +public: + ReverbComponent(SirenePlugAudioProcessor& p); + ~ReverbComponent(); + + void paint (juce::Graphics&) override; + void resized() override; + void sliderValueChanged(juce::Slider* slider) override; + void buttonClicked(juce::Button* button) override; + +private: + SirenePlugAudioProcessor& audioProcessor; + + juce::ToggleButton enableButton; + juce::Slider roomSizeSlider; + juce::Slider wetSlider; + juce::Slider dampSlider; + juce::Slider widthSlider; + juce::Slider highpassSlider; + juce::Slider lowpassSlider; + + juce::Label titleLabel; + juce::Label roomSizeLabel; + juce::Label wetLabel; + juce::Label dampLabel; + juce::Label widthLabel; + juce::Label highpassLabel; + juce::Label lowpassLabel; + juce::Label ccInfoLabel; // Pour afficher les CC sur canal 16 +}; + +//============================================================================== +// Composant mixeur complet +class MixerComponent : public juce::Component +{ +public: + MixerComponent(SirenePlugAudioProcessor& p); + ~MixerComponent(); + + void paint (juce::Graphics&) override; + void resized() override; + +private: + SirenePlugAudioProcessor& audioProcessor; + + std::unique_ptr strips[7]; + std::unique_ptr reverb; +}; + +//============================================================================== + class SirenePlugAudioProcessorEditor : public juce::AudioProcessorEditor//, public juce::Button::Listener @@ -76,6 +162,7 @@ class SirenePlugAudioProcessorEditor : public juce::AudioProcessorEditor//, pub headComponent head; MainCommandsComponent mainCommands; + MixerComponent mixer; /* void buttonClicked (juce::Button* button) override diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index ed1ee71..69a2f00 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -1,283 +1,504 @@ -/* - ============================================================================== - - This file contains the basic framework code for a JUCE plugin processor. - - ============================================================================== -*/ - -#include "PluginProcessor.h" -#include "PluginEditor.h" - -#include - -//============================================================================== -SirenePlugAudioProcessor::SirenePlugAudioProcessor() -#ifndef JucePlugin_PreferredChannelConfigurations - : AudioProcessor (BusesProperties() - #if ! JucePlugin_IsMidiEffect - #if ! JucePlugin_IsSynth - .withInput ("Input", juce::AudioChannelSet::stereo(), true) - #endif - .withOutput ("Output", juce::AudioChannelSet::stereo(), true) - #endif - ) -#endif -{ - startTimer(1); - this->mySynth = new Synth(); - auto onVelocityChanged = - [this](int ch, int val) - { - mySynth->setVelocite(ch, val); - }; - - auto onEnginePitchChanged = - [this](int ch, int val) - { - mySynth->setVitesse(ch, val); - }; - - myMidiInHandler = new MidiIn(onVelocityChanged, onEnginePitchChanged); - -} - -SirenePlugAudioProcessor::~SirenePlugAudioProcessor() -{ -} - -//============================================================================== -const juce::String SirenePlugAudioProcessor::getName() const -{ - return JucePlugin_Name; -} - -bool SirenePlugAudioProcessor::acceptsMidi() const -{ - #if JucePlugin_WantsMidiInput - return true; - #else - return false; - #endif -} - -bool SirenePlugAudioProcessor::producesMidi() const -{ - #if JucePlugin_ProducesMidiOutput - return true; - #else - return false; - #endif -} - -bool SirenePlugAudioProcessor::isMidiEffect() const -{ - #if JucePlugin_IsMidiEffect - return true; - #else - return false; - #endif -} - -double SirenePlugAudioProcessor::getTailLengthSeconds() const -{ - return 0.0; -} - -int SirenePlugAudioProcessor::getNumPrograms() -{ - return 1; // NB: some hosts don't cope very well if you tell them there are 0 programs, - // so this should be at least 1, even if you're not really implementing programs. -} - -int SirenePlugAudioProcessor::getCurrentProgram() -{ - return 0; -} - -void SirenePlugAudioProcessor::setCurrentProgram (int index) -{ -} - -const juce::String SirenePlugAudioProcessor::getProgramName (int index) -{ - return {}; -} - -void SirenePlugAudioProcessor::changeProgramName (int index, const juce::String& newName) -{ -} - -//============================================================================== -void SirenePlugAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock) -{ - -} - -void SirenePlugAudioProcessor::releaseResources() -{ - // When playback stops, you can use this as an opportunity to free up any - // spare memory, etc. -} - -#ifndef JucePlugin_PreferredChannelConfigurations -bool SirenePlugAudioProcessor::isBusesLayoutSupported (const BusesLayout& layouts) const -{ - #if JucePlugin_IsMidiEffect - juce::ignoreUnused (layouts); - return true; - #else - // This is the place where you check if the layout is supported. - // In this template code we only support mono or stereo. - if (layouts.getMainOutputChannelSet() != juce::AudioChannelSet::mono() && - layouts.getMainOutputChannelSet() != juce::AudioChannelSet::stereo()) - return false; - - // This checks if the input layout matches the output layout - #if !JucePlugin_IsSynth - if (layouts.getMainOutputChannelSet() != layouts.getMainInputChannelSet()) - return false; - #endif - - return true; - #endif -} -#endif - -void SirenePlugAudioProcessor::processBlock (juce::AudioBuffer& buffer, juce::MidiBuffer& midiMessages) -{ - buffer.clear(); - - // process midi message - for (const auto meta : midiMessages) { - const auto msg = meta.getMessage(); - midiMessageIntArray = getIntFromMidiMessage(msg.getRawData(), msg.getRawDataSize()); - myMidiInHandler->handleMIDIMessage2(midiMessageIntArray[0], midiMessageIntArray[1], midiMessageIntArray[2]); - - } - - float sampleS1 = 0; - float sampleS2 = 0; - float sampleS3 = 0; - float sampleS4 = 0; - float sampleS5 = 0; - float sampleS6 = 0; - float sampleS7 = 0; - - juce::ScopedNoDenormals noDenormals; - - auto* channelLeft = buffer.getWritePointer(0); - auto* channelRight = buffer.getWritePointer(1); - - for (auto sample = 0; sample < buffer.getNumSamples(); sample++) { - // in original code, this timer updating dsp computations related to note slide, vibrato & tremolo is - // only called once every block, and block is hardcoded to be 512 - // implement this with a counter - if(sampleCountForMidiInTimer % 512 == 0) - { - myMidiInHandler->timerAudio(); - } - ++sampleCountForMidiInTimer; - - sampleS1 = mySynth->s1->calculwave(); - sampleS2 = mySynth->s2->calculwave(); - sampleS3 = mySynth->s3->calculwave(); - sampleS4 = mySynth->s4->calculwave(); - sampleS5 = mySynth->s5->calculwave(); - sampleS6 = mySynth->s6->calculwave(); - sampleS7 = mySynth->s7->calculwave(); - - channelLeft[sample] = - sampleS1 * mySynth->getPan(1,0) + - sampleS2 * mySynth->getPan(2,0) + - sampleS3 * mySynth->getPan(3,0) + - sampleS4 * mySynth->getPan(4,0) + - sampleS5 * mySynth->getPan(5,0) + - sampleS6 * mySynth->getPan(6,0) + - sampleS7 * mySynth->getPan(7,0); - - channelRight[sample] = - sampleS1 * mySynth->getPan(1,1) + - sampleS2 * mySynth->getPan(2,1) + - sampleS3 * mySynth->getPan(3,1) + - sampleS4 * mySynth->getPan(4,1) + - sampleS5 * mySynth->getPan(5,1) + - sampleS6 * mySynth->getPan(6,1) + - sampleS7 * mySynth->getPan(7,1); - - if(channelLeft[sample] != 0) { - ; - } - } -} - -//============================================================================== -bool SirenePlugAudioProcessor::hasEditor() const -{ - return true; // (change this to false if you choose to not supply an editor) -} - -juce::AudioProcessorEditor* SirenePlugAudioProcessor::createEditor() -{ - return new SirenePlugAudioProcessorEditor (*this); -} - -//============================================================================== -void SirenePlugAudioProcessor::getStateInformation (juce::MemoryBlock& destData) -{ - // You should use this method to store your parameters in the memory block. - // You could do that either as raw data, or use the XML or ValueTree classes - // as intermediaries to make it easy to save and load complex data. -} - -void SirenePlugAudioProcessor::setStateInformation (const void* data, int sizeInBytes) -{ - // You should use this method to restore your parameters from this memory block, - // whose contents will have been created by the getStateInformation() call. -} - - - -int* SirenePlugAudioProcessor::getIntFromMidiMessage(const void * data, int size) -// From a midi message and its size, output the midi message as an array of 3 integers -{ - static int arr[3]; - unsigned int x; - - juce::String hexaMessage = juce::String::toHexString (data, size); // convert message to hexadecimal string - - juce::String value; - int begin, end; - // loop to split the string in 3 and convert each part in integer - for (int i = 0; i < 3; ++i) - { - std::stringstream ss; - begin = i*3; - end = begin + 2; - value = hexaMessage.substring(begin, end); - ss << std::hex << value; - ss >> x; - arr[i] = static_cast(x); - } - return arr; -} - -void SirenePlugAudioProcessor::timerCallback() -{ - mySynth->s1->setnote(); - mySynth->s2->setnote(); - mySynth->s3->setnote(); - mySynth->s4->setnote(); - mySynth->s5->setnote(); - mySynth->s6->setnote(); - mySynth->s7->setnote(); -} - - - -//============================================================================== -// This creates new instances of the plugin.. -juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() -{ - return new SirenePlugAudioProcessor(); -} +/* + ============================================================================== + + This file contains the basic framework code for a JUCE plugin processor. + + ============================================================================== +*/ + +#include "PluginProcessor.h" +#include "PluginEditor.h" + +#include + +// Constante pour atténuer S7 (piccolo) qui est trop fort +static const float S7_ATTENUATION = 0.3f; + +// Variable globale pour accéder au processor depuis Sirene +SirenePlugAudioProcessor* g_processor = nullptr; + +//============================================================================== +SirenePlugAudioProcessor::SirenePlugAudioProcessor() +#ifndef JucePlugin_PreferredChannelConfigurations + : AudioProcessor (BusesProperties() + #if ! JucePlugin_IsMidiEffect + #if ! JucePlugin_IsSynth + .withInput ("Input", juce::AudioChannelSet::stereo(), true) + #endif + .withOutput ("Output", juce::AudioChannelSet::stereo(), true) + #endif + ) +#endif +{ + g_processor = this; // Assigner l'instance courante + startTimer(1); + this->mySynth = new Synth(); + + auto onVelocityChanged = + [this](int ch, int val) + { + mySynth->setVelocite(ch, val); + }; + + auto onEnginePitchChanged = + [this](int ch, int val) + { + mySynth->setVitesse(ch, val); + }; + + myMidiInHandler = new MidiIn(onVelocityChanged, onEnginePitchChanged); + +} + +SirenePlugAudioProcessor::~SirenePlugAudioProcessor() +{ +} + +//============================================================================== +const juce::String SirenePlugAudioProcessor::getName() const +{ + return JucePlugin_Name; +} + +bool SirenePlugAudioProcessor::acceptsMidi() const +{ + #if JucePlugin_WantsMidiInput + return true; + #else + return false; + #endif +} + +bool SirenePlugAudioProcessor::producesMidi() const +{ + #if JucePlugin_ProducesMidiOutput + return true; + #else + return false; + #endif +} + +bool SirenePlugAudioProcessor::isMidiEffect() const +{ + #if JucePlugin_IsMidiEffect + return true; + #else + return false; + #endif +} + +double SirenePlugAudioProcessor::getTailLengthSeconds() const +{ + return 0.0; +} + +int SirenePlugAudioProcessor::getNumPrograms() +{ + return 1; // NB: some hosts don't cope very well if you tell them there are 0 programs, + // so this should be at least 1, even if you're not really implementing programs. +} + +int SirenePlugAudioProcessor::getCurrentProgram() +{ + return 0; +} + +void SirenePlugAudioProcessor::setCurrentProgram (int index) +{ +} + +const juce::String SirenePlugAudioProcessor::getProgramName (int index) +{ + return {}; +} + +void SirenePlugAudioProcessor::changeProgramName (int index, const juce::String& newName) +{ +} + +//============================================================================== +void SirenePlugAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock) +{ + // Propager le sample rate aux composants qui en ont besoin + if (mySynth != nullptr) { + mySynth->setSampleRate(sampleRate); + } + + if (myMidiInHandler != nullptr) { + myMidiInHandler->setSampleRate(sampleRate); + } +} + +void SirenePlugAudioProcessor::releaseResources() +{ + // When playback stops, you can use this as an opportunity to free up any + // spare memory, etc. +} + +#ifndef JucePlugin_PreferredChannelConfigurations +bool SirenePlugAudioProcessor::isBusesLayoutSupported (const BusesLayout& layouts) const +{ + #if JucePlugin_IsMidiEffect + juce::ignoreUnused (layouts); + return true; + #else + // This is the place where you check if the layout is supported. + // In this template code we only support mono or stereo. + if (layouts.getMainOutputChannelSet() != juce::AudioChannelSet::mono() && + layouts.getMainOutputChannelSet() != juce::AudioChannelSet::stereo()) + return false; + + // This checks if the input layout matches the output layout + #if !JucePlugin_IsSynth + if (layouts.getMainOutputChannelSet() != layouts.getMainInputChannelSet()) + return false; + #endif + + return true; + #endif +} +#endif + +void SirenePlugAudioProcessor::processBlock (juce::AudioBuffer& buffer, juce::MidiBuffer& midiMessages) +{ + // Supprimer les messages de debug trop fréquents qui causent des plantages + + buffer.clear(); + + // process midi message + for (const auto meta : midiMessages) { + const auto msg = meta.getMessage(); + midiMessageIntArray = getIntFromMidiMessage(msg.getRawData(), msg.getRawDataSize()); + + // Gérer les Control Change pour mixeur et reverb + int statusByte = midiMessageIntArray[0]; + int ccNumber = midiMessageIntArray[1]; + int ccValue = midiMessageIntArray[2]; + + if (statusByte >= 176 && statusByte < 192) { // Control Change messages + int channel = statusByte - 175; // Canal MIDI (1-16) + + if (channel >= 1 && channel <= 7) { // Canaux 1-7 : sirènes individuelles + if (ccNumber == 10) { // CC10 = Pan + float pan = (ccValue / 127.0f) - 0.5f; + mySynth->setPan(channel, pan); + } + else if (ccNumber == 70) { // CC70 = Master Volume indépendant + float volume = ccValue / 127.0f; + mySynth->setMasterVolume(channel, volume); + } + } + else if (channel == 16) { // Canal 16 : contrôles reverb globale et reset + switch (ccNumber) { + case 121: // Reset All Controllers - Reset toutes les sirènes + myMidiInHandler->resetSireneCh(1); + myMidiInHandler->resetSireneCh(2); + myMidiInHandler->resetSireneCh(3); + myMidiInHandler->resetSireneCh(4); + myMidiInHandler->resetSireneCh(5); + myMidiInHandler->resetSireneCh(6); + myMidiInHandler->resetSireneCh(7); + break; + case 64: // Enable reverb + mySynth->setReverbEnabled(ccValue >= 64); + break; + case 65: // Room Size + mySynth->reverb->setroomsize(ccValue / 127.0f); + break; + case 66: // Dry/Wet + { + float dryWet = ccValue / 127.0f; + mySynth->reverb->setwet(dryWet); + mySynth->reverb->setdry(1.0f - dryWet); + } + break; + case 67: // Damp + mySynth->reverb->setdamp(ccValue / 127.0f); + break; + case 68: // Highpass (20-2000 Hz) + { + float freq = 20.0f + (ccValue / 127.0f) * 1980.0f; + mySynth->setReverbHighpass(freq); + } + break; + case 69: // Lowpass (2kHz-20kHz) + { + float freq = 2000.0f + (ccValue / 127.0f) * 18000.0f; + mySynth->setReverbLowpass(freq); + } + break; + case 70: // Width + mySynth->reverb->setwidth(ccValue / 127.0f); + break; + } + } + } + + myMidiInHandler->handleMIDIMessage2(midiMessageIntArray[0], midiMessageIntArray[1], midiMessageIntArray[2]); + + } + + float sampleS1 = 0; + float sampleS2 = 0; + float sampleS3 = 0; + float sampleS4 = 0; + float sampleS5 = 0; + float sampleS6 = 0; + float sampleS7 = 0; + + juce::ScopedNoDenormals noDenormals; + + auto* channelLeft = buffer.getWritePointer(0); + auto* channelRight = buffer.getWritePointer(1); + + for (auto sample = 0; sample < buffer.getNumSamples(); sample++) { + // in original code, this timer updating dsp computations related to note slide, vibrato & tremolo is + // only called once every block, and block is hardcoded to be 512 + // implement this with a counter + if(sampleCountForMidiInTimer % 512 == 0) + { + myMidiInHandler->timerAudio(); + } + ++sampleCountForMidiInTimer; + + // Calculer les samples de chaque sirène + sampleS1 = mySynth->s1->calculwave(); + sampleS2 = mySynth->s2->calculwave(); + sampleS3 = mySynth->s3->calculwave(); + sampleS4 = mySynth->s4->calculwave(); + sampleS5 = mySynth->s5->calculwave(); + sampleS6 = mySynth->s6->calculwave(); + sampleS7 = mySynth->s7->calculwave(); + + // Appliquer le master volume (CC70) - multiplicatif avec le volume original + sampleS1 *= mySynth->getMasterVolume(1); + sampleS2 *= mySynth->getMasterVolume(2); + sampleS3 *= mySynth->getMasterVolume(3); + sampleS4 *= mySynth->getMasterVolume(4); + sampleS5 *= mySynth->getMasterVolume(5); + sampleS6 *= mySynth->getMasterVolume(6); + sampleS7 *= mySynth->getMasterVolume(7); + + // Mixer avec panoramique + channelLeft[sample] = + sampleS1 * mySynth->getPan(1,0) + + sampleS2 * mySynth->getPan(2,0) + + sampleS3 * mySynth->getPan(3,0) + + sampleS4 * mySynth->getPan(4,0) + + sampleS5 * mySynth->getPan(5,0) + + sampleS6 * mySynth->getPan(6,0) + + sampleS7 * mySynth->getPan(7,0) * S7_ATTENUATION; + + channelRight[sample] = + sampleS1 * mySynth->getPan(1,1) + + sampleS2 * mySynth->getPan(2,1) + + sampleS3 * mySynth->getPan(3,1) + + sampleS4 * mySynth->getPan(4,1) + + sampleS5 * mySynth->getPan(5,1) + + sampleS6 * mySynth->getPan(6,1) + + sampleS7 * mySynth->getPan(7,1) * S7_ATTENUATION; + + // TODO: Investiguer pourquoi le niveau audio est ~1000x trop faible sur Linux + // Fix temporaire : gain de compensation sur Linux uniquement + #if defined(__linux__) || defined(__unix__) + const float LINUX_OUTPUT_GAIN = 50.0f; + channelLeft[sample] *= LINUX_OUTPUT_GAIN; + channelRight[sample] *= LINUX_OUTPUT_GAIN; + #endif + + if(channelLeft[sample] != 0) { + ; + } + } + + // Appliquer la reverb avec filtres si activée + if(buffer.getNumSamples() > 0) { + auto* left = buffer.getWritePointer(0); + auto* right = buffer.getWritePointer(1); + mySynth->processReverbWithFilters(left, right, buffer.getNumSamples()); + } +} + +//============================================================================== +bool SirenePlugAudioProcessor::hasEditor() const +{ + return true; // (change this to false if you choose to not supply an editor) +} + +juce::AudioProcessorEditor* SirenePlugAudioProcessor::createEditor() +{ + return new SirenePlugAudioProcessorEditor (*this); +} + +//============================================================================== +void SirenePlugAudioProcessor::getStateInformation (juce::MemoryBlock& destData) +{ + // Sauvegarder les paramètres du mixeur et de la reverb + juce::ValueTree state("MixerState"); + + // Sauvegarder les pans + for (int i = 1; i <= 7; i++) + { + // getPan retourne une valeur de 0.5 à 1.5, donc on sauvegarde la valeur brute + float panLeft = mySynth->getPan(i, 0); + state.setProperty("pan_s" + juce::String(i), panLeft - 0.5, nullptr); + } + + // Sauvegarder les volumes via le système original (CC7) + for (int i = 1; i <= 7; i++) + { + state.setProperty("volume_s" + juce::String(i), myMidiInHandler->getVolumeFinal(i), nullptr); + } + + // Sauvegarder les master volumes (CC70) + for (int i = 1; i <= 7; i++) + { + state.setProperty("master_volume_s" + juce::String(i), mySynth->getMasterVolume(i), nullptr); + } + + // Sauvegarder les paramètres de reverb + state.setProperty("reverb_enabled", mySynth->isReverbEnabled(), nullptr); + state.setProperty("reverb_roomsize", mySynth->reverb->getroomsize(), nullptr); + state.setProperty("reverb_wet", mySynth->reverb->getwet(), nullptr); + state.setProperty("reverb_damp", mySynth->reverb->getdamp(), nullptr); + state.setProperty("reverb_width", mySynth->reverb->getwidth(), nullptr); + state.setProperty("reverb_highpass", mySynth->getReverbHighpass(), nullptr); + state.setProperty("reverb_lowpass", mySynth->getReverbLowpass(), nullptr); + + // Convertir en XML et sauvegarder + auto xml = state.createXml(); + copyXmlToBinary(*xml, destData); +} + +void SirenePlugAudioProcessor::setStateInformation (const void* data, int sizeInBytes) +{ + // Restaurer les paramètres depuis les données sauvegardées + auto xmlState = getXmlFromBinary(data, sizeInBytes); + + if (xmlState != nullptr) + { + if (xmlState->hasTagName("MixerState")) + { + juce::ValueTree state = juce::ValueTree::fromXml(*xmlState); + + // Restaurer les pans + for (int i = 1; i <= 7; i++) + { + if (state.hasProperty("pan_s" + juce::String(i))) + { + float pan = state.getProperty("pan_s" + juce::String(i)); + mySynth->setPan(i, pan); + } + } + + // Restaurer les volumes via le système original (CC7) + for (int i = 1; i <= 7; i++) + { + if (state.hasProperty("volume_s" + juce::String(i))) + { + float volume = state.getProperty("volume_s" + juce::String(i)); + myMidiInHandler->setVolumeFinal(i, volume); + } + } + + // Restaurer les master volumes (CC70) + for (int i = 1; i <= 7; i++) + { + if (state.hasProperty("master_volume_s" + juce::String(i))) + { + float masterVol = state.getProperty("master_volume_s" + juce::String(i)); + mySynth->setMasterVolume(i, masterVol); + } + } + + // Restaurer les paramètres de reverb + if (state.hasProperty("reverb_enabled")) + { + bool enabled = state.getProperty("reverb_enabled"); + mySynth->setReverbEnabled(enabled); + } + + if (state.hasProperty("reverb_roomsize")) + { + float roomsize = state.getProperty("reverb_roomsize"); + mySynth->reverb->setroomsize(roomsize); + } + + if (state.hasProperty("reverb_wet")) + { + float wet = state.getProperty("reverb_wet"); + mySynth->reverb->setwet(wet); + } + + if (state.hasProperty("reverb_damp")) + { + float damp = state.getProperty("reverb_damp"); + mySynth->reverb->setdamp(damp); + } + + if (state.hasProperty("reverb_width")) + { + float width = state.getProperty("reverb_width"); + mySynth->reverb->setwidth(width); + } + + if (state.hasProperty("reverb_highpass")) + { + float hpf = state.getProperty("reverb_highpass"); + mySynth->setReverbHighpass(hpf); + } + + if (state.hasProperty("reverb_lowpass")) + { + float lpf = state.getProperty("reverb_lowpass"); + mySynth->setReverbLowpass(lpf); + } + } + } +} + + + +int* SirenePlugAudioProcessor::getIntFromMidiMessage(const void * data, int size) +// From a midi message and its size, output the midi message as an array of 3 integers +{ + static int arr[3]; + unsigned int x; + + juce::String hexaMessage = juce::String::toHexString (data, size); // convert message to hexadecimal string + + juce::String value; + int begin, end; + // loop to split the string in 3 and convert each part in integer + for (int i = 0; i < 3; ++i) + { + std::stringstream ss; + begin = i*3; + end = begin + 2; + value = hexaMessage.substring(begin, end); + ss << std::hex << value; + ss >> x; + arr[i] = static_cast(x); + } + return arr; +} + +void SirenePlugAudioProcessor::timerCallback() +{ + mySynth->s1->setnote(); + mySynth->s2->setnote(); + mySynth->s3->setnote(); + mySynth->s4->setnote(); + mySynth->s5->setnote(); + mySynth->s6->setnote(); + mySynth->s7->setnote(); +} + + + +//============================================================================== +// This creates new instances of the plugin.. +juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() +{ + return new SirenePlugAudioProcessor(); +} diff --git a/Source/Sirene.cpp b/Source/Sirene.cpp index 4e8119f..e2ee97c 100644 --- a/Source/Sirene.cpp +++ b/Source/Sirene.cpp @@ -16,9 +16,14 @@ Sirene::Sirene(const std::string& str, const std::string& dataFilePath) : name(str) { + // Initialiser le sample rate par défaut à 44.1kHz + sampleRate = 44100.0; + deuxPieSampleRate = (2.0 * M_PI) / sampleRate; + memset(&tabAmp, 0, sizeof(tabAmp)); memset(&tabFreq, 0, sizeof(tabFreq)); memset(&dureTabs, 0, sizeof(dureTabs)); + memset(&vectorInterval, 0, sizeof(vectorInterval)); std::string sireneNameForData(name); @@ -33,59 +38,106 @@ name(str) { // readDataFromBinaryData(sireneNameForData); // we read from files instead, still slow but acceptable : + // S7 (Piccolo) utilise les données vectorInterval de S5 + std::string vectorIntervalSuffix = sireneNameForData; + if (name == "S7") { + vectorIntervalSuffix = "S5"; + } + readDataFromBinaryFile( dataFilePath, "dataAmp" + sireneNameForData, "dataFreq" + sireneNameForData, - "datadureTabs" + sireneNameForData + "datadureTabs" + sireneNameForData, + "dataVectorInterval" + vectorIntervalSuffix ); - std::cout << "tabFreq[46][20][3] : " << std::fixed << std::setprecision(7) << tabFreq[46][20][3] << std::endl; + // Sirene constructor - if (name=="S1") {noteMidiCentMax=7200; pourcentClapetOff=7; noteMin=24; coeffPicolo=1.; inertiaFactorTweak = 24;} - else if (name=="S2") {noteMidiCentMax=7200; pourcentClapetOff=7; noteMin=24; coeffPicolo=1.; inertiaFactorTweak = 24;} - else if (name=="S3") {noteMidiCentMax=6400; pourcentClapetOff=7; noteMin=24; coeffPicolo=1.; inertiaFactorTweak = 12;} - else if (name=="S4") {noteMidiCentMax=6500; pourcentClapetOff=15; noteMin=24; coeffPicolo=1.; inertiaFactorTweak = 12;} + if (name=="S1") {noteMidiCentMax=7200; pourcentClapetOff=7; noteMin=24; coeffPicolo=1.; inertiaFactorTweak = 32;} + else if (name=="S2") {noteMidiCentMax=7200; pourcentClapetOff=7; noteMin=24; coeffPicolo=1.; inertiaFactorTweak = 32;} + else if (name=="S3") {noteMidiCentMax=6400; pourcentClapetOff=7; noteMin=24; coeffPicolo=1.; inertiaFactorTweak = 28;} + else if (name=="S4") {noteMidiCentMax=6500; pourcentClapetOff=15; noteMin=24; coeffPicolo=1.; inertiaFactorTweak = 28;} else if (name=="S5") {noteMidiCentMax=7900; pourcentClapetOff=7; noteMin=36; coeffPicolo=1.; inertiaFactorTweak = 48;} else if (name=="S6") {noteMidiCentMax=7900; pourcentClapetOff=7; noteMin=36; coeffPicolo=1.; inertiaFactorTweak = 48;} - else if (name=="S7") {noteMidiCentMax=7900; pourcentClapetOff=7; noteMin=36; coeffPicolo=2.; inertiaFactorTweak = 24;} + else if (name=="S7") {noteMidiCentMax=7900; pourcentClapetOff=7; noteMin=36; coeffPicolo=2.; inertiaFactorTweak = 36;} //pat } Sirene::~Sirene() {} +void Sirene::setSampleRate(double newSampleRate) { + sampleRate = newSampleRate; + deuxPieSampleRate = (2.0 * M_PI) / sampleRate; + + // Recalculer les pitchSchift avec le nouveau sample rate + if (midiCentVoulue > 0) { + setMidicent(midiCentVoulue); + } +} -void Sirene::readDataFromBinaryFile(std::string dataFilePath, std::string tabAmpFile, std::string tabFreqFile, std::string dureTabFile){ +void Sirene::readDataFromBinaryFile(std::string dataFilePath, std::string tabAmpFile, std::string tabFreqFile, std::string dureTabFile, std::string vectorIntervalFile){ std::ifstream myfile; + bool allLoaded = true; // Read tabAmpFile - myfile.open(dataFilePath + tabAmpFile, std::ios::binary); + std::string fullPath = dataFilePath + tabAmpFile; + myfile.open(fullPath, std::ios::binary); if (myfile.is_open()) { - myfile.read(reinterpret_cast(tabAmp), sizeof tabAmp); // todo: check that input.gcount() is the number of bytes expected + myfile.read(reinterpret_cast(tabAmp), sizeof tabAmp); myfile.close(); } - else std::cout << "Error. Binary file not found: " << dataFilePath + tabAmpFile << "\n"; + else { + DBG("✗ FAILED to load " << fullPath); + allLoaded = false; + } // Read dataFreqFile - myfile.open(dataFilePath + tabFreqFile, std::ios::binary); + fullPath = dataFilePath + tabFreqFile; + myfile.open(fullPath, std::ios::binary); if (myfile.is_open()) { - myfile.read(reinterpret_cast(tabFreq), sizeof tabFreq); // todo: check that input.gcount() is the number of bytes expected + myfile.read(reinterpret_cast(tabFreq), sizeof tabFreq); myfile.close(); } - else std::cout << "Error. Binary file not found.\n"; + else { + DBG("✗ FAILED to load " << fullPath); + allLoaded = false; + } // Read dureTabFile - myfile.open(dataFilePath + dureTabFile, std::ios::binary); + fullPath = dataFilePath + dureTabFile; + myfile.open(fullPath, std::ios::binary); if (myfile.is_open()) { - myfile.read(reinterpret_cast(dureTabs), sizeof dureTabs); // todo: check that input.gcount() is the number of bytes expected + myfile.read(reinterpret_cast(dureTabs), sizeof dureTabs); myfile.close(); } - else std::cout << "Error. Binary file not found.\n"; + else { + DBG("✗ FAILED to load " << fullPath); + allLoaded = false; + } + + // Read vectorIntervalFile + fullPath = dataFilePath + vectorIntervalFile; + myfile.open(fullPath, std::ios::binary); + if (myfile.is_open()) + { + myfile.read(reinterpret_cast(vectorInterval), sizeof vectorInterval); + myfile.close(); + } + else { + DBG("✗ FAILED to load " << fullPath); + allLoaded = false; + } + + // Log seulement en cas d'erreur + if (!allLoaded) { + DBG("✗ Some resources failed to load for " << name); + } } @@ -95,8 +147,15 @@ void Sirene::setMidicent(int note) { else if (midiCentVoulue % 100 == 99) midiCentVoulue++; noteInf = midiCentVoulue / 100; noteSup = noteInf + 1; - pitchSchift[noteInf] = ((440.0 * pow(2., ((midiCentVoulue/100.) - 69.) / 12.)) / (440.0 * pow(2., ((noteInf) - 69.) / 12.))) * DeuxPieSampleRate; - pitchSchift[noteSup] = ((440.0 * pow(2., ((midiCentVoulue/100.) - 69.) / 12.)) / (440.0 * pow(2., ((noteSup) - 69.) / 12.))) * DeuxPieSampleRate; + + // Réinitialiser les compteurs de fenêtres FFT pour les nouvelles notes + countP[noteInf] = 0; + countP[noteSup] = 0; + countKInf = 0; + countKSup = 0; + + pitchSchift[noteInf] = ((440.0 * pow(2., ((midiCentVoulue/100.) - 69.) / 12.)) / (440.0 * pow(2., ((noteInf) - 69.) / 12.))) * deuxPieSampleRate; + pitchSchift[noteSup] = ((440.0 * pow(2., ((midiCentVoulue/100.) - 69.) / 12.)) / (440.0 * pow(2., ((noteSup) - 69.) / 12.))) * deuxPieSampleRate; } void Sirene::setnoteFromExt(int note) { @@ -133,32 +192,49 @@ int Sirene::computeInertiaBias(SireneSpeedSlideState ouJeSuis){ void Sirene::setnote() { SireneSpeedSlideState ouJeSuis = oujesuis(); auto appliedFactor = coeffPicolo; - auto inertiaBias = computeInertiaBias(ouJeSuis); - auto inertiaFactor = computeInertiaFactor(noteEncour); - - auto inertiaSpeedToTweak = this->inertiaFactorTweak; - if(inertiaBias != 0){ - auto vectorIntervalValueNew = inertiaBias * appliedFactor * inertiaFactor * inertiaSpeedToTweak; - noteEncour=noteEncour+vectorIntervalValueNew; - switch(ouJeSuis){ - case Montant: - case QuartUpBefore: - case QuartUpAfter: - if(noteEncour > noteVoulueAvantSlide)noteEncour=noteVoulueAvantSlide; - break; - case Descandant: - case QuartDownAfter: - case QuartDownBefore: - if(noteEncour < noteVoulueAvantSlide)noteEncour=noteVoulueAvantSlide; - break; - case TonUpBefore: - case DemiUpBefore: - case Boucle: - case jesuisrest: - break; - } - } - + + // Convertir noteEncour en note entière pour l'indexation (comme dans l'original) + int note = (int)((noteEncour-50)/100.); + if (note < noteMin) note = noteMin; + + // Calculer baseNoteIndex (note - noteMin, comme dans l'original) + int baseNoteIndex = note - noteMin; + + // Appliquer les formules vectorInterval originales + if (ouJeSuis == Montant) { + noteEncour = noteEncour + (100.0f / (vectorInterval[baseNoteIndex + 294] * appliedFactor)); + if(noteEncour > noteVoulueAvantSlide) noteEncour = noteVoulueAvantSlide; + } + else if (ouJeSuis == Descandant) { + noteEncour = noteEncour - (100.0f / (vectorInterval[391 - baseNoteIndex] * appliedFactor)); + if(noteEncour < noteVoulueAvantSlide) noteEncour = noteVoulueAvantSlide; + } + else if (ouJeSuis == TonUpBefore) { + noteEncour = noteEncour + (100.0f / (vectorInterval[((baseNoteIndex + 2) * 6) + 1] * appliedFactor)); + } + else if (ouJeSuis == DemiUpBefore) { + noteEncour = noteEncour + (100.0f / (vectorInterval[((baseNoteIndex + 1) * 6) + 2] * appliedFactor)); + } + else if (ouJeSuis == QuartUpBefore) { + noteEncour = noteEncour + (100.0f / (vectorInterval[(baseNoteIndex * 6) + 3] * appliedFactor)); + if(noteEncour > noteVoulueAvantSlide) noteEncour = noteVoulueAvantSlide; + } + else if (ouJeSuis == Boucle) { + // Pas de changement + } + else if (ouJeSuis == QuartDownAfter) { + noteEncour = noteEncour - (100.0f / (vectorInterval[(baseNoteIndex * 6) + 4] * appliedFactor)); + if(noteEncour < noteVoulueAvantSlide) noteEncour = noteVoulueAvantSlide; + } + else if (ouJeSuis == QuartDownBefore) { + noteEncour = noteEncour - (100.0f / (vectorInterval[baseNoteIndex * 6] * appliedFactor)); + if(noteEncour < noteVoulueAvantSlide) noteEncour = noteVoulueAvantSlide; + } + else if (ouJeSuis == QuartUpAfter) { + noteEncour = noteEncour + (100.0f / (vectorInterval[(baseNoteIndex * 6) + 5] * appliedFactor)); + if(noteEncour > noteVoulueAvantSlide) noteEncour = noteVoulueAvantSlide; + } + setMidicent(noteEncour); } @@ -195,7 +271,7 @@ void Sirene::set16ou8Bit(bool is) { } void Sirene::setVelocite(int velo) { - // printf("velo:%i\n",velo); + // Set velocity ampMax = velo / 500.; ampvoulu = (velo / 500.) / (100. / (100 - pourcentClapetOff)) + (pourcentClapetOff / 100.); } @@ -204,3 +280,5 @@ void Sirene::setisCrossFade(int is) { if (is == 0) isCrossfade = false; else isCrossfade = true; } + + diff --git a/Source/Sirene.h b/Source/Sirene.h index ef38081..92df7b6 100644 --- a/Source/Sirene.h +++ b/Source/Sirene.h @@ -29,7 +29,8 @@ #include #endif -#define DeuxPieSampleRate (2.* M_PI / 44100) +// Remplacer la macro hardcodée par une variable dynamique +// #define DeuxPieSampleRate (2.* M_PI / 44100) #define MAX_Partiel 200 #define NOMBRE_DE_NOTE 80 #define MAX_TAB 1000 @@ -49,18 +50,23 @@ enum SireneSpeedSlideState { class Sirene { public: - Sirene(const std::string& str, const std::string& dataFolderPath); + Sirene(const std::string& str, const std::string& dataFolderPath); ~Sirene(); private: // Pat added ------------------ - std::string name; - int noteMidiCentMax; - int noteMin; - int pourcentClapetOff; - - int coeffPicolo; - float inertiaFactorTweak; +std::string name; +int noteMidiCentMax; +int noteMin; +int pourcentClapetOff; + +int coeffPicolo; +float inertiaFactorTweak; + +// Variables pour le sample rate dynamique +double sampleRate; +double deuxPieSampleRate; + public: void setMidicent(int note); void setnoteFromExt(int note); @@ -69,6 +75,9 @@ class Sirene { void changeQualite(int qualt); void set16ou8Bit(bool is); void setVelocite(int velo); + + // Nouvelle méthode pour mettre à jour le sample rate + void setSampleRate(double newSampleRate); void setisCrossFade(int is); @@ -79,11 +88,13 @@ class Sirene { std::string dataFilePath, std::string tabAmpFile, std::string tabFreqFile, - std::string dureTabFile + std::string dureTabFile, + std::string vectorIntervalFile ); // to fill tabAmp, tabFreq and dureTabs inline float calculwave() { + isChangementdenote = false; float wavefinal = 0.; @@ -107,7 +118,10 @@ class Sirene { countKSup = 0; } countKSup++; + if (noteInf == 36) { + // Note debugging kept for reference + } if (ampvouluz < ampvoulu) ampvouluz += vitesseClape; if (ampvouluz > ampvoulu) ampvouluz -= vitesseClape; @@ -119,13 +133,9 @@ class Sirene { if (is16Bit || isChangementdenote || count8bit) { if (isCrossfade) { phaseInf[i] += ( - tabFreq[noteInf][countP[noteInf]][i] * - pitchSchift[noteInf] * - eloignementfreq / 100. + (tabFreq[noteInf][countP[noteInf]][i] * pitchSchift[noteInf] * eloignementfreq / 100.) ) + ( - tabFreq[noteSup][countP[noteSup]][i] * - pitchSchift[noteSup] * - (100 - eloignementfreq) / 100. + (tabFreq[noteSup][countP[noteSup]][i] * pitchSchift[noteSup] * (100 - eloignementfreq) / 100.) ); amp[i] = ( @@ -137,7 +147,7 @@ class Sirene { ); } else { amp[i] = tabAmp[noteInf][countP[noteInf]][i]; - phaseInf[i] += (tabFreq[noteInf][countP[noteInf]][i] * pitchSchift[noteInf]); + phaseInf[i] += tabFreq[noteInf][countP[noteInf]][i] * pitchSchift[noteInf]; } ampz[i] = 0.001 * amp[i] + 0.999 * ampz[i] ; @@ -147,7 +157,7 @@ class Sirene { phaseInf[i] = 0.; } else { - phaseInf[i] += (tabFreq[noteInf][countP[noteInf]][i] * pitchSchift[noteInf]); + phaseInf[i] += tabFreq[noteInf][countP[noteInf]][i] * pitchSchift[noteInf]; } } @@ -162,6 +172,7 @@ class Sirene { float tabAmp[NOMBRE_DE_NOTE][MAX_TAB][MAX_Partiel]; float tabFreq[NOMBRE_DE_NOTE][MAX_TAB][MAX_Partiel]; float dureTabs[NOMBRE_DE_NOTE][3]; // 0=dureTab en samples // 1=nombreMax de Tab // 2=FreqMoyenne + float vectorInterval[392]; // Données pour l'inertie des sirènes bool count8bit = true; double vitesseClape = 0.0002; diff --git a/Source/allpass.cpp b/Source/allpass.cpp new file mode 100644 index 0000000..d57ae49 --- /dev/null +++ b/Source/allpass.cpp @@ -0,0 +1,37 @@ +// Allpass filter implementation +// +// Written by Jezar at Dreampoint, June 2000 +// http://www.dreampoint.co.uk +// This code is public domain + +#include "allpass.h" + +allpass::allpass() +{ + bufidx = 0; +} + +void allpass::setbuffer(float *buf, int size) +{ + buffer = buf; + bufsize = size; +} + +void allpass::mute() +{ + for (int i=0; i=bufsize) bufidx = 0; + + return output; +} + +#endif//_allpass + +//ends + diff --git a/Source/comb.cpp b/Source/comb.cpp new file mode 100644 index 0000000..bbf1bae --- /dev/null +++ b/Source/comb.cpp @@ -0,0 +1,49 @@ +// Comb filter implementation +// +// Written by Jezar at Dreampoint, June 2000 +// http://www.dreampoint.co.uk +// This code is public domain + +#include "comb.h" + +comb::comb() +{ + filterstore = 0; + bufidx = 0; +} + +void comb::setbuffer(float *buf, int size) +{ + buffer = buf; + bufsize = size; +} + +void comb::mute() +{ + for (int i=0; i=-1.0))) filterstore=0.0; + + + filterstore = (output*damp2) + (filterstore*damp1); + + buffer[bufidx] = input + (filterstore*feedback); + + if(++bufidx>=bufsize) bufidx = 0; + + return output; + } + + void mute(); + void setdamp(float val); + float getdamp(); + void setfeedback(float val); + float getfeedback(); +private: + float feedback; + float filterstore; + float damp1; + float damp2; + float *buffer; + int bufsize; + int bufidx; +}; + + +#endif //_comb_ + +//ends + diff --git a/Source/mareverbe.cpp b/Source/mareverbe.cpp new file mode 100644 index 0000000..54f7f5d --- /dev/null +++ b/Source/mareverbe.cpp @@ -0,0 +1,257 @@ +/* + * mareverbe.cpp + * S1IN + * + * Created by benoit louette on 19/03/10. + * Copyright 2010 __MyCompanyName__. All rights reserved. + * + */ + +#include "mareverbe.h" +#include + +mareverbe::mareverbe() +{ + // Tie the components to their buffers + combL[0].setbuffer(bufcombL1,combtuningL1); + combR[0].setbuffer(bufcombR1,combtuningR1); + combL[1].setbuffer(bufcombL2,combtuningL2); + combR[1].setbuffer(bufcombR2,combtuningR2); + combL[2].setbuffer(bufcombL3,combtuningL3); + combR[2].setbuffer(bufcombR3,combtuningR3); + combL[3].setbuffer(bufcombL4,combtuningL4); + combR[3].setbuffer(bufcombR4,combtuningR4); + combL[4].setbuffer(bufcombL5,combtuningL5); + combR[4].setbuffer(bufcombR5,combtuningR5); + combL[5].setbuffer(bufcombL6,combtuningL6); + combR[5].setbuffer(bufcombR6,combtuningR6); + combL[6].setbuffer(bufcombL7,combtuningL7); + combR[6].setbuffer(bufcombR7,combtuningR7); + combL[7].setbuffer(bufcombL8,combtuningL8); + combR[7].setbuffer(bufcombR8,combtuningR8); + allpassL[0].setbuffer(bufallpassL1,allpasstuningL1); + allpassR[0].setbuffer(bufallpassR1,allpasstuningR1); + allpassL[1].setbuffer(bufallpassL2,allpasstuningL2); + allpassR[1].setbuffer(bufallpassR2,allpasstuningR2); + allpassL[2].setbuffer(bufallpassL3,allpasstuningL3); + allpassR[2].setbuffer(bufallpassR3,allpasstuningR3); + allpassL[3].setbuffer(bufallpassL4,allpasstuningL4); + allpassR[3].setbuffer(bufallpassR4,allpasstuningR4); + + // Set default values + allpassL[0].setfeedback(0.5f); + allpassR[0].setfeedback(0.5f); + allpassL[1].setfeedback(0.5f); + allpassR[1].setfeedback(0.5f); + allpassL[2].setfeedback(0.5f); + allpassR[2].setfeedback(0.5f); + allpassL[3].setfeedback(0.5f); + allpassR[3].setfeedback(0.5f); + setwet(initialwet); + setroomsize(initialroom); + setdry(initialdry); + setdamp(initialdamp); + setwidth(initialwidth); + setmode(initialmode); + + // Buffer will be full of rubbish - so we MUST mute them + mute(); + +} + +mareverbe::~mareverbe() { +} + +void mareverbe::process_events() { + if (roomsize) + setroomsize(roomsize); + if (damp) + setdamp(damp); + if (wet) + setwet(dbtoamp(wet,-48.0f)); + if (dry) + setdry(dbtoamp(dry,-48.0f)); + if (width) + setwidth(width); + if (freeze) + setmode(freeze); +} + +void mareverbe::process_stereo(float *inL, float *inR, float *outL, float *outR, int n) { + processreplace(inL, inR, outL, outR, n, 1); + dsp_clip(outL, n, 1); + dsp_clip(outR, n, 1); // signal may never exceed -1..1 + +} + +void mareverbe::mute() +{ + int i; + + if (getmode() >= freezemode) + return; + + for (i=0;i=-1.0)){ + + while(numsamples--) + { + outL = outR = 0; + input = (*inputL + *inputR) * gain; + + // Accumulate comb filters in parallel + for(i=0; i= freezemode) + { + roomsize1 = 1; + damp1 = 0; + gain = muted; + } + else + { + roomsize1 = roomsize; + damp1 = damp; + gain = fixedgain; + } + + for(i=0; i= freezemode) + return 1; + else + return 0; +} + diff --git a/Source/mareverbe.h b/Source/mareverbe.h new file mode 100644 index 0000000..223f3d9 --- /dev/null +++ b/Source/mareverbe.h @@ -0,0 +1,155 @@ +/* + * mareverbe.h + * S1IN + * + * Created by benoit louette on 19/03/10. + * Copyright 2010 __MyCompanyName__. All rights reserved. + * + */ +#include "comb.h" +#include "allpass.h" + +#include + +const int numcombs = 8; +const int numallpasses = 4; +const float muted = 0; +const float fixedgain = 0.015f; +const float scalewet = 3; +const float scaledry = 2; +const float scaledamp = 0.4f; +const float scaleroom = 0.28f; +const float offsetroom = 0.7f; +const float initialroom = 0.5f; +const float initialdamp = 0.5f; +const float initialwet = 1/scalewet; +const float initialdry = 0; +const float initialwidth = 1; +const float initialmode = 0; +const float freezemode = 0.5f; +const int stereospread = 23; + +// These values assume 44.1KHz sample rate +// they will probably be OK for 48KHz sample rate +// but would need scaling for 96KHz (or other) sample rates. +// The values were obtained by listening tests. +const int combtuningL1 = 1116; +const int combtuningR1 = 1116+stereospread; +const int combtuningL2 = 1188; +const int combtuningR2 = 1188+stereospread; +const int combtuningL3 = 1277; +const int combtuningR3 = 1277+stereospread; +const int combtuningL4 = 1356; +const int combtuningR4 = 1356+stereospread; +const int combtuningL5 = 1422; +const int combtuningR5 = 1422+stereospread; +const int combtuningL6 = 1491; +const int combtuningR6 = 1491+stereospread; +const int combtuningL7 = 1557; +const int combtuningR7 = 1557+stereospread; +const int combtuningL8 = 1617; +const int combtuningR8 = 1617+stereospread; +const int allpasstuningL1 = 556; +const int allpasstuningR1 = 556+stereospread; +const int allpasstuningL2 = 441; +const int allpasstuningR2 = 441+stereospread; +const int allpasstuningL3 = 341; +const int allpasstuningR3 = 341+stereospread; +const int allpasstuningL4 = 225; +const int allpasstuningR4 = 225+stereospread; + + +class mareverbe +{ +public: + mareverbe(); + ~mareverbe() ; + + void process_events(); + void process_stereo(float *inL, float *inR, float *outL, float *outR, int n); + + void mute(); + void processmix(float *inputL, float *inputR, float *outputL, float *outputR, long numsamples, int skip); + void processreplace(float *inputL, float *inputR, float *outputL, float *outputR, long numsamples, int skip); + void setroomsize(float value); + float getroomsize(); + void setdamp(float value); + float getdamp(); + void setwet(float value); + float getwet(); + void setdry(float value); + float getdry(); + void setwidth(float value); + float getwidth(); + void setmode(float value); + float getmode(); + + void update(); + float gain; + float roomsize,roomsize1; + float damp,damp1; + float wet,wet1,wet2; + float dry; + float width; + float mode; + float freeze; + +private: + // The following are all declared inline + // to remove the need for dynamic allocation + // with its subsequent error-checking messiness + + // Comb filters + comb combL[numcombs]; + comb combR[numcombs]; + + // Allpass filters + allpass allpassL[numallpasses]; + allpass allpassR[numallpasses]; + + // Buffers for the combs + float bufcombL1[combtuningL1]; + float bufcombR1[combtuningR1]; + float bufcombL2[combtuningL2]; + float bufcombR2[combtuningR2]; + float bufcombL3[combtuningL3]; + float bufcombR3[combtuningR3]; + float bufcombL4[combtuningL4]; + float bufcombR4[combtuningR4]; + float bufcombL5[combtuningL5]; + float bufcombR5[combtuningR5]; + float bufcombL6[combtuningL6]; + float bufcombR6[combtuningR6]; + float bufcombL7[combtuningL7]; + float bufcombR7[combtuningR7]; + float bufcombL8[combtuningL8]; + float bufcombR8[combtuningR8]; + + // Buffers for the allpasses + float bufallpassL1[allpasstuningL1]; + float bufallpassR1[allpasstuningR1]; + float bufallpassL2[allpasstuningL2]; + float bufallpassR2[allpasstuningR2]; + float bufallpassL3[allpasstuningL3]; + float bufallpassR3[allpasstuningR3]; + float bufallpassL4[allpasstuningL4]; + float bufallpassR4[allpasstuningR4]; + + inline float dbtoamp(float db, float limit) { + if (db <= limit) + return 0.0f; + return std::pow(10.0f, db / 20.0f); + } + inline void dsp_clip(float *b, int numsamples, float s) { + while (numsamples--) { + if (*b > s) + *b = s; + if (*b < -s) + *b = -s; + b++; + } + } + + +}; + diff --git a/Source/synth.cpp b/Source/synth.cpp index a9eb184..f2f3e66 100644 --- a/Source/synth.cpp +++ b/Source/synth.cpp @@ -1,206 +1,421 @@ -/* - ============================================================================== - - synth.cpp - Created: 7 May 2020 10:56:27am - Author: guyot - - ============================================================================== -*/ - -#include "synth.h" - -#include -#include - -Synth::Synth(){ - - // left channel - PanS1=0.75; - PanS2=0.25; - PanS3=0.6; - PanS4=0.4; - PanS5=0.9; - PanS6=0.1; - PanS7=0.45; - - isWithSynthe=true; - isWithClic=false; - WideCoeff=1.5; - -#if defined (_MSC_VER) - std::string dataFilePath = "C:\\Program Files\\Common Files\\Mecanique Vivante\\ComposeSiren\\Resources\\"; -#else -#if CMS_BUILD_WITH_PROJUCER - std::string dataFilePath = juce::File::getSpecialLocation(juce::File::currentApplicationFile).getChildFile ("Contents/Resources/").getFullPathName().toStdString() + '/'; -#elif CMS_BUILD_WITH_CMAKE - std::string dataFilePath = "/Library/Audio/Plug-ins/Mecanique Vivante/ComposeSiren/Resources/"; -#endif -#endif - s1 = new Sirene("S1", dataFilePath); - s2 = new Sirene("S2", dataFilePath); - s3 = new Sirene("S3", dataFilePath); - s4 = new Sirene("S4", dataFilePath); - s5 = new Sirene("S5", dataFilePath); - s6 = new Sirene("S6", dataFilePath); - s7 = new Sirene("S7", dataFilePath); -} - -Synth::~Synth(){ - delete (s1); - delete (s2); - delete (s3); - delete (s4); - delete (s5); - delete (s6); - delete (s7); -} - - -void Synth::setnote(int sireneNumber, int note) -{ - if(isWithSynthe){ - switch (sireneNumber) { - case 1:s1->setnoteFromExt(note);break; - case 2:s2->setnoteFromExt(note);break; - case 3:s3->setnoteFromExt(note);break; - case 4:s4->setnoteFromExt(note);break; - case 5:s5->setnoteFromExt(note);break; - case 6:s6->setnoteFromExt(note);break; - case 7:s7->setnoteFromExt(note);break; - case 8: break; - default: - break; - } - } - -} - -void Synth::setVelocite(int sireneNumber, int velo){ - if(isWithSynthe){ - switch (sireneNumber) { - case 1:s1->setVelocite(velo);break; - case 2:s2->setVelocite(velo);break; - case 3:s3->setVelocite(velo);break; - case 4:s4->setVelocite(velo);break; - case 5:s5->setVelocite(velo);break; - case 6:s6->setVelocite(velo);break; - case 7:s7->setVelocite(velo);break; - case 8: break; - default: - break; - } - } -} - - -void Synth::setPan(int sireneNumber, float value){ - if(isWithSynthe){ - switch (sireneNumber) { - case 1:PanS1=value; break; - case 2:PanS2=value; break; - case 3:PanS3=value; break; - case 4:PanS4=value; break; - case 5:PanS5=value; break; - case 6:PanS6=value; break; - case 7:PanS7=value; break; - default: - break; - } - } -} - -float Synth::getPan(int sireneNumber, int channel) -{ - // Return the panoramic value according to the sirene number and the channel (left : 0, right :1) - if(channel){ - // right channel - switch (sireneNumber) { - case 1: return 1-PanS1 + 0.5; break; - case 2: return 1-PanS2+ 0.5; break; - case 3: return 1-PanS3+ 0.5; break; - case 4: return 1-PanS4+ 0.5; break; - case 5: return 1-PanS5+ 0.5; break; - case 6: return 1-PanS6+ 0.5; break; - case 7: return 1-PanS7+ 0.5; break; - default: return 0.5; - } - } - else - { - // left channel - - switch (sireneNumber) { - case 1: return PanS1+ 0.5; break; - case 2: return PanS2+ 0.5; break; - case 3: return PanS3+ 0.5; break; - case 4: return PanS4+ 0.5; break; - case 5: return PanS5+ 0.5; break; - case 6: return PanS6+ 0.5; break; - case 7: return PanS7+ 0.5; break; - default: return 0.5; - - } - } -} - - -void Synth::changeQualite(int qualt){ - if(isWithSynthe){ - s1->changeQualite(qualt); - s2->changeQualite(qualt); - s3->changeQualite(qualt); - s4->changeQualite(qualt); - s5->changeQualite(qualt); - s6->changeQualite(qualt); - s7->changeQualite(qualt); - } -} - - -void Synth::setVitesse(int chanal, float vitesse){ - if(isWithSynthe){ - int midicent=0; - switch (chanal) { - case 1: - midicent=(int)roundf((69+12.*log2f((vitesse/5.)/440.0))*100.); - if (midicent<0) midicent=0; - setnote(1, midicent); - break; - case 2: - midicent=(int)roundf((69+12.*log2f((vitesse/5.)/440.0))*100.); - if (midicent<0) midicent=0; - setnote(2, midicent); - break; - case 3: - midicent=(int)roundf((69+12.*log2f((vitesse/7.5)/440.0))*100.); - if (midicent<0) midicent=0; - setnote(3, midicent); - break; - case 4: - midicent=(int)roundf((69+12.*log2f((vitesse/(20./3.))/440.0))*100.); - if (midicent<0) midicent=0; - setnote(4, midicent); - break; - case 5: - midicent=(int)roundf((69+12.*log2f((vitesse/7.5)/440.0))*100.); - if (midicent<0) midicent=0; - setnote(5, midicent); - break; - case 6: - midicent=(int)roundf((69+12.*log2f((vitesse/7.5)/440.0))*100.); - if (midicent<0) midicent=0; - setnote(6, midicent); - break; - case 7: - midicent=(int)roundf((69+12.*log2f((vitesse/7.5)/440.0))*100.); - if (midicent<0) midicent=0; - setnote(7, midicent); - break; - - - default: - break; - } - } -} +/* + ============================================================================== + + synth.cpp + Created: 7 May 2020 10:56:27am + Author: guyot + + ============================================================================== +*/ + +#include "synth.h" + +#include +#include +#include + +Synth::Synth(){ + + // left channel + PanS1=0.75; + PanS2=0.25; + PanS3=0.6; + PanS4=0.4; + PanS5=0.9; + PanS6=0.1; + PanS7=0.45; + + // Volumes indépendants (master) initialisés à 1.0 (100%) + masterVolumeS1 = 1.0f; + masterVolumeS2 = 1.0f; + masterVolumeS3 = 1.0f; + masterVolumeS4 = 1.0f; + masterVolumeS5 = 1.0f; + masterVolumeS6 = 1.0f; + masterVolumeS7 = 1.0f; + + // Initialiser la reverb + reverb = new mareverbe(); + reverbEnabled = false; + reverbHighpassFreq = 20.0f; + reverbLowpassFreq = 20000.0f; + currentSampleRate = 44100.0; // Sera mis à jour dans setSampleRate + + // Initialiser les filtres IIR avec des coefficients par défaut + reverbHighpassL = std::make_unique(); + reverbHighpassR = std::make_unique(); + reverbLowpassL = std::make_unique(); + reverbLowpassR = std::make_unique(); + + reverbHighpassL->setCoefficients(juce::IIRCoefficients::makeHighPass(currentSampleRate, reverbHighpassFreq)); + reverbHighpassR->setCoefficients(juce::IIRCoefficients::makeHighPass(currentSampleRate, reverbHighpassFreq)); + reverbLowpassL->setCoefficients(juce::IIRCoefficients::makeLowPass(currentSampleRate, reverbLowpassFreq)); + reverbLowpassR->setCoefficients(juce::IIRCoefficients::makeLowPass(currentSampleRate, reverbLowpassFreq)); + + isWithSynthe=true; + isWithClic=false; + WideCoeff=1.5; + + // Déterminer le chemin des ressources selon le contexte + std::string dataFilePath; + +#ifdef JucePlugin_Build_Standalone + // Pour le standalone, chercher les ressources dans plusieurs emplacements + juce::File resourcesDir; + + #if defined(__APPLE__) + // macOS: chercher dans le bundle de l'app + resourcesDir = juce::File::getSpecialLocation(juce::File::currentExecutableFile) + .getParentDirectory() + .getChildFile("../Resources"); + if (!resourcesDir.exists()) { + // Fallback pour développement + resourcesDir = juce::File::getSpecialLocation(juce::File::currentApplicationFile) + .getParentDirectory() + .getParentDirectory() + .getParentDirectory() + .getChildFile("Resources"); + } + #elif defined(_MSC_VER) + // Windows: chercher à côté de l'exécutable + resourcesDir = juce::File::getSpecialLocation(juce::File::currentExecutableFile) + .getParentDirectory() + .getChildFile("Resources"); + #else + // Linux: chercher dans plusieurs emplacements possibles + resourcesDir = juce::File("/usr/share/ComposeSiren/Resources"); + + if (!resourcesDir.exists()) { + // Fallback: chemin macOS (pour compatibilité/symlink) + resourcesDir = juce::File("/Library/Audio/Plug-ins/Mecanique Vivante/ComposeSiren/Resources"); + } + + if (!resourcesDir.exists()) { + // Fallback: à côté de l'exécutable (développement) + resourcesDir = juce::File::getSpecialLocation(juce::File::currentExecutableFile) + .getParentDirectory() + .getChildFile("Resources"); + } + + if (!resourcesDir.exists()) { + // Fallback: dossier du projet (développement) + auto projectDir = juce::File::getSpecialLocation(juce::File::currentExecutableFile) + .getParentDirectory() + .getParentDirectory() + .getParentDirectory(); + resourcesDir = projectDir.getChildFile("Resources"); + } + #endif + + dataFilePath = resourcesDir.getFullPathName().toStdString() + "/"; +#else + // Pour les plugins (AU, VST, etc.), utiliser le chemin d'installation + #if defined (_MSC_VER) + // Windows + dataFilePath = "C:\\Program Files\\Common Files\\Mecanique Vivante\\ComposeSiren\\Resources\\"; + #elif defined (__APPLE__) + // macOS + dataFilePath = "/Library/Audio/Plug-ins/Mecanique Vivante/ComposeSiren/Resources/"; + #else + // Linux + dataFilePath = "/usr/share/ComposeSiren/Resources/"; + #endif +#endif + + s1 = new Sirene("S1", dataFilePath); + s2 = new Sirene("S2", dataFilePath); + s3 = new Sirene("S3", dataFilePath); + s4 = new Sirene("S4", dataFilePath); + s5 = new Sirene("S5", dataFilePath); + s6 = new Sirene("S6", dataFilePath); + s7 = new Sirene("S7", dataFilePath); +} + +Synth::~Synth(){ + delete (s1); + delete (s2); + delete (s3); + delete (s4); + delete (s5); + delete (s6); + delete (s7); + delete (reverb); +} + +void Synth::setSampleRate(double newSampleRate) { + // Propager le sample rate à toutes les sirènes + s1->setSampleRate(newSampleRate); + s2->setSampleRate(newSampleRate); + s3->setSampleRate(newSampleRate); + s4->setSampleRate(newSampleRate); + s5->setSampleRate(newSampleRate); + s6->setSampleRate(newSampleRate); + s7->setSampleRate(newSampleRate); + + // Mettre à jour le sample rate pour les filtres reverb + currentSampleRate = newSampleRate; + + // Recalculer les coefficients des filtres + if (reverbHighpassL) reverbHighpassL->setCoefficients(juce::IIRCoefficients::makeHighPass(currentSampleRate, reverbHighpassFreq)); + if (reverbHighpassR) reverbHighpassR->setCoefficients(juce::IIRCoefficients::makeHighPass(currentSampleRate, reverbHighpassFreq)); + if (reverbLowpassL) reverbLowpassL->setCoefficients(juce::IIRCoefficients::makeLowPass(currentSampleRate, reverbLowpassFreq)); + if (reverbLowpassR) reverbLowpassR->setCoefficients(juce::IIRCoefficients::makeLowPass(currentSampleRate, reverbLowpassFreq)); +} + + +void Synth::setnote(int sireneNumber, int note) +{ + if(isWithSynthe){ + switch (sireneNumber) { + case 1:s1->setnoteFromExt(note);break; + case 2:s2->setnoteFromExt(note);break; + case 3:s3->setnoteFromExt(note);break; + case 4:s4->setnoteFromExt(note);break; + case 5:s5->setnoteFromExt(note);break; + case 6:s6->setnoteFromExt(note);break; + case 7:s7->setnoteFromExt(note);break; + case 8: break; + default: + break; + } + } + +} + +void Synth::setVelocite(int sireneNumber, int velo){ + if(isWithSynthe){ + switch (sireneNumber) { + case 1:s1->setVelocite(velo);break; + case 2:s2->setVelocite(velo);break; + case 3:s3->setVelocite(velo);break; + case 4:s4->setVelocite(velo);break; + case 5:s5->setVelocite(velo);break; + case 6:s6->setVelocite(velo);break; + case 7:s7->setVelocite(velo);break; + case 8: break; + default: + break; + } + } +} + + +void Synth::setPan(int sireneNumber, float value){ + if(isWithSynthe){ + switch (sireneNumber) { + case 1:PanS1=value; break; + case 2:PanS2=value; break; + case 3:PanS3=value; break; + case 4:PanS4=value; break; + case 5:PanS5=value; break; + case 6:PanS6=value; break; + case 7:PanS7=value; break; + default: + break; + } + } +} + +float Synth::getPan(int sireneNumber, int channel) +{ + // Return the panoramic value according to the sirene number and the channel (left : 0, right :1) + if(channel){ + // right channel + switch (sireneNumber) { + case 1: return 1-PanS1 + 0.5; break; + case 2: return 1-PanS2+ 0.5; break; + case 3: return 1-PanS3+ 0.5; break; + case 4: return 1-PanS4+ 0.5; break; + case 5: return 1-PanS5+ 0.5; break; + case 6: return 1-PanS6+ 0.5; break; + case 7: return 1-PanS7+ 0.5; break; + default: return 0.5; + } + } + else + { + // left channel + + switch (sireneNumber) { + case 1: return PanS1+ 0.5; break; + case 2: return PanS2+ 0.5; break; + case 3: return PanS3+ 0.5; break; + case 4: return PanS4+ 0.5; break; + case 5: return PanS5+ 0.5; break; + case 6: return PanS6+ 0.5; break; + case 7: return PanS7+ 0.5; break; + default: return 0.5; + + } + } +} + + +void Synth::changeQualite(int qualt){ + if(isWithSynthe){ + s1->changeQualite(qualt); + s2->changeQualite(qualt); + s3->changeQualite(qualt); + s4->changeQualite(qualt); + s5->changeQualite(qualt); + s6->changeQualite(qualt); + s7->changeQualite(qualt); + } +} + +void Synth::setMasterVolume(int sireneNumber, float volume){ + if(volume < 0.0f) volume = 0.0f; + if(volume > 1.0f) volume = 1.0f; + + switch (sireneNumber) { + case 1: masterVolumeS1 = volume; break; + case 2: masterVolumeS2 = volume; break; + case 3: masterVolumeS3 = volume; break; + case 4: masterVolumeS4 = volume; break; + case 5: masterVolumeS5 = volume; break; + case 6: masterVolumeS6 = volume; break; + case 7: masterVolumeS7 = volume; break; + default: break; + } +} + +float Synth::getMasterVolume(int sireneNumber){ + switch (sireneNumber) { + case 1: return masterVolumeS1; + case 2: return masterVolumeS2; + case 3: return masterVolumeS3; + case 4: return masterVolumeS4; + case 5: return masterVolumeS5; + case 6: return masterVolumeS6; + case 7: return masterVolumeS7; + default: return 1.0f; + } +} + +void Synth::setReverbEnabled(bool enabled){ + reverbEnabled = enabled; +} + +bool Synth::isReverbEnabled(){ + return reverbEnabled; +} + +void Synth::setReverbHighpass(float freq){ + reverbHighpassFreq = freq; + // Mettre à jour les coefficients du filtre + if (reverbHighpassL) reverbHighpassL->setCoefficients(juce::IIRCoefficients::makeHighPass(currentSampleRate, freq)); + if (reverbHighpassR) reverbHighpassR->setCoefficients(juce::IIRCoefficients::makeHighPass(currentSampleRate, freq)); +} + +float Synth::getReverbHighpass(){ + return reverbHighpassFreq; +} + +void Synth::setReverbLowpass(float freq){ + reverbLowpassFreq = freq; + // Mettre à jour les coefficients du filtre + if (reverbLowpassL) reverbLowpassL->setCoefficients(juce::IIRCoefficients::makeLowPass(currentSampleRate, freq)); + if (reverbLowpassR) reverbLowpassR->setCoefficients(juce::IIRCoefficients::makeLowPass(currentSampleRate, freq)); +} + +float Synth::getReverbLowpass(){ + return reverbLowpassFreq; +} + +void Synth::processReverbWithFilters(float* left, float* right, int numSamples){ + if (!reverbEnabled || numSamples <= 0 || !reverbHighpassL || !reverbHighpassR || !reverbLowpassL || !reverbLowpassR) return; + + // Buffers temporaires pour dry et wet + std::vector dryLeft(numSamples); + std::vector dryRight(numSamples); + std::vector wetLeft(numSamples); + std::vector wetRight(numSamples); + + // Sauvegarder le dry (signal original) + float dryLevel = reverb->getdry(); + float wetLevel = reverb->getwet(); + + for (int i = 0; i < numSamples; i++) + { + dryLeft[i] = left[i] * dryLevel; + dryRight[i] = right[i] * dryLevel; + wetLeft[i] = left[i]; + wetRight[i] = right[i]; + } + + // 1. Appliquer le highpass sur le wet avant la reverb + for (int i = 0; i < numSamples; i++) + { + wetLeft[i] = reverbHighpassL->processSingleSampleRaw(wetLeft[i]); + wetRight[i] = reverbHighpassR->processSingleSampleRaw(wetRight[i]); + } + + // 2. Appliquer la reverb sur le wet (avec dry=0 pour éviter le double dry) + float originalDry = reverb->dry; + reverb->setdry(0.0f); // Temporairement mettre dry à 0 + reverb->processreplace(wetLeft.data(), wetRight.data(), wetLeft.data(), wetRight.data(), numSamples, 1); + reverb->dry = originalDry; // Restaurer + + // 3. Appliquer le lowpass sur le wet après la reverb + for (int i = 0; i < numSamples; i++) + { + wetLeft[i] = reverbLowpassL->processSingleSampleRaw(wetLeft[i]); + wetRight[i] = reverbLowpassR->processSingleSampleRaw(wetRight[i]); + } + + // 4. Mixer dry (non filtré) + wet (filtré) + for (int i = 0; i < numSamples; i++) + { + left[i] = dryLeft[i] + wetLeft[i]; + right[i] = dryRight[i] + wetRight[i]; + } +} + +void Synth::setVitesse(int chanal, float vitesse){ + if(isWithSynthe){ + int midicent=0; + switch (chanal) { + case 1: + midicent=(int)roundf((69+12.*log2f((vitesse/5.)/440.0))*100.); + if (midicent<0) midicent=0; + setnote(1, midicent); + break; + case 2: + midicent=(int)roundf((69+12.*log2f((vitesse/5.)/440.0))*100.); + if (midicent<0) midicent=0; + setnote(2, midicent); + break; + case 3: + midicent=(int)roundf((69+12.*log2f((vitesse/7.5)/440.0))*100.); + if (midicent<0) midicent=0; + setnote(3, midicent); + break; + case 4: + midicent=(int)roundf((69+12.*log2f((vitesse/(20./3.))/440.0))*100.); + if (midicent<0) midicent=0; + setnote(4, midicent); + break; + case 5: + midicent=(int)roundf((69+12.*log2f((vitesse/7.5)/440.0))*100.); + if (midicent<0) midicent=0; + setnote(5, midicent); + break; + case 6: + midicent=(int)roundf((69+12.*log2f((vitesse/7.5)/440.0))*100.); + if (midicent<0) midicent=0; + setnote(6, midicent); + break; + case 7: + midicent=(int)roundf((69+12.*log2f((vitesse/7.5)/440.0))*100.); + if (midicent<0) midicent=0; + setnote(7, midicent); + break; + + + default: + break; + } + } +} + + diff --git a/Source/synth.h b/Source/synth.h index 3769541..8332c1e 100644 --- a/Source/synth.h +++ b/Source/synth.h @@ -11,6 +11,8 @@ #pragma once #include "Sirene.h" +#include "mareverbe.h" +#include @@ -49,6 +51,22 @@ class Synth void setisCrossfade(int is); void timer512(); + // Nouvelle méthode pour mettre à jour le sample rate de toutes les sirènes + void setSampleRate(double newSampleRate); + + // Méthodes pour le mixeur + void setMasterVolume(int sireneNumber, float volume); // Volume indépendant CC70 + float getMasterVolume(int sireneNumber); + void setReverbEnabled(bool enabled); + bool isReverbEnabled(); + void setReverbHighpass(float freq); // 20Hz-2000Hz + float getReverbHighpass(); + void setReverbLowpass(float freq); // 2kHz-20kHz + float getReverbLowpass(); + + // Appliquer la reverb avec filtres sur un buffer + void processReverbWithFilters(float* left, float* right, int numSamples); + float getPan(int sireneNumber, int channel); @@ -59,7 +77,10 @@ class Synth Sirene* s5; Sirene* s6; Sirene* s7; - + + // Reverb - publique pour accès direct depuis UI et Processor + mareverbe* reverb; + private: @@ -72,6 +93,28 @@ class Synth float PanS5;//0.1; float PanS6;//0.9; float PanS7;//0.65; + + // Volumes indépendants (master volume) par sirène - CC70 + float masterVolumeS1; + float masterVolumeS2; + float masterVolumeS3; + float masterVolumeS4; + float masterVolumeS5; + float masterVolumeS6; + float masterVolumeS7; + + // Reverb + bool reverbEnabled; + float reverbHighpassFreq; // 20-2000 Hz + float reverbLowpassFreq; // 2000-20000 Hz + + // Filtres pour la reverb (stéréo) - pointeurs pour éviter les problèmes de copie + std::unique_ptr reverbHighpassL; + std::unique_ptr reverbHighpassR; + std::unique_ptr reverbLowpassL; + std::unique_ptr reverbLowpassR; + double currentSampleRate; + //float Volsynthz; //AudioComponentDescription cd1; //AudioComponentDescription cdmix;