aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEddy Pedroni <epedroni@pm.me>2026-10-05 20:38:57 +0200
committerEddy Pedroni <epedroni@pm.me>2026-10-05 20:38:57 +0200
commit3c3fae1c29348c8b5674dac7ccdb30eab78668f6 (patch)
tree165365b9478d1c0f8ab47a9aaea13ad0a1d4daa3
parentd75ed4040c331eee2643c28c6887bf0078fc3188 (diff)
Add metronome MVP
-rw-r--r--solo-tool-project/src/solo_tool/metronome.py11
-rw-r--r--web-project/src/recording.py8
-rw-r--r--web-project/src/solo_tool_web.py20
3 files changed, 27 insertions, 12 deletions
diff --git a/solo-tool-project/src/solo_tool/metronome.py b/solo-tool-project/src/solo_tool/metronome.py
index d99e0b8..9c65357 100644
--- a/solo-tool-project/src/solo_tool/metronome.py
+++ b/solo-tool-project/src/solo_tool/metronome.py
@@ -2,13 +2,14 @@ import pyaudio as pa
from array import array
def _createClickSample(clickFrequency: int, samplingRate: int) -> array:
- clickDuration = 0.1 # seconds
+ clickDuration = 0.03 # seconds
sampleLength = int(clickDuration * samplingRate)
- fadeSamples = 10
- attackEnv = [i/fadeSamples for i in range(1, fadeSamples, 1)]
- releaseEnv = [i/fadeSamples for i in range(fadeSamples, 0, -1)]
- amplitudeEnv = attackEnv + [1.0] * (sampleLength - len(attackEnv) - len(releaseEnv)) + releaseEnv
+ fadeSamples = 50
+ amplitude = 0.8
+ attackEnv = [(i/fadeSamples) * amplitude for i in range(1, fadeSamples, 1)]
+ releaseEnv = [(i/fadeSamples) * amplitude for i in range(fadeSamples, 0, -1)]
+ amplitudeEnv = attackEnv + [amplitude] * (sampleLength - len(attackEnv) - len(releaseEnv)) + releaseEnv
cycleLength = (1 / clickFrequency) * samplingRate
from math import sin, pi
diff --git a/web-project/src/recording.py b/web-project/src/recording.py
index 4301b02..7019697 100644
--- a/web-project/src/recording.py
+++ b/web-project/src/recording.py
@@ -103,13 +103,15 @@ def recordingControls(soloTool, recorder, sessionManager, getCurrentSong):
with ui.button_group().classes('').style('height: 40px'):
recordButton = ui.button(icon='fiber_manual_record', color='negative') \
- .bind_icon_from(recorder, 'recording', lambda recording: 'radio_button_unchecked' if recording else 'fiber_manual_record')
+ .bind_icon_from(recorder, 'recording', lambda recording: 'radio_button_unchecked' if recording else 'fiber_manual_record') \
+ .style("width: 80px")
playButton = ui.button(icon='hearing') \
- .bind_icon_from(soloTool, 'playingAdHoc', lambda adHoc: 'close' if adHoc else 'hearing')
+ .bind_icon_from(soloTool, 'playingAdHoc', lambda adHoc: 'close' if adHoc else 'hearing') \
+ .style("width: 80px")
playButton.disable()
- uploadButton = ui.button(icon='cloud_upload')
+ uploadButton = ui.button(icon='cloud_upload').style("width: 80px")
uploadButton.disable()
recordButton.on_click(_makeRecordCallback(playButton, recordButton, uploadButton, soloTool, recorder, wavFile))
diff --git a/web-project/src/solo_tool_web.py b/web-project/src/solo_tool_web.py
index 98cb9df..4a3de3a 100644
--- a/web-project/src/solo_tool_web.py
+++ b/web-project/src/solo_tool_web.py
@@ -11,6 +11,7 @@ from solo_tool import SoloTool, handlers
from solo_tool.session_manager import SessionManager
from solo_tool.midi_controller_actition import ActitionController
from solo_tool.recorder import Recorder
+from solo_tool.metronome import Metronome
from recording import recordingControls
@@ -34,6 +35,7 @@ sessions = {}
sessionManager = None
midiPedal = ActitionController()
recorder = None
+metronome = None
def makeKeyboardHandler(st: SoloTool):
restartOrPrevious = handlers.restartOrPreviousSong(st, 0.01)
@@ -87,10 +89,19 @@ def sessionPage(sessionId: str):
ui.button(icon='menu', on_click=lambda: songDrawer.toggle()).props('flat dense round color=white')
ui.label().bind_text_from(st, 'song', lambda index: fileName(st.songs[index]) if index is not None else "Select a song").classes('text-lg')
with ui.row().classes('items-center justify-start'):
- ui.button(icon='home', on_click=lambda: ui.navigate.to("/")).props('flat dense round color=white')
+ buttonSize = "20px"
+ bpm = ui.number(value=90, precision=0, min=10, max=300)
+ def toggleMetronome():
+ if metronome.running: metronome.stop()
+ else: metronome.start(bpm.value, 600)
+ ui.button(on_click=toggleMetronome) \
+ .props(f'flat dense round color=white size={buttonSize}') \
+ .bind_icon_from(metronome, "running", lambda running: "timer_off" if running else "timer")
+
+ ui.button(icon='home', on_click=lambda: ui.navigate.to("/")).props(f'flat dense round color=white size={buttonSize}')
def save(): sessionManager.saveSession(st, sessionId)
- ui.button(icon='save', on_click=save).props('flat dense round color=white')
- ui.button(icon='fullscreen', on_click=fullscreen.toggle).props('flat dense round color=white')
+ ui.button(icon='save', on_click=save).props(f'flat dense round color=white size={buttonSize}')
+ ui.button(icon='fullscreen', on_click=fullscreen.toggle).props(f'flat dense round color=white size={buttonSize}')
# Key points list
with ui.right_drawer(top_corner=True, bottom_corner=True).props('width=120 behavior=desktop'):
@@ -148,9 +159,10 @@ def landingPage():
ui.button(id, on_click=partial(ui.navigate.to, f"/{id}"))
def start(port, refresh, reload, sessionPath, bufferSize, samplingRate):
- global sessionManager, recorder
+ global sessionManager, recorder, metronome
sessionManager = SessionManager(sessionPath)
recorder = Recorder(bufferSize, samplingRate)
+ metronome = Metronome(bufferSize, samplingRate)
for id in sessionManager.getSessions():
songTool = sessionManager.loadSession(id)