diff --git a/.vscode/launch.json b/.vscode/launch.json index 5411cdd..2adb553 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,6 +1,7 @@ { "version": "0.2.0", "configurations": [ + { "name": "Debug Boot", "type": "cortex-debug", diff --git a/Appli/Core/Inc/audio_analysis.h b/Appli/Core/Inc/audio_analysis.h new file mode 100644 index 0000000..8f2968b --- /dev/null +++ b/Appli/Core/Inc/audio_analysis.h @@ -0,0 +1,50 @@ +#ifndef AUDIO_ANALYSIS_H +#define AUDIO_ANALYSIS_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* FFT calculee sur 2048 points : le spectre d'un signal reel est symetrique, donc + * seule la moitie (0 a Fe/2) est utile -> exactement 1024 points affichables, + * correspondant a la capacite du widget Graph cote IHM. */ +#define AUDIO_ANALYSIS_SAMPLE_RATE_HZ 48000U +#define AUDIO_ANALYSIS_FFT_SIZE 2048U +#define AUDIO_ANALYSIS_NUM_BINS (AUDIO_ANALYSIS_FFT_SIZE / 2U) /* 1024 : 0 a Fe/2 */ + +/* A appeler une fois au demarrage, comme AudioEffects_Init(). */ +void AudioAnalysis_Init(void); + +/* A appeler depuis le chemin audio temps reel (ex: push_to_fifo), un echantillon + * a la fois. Ne fait rien (cout quasi nul) tant que l'analyse n'est pas activee. */ +void AudioAnalysis_PushSample(float sample); + +/* A appeler depuis la tache IHM (jamais depuis le contexte audio) : si un bloc de + * AUDIO_ANALYSIS_FFT_SIZE echantillons est pret, calcule son spectre (fenetrage + + * FFT + magnitude en dB). Retourne true si un nouveau spectre vient d'etre calcule. */ +bool AudioAnalysis_Update(void); + +/* Spectre courant, AUDIO_ANALYSIS_NUM_BINS valeurs en dB (echelle approximative, + * pour affichage uniquement), du bin 0 (continu) au bin NUM_BINS-1 (~Fe/2). Le + * pas entre deux bins vaut AUDIO_ANALYSIS_SAMPLE_RATE_HZ / AUDIO_ANALYSIS_FFT_SIZE + * (23.4375 Hz ici). Valide apres un AudioAnalysis_Update() ayant retourne true ; + * inchange sinon (dernier spectre calcule). */ +const float *AudioAnalysis_GetMagnitudesDb(void); + +/* Frequence (en Hz) du bin de plus forte magnitude dans le dernier spectre calcule + * (frequence dominante / pic). Valide dans les memes conditions que + * AudioAnalysis_GetMagnitudesDb(). */ +float AudioAnalysis_GetPeakFrequencyHz(void); + +/* Active/desactive la capture et le calcul (pilote par le toggle de l'ecran Oscillo). */ +void AudioAnalysis_SetEnabled(bool enabled); +bool AudioAnalysis_IsEnabled(void); + +#ifdef __cplusplus +} +#endif + +#endif /* AUDIO_ANALYSIS_H */ diff --git a/Appli/Core/Inc/audio_effects.h b/Appli/Core/Inc/audio_effects.h index 57183ee..e594fe8 100644 --- a/Appli/Core/Inc/audio_effects.h +++ b/Appli/Core/Inc/audio_effects.h @@ -18,7 +18,8 @@ extern "C" { * octaveur -> egaliseur -> overdrive -> saturation -> echo -> reverberation. */ typedef struct { - bool audio_on_off; + bool audio_on_off; + float master_volume; /* volume general en sortie de chaine, 0.0 a 1.0 */ FxOctaverParams octaver; FxEqualizerParams equalizer; FxOverdriveParams overdrive; diff --git a/Appli/Core/Src/audio_analysis.c b/Appli/Core/Src/audio_analysis.c new file mode 100644 index 0000000..525d0b9 --- /dev/null +++ b/Appli/Core/Src/audio_analysis.c @@ -0,0 +1,211 @@ +#include "audio_analysis.h" +#include +#include + +#define FFT_SIZE AUDIO_ANALYSIS_FFT_SIZE +#define NUM_BINS AUDIO_ANALYSIS_NUM_BINS + +/* --- Capture en double buffer (ping-pong), ecrite depuis le chemin audio temps + * reel, lue depuis la tache IHM. Un seul ecrivain, un seul lecteur : pas de verrou + * necessaire, meme principe que la FIFO audio principale. --- */ +static float g_capture_buf[2][FFT_SIZE]; +static uint32_t g_capture_fill_idx = 0; +static uint8_t g_capture_write_buf = 0; +static volatile uint8_t g_capture_ready_buf = 0xFFU; /* 0xFF = aucun buffer complet en attente */ + +static bool g_analysis_enabled = false; + +/* --- Tables precalculees (a l'init uniquement, jamais recalculees en boucle) --- */ +static float g_hann_window[FFT_SIZE]; +static float g_twiddle_cos[FFT_SIZE / 2U]; +static float g_twiddle_sin[FFT_SIZE / 2U]; + +/* --- Buffers de travail pour la FFT (reels/imaginaires en place) et resultat --- */ +static float g_fft_real[FFT_SIZE]; +static float g_fft_imag[FFT_SIZE]; +static float g_magnitudes_db[NUM_BINS]; /* seule la moitie 0..Fe/2 est conservee */ +static float g_peak_freq_hz = 0.0f; + +static void AudioAnalysis_BuildTables(void) +{ + for (uint32_t i = 0; i < FFT_SIZE; i++) + { + /* Fenetre de Hann : reduit les fuites spectrales dues a la troncature du bloc. */ + g_hann_window[i] = 0.5f - 0.5f * cosf(2.0f * (float)M_PI * (float)i / (float)(FFT_SIZE - 1U)); + } + for (uint32_t i = 0; i < FFT_SIZE / 2U; i++) + { + float angle = -2.0f * (float)M_PI * (float)i / (float)FFT_SIZE; + g_twiddle_cos[i] = cosf(angle); + g_twiddle_sin[i] = sinf(angle); + } +} + +/* Permutation en bit-reverse, etape preliminaire classique d'une FFT radix-2 + * "decimation in time" calculee en place. */ +static void AudioAnalysis_BitReverse(float *real, float *imag) +{ + uint32_t j = 0; + for (uint32_t i = 0; i < FFT_SIZE - 1U; i++) + { + if (i < j) + { + float tmp = real[i]; real[i] = real[j]; real[j] = tmp; + tmp = imag[i]; imag[i] = imag[j]; imag[j] = tmp; + } + uint32_t k = FFT_SIZE >> 1U; + while (k <= j) + { + j -= k; + k >>= 1U; + } + j += k; + } +} + +/* FFT radix-2 iterative (Cooley-Tukey), en place, a partir des tables de twiddle + * precalculees. Complexite O(N log N), largement dans les capacites d'un Cortex-M7 + * a FPU materielle pour N=1024, execute hors du chemin audio temps reel. */ +static void AudioAnalysis_ComputeFFT(float *real, float *imag) +{ + AudioAnalysis_BitReverse(real, imag); + + for (uint32_t size = 2U; size <= FFT_SIZE; size <<= 1U) + { + uint32_t half = size >> 1U; + uint32_t table_step = FFT_SIZE / size; + for (uint32_t i = 0; i < FFT_SIZE; i += size) + { + for (uint32_t k = 0; k < half; k++) + { + uint32_t twiddle_idx = k * table_step; + float c = g_twiddle_cos[twiddle_idx]; + float s = g_twiddle_sin[twiddle_idx]; + uint32_t idx1 = i + k; + uint32_t idx2 = i + k + half; + + float tr = real[idx2] * c - imag[idx2] * s; + float ti = real[idx2] * s + imag[idx2] * c; + + real[idx2] = real[idx1] - tr; + imag[idx2] = imag[idx1] - ti; + real[idx1] += tr; + imag[idx1] += ti; + } + } + } +} + +void AudioAnalysis_Init(void) +{ + memset(g_capture_buf, 0, sizeof(g_capture_buf)); + g_capture_fill_idx = 0; + g_capture_write_buf = 0; + g_capture_ready_buf = 0xFFU; + g_analysis_enabled = false; + + memset(g_magnitudes_db, 0, sizeof(g_magnitudes_db)); + + AudioAnalysis_BuildTables(); +} + +void AudioAnalysis_PushSample(float sample) +{ + if (!g_analysis_enabled) + { + return; + } + + g_capture_buf[g_capture_write_buf][g_capture_fill_idx] = sample; + g_capture_fill_idx++; + + if (g_capture_fill_idx >= FFT_SIZE) + { + g_capture_fill_idx = 0; + g_capture_ready_buf = g_capture_write_buf; + g_capture_write_buf = 1U - g_capture_write_buf; + } +} + +bool AudioAnalysis_Update(void) +{ + if (!g_analysis_enabled) + { + return false; + } + + uint8_t ready = g_capture_ready_buf; + if (ready == 0xFFU) + { + return false; + } + g_capture_ready_buf = 0xFFU; + + for (uint32_t i = 0; i < FFT_SIZE; i++) + { + g_fft_real[i] = g_capture_buf[ready][i] * g_hann_window[i]; + g_fft_imag[i] = 0.0f; + } + + AudioAnalysis_ComputeFFT(g_fft_real, g_fft_imag); + + /* Magnitude normalisee (une sinusoide plein-echelle donne ~0dB) puis convertie + * en dB, avec un plancher pour eviter log(0). Destine a un affichage visuel, + * pas a une mesure calibree. Seule la moitie 0..Fe/2 du spectre est conservee : + * le spectre d'un signal reel est symetrique, la seconde moitie n'apporte rien. */ + /* Recherche du pic en meme temps que le calcul des magnitudes. Le bin 0 (continu, + * 0Hz) est ignore : une composante DC ne doit pas etre rapportee comme "frequence + * dominante". */ + uint32_t peak_bin = 1U; + float peak_mag = 0.0f; + + for (uint32_t i = 0; i < NUM_BINS; i++) + { + float re = g_fft_real[i]; + float im = g_fft_imag[i]; + float mag = sqrtf(re * re + im * im) / ((float)FFT_SIZE / 4.0f); + + if (i >= 1U && mag > peak_mag) + { + peak_mag = mag; + peak_bin = i; + } + + if (mag < 1.0e-6f) + { + mag = 1.0e-6f; + } + g_magnitudes_db[i] = 20.0f * log10f(mag); + } + + g_peak_freq_hz = (float)peak_bin * (float)AUDIO_ANALYSIS_SAMPLE_RATE_HZ / (float)FFT_SIZE; + + return true; +} + +const float *AudioAnalysis_GetMagnitudesDb(void) +{ + return g_magnitudes_db; +} + +float AudioAnalysis_GetPeakFrequencyHz(void) +{ + return g_peak_freq_hz; +} + +void AudioAnalysis_SetEnabled(bool enabled) +{ + if (enabled && !g_analysis_enabled) + { + /* Redemarre une capture propre a chaque activation. */ + g_capture_fill_idx = 0; + g_capture_write_buf = 0; + g_capture_ready_buf = 0xFFU; + } + g_analysis_enabled = enabled; +} + +bool AudioAnalysis_IsEnabled(void) +{ + return g_analysis_enabled; +} diff --git a/Appli/Core/Src/audio_effects.c b/Appli/Core/Src/audio_effects.c index ec30df1..d26d86c 100644 --- a/Appli/Core/Src/audio_effects.c +++ b/Appli/Core/Src/audio_effects.c @@ -12,6 +12,7 @@ static FxReverbState g_reverbState; void AudioEffects_Init(void) { g_params.audio_on_off = false; + g_params.master_volume = 0.0f; FxOctaver_Init(&g_octaverState, &g_params.octaver); FxEqualizer_Init(&g_eqState, &g_params.equalizer); FxOverdrive_Init(&g_overdriveState, &g_params.overdrive); @@ -37,7 +38,7 @@ float AudioEffects_Process(float input) return 0.0f; } - return sample; + return sample * g_params.master_volume; } AudioEffectsParams *AudioEffects_GetParams(void) diff --git a/Appli/Core/Src/main.c b/Appli/Core/Src/main.c index 6950a45..5990b13 100644 --- a/Appli/Core/Src/main.c +++ b/Appli/Core/Src/main.c @@ -26,6 +26,7 @@ /* USER CODE BEGIN Includes */ #include "stm32h7s78_discovery_audio.h" #include "audio_effects.h" +#include "audio_analysis.h" #include /* USER CODE END Includes */ @@ -188,6 +189,7 @@ int main(void) fifo_read_idx = 0; AudioEffects_Init(); + AudioAnalysis_Init(); sDmaConfig.Address = (uint32_t)adf_buf; sDmaConfig.DataLength = AUDIO_SAMPLES * sizeof(int32_t); /* OCTETS */ @@ -793,6 +795,7 @@ static void push_to_fifo(int32_t raw) } float sample = adf_sample_to_float(raw); sample = AudioEffects_Process(sample); + AudioAnalysis_PushSample(sample); int16_t s = float_to_pcm16(sample); uint32_t idx = fifo_write_idx & (FIFO_SIZE - 1); audio_fifo[idx * 2] = s; diff --git a/Appli/TouchGFX/CelloEffects.touchgfx b/Appli/TouchGFX/CelloEffects.touchgfx index 09148b8..cdea8dd 100644 --- a/Appli/TouchGFX/CelloEffects.touchgfx +++ b/Appli/TouchGFX/CelloEffects.touchgfx @@ -71,7 +71,7 @@ { "Type": "ButtonWithLabel", "Name": "buttonWithLabel1", - "Y": 93, + "Y": 134, "Width": 420, "Height": 94, "Pressed": "__generated\\glass_theme_images_widgets_button_regular_height_50_large_rounded_pressed.png", @@ -93,7 +93,7 @@ { "Type": "ButtonWithLabel", "Name": "buttonWithLabel2", - "Y": 281, + "Y": 322, "Width": 420, "Height": 94, "Pressed": "__generated\\glass_theme_images_widgets_button_regular_height_50_large_rounded_pressed.png", @@ -116,7 +116,7 @@ "Type": "ButtonWithLabel", "Name": "buttonWithLabel3", "X": 380, - "Y": 281, + "Y": 322, "Width": 420, "Height": 94, "Pressed": "__generated\\glass_theme_images_widgets_button_regular_height_50_large_rounded_pressed.png", @@ -138,7 +138,7 @@ { "Type": "ButtonWithLabel", "Name": "buttonWithLabel4", - "Y": 187, + "Y": 228, "Width": 420, "Height": 94, "Pressed": "__generated\\glass_theme_images_widgets_button_regular_height_50_large_rounded_pressed.png", @@ -161,7 +161,7 @@ "Type": "ButtonWithLabel", "Name": "buttonWithLabel5", "X": 380, - "Y": 187, + "Y": 228, "Width": 420, "Height": 94, "Pressed": "__generated\\glass_theme_images_widgets_button_regular_height_50_large_rounded_pressed.png", @@ -184,7 +184,7 @@ "Type": "ButtonWithLabel", "Name": "buttonWithLabel6", "X": 380, - "Y": 93, + "Y": 134, "Width": 420, "Height": 94, "Pressed": "__generated\\glass_theme_images_widgets_button_regular_height_50_large_rounded_pressed.png", @@ -203,16 +203,67 @@ "TextRotation": "0", "Preset": "glass_theme\\presets\\button\\regular\\height_50\\large_rounded_normal.json" }, + { + "Type": "ButtonWithLabel", + "Name": "buttonWithLabel7", + "X": 190, + "Y": 396, + "Width": 420, + "Height": 94, + "Pressed": "__generated\\glass_theme_images_widgets_button_regular_height_50_large_rounded_pressed.png", + "Released": "__generated\\glass_theme_images_widgets_button_regular_height_50_large_rounded_normal.png", + "TextId": "OSCILLO", + "ReleasedColor": { + "Red": 230, + "Green": 214, + "Blue": 170 + }, + "PressedColor": { + "Red": 230, + "Green": 214, + "Blue": 170 + }, + "TextRotation": "0", + "Preset": "glass_theme\\presets\\button\\regular\\height_50\\large_rounded_normal.json" + }, { "Type": "ToggleButton", "Name": "toggleButton1", - "X": 118, - "Y": 22, + "X": 135, + "Y": 18, "Width": 170, "Height": 60, "Pressed": "__generated\\glass_theme_images_widgets_togglebutton_large_rounded_text_on_active.png", "Released": "__generated\\glass_theme_images_widgets_togglebutton_large_rounded_text_off_normal.png", "Preset": "glass_theme\\presets\\togglebutton\\large\\rounded_active_text.json" + }, + { + "Type": "Slider", + "Name": "slider1", + "X": 329, + "Y": 22, + "Width": 456, + "Height": 52, + "IsHorizontalSlider": true, + "BackgroundUnselected": "__generated\\glass_theme_images_widgets_slider_horizontal_thick_track_large.png", + "BackgroundSelected": "__generated\\glass_theme_images_widgets_slider_horizontal_thick_filler_large.png", + "Indicator": "__generated\\glass_theme_images_widgets_slider_horizontal_thick_rounded_dark.png", + "BackgroundX": 28, + "BackgroundY": 21, + "IndicatorMax": 400, + "ValueMax": 100, + "Preset": "glass_theme\\presets\\slider\\horizontal\\thick\\large_rounded_dark.json" + }, + { + "Type": "ScalableImage", + "Name": "scalableImage1", + "X": 15, + "Y": 4, + "Width": 105, + "Height": 95, + "Filename": "__generated\\icon_theme_images_hardware_headset_50_50_FFFFFF.svg.png", + "Alpha": 149, + "ScalingAlgorithm": "Bilinear Interpolation" } ], "Interactions": [ @@ -298,6 +349,39 @@ "ScreenTransitionType": "ScreenTransitionNone", "ActionComponent": "Octaver" } + }, + { + "InteractionName": "Interaction8", + "Trigger": { + "Type": "TriggerSliderValueChanged", + "TriggerComponent": "slider1" + }, + "Action": { + "Type": "ActionCustom", + "FunctionName": "slider1Changed" + } + }, + { + "InteractionName": "Interaction9", + "Trigger": { + "Type": "TriggerSliderValueConfirmed", + "TriggerComponent": "slider1" + }, + "Action": { + "Type": "ActionNone" + } + }, + { + "InteractionName": "Interaction10", + "Trigger": { + "Type": "TriggerClicked", + "TriggerComponent": "buttonWithLabel7" + }, + "Action": { + "Type": "ActionGotoScreen", + "ScreenTransitionType": "ScreenTransitionNone", + "ActionComponent": "Oscillo" + } } ] }, @@ -2018,6 +2102,198 @@ } } ] + }, + { + "Name": "Oscillo", + "CanvasBufferSize": 12000, + "Components": [ + { + "Type": "Image", + "Name": "image1", + "Y": -1, + "Width": 800, + "Height": 483, + "RelativeFilename": "fondHorlogerie1.png" + }, + { + "Type": "Box", + "Name": "box1", + "Width": 800, + "Height": 480, + "Color": { + "Red": 35, + "Green": 24, + "Blue": 15 + }, + "Alpha": 240 + }, + { + "Type": "TextArea", + "Name": "textArea1", + "X": 314, + "Y": 26, + "Width": 173, + "Height": 42, + "TextId": "OSCILLO", + "TextRotation": "0", + "Color": { + "Red": 230, + "Green": 214, + "Blue": 170 + }, + "AutoSize": true + }, + { + "Type": "ToggleButton", + "Name": "toggleButton1", + "X": 576, + "Y": 20, + "Width": 170, + "Height": 60, + "Pressed": "__generated\\glass_theme_images_widgets_togglebutton_large_round_text_on_active.png", + "Released": "__generated\\glass_theme_images_widgets_togglebutton_large_round_text_off_normal.png", + "Preset": "glass_theme\\presets\\togglebutton\\large\\round_active_text.json" + }, + { + "Type": "ButtonWithLabel", + "Name": "buttonWithLabel1", + "Width": 225, + "Height": 94, + "Pressed": "__generated\\glass_theme_images_widgets_button_regular_height_50_small_rounded_pressed.png", + "Released": "__generated\\glass_theme_images_widgets_button_regular_height_50_small_rounded_normal.png", + "TextId": "BACK", + "ReleasedColor": { + "Red": 230, + "Green": 214, + "Blue": 170 + }, + "PressedColor": { + "Red": 230, + "Green": 214, + "Blue": 170 + }, + "TextRotation": "0", + "Preset": "glass_theme\\presets\\button\\regular\\height_50\\small_rounded_normal.json" + }, + { + "Type": "StaticGraph", + "Name": "graph1", + "Y": 94, + "Width": 800, + "Height": 386, + "DataSeed": 676876642, + "NumberOfValues": 1024, + "MaxX": 24000.0, + "PrecisionX": "1", + "MaxY": 100.0, + "PrecisionY": "1", + "XAxisScale": 1.0, + "InsertRandomValues": true, + "LabelSizeBottom": 63, + "LabelSizeLeft": 16, + "LabelSizeRight": 16, + "PaddingBottom": 17, + "GraphDrawers": [ + { + "Type": "LineDrawer", + "LineWidth": 2, + "FileNameImage": "", + "Color": { + "Red": 20, + "Green": 151, + "Blue": 197 + } + } + ], + "XAxisGrid": { + "Type": "GraphGrid", + "Color": { + "Red": 230, + "Green": 214, + "Blue": 170 + }, + "Interval": 2400.0, + "LineWidth": 1 + }, + "YAxisGrid": { + "Type": "GraphGrid", + "Color": { + "Red": 230, + "Green": 214, + "Blue": 170 + }, + "Interval": 25.0, + "LineWidth": 1 + }, + "XAxisLabel": { + "Type": "GraphLabel", + "Interval": 2400.0, + "DecimalSeparator": "Point", + "TextColor": { + "Red": 230, + "Green": 214, + "Blue": 170 + }, + "TextId": "__SingleUse_2C9Q", + "TextRotation": "270", + "Position": "Bottom" + } + }, + { + "Type": "Image", + "Name": "image2", + "X": 535, + "Y": 74, + "Width": 264, + "Height": 80, + "RelativeFilename": "__generated\\glass_theme_images_widgets_button_regular_height_36_medium_round_action.png" + }, + { + "Type": "TextArea", + "Name": "textArea2", + "X": 582, + "Y": 104, + "Width": 181, + "Height": 23, + "TextId": "textAreaMax", + "TextRotation": "0", + "Color": { + "Red": 230, + "Green": 214, + "Blue": 170 + }, + "Wildcard1": { + "TextId": "__SingleUse_U5V0", + "UseBuffer": true, + "BufferSize": 10 + } + } + ], + "Interactions": [ + { + "InteractionName": "Interaction1", + "Trigger": { + "Type": "TriggerClicked", + "TriggerComponent": "buttonWithLabel1" + }, + "Action": { + "Type": "ActionGotoScreen", + "ScreenTransitionType": "ScreenTransitionNone", + "ActionComponent": "Overview" + } + }, + { + "InteractionName": "Interaction2", + "Trigger": { + "Type": "TriggerClicked", + "TriggerComponent": "toggleButton1" + }, + "Action": { + "Type": "ActionCustom", + "FunctionName": "toggleClicked" + } + } + ] } ], "CustomContainerDefinitions": [], diff --git a/Appli/TouchGFX/assets/images/__generated/glass_theme_images_widgets_button_regular_height_36_medium_round_action.png b/Appli/TouchGFX/assets/images/__generated/glass_theme_images_widgets_button_regular_height_36_medium_round_action.png new file mode 100644 index 0000000..9f54782 Binary files /dev/null and b/Appli/TouchGFX/assets/images/__generated/glass_theme_images_widgets_button_regular_height_36_medium_round_action.png differ diff --git a/Appli/TouchGFX/assets/images/__generated/icon_theme_images_hardware_headset_50_50_FFFFFF.svg.png b/Appli/TouchGFX/assets/images/__generated/icon_theme_images_hardware_headset_50_50_FFFFFF.svg.png new file mode 100644 index 0000000..73065dd Binary files /dev/null and b/Appli/TouchGFX/assets/images/__generated/icon_theme_images_hardware_headset_50_50_FFFFFF.svg.png differ diff --git a/Appli/TouchGFX/assets/texts/texts.xml b/Appli/TouchGFX/assets/texts/texts.xml index 478914b..8bd9921 100644 --- a/Appli/TouchGFX/assets/texts/texts.xml +++ b/Appli/TouchGFX/assets/texts/texts.xml @@ -14,6 +14,9 @@ OCTAVER + + OSCILLO + OVERDRIVE @@ -105,10 +108,20 @@ +2 - + + + 100 + + + Max : <d> Hz + + + <> + + - + diff --git a/Appli/TouchGFX/gui/include/gui/oscillo_screen/OscilloPresenter.hpp b/Appli/TouchGFX/gui/include/gui/oscillo_screen/OscilloPresenter.hpp new file mode 100644 index 0000000..0fe60e8 --- /dev/null +++ b/Appli/TouchGFX/gui/include/gui/oscillo_screen/OscilloPresenter.hpp @@ -0,0 +1,36 @@ +#ifndef OSCILLOPRESENTER_HPP +#define OSCILLOPRESENTER_HPP + +#include +#include + +using namespace touchgfx; + +class OscilloView; + +class OscilloPresenter : public touchgfx::Presenter, public ModelListener +{ +public: + OscilloPresenter(OscilloView& v); + + /** + * The activate function is called automatically when this screen is "switched in" + * (ie. made active). Initialization logic can be placed here. + */ + virtual void activate(); + + /** + * The deactivate function is called automatically when this screen is "switched out" + * (ie. made inactive). Teardown functionality can be placed here. + */ + virtual void deactivate(); + + virtual ~OscilloPresenter() {} + +private: + OscilloPresenter(); + + OscilloView& view; +}; + +#endif // OSCILLOPRESENTER_HPP diff --git a/Appli/TouchGFX/gui/include/gui/oscillo_screen/OscilloView.hpp b/Appli/TouchGFX/gui/include/gui/oscillo_screen/OscilloView.hpp new file mode 100644 index 0000000..396cd99 --- /dev/null +++ b/Appli/TouchGFX/gui/include/gui/oscillo_screen/OscilloView.hpp @@ -0,0 +1,20 @@ +#ifndef OSCILLOVIEW_HPP +#define OSCILLOVIEW_HPP + +#include +#include + +class OscilloView : public OscilloViewBase +{ +public: + OscilloView(); + virtual ~OscilloView() {} + virtual void setupScreen(); + virtual void tearDownScreen(); + virtual void handleTickEvent(); + + virtual void toggleClicked(); +protected: +}; + +#endif // OSCILLOVIEW_HPP diff --git a/Appli/TouchGFX/gui/include/gui/overview_screen/OverviewView.hpp b/Appli/TouchGFX/gui/include/gui/overview_screen/OverviewView.hpp index ce74378..345fb77 100644 --- a/Appli/TouchGFX/gui/include/gui/overview_screen/OverviewView.hpp +++ b/Appli/TouchGFX/gui/include/gui/overview_screen/OverviewView.hpp @@ -13,6 +13,7 @@ public: virtual void tearDownScreen(); virtual void toggleAudioOnOFF(); + virtual void slider1Changed(int value); protected: }; diff --git a/Appli/TouchGFX/gui/src/oscillo_screen/OscilloPresenter.cpp b/Appli/TouchGFX/gui/src/oscillo_screen/OscilloPresenter.cpp new file mode 100644 index 0000000..ff6116a --- /dev/null +++ b/Appli/TouchGFX/gui/src/oscillo_screen/OscilloPresenter.cpp @@ -0,0 +1,18 @@ +#include +#include + +OscilloPresenter::OscilloPresenter(OscilloView& v) + : view(v) +{ + +} + +void OscilloPresenter::activate() +{ + +} + +void OscilloPresenter::deactivate() +{ + +} diff --git a/Appli/TouchGFX/gui/src/oscillo_screen/OscilloView.cpp b/Appli/TouchGFX/gui/src/oscillo_screen/OscilloView.cpp new file mode 100644 index 0000000..debebd2 --- /dev/null +++ b/Appli/TouchGFX/gui/src/oscillo_screen/OscilloView.cpp @@ -0,0 +1,59 @@ +#include +#include +#include + +OscilloView::OscilloView() +{ +} + +void OscilloView::setupScreen() +{ + OscilloViewBase::setupScreen(); + toggleButton1.forceState(AudioAnalysis_IsEnabled()); + + /* Echelle du graphe en dB (affichage uniquement, pas une mesure calibree) : + * de -80dB (proche du plancher de bruit numerique) a +5dB de marge au-dessus + * du 0dB nominal. */ + graph1.setGraphRangeY(-80.0f, 5.0f); + + /* Axe X en Hz, de 0 a Fe/2 (24kHz) : ce graphe (statique, points X/Y explicites, + * contrairement au precedent DynamicGraph a increment X automatique) recoit + * directement la frequence de chaque bin avec addDataPoint(x, y). */ + graph1.setGraphRangeX(0.0f, (float)AUDIO_ANALYSIS_SAMPLE_RATE_HZ / 2.0f); +} + +void OscilloView::tearDownScreen() +{ + OscilloViewBase::tearDownScreen(); +} + +void OscilloView::handleTickEvent() +{ + OscilloViewBase::handleTickEvent(); + + if (!AudioAnalysis_IsEnabled()) + { + return; + } + + if (AudioAnalysis_Update()) + { + const float *magnitudesDb = AudioAnalysis_GetMagnitudesDb(); + const float binHz = (float)AUDIO_ANALYSIS_SAMPLE_RATE_HZ / (float)AUDIO_ANALYSIS_FFT_SIZE; + + graph1.clear(); + for (int i = 0; i < (int)AUDIO_ANALYSIS_NUM_BINS; i++) + { + graph1.addDataPoint((float)i * binHz, magnitudesDb[i]); + } + + touchgfx::Unicode::snprintf(textArea2Buffer, TEXTAREA2_SIZE, "%d", (int)AudioAnalysis_GetPeakFrequencyHz()); + textArea2.setWildcard(textArea2Buffer); + textArea2.invalidate(); + } +} + +void OscilloView::toggleClicked() +{ + AudioAnalysis_SetEnabled(toggleButton1.getState()); +} diff --git a/Appli/TouchGFX/gui/src/overview_screen/OverviewView.cpp b/Appli/TouchGFX/gui/src/overview_screen/OverviewView.cpp index 3e6d5da..ef21a70 100644 --- a/Appli/TouchGFX/gui/src/overview_screen/OverviewView.cpp +++ b/Appli/TouchGFX/gui/src/overview_screen/OverviewView.cpp @@ -10,6 +10,7 @@ void OverviewView::setupScreen() { OverviewViewBase::setupScreen(); toggleButton1.forceState(AudioEffects_GetParams()->audio_on_off); + slider1.setValue((int16_t)(AudioEffects_GetParams()->master_volume * 100.0f)); } void OverviewView::tearDownScreen() @@ -21,3 +22,15 @@ void OverviewView::toggleAudioOnOFF() { AudioEffects_GetParams()->audio_on_off = toggleButton1.getState(); } + +void OverviewView::slider1Changed(int value) +{ + AudioEffects_GetParams()->master_volume = value / 100.0f; + + /* Le volume pilote l'etat marche/arret (0 = off, non nul = on), mais pas + * l'inverse : agir sur le toggle ne doit pas modifier le volume. */ + bool en = (value != 0); + AudioEffects_GetParams()->audio_on_off = en; + toggleButton1.forceState(en); + toggleButton1.invalidate(); +} diff --git a/STM32CubeIDE/Appli/.project b/STM32CubeIDE/Appli/.project index 9d607a4..c26e124 100644 --- a/STM32CubeIDE/Appli/.project +++ b/STM32CubeIDE/Appli/.project @@ -430,6 +430,16 @@ 1 PARENT-2-PROJECT_LOC/Appli/TouchGFX/gui/src/octaver_screen/OctaverView.cpp + + Application/User/gui/OscilloPresenter.cpp + 1 + PARENT-2-PROJECT_LOC/Appli/TouchGFX/gui/src/oscillo_screen/OscilloPresenter.cpp + + + Application/User/gui/OscilloView.cpp + 1 + PARENT-2-PROJECT_LOC/Appli/TouchGFX/gui/src/oscillo_screen/OscilloView.cpp + Application/User/gui/OverdrivePresenter.cpp 1 @@ -580,6 +590,11 @@ 1 PARENT-2-PROJECT_LOC/Appli/TouchGFX/generated/gui_generated/src/octaver_screen/OctaverViewBase.cpp + + Application/User/generated/OscilloViewBase.cpp + 1 + PARENT-2-PROJECT_LOC/Appli/TouchGFX/generated/gui_generated/src/oscillo_screen/OscilloViewBase.cpp + Application/User/generated/OverdriveViewBase.cpp 1 @@ -620,6 +635,11 @@ 1 PARENT-2-PROJECT_LOC/Appli/TouchGFX/generated/images/src/__generated/image_glass_theme_images_widgets_button_icon_round_huge_fill_pressed.cpp + + Application/User/generated/image_glass_theme_images_widgets_button_regular_height_36_medium_round_action.cpp + 1 + PARENT-2-PROJECT_LOC/Appli/TouchGFX/generated/images/src/__generated/image_glass_theme_images_widgets_button_regular_height_36_medium_round_action.cpp + Application/User/generated/image_glass_theme_images_widgets_button_regular_height_50_large_rounded_normal.cpp 1 @@ -700,6 +720,11 @@ 1 PARENT-2-PROJECT_LOC/Appli/TouchGFX/generated/images/src/__generated/image_icon_theme_images_av_fiber_manual_record_50_50_5E3E10.svg.cpp + + Application/User/generated/image_icon_theme_images_hardware_headset_50_50_FFFFFF.svg.cpp + 1 + PARENT-2-PROJECT_LOC/Appli/TouchGFX/generated/images/src/__generated/image_icon_theme_images_hardware_headset_50_50_FFFFFF.svg.cpp + Application/User/generated/image_fondHorlogerie1.cpp 1 diff --git a/gcc/makefile_appli b/gcc/makefile_appli index 50ff7ed..6489936 100644 --- a/gcc/makefile_appli +++ b/gcc/makefile_appli @@ -300,6 +300,7 @@ board_c_files += \ Appli/Core/Src/stm32h7rsxx_hal_timebase_tim.c \ Appli/Core/Src/system_stm32h7rsxx.c \ Appli/Core/Src/audio_effects.c \ + Appli/Core/Src/audio_analysis.c \ Appli/Core/Src/fx_octaver.c \ Appli/Core/Src/fx_equalizer.c \ Appli/Core/Src/fx_overdrive.c \