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: [PATCH] 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 0000000000000000000000000000000000000000..4a50ff1ab3429aba23f82a103200871ad19038ec GIT binary patch literal 8495 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 literal 0 HcmV?d00001 diff --git a/assets/realtime-beat-detector.py b/assets/realtime-beat-detector.py new file mode 100755 index 00000000..dfecad67 --- /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 7894c598..69ff8815 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