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
+
+
+
+**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;