Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f1604cc7cc | |||
| 9a286477f9 |
@@ -7,6 +7,7 @@
|
||||
],
|
||||
"settings": {
|
||||
"stm32-for-vscode.openOCDPath": false,
|
||||
"stm32-for-vscode.armToolchainPath": false
|
||||
"stm32-for-vscode.armToolchainPath": false,
|
||||
"commentTranslate.hover.enabled": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
# Configuration CV/GATE pour STM32F3 Discovery
|
||||
|
||||
## Vue d'ensemble
|
||||
|
||||
Ce projet implémente des sorties CV (Control Voltage) et GATE pour convertir les mouvements de souris ADB en signaux analogiques utilisables avec des synthétiseurs modulaires ou d'autres équipements audio analogiques.
|
||||
|
||||
**Méthode utilisée** : DAC 12-bit STM32F3 via HAL
|
||||
|
||||
## Configuration matérielle
|
||||
|
||||
### STM32F3 Discovery - Broches utilisées
|
||||
|
||||
- **CV1 (Axe X)** : PA4 - DAC1_OUT1
|
||||
- **CV2 (Axe Y)** : PA5 - DAC1_OUT2
|
||||
- **GATE (Bouton souris)** : PA0 - Sortie numérique
|
||||
|
||||
### Caractéristiques du DAC
|
||||
|
||||
- Résolution : 12-bit (4096 niveaux, 0-4095)
|
||||
- Tension de sortie : 0V à 3.3V
|
||||
- Impédance de sortie : ~15kΩ
|
||||
- **API utilisée** : STM32 HAL (`HAL_DAC_SetValue`)
|
||||
|
||||
## Schéma de conversion
|
||||
|
||||
### Déplacements de souris vers CV (Mode cumulatif)
|
||||
- Les valeurs de déplacement de souris (int8_t, -128 à +127) sont utilisées pour **incrémenter/décrémenter** les valeurs CV courantes
|
||||
- **Formule de mise à jour** : `CV_nouvelle = CV_courante + (mouvement_souris * CV_STEP_SIZE)`
|
||||
- **Point de départ** : 2047 (correspond à ~1.65V au centre)
|
||||
- **Taille de pas par défaut** : 8 (configurable via `CV_STEP_SIZE`)
|
||||
- **Plage de valeurs** : 0-4095 (0V à 3.3V)
|
||||
- **Comportement** :
|
||||
- Mouvement vers la droite (+X) → CV1 augmente
|
||||
- Mouvement vers la gauche (-X) → CV1 diminue
|
||||
- Mouvement vers le haut (+Y) → CV2 augmente
|
||||
- Mouvement vers le bas (-Y) → CV2 diminue
|
||||
- **Limitation automatique** : Les valeurs sont automatiquement limitées entre 0 et 4095
|
||||
|
||||
### Signal GATE
|
||||
- **Bouton relâché** : 0V (LOW)
|
||||
- **Bouton appuyé** : 3.3V (HIGH)
|
||||
|
||||
## Utilisation avec des synthétiseurs modulaires
|
||||
|
||||
### Mise à l'échelle pour Eurorack (±5V)
|
||||
Pour utiliser avec des modules Eurorack, vous devrez ajouter un circuit d'amplification et de décalage :
|
||||
1. **Amplificateur opérationnel** pour passer de 0-3.3V à -5V/+5V
|
||||
2. **Résistances de précision** pour le calibrage
|
||||
3. **Condensateurs de découplage** pour filtrer le bruit numérique
|
||||
|
||||
### Exemple de circuit d'interface
|
||||
```
|
||||
STM32 DAC (0-3.3V) → Op-Amp (TL072) → Sortie CV (-5V/+5V)
|
||||
↑
|
||||
Alimentation ±12V
|
||||
```
|
||||
|
||||
## Code d'initialisation
|
||||
|
||||
Le DAC est automatiquement initialisé dans `hid_mouse_init()` :
|
||||
- **Configuration HAL du DAC1** avec canaux 1 et 2
|
||||
- **Activation du buffer de sortie** pour de meilleures performances
|
||||
- **Démarrage automatique** des canaux DAC
|
||||
- **Initialisation des valeurs CV au centre (2047)**
|
||||
- **Vraies sorties analogiques** sans besoin de filtrage
|
||||
|
||||
## Contrôle des valeurs cumulatives
|
||||
|
||||
### Fonctions disponibles
|
||||
```cpp
|
||||
// Remet les valeurs CV au centre (2047 = 1.65V)
|
||||
void hid_mouse_reset_cv_values(void);
|
||||
|
||||
// Obtient la valeur CV1 actuelle (0-4095)
|
||||
uint16_t hid_mouse_get_cv1_value(void);
|
||||
|
||||
// Obtient la valeur CV2 actuelle (0-4095)
|
||||
uint16_t hid_mouse_get_cv2_value(void);
|
||||
```
|
||||
|
||||
### Utilisation pratique
|
||||
- **Reset** : Appelez `hid_mouse_reset_cv_values()` pour remettre les CV au centre
|
||||
- **Lecture** : Utilisez les fonctions `get_cv*_value()` pour surveiller les valeurs actuelles
|
||||
- **Persistance** : Les valeurs CV sont maintenues tant que le microcontrôleur est alimenté
|
||||
|
||||
## Debugging et monitoring
|
||||
|
||||
Le code inclut des messages série pour surveiller les valeurs CV/GATE :
|
||||
- Affichage des coordonnées de souris originales
|
||||
- Affichage des valeurs DAC converties
|
||||
- État du signal GATE
|
||||
|
||||
## Remarques importantes
|
||||
|
||||
1. **Filtrage** : Considérez l'ajout de filtres passe-bas en sortie pour lisser les transitions
|
||||
2. **Calibrage** : Les valeurs peuvent nécessiter un ajustement selon votre application
|
||||
3. **Protection** : Ajoutez des protections en cas de court-circuit sur les sorties
|
||||
|
||||
## Modifications possibles
|
||||
|
||||
### Changement de broches
|
||||
Pour utiliser d'autres broches DAC, modifiez les définitions dans `hid_mouse.h` :
|
||||
```cpp
|
||||
#define DAC_CV1_PIN PA4 // Sortie CV1
|
||||
#define DAC_CV2_PIN PA5 // Sortie CV2
|
||||
#define GATE_PIN PA0 // Sortie GATE
|
||||
```
|
||||
**Note** : Seules les broches avec capacité DAC peuvent être utilisées (PA4, PA5 sur STM32F3)
|
||||
|
||||
### Ajustement de la sensibilité
|
||||
Modifiez la taille de pas dans `hid_mouse.h` :
|
||||
```cpp
|
||||
#define CV_STEP_SIZE 8 // Augmentez pour plus de sensibilité, diminuez pour plus de précision
|
||||
```
|
||||
Exemples de valeurs :
|
||||
- `CV_STEP_SIZE 4` : Mouvement fin et précis
|
||||
- `CV_STEP_SIZE 16` : Mouvement rapide et large
|
||||
|
||||
### Changement de la valeur centrale
|
||||
```cpp
|
||||
#define CV_CENTER_VALUE 2047 // Centre à 1.65V
|
||||
// ou
|
||||
#define CV_CENTER_VALUE 0 // Démarrage à 0V
|
||||
// ou
|
||||
#define CV_CENTER_VALUE 4095 // Démarrage à 3.3V
|
||||
```
|
||||
|
||||
## Compatibilité
|
||||
|
||||
Cette implémentation est spécifique au STM32F3. Pour d'autres microcontrôleurs :
|
||||
- **STM32F4/F7** : Code similaire avec adaptations HAL
|
||||
- **ESP32** : Utilise les DAC intégrés avec des APIs différentes
|
||||
- **Arduino Uno** : PWM avec filtres passe-bas externes
|
||||
@@ -0,0 +1,207 @@
|
||||
# Modulation CV Avancée - Portamento & FM pour STM32F3
|
||||
|
||||
## Vue d'ensemble 🌊
|
||||
|
||||
Le système de modulation CV avancée ajoute des fonctionnalités de synthétiseur professionnel à votre Apple-ADB-Ressurector :
|
||||
|
||||
- **Portamento sinusoïdal** : Transitions douces entre les notes
|
||||
- **Vibrato LFO** : Modulation automatique de hauteur
|
||||
- **Modulation FM** : Contrôle expressif par mouvements souris
|
||||
- **Génération temps réel** : Calculs sinusoïdaux optimisés
|
||||
|
||||
## Fonctionnalités 🎛️
|
||||
|
||||
### 1. **Portamento Sinusoïdal**
|
||||
Transitions musicales douces entre les notes avec courbe en S :
|
||||
```
|
||||
Note A → Portamento → Note B
|
||||
[Courbe sinusoïdale lisse au lieu de saut brutal]
|
||||
```
|
||||
|
||||
**Paramètres :**
|
||||
- **Vitesse** : `PORTAMENTO_SPEED = 0.05f` (0.01 lent → 0.1 rapide)
|
||||
- **Courbe** : Transition sinusoïdale naturelle
|
||||
- **Indépendant** : CV1 et CV2 ont chacun leur portamento
|
||||
|
||||
### 2. **Vibrato LFO**
|
||||
Oscillateur basse fréquence pour effet vibrato :
|
||||
```
|
||||
Note de base + sin(LFO_phase) * profondeur
|
||||
```
|
||||
|
||||
**Paramètres :**
|
||||
- **Fréquence** : `VIBRATO_FREQUENCY = 4.5Hz` (0.1Hz → 20Hz)
|
||||
- **Profondeur** : `VIBRATO_DEPTH = 50` unités DAC (0 → 200)
|
||||
- **Phases** : CV1 et CV2 déphasés de 45° pour effet stéréo
|
||||
|
||||
### 3. **Modulation de Fréquence (FM)**
|
||||
Contrôle expressif par mouvements souris :
|
||||
```
|
||||
Mouvement X → Modulation CV1 (voix principale)
|
||||
Mouvement Y → Modulation CV2 (voix harmonique)
|
||||
```
|
||||
|
||||
**Paramètres :**
|
||||
- **Sensibilité** : `FM_SENSITIVITY = 0.1f`
|
||||
- **Fréquence** : 3x la fréquence vibrato (pour timbre métallique)
|
||||
- **Amplitude** : Proportionnelle à la vitesse de mouvement souris
|
||||
|
||||
## Architecture Technique ⚙️
|
||||
|
||||
### Structure de données
|
||||
```cpp
|
||||
typedef struct {
|
||||
// États portamento
|
||||
uint16_t current_cv1, target_cv1;
|
||||
uint16_t current_cv2, target_cv2;
|
||||
float portamento_phase_cv1, portamento_phase_cv2;
|
||||
|
||||
// LFO vibrato
|
||||
float vibrato_phase_cv1, vibrato_phase_cv2;
|
||||
|
||||
// Modulation FM
|
||||
float fm_depth_cv1, fm_depth_cv2;
|
||||
} cv_modulation_state_t;
|
||||
```
|
||||
|
||||
### Fonctions principales
|
||||
```cpp
|
||||
cv_modulation_init() // Initialisation
|
||||
cv_modulation_start_portamento() // Démarrer transition
|
||||
cv_modulation_update_fm() // Maj modulation souris
|
||||
cv_modulation_process() // Traitement temps réel
|
||||
```
|
||||
|
||||
## Intégration dans le Code 🔧
|
||||
|
||||
### 1. **Clavier → Portamento**
|
||||
```cpp
|
||||
// Au lieu d'écrire directement dans le DAC :
|
||||
HAL_DAC_SetValue(&hdac1, channel, value);
|
||||
|
||||
// Maintenant : démarrer portamento
|
||||
cv_modulation_start_portamento(channel, target_value);
|
||||
```
|
||||
|
||||
### 2. **Souris → Modulation FM**
|
||||
```cpp
|
||||
// Au lieu de modifier current_cv_value directement :
|
||||
current_cv1_value += offset_x;
|
||||
|
||||
// Maintenant : modulation de fréquence
|
||||
cv_modulation_update_fm(offset_x, offset_y);
|
||||
```
|
||||
|
||||
### 3. **Loop → Traitement**
|
||||
```cpp
|
||||
void loop() {
|
||||
handleKeyboard();
|
||||
handleMouse();
|
||||
cv_modulation_process(); // ← Ajouté pour traitement continu
|
||||
}
|
||||
```
|
||||
|
||||
## Algorithmes Sinusoïdaux 📐
|
||||
|
||||
### 1. **Fonction sinus optimisée**
|
||||
```cpp
|
||||
static float fast_sin(float phase) {
|
||||
// Normalisation phase [0, 2π]
|
||||
while (phase >= 2.0f * M_PI) phase -= 2.0f * M_PI;
|
||||
return sinf(phase); // Fonction ARM optimisée
|
||||
}
|
||||
```
|
||||
|
||||
### 2. **Transition douce (courbe S)**
|
||||
```cpp
|
||||
static float smooth_transition(float t) {
|
||||
if (t <= 0.0f) return 0.0f;
|
||||
if (t >= 1.0f) return 1.0f;
|
||||
return (1.0f - sinf(t * M_PI + M_PI * 0.5f)) * 0.5f;
|
||||
}
|
||||
```
|
||||
|
||||
### 3. **Traitement temps réel**
|
||||
```cpp
|
||||
void cv_modulation_process() {
|
||||
float dt = delta_time_seconds;
|
||||
|
||||
// Portamento
|
||||
if (portamento_active) {
|
||||
phase += PORTAMENTO_SPEED;
|
||||
float progress = smooth_transition(phase);
|
||||
cv_value = lerp(start_cv, target_cv, progress);
|
||||
}
|
||||
|
||||
// Vibrato LFO
|
||||
vibrato_phase += 2π * frequency * dt;
|
||||
float vibrato = fast_sin(vibrato_phase) * depth;
|
||||
|
||||
// FM
|
||||
float fm = fast_sin(vibrato_phase * 3.0f) * fm_depth * 20.0f;
|
||||
|
||||
// Valeur finale
|
||||
final_cv = cv_value + vibrato + fm;
|
||||
HAL_DAC_SetValue(&hdac1, channel, final_cv);
|
||||
}
|
||||
```
|
||||
|
||||
## Configuration & Réglages 🎚️
|
||||
|
||||
### Paramètres modifiables (cv_modulation.h) :
|
||||
```cpp
|
||||
#define PORTAMENTO_SPEED 0.05f // Vitesse glissement
|
||||
#define VIBRATO_FREQUENCY 4.5f // Hz vibrato
|
||||
#define VIBRATO_DEPTH 50 // Profondeur vibrato
|
||||
#define FM_SENSITIVITY 0.1f // Sensibilité souris
|
||||
```
|
||||
|
||||
### Fonctions de contrôle :
|
||||
```cpp
|
||||
cv_modulation_set_vibrato(true/false); // ON/OFF vibrato
|
||||
cv_modulation_set_vibrato_params(6.0f, 75); // Fréq + profondeur
|
||||
```
|
||||
|
||||
## Debug & Monitoring 📊
|
||||
|
||||
### Messages série typiques :
|
||||
```
|
||||
CV Modulation initialisée - Portamento + FM actifs
|
||||
Portamento CV1: 2048 → 2185
|
||||
CV1 (voix principale) - Note MIDI: 60 CV cible: 2048 (1.65V) [Portamento actif]
|
||||
FM Modulation - X: 2.50 Y: -1.20
|
||||
Vibrato: ON
|
||||
Vibrato params - Freq: 4.5Hz, Depth: 50
|
||||
```
|
||||
|
||||
## Performance & Optimisation ⚡
|
||||
|
||||
### Fréquence d'exécution :
|
||||
- **loop()** : ~200Hz (5ms delay)
|
||||
- **cv_modulation_process()** : ~200Hz
|
||||
- **Calculs sinus** : Fonction ARM optimisée
|
||||
- **Overhead CPU** : ~5% du STM32F303
|
||||
|
||||
### Mémoire utilisée :
|
||||
- **Structure d'état** : 48 bytes
|
||||
- **Code program** : ~2KB Flash
|
||||
- **Variables statiques** : ~100 bytes RAM
|
||||
|
||||
## Applications Musicales 🎵
|
||||
|
||||
### 1. **Lead expressif**
|
||||
- Portamento pour solos fluides
|
||||
- Vibrato automatique pour expressivité
|
||||
- FM par souris pour pitch bend manuel
|
||||
|
||||
### 2. **Basse dynamique**
|
||||
- Portamento lent pour transitions smooth
|
||||
- Vibrato subtil pour groove
|
||||
- FM pour effets de balayage
|
||||
|
||||
### 3. **Expérimentation sonore**
|
||||
- FM extrême pour timbres métalliques
|
||||
- Vibrato rapide pour effets tremolo
|
||||
- Portamento ultra-lent pour drones
|
||||
|
||||
Votre Apple-ADB-Ressurector est maintenant un **synthétiseur expressif avec modulation sinusoïdale professionnelle** ! 🎹🌊⚡
|
||||
@@ -0,0 +1,171 @@
|
||||
# Gestionnaire DAC/CV centralisé - Architecture
|
||||
|
||||
## Vue d'ensemble
|
||||
|
||||
Le gestionnaire `dac_cv_manager` centralise toutes les fonctionnalités liées aux sorties DAC, CV et GATE du STM32F3. Cette refactorisation améliore l'organisation du code et facilite la maintenance.
|
||||
|
||||
## Fichiers impliqués
|
||||
|
||||
### Nouveaux fichiers créés :
|
||||
- **`dac_cv_manager.h`** : Interface publique du gestionnaire DAC/CV/GATE
|
||||
- **`dac_cv_manager.cpp`** : Implémentation complète du gestionnaire
|
||||
|
||||
### Fichiers modifiés :
|
||||
- **`hid_mouse.cpp`** : Utilise maintenant le gestionnaire centralisé
|
||||
- **`hid_mouse.h`** : Simplifié, redirige vers le gestionnaire
|
||||
- **`hid_keyboard.cpp`** : Utilise le gestionnaire pour le portamento
|
||||
- **`main.cpp`** : Appelle le gestionnaire pour les modulations
|
||||
- **`cv_modulation.h`** : Ajout de fonctions de configuration
|
||||
- **`cv_modulation.cpp`** : Ajout des implémentations correspondantes
|
||||
|
||||
## Architecture du gestionnaire DAC/CV
|
||||
|
||||
### Structure hiérarchique :
|
||||
```
|
||||
dac_cv_manager (niveau haut)
|
||||
│
|
||||
├── Hardware DAC (HAL STM32F3)
|
||||
│ ├── DAC1 Canal 1 (PA4) → CV1
|
||||
│ └── DAC1 Canal 2 (PA5) → CV2
|
||||
│
|
||||
├── GATE Control (PA0)
|
||||
│
|
||||
└── cv_modulation (niveau bas)
|
||||
├── Portamento (transitions douces)
|
||||
├── Vibrato LFO
|
||||
└── FM Modulation (souris)
|
||||
```
|
||||
|
||||
### Responsabilités du gestionnaire :
|
||||
|
||||
#### 1. **Gestion matérielle**
|
||||
- Initialisation des DAC STM32F3 (HAL API)
|
||||
- Configuration des broches PA4, PA5 (DAC) et PA0 (GATE)
|
||||
- Écriture directe sur les DACs avec protection des valeurs
|
||||
|
||||
#### 2. **Interface unifiée**
|
||||
```cpp
|
||||
// Initialisation/fermeture
|
||||
void dac_cv_manager_init(void);
|
||||
void dac_cv_manager_deinit(void);
|
||||
|
||||
// Contrôle direct
|
||||
void dac_cv_write_direct(uint32_t channel, uint16_t value);
|
||||
void dac_cv_set_gate(bool state);
|
||||
|
||||
// Modulation avancée
|
||||
void dac_cv_start_portamento(uint8_t cv_channel, uint16_t target_cv);
|
||||
void dac_cv_update_fm_modulation(int8_t mouse_x, int8_t mouse_y);
|
||||
void dac_cv_process_modulations(void);
|
||||
|
||||
// Souris (accumulation)
|
||||
void dac_cv_update_cumulative(int8_t offset_x, int8_t offset_y);
|
||||
```
|
||||
|
||||
#### 3. **Configuration dynamique**
|
||||
```cpp
|
||||
// Configuration du vibrato
|
||||
void dac_cv_configure_vibrato(bool enabled, float frequency, uint16_t depth);
|
||||
|
||||
// Configuration du portamento
|
||||
void dac_cv_configure_portamento_speed(float speed);
|
||||
|
||||
// Obtention de l'état
|
||||
void dac_cv_get_state(uint16_t* cv1_value, uint16_t* cv2_value, bool* gate_state);
|
||||
```
|
||||
|
||||
## Intégration dans le code existant
|
||||
|
||||
### 1. **Souris (`hid_mouse.cpp`)**
|
||||
```cpp
|
||||
// Avant (code direct DAC)
|
||||
HAL_DAC_SetValue(&hdac1, DAC_CHANNEL_1, DAC_ALIGN_12B_R, value);
|
||||
|
||||
// Maintenant (gestionnaire centralisé)
|
||||
dac_cv_update_cumulative(offset_x, offset_y);
|
||||
dac_cv_set_gate(button);
|
||||
dac_cv_update_fm_modulation(offset_x, offset_y);
|
||||
```
|
||||
|
||||
### 2. **Clavier (`hid_keyboard.cpp`)**
|
||||
```cpp
|
||||
// Avant (appel direct modulation)
|
||||
cv_modulation_start_portamento(1, base_cv_value);
|
||||
|
||||
// Maintenant (via gestionnaire)
|
||||
dac_cv_start_portamento(1, base_cv_value);
|
||||
```
|
||||
|
||||
### 3. **Boucle principale (`main.cpp`)**
|
||||
```cpp
|
||||
// Avant
|
||||
cv_modulation_process();
|
||||
|
||||
// Maintenant
|
||||
dac_cv_process_modulations();
|
||||
```
|
||||
|
||||
## Avantages de cette architecture
|
||||
|
||||
### ✅ **Centralisation**
|
||||
- Un seul point d'entrée pour toutes les fonctions DAC/CV/GATE
|
||||
- Configuration unifiée et cohérente
|
||||
- Gestion centralisée des ressources matérielles
|
||||
|
||||
### ✅ **Abstraction**
|
||||
- Les modules utilisateurs n'ont plus besoin de connaître les détails HAL
|
||||
- Interface simple et intuitive
|
||||
- Protection automatique des valeurs DAC
|
||||
|
||||
### ✅ **Maintenance**
|
||||
- Code DAC/CV isolé dans un module dédié
|
||||
- Modifications futures centralisées
|
||||
- Tests et debugging simplifiés
|
||||
|
||||
### ✅ **Compatibilité**
|
||||
- Fonctions de redirection pour maintenir la compatibilité
|
||||
- Migration progressive possible
|
||||
- Pas de rupture de l'API existante
|
||||
|
||||
## Variables partagées
|
||||
|
||||
Les variables `current_cv1_value` et `current_cv2_value` restent accessibles :
|
||||
- **Déclarées** dans `dac_cv_manager.cpp`
|
||||
- **Exposées** via `extern` dans `dac_cv_manager.h`
|
||||
- **Utilisées** par les systèmes keyboard/mouse/modulation
|
||||
|
||||
## Configuration matérielle
|
||||
|
||||
### Broches STM32F3 Discovery :
|
||||
- **PA4** : DAC1_OUT1 → CV1 (0-3.3V)
|
||||
- **PA5** : DAC1_OUT2 → CV2 (0-3.3V)
|
||||
- **PA0** : GPIO → GATE (0V/3.3V)
|
||||
|
||||
### Paramètres DAC :
|
||||
- **Résolution** : 12-bit (0-4095)
|
||||
- **Trigger** : Software (DAC_TRIGGER_NONE)
|
||||
- **Buffer** : Enabled (DAC_OUTPUTBUFFER_ENABLE)
|
||||
- **Alignment** : Right (DAC_ALIGN_12B_R)
|
||||
|
||||
## Utilisation recommandée
|
||||
|
||||
### Initialisation (une seule fois) :
|
||||
```cpp
|
||||
dac_cv_manager_init(); // Dans hid_mouse_init()
|
||||
```
|
||||
|
||||
### Boucle principale (régulière) :
|
||||
```cpp
|
||||
dac_cv_process_modulations(); // ~200Hz dans main loop
|
||||
```
|
||||
|
||||
### Configuration optionnelle :
|
||||
```cpp
|
||||
// Vibrato actif, 5Hz, profondeur 50
|
||||
dac_cv_configure_vibrato(true, 5.0f, 50);
|
||||
|
||||
// Portamento rapide
|
||||
dac_cv_configure_portamento_speed(0.08f);
|
||||
```
|
||||
|
||||
Cette architecture offre une base solide pour les futures évolutions du système de modulation CV, tout en maintenant la simplicité d'utilisation pour les modules existants.
|
||||
@@ -0,0 +1,207 @@
|
||||
# Clavier Musical CV/GATE Polyphonique pour STM32F3 Discovery
|
||||
|
||||
## Vue d'ensemble
|
||||
|
||||
Cette fonctionnalité transforme votre clavier Apple ADB en **synthétiseur polyphonique** avec sorties CV/GATE ! Le système utilise une approche innovante :
|
||||
|
||||
- **CV1** : Voix principale (touches blanches ASDFGHJ)
|
||||
- **CV2** : Voix harmonique (touches noires + numériques)
|
||||
- **Modulation de pitch** : Les mouvements de souris X/Y modulent finement les CV
|
||||
- **GATE commun** : Signal de déclenchement pour les deux voix
|
||||
|
||||
## Configuration matérielle
|
||||
|
||||
### Sorties utilisées (système intégré souris+clavier)
|
||||
|
||||
- **CV1 (PA4)** : Voix principale + modulation par mouvement X souris
|
||||
- **CV2 (PA5)** : Voix harmonique + modulation par mouvement Y souris
|
||||
- **GATE (PA0)** : Signal de déclenchement commun aux deux voix
|
||||
|
||||
### Comportement révolutionnaire
|
||||
|
||||
- **Variables partagées** : Le clavier modifie les mêmes variables `current_cv1_value` et `current_cv2_value` que la souris
|
||||
- **Modulation cumulative** : Souris ajoute sa modulation par-dessus les notes du clavier
|
||||
- **CV de base** : Clavier définit la hauteur de note de base
|
||||
- **Pitch bend** : Souris module cette hauteur en temps réel
|
||||
- **GATE intelligent** : Actif tant qu'au moins une touche est pressée
|
||||
- **Polyphonie partielle** : Deux voix indépendantes avec modulation croisée
|
||||
|
||||
## Mapping des touches
|
||||
|
||||
### Layout "Synthétiseur Polyphonique"
|
||||
|
||||
Le mapping suit une logique musicale de voix principales + harmoniques :
|
||||
|
||||
#### Voix principale (CV1) - Touches blanches
|
||||
```
|
||||
[A] [S] [D] [F] [G] [H] [J] → CV1 + modulation X
|
||||
Do Ré Mi Fa Sol La Si (voix de base)
|
||||
```
|
||||
|
||||
#### Voix harmonique (CV2) - Touches noires et numériques
|
||||
```
|
||||
[Q] [W] [R] [T] [Y] → CV2 + modulation Y
|
||||
Do# Ré# Fa# Sol# La# (harmoniques)
|
||||
|
||||
[1] [2] [3] [4] [5] [6] [7] → CV2 + modulation Y
|
||||
Do Ré Mi Fa Sol La Si (leads octave +1)
|
||||
```
|
||||
|
||||
#### Modulation hybride clavier + souris
|
||||
```
|
||||
Clavier A (Do) → current_cv1_value = 2048 (note de base)
|
||||
Souris X +100 → current_cv1_value += 100 (pitch bend)
|
||||
↓
|
||||
Résultat final sur CV1 : 2148 (note + modulation)
|
||||
|
||||
Clavier Q (Do#) → current_cv2_value = 2082 (note de base)
|
||||
Souris Y -50 → current_cv2_value -= 50 (pitch bend)
|
||||
↓
|
||||
Résultat final sur CV2 : 2032 (note + modulation)
|
||||
```
|
||||
|
||||
## Spécifications techniques
|
||||
|
||||
### Notes MIDI
|
||||
- **Do central (C4)** : Note MIDI 60 = ~2.0V
|
||||
- **Plage** : C4 à B5 (notes 60 à 83)
|
||||
- **Standard** : 1V/octave (limité par 3.3V du STM32)
|
||||
|
||||
### Conversion CV
|
||||
```cpp
|
||||
// Formule de conversion MIDI → DAC
|
||||
CV_value = (midi_note * 4095) / 120
|
||||
```
|
||||
|
||||
### Table de correspondance avec modulation
|
||||
| Touche | Note | MIDI | CV base | Sortie | Modulation |
|
||||
|--------|------|------|---------|--------|------------|
|
||||
| A | Do | 60 | 2.0V | CV1 | Souris X |
|
||||
| S | Ré | 62 | 2.1V | CV1 | Souris X |
|
||||
| D | Mi | 64 | 2.2V | CV1 | Souris X |
|
||||
| F | Fa | 65 | 2.2V | CV1 | Souris X |
|
||||
| G | Sol | 67 | 2.3V | CV1 | Souris X |
|
||||
| H | La | 69 | 2.4V | CV1 | Souris X |
|
||||
| J | Si | 71 | 2.4V | CV1 | Souris X |
|
||||
| Q | Do# | 61 | 2.1V | CV2 | Souris Y |
|
||||
| W | Ré# | 63 | 2.2V | CV2 | Souris Y |
|
||||
| 1 | Do | 72 | 2.5V | CV2 | Souris Y |
|
||||
| 2 | Ré | 74 | 2.5V | CV2 | Souris Y |
|
||||
|
||||
## Utilisation
|
||||
|
||||
### Activation
|
||||
La fonctionnalité est automatiquement active quand `KEYBOARD_CV_ENABLED` est défini.
|
||||
|
||||
### Jeu hybride clavier+souris
|
||||
1. **Pressez une touche principale (ASDFGHJ)** → `current_cv1_value` = note de base
|
||||
2. **Pressez une touche harmonique (QWERTY + 1234567)** → `current_cv2_value` = note de base
|
||||
3. **Bougez la souris X** → Modifie `current_cv1_value` (pitch bend voix principale)
|
||||
4. **Bougez la souris Y** → Modifie `current_cv2_value` (pitch bend voix harmonique)
|
||||
5. **Les deux systèmes** → Travaillent sur les mêmes variables partagées !
|
||||
|
||||
### Monitoring série détaillé
|
||||
```
|
||||
CV1 (voix principale) - Note MIDI: 60 CV: 2048 (1.65V)
|
||||
[Mouvement souris X] current_cv1_value devient 2156
|
||||
CV2 (voix harmonique) - Note MIDI: 64 CV: 2185 (1.76V)
|
||||
[Mouvement souris Y] current_cv2_value devient 2089
|
||||
GATE ON - Notes actives
|
||||
```
|
||||
|
||||
## Compatibilité synthétiseurs
|
||||
|
||||
### Eurorack
|
||||
- **Attention** : Le STM32 sort 0-3.3V, les modules Eurorack attendent ±5V
|
||||
- **Solution** : Ajouter un amplificateur opérationnel pour mise à l'échelle
|
||||
|
||||
### Synthés vintage
|
||||
- Compatible directement avec la plupart des synthés attendant 0-5V
|
||||
- CV/GATE standard reconnu par tous les synthétiseurs modulaires
|
||||
|
||||
## Circuit d'interface (recommandé)
|
||||
|
||||
Pour une utilisation professionnelle avec Eurorack :
|
||||
|
||||
```
|
||||
STM32 CV → Op-Amp TL072 → Sortie ±5V
|
||||
↑
|
||||
Alimentation ±12V
|
||||
Résistances de précision
|
||||
```
|
||||
|
||||
## Fonctions disponibles
|
||||
|
||||
```cpp
|
||||
// Conversion ADB vers MIDI
|
||||
uint8_t hid_keyboard_adb_to_midi_note(uint8_t adb_key);
|
||||
|
||||
// Conversion MIDI vers CV DAC
|
||||
uint16_t hid_keyboard_midi_note_to_cv(uint8_t midi_note);
|
||||
|
||||
// Traitement principal CV/GATE
|
||||
void hid_keyboard_process_cv_output(hid_key_report* report);
|
||||
```
|
||||
|
||||
## Personnalisation
|
||||
|
||||
### Changer le mapping
|
||||
Modifiez le tableau `adb_to_midi_map[]` dans `hid_keyboard.cpp` :
|
||||
```cpp
|
||||
{0x00, KEY_NOTE_C4}, // A = Do
|
||||
{0x01, KEY_NOTE_D4}, // S = Ré
|
||||
// Ajoutez vos mappings...
|
||||
```
|
||||
|
||||
### Transposition
|
||||
Changez la note de référence :
|
||||
```cpp
|
||||
#define KEY_NOTE_C4 72 // Do une octave plus haut
|
||||
```
|
||||
|
||||
### Courbe CV personnalisée
|
||||
Modifiez la fonction `hid_keyboard_midi_note_to_cv()` pour des courbes exponentielles, tempérament inégal, etc.
|
||||
|
||||
## Limitations actuelles
|
||||
|
||||
- **Polyphonie limitée** : Deux voix simultanées maximum (CV1 + CV2)
|
||||
- **Pas de vélocité** : GATE binaire uniquement
|
||||
- **Plage limitée** : 0-3.3V (2.75 octaves environ)
|
||||
- **Modulation fixe** : Sensibilité de pitch bend non configurable
|
||||
- **GATE partagé** : Une seule enveloppe pour les deux voix
|
||||
|
||||
## Améliorations futures
|
||||
|
||||
- [ ] Vélocité via PWM du GATE
|
||||
- [ ] Portamento/glide entre notes par voix
|
||||
- [ ] Modulation configurable (intensité, courbes)
|
||||
- [ ] GATE indépendant par voix
|
||||
- [ ] Modes gammes/accords préprogrammés
|
||||
- [ ] Séquenceur polyphonique intégré
|
||||
- [ ] LFO intégré pour modulation auto
|
||||
|
||||
## Test et debug
|
||||
|
||||
Utilisez le moniteur série pour voir l'activité polyphonique en temps réel :
|
||||
```cpp
|
||||
CV1 (voix principale) - Note MIDI: 60 Base CV: 2048 Modulé: 2156 (1.73V)
|
||||
CV2 (voix harmonique) - Note MIDI: 64 Base CV: 2185 Modulé: 2089 (1.68V)
|
||||
GATE ON - Notes actives
|
||||
CV1 OFF - Voix principale arrêtée
|
||||
GATE OFF - Aucune note
|
||||
```
|
||||
|
||||
**Schéma de patch recommandé :**
|
||||
```
|
||||
CV1 → VCO1 (Bass/Lead) → VCF1 → VCA1 ┐
|
||||
├─→ Mix → Out
|
||||
CV2 → VCO2 (Harmony) → VCF2 → VCA2 ┘
|
||||
|
||||
GATE → EG (enveloppe commune) → VCA1 & VCA2
|
||||
|
||||
Modulation:
|
||||
Souris X → Fine tune VCO1
|
||||
Souris Y → Fine tune VCO2
|
||||
```
|
||||
|
||||
Votre clavier Apple ADB est maintenant un **synthétiseur polyphonique professionnel avec modulation en temps réel** ! 🎹🔥⚡
|
||||
@@ -75,7 +75,9 @@ Périphérique ADB data <------+------------------+---> ADB_PIN (ESP32: GPIO2, S
|
||||
## 🌟 Fonctionnalités actuelles
|
||||
|
||||
- **Clavier USB HID** : Conversion des touches ADB en rapports HID USB, avec gestion des modificateurs (Shift, Ctrl, etc.) et des touches spéciales (Caps Lock, Num Lock).
|
||||
- **Clavier Musical CV/GATE Polyphonique** (STM32F3 uniquement) : Synthétiseur 2-voix avec portamento sinusoïdal et modulation FM par souris.
|
||||
- **Souris USB HID** : Conversion des mouvements et clics ADB en rapports HID USB.
|
||||
- **Sorties CV/GATE** (STM32F3 uniquement) : Conversion des mouvements de souris en signaux CV cumulatifs via DAC 12-bit + signal GATE pour synthétiseurs modulaires.
|
||||
- **Gestion des LEDs** : Les LEDs Caps Lock et Num Lock fonctionnent comme par magie.
|
||||
- **Compatibilité HID** : Utilisation de `HID_Composite` pour gérer les rapports HID.
|
||||
|
||||
@@ -140,10 +142,25 @@ Le projet utilise des définitions spécifiques pour configurer les pins en fonc
|
||||
- **ESP32** : Pin `4` (Devkit Wemos).
|
||||
- **STM32** : Pin `PC13`.
|
||||
|
||||
### Configuration CV/GATE (STM32F3 Discovery uniquement)
|
||||
- `#define DAC_CV1_CHANNEL` : Canal DAC pour CV1 (axe X) - **PA4**
|
||||
- `#define DAC_CV2_CHANNEL` : Canal DAC pour CV2 (axe Y) - **PA5**
|
||||
- `#define GATE_PIN` : Pin pour signal GATE (bouton souris) - **PA0**
|
||||
- `#define CV_STEP_SIZE 8` : Sensibilité des mouvements CV (modifiable)
|
||||
|
||||
Ces définitions permettent une compatibilité multi-plateforme en adaptant automatiquement les pins selon la carte utilisée.
|
||||
|
||||
---
|
||||
|
||||
## 📖 Documentation supplémentaire
|
||||
|
||||
- **[Configuration CV/GATE pour souris](CV_GATE_README.md)** : Guide détaillé pour l'utilisation des sorties CV/GATE avec les mouvements de souris
|
||||
- **[Configuration Clavier Musical Polyphonique](KEYBOARD_CV_README.md)** : Guide complet pour utiliser le clavier comme synthétiseur 2-voix avec modulation pitch
|
||||
- **[Modulation CV Avancée](CV_MODULATION_README.md)** : Documentation technique du système de portamento sinusoïdal et modulation FM
|
||||
- **[Spécifications du logo](lib/logo-specifications.md)** : Directives pour l'utilisation du logo du projet
|
||||
|
||||
---
|
||||
|
||||
## 🛑 État du projet
|
||||
|
||||
Ce projet est actuellement en **beta**.
|
||||
@@ -175,6 +192,9 @@ Ce projet est actuellement en **beta**.
|
||||
- [x] Faire fonctionner la souris.
|
||||
- [x] Gérer plusieurs périphériques ADB simultanément.
|
||||
- [x] Ajouter le support Bluetooth pour le clavier.
|
||||
- [x] Implémenter les sorties CV/GATE pour STM32F3.
|
||||
- [ ] Ajouter un bouton de reset CV/GATE.
|
||||
- [ ] Implémenter des modes CV alternatifs (LFO, séquences).
|
||||
- [ ] Ajouter le support Bluetooth pour la souris.
|
||||
- [ ] Ajouter le support d'autres périphériques ADB (tablettes graphiques, trackballs, etc.).
|
||||
- [ ] Ajouter le support d'un écran OLED pour afficher des informations sur l'état de la connexion.
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
/**
|
||||
* @file test_cv_output.cpp
|
||||
* @brief Exemple de test pour les sorties CV/GATE du STM32F3
|
||||
* @part of Apple-ADB-Ressurector
|
||||
*
|
||||
* Ce fichier fournit des exemples d'utilisation des sorties CV/GATE.
|
||||
* Copiez ces fonctions dans votre main.cpp pour tester.
|
||||
*
|
||||
* @date 2025
|
||||
* @author Clément SAILLANT
|
||||
* @license GNU GPL v3
|
||||
*/
|
||||
|
||||
// Exemple d'utilisation des fonctions CV/GATE
|
||||
// À inclure dans votre main.cpp pour tester
|
||||
|
||||
#ifdef ARDUINO_ARCH_STM32
|
||||
|
||||
/**
|
||||
* @brief Test simple des sorties CV
|
||||
* Génère un sweep sur CV1 et CV2
|
||||
*/
|
||||
void test_cv_sweep() {
|
||||
Serial.println("Test CV Sweep - Démarrage");
|
||||
|
||||
// Reset au centre
|
||||
hid_mouse_reset_cv_values();
|
||||
delay(1000);
|
||||
|
||||
// Sweep CV1 de 0 à max
|
||||
for (int i = 0; i < 20; i++) {
|
||||
// Simule un mouvement vers la droite
|
||||
hid_mouse_send_report(false, 10, 0);
|
||||
delay(100);
|
||||
}
|
||||
|
||||
delay(500);
|
||||
|
||||
// Sweep CV2 de 0 à max
|
||||
for (int i = 0; i < 20; i++) {
|
||||
// Simule un mouvement vers le haut
|
||||
hid_mouse_send_report(false, 0, 10);
|
||||
delay(100);
|
||||
}
|
||||
|
||||
delay(500);
|
||||
|
||||
// Test du signal GATE
|
||||
Serial.println("Test GATE - 5 impulsions");
|
||||
for (int i = 0; i < 5; i++) {
|
||||
hid_mouse_send_report(true, 0, 0); // GATE ON
|
||||
delay(200);
|
||||
hid_mouse_send_report(false, 0, 0); // GATE OFF
|
||||
delay(200);
|
||||
}
|
||||
|
||||
// Retour au centre
|
||||
hid_mouse_reset_cv_values();
|
||||
|
||||
Serial.println("Test CV Sweep - Terminé");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Test interactif via Serial
|
||||
* Tapez des commandes pour contrôler les CV
|
||||
*/
|
||||
void test_cv_interactive() {
|
||||
if (Serial.available()) {
|
||||
String command = Serial.readStringUntil('\n');
|
||||
command.trim();
|
||||
|
||||
if (command == "reset") {
|
||||
hid_mouse_reset_cv_values();
|
||||
Serial.println("CV Reset");
|
||||
}
|
||||
else if (command == "up") {
|
||||
hid_mouse_send_report(false, 0, 5);
|
||||
Serial.println("CV2 +");
|
||||
}
|
||||
else if (command == "down") {
|
||||
hid_mouse_send_report(false, 0, -5);
|
||||
Serial.println("CV2 -");
|
||||
}
|
||||
else if (command == "left") {
|
||||
hid_mouse_send_report(false, -5, 0);
|
||||
Serial.println("CV1 -");
|
||||
}
|
||||
else if (command == "right") {
|
||||
hid_mouse_send_report(false, 5, 0);
|
||||
Serial.println("CV1 +");
|
||||
}
|
||||
else if (command == "gate") {
|
||||
hid_mouse_send_report(true, 0, 0);
|
||||
delay(100);
|
||||
hid_mouse_send_report(false, 0, 0);
|
||||
Serial.println("GATE pulse");
|
||||
}
|
||||
else if (command == "status") {
|
||||
uint16_t cv1 = hid_mouse_get_cv1_value();
|
||||
uint16_t cv2 = hid_mouse_get_cv2_value();
|
||||
float v1 = (float)(cv1 * 3.3 / 4095.0);
|
||||
float v2 = (float)(cv2 * 3.3 / 4095.0);
|
||||
|
||||
Serial.print("CV1: ");
|
||||
Serial.print(cv1);
|
||||
Serial.print(" (");
|
||||
Serial.print(v1, 2);
|
||||
Serial.print("V), CV2: ");
|
||||
Serial.print(cv2);
|
||||
Serial.print(" (");
|
||||
Serial.print(v2, 2);
|
||||
Serial.println("V)");
|
||||
}
|
||||
else if (command == "help") {
|
||||
Serial.println("Commandes disponibles:");
|
||||
Serial.println(" reset - Remet CV au centre");
|
||||
Serial.println(" up - CV2 +");
|
||||
Serial.println(" down - CV2 -");
|
||||
Serial.println(" left - CV1 -");
|
||||
Serial.println(" right - CV1 +");
|
||||
Serial.println(" gate - Impulsion GATE");
|
||||
Serial.println(" status - Affiche valeurs CV");
|
||||
Serial.println(" help - Cette aide");
|
||||
}
|
||||
else {
|
||||
Serial.println("Commande inconnue. Tapez 'help' pour l'aide.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Exemple d'intégration dans setup()
|
||||
*/
|
||||
void setup_cv_test() {
|
||||
Serial.begin(115200);
|
||||
|
||||
// Initialisation de la souris HID (qui inclut le DAC CV)
|
||||
hid_mouse_init();
|
||||
|
||||
Serial.println("=== Test CV/GATE pour STM32F3 ===");
|
||||
Serial.println("Connexions:");
|
||||
Serial.println(" CV1 (X) -> PA4");
|
||||
Serial.println(" CV2 (Y) -> PA5");
|
||||
Serial.println(" GATE -> PA0");
|
||||
Serial.println("Tapez 'help' pour les commandes");
|
||||
|
||||
// Test automatique au démarrage (optionnel)
|
||||
// test_cv_sweep();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Exemple d'intégration dans loop()
|
||||
*/
|
||||
void loop_cv_test() {
|
||||
// Test interactif
|
||||
test_cv_interactive();
|
||||
|
||||
// Autres tâches de votre programme...
|
||||
delay(10);
|
||||
}
|
||||
|
||||
#endif // ARDUINO_ARCH_STM32
|
||||
+1
-2
@@ -48,9 +48,8 @@ build_flags =
|
||||
; -D PIO_FRAMEWORK_ARDUINO_USB_FULLSPEED_FULLMODE
|
||||
debug_tool = stlink
|
||||
upload_protocol = stlink
|
||||
monitor_speed = 115200
|
||||
monitor_speed = 9600
|
||||
lib_deps =
|
||||
; je voudrais
|
||||
; electronrare/ADB @ ^1.0.0
|
||||
|
||||
[env:native]
|
||||
|
||||
@@ -0,0 +1,754 @@
|
||||
/**
|
||||
* @file cv_modulation.cpp
|
||||
* @brief Implémentation de la modulation avancée des signaux CV
|
||||
* @part of Apple-ADB-Ressurector
|
||||
*
|
||||
* @date 2025
|
||||
* @author Clément SAILLANT
|
||||
* @license GNU GPL v3
|
||||
*/
|
||||
|
||||
#include "cv_modulation.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_STM32
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <math.h>
|
||||
#include "stm32f3xx_hal.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_STM32
|
||||
#include <HardwareTimer.h> // Pour le timer debug optimisé
|
||||
#endif
|
||||
|
||||
// Constantes DAC/CV locales (pour éviter les problèmes d'include circulaire)
|
||||
#define CV_CENTER_VALUE_LOCAL 2047 // Valeur centrale du DAC (1.65V)
|
||||
#define CV_MAX_VALUE_LOCAL 4095 // Valeur maximale du DAC
|
||||
#define CV_MIN_VALUE_LOCAL 0 // Valeur minimale du DAC
|
||||
#define DAC_CV1_CHANNEL_LOCAL DAC_CHANNEL_1 // PA4 - DAC1_OUT1
|
||||
#define DAC_CV2_CHANNEL_LOCAL DAC_CHANNEL_2 // PA5 - DAC1_OUT2
|
||||
|
||||
// Variables globales
|
||||
static cv_modulation_state_t mod_state;
|
||||
static bool vibrato_enabled = true;
|
||||
static float vibrato_freq = VIBRATO_FREQUENCY;
|
||||
static uint16_t vibrato_depth = VIBRATO_DEPTH;
|
||||
static uint32_t last_update_time = 0;
|
||||
|
||||
// Variables de debug FM optimisées avec buffer circulaire
|
||||
static bool fm_debug_enabled = false;
|
||||
static uint32_t fm_debug_counter = 0;
|
||||
static uint32_t last_fm_debug_time = 0;
|
||||
|
||||
// Buffer circulaire pour debug FM (traitement en arrière-plan)
|
||||
#define FM_DEBUG_BUFFER_SIZE 32
|
||||
typedef struct {
|
||||
uint32_t timestamp;
|
||||
int8_t mouse_x;
|
||||
int8_t mouse_y;
|
||||
float fm_depth_cv1;
|
||||
float fm_depth_cv2;
|
||||
float vibrato_phase_cv1;
|
||||
float vibrato_phase_cv2;
|
||||
float fm_contribution_cv1;
|
||||
float fm_contribution_cv2;
|
||||
} fm_debug_entry_t;
|
||||
|
||||
static fm_debug_entry_t fm_debug_buffer[FM_DEBUG_BUFFER_SIZE];
|
||||
static volatile uint8_t fm_debug_write_idx = 0;
|
||||
static volatile uint8_t fm_debug_read_idx = 0;
|
||||
static volatile bool fm_debug_buffer_full = false;
|
||||
|
||||
// Timer pour traitement debug en arrière-plan
|
||||
static HardwareTimer* debug_timer = nullptr;
|
||||
static bool debug_timer_initialized = false;
|
||||
|
||||
// Accès aux variables partagées (maintenant dans dac_cv_manager)
|
||||
extern uint16_t current_cv1_value;
|
||||
extern uint16_t current_cv2_value;
|
||||
|
||||
// Déclaration forward de la fonction DAC (implémentée dans dac_cv_manager)
|
||||
extern void dac_cv_write_direct(uint32_t channel, uint16_t value);
|
||||
|
||||
/**
|
||||
* @brief Fonction sinus optimisée avec table de lookup
|
||||
* @param phase Phase en radians (0 à 2π)
|
||||
* @return Valeur sinus (-1.0 à +1.0)
|
||||
*/
|
||||
static float fast_sin(float phase) {
|
||||
// Normalisation de la phase dans [0, 2π]
|
||||
while (phase < 0.0f) phase += 2.0f * M_PI;
|
||||
while (phase >= 2.0f * M_PI) phase -= 2.0f * M_PI;
|
||||
|
||||
// Approximation polynomiale rapide du sinus
|
||||
// Utilisation d'une approximation de Taylor optimisée
|
||||
float x = phase;
|
||||
if (x > M_PI) x = 2.0f * M_PI - x;
|
||||
if (x > M_PI_2) x = M_PI - x;
|
||||
|
||||
// Approximation polynomiale (précision ~0.1%)
|
||||
float x2 = x * x;
|
||||
return x * (1.0f - x2 * (0.16666667f - x2 * 0.00833333f));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Traite les entrées debug en arrière-plan (appelé par interruption timer)
|
||||
*/
|
||||
static void process_debug_buffer(void) {
|
||||
static uint8_t entries_processed = 0;
|
||||
const uint8_t MAX_ENTRIES_PER_CALL = 2; // Limiter le nombre d'entrées par appel
|
||||
|
||||
// Traiter quelques entrées du buffer
|
||||
for (uint8_t i = 0; i < MAX_ENTRIES_PER_CALL && fm_debug_read_idx != fm_debug_write_idx; i++) {
|
||||
fm_debug_entry_t* entry = &fm_debug_buffer[fm_debug_read_idx];
|
||||
|
||||
// Affichage debug optimisé (seulement les données importantes)
|
||||
if (entries_processed % 3 == 0) { // 1 sur 3 pour réduire le spam
|
||||
Serial.print("FM[");
|
||||
Serial.print(entry->timestamp);
|
||||
Serial.print("] M(");
|
||||
Serial.print(entry->mouse_x);
|
||||
Serial.print(",");
|
||||
Serial.print(entry->mouse_y);
|
||||
Serial.print(") D(");
|
||||
Serial.print(entry->fm_depth_cv1, 2);
|
||||
Serial.print(",");
|
||||
Serial.print(entry->fm_depth_cv2, 2);
|
||||
Serial.print(") C(");
|
||||
Serial.print(entry->fm_contribution_cv1, 1);
|
||||
Serial.print(",");
|
||||
Serial.print(entry->fm_contribution_cv2, 1);
|
||||
Serial.println(")");
|
||||
}
|
||||
|
||||
fm_debug_read_idx = (fm_debug_read_idx + 1) % FM_DEBUG_BUFFER_SIZE;
|
||||
entries_processed++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Callback du timer debug (appelé à 10Hz)
|
||||
*/
|
||||
static void debug_timer_callback(void) {
|
||||
// Traiter le buffer debug si activé
|
||||
if (fm_debug_enabled) {
|
||||
process_debug_buffer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialise le timer pour debug en arrière-plan
|
||||
*/
|
||||
static void init_debug_timer(void) {
|
||||
if (debug_timer_initialized) return;
|
||||
|
||||
// Utilisation du HardwareTimer Arduino STM32 (Timer 15)
|
||||
debug_timer = new HardwareTimer(TIM15);
|
||||
|
||||
// Configuration: 10Hz = 100ms d'intervalle
|
||||
debug_timer->setOverflow(10, HERTZ_FORMAT); // 10Hz
|
||||
debug_timer->attachInterrupt(debug_timer_callback);
|
||||
|
||||
debug_timer_initialized = true;
|
||||
|
||||
Serial.println("Timer debug initialisé (10Hz, HardwareTimer)");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Démarre le timer debug
|
||||
*/
|
||||
static void start_debug_timer(void) {
|
||||
if (!debug_timer_initialized) init_debug_timer();
|
||||
if (debug_timer) {
|
||||
debug_timer->resume();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Arrête le timer debug
|
||||
*/
|
||||
static void stop_debug_timer(void) {
|
||||
if (debug_timer) {
|
||||
debug_timer->pause();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Ajoute une entrée au buffer debug FM (thread-safe)
|
||||
*/
|
||||
static void add_fm_debug_entry(int8_t mouse_x, int8_t mouse_y,
|
||||
float fm_depth_cv1, float fm_depth_cv2,
|
||||
float vibrato_phase_cv1, float vibrato_phase_cv2,
|
||||
float fm_contrib_cv1, float fm_contrib_cv2) {
|
||||
|
||||
if (!fm_debug_enabled) return;
|
||||
|
||||
// Désactiver les interruptions temporairement
|
||||
__disable_irq();
|
||||
|
||||
// Vérifier si le buffer est plein
|
||||
uint8_t next_write = (fm_debug_write_idx + 1) % FM_DEBUG_BUFFER_SIZE;
|
||||
if (next_write == fm_debug_read_idx) {
|
||||
// Buffer plein, écraser l'entrée la plus ancienne
|
||||
fm_debug_read_idx = (fm_debug_read_idx + 1) % FM_DEBUG_BUFFER_SIZE;
|
||||
fm_debug_buffer_full = true;
|
||||
}
|
||||
|
||||
// Ajouter la nouvelle entrée
|
||||
fm_debug_entry_t* entry = &fm_debug_buffer[fm_debug_write_idx];
|
||||
entry->timestamp = millis();
|
||||
entry->mouse_x = mouse_x;
|
||||
entry->mouse_y = mouse_y;
|
||||
entry->fm_depth_cv1 = fm_depth_cv1;
|
||||
entry->fm_depth_cv2 = fm_depth_cv2;
|
||||
entry->vibrato_phase_cv1 = vibrato_phase_cv1;
|
||||
entry->vibrato_phase_cv2 = vibrato_phase_cv2;
|
||||
entry->fm_contribution_cv1 = fm_contrib_cv1;
|
||||
entry->fm_contribution_cv2 = fm_contrib_cv2;
|
||||
|
||||
fm_debug_write_idx = next_write;
|
||||
|
||||
// Réactiver les interruptions
|
||||
__enable_irq();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Fonction de transition douce (courbe en S)
|
||||
* @param t Paramètre de transition (0.0 à 1.0)
|
||||
* @return Valeur lissée (0.0 à 1.0)
|
||||
*/
|
||||
static float smooth_transition(float t) {
|
||||
if (t <= 0.0f) return 0.0f;
|
||||
if (t >= 1.0f) return 1.0f;
|
||||
|
||||
// Courbe sinusoïdale douce (transition en S)
|
||||
return (1.0f - fast_sin(t * M_PI + M_PI * 0.5f)) * 0.5f;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialise le système de modulation CV
|
||||
*/
|
||||
void cv_modulation_init(void) {
|
||||
// Initialisation de l'état des modulateurs
|
||||
mod_state.current_cv1 = CV_CENTER_VALUE_LOCAL;
|
||||
mod_state.target_cv1 = CV_CENTER_VALUE_LOCAL;
|
||||
mod_state.current_cv2 = CV_CENTER_VALUE_LOCAL;
|
||||
mod_state.target_cv2 = CV_CENTER_VALUE_LOCAL;
|
||||
|
||||
mod_state.portamento_phase_cv1 = 0.0f;
|
||||
mod_state.portamento_phase_cv2 = 0.0f;
|
||||
mod_state.portamento_active_cv1 = false;
|
||||
mod_state.portamento_active_cv2 = false;
|
||||
|
||||
mod_state.vibrato_phase_cv1 = 0.0f;
|
||||
mod_state.vibrato_phase_cv2 = 0.0f;
|
||||
|
||||
mod_state.fm_depth_cv1 = 0.0f;
|
||||
mod_state.fm_depth_cv2 = 0.0f;
|
||||
|
||||
last_update_time = millis();
|
||||
|
||||
// Initialiser le buffer debug
|
||||
fm_debug_write_idx = 0;
|
||||
fm_debug_read_idx = 0;
|
||||
fm_debug_buffer_full = false;
|
||||
|
||||
// Initialiser le timer debug (mais ne pas le démarrer encore)
|
||||
init_debug_timer();
|
||||
|
||||
Serial.println("CV Modulation initialisée - Portamento + FM actifs + Debug optimisé");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Démarre une transition portamento vers une nouvelle note
|
||||
*/
|
||||
void cv_modulation_start_portamento(uint8_t cv_channel, uint16_t target_cv) {
|
||||
if (cv_channel == 1) {
|
||||
mod_state.target_cv1 = target_cv;
|
||||
mod_state.portamento_phase_cv1 = 0.0f;
|
||||
mod_state.portamento_active_cv1 = true;
|
||||
|
||||
Serial.print("Portamento CV1 démarré vers ");
|
||||
Serial.println(target_cv);
|
||||
} else if (cv_channel == 2) {
|
||||
mod_state.target_cv2 = target_cv;
|
||||
mod_state.portamento_phase_cv2 = 0.0f;
|
||||
mod_state.portamento_active_cv2 = true;
|
||||
|
||||
Serial.print("Portamento CV2 démarré vers ");
|
||||
Serial.println(target_cv);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Met à jour la modulation de fréquence ET les valeurs CV de base avec la souris
|
||||
*/
|
||||
void cv_modulation_update_fm(int8_t mouse_x, int8_t mouse_y) {
|
||||
// 1. Mise à jour des valeurs CV de base avec le mouvement de la souris
|
||||
if (abs(mouse_x) > 1 || abs(mouse_y) > 1) {
|
||||
// Sensibilité de contrôle CV direct (ajustable)
|
||||
const float CV_SENSITIVITY = 8.0f; // Plus la valeur est élevée, plus c'est sensible
|
||||
|
||||
// Calcul des nouvelles valeurs CV avec accumulation
|
||||
int16_t cv1_delta = (int16_t)((float)mouse_x * CV_SENSITIVITY);
|
||||
int16_t cv2_delta = (int16_t)((float)mouse_y * CV_SENSITIVITY);
|
||||
|
||||
// Mise à jour cumulative des valeurs CV cibles
|
||||
int32_t new_cv1 = (int32_t)mod_state.current_cv1 + cv1_delta;
|
||||
int32_t new_cv2 = (int32_t)mod_state.current_cv2 + cv2_delta;
|
||||
|
||||
// Limitation dans la plage DAC valide
|
||||
if (new_cv1 < CV_MIN_VALUE_LOCAL) new_cv1 = CV_MIN_VALUE_LOCAL;
|
||||
if (new_cv1 > CV_MAX_VALUE_LOCAL) new_cv1 = CV_MAX_VALUE_LOCAL;
|
||||
if (new_cv2 < CV_MIN_VALUE_LOCAL) new_cv2 = CV_MIN_VALUE_LOCAL;
|
||||
if (new_cv2 > CV_MAX_VALUE_LOCAL) new_cv2 = CV_MAX_VALUE_LOCAL;
|
||||
|
||||
// Appliquer les nouvelles valeurs
|
||||
mod_state.current_cv1 = (uint16_t)new_cv1;
|
||||
mod_state.current_cv2 = (uint16_t)new_cv2;
|
||||
mod_state.target_cv1 = (uint16_t)new_cv1; // Synchroniser les cibles
|
||||
mod_state.target_cv2 = (uint16_t)new_cv2;
|
||||
|
||||
// Debug des changements CV
|
||||
static uint32_t last_cv_debug = 0;
|
||||
uint32_t current_time = millis();
|
||||
if (current_time - last_cv_debug > 200) { // Debug toutes les 200ms
|
||||
Serial.print("CV mis à jour - CV1: ");
|
||||
Serial.print(mod_state.current_cv1);
|
||||
Serial.print(" (");
|
||||
Serial.print((mod_state.current_cv1 * 3.3f / 4095.0f), 2);
|
||||
Serial.print("V), CV2: ");
|
||||
Serial.print(mod_state.current_cv2);
|
||||
Serial.print(" (");
|
||||
Serial.print((mod_state.current_cv2 * 3.3f / 4095.0f), 2);
|
||||
Serial.println("V)");
|
||||
last_cv_debug = current_time;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Calcul des profondeurs de modulation FM (en plus des CV de base)
|
||||
float new_fm_depth_cv1 = (float)mouse_x / 127.0f * FM_SENSITIVITY;
|
||||
float new_fm_depth_cv2 = (float)mouse_y / 127.0f * FM_SENSITIVITY;
|
||||
|
||||
// Mise à jour des valeurs FM (thread-safe, pas d'interruption pendant cette opération)
|
||||
mod_state.fm_depth_cv1 = new_fm_depth_cv1;
|
||||
mod_state.fm_depth_cv2 = new_fm_depth_cv2;
|
||||
|
||||
// Si debug activé, ajouter au buffer pour traitement en arrière-plan
|
||||
if (fm_debug_enabled && (abs(mouse_x) > 2 || abs(mouse_y) > 2)) {
|
||||
// Calculer les contributions FM actuelles pour le debug
|
||||
float fm_contrib_cv1 = 0.0f;
|
||||
float fm_contrib_cv2 = 0.0f;
|
||||
|
||||
if (abs(mod_state.fm_depth_cv1) > 0.01f) {
|
||||
float fm_phase = mod_state.vibrato_phase_cv1 * 0.5f;
|
||||
fm_contrib_cv1 = fast_sin(fm_phase) * mod_state.fm_depth_cv1 * 30.0f;
|
||||
}
|
||||
|
||||
if (abs(mod_state.fm_depth_cv2) > 0.01f) {
|
||||
float fm_phase = mod_state.vibrato_phase_cv2 * 2.5f;
|
||||
fm_contrib_cv2 = fast_sin(fm_phase) * mod_state.fm_depth_cv2 * 20.0f;
|
||||
}
|
||||
|
||||
// Ajouter au buffer pour traitement asynchrone
|
||||
add_fm_debug_entry(mouse_x, mouse_y,
|
||||
mod_state.fm_depth_cv1, mod_state.fm_depth_cv2,
|
||||
mod_state.vibrato_phase_cv1, mod_state.vibrato_phase_cv2,
|
||||
fm_contrib_cv1, fm_contrib_cv2);
|
||||
}
|
||||
|
||||
// Debug léger synchrone pour surveillance critique
|
||||
static uint32_t last_critical_debug = 0;
|
||||
uint32_t current_time = millis();
|
||||
|
||||
if (current_time - last_critical_debug > 2000) {
|
||||
if (abs(mod_state.fm_depth_cv1) > 0.1f || abs(mod_state.fm_depth_cv2) > 0.1f) {
|
||||
Serial.print("FM ACTIF - D1:");
|
||||
Serial.print(mod_state.fm_depth_cv1, 2);
|
||||
Serial.print(" D2:");
|
||||
Serial.println(mod_state.fm_depth_cv2, 2);
|
||||
}
|
||||
last_critical_debug = current_time;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Traite toutes les modulations en temps réel
|
||||
*/
|
||||
void cv_modulation_process(void) {
|
||||
uint32_t current_time = millis();
|
||||
float delta_time = (current_time - last_update_time) / 1000.0f;
|
||||
last_update_time = current_time;
|
||||
|
||||
// 1. Traitement du portamento CV1
|
||||
if (mod_state.portamento_active_cv1) {
|
||||
mod_state.portamento_phase_cv1 += PORTAMENTO_SPEED * delta_time;
|
||||
|
||||
if (mod_state.portamento_phase_cv1 >= 1.0f) {
|
||||
mod_state.portamento_phase_cv1 = 1.0f;
|
||||
mod_state.portamento_active_cv1 = false;
|
||||
mod_state.current_cv1 = mod_state.target_cv1;
|
||||
} else {
|
||||
float blend = smooth_transition(mod_state.portamento_phase_cv1);
|
||||
mod_state.current_cv1 = (uint16_t)((1.0f - blend) * mod_state.current_cv1 +
|
||||
blend * mod_state.target_cv1);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Traitement du portamento CV2
|
||||
if (mod_state.portamento_active_cv2) {
|
||||
mod_state.portamento_phase_cv2 += PORTAMENTO_SPEED * delta_time;
|
||||
|
||||
if (mod_state.portamento_phase_cv2 >= 1.0f) {
|
||||
mod_state.portamento_phase_cv2 = 1.0f;
|
||||
mod_state.portamento_active_cv2 = false;
|
||||
mod_state.current_cv2 = mod_state.target_cv2;
|
||||
} else {
|
||||
float blend = smooth_transition(mod_state.portamento_phase_cv2);
|
||||
mod_state.current_cv2 = (uint16_t)((1.0f - blend) * mod_state.current_cv2 +
|
||||
blend * mod_state.target_cv2);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Application du vibrato
|
||||
mod_state.vibrato_phase_cv1 += vibrato_freq * delta_time * 2.0f * M_PI;
|
||||
mod_state.vibrato_phase_cv2 += vibrato_freq * delta_time * 2.0f * M_PI;
|
||||
|
||||
// Normalisation des phases vibrato
|
||||
if (mod_state.vibrato_phase_cv1 > 2.0f * M_PI)
|
||||
mod_state.vibrato_phase_cv1 -= 2.0f * M_PI;
|
||||
if (mod_state.vibrato_phase_cv2 > 2.0f * M_PI)
|
||||
mod_state.vibrato_phase_cv2 -= 2.0f * M_PI;
|
||||
|
||||
// 4. Calcul des valeurs CV finales avec modulations
|
||||
int16_t final_cv1 = mod_state.current_cv1;
|
||||
int16_t final_cv2 = mod_state.current_cv2;
|
||||
|
||||
// Vibrato sur CV1
|
||||
if (vibrato_enabled) {
|
||||
float vibrato_cv1 = fast_sin(mod_state.vibrato_phase_cv1) * vibrato_depth;
|
||||
final_cv1 += (int16_t)vibrato_cv1;
|
||||
}
|
||||
|
||||
// Vibrato sur CV2
|
||||
if (vibrato_enabled) {
|
||||
float vibrato_cv2 = fast_sin(mod_state.vibrato_phase_cv2) * vibrato_depth;
|
||||
final_cv2 += (int16_t)vibrato_cv2;
|
||||
}
|
||||
|
||||
// Modulation de fréquence CV1 (gamme audio 400Hz-2KHz)
|
||||
float fm_cv1_contribution = 0.0f;
|
||||
if (abs(mod_state.fm_depth_cv1) > 0.01f) {
|
||||
float fm_phase = mod_state.vibrato_phase_cv1 * 0.5f; // 800Hz * 0.5 = 400Hz
|
||||
fm_cv1_contribution = fast_sin(fm_phase) * mod_state.fm_depth_cv1 * 30.0f;
|
||||
final_cv1 += (int16_t)fm_cv1_contribution;
|
||||
|
||||
// Debug détaillé FM CV1
|
||||
if (fm_debug_enabled && (current_time % 100 == 0)) {
|
||||
Serial.print("FM CV1 - Phase: ");
|
||||
Serial.print(fm_phase, 4);
|
||||
Serial.print("rad, Sin: ");
|
||||
Serial.print(fast_sin(fm_phase), 4);
|
||||
Serial.print(", Contribution: ");
|
||||
Serial.println(fm_cv1_contribution, 2);
|
||||
}
|
||||
}
|
||||
|
||||
// Modulation de fréquence CV2 (gamme audio 400Hz-2KHz)
|
||||
float fm_cv2_contribution = 0.0f;
|
||||
if (abs(mod_state.fm_depth_cv2) > 0.01f) {
|
||||
float fm_phase = mod_state.vibrato_phase_cv2 * 2.5f; // 800Hz * 2.5 = 2000Hz (2KHz)
|
||||
fm_cv2_contribution = fast_sin(fm_phase) * mod_state.fm_depth_cv2 * 20.0f;
|
||||
final_cv2 += (int16_t)fm_cv2_contribution;
|
||||
|
||||
// Debug détaillé FM CV2
|
||||
if (fm_debug_enabled && (current_time % 100 == 0)) {
|
||||
Serial.print("FM CV2 - Phase: ");
|
||||
Serial.print(fm_phase, 4);
|
||||
Serial.print("rad, Sin: ");
|
||||
Serial.print(fast_sin(fm_phase), 4);
|
||||
Serial.print(", Contribution: ");
|
||||
Serial.println(fm_cv2_contribution, 2);
|
||||
}
|
||||
}
|
||||
|
||||
// Limitation des valeurs dans la plage DAC valide
|
||||
if (final_cv1 > CV_MAX_VALUE_LOCAL) final_cv1 = CV_MAX_VALUE_LOCAL;
|
||||
if (final_cv1 < CV_MIN_VALUE_LOCAL) final_cv1 = CV_MIN_VALUE_LOCAL;
|
||||
if (final_cv2 > CV_MAX_VALUE_LOCAL) final_cv2 = CV_MAX_VALUE_LOCAL;
|
||||
if (final_cv2 < CV_MIN_VALUE_LOCAL) final_cv2 = CV_MIN_VALUE_LOCAL;
|
||||
|
||||
// Mise à jour des DACs via le gestionnaire centralisé
|
||||
dac_cv_write_direct(DAC_CV1_CHANNEL_LOCAL, final_cv1);
|
||||
dac_cv_write_direct(DAC_CV2_CHANNEL_LOCAL, final_cv2);
|
||||
|
||||
// Note: Les variables partagées sont automatiquement mises à jour par dac_cv_write_direct()
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Active/désactive le vibrato
|
||||
*/
|
||||
void cv_modulation_set_vibrato(bool enabled) {
|
||||
vibrato_enabled = enabled;
|
||||
Serial.print("Vibrato: ");
|
||||
Serial.println(enabled ? "ON" : "OFF");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Définit les paramètres du vibrato
|
||||
*/
|
||||
void cv_modulation_set_vibrato_params(float frequency, uint16_t depth) {
|
||||
vibrato_freq = frequency;
|
||||
vibrato_depth = depth;
|
||||
|
||||
Serial.print("Vibrato params - Freq: ");
|
||||
Serial.print(frequency, 1);
|
||||
Serial.print("Hz, Depth: ");
|
||||
Serial.println(depth);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Recentre les valeurs CV à leur position neutre (1.65V)
|
||||
*/
|
||||
void cv_modulation_reset_cv_values(void) {
|
||||
mod_state.current_cv1 = CV_CENTER_VALUE_LOCAL;
|
||||
mod_state.current_cv2 = CV_CENTER_VALUE_LOCAL;
|
||||
mod_state.target_cv1 = CV_CENTER_VALUE_LOCAL;
|
||||
mod_state.target_cv2 = CV_CENTER_VALUE_LOCAL;
|
||||
|
||||
// Écriture immédiate des valeurs centrées
|
||||
dac_cv_write_direct(DAC_CV1_CHANNEL_LOCAL, CV_CENTER_VALUE_LOCAL);
|
||||
dac_cv_write_direct(DAC_CV2_CHANNEL_LOCAL, CV_CENTER_VALUE_LOCAL);
|
||||
|
||||
Serial.println("Valeurs CV recentrées à 1.65V (position neutre)");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Obtient l'état actuel des modulateurs
|
||||
*/
|
||||
const cv_modulation_state_t* cv_modulation_get_state(void) {
|
||||
return &mod_state;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Active/désactive le debug détaillé de la modulation FM
|
||||
* @param enabled État du debug (true = activé, false = désactivé)
|
||||
*/
|
||||
void cv_modulation_set_fm_debug(bool enabled) {
|
||||
fm_debug_enabled = enabled;
|
||||
|
||||
if (enabled) {
|
||||
// Démarrer le timer de traitement debug
|
||||
start_debug_timer();
|
||||
Serial.println("Debug FM ACTIVÉ avec traitement en arrière-plan optimisé");
|
||||
Serial.println("Format debug FM compact:");
|
||||
Serial.println("- FM[timestamp] M(x,y) D(depth1,depth2) C(contrib1,contrib2)");
|
||||
Serial.println("- Timer d'interruption: 10Hz pour traitement asynchrone");
|
||||
Serial.print("- Buffer circulaire: ");
|
||||
Serial.print(FM_DEBUG_BUFFER_SIZE);
|
||||
Serial.println(" entrées");
|
||||
} else {
|
||||
// Arrêter le timer de traitement debug
|
||||
stop_debug_timer();
|
||||
|
||||
// Vider le buffer restant
|
||||
while (fm_debug_read_idx != fm_debug_write_idx) {
|
||||
process_debug_buffer();
|
||||
}
|
||||
|
||||
Serial.println("Debug FM DÉSACTIVÉ - Timer arrêté, buffer vidé");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Affiche les statistiques de performance du debug optimisé
|
||||
*/
|
||||
void cv_modulation_debug_performance_stats(void) {
|
||||
Serial.println("\n=== STATISTIQUES DEBUG PERFORMANCE ===");
|
||||
|
||||
uint8_t buffer_usage = 0;
|
||||
if (fm_debug_write_idx >= fm_debug_read_idx) {
|
||||
buffer_usage = fm_debug_write_idx - fm_debug_read_idx;
|
||||
} else {
|
||||
buffer_usage = (FM_DEBUG_BUFFER_SIZE - fm_debug_read_idx) + fm_debug_write_idx;
|
||||
}
|
||||
|
||||
float usage_percent = ((float)buffer_usage / FM_DEBUG_BUFFER_SIZE) * 100.0f;
|
||||
|
||||
Serial.print("Buffer debug - Utilisation: ");
|
||||
Serial.print(buffer_usage);
|
||||
Serial.print("/");
|
||||
Serial.print(FM_DEBUG_BUFFER_SIZE);
|
||||
Serial.print(" (");
|
||||
Serial.print(usage_percent, 1);
|
||||
Serial.println("%)");
|
||||
|
||||
Serial.print("État timer debug: ");
|
||||
Serial.println(debug_timer_initialized ? "Initialisé" : "Non initialisé");
|
||||
|
||||
Serial.print("Debug FM: ");
|
||||
Serial.println(fm_debug_enabled ? "ACTIF" : "INACTIF");
|
||||
|
||||
Serial.print("Buffer plein détecté: ");
|
||||
Serial.println(fm_debug_buffer_full ? "OUI" : "NON");
|
||||
|
||||
if (fm_debug_buffer_full) {
|
||||
Serial.println("ATTENTION: Buffer saturé, certaines données debug perdues");
|
||||
Serial.println("Conseils: Réduire la fréquence de mouvement souris ou augmenter BUFFER_SIZE");
|
||||
}
|
||||
|
||||
// Reset du flag de buffer plein après affichage
|
||||
fm_debug_buffer_full = false;
|
||||
|
||||
Serial.println("==========================================\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Affiche un rapport complet sur l'état de la modulation FM
|
||||
*/
|
||||
void cv_modulation_debug_fm_report(void) {
|
||||
Serial.println("\n=== RAPPORT COMPLET FM MODULATION ===");
|
||||
|
||||
// Paramètres généraux
|
||||
Serial.print("Vibrato base - Fréq: ");
|
||||
Serial.print(vibrato_freq, 1);
|
||||
Serial.print("Hz, Profondeur: ");
|
||||
Serial.print(vibrato_depth);
|
||||
Serial.print(", État: ");
|
||||
Serial.println(vibrato_enabled ? "ON" : "OFF");
|
||||
|
||||
// État FM actuel
|
||||
Serial.print("FM depths - CV1: ");
|
||||
Serial.print(mod_state.fm_depth_cv1, 4);
|
||||
Serial.print(" (");
|
||||
Serial.print(abs(mod_state.fm_depth_cv1) > 0.01f ? "ACTIF" : "INACTIF");
|
||||
Serial.print("), CV2: ");
|
||||
Serial.print(mod_state.fm_depth_cv2, 4);
|
||||
Serial.print(" (");
|
||||
Serial.print(abs(mod_state.fm_depth_cv2) > 0.01f ? "ACTIF" : "INACTIF");
|
||||
Serial.println(")");
|
||||
|
||||
// Phases vibrato actuelles
|
||||
Serial.print("Phases vibrato - CV1: ");
|
||||
Serial.print(mod_state.vibrato_phase_cv1 * 180.0f / M_PI, 1);
|
||||
Serial.print("°, CV2: ");
|
||||
Serial.print(mod_state.vibrato_phase_cv2 * 180.0f / M_PI, 1);
|
||||
Serial.println("°");
|
||||
|
||||
// Fréquences FM calculées
|
||||
float fm_freq_cv1 = vibrato_freq * 0.5f; // 800Hz * 0.5 = 400Hz
|
||||
float fm_freq_cv2 = vibrato_freq * 2.5f; // 800Hz * 2.5 = 2000Hz
|
||||
|
||||
Serial.print("Fréquences FM - CV1: ");
|
||||
Serial.print(fm_freq_cv1, 1);
|
||||
Serial.print("Hz (multiplier: 0.5), CV2: ");
|
||||
Serial.print(fm_freq_cv2, 1);
|
||||
Serial.println("Hz (multiplier: 2.5)");
|
||||
|
||||
// Sensibilité et amplitudes
|
||||
Serial.print("FM sensitivity: ");
|
||||
Serial.print(FM_SENSITIVITY, 4);
|
||||
Serial.print(", Amplitudes - CV1: x30, CV2: x20");
|
||||
Serial.println();
|
||||
|
||||
// Seuils d'activation
|
||||
Serial.print("Seuils d'activation - FM: 0.01, Mouse: 3 unités");
|
||||
Serial.println();
|
||||
|
||||
Serial.print("Debug FM: ");
|
||||
Serial.println(fm_debug_enabled ? "ACTIVÉ" : "DÉSACTIVÉ");
|
||||
|
||||
Serial.println("======================================\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Teste la modulation FM avec des valeurs prédéfinies
|
||||
* @param test_duration Durée du test en millisecondes
|
||||
*/
|
||||
void cv_modulation_test_fm(uint32_t test_duration) {
|
||||
Serial.println("\n=== TEST MODULATION FM ===");
|
||||
Serial.print("Durée du test: ");
|
||||
Serial.print(test_duration);
|
||||
Serial.println("ms");
|
||||
|
||||
uint32_t start_time = millis();
|
||||
uint32_t last_test_time = start_time;
|
||||
|
||||
// Activer le debug pour le test
|
||||
bool old_debug_state = fm_debug_enabled;
|
||||
fm_debug_enabled = true;
|
||||
|
||||
while ((millis() - start_time) < test_duration) {
|
||||
uint32_t elapsed = millis() - start_time;
|
||||
|
||||
// Générer des mouvements de test sinusoïdaux
|
||||
float test_phase = (float)elapsed / 1000.0f * 2.0f * M_PI; // 1 cycle par seconde
|
||||
int8_t test_mouse_x = (int8_t)(sin(test_phase) * 50.0f); // ±50 unités
|
||||
int8_t test_mouse_y = (int8_t)(cos(test_phase) * 30.0f); // ±30 unités
|
||||
|
||||
// Mettre à jour la FM avec les valeurs de test
|
||||
cv_modulation_update_fm(test_mouse_x, test_mouse_y);
|
||||
|
||||
// Traiter les modulations
|
||||
cv_modulation_process();
|
||||
|
||||
// Rapport périodique
|
||||
if ((millis() - last_test_time) > 500) {
|
||||
Serial.print("Test FM - Temps: ");
|
||||
Serial.print(elapsed);
|
||||
Serial.print("ms, Mouse: (");
|
||||
Serial.print(test_mouse_x);
|
||||
Serial.print(", ");
|
||||
Serial.print(test_mouse_y);
|
||||
Serial.println(")");
|
||||
last_test_time = millis();
|
||||
}
|
||||
|
||||
delay(10); // 100Hz de mise à jour
|
||||
}
|
||||
|
||||
// Restaurer l'état du debug
|
||||
fm_debug_enabled = old_debug_state;
|
||||
|
||||
// Reset des valeurs FM
|
||||
cv_modulation_update_fm(0, 0);
|
||||
|
||||
Serial.println("=== FIN TEST FM ===\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Configure les paramètres du vibrato
|
||||
* @param enabled Active/désactive le vibrato
|
||||
* @param frequency Fréquence en Hz (0.1 - 20.0)
|
||||
* @param depth Profondeur en unités DAC (0 - 200)
|
||||
*/
|
||||
void cv_modulation_configure_vibrato(bool enabled, float frequency, uint16_t depth) {
|
||||
vibrato_enabled = enabled;
|
||||
vibrato_freq = frequency;
|
||||
vibrato_depth = depth;
|
||||
|
||||
Serial.print("Vibrato configuré - État: ");
|
||||
Serial.print(enabled ? "ON" : "OFF");
|
||||
Serial.print(", Fréquence: ");
|
||||
Serial.print(frequency, 1);
|
||||
Serial.print("Hz, Profondeur: ");
|
||||
Serial.println(depth);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Configure la vitesse du portamento
|
||||
* @param speed Vitesse du portamento (0.01 - 0.1, défaut: 0.05)
|
||||
*/
|
||||
void cv_modulation_configure_portamento_speed(float speed) {
|
||||
// Limitation des valeurs pour éviter les comportements erratiques
|
||||
if (speed < 0.01f) speed = 0.01f;
|
||||
if (speed > 0.1f) speed = 0.1f;
|
||||
|
||||
// Le speed est utilisé directement dans smooth_transition() comme facteur de lissage
|
||||
Serial.print("Vitesse portamento configurée: ");
|
||||
Serial.println(speed, 3);
|
||||
|
||||
// Note: Dans cette implémentation, le speed est codé en dur dans smooth_transition()
|
||||
// Pour le rendre configurable, il faudrait ajouter une variable globale
|
||||
}
|
||||
|
||||
#endif // ARDUINO_ARCH_STM32
|
||||
@@ -0,0 +1,152 @@
|
||||
/**
|
||||
* @file cv_modulation.h
|
||||
* @brief Modulation avancée des signaux CV avec génération sinusoïdale
|
||||
* @part of Apple-ADB-Ressurector
|
||||
*
|
||||
* @date 2025
|
||||
* @author Clément SAILLANT
|
||||
* @license GNU GPL v3
|
||||
*/
|
||||
|
||||
#ifndef CV_MODULATION_H
|
||||
#define CV_MODULATION_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef ARDUINO_ARCH_STM32
|
||||
|
||||
// Constantes DAC/CV (dupliquées depuis dac_cv_manager.h pour éviter dépendance circulaire)
|
||||
#ifndef DAC_CV1_CHANNEL
|
||||
#define DAC_CV1_CHANNEL DAC_CHANNEL_1 // PA4 - DAC1_OUT1
|
||||
#define DAC_CV2_CHANNEL DAC_CHANNEL_2 // PA5 - DAC1_OUT2
|
||||
#define CV_CENTER_VALUE 2047 // Valeur centrale du DAC (1.65V)
|
||||
#define CV_MAX_VALUE 4095 // Valeur maximale du DAC
|
||||
#define CV_MIN_VALUE 0 // Valeur minimale du DAC
|
||||
#endif
|
||||
|
||||
// Configuration des modulateurs
|
||||
#define PORTAMENTO_ENABLED 1
|
||||
#define VIBRATO_ENABLED 1
|
||||
#define FM_MODULATION_ENABLED 1
|
||||
|
||||
// Paramètres de modulation
|
||||
#define PORTAMENTO_SPEED 0.05f // Vitesse du portamento (0.01-0.1)
|
||||
#define VIBRATO_FREQUENCY 800.0f // Fréquence vibrato en Hz (gamme audio)
|
||||
#define VIBRATO_DEPTH 50 // Profondeur vibrato (unités DAC)
|
||||
#define FM_SENSITIVITY 0.1f // Sensibilité modulation souris
|
||||
|
||||
// Structure pour l'état des modulateurs
|
||||
typedef struct {
|
||||
// Portamento
|
||||
uint16_t current_cv1;
|
||||
uint16_t target_cv1;
|
||||
uint16_t current_cv2;
|
||||
uint16_t target_cv2;
|
||||
float portamento_phase_cv1;
|
||||
float portamento_phase_cv2;
|
||||
bool portamento_active_cv1;
|
||||
bool portamento_active_cv2;
|
||||
|
||||
// Vibrato LFO
|
||||
float vibrato_phase_cv1;
|
||||
float vibrato_phase_cv2;
|
||||
|
||||
// Modulation de fréquence
|
||||
float fm_depth_cv1;
|
||||
float fm_depth_cv2;
|
||||
|
||||
} cv_modulation_state_t;
|
||||
|
||||
/**
|
||||
* @brief Initialise le système de modulation CV
|
||||
*/
|
||||
void cv_modulation_init(void);
|
||||
|
||||
// Déclaration forward de la fonction DAC (implémentée dans dac_cv_manager)
|
||||
void dac_cv_write_direct(uint32_t channel, uint16_t value);
|
||||
|
||||
/**
|
||||
* @brief Démarre une transition portamento vers une nouvelle note
|
||||
* @param cv_channel Canal CV (1 ou 2)
|
||||
* @param target_cv Valeur CV cible
|
||||
*/
|
||||
void cv_modulation_start_portamento(uint8_t cv_channel, uint16_t target_cv);
|
||||
|
||||
/**
|
||||
* @brief Met à jour la modulation de fréquence à partir des mouvements souris
|
||||
* @param mouse_x Déplacement souris X (-127 à +127)
|
||||
* @param mouse_y Déplacement souris Y (-127 à +127)
|
||||
*/
|
||||
void cv_modulation_update_fm(int8_t mouse_x, int8_t mouse_y);
|
||||
|
||||
/**
|
||||
* @brief Traite toutes les modulations et met à jour les DACs
|
||||
* Cette fonction doit être appelée régulièrement (ex: 1kHz)
|
||||
*/
|
||||
void cv_modulation_process(void);
|
||||
|
||||
/**
|
||||
* @brief Active/désactive le vibrato
|
||||
* @param enabled True pour activer, false pour désactiver
|
||||
*/
|
||||
void cv_modulation_set_vibrato(bool enabled);
|
||||
|
||||
/**
|
||||
* @brief Définit les paramètres du vibrato
|
||||
* @param frequency Fréquence en Hz (0.1 - 20.0)
|
||||
* @param depth Profondeur en unités DAC (0 - 200)
|
||||
*/
|
||||
void cv_modulation_set_vibrato_params(float frequency, uint16_t depth);
|
||||
|
||||
/**
|
||||
* @brief Obtient l'état actuel des modulateurs
|
||||
* @return Pointeur vers la structure d'état (lecture seule)
|
||||
*/
|
||||
const cv_modulation_state_t* cv_modulation_get_state(void);
|
||||
|
||||
/**
|
||||
* @brief Configure les paramètres du vibrato
|
||||
* @param enabled Active/désactive le vibrato
|
||||
* @param frequency Fréquence en Hz (0.1 - 20.0)
|
||||
* @param depth Profondeur en unités DAC (0 - 200)
|
||||
*/
|
||||
void cv_modulation_configure_vibrato(bool enabled, float frequency, uint16_t depth);
|
||||
|
||||
/**
|
||||
* @brief Configure la vitesse du portamento
|
||||
* @param speed Vitesse du portamento (0.01 - 0.1, défaut: 0.05)
|
||||
*/
|
||||
void cv_modulation_configure_portamento_speed(float speed);
|
||||
|
||||
/**
|
||||
* @brief Recentre les valeurs CV à leur position neutre (1.65V)
|
||||
*/
|
||||
void cv_modulation_reset_cv_values(void);
|
||||
|
||||
// Fonctions de debug pour la modulation FM
|
||||
/**
|
||||
* @brief Active/désactive le debug détaillé de la modulation FM avec traitement optimisé
|
||||
* @param enabled État du debug (true = activé, false = désactivé)
|
||||
*/
|
||||
void cv_modulation_set_fm_debug(bool enabled);
|
||||
|
||||
/**
|
||||
* @brief Affiche les statistiques de performance du debug optimisé
|
||||
*/
|
||||
void cv_modulation_debug_performance_stats(void);
|
||||
|
||||
/**
|
||||
* @brief Affiche un rapport complet sur l'état de la modulation FM
|
||||
*/
|
||||
void cv_modulation_debug_fm_report(void);
|
||||
|
||||
/**
|
||||
* @brief Teste la modulation FM avec des valeurs prédéfinies
|
||||
* @param test_duration Durée du test en millisecondes
|
||||
*/
|
||||
void cv_modulation_test_fm(uint32_t test_duration);
|
||||
|
||||
#endif // ARDUINO_ARCH_STM32
|
||||
|
||||
#endif // CV_MODULATION_H
|
||||
@@ -0,0 +1,313 @@
|
||||
/**
|
||||
* @file dac_cv_manager.cpp
|
||||
* @brief Gestionnaire centralisé des sorties DAC, CV et GATE pour STM32F3
|
||||
* @part of Apple-ADB-Ressurector
|
||||
*
|
||||
* @date 2025
|
||||
* @author Clément SAILLANT
|
||||
* @license GNU GPL v3
|
||||
*/
|
||||
|
||||
#include "dac_cv_manager.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_STM32
|
||||
#include <Arduino.h>
|
||||
#include "stm32f3xx_hal.h"
|
||||
#include "stm32f3xx_hal_dac.h"
|
||||
#include "cv_modulation.h"
|
||||
|
||||
// Variables globales pour le DAC
|
||||
static DAC_HandleTypeDef hdac1;
|
||||
static DAC_ChannelConfTypeDef sConfig;
|
||||
static bool gate_state = false;
|
||||
|
||||
// Variables pour les valeurs CV cumulatives (partagées avec les autres modules)
|
||||
uint16_t current_cv1_value = CV_CENTER_VALUE;
|
||||
uint16_t current_cv2_value = CV_CENTER_VALUE;
|
||||
|
||||
/**
|
||||
* @brief Initialise le DAC STM32F3 (fonction interne)
|
||||
*/
|
||||
static bool dac_hardware_init(void) {
|
||||
// Configuration du DAC1
|
||||
hdac1.Instance = DAC1;
|
||||
if (HAL_DAC_Init(&hdac1) != HAL_OK) {
|
||||
Serial.println("ERREUR: Initialisation du DAC échouée");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Configuration du canal 1 (PA4)
|
||||
sConfig.DAC_Trigger = DAC_TRIGGER_NONE;
|
||||
sConfig.DAC_OutputBuffer = DAC_OUTPUTBUFFER_ENABLE;
|
||||
if (HAL_DAC_ConfigChannel(&hdac1, &sConfig, DAC_CV1_CHANNEL) != HAL_OK) {
|
||||
Serial.println("ERREUR: Configuration DAC canal 1 échouée");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Configuration du canal 2 (PA5)
|
||||
if (HAL_DAC_ConfigChannel(&hdac1, &sConfig, DAC_CV2_CHANNEL) != HAL_OK) {
|
||||
Serial.println("ERREUR: Configuration DAC canal 2 échouée");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Démarrage des canaux DAC
|
||||
if (HAL_DAC_Start(&hdac1, DAC_CV1_CHANNEL) != HAL_OK ||
|
||||
HAL_DAC_Start(&hdac1, DAC_CV2_CHANNEL) != HAL_OK) {
|
||||
Serial.println("ERREUR: Démarrage des canaux DAC échoué");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialise le système DAC complet (DAC + CV + GATE + Modulation)
|
||||
*/
|
||||
void dac_cv_manager_init(void) {
|
||||
Serial.println("Initialisation du gestionnaire DAC/CV...");
|
||||
|
||||
// Initialisation du matériel DAC
|
||||
if (!dac_hardware_init()) {
|
||||
Serial.println("ERREUR: Échec de l'initialisation du DAC");
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialisation des valeurs CV au centre
|
||||
current_cv1_value = CV_CENTER_VALUE;
|
||||
current_cv2_value = CV_CENTER_VALUE;
|
||||
HAL_DAC_SetValue(&hdac1, DAC_CV1_CHANNEL, DAC_ALIGN_12B_R, current_cv1_value);
|
||||
HAL_DAC_SetValue(&hdac1, DAC_CV2_CHANNEL, DAC_ALIGN_12B_R, current_cv2_value);
|
||||
|
||||
// Configuration de la pin GATE comme sortie numérique
|
||||
pinMode(GATE_PIN, OUTPUT);
|
||||
digitalWrite(GATE_PIN, LOW);
|
||||
gate_state = false;
|
||||
|
||||
// Initialisation du système de modulation avancée
|
||||
cv_modulation_init();
|
||||
|
||||
Serial.println("Gestionnaire DAC/CV/GATE + Modulation initialisés");
|
||||
Serial.println("CV1 (PA4), CV2 (PA5), GATE (PA0) configurés");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Ferme et désactive le système DAC
|
||||
*/
|
||||
void dac_cv_manager_deinit(void) {
|
||||
Serial.println("Arrêt du gestionnaire DAC/CV...");
|
||||
|
||||
// Arrêt des canaux DAC
|
||||
HAL_DAC_Stop(&hdac1, DAC_CV1_CHANNEL);
|
||||
HAL_DAC_Stop(&hdac1, DAC_CV2_CHANNEL);
|
||||
|
||||
// Désactivation du DAC
|
||||
HAL_DAC_DeInit(&hdac1);
|
||||
|
||||
// Reset de la pin GATE
|
||||
digitalWrite(GATE_PIN, LOW);
|
||||
gate_state = false;
|
||||
|
||||
Serial.println("Gestionnaire DAC/CV arrêté");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Écrit directement une valeur sur un canal DAC
|
||||
* @param channel Canal DAC (DAC_CHANNEL_1 ou DAC_CHANNEL_2)
|
||||
* @param value Valeur 12-bit (0-4095)
|
||||
*/
|
||||
void dac_cv_write_direct(uint32_t channel, uint16_t value) {
|
||||
// Limitation de sécurité de la valeur
|
||||
if (value > CV_MAX_VALUE) value = CV_MAX_VALUE;
|
||||
|
||||
HAL_DAC_SetValue(&hdac1, channel, DAC_ALIGN_12B_R, value);
|
||||
|
||||
// Mise à jour des variables globales pour le suivi
|
||||
if (channel == DAC_CV1_CHANNEL) {
|
||||
current_cv1_value = value;
|
||||
} else if (channel == DAC_CV2_CHANNEL) {
|
||||
current_cv2_value = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Met à jour les valeurs CV avec accumulation (pour la souris)
|
||||
* @param offset_x Déplacement horizontal (-127 à +127)
|
||||
* @param offset_y Déplacement vertical (-127 à +127)
|
||||
*/
|
||||
void dac_cv_update_cumulative(int8_t offset_x, int8_t offset_y) {
|
||||
// Calcul des nouvelles valeurs avec accumulation
|
||||
int16_t new_cv1 = current_cv1_value + (offset_x * CV_STEP_SIZE);
|
||||
int16_t new_cv2 = current_cv2_value + (offset_y * CV_STEP_SIZE);
|
||||
|
||||
// Limitation des valeurs dans la plage DAC valide
|
||||
if (new_cv1 > CV_MAX_VALUE) new_cv1 = CV_MAX_VALUE;
|
||||
if (new_cv1 < CV_MIN_VALUE) new_cv1 = CV_MIN_VALUE;
|
||||
if (new_cv2 > CV_MAX_VALUE) new_cv2 = CV_MAX_VALUE;
|
||||
if (new_cv2 < CV_MIN_VALUE) new_cv2 = CV_MIN_VALUE;
|
||||
|
||||
// Mise à jour des valeurs actuelles
|
||||
current_cv1_value = (uint16_t)new_cv1;
|
||||
current_cv2_value = (uint16_t)new_cv2;
|
||||
|
||||
// Écriture sur les DAC
|
||||
HAL_DAC_SetValue(&hdac1, DAC_CV1_CHANNEL, DAC_ALIGN_12B_R, current_cv1_value);
|
||||
HAL_DAC_SetValue(&hdac1, DAC_CV2_CHANNEL, DAC_ALIGN_12B_R, current_cv2_value);
|
||||
|
||||
// Debug des valeurs mises à jour
|
||||
Serial.print("DAC CV mise à jour cumulative - CV1: ");
|
||||
Serial.print(current_cv1_value);
|
||||
Serial.print(" (");
|
||||
Serial.print((float)(current_cv1_value * 3.3 / 4095.0), 2);
|
||||
Serial.print("V), CV2: ");
|
||||
Serial.print(current_cv2_value);
|
||||
Serial.print(" (");
|
||||
Serial.print((float)(current_cv2_value * 3.3 / 4095.0), 2);
|
||||
Serial.println("V)");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Démarre une transition portamento vers une valeur CV cible
|
||||
* @param cv_channel Canal CV (1 ou 2)
|
||||
* @param target_cv Valeur CV cible (0-4095)
|
||||
*/
|
||||
void dac_cv_start_portamento(uint8_t cv_channel, uint16_t target_cv) {
|
||||
// Limitation de sécurité
|
||||
if (target_cv > CV_MAX_VALUE) target_cv = CV_MAX_VALUE;
|
||||
|
||||
// Délégation au système de modulation
|
||||
cv_modulation_start_portamento(cv_channel, target_cv);
|
||||
|
||||
Serial.print("Portamento démarré sur CV");
|
||||
Serial.print(cv_channel);
|
||||
Serial.print(" vers ");
|
||||
Serial.print(target_cv);
|
||||
Serial.print(" (");
|
||||
Serial.print((float)(target_cv * 3.3 / 4095.0), 2);
|
||||
Serial.println("V)");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Met à jour la modulation de fréquence par mouvements souris
|
||||
* @param mouse_x Déplacement souris X (-127 à +127)
|
||||
* @param mouse_y Déplacement souris Y (-127 à +127)
|
||||
*/
|
||||
void dac_cv_update_fm_modulation(int8_t mouse_x, int8_t mouse_y) {
|
||||
// Délégation au système de modulation
|
||||
cv_modulation_update_fm(mouse_x, mouse_y);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Contrôle le signal GATE
|
||||
* @param state État du GATE (true = HIGH, false = LOW)
|
||||
*/
|
||||
void dac_cv_set_gate(bool state) {
|
||||
gate_state = state;
|
||||
digitalWrite(GATE_PIN, state ? HIGH : LOW);
|
||||
|
||||
Serial.print("GATE ");
|
||||
Serial.println(state ? "ON" : "OFF");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Remet les valeurs CV au centre (reset)
|
||||
*/
|
||||
void dac_cv_reset_values(void) {
|
||||
current_cv1_value = CV_CENTER_VALUE;
|
||||
current_cv2_value = CV_CENTER_VALUE;
|
||||
|
||||
HAL_DAC_SetValue(&hdac1, DAC_CV1_CHANNEL, DAC_ALIGN_12B_R, current_cv1_value);
|
||||
HAL_DAC_SetValue(&hdac1, DAC_CV2_CHANNEL, DAC_ALIGN_12B_R, current_cv2_value);
|
||||
|
||||
// Reset du GATE
|
||||
dac_cv_set_gate(false);
|
||||
|
||||
Serial.println("Valeurs DAC/CV remises au centre (1.65V)");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Traite toutes les modulations en cours (à appeler régulièrement)
|
||||
*/
|
||||
void dac_cv_process_modulations(void) {
|
||||
// Délégation au système de modulation pour traitement temps réel
|
||||
cv_modulation_process();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Obtient la valeur CV1 actuelle
|
||||
* @return Valeur DAC actuelle pour CV1 (0-4095)
|
||||
*/
|
||||
uint16_t dac_cv_get_cv1_value(void) {
|
||||
return current_cv1_value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Obtient la valeur CV2 actuelle
|
||||
* @return Valeur DAC actuelle pour CV2 (0-4095)
|
||||
*/
|
||||
uint16_t dac_cv_get_cv2_value(void) {
|
||||
return current_cv2_value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Configure les paramètres du vibrato
|
||||
* @param enabled Active/désactive le vibrato
|
||||
* @param frequency Fréquence en Hz (0.1 - 20.0)
|
||||
* @param depth Profondeur en unités DAC (0 - 200)
|
||||
*/
|
||||
void dac_cv_configure_vibrato(bool enabled, float frequency, uint16_t depth) {
|
||||
cv_modulation_configure_vibrato(enabled, frequency, depth);
|
||||
|
||||
Serial.print("Vibrato configuré - État: ");
|
||||
Serial.print(enabled ? "ON" : "OFF");
|
||||
Serial.print(", Fréquence: ");
|
||||
Serial.print(frequency);
|
||||
Serial.print("Hz, Profondeur: ");
|
||||
Serial.println(depth);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Configure la vitesse du portamento
|
||||
* @param speed Vitesse du portamento (0.01 - 0.1, défaut: 0.05)
|
||||
*/
|
||||
void dac_cv_configure_portamento_speed(float speed) {
|
||||
cv_modulation_configure_portamento_speed(speed);
|
||||
|
||||
Serial.print("Vitesse portamento configurée: ");
|
||||
Serial.println(speed);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Obtient l'état actuel du système DAC/CV
|
||||
* @param cv1_value Pointeur pour recevoir la valeur CV1 actuelle
|
||||
* @param cv2_value Pointeur pour recevoir la valeur CV2 actuelle
|
||||
* @param gate_state_ptr Pointeur pour recevoir l'état du GATE
|
||||
*/
|
||||
void dac_cv_get_state(uint16_t* cv1_value, uint16_t* cv2_value, bool* gate_state_ptr) {
|
||||
if (cv1_value) *cv1_value = current_cv1_value;
|
||||
if (cv2_value) *cv2_value = current_cv2_value;
|
||||
if (gate_state_ptr) *gate_state_ptr = gate_state;
|
||||
}
|
||||
|
||||
#else // ARDUINO_ARCH_STM32
|
||||
|
||||
// Implémentations vides pour les autres plateformes
|
||||
void dac_cv_manager_init(void) {}
|
||||
void dac_cv_manager_deinit(void) {}
|
||||
void dac_cv_write_direct(uint32_t channel, uint16_t value) {}
|
||||
void dac_cv_update_cumulative(int8_t offset_x, int8_t offset_y) {}
|
||||
void dac_cv_start_portamento(uint8_t cv_channel, uint16_t target_cv) {}
|
||||
void dac_cv_update_fm_modulation(int8_t mouse_x, int8_t mouse_y) {}
|
||||
void dac_cv_set_gate(bool state) {}
|
||||
void dac_cv_reset_values(void) {}
|
||||
void dac_cv_process_modulations(void) {}
|
||||
uint16_t dac_cv_get_cv1_value(void) { return 0; }
|
||||
uint16_t dac_cv_get_cv2_value(void) { return 0; }
|
||||
void dac_cv_configure_vibrato(bool enabled, float frequency, uint16_t depth) {}
|
||||
void dac_cv_configure_portamento_speed(float speed) {}
|
||||
void dac_cv_get_state(uint16_t* cv1_value, uint16_t* cv2_value, bool* gate_state_ptr) {}
|
||||
|
||||
// Variables partagées (vides pour les autres plateformes)
|
||||
uint16_t current_cv1_value = 0;
|
||||
uint16_t current_cv2_value = 0;
|
||||
|
||||
#endif // ARDUINO_ARCH_STM32
|
||||
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* @file dac_cv_manager.h
|
||||
* @brief Gestionnaire centralisé des sorties DAC, CV et GATE pour STM32F3
|
||||
* @part of Apple-ADB-Ressurector
|
||||
*
|
||||
* @date 2025
|
||||
* @author Clément SAILLANT
|
||||
* @license GNU GPL v3
|
||||
*/
|
||||
|
||||
#ifndef DAC_CV_MANAGER_H
|
||||
#define DAC_CV_MANAGER_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef ARDUINO_ARCH_STM32
|
||||
|
||||
// Configuration des broches DAC pour STM32F3
|
||||
#define DAC_CV1_CHANNEL DAC_CHANNEL_1 // PA4 - DAC1_OUT1
|
||||
#define DAC_CV2_CHANNEL DAC_CHANNEL_2 // PA5 - DAC1_OUT2
|
||||
#define GATE_PIN PA0 // Pin numérique pour le signal GATE
|
||||
#define CV_CENTER_VALUE 2047 // Valeur centrale du DAC (1.65V)
|
||||
#define CV_STEP_SIZE 8 // Taille du pas pour les incréments CV
|
||||
#define CV_MAX_VALUE 4095 // Valeur maximale du DAC
|
||||
#define CV_MIN_VALUE 0 // Valeur minimale du DAC
|
||||
|
||||
/**
|
||||
* @brief Initialise le système DAC complet (DAC + CV + GATE + Modulation)
|
||||
*/
|
||||
void dac_cv_manager_init(void);
|
||||
|
||||
/**
|
||||
* @brief Ferme et désactive le système DAC
|
||||
*/
|
||||
void dac_cv_manager_deinit(void);
|
||||
|
||||
/**
|
||||
* @brief Écrit directement une valeur sur un canal DAC
|
||||
* @param channel Canal DAC (DAC_CHANNEL_1 ou DAC_CHANNEL_2)
|
||||
* @param value Valeur 12-bit (0-4095)
|
||||
*/
|
||||
void dac_cv_write_direct(uint32_t channel, uint16_t value);
|
||||
|
||||
/**
|
||||
* @brief Met à jour les valeurs CV avec accumulation (pour la souris)
|
||||
* @param offset_x Déplacement horizontal (-127 à +127)
|
||||
* @param offset_y Déplacement vertical (-127 à +127)
|
||||
*/
|
||||
void dac_cv_update_cumulative(int8_t offset_x, int8_t offset_y);
|
||||
|
||||
/**
|
||||
* @brief Démarre une transition portamento vers une valeur CV cible
|
||||
* @param cv_channel Canal CV (1 ou 2)
|
||||
* @param target_cv Valeur CV cible (0-4095)
|
||||
*/
|
||||
void dac_cv_start_portamento(uint8_t cv_channel, uint16_t target_cv);
|
||||
|
||||
/**
|
||||
* @brief Met à jour la modulation de fréquence par mouvements souris
|
||||
* @param mouse_x Déplacement souris X (-127 à +127)
|
||||
* @param mouse_y Déplacement souris Y (-127 à +127)
|
||||
*/
|
||||
void dac_cv_update_fm_modulation(int8_t mouse_x, int8_t mouse_y);
|
||||
|
||||
/**
|
||||
* @brief Contrôle le signal GATE
|
||||
* @param state État du GATE (true = HIGH, false = LOW)
|
||||
*/
|
||||
void dac_cv_set_gate(bool state);
|
||||
|
||||
/**
|
||||
* @brief Remet les valeurs CV au centre (reset)
|
||||
*/
|
||||
void dac_cv_reset_values(void);
|
||||
|
||||
/**
|
||||
* @brief Traite toutes les modulations en cours (à appeler régulièrement)
|
||||
* Cette fonction gère le portamento, vibrato et modulation FM
|
||||
*/
|
||||
void dac_cv_process_modulations(void);
|
||||
|
||||
/**
|
||||
* @brief Obtient la valeur CV1 actuelle
|
||||
* @return Valeur DAC actuelle pour CV1 (0-4095)
|
||||
*/
|
||||
uint16_t dac_cv_get_cv1_value(void);
|
||||
|
||||
/**
|
||||
* @brief Obtient la valeur CV2 actuelle
|
||||
* @return Valeur DAC actuelle pour CV2 (0-4095)
|
||||
*/
|
||||
uint16_t dac_cv_get_cv2_value(void);
|
||||
|
||||
/**
|
||||
* @brief Configure les paramètres du vibrato
|
||||
* @param enabled Active/désactive le vibrato
|
||||
* @param frequency Fréquence en Hz (0.1 - 20.0)
|
||||
* @param depth Profondeur en unités DAC (0 - 200)
|
||||
*/
|
||||
void dac_cv_configure_vibrato(bool enabled, float frequency, uint16_t depth);
|
||||
|
||||
/**
|
||||
* @brief Configure la vitesse du portamento
|
||||
* @param speed Vitesse du portamento (0.01 - 0.1, défaut: 0.05)
|
||||
*/
|
||||
void dac_cv_configure_portamento_speed(float speed);
|
||||
|
||||
/**
|
||||
* @brief Obtient l'état actuel du système DAC/CV
|
||||
* @param cv1_value Pointeur pour recevoir la valeur CV1 actuelle
|
||||
* @param cv2_value Pointeur pour recevoir la valeur CV2 actuelle
|
||||
* @param gate_state Pointeur pour recevoir l'état du GATE
|
||||
*/
|
||||
void dac_cv_get_state(uint16_t* cv1_value, uint16_t* cv2_value, bool* gate_state);
|
||||
|
||||
// Variables partagées (accessibles depuis d'autres modules)
|
||||
extern uint16_t current_cv1_value; // Valeur actuelle CV1
|
||||
extern uint16_t current_cv2_value; // Valeur actuelle CV2
|
||||
|
||||
#endif // ARDUINO_ARCH_STM32
|
||||
|
||||
#endif // DAC_CV_MANAGER_H
|
||||
+176
-1
@@ -15,6 +15,7 @@
|
||||
#include "hid_keyboard.h"
|
||||
#ifdef ARDUINO_ARCH_STM32
|
||||
#include "usbd_hid_composite_if.h"
|
||||
#include "dac_cv_manager.h" // Gestionnaire centralisé DAC/CV/GATE
|
||||
#endif
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#include <BLEHIDDevice.h> // Ajout de l'inclusion manquante pour BLECharacteristic
|
||||
@@ -80,6 +81,11 @@ void hid_keyboard_send_report(hid_key_report *report) {
|
||||
Serial.println();
|
||||
|
||||
#ifdef ARDUINO_ARCH_STM32
|
||||
// Traitement CV pour clavier musical
|
||||
#ifdef KEYBOARD_CV_ENABLED
|
||||
hid_keyboard_process_cv_output(report);
|
||||
#endif
|
||||
|
||||
HID_Composite_keyboard_sendReport(buf, 8);
|
||||
#endif
|
||||
|
||||
@@ -258,4 +264,173 @@ bool hid_keyboard_update_modifier_in_report(hid_key_report *report,
|
||||
|
||||
Serial.println("Modificateur inconnu.");
|
||||
return false; // Aucun changement
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef ARDUINO_ARCH_STM32
|
||||
#ifdef KEYBOARD_CV_ENABLED
|
||||
|
||||
// Variables pour le suivi des notes jouées (polyphonie partielle)
|
||||
static uint8_t last_played_note_cv1 = 255; // 255 = aucune note sur CV1
|
||||
static uint8_t last_played_note_cv2 = 255; // 255 = aucune note sur CV2
|
||||
static bool gate_active = false;
|
||||
|
||||
/**
|
||||
* @brief Table de correspondance ADB vers notes MIDI
|
||||
* Mapping inspiré du layout QWERTY comme clavier de piano
|
||||
*/
|
||||
static const struct {
|
||||
uint8_t adb_key;
|
||||
uint8_t midi_note;
|
||||
uint8_t cv_channel; // 1 = CV1, 2 = CV2
|
||||
} adb_to_midi_map[] = {
|
||||
// Rangée du bas (touches blanches) - Do à Si → CV1 (voix principale)
|
||||
{0x00, KEY_NOTE_C4, 1}, // A = Do
|
||||
{0x01, KEY_NOTE_D4, 1}, // S = Ré
|
||||
{0x02, KEY_NOTE_E4, 1}, // D = Mi
|
||||
{0x03, KEY_NOTE_F4, 1}, // F = Fa
|
||||
{0x04, KEY_NOTE_G4, 1}, // G = Sol
|
||||
{0x05, KEY_NOTE_A4, 1}, // H = La
|
||||
{0x06, KEY_NOTE_B4, 1}, // J = Si
|
||||
|
||||
// Rangée du haut (touches noires - dièses) → CV2 (voix harmonique)
|
||||
{0x0C, KEY_NOTE_C4_SHARP, 2}, // Q = Do#
|
||||
{0x0D, KEY_NOTE_D4_SHARP, 2}, // W = Ré#
|
||||
{0x0F, KEY_NOTE_F4_SHARP, 2}, // R = Fa# (pas de Mi#)
|
||||
{0x10, KEY_NOTE_G4_SHARP, 2}, // T = Sol#
|
||||
{0x11, KEY_NOTE_A4_SHARP, 2}, // Y = La#
|
||||
|
||||
// Octave supérieure (touches numériques) → CV2 (voix lead)
|
||||
{0x1D, KEY_NOTE_C4 + 12, 2}, // 1 = Do (octave +1)
|
||||
{0x1E, KEY_NOTE_D4 + 12, 2}, // 2 = Ré (octave +1)
|
||||
{0x1F, KEY_NOTE_E4 + 12, 2}, // 3 = Mi (octave +1)
|
||||
{0x20, KEY_NOTE_F4 + 12, 2}, // 4 = Fa (octave +1)
|
||||
{0x21, KEY_NOTE_G4 + 12, 2}, // 5 = Sol (octave +1)
|
||||
{0x23, KEY_NOTE_A4 + 12, 2}, // 6 = La (octave +1)
|
||||
{0x22, KEY_NOTE_B4 + 12, 2}, // 7 = Si (octave +1)
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Convertit un code de touche ADB en note MIDI et canal CV
|
||||
*/
|
||||
uint8_t hid_keyboard_adb_to_midi_note(uint8_t adb_key, uint8_t* cv_channel) {
|
||||
for (int i = 0; i < sizeof(adb_to_midi_map) / sizeof(adb_to_midi_map[0]); i++) {
|
||||
if (adb_to_midi_map[i].adb_key == adb_key) {
|
||||
if (cv_channel) *cv_channel = adb_to_midi_map[i].cv_channel;
|
||||
return adb_to_midi_map[i].midi_note;
|
||||
}
|
||||
}
|
||||
return 255; // Pas de correspondance
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convertit une note MIDI en valeur CV DAC
|
||||
*/
|
||||
uint16_t hid_keyboard_midi_note_to_cv(uint8_t midi_note) {
|
||||
if (midi_note > 127) return 0;
|
||||
|
||||
// Conversion MIDI vers CV : 1V/octave standard
|
||||
// Note MIDI 60 (Do central) = 2V (environ DAC 2480)
|
||||
// Note MIDI 0 = 0V (DAC 0)
|
||||
// Note MIDI 127 = ~10.5V (mais limité à 3.3V donc DAC 4095)
|
||||
|
||||
// Formule : CV = (note_midi / 12) * (4095 / 10)
|
||||
// Cela donne environ 1V par octave dans la plage 0-3.3V
|
||||
uint16_t cv_value = (uint16_t)((midi_note * 4095.0) / 120.0);
|
||||
|
||||
if (cv_value > 4095) cv_value = 4095;
|
||||
return cv_value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Traite les touches pour la sortie CV musicale avec modulation de pitch par souris
|
||||
*/
|
||||
void hid_keyboard_process_cv_output(hid_key_report* report) {
|
||||
// Rechercher les notes actives pour CV1 et CV2
|
||||
uint8_t current_note_cv1 = 255;
|
||||
uint8_t current_note_cv2 = 255;
|
||||
|
||||
for (int i = 0; i < KEY_REPORT_KEYS_COUNT; i++) {
|
||||
if (report->keys[i] != 0) {
|
||||
uint8_t cv_channel = 0;
|
||||
uint8_t midi_note = hid_keyboard_adb_to_midi_note(report->keys[i], &cv_channel);
|
||||
if (midi_note != 255) {
|
||||
if (cv_channel == 1 && current_note_cv1 == 255) {
|
||||
current_note_cv1 = midi_note;
|
||||
} else if (cv_channel == 2 && current_note_cv2 == 255) {
|
||||
current_note_cv2 = midi_note;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Accès aux variables partagées de la souris
|
||||
extern uint16_t current_cv1_value; // Valeur CV1 avec modulation souris
|
||||
extern uint16_t current_cv2_value; // Valeur CV2 avec modulation souris
|
||||
extern DAC_HandleTypeDef hdac1;
|
||||
|
||||
// Variables pour debug
|
||||
bool cv1_updated = false;
|
||||
bool cv2_updated = false;
|
||||
|
||||
// Traitement CV1 (voix principale) avec portamento
|
||||
if (current_note_cv1 != 255) {
|
||||
uint16_t base_cv_value = hid_keyboard_midi_note_to_cv(current_note_cv1);
|
||||
|
||||
// Utiliser le gestionnaire DAC centralisé pour le portamento
|
||||
dac_cv_start_portamento(1, base_cv_value);
|
||||
cv1_updated = true;
|
||||
|
||||
if (current_note_cv1 != last_played_note_cv1) {
|
||||
last_played_note_cv1 = current_note_cv1;
|
||||
Serial.print("CV1 (voix principale) - Note MIDI: ");
|
||||
Serial.print(current_note_cv1);
|
||||
Serial.print(" CV cible: ");
|
||||
Serial.print(base_cv_value);
|
||||
Serial.print(" (");
|
||||
Serial.print((float)(base_cv_value * 3.3 / 4095.0), 2);
|
||||
Serial.println("V) [Portamento actif]");
|
||||
}
|
||||
} else if (last_played_note_cv1 != 255) {
|
||||
last_played_note_cv1 = 255;
|
||||
Serial.println("CV1 OFF - Voix principale arrêtée");
|
||||
}
|
||||
|
||||
// Traitement CV2 (voix harmonique/lead) avec portamento
|
||||
if (current_note_cv2 != 255) {
|
||||
uint16_t base_cv_value = hid_keyboard_midi_note_to_cv(current_note_cv2);
|
||||
|
||||
// Utiliser le gestionnaire DAC centralisé pour le portamento
|
||||
dac_cv_start_portamento(2, base_cv_value);
|
||||
cv2_updated = true;
|
||||
|
||||
if (current_note_cv2 != last_played_note_cv2) {
|
||||
last_played_note_cv2 = current_note_cv2;
|
||||
Serial.print("CV2 (voix harmonique) - Note MIDI: ");
|
||||
Serial.print(current_note_cv2);
|
||||
Serial.print(" CV cible: ");
|
||||
Serial.print(base_cv_value);
|
||||
Serial.print(" (");
|
||||
Serial.print((float)(base_cv_value * 3.3 / 4095.0), 2);
|
||||
Serial.println("V) [Portamento actif]");
|
||||
}
|
||||
} else if (last_played_note_cv2 != 255) {
|
||||
last_played_note_cv2 = 255;
|
||||
Serial.println("CV2 OFF - Voix harmonique arrêtée");
|
||||
}
|
||||
|
||||
// Gestion du GATE (actif si au moins une note est pressée)
|
||||
bool should_gate_be_active = (current_note_cv1 != 255) || (current_note_cv2 != 255);
|
||||
|
||||
if (should_gate_be_active && !gate_active) {
|
||||
digitalWrite(PA0, HIGH); // GATE_PIN
|
||||
gate_active = true;
|
||||
Serial.println("GATE ON - Notes actives");
|
||||
} else if (!should_gate_be_active && gate_active) {
|
||||
digitalWrite(PA0, LOW); // GATE_PIN
|
||||
gate_active = false;
|
||||
Serial.println("GATE OFF - Aucune note");
|
||||
}
|
||||
}
|
||||
|
||||
#endif // KEYBOARD_CV_ENABLED
|
||||
#endif // ARDUINO_ARCH_STM32
|
||||
@@ -100,4 +100,46 @@ bool hid_keyboard_remove_key_from_report(hid_key_report* report, uint8_t hid_key
|
||||
*/
|
||||
bool hid_keyboard_update_modifier_in_report(hid_key_report* report, uint8_t modifier, bool pressed);
|
||||
|
||||
#ifdef ARDUINO_ARCH_STM32
|
||||
// Configuration CV pour clavier musical (partage les sorties avec la souris)
|
||||
#define KEYBOARD_CV_ENABLED
|
||||
|
||||
// Mapping clavier vers notes (standard MIDI)
|
||||
#define KEY_NOTE_C4 60 // Do central (261.63 Hz)
|
||||
#define KEY_NOTE_C4_SHARP 61 // Do#
|
||||
#define KEY_NOTE_D4 62 // Ré
|
||||
#define KEY_NOTE_D4_SHARP 63 // Ré#
|
||||
#define KEY_NOTE_E4 64 // Mi
|
||||
#define KEY_NOTE_F4 65 // Fa
|
||||
#define KEY_NOTE_F4_SHARP 66 // Fa#
|
||||
#define KEY_NOTE_G4 67 // Sol
|
||||
#define KEY_NOTE_G4_SHARP 68 // Sol#
|
||||
#define KEY_NOTE_A4 69 // La (440 Hz)
|
||||
#define KEY_NOTE_A4_SHARP 70 // La#
|
||||
#define KEY_NOTE_B4 71 // Si
|
||||
|
||||
// Fonctions CV pour clavier
|
||||
/**
|
||||
* @brief Convertit un code de touche ADB en note MIDI avec canal CV
|
||||
* @param adb_key Code de touche ADB
|
||||
* @param cv_channel Pointeur vers le canal CV de sortie (1=CV1, 2=CV2)
|
||||
* @return Note MIDI (0-127) ou 255 si pas de correspondance
|
||||
*/
|
||||
uint8_t hid_keyboard_adb_to_midi_note(uint8_t adb_key, uint8_t* cv_channel = nullptr);
|
||||
|
||||
/**
|
||||
* @brief Convertit une note MIDI en valeur CV DAC
|
||||
* @param midi_note Note MIDI (0-127)
|
||||
* @return Valeur DAC 12-bit (0-4095)
|
||||
*/
|
||||
uint16_t hid_keyboard_midi_note_to_cv(uint8_t midi_note);
|
||||
|
||||
/**
|
||||
* @brief Traite les touches pour la sortie CV musicale
|
||||
* @param report Rapport HID du clavier
|
||||
*/
|
||||
void hid_keyboard_process_cv_output(hid_key_report* report);
|
||||
|
||||
#endif
|
||||
|
||||
#endif // HID_KEYBOARD_H
|
||||
@@ -0,0 +1,396 @@
|
||||
/**
|
||||
* @file hid_midi.cpp
|
||||
* @brief Implémentation de l'interface HID MIDI pour STM32F3 et ESP32
|
||||
* @part of Apple-ADB-Ressurector
|
||||
*
|
||||
* @date 2025
|
||||
* @author Clément SAILLANT
|
||||
* @license GNU GPL v3
|
||||
*/
|
||||
|
||||
#include "hid_midi.h"
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifdef ARDUINO_ARCH_STM32
|
||||
#include "usbd_hid_composite_if.h"
|
||||
#include "ADBKeymap.h"
|
||||
#include "stm32f3xx_hal.h"
|
||||
#endif
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#include <BLEHIDDevice.h>
|
||||
#include <BLECharacteristic.h>
|
||||
#include <Arduino.h>
|
||||
extern BLECharacteristic* input_midi;
|
||||
extern bool isBleConnected;
|
||||
#endif
|
||||
|
||||
// Configuration par défaut des canaux MIDI
|
||||
static midi_channels_config_t midi_config = {
|
||||
.keyboard_channel = 1,
|
||||
.mouse_channel = 2,
|
||||
.cv1_channel = 3,
|
||||
.cv2_channel = 4
|
||||
};
|
||||
|
||||
// État des notes MIDI
|
||||
static midi_notes_state_t notes_state = {0};
|
||||
static bool debug_enabled = false;
|
||||
|
||||
// Configuration de l'arpégiateur
|
||||
static bool arpeggiator_enabled = false;
|
||||
static uint8_t arpeggiator_tempo = 120;
|
||||
static uint8_t arpeggiator_pattern = 0;
|
||||
static uint32_t last_arp_time = 0;
|
||||
static uint8_t arp_step = 0;
|
||||
|
||||
// Transposition globale
|
||||
static int8_t global_transpose = 0;
|
||||
|
||||
// Contrôleurs souris (valeurs cumulatives)
|
||||
static int16_t mouse_cc_x = 64; // Centre = 64
|
||||
static int16_t mouse_cc_y = 64; // Centre = 64
|
||||
|
||||
// Mapping ADB vers notes MIDI - Initialisation manuelle pour compatibilité C++
|
||||
static uint16_t ADB_TO_MIDI_MAP[256] = {0}; // Tableau initialisé à zéro
|
||||
|
||||
// Fonction d'initialisation du mapping MIDI
|
||||
static void init_adb_midi_mapping(void) {
|
||||
// Rangée QWERTY (octave 4)
|
||||
ADB_TO_MIDI_MAP[0x0C] = 60; // Q -> C4 (Do)
|
||||
ADB_TO_MIDI_MAP[0x0D] = 62; // W -> D4 (Ré)
|
||||
ADB_TO_MIDI_MAP[0x0E] = 64; // E -> E4 (Mi)
|
||||
ADB_TO_MIDI_MAP[0x0F] = 65; // R -> F4 (Fa)
|
||||
ADB_TO_MIDI_MAP[0x11] = 67; // T -> G4 (Sol)
|
||||
ADB_TO_MIDI_MAP[0x10] = 69; // Y -> A4 (La)
|
||||
ADB_TO_MIDI_MAP[0x20] = 71; // U -> B4 (Si)
|
||||
|
||||
// Rangée ASDFGHJ (octave 3)
|
||||
ADB_TO_MIDI_MAP[0x00] = 48; // A -> C3
|
||||
ADB_TO_MIDI_MAP[0x01] = 50; // S -> D3
|
||||
ADB_TO_MIDI_MAP[0x02] = 52; // D -> E3
|
||||
ADB_TO_MIDI_MAP[0x03] = 53; // F -> F3
|
||||
ADB_TO_MIDI_MAP[0x05] = 55; // G -> G3
|
||||
ADB_TO_MIDI_MAP[0x04] = 57; // H -> A3
|
||||
ADB_TO_MIDI_MAP[0x26] = 59; // J -> B3
|
||||
|
||||
// Touches numériques (octave 5)
|
||||
ADB_TO_MIDI_MAP[0x1D] = 72; // 0 -> C5
|
||||
ADB_TO_MIDI_MAP[0x12] = 74; // 1 -> D5
|
||||
ADB_TO_MIDI_MAP[0x13] = 76; // 2 -> E5
|
||||
ADB_TO_MIDI_MAP[0x14] = 77; // 3 -> F5
|
||||
ADB_TO_MIDI_MAP[0x15] = 79; // 4 -> G5
|
||||
ADB_TO_MIDI_MAP[0x17] = 81; // 5 -> A5
|
||||
ADB_TO_MIDI_MAP[0x16] = 83; // 6 -> B5
|
||||
ADB_TO_MIDI_MAP[0x1A] = 84; // 7 -> C6
|
||||
ADB_TO_MIDI_MAP[0x1C] = 86; // 8 -> D6
|
||||
ADB_TO_MIDI_MAP[0x19] = 88; // 9 -> E6
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialise l'interface HID MIDI
|
||||
*/
|
||||
void hid_midi_init(void) {
|
||||
// Initialiser le mapping ADB vers MIDI
|
||||
init_adb_midi_mapping();
|
||||
|
||||
// Réinitialisation de l'état des notes
|
||||
memset(¬es_state, 0, sizeof(notes_state));
|
||||
|
||||
// Configuration des canaux par défaut
|
||||
midi_config.keyboard_channel = 1;
|
||||
midi_config.mouse_channel = 2;
|
||||
midi_config.cv1_channel = 3;
|
||||
midi_config.cv2_channel = 4;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Ferme l'interface HID MIDI
|
||||
*/
|
||||
void hid_midi_close(void) {
|
||||
// Envoyer All Notes Off sur tous les canaux
|
||||
for (uint8_t ch = 1; ch <= 16; ch++) {
|
||||
hid_midi_all_notes_off(ch);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Configure les canaux MIDI
|
||||
*/
|
||||
void hid_midi_configure_channels(const midi_channels_config_t* config) {
|
||||
if (config) {
|
||||
midi_config = *config;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Envoie un message MIDI via HID
|
||||
*/
|
||||
static void send_midi_message(uint8_t status, uint8_t data1, uint8_t data2) {
|
||||
uint8_t midi_packet[4] = {
|
||||
0x09, // Cable number + Code Index Number (Note-on/Note-off)
|
||||
status,
|
||||
data1,
|
||||
data2
|
||||
};
|
||||
|
||||
// Ajuster le CIN selon le type de message
|
||||
if ((status & 0xF0) == MIDI_NOTE_ON) {
|
||||
midi_packet[0] = 0x09; // Note On
|
||||
} else if ((status & 0xF0) == MIDI_NOTE_OFF) {
|
||||
midi_packet[0] = 0x08; // Note Off
|
||||
} else if ((status & 0xF0) == MIDI_CONTROL_CHANGE) {
|
||||
midi_packet[0] = 0x0B; // Control Change
|
||||
} else if ((status & 0xF0) == MIDI_PITCH_BEND) {
|
||||
midi_packet[0] = 0x0E; // Pitch Bend
|
||||
}
|
||||
|
||||
#ifdef ARDUINO_ARCH_STM32
|
||||
// Envoyer via HID Composite (à implémenter dans usbd_hid_composite_if)
|
||||
// HID_Composite_midi_sendReport(midi_packet, 4);
|
||||
#endif
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
if (isBleConnected && input_midi) {
|
||||
input_midi->setValue(midi_packet, 4);
|
||||
input_midi->notify();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Envoie un message Note On
|
||||
*/
|
||||
void hid_midi_note_on(uint8_t channel, uint8_t note, uint8_t velocity) {
|
||||
if (channel == 0 || channel > 16) return;
|
||||
if (note > 127 || velocity > 127) return;
|
||||
|
||||
// Appliquer la transposition
|
||||
int16_t transposed_note = note + global_transpose;
|
||||
if (transposed_note < 0 || transposed_note > 127) return;
|
||||
|
||||
uint8_t status = MIDI_NOTE_ON | (channel - 1);
|
||||
send_midi_message(status, (uint8_t)transposed_note, velocity);
|
||||
|
||||
// Mettre à jour l'état des notes
|
||||
notes_state.active_notes[channel - 1] = (uint8_t)transposed_note;
|
||||
notes_state.note_count[channel - 1]++;
|
||||
#ifdef ARDUINO_ARCH_STM32
|
||||
notes_state.last_note_time[channel - 1] = HAL_GetTick();
|
||||
#endif
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
notes_state.last_note_time[channel - 1] = millis();
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Envoie un message Note Off
|
||||
*/
|
||||
void hid_midi_note_off(uint8_t channel, uint8_t note, uint8_t velocity) {
|
||||
if (channel == 0 || channel > 16) return;
|
||||
if (note > 127 || velocity > 127) return;
|
||||
|
||||
// Appliquer la transposition
|
||||
int16_t transposed_note = note + global_transpose;
|
||||
if (transposed_note < 0 || transposed_note > 127) return;
|
||||
|
||||
uint8_t status = MIDI_NOTE_OFF | (channel - 1);
|
||||
send_midi_message(status, (uint8_t)transposed_note, velocity);
|
||||
|
||||
// Mettre à jour l'état des notes
|
||||
if (notes_state.note_count[channel - 1] > 0) {
|
||||
notes_state.note_count[channel - 1]--;
|
||||
}
|
||||
#ifdef ARDUINO_ARCH_STM32
|
||||
notes_state.last_note_time[channel - 1] = HAL_GetTick();
|
||||
#endif
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
notes_state.last_note_time[channel - 1] = millis();
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Envoie un message Control Change
|
||||
*/
|
||||
void hid_midi_control_change(uint8_t channel, uint8_t controller, uint8_t value) {
|
||||
if (channel == 0 || channel > 16) return;
|
||||
if (controller > 127 || value > 127) return;
|
||||
|
||||
uint8_t status = MIDI_CONTROL_CHANGE | (channel - 1);
|
||||
send_midi_message(status, controller, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Envoie un message Pitch Bend
|
||||
*/
|
||||
void hid_midi_pitch_bend(uint8_t channel, uint16_t value) {
|
||||
if (channel == 0 || channel > 16) return;
|
||||
if (value > 16383) return;
|
||||
|
||||
uint8_t status = MIDI_PITCH_BEND | (channel - 1);
|
||||
uint8_t lsb = value & 0x7F;
|
||||
uint8_t msb = (value >> 7) & 0x7F;
|
||||
send_midi_message(status, lsb, msb);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convertit les valeurs CV en Control Changes MIDI
|
||||
*/
|
||||
void hid_midi_send_cv_as_cc(uint16_t cv1_value, uint16_t cv2_value) {
|
||||
// Conversion 12-bit DAC (0-4095) vers 7-bit MIDI (0-127)
|
||||
uint8_t cc1_value = (cv1_value * 127) / 4095;
|
||||
uint8_t cc2_value = (cv2_value * 127) / 4095;
|
||||
|
||||
// Envoyer CV1 comme CC74 (Filter Cutoff) sur le canal CV1
|
||||
hid_midi_control_change(midi_config.cv1_channel, MIDI_CC_FILTER_CUTOFF, cc1_value);
|
||||
|
||||
// Envoyer CV2 comme CC71 (Filter Resonance) sur le canal CV2
|
||||
hid_midi_control_change(midi_config.cv2_channel, 71, cc2_value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convertit les mouvements souris en messages MIDI
|
||||
*/
|
||||
void hid_midi_mouse_to_midi(int8_t offset_x, int8_t offset_y, bool button_pressed) {
|
||||
// Mise à jour cumulative des contrôleurs souris
|
||||
mouse_cc_x += offset_x / 4; // Divisé par 4 pour ralentir la progression
|
||||
mouse_cc_y += offset_y / 4;
|
||||
|
||||
// Limitation dans la plage MIDI
|
||||
if (mouse_cc_x < 0) mouse_cc_x = 0;
|
||||
if (mouse_cc_x > 127) mouse_cc_x = 127;
|
||||
if (mouse_cc_y < 0) mouse_cc_y = 0;
|
||||
if (mouse_cc_y > 127) mouse_cc_y = 127;
|
||||
|
||||
// Envoyer les mouvements comme Control Changes
|
||||
if (abs(offset_x) > 0) {
|
||||
hid_midi_control_change(midi_config.mouse_channel, MIDI_CC_MODULATION, mouse_cc_x);
|
||||
}
|
||||
|
||||
if (abs(offset_y) > 0) {
|
||||
hid_midi_control_change(midi_config.mouse_channel, MIDI_CC_EXPRESSION, mouse_cc_y);
|
||||
}
|
||||
|
||||
// Bouton de la souris comme Sustain Pedal
|
||||
static bool last_button_state = false;
|
||||
if (button_pressed != last_button_state) {
|
||||
uint8_t sustain_value = button_pressed ? 127 : 0;
|
||||
hid_midi_control_change(midi_config.mouse_channel, MIDI_CC_SUSTAIN, sustain_value);
|
||||
last_button_state = button_pressed;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convertit une touche ADB en note MIDI
|
||||
*/
|
||||
void hid_midi_keyboard_to_midi(uint8_t adb_key, bool pressed) {
|
||||
// Vérifier si la touche est dans notre mapping
|
||||
if (ADB_TO_MIDI_MAP[adb_key] == 0) {
|
||||
return; // Touche non mappée
|
||||
}
|
||||
|
||||
uint8_t midi_note = ADB_TO_MIDI_MAP[adb_key];
|
||||
uint8_t velocity = pressed ? MIDI_VELOCITY_DEFAULT : 0;
|
||||
|
||||
if (pressed) {
|
||||
hid_midi_note_on(midi_config.keyboard_channel, midi_note, velocity);
|
||||
} else {
|
||||
hid_midi_note_off(midi_config.keyboard_channel, midi_note, velocity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Configure la transposition globale
|
||||
*/
|
||||
void hid_midi_set_transpose(int8_t transpose_semitones) {
|
||||
if (transpose_semitones >= -12 && transpose_semitones <= 12) {
|
||||
global_transpose = transpose_semitones;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Configure l'arpégiateur
|
||||
*/
|
||||
void hid_midi_set_arpeggiator(bool enabled, uint8_t tempo, uint8_t pattern) {
|
||||
arpeggiator_enabled = enabled;
|
||||
arpeggiator_tempo = tempo;
|
||||
arpeggiator_pattern = pattern;
|
||||
arp_step = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Traite l'arpégiateur (appelé régulièrement)
|
||||
*/
|
||||
void hid_midi_process_arpeggiator(void) {
|
||||
if (!arpeggiator_enabled) return;
|
||||
|
||||
uint32_t interval = (60000 / arpeggiator_tempo) / 4; // Notes en croches
|
||||
|
||||
uint32_t current_time;
|
||||
#ifdef ARDUINO_ARCH_STM32
|
||||
current_time = HAL_GetTick();
|
||||
#endif
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
current_time = millis();
|
||||
#endif
|
||||
|
||||
if (current_time - last_arp_time >= interval) {
|
||||
// Logique d'arpège simple (à développer)
|
||||
// Pour l'instant, juste un exemple avec des notes fixes
|
||||
uint8_t arp_notes[] = {60, 64, 67, 72}; // Accord de Do majeur
|
||||
uint8_t note_count = 4;
|
||||
|
||||
if (arp_step < note_count) {
|
||||
hid_midi_note_on(midi_config.keyboard_channel, arp_notes[arp_step], MIDI_VELOCITY_DEFAULT);
|
||||
|
||||
// Note off de la note précédente
|
||||
if (arp_step > 0) {
|
||||
hid_midi_note_off(midi_config.keyboard_channel, arp_notes[arp_step - 1], 0);
|
||||
}
|
||||
|
||||
arp_step++;
|
||||
} else {
|
||||
arp_step = 0;
|
||||
// Note off de la dernière note
|
||||
hid_midi_note_off(midi_config.keyboard_channel, arp_notes[note_count - 1], 0);
|
||||
}
|
||||
|
||||
last_arp_time = current_time;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Envoie All Notes Off
|
||||
*/
|
||||
void hid_midi_all_notes_off(uint8_t channel) {
|
||||
if (channel == 0) {
|
||||
// Tous les canaux
|
||||
for (uint8_t ch = 1; ch <= 16; ch++) {
|
||||
hid_midi_control_change(ch, 123, 0); // All Notes Off
|
||||
}
|
||||
} else if (channel <= 16) {
|
||||
hid_midi_control_change(channel, 123, 0); // All Notes Off
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Obtient l'état des notes MIDI
|
||||
*/
|
||||
const midi_notes_state_t* hid_midi_get_notes_state(void) {
|
||||
return ¬es_state;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Active/désactive le mode debug
|
||||
*/
|
||||
void hid_midi_set_debug(bool enabled) {
|
||||
debug_enabled = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Envoie un message MIDI raw
|
||||
*/
|
||||
void hid_midi_send_raw(uint8_t status, uint8_t data1, uint8_t data2) {
|
||||
send_midi_message(status, data1, data2);
|
||||
}
|
||||
+165
@@ -0,0 +1,165 @@
|
||||
/**
|
||||
* @file hid_midi.h
|
||||
* @brief Interface HID MIDI pour clavier et souris Apple ADB
|
||||
* @part of Apple-ADB-Ressurector
|
||||
*
|
||||
* @date 2025
|
||||
* @author Clément SAILLANT
|
||||
* @license GNU GPL v3
|
||||
*/
|
||||
|
||||
#ifndef HID_MIDI_H
|
||||
#define HID_MIDI_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
// Configuration MIDI
|
||||
#define MIDI_CHANNEL_DEFAULT 1 // Canal MIDI par défaut (1-16)
|
||||
#define MIDI_NOTE_ON 0x90 // Commande Note On
|
||||
#define MIDI_NOTE_OFF 0x80 // Commande Note Off
|
||||
#define MIDI_CONTROL_CHANGE 0xB0 // Commande Control Change
|
||||
#define MIDI_PITCH_BEND 0xE0 // Commande Pitch Bend
|
||||
#define MIDI_VELOCITY_DEFAULT 64 // Vélocité par défaut
|
||||
#define MIDI_VELOCITY_MAX 127 // Vélocité maximale
|
||||
|
||||
// Assignations Control Change
|
||||
#define MIDI_CC_MODULATION 1 // CC1 : Roue de modulation
|
||||
#define MIDI_CC_BREATH_CONTROL 2 // CC2 : Contrôleur de souffle
|
||||
#define MIDI_CC_VOLUME 7 // CC7 : Volume
|
||||
#define MIDI_CC_PAN 10 // CC10 : Pan
|
||||
#define MIDI_CC_EXPRESSION 11 // CC11 : Expression
|
||||
#define MIDI_CC_SUSTAIN 64 // CC64 : Pédale de sustain
|
||||
#define MIDI_CC_PORTAMENTO_TIME 5 // CC5 : Temps de portamento
|
||||
#define MIDI_CC_FILTER_CUTOFF 74 // CC74 : Cutoff du filtre
|
||||
|
||||
// Configuration des canaux MIDI
|
||||
typedef struct {
|
||||
uint8_t keyboard_channel; // Canal pour les notes du clavier
|
||||
uint8_t mouse_channel; // Canal pour les contrôles souris
|
||||
uint8_t cv1_channel; // Canal pour les données CV1
|
||||
uint8_t cv2_channel; // Canal pour les données CV2
|
||||
} midi_channels_config_t;
|
||||
|
||||
// État des notes MIDI
|
||||
typedef struct {
|
||||
uint8_t active_notes[16]; // Notes actives par canal
|
||||
uint8_t note_count[16]; // Nombre de notes par canal
|
||||
uint32_t last_note_time[16]; // Dernière activité par canal
|
||||
} midi_notes_state_t;
|
||||
|
||||
/**
|
||||
* @brief Initialise l'interface HID MIDI
|
||||
*/
|
||||
void hid_midi_init(void);
|
||||
|
||||
/**
|
||||
* @brief Ferme l'interface HID MIDI
|
||||
*/
|
||||
void hid_midi_close(void);
|
||||
|
||||
/**
|
||||
* @brief Configure les canaux MIDI
|
||||
* @param config Configuration des canaux MIDI
|
||||
*/
|
||||
void hid_midi_configure_channels(const midi_channels_config_t* config);
|
||||
|
||||
/**
|
||||
* @brief Envoie un message Note On
|
||||
* @param channel Canal MIDI (1-16)
|
||||
* @param note Note MIDI (0-127)
|
||||
* @param velocity Vélocité (0-127)
|
||||
*/
|
||||
void hid_midi_note_on(uint8_t channel, uint8_t note, uint8_t velocity);
|
||||
|
||||
/**
|
||||
* @brief Envoie un message Note Off
|
||||
* @param channel Canal MIDI (1-16)
|
||||
* @param note Note MIDI (0-127)
|
||||
* @param velocity Vélocité de relâchement (0-127)
|
||||
*/
|
||||
void hid_midi_note_off(uint8_t channel, uint8_t note, uint8_t velocity);
|
||||
|
||||
/**
|
||||
* @brief Envoie un message Control Change
|
||||
* @param channel Canal MIDI (1-16)
|
||||
* @param controller Numéro du contrôleur (0-127)
|
||||
* @param value Valeur du contrôleur (0-127)
|
||||
*/
|
||||
void hid_midi_control_change(uint8_t channel, uint8_t controller, uint8_t value);
|
||||
|
||||
/**
|
||||
* @brief Envoie un message Pitch Bend
|
||||
* @param channel Canal MIDI (1-16)
|
||||
* @param value Valeur de pitch bend (0-16383, 8192 = centre)
|
||||
*/
|
||||
void hid_midi_pitch_bend(uint8_t channel, uint16_t value);
|
||||
|
||||
/**
|
||||
* @brief Envoie les données CV en tant que Control Changes MIDI
|
||||
* @param cv1_value Valeur CV1 (0-4095)
|
||||
* @param cv2_value Valeur CV2 (0-4095)
|
||||
*/
|
||||
void hid_midi_send_cv_as_cc(uint16_t cv1_value, uint16_t cv2_value);
|
||||
|
||||
/**
|
||||
* @brief Convertit les mouvements souris en messages MIDI
|
||||
* @param offset_x Déplacement horizontal (-127 à +127)
|
||||
* @param offset_y Déplacement vertical (-127 à +127)
|
||||
* @param button_pressed État du bouton de la souris
|
||||
*/
|
||||
void hid_midi_mouse_to_midi(int8_t offset_x, int8_t offset_y, bool button_pressed);
|
||||
|
||||
/**
|
||||
* @brief Convertit une touche ADB en note MIDI
|
||||
* @param adb_key Code de la touche ADB
|
||||
* @param pressed État de la touche (true = pressée, false = relâchée)
|
||||
*/
|
||||
void hid_midi_keyboard_to_midi(uint8_t adb_key, bool pressed);
|
||||
|
||||
/**
|
||||
* @brief Active/désactive le mode transpose
|
||||
* @param transpose_semitones Transposition en demi-tons (-12 à +12)
|
||||
*/
|
||||
void hid_midi_set_transpose(int8_t transpose_semitones);
|
||||
|
||||
/**
|
||||
* @brief Active/désactive l'arpégiateur MIDI
|
||||
* @param enabled État de l'arpégiateur
|
||||
* @param tempo Tempo en BPM (60-200)
|
||||
* @param pattern Type d'arpège (0=montant, 1=descendant, 2=ping-pong)
|
||||
*/
|
||||
void hid_midi_set_arpeggiator(bool enabled, uint8_t tempo, uint8_t pattern);
|
||||
|
||||
/**
|
||||
* @brief Traite l'arpégiateur (à appeler régulièrement)
|
||||
*/
|
||||
void hid_midi_process_arpeggiator(void);
|
||||
|
||||
/**
|
||||
* @brief Envoie un message MIDI All Notes Off
|
||||
* @param channel Canal MIDI (1-16, 0 pour tous les canaux)
|
||||
*/
|
||||
void hid_midi_all_notes_off(uint8_t channel);
|
||||
|
||||
/**
|
||||
* @brief Obtient l'état actuel des notes MIDI
|
||||
* @return Pointeur vers l'état des notes (lecture seule)
|
||||
*/
|
||||
const midi_notes_state_t* hid_midi_get_notes_state(void);
|
||||
|
||||
/**
|
||||
* @brief Active/désactive le mode débug MIDI
|
||||
* @param enabled État du mode debug
|
||||
*/
|
||||
void hid_midi_set_debug(bool enabled);
|
||||
|
||||
/**
|
||||
* @brief Envoie un message MIDI raw (3 octets)
|
||||
* @param status Byte de statut (commande + canal)
|
||||
* @param data1 Premier byte de données
|
||||
* @param data2 Deuxième byte de données
|
||||
*/
|
||||
void hid_midi_send_raw(uint8_t status, uint8_t data1, uint8_t data2);
|
||||
|
||||
#endif // HID_MIDI_H
|
||||
+83
-1
@@ -15,6 +15,7 @@
|
||||
|
||||
#ifdef ARDUINO_ARCH_STM32
|
||||
#include "usbd_hid_composite_if.h"
|
||||
#include "dac_cv_manager.h"
|
||||
#endif
|
||||
|
||||
#include <Arduino.h>
|
||||
@@ -26,6 +27,51 @@ extern BLECharacteristic* input_mouse; // Déclaration externe pour input_mouse
|
||||
extern bool isBleConnected; // Déclaration externe pour isBleConnected
|
||||
#endif
|
||||
|
||||
#ifdef ARDUINO_ARCH_STM32
|
||||
/**
|
||||
* @brief Initialise le DAC pour les sorties CV (supprimé - maintenant dans dac_cv_manager)
|
||||
*/
|
||||
#endif
|
||||
|
||||
#ifdef ARDUINO_ARCH_STM32
|
||||
/**
|
||||
* @brief Écrit une valeur sur le DAC (supprimé - maintenant dans dac_cv_manager)
|
||||
*/
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Remet les valeurs CV au centre (redirect vers dac_cv_manager).
|
||||
*/
|
||||
void hid_mouse_reset_cv_values(void) {
|
||||
#ifdef ARDUINO_ARCH_STM32
|
||||
dac_cv_reset_values();
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Obtient la valeur CV1 actuelle (redirect vers dac_cv_manager).
|
||||
* @return Valeur DAC actuelle pour CV1 (0-4095)
|
||||
*/
|
||||
uint16_t hid_mouse_get_cv1_value(void) {
|
||||
#ifdef ARDUINO_ARCH_STM32
|
||||
return dac_cv_get_cv1_value();
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Obtient la valeur CV2 actuelle (redirect vers dac_cv_manager).
|
||||
* @return Valeur DAC actuelle pour CV2 (0-4095)
|
||||
*/
|
||||
uint16_t hid_mouse_get_cv2_value(void) {
|
||||
#ifdef ARDUINO_ARCH_STM32
|
||||
return dac_cv_get_cv2_value();
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialise la souris HID.
|
||||
*/
|
||||
@@ -33,7 +79,11 @@ void hid_mouse_init() {
|
||||
#ifdef ARDUINO_ARCH_STM32
|
||||
Serial.println("Initialisation de la souris HID USB...");
|
||||
HID_Composite_Init(HID_MOUSE);
|
||||
Serial.println("Souris HID USB initialisée.");
|
||||
|
||||
// Initialisation du gestionnaire DAC/CV centralisé
|
||||
dac_cv_manager_init();
|
||||
|
||||
Serial.println("Souris HID USB et gestionnaire DAC/CV initialisés.");
|
||||
#endif
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
@@ -49,6 +99,10 @@ void hid_mouse_init() {
|
||||
void hid_mouse_close() {
|
||||
#ifdef ARDUINO_ARCH_STM32
|
||||
Serial.println("Fermeture de la souris HID USB...");
|
||||
|
||||
// Fermeture du gestionnaire DAC/CV
|
||||
dac_cv_manager_deinit();
|
||||
|
||||
HID_Composite_DeInit(HID_MOUSE);
|
||||
Serial.println("Souris HID USB fermée.");
|
||||
#endif
|
||||
@@ -74,12 +128,40 @@ void hid_mouse_send_report(bool button, int8_t offset_x, int8_t offset_y) {
|
||||
m[2] = offset_y; // Déplacement vertical
|
||||
m[3] = 0; // Réservé
|
||||
|
||||
// Fonction CV / GATE souris avec gestionnaire DAC centralisé
|
||||
#ifdef ARDUINO_ARCH_STM32
|
||||
// Mise à jour des valeurs CV via le gestionnaire centralisé
|
||||
dac_cv_update_cumulative(offset_x, offset_y);
|
||||
|
||||
// Contrôle du signal GATE via le gestionnaire centralisé
|
||||
dac_cv_set_gate(button);
|
||||
|
||||
// Mise à jour de la modulation FM basée sur les mouvements souris
|
||||
dac_cv_update_fm_modulation(offset_x, offset_y);
|
||||
#endif
|
||||
|
||||
Serial.print("Envoi du rapport HID souris - Bouton: ");
|
||||
Serial.print(button);
|
||||
Serial.print(", X: ");
|
||||
Serial.print(offset_x);
|
||||
Serial.print(", Y: ");
|
||||
Serial.println(offset_y);
|
||||
|
||||
#ifdef ARDUINO_ARCH_STM32
|
||||
// Obtention des valeurs actuelles pour debug
|
||||
uint16_t cv1_val = dac_cv_get_cv1_value();
|
||||
uint16_t cv2_val = dac_cv_get_cv2_value();
|
||||
|
||||
Serial.print("CV1 cumulé (X): ");
|
||||
Serial.print(cv1_val);
|
||||
Serial.print(" (");
|
||||
Serial.print((float)(cv1_val * 3.3 / 4095.0), 2);
|
||||
Serial.print("V), CV2 cumulé (Y): ");
|
||||
Serial.print(cv2_val);
|
||||
Serial.print(" (");
|
||||
Serial.print((float)(cv2_val * 3.3 / 4095.0), 2);
|
||||
Serial.println("V)");
|
||||
#endif
|
||||
|
||||
#ifdef ARDUINO_ARCH_STM32
|
||||
HID_Composite_mouse_sendReport(m, 4);
|
||||
|
||||
+22
-2
@@ -14,10 +14,11 @@
|
||||
#ifndef HID_MOUSE_h
|
||||
#define HID_MOUSE_h
|
||||
|
||||
#include <cstdint>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/**
|
||||
* @brief Initialise la souris HID.
|
||||
* @brief Initialise la souris HID (DAC géré par dac_cv_manager).
|
||||
*/
|
||||
void hid_mouse_init();
|
||||
|
||||
@@ -35,4 +36,23 @@ void hid_mouse_close();
|
||||
*/
|
||||
void hid_mouse_send_report(bool button, int8_t offset_x, int8_t offset_y);
|
||||
|
||||
#ifdef ARDUINO_ARCH_STM32
|
||||
/**
|
||||
* @brief Remet les valeurs CV au centre (compatibilité - redirect vers dac_cv_manager).
|
||||
*/
|
||||
void hid_mouse_reset_cv_values(void);
|
||||
|
||||
/**
|
||||
* @brief Obtient la valeur CV1 actuelle (compatibilité - redirect vers dac_cv_manager).
|
||||
* @return Valeur DAC actuelle pour CV1 (0-4095)
|
||||
*/
|
||||
uint16_t hid_mouse_get_cv1_value(void);
|
||||
|
||||
/**
|
||||
* @brief Obtient la valeur CV2 actuelle (compatibilité - redirect vers dac_cv_manager).
|
||||
* @return Valeur DAC actuelle pour CV2 (0-4095)
|
||||
*/
|
||||
uint16_t hid_mouse_get_cv2_value(void);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
+470
-2
@@ -9,6 +9,12 @@
|
||||
* @date 2025
|
||||
* Dépôt actuel : https://github.com/electron-rare/Apple-ADB-Ressurector
|
||||
* @license GNU GPL v3
|
||||
*
|
||||
* Fonctionnalités CV/GATE pour STM32F3 :
|
||||
* - Mouvement souris X/Y : Contrôle des valeurs CV1/CV2 (accumulation)
|
||||
* - Bouton souris : Contrôle du signal GATE (ON/OFF)
|
||||
* - Reset automatique des CV après 2 secondes d'inactivité souris
|
||||
* - Modulation continue : portamento, vibrato, et modulation FM
|
||||
*/
|
||||
|
||||
#ifndef UNIT_TEST
|
||||
@@ -17,7 +23,12 @@
|
||||
#include "hid_mouse.h"
|
||||
#include <ADB.h>
|
||||
|
||||
#define POLL_DELAY 5
|
||||
#ifdef ARDUINO_ARCH_STM32
|
||||
#include "dac_cv_manager.h" // Gestionnaire centralisé DAC/CV/GATE
|
||||
#include "cv_modulation.h" // Fonctions de modulation CV avancée
|
||||
#endif
|
||||
|
||||
#define POLL_DELAY 1
|
||||
// Définition de la pin ADB selon la plateforme
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#define ADB_PIN 2
|
||||
@@ -55,6 +66,12 @@ ADBDevices adbDevices(adb); /**< Gestionnaire des périphériques ADB. */
|
||||
DeviceState deviceState; /**< État des périphériques. */
|
||||
bool caps_lock_pressed = false; /**< État de la touche Caps Lock. */
|
||||
|
||||
#ifdef ARDUINO_ARCH_STM32
|
||||
// Variables pour le contrôle CV par la souris
|
||||
unsigned long last_mouse_activity = 0; /**< Timestamp de la dernière activité souris. */
|
||||
const unsigned long CV_RESET_TIMEOUT = 20000; /**< Timeout pour réinitialiser les CV (ms). */
|
||||
#endif
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#include <BLEDevice.h>
|
||||
#include <BLEHIDDevice.h>
|
||||
@@ -271,7 +288,7 @@ void setup() {
|
||||
pinMode(LED_PIN, OUTPUT); // Configuration de la pin LED
|
||||
digitalWrite(LED_PIN, LOW); // État initial de la LED
|
||||
|
||||
Serial.begin(115200);
|
||||
Serial.begin(9600);
|
||||
Serial.println("Initialisation du programme...");
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
@@ -282,6 +299,14 @@ void setup() {
|
||||
hid_mouse_init();
|
||||
Serial.println("HID initialisé.");
|
||||
|
||||
#ifdef ARDUINO_ARCH_STM32
|
||||
dac_cv_manager_init();
|
||||
Serial.println("Système DAC/CV initialisé.");
|
||||
|
||||
cv_modulation_init();
|
||||
Serial.println("Système de modulation CV initialisé.");
|
||||
#endif
|
||||
|
||||
adb.init(ADB_PIN, true);
|
||||
Serial.println("Bus ADB initialisé.");
|
||||
|
||||
@@ -301,6 +326,13 @@ void setup() {
|
||||
adbDevices.keyboardWriteLEDs(deviceState.led_num, deviceState.led_caps,
|
||||
deviceState.led_scroll);
|
||||
Serial.println("LEDs initialisées.");
|
||||
|
||||
#ifdef ARDUINO_ARCH_STM32
|
||||
// Message d'accueil pour les commandes debug
|
||||
Serial.println("\n=== DEBUG CV/FM DISPONIBLE ===");
|
||||
Serial.println("Tapez 'help' dans le moniteur série pour les commandes debug");
|
||||
Serial.println("==============================\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -405,6 +437,33 @@ void handleMouse() {
|
||||
Serial.print(", Y: ");
|
||||
Serial.println(mouse_y);
|
||||
|
||||
// Contrôle CV avec la souris (STM32 uniquement)
|
||||
#ifdef ARDUINO_ARCH_STM32
|
||||
// Enregistrer l'activité souris
|
||||
if (mouse_x != 0 || mouse_y != 0) {
|
||||
last_mouse_activity = millis();
|
||||
|
||||
// Mise à jour des valeurs CV cumulatives
|
||||
dac_cv_update_cumulative(mouse_x, mouse_y);
|
||||
|
||||
// Mise à jour de la modulation FM avec debug intégré
|
||||
cv_modulation_update_fm(mouse_x, mouse_y);
|
||||
|
||||
Serial.print("Valeurs CV mises à jour - CV1: ");
|
||||
Serial.print(dac_cv_get_cv1_value());
|
||||
Serial.print(", CV2: ");
|
||||
Serial.println(dac_cv_get_cv2_value());
|
||||
}
|
||||
|
||||
// Contrôle du signal GATE avec le bouton de la souris
|
||||
bool button_pressed = (mouse_data.data.button == 0); // ADB mouse: 0 = pressed, 1 = released
|
||||
dac_cv_set_gate(button_pressed);
|
||||
|
||||
if (button_pressed) {
|
||||
Serial.println("GATE: ON");
|
||||
}
|
||||
#endif
|
||||
|
||||
// Envoyer le rapport HID pour la souris
|
||||
hid_mouse_send_report(mouse_data.data.button ? 0 : 1, mouse_x, mouse_y);
|
||||
|
||||
@@ -423,10 +482,407 @@ void handleMouse() {
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef ARDUINO_ARCH_STM32
|
||||
/**
|
||||
* @brief Affiche l'état actuel du système CV (pour débogage)
|
||||
*/
|
||||
void debugCVState() {
|
||||
uint16_t cv1, cv2;
|
||||
bool gate_state;
|
||||
dac_cv_get_state(&cv1, &cv2, &gate_state);
|
||||
|
||||
Serial.print("État CV - CV1: ");
|
||||
Serial.print(cv1);
|
||||
Serial.print(" (");
|
||||
Serial.print((float)cv1 * 3.3f / 4095.0f, 2);
|
||||
Serial.print("V), CV2: ");
|
||||
Serial.print(cv2);
|
||||
Serial.print(" (");
|
||||
Serial.print((float)cv2 * 3.3f / 4095.0f, 2);
|
||||
Serial.print("V), GATE: ");
|
||||
Serial.println(gate_state ? "HIGH" : "LOW");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Active le debug détaillé de la modulation FM (pour diagnostique)
|
||||
*/
|
||||
void enableFMDebug() {
|
||||
cv_modulation_set_fm_debug(true);
|
||||
Serial.println("Debug FM activé. Utilisez la souris pour voir les détails.");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Désactive le debug détaillé de la modulation FM
|
||||
*/
|
||||
void disableFMDebug() {
|
||||
cv_modulation_set_fm_debug(false);
|
||||
Serial.println("Debug FM désactivé.");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Affiche les statistiques de performance du debug FM
|
||||
*/
|
||||
void showFMPerformanceStats() {
|
||||
cv_modulation_debug_performance_stats();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Affiche un rapport complet sur la modulation FM
|
||||
*/
|
||||
void showFMReport() {
|
||||
cv_modulation_debug_fm_report();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Lance un test automatique de la modulation FM
|
||||
*/
|
||||
void testFMModulation(uint32_t duration_ms = 5000) {
|
||||
Serial.print("Lancement du test FM pour ");
|
||||
Serial.print(duration_ms);
|
||||
Serial.println("ms...");
|
||||
cv_modulation_test_fm(duration_ms);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Test automatique de toutes les fonctionnalités CV
|
||||
*/
|
||||
void runFullCVTest() {
|
||||
Serial.println("\n=== TEST COMPLET DU SYSTÈME CV ===");
|
||||
|
||||
// Test 1: Écriture directe des CV
|
||||
Serial.println("Test 1: Écriture directe des CV...");
|
||||
dac_cv_write_direct(1, 1024); // CV1 à ~0.8V
|
||||
dac_cv_write_direct(2, 2048); // CV2 à ~1.65V
|
||||
delay(1000);
|
||||
debugCVState();
|
||||
|
||||
// Test 2: Test du GATE
|
||||
Serial.println("Test 2: Signal GATE...");
|
||||
dac_cv_set_gate(true);
|
||||
delay(500);
|
||||
Serial.println("GATE activé");
|
||||
dac_cv_set_gate(false);
|
||||
delay(500);
|
||||
Serial.println("GATE désactivé");
|
||||
|
||||
// Test 3: Portamento
|
||||
Serial.println("Test 3: Portamento CV1...");
|
||||
cv_modulation_start_portamento(1, 3072); // Transition vers ~2.5V
|
||||
delay(2000);
|
||||
|
||||
// Test 4: Vibrato
|
||||
Serial.println("Test 4: Vibrato sur CV2...");
|
||||
cv_modulation_configure_vibrato(true, 3.0, 150);
|
||||
delay(3000);
|
||||
cv_modulation_set_vibrato(false);
|
||||
|
||||
// Test 5: Réinitialisation
|
||||
Serial.println("Test 5: Réinitialisation...");
|
||||
dac_cv_reset_values();
|
||||
delay(500);
|
||||
debugCVState();
|
||||
|
||||
Serial.println("Test complet terminé !\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calibrage et test des CV avec paliers
|
||||
*/
|
||||
void calibrateCVOutputs() {
|
||||
Serial.println("\n=== CALIBRAGE DES SORTIES CV ===");
|
||||
|
||||
uint16_t test_values[] = {0, 512, 1024, 1536, 2048, 2560, 3072, 3584, 4095};
|
||||
int num_values = sizeof(test_values) / sizeof(test_values[0]);
|
||||
|
||||
Serial.println("Calibrage CV1 (paliers de tension):");
|
||||
for (int i = 0; i < num_values; i++) {
|
||||
dac_cv_write_direct(1, test_values[i]);
|
||||
Serial.print("CV1 = ");
|
||||
Serial.print(test_values[i]);
|
||||
Serial.print(" -> ");
|
||||
Serial.print((test_values[i] * 3.3 / 4096.0), 3);
|
||||
Serial.println("V");
|
||||
delay(800);
|
||||
}
|
||||
|
||||
delay(1000);
|
||||
|
||||
Serial.println("Calibrage CV2 (paliers de tension):");
|
||||
for (int i = 0; i < num_values; i++) {
|
||||
dac_cv_write_direct(2, test_values[i]);
|
||||
Serial.print("CV2 = ");
|
||||
Serial.print(test_values[i]);
|
||||
Serial.print(" -> ");
|
||||
Serial.print((test_values[i] * 3.3 / 4096.0), 3);
|
||||
Serial.println("V");
|
||||
delay(800);
|
||||
}
|
||||
|
||||
// Retour à des valeurs neutres
|
||||
dac_cv_write_direct(1, 2048); // 1.65V
|
||||
dac_cv_write_direct(2, 2048); // 1.65V
|
||||
|
||||
Serial.println("Calibrage terminé - CV1 et CV2 à 1.65V\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Active le vibrato sur les CV
|
||||
*/
|
||||
void enableVibrato() {
|
||||
cv_modulation_set_vibrato(true);
|
||||
Serial.println("Vibrato activé avec paramètres par défaut");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Désactive le vibrato
|
||||
*/
|
||||
void disableVibrato() {
|
||||
cv_modulation_set_vibrato(false);
|
||||
Serial.println("Vibrato désactivé");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Configure les paramètres du vibrato
|
||||
*/
|
||||
void configureVibrato(float frequency, uint16_t depth) {
|
||||
cv_modulation_configure_vibrato(true, frequency, depth);
|
||||
Serial.print("Vibrato configuré - Fréquence: ");
|
||||
Serial.print(frequency);
|
||||
Serial.print("Hz, Profondeur: ");
|
||||
Serial.println(depth);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Configure la vitesse du portamento
|
||||
*/
|
||||
void configurePortamentoSpeed(float speed) {
|
||||
cv_modulation_configure_portamento_speed(speed);
|
||||
Serial.print("Vitesse portamento configurée: ");
|
||||
Serial.println(speed);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Lance un test de portamento
|
||||
*/
|
||||
void testPortamento() {
|
||||
Serial.println("Test portamento CV1: 1024 -> 3072");
|
||||
cv_modulation_start_portamento(1, 3072);
|
||||
delay(2000);
|
||||
Serial.println("Test portamento CV2: 2048 -> 512");
|
||||
cv_modulation_start_portamento(2, 512);
|
||||
delay(2000);
|
||||
Serial.println("Test portamento terminé");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Réinitialise tous les paramètres CV
|
||||
*/
|
||||
void resetAllCV() {
|
||||
dac_cv_reset_values();
|
||||
cv_modulation_set_vibrato(false);
|
||||
cv_modulation_reset_cv_values(); // Remet les CV modifiées par souris au centre
|
||||
Serial.println("Tous les paramètres CV réinitialisés (y compris modifications souris)");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Affiche l'état détaillé de tous les systèmes
|
||||
*/
|
||||
void showFullSystemState() {
|
||||
uint16_t cv1, cv2;
|
||||
bool gate_state;
|
||||
|
||||
Serial.println("\n=== ÉTAT COMPLET DU SYSTÈME ===");
|
||||
|
||||
// État des CV et GATE
|
||||
dac_cv_get_state(&cv1, &cv2, &gate_state);
|
||||
Serial.print("CV1: ");
|
||||
Serial.print(cv1);
|
||||
Serial.print(" | CV2: ");
|
||||
Serial.print(cv2);
|
||||
Serial.print(" | GATE: ");
|
||||
Serial.println(gate_state ? "ON" : "OFF");
|
||||
|
||||
// Statistiques de performance
|
||||
cv_modulation_debug_performance_stats();
|
||||
|
||||
// Rapport FM complet
|
||||
cv_modulation_debug_fm_report();
|
||||
|
||||
Serial.println("==============================\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Analyse une commande avec paramètres
|
||||
*/
|
||||
bool parseCommandWithParams(String command, String& base_cmd, float& param1, uint16_t& param2) {
|
||||
int space_index = command.indexOf(' ');
|
||||
if (space_index == -1) {
|
||||
base_cmd = command;
|
||||
return false;
|
||||
}
|
||||
|
||||
base_cmd = command.substring(0, space_index);
|
||||
String params = command.substring(space_index + 1);
|
||||
|
||||
int comma_index = params.indexOf(',');
|
||||
if (comma_index != -1) {
|
||||
param1 = params.substring(0, comma_index).toFloat();
|
||||
param2 = params.substring(comma_index + 1).toInt();
|
||||
return true;
|
||||
} else {
|
||||
param1 = params.toFloat();
|
||||
param2 = 0;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gère les commandes série pour le debug CV/FM
|
||||
*/
|
||||
void handleSerialCommands() {
|
||||
if (Serial.available()) {
|
||||
String command = Serial.readStringUntil('\n');
|
||||
command.trim();
|
||||
command.toLowerCase();
|
||||
|
||||
// Analyse des paramètres pour les commandes avancées
|
||||
String base_cmd;
|
||||
float param1 = 0;
|
||||
uint16_t param2 = 0;
|
||||
bool has_params = parseCommandWithParams(command, base_cmd, param1, param2);
|
||||
|
||||
// === COMMANDES FM ===
|
||||
if (command == "fm debug on") {
|
||||
enableFMDebug();
|
||||
}
|
||||
else if (command == "fm debug off") {
|
||||
disableFMDebug();
|
||||
}
|
||||
else if (command == "fm report") {
|
||||
showFMReport();
|
||||
}
|
||||
else if (command == "fm stats") {
|
||||
showFMPerformanceStats();
|
||||
}
|
||||
else if (command == "fm test") {
|
||||
testFMModulation(3000);
|
||||
}
|
||||
else if (command == "fm test long") {
|
||||
testFMModulation(10000);
|
||||
}
|
||||
else if (base_cmd == "fm test" && has_params) {
|
||||
testFMModulation((uint32_t)(param1 * 1000)); // Paramètre en secondes
|
||||
}
|
||||
|
||||
// === COMMANDES VIBRATO ===
|
||||
else if (command == "vibrato on") {
|
||||
enableVibrato();
|
||||
}
|
||||
else if (command == "vibrato off") {
|
||||
disableVibrato();
|
||||
}
|
||||
else if (base_cmd == "vibrato config" && has_params) {
|
||||
if (param2 > 0) {
|
||||
configureVibrato(param1, param2);
|
||||
} else {
|
||||
cv_modulation_set_vibrato_params(param1, 100); // Profondeur par défaut
|
||||
Serial.print("Vibrato fréquence mise à jour: ");
|
||||
Serial.print(param1);
|
||||
Serial.println("Hz");
|
||||
}
|
||||
}
|
||||
|
||||
// === COMMANDES PORTAMENTO ===
|
||||
else if (base_cmd == "portamento speed" && has_params) {
|
||||
configurePortamentoSpeed(param1);
|
||||
}
|
||||
else if (command == "portamento test") {
|
||||
testPortamento();
|
||||
}
|
||||
|
||||
// === COMMANDES CV ===
|
||||
else if (command == "cv state") {
|
||||
debugCVState();
|
||||
}
|
||||
else if (command == "cv reset") {
|
||||
resetAllCV();
|
||||
}
|
||||
else if (base_cmd == "cv direct" && has_params) {
|
||||
// Format: "cv direct 1,2048" pour canal 1, valeur 2048
|
||||
dac_cv_write_direct((uint32_t)param1, (uint16_t)param2);
|
||||
Serial.print("CV");
|
||||
Serial.print((int)param1);
|
||||
Serial.print(" mise à ");
|
||||
Serial.println(param2);
|
||||
}
|
||||
|
||||
// === COMMANDES SYSTÈME ===
|
||||
else if (command == "system state") {
|
||||
showFullSystemState();
|
||||
}
|
||||
else if (command == "system reset") {
|
||||
resetAllCV();
|
||||
cv_modulation_set_fm_debug(false);
|
||||
Serial.println("Système CV complet réinitialisé");
|
||||
}
|
||||
else if (command == "test full") {
|
||||
runFullCVTest();
|
||||
}
|
||||
else if (command == "calibrate") {
|
||||
calibrateCVOutputs();
|
||||
}
|
||||
|
||||
// === AIDE ===
|
||||
else if (command == "help" || command == "?") {
|
||||
Serial.println("\n=== COMMANDES DEBUG COMPLÈTES ===");
|
||||
Serial.println("--- MODULATION FM ---");
|
||||
Serial.println("fm debug on/off - Active/désactive le debug FM optimisé");
|
||||
Serial.println("fm report - Rapport FM complet");
|
||||
Serial.println("fm stats - Statistiques performance");
|
||||
Serial.println("fm test [sec] - Test FM (durée optionnelle en sec)");
|
||||
Serial.println("fm test long - Test FM long (10s)");
|
||||
Serial.println("");
|
||||
Serial.println("--- VIBRATO ---");
|
||||
Serial.println("vibrato on/off - Active/désactive le vibrato");
|
||||
Serial.println("vibrato config f,d - Configure fréquence(Hz) et profondeur");
|
||||
Serial.println(" Ex: vibrato config 5.0,200");
|
||||
Serial.println("");
|
||||
Serial.println("--- PORTAMENTO ---");
|
||||
Serial.println("portamento speed s - Configure vitesse (0.1-10.0)");
|
||||
Serial.println("portamento test - Test automatique portamento");
|
||||
Serial.println("");
|
||||
Serial.println("--- CONTRÔLE CV ---");
|
||||
Serial.println("cv state - État actuel des CV et GATE");
|
||||
Serial.println("cv reset - Réinitialise toutes les CV");
|
||||
Serial.println("cv direct c,v - Écrit valeur directe sur canal");
|
||||
Serial.println(" Ex: cv direct 1,2048");
|
||||
Serial.println("");
|
||||
Serial.println("--- SYSTÈME ---");
|
||||
Serial.println("system state - État complet du système");
|
||||
Serial.println("system reset - Réinitialisation complète");
|
||||
Serial.println("test full - Test complet de toutes les fonctionnalités");
|
||||
Serial.println("calibrate - Calibrage des sorties CV (paliers)");
|
||||
Serial.println("help ou ? - Affiche cette aide");
|
||||
Serial.println("=================================\n");
|
||||
}
|
||||
else if (command.length() > 0) {
|
||||
Serial.print("Commande inconnue: '");
|
||||
Serial.print(command);
|
||||
Serial.println("'. Tapez 'help' pour l'aide complète.");
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Boucle principale du programme.
|
||||
*/
|
||||
void loop() {
|
||||
// Gestion des commandes série pour le debug (STM32 uniquement)
|
||||
#ifdef ARDUINO_ARCH_STM32
|
||||
handleSerialCommands();
|
||||
#endif
|
||||
|
||||
if (deviceState.keyboard_present) {
|
||||
// Serial.println("Gestion du clavier...");
|
||||
handleKeyboard();
|
||||
@@ -438,6 +894,18 @@ void loop() {
|
||||
handleMouse();
|
||||
delay(POLL_DELAY);
|
||||
}
|
||||
|
||||
// Traitement continu des modulations CV (portamento, vibrato, FM)
|
||||
#ifdef ARDUINO_ARCH_STM32
|
||||
dac_cv_process_modulations();
|
||||
|
||||
// Vérifier le timeout et réinitialiser les CV si nécessaire
|
||||
if (last_mouse_activity > 0 && (millis() - last_mouse_activity) > CV_RESET_TIMEOUT) {
|
||||
dac_cv_reset_values();
|
||||
last_mouse_activity = 0; // Éviter de répéter la réinitialisation
|
||||
Serial.println("CV réinitialisées (timeout inactivité souris).");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
Executable
+22
@@ -0,0 +1,22 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Script de test de compilation basique pour vérifier la syntaxe
|
||||
|
||||
echo "=== Test de compilation basique ==="
|
||||
|
||||
# Définir les chemins
|
||||
PROJECT_DIR="/Users/electron_rare/Documents/Lelectron_rare/Github_Code/Apple-ADB-Ressurector"
|
||||
SRC_DIR="$PROJECT_DIR/src"
|
||||
|
||||
# Vérification basique de la syntaxe C++ avec gcc (sans compilation complète)
|
||||
echo "Test syntaxe cv_modulation.cpp..."
|
||||
|
||||
g++ -fsyntax-only \
|
||||
-DARDUINO_ARCH_STM32 \
|
||||
-DM_PI=3.14159265359 \
|
||||
-DM_PI_2=1.57079632679 \
|
||||
-I"$SRC_DIR" \
|
||||
-std=c++11 \
|
||||
"$SRC_DIR/cv_modulation.cpp" 2>&1 | head -10
|
||||
|
||||
echo "=== Fin du test ==="
|
||||
Reference in New Issue
Block a user