From cf965934ac789f3f0ba554e872cebae5be32ab9e Mon Sep 17 00:00:00 2001 From: 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com> Date: Fri, 23 May 2025 00:05:13 +0800 Subject: dashboard: media bongo cat --- assets/bongocat.gif | Bin 0 -> 8495 bytes assets/realtime-beat-detector.py | 73 +++++++++++++++++++++++++++++++++++++++ modules/dashboard/dash/Media.qml | 29 ++++++++++++++++ 3 files changed, 102 insertions(+) create mode 100644 assets/bongocat.gif create mode 100755 assets/realtime-beat-detector.py diff --git a/assets/bongocat.gif b/assets/bongocat.gif new file mode 100644 index 0000000..4a50ff1 Binary files /dev/null and b/assets/bongocat.gif differ diff --git a/assets/realtime-beat-detector.py b/assets/realtime-beat-detector.py new file mode 100755 index 0000000..dfecad6 --- /dev/null +++ b/assets/realtime-beat-detector.py @@ -0,0 +1,73 @@ +#!/bin/python + +import pyaudio +import numpy as np +import aubio +import signal +import sys + +from typing import List, Tuple + + +class BeatDetector: + def __init__(self, buf_size: int): + self.buf_size: int = buf_size + + # Set up pyaudio and aubio beat detector + self.audio: pyaudio.PyAudio = pyaudio.PyAudio() + samplerate: int = 44100 + + self.stream: pyaudio.Stream = self.audio.open( + format=pyaudio.paFloat32, + channels=1, + rate=samplerate, + input=True, + frames_per_buffer=self.buf_size, + stream_callback=self._pyaudio_callback + ) + + fft_size: int = self.buf_size * 2 + + # tempo detection + self.tempo: aubio.tempo = aubio.tempo("default", fft_size, self.buf_size, samplerate) + + # this one is called every time enough audio data (buf_size) has been read by the stream + def _pyaudio_callback(self, in_data, frame_count, time_info, status): + # Interpret a buffer as a 1-dimensional array (aubio do not work with raw data) + audio_data = np.frombuffer(in_data, dtype=np.float32) + # true if beat present + beat = self.tempo(audio_data) + + # if beat detected, calculate BPM and send to OSC + if beat[0]: + print(self.tempo.get_bpm(), flush=True) + + return None, pyaudio.paContinue # Tell pyAudio to continue + + def __del__(self): + self.stream.close() + self.audio.terminate() + print('--- Stopped ---') + + +# main +def main(): + bd = BeatDetector(512) + + # capture ctrl+c to stop gracefully process + def signal_handler(none, frame): + bd.stream.stop_stream() + bd.stream.close() + bd.audio.terminate() + print(' ===> Ctrl + C') + sys.exit(0) + + signal.signal(signal.SIGINT, signal_handler) + + # Audio processing happens in separate thread, so put this thread to sleep + signal.pause() + + +# main run +if __name__ == "__main__": + main() diff --git a/modules/dashboard/dash/Media.qml b/modules/dashboard/dash/Media.qml index 7894c59..69ff881 100644 --- a/modules/dashboard/dash/Media.qml +++ b/modules/dashboard/dash/Media.qml @@ -1,6 +1,8 @@ import "root:/widgets" import "root:/services" import "root:/config" +import Quickshell +import Quickshell.Io import Quickshell.Widgets import QtQuick import QtQuick.Shapes @@ -210,6 +212,33 @@ Item { } } + AnimatedImage { + id: bongocat + + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottomMargin: Appearance.padding.large + anchors.margins: Appearance.padding.large * 2 + + playing: false + source: "root:/assets/bongocat.gif" + asynchronous: true + fillMode: AnimatedImage.PreserveAspectFit + z: -1 + + Process { + running: true + command: [`${Quickshell.shellRoot}/assets/realtime-beat-detector.py`] + stdout: SplitParser { + onRead: data => { + if (Players.active?.isPlaying) + bongocat.currentFrame++; + } + } + } + } + component Control: StyledRect { id: control -- cgit v1.2.3-freya