CV GATE FM

This commit is contained in:
Clément SAILLANT
2025-08-06 23:25:38 +02:00
parent 9a286477f9
commit f1604cc7cc
8 changed files with 1562 additions and 344 deletions
+1 -3
View File
@@ -42,15 +42,13 @@ build_flags =
-D USB_PRODUCT="Apple Desktop Bus Device"
-D STM32F3link
-D HAL_PCD_MODULE_ENABLED
-D HAL_DAC_MODULE_ENABLED
-D HAL_TIM_MODULE_ENABLED
-D USBD_USE_HID_COMPOSITE
-D PIO_FRAMEWORK_ARDUINO_ENABLE_HID
; -D PIO_FRAMEWORK_ARDUINO_ENABLE_CDC
; -D PIO_FRAMEWORK_ARDUINO_USB_FULLSPEED_FULLMODE
debug_tool = stlink
upload_protocol = stlink
monitor_speed = 115200
monitor_speed = 9600
lib_deps =
; electronrare/ADB @ ^1.0.0
+488 -36
View File
@@ -16,6 +16,10 @@
#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
@@ -30,6 +34,34 @@ 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;
@@ -48,15 +80,137 @@ static float fast_sin(float phase) {
while (phase >= 2.0f * M_PI) phase -= 2.0f * M_PI;
// Approximation polynomiale rapide du sinus
if (phase <= M_PI) {
float x = phase / M_PI; // [0, 1]
return 4.0f * x * (1.0f - x); // Approximation parabolique
} else {
float x = (phase - M_PI) / M_PI; // [0, 1]
return -4.0f * x * (1.0f - x);
// 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)
@@ -93,7 +247,15 @@ void cv_modulation_init(void) {
last_update_time = millis();
Serial.println("CV Modulation initialisée - Portamento + FM actifs");
// 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é");
}
/**
@@ -118,28 +280,94 @@ void cv_modulation_start_portamento(uint8_t cv_channel, uint16_t target_cv) {
}
/**
* @brief Met à jour la modulation de fréquence
* @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) {
// Conversion des mouvements souris en profondeur de modulation
mod_state.fm_depth_cv1 = (float)mouse_x / 127.0f * FM_SENSITIVITY;
mod_state.fm_depth_cv2 = (float)mouse_y / 127.0f * FM_SENSITIVITY;
// Debug occasionnel pour éviter le spam
static uint32_t last_fm_debug = 0;
if (millis() - last_fm_debug > 500) {
if (abs(mouse_x) > 5 || abs(mouse_y) > 5) {
Serial.print("FM update - X: ");
Serial.print(mouse_x);
Serial.print(" (depth: ");
Serial.print(mod_state.fm_depth_cv1, 2);
Serial.print("), Y: ");
Serial.print(mouse_y);
Serial.print(" (depth: ");
Serial.print(mod_state.fm_depth_cv2, 2);
Serial.println(")");
// 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;
}
last_fm_debug = millis();
}
// 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;
}
}
@@ -207,18 +435,40 @@ void cv_modulation_process(void) {
final_cv2 += (int16_t)vibrato_cv2;
}
// Modulation de fréquence CV1
if (abs(mod_state.fm_depth_cv1) > 0.1f) {
float fm_phase = mod_state.vibrato_phase_cv1 * 2.5f;
float fm_cv1 = fast_sin(fm_phase) * mod_state.fm_depth_cv1 * 30.0f;
final_cv1 += (int16_t)fm_cv1;
// 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
if (abs(mod_state.fm_depth_cv2) > 0.1f) {
float fm_phase = mod_state.vibrato_phase_cv2 * 3.0f;
float fm_cv2 = fast_sin(fm_phase) * mod_state.fm_depth_cv2 * 20.0f;
final_cv2 += (int16_t)fm_cv2;
// 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
@@ -256,6 +506,22 @@ void cv_modulation_set_vibrato_params(float frequency, uint16_t 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
*/
@@ -263,6 +529,192 @@ 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
+29 -1
View File
@@ -32,7 +32,7 @@
// Paramètres de modulation
#define PORTAMENTO_SPEED 0.05f // Vitesse du portamento (0.01-0.1)
#define VIBRATO_FREQUENCY 4.5f // Fréquence vibrato en Hz
#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
@@ -119,6 +119,34 @@ void cv_modulation_configure_vibrato(bool enabled, float frequency, uint16_t dep
*/
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
-302
View File
@@ -1,302 +0,0 @@
/**
* @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"
// 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;
// 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
if (phase <= M_PI) {
float x = phase / M_PI; // [0, 1]
return 4.0f * x * (1.0f - x); // Approximation parabolique
} else {
float x = (phase - M_PI) / M_PI; // [0, 1]
return -4.0f * x * (1.0f - x);
}
}
/**
* @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();
Serial.println("CV Modulation initialisée - Portamento + FM actifs");
}
/**
* @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
*/
void cv_modulation_update_fm(int8_t mouse_x, int8_t mouse_y) {
// Conversion des mouvements souris en profondeur de modulation
mod_state.fm_depth_cv1 = (float)mouse_x / 127.0f * FM_SENSITIVITY;
mod_state.fm_depth_cv2 = (float)mouse_y / 127.0f * FM_SENSITIVITY;
// Debug occasionnel pour éviter le spam
static uint32_t last_fm_debug = 0;
if (millis() - last_fm_debug > 500) {
if (abs(mouse_x) > 5 || abs(mouse_y) > 5) {
Serial.print("FM update - X: ");
Serial.print(mouse_x);
Serial.print(" (depth: ");
Serial.print(mod_state.fm_depth_cv1, 2);
Serial.print("), Y: ");
Serial.print(mouse_y);
Serial.print(" (depth: ");
Serial.print(mod_state.fm_depth_cv2, 2);
Serial.println(")");
}
last_fm_debug = millis();
}
}
/**
* @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
if (abs(mod_state.fm_depth_cv1) > 0.1f) {
float fm_phase = mod_state.vibrato_phase_cv1 * 2.5f;
float fm_cv1 = fast_sin(fm_phase) * mod_state.fm_depth_cv1 * 30.0f;
final_cv1 += (int16_t)fm_cv1;
}
// Modulation de fréquence CV2
if (abs(mod_state.fm_depth_cv2) > 0.1f) {
float fm_phase = mod_state.vibrato_phase_cv2 * 3.0f;
float fm_cv2 = fast_sin(fm_phase) * mod_state.fm_depth_cv2 * 20.0f;
final_cv2 += (int16_t)fm_cv2;
}
// 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 Obtient l'état actuel des modulateurs
*/
const cv_modulation_state_t* cv_modulation_get_state(void) {
return &mod_state;
}
/**
* @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
+396
View File
@@ -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(&notes_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 &notes_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
View File
@@ -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
+461 -2
View File
@@ -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
@@ -19,9 +25,10 @@
#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 5
#define POLL_DELAY 1
// Définition de la pin ADB selon la plateforme
#ifdef ARDUINO_ARCH_ESP32
#define ADB_PIN 2
@@ -59,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>
@@ -275,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
@@ -286,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é.");
@@ -305,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
}
/**
@@ -409,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);
@@ -427,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();
@@ -446,6 +898,13 @@ void loop() {
// 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
}
+22
View File
@@ -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 ==="