diff --git a/platformio.ini b/platformio.ini index 38cda20..0e8dea4 100644 --- a/platformio.ini +++ b/platformio.ini @@ -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 diff --git a/src/cv_modulation.cpp b/src/cv_modulation.cpp index be3a3d7..f7e4296 100644 --- a/src/cv_modulation.cpp +++ b/src/cv_modulation.cpp @@ -16,6 +16,10 @@ #include #include "stm32f3xx_hal.h" +#ifdef ARDUINO_ARCH_STM32 +#include // 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 diff --git a/src/cv_modulation.h b/src/cv_modulation.h index b29dea7..373e4ce 100644 --- a/src/cv_modulation.h +++ b/src/cv_modulation.h @@ -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 diff --git a/src/cv_modulation_fixed.cpp b/src/cv_modulation_fixed.cpp deleted file mode 100644 index be3a3d7..0000000 --- a/src/cv_modulation_fixed.cpp +++ /dev/null @@ -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 -#include -#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 diff --git a/src/hid_midi.cpp b/src/hid_midi.cpp new file mode 100644 index 0000000..776d052 --- /dev/null +++ b/src/hid_midi.cpp @@ -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 +#include + +#ifdef ARDUINO_ARCH_STM32 +#include "usbd_hid_composite_if.h" +#include "ADBKeymap.h" +#include "stm32f3xx_hal.h" +#endif + +#ifdef ARDUINO_ARCH_ESP32 +#include +#include +#include +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); +} diff --git a/src/hid_midi.h b/src/hid_midi.h new file mode 100644 index 0000000..871d17d --- /dev/null +++ b/src/hid_midi.h @@ -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 +#include + +// 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 diff --git a/src/main.cpp b/src/main.cpp index 2540331..7e58ab2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -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 #include @@ -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 } diff --git a/test_compile.sh b/test_compile.sh new file mode 100755 index 0000000..c4139a0 --- /dev/null +++ b/test_compile.sh @@ -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 ==="