From 823552ce7f075ca0265e5810d5ae2a50012f9e2e Mon Sep 17 00:00:00 2001 From: Martin Bauer Date: Fri, 24 Dec 2021 00:12:43 +0100 Subject: [PATCH] updates --- espmusicmouse/host_driver/audio_analysis.py | 129 ++++++++++++++++++++ espmusicmouse/host_driver/main.py | 30 ++++- espmusicmouse/src/TaskLed.h | 2 +- espmusicmouse/src/main.cpp | 16 ++- 4 files changed, 169 insertions(+), 8 deletions(-) create mode 100644 espmusicmouse/host_driver/audio_analysis.py diff --git a/espmusicmouse/host_driver/audio_analysis.py b/espmusicmouse/host_driver/audio_analysis.py new file mode 100644 index 0000000..a506d02 --- /dev/null +++ b/espmusicmouse/host_driver/audio_analysis.py @@ -0,0 +1,129 @@ +from typing import overload +import librosa +from numba import jit +import numpy as np + + +@jit(nopython=True) +def normalize_feature_sequence(X, norm='2', threshold=0.0001, v=None): + """Normalizes the columns of a feature sequence + Notebook: C3/C3S1_FeatureNormalization.ipynb + Args: + X (np.ndarray): Feature sequence + norm (str): The norm to be applied. '1', '2', 'max' or 'z' (Default value = '2') + threshold (float): An threshold below which the vector ``v`` used instead of normalization + (Default value = 0.0001) + v (float): Used instead of normalization below ``threshold``. If None, uses unit vector for given norm + (Default value = None) + Returns: + X_norm (np.ndarray): Normalized feature sequence + """ + assert norm in ['1', '2', 'max', 'z'] + + K, N = X.shape + X_norm = np.zeros((K, N)) + + if norm == '1': + if v is None: + v = np.ones(K, dtype=np.float64) / K + for n in range(N): + s = np.sum(np.abs(X[:, n])) + if s > threshold: + X_norm[:, n] = X[:, n] / s + else: + X_norm[:, n] = v + + if norm == '2': + if v is None: + v = np.ones(K, dtype=np.float64) / np.sqrt(K) + for n in range(N): + s = np.sqrt(np.sum(X[:, n]**2)) + if s > threshold: + X_norm[:, n] = X[:, n] / s + else: + X_norm[:, n] = v + + if norm == 'max': + if v is None: + v = np.ones(K, dtype=np.float64) + for n in range(N): + s = np.max(np.abs(X[:, n])) + if s > threshold: + X_norm[:, n] = X[:, n] / s + else: + X_norm[:, n] = v + + if norm == 'z': + if v is None: + v = np.zeros(K, dtype=np.float64) + for n in range(N): + mu = np.sum(X[:, n]) / K + sigma = np.sqrt(np.sum((X[:, n] - mu)**2) / (K - 1)) + if sigma > threshold: + X_norm[:, n] = (X[:, n] - mu) / sigma + else: + X_norm[:, n] = v + + return X_norm + + +def compute_chromagram_from_filename(fn_wav, + Fs=22050, + N=4096, + H=2048, + gamma=None, + version='STFT', + norm='2'): + """Compute chromagram for WAV file specified by filename + + Notebook: C5/C5S2_ChordRec_Templates.ipynb + + Args: + fn_wav (str): Filenname of WAV + Fs (scalar): Sampling rate (Default value = 22050) + N (int): Window size (Default value = 4096) + H (int): Hop size (Default value = 2048) + gamma (float): Constant for logarithmic compression (Default value = None) + version (str): Technique used for front-end decomposition ('STFT', 'IIS', 'CQT') (Default value = 'STFT') + norm (str): If not 'None', chroma vectors are normalized by norm as specified ('1', '2', 'max') + (Default value = '2') + + Returns: + X (np.ndarray): Chromagram + Fs_X (scalar): Feature reate of chromagram + x (np.ndarray): Audio signal + Fs (scalar): Sampling rate of audio signal + x_dur (float): Duration (seconds) of audio signal + """ + x, Fs = librosa.load(fn_wav, sr=Fs) + x_dur = x.shape[0] / Fs + if version == 'STFT': + # Compute chroma features with STFT + X = librosa.stft(x, n_fft=N, hop_length=H, pad_mode='constant', center=True) + if gamma is not None: + X = np.log(1 + gamma * np.abs(X)**2) + else: + X = np.abs(X)**2 + X = librosa.feature.chroma_stft(S=X, sr=Fs, tuning=0, norm=None, hop_length=H, n_fft=N) + if version == 'CQT': + # Compute chroma features with CQT decomposition + X = librosa.feature.chroma_cqt(y=x, sr=Fs, hop_length=H, norm=None) + if version == 'IIR': + # Compute chroma features with filter bank (using IIR elliptic filter) + X = librosa.iirt(y=x, sr=Fs, win_length=N, hop_length=H, center=True, tuning=0.0) + if gamma is not None: + X = np.log(1.0 + gamma * X) + X = librosa.feature.chroma_cqt(C=X, + bins_per_octave=12, + n_octaves=7, + fmin=librosa.midi_to_hz(24), + norm=None) + if norm is not None: + X = normalize_feature_sequence(X, norm='2') + Fs_X = Fs / H + return X, Fs_X, x, Fs, x_dur + + +if __name__ == "__main__": + test_file = "/home/martin/Music/deemix Music/Simone Sommerland - Ki-Ka-Kinderturnen.mp3" + chroma = compute_chromagram_from_filename(test_file) diff --git a/espmusicmouse/host_driver/main.py b/espmusicmouse/host_driver/main.py index 11b5046..cf8e113 100644 --- a/espmusicmouse/host_driver/main.py +++ b/espmusicmouse/host_driver/main.py @@ -8,7 +8,10 @@ from player import AudioPlayer from glob import glob from copy import deepcopy import os +from hass_client import HomeAssistantClient +HASS_URL = "https://ha.bauer.tech" +HASS_TOKEN = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiI3YmY1OTc3NWJhZGI0MzFkOTBiNDBkZDA3OGQ1MTMxNSIsImlhdCI6MTY0MDAzNzI3MSwiZXhwIjoxOTU1Mzk3MjcxfQ.c7RPRP_hxzIwd3xcFTNLz94rOjLQDR0elH8RE-jCDgc" MUSIC_FOLDER = "/home/martin/code/musicmouse/espmusicmouse/host_driver/music" audio_player = AudioPlayer() @@ -96,6 +99,7 @@ def on_rfid(protocol, tagid): ring_eff = EffectSwipeAndChange() ring_eff.swipe.primary_color = ccfg.primary ring_eff.swipe.secondary_color = ccfg.secondary + ring_eff.swipe.swipe_speed = 180 ring_eff.change.color1 = ccfg.primary ring_eff.change.color2 = ccfg.secondary protocol.led_ring_effect(ring_eff) @@ -104,6 +108,7 @@ def on_rfid(protocol, tagid): mouse_eff = deepcopy(ring_eff) mouse_eff.swipe.start_position = 6 / 45 * 360 mouse_eff.swipe.bell_curve_width_in_leds = 16 + mouse_eff.swipe.swipe_speed = 180 protocol.mouse_led_effect(mouse_eff) protocol.button_background_led_prev(0.3) @@ -132,12 +137,23 @@ def on_firmware_msg(protocol: MusicMouseProtocol, message): audio_player.previous() elif message.button == "right" and message.event == "pressed": audio_player.next() - elif isinstance(message, TouchButtonPress): - ccfg = color_cfg[current_figure] - protocol.mouse_led_effect(EffectStaticConfig(ccfg.accent, - *mouse_leds[message.touch_button])) - elif isinstance(message, TouchButtonRelease): - ccfg = color_cfg[current_figure] + elif False and isinstance(message, TouchButtonPress): + if current_figure: + ccfg = color_cfg[current_figure] + protocol.mouse_led_effect( + EffectStaticConfig(ccfg.accent, *mouse_leds[message.touch_button])) + #if message.touch_button == TouchButton.RIGHT_FOOT: + # asyncio.create_task( + # hass.call_service("switch", "toggle", {"entity_id": "switch.tasmota06"})) + # asyncio.create_task( + # hass.call_service("light", "turn_on", {"entity_id": "light.arbeitszimmer_fluter"})) + #elif message.touch_button == TouchButton.LEFT_FOOT: + # asyncio.create_task( + # hass.call_service("light", "turn_off", {"entity_id": "light.arbeitszimmer_fluter"})) + + elif False and isinstance(message, TouchButtonRelease): + if current_figure: + ccfg = color_cfg[current_figure] eff_change = EffectRandomTwoColorInterpolationConfig() eff_static = EffectStaticConfig(ColorRGBW(0, 0, 0, 0), *mouse_leds[message.touch_button]) if audio_player.is_playing(): @@ -152,6 +168,7 @@ def on_firmware_msg(protocol: MusicMouseProtocol, message): loop = asyncio.get_event_loop() +hass = HomeAssistantClient(HASS_URL, HASS_TOKEN, loop) coro = serial_asyncio.create_serial_connection(loop, MusicMouseProtocol, '/dev/ttyUSB0', @@ -161,5 +178,6 @@ protocol.register_message_callback(on_firmware_msg) audio_player.on_playlist_end_callback = lambda: on_music_end_callback(protocol) +loop.create_task(hass.connect()) loop.run_forever() loop.close() \ No newline at end of file diff --git a/espmusicmouse/src/TaskLed.h b/espmusicmouse/src/TaskLed.h index 2bd1c5d..9fd455c 100644 --- a/espmusicmouse/src/TaskLed.h +++ b/espmusicmouse/src/TaskLed.h @@ -12,7 +12,7 @@ #include static constexpr int MAX_EFFECT_CONFIG_SIZE = 128; -static constexpr int MAX_EFFECT_CLASS_SIZE = 4 * 1024; +static constexpr int MAX_EFFECT_CLASS_SIZE = 8 * 1024; template class LedTask diff --git a/espmusicmouse/src/main.cpp b/espmusicmouse/src/main.cpp index 7312f51..897f026 100644 --- a/espmusicmouse/src/main.cpp +++ b/espmusicmouse/src/main.cpp @@ -129,7 +129,7 @@ LedTask ledTaskCircle; void setupLedCircle() { - ledDriverCircle.begin(23, 0); + ledDriverCircle.begin(22, 0); ledTaskCircle.begin(ledStripCircle, ledDriverCircle); ledTaskCircle.startEffect(EffectStaticConfig(ColorRGBW{0, 0, 0, 0})); } @@ -147,6 +147,19 @@ void setupMouseLeds() ledTaskMouse.startEffect(EffectStaticConfig{ColorRGBW{0, 0, 0, 0}, 0, 0}); } +// -------------------------------------------------- Shelf Leds ------------------------------------------- + +LedStripRGBW<250> ledStripShelf; +Esp32DriverRGBW ledDriverShelf; +LedTask ledTaskShelf; + +void setupShelfLeds() +{ + ledDriverShelf.begin(17, 2); + ledTaskShelf.begin(ledStripShelf, ledDriverShelf); + ledTaskShelf.startEffect(EffectStaticConfig{ColorRGBW{0, 0, 30}, 0, 0}); +} + // -------------------------------------------------- Touch Buttons ---------------------------------------- constexpr auto TOUCH_PAD_RIGHT_EAR = TOUCH_PAD_NUM0; @@ -205,6 +218,7 @@ void setup() setupButtons(); setupTouchButtons(); setupMouseLeds(); + setupShelfLeds(); } void loop()