diff options
| -rw-r--r-- | solo-tool-project/pyproject.toml | 3 | ||||
| -rw-r--r-- | solo-tool-project/src/solo_tool/external_volume.py | 31 | ||||
| -rw-r--r-- | web-project/src/solo_tool_web.py | 41 |
3 files changed, 65 insertions, 10 deletions
diff --git a/solo-tool-project/pyproject.toml b/solo-tool-project/pyproject.toml index 40d09d8..2b70459 100644 --- a/solo-tool-project/pyproject.toml +++ b/solo-tool-project/pyproject.toml @@ -16,7 +16,8 @@ dependencies = [ "mido", "python-mpv", "pyaudio", - "pydub-ng" + "pydub-ng", + "python-osc" ] [project.optional-dependencies] diff --git a/solo-tool-project/src/solo_tool/external_volume.py b/solo-tool-project/src/solo_tool/external_volume.py new file mode 100644 index 0000000..425351a --- /dev/null +++ b/solo-tool-project/src/solo_tool/external_volume.py @@ -0,0 +1,31 @@ +from pythonosc import udp_client + +class VolumeControllerXR18(): + def __init__(self, ip: str): + port = 10024 + self._client = udp_client.SimpleUDPClient(ip, port, timeout=1) + self._headphoneVolume = 0.0 + self._speakerVolume = 0.0 + + def __del__(self): + self._client.close() + + @property + def headphoneVolume(self) -> float: + return self._headphoneVolume + + @headphoneVolume.setter + def headphoneVolume(self, new: float) -> None: + if new is not None: + self._headphoneVolume = min(max(new, 0.0), 1.0) + self._client.send_message("/config/solo/level", self._headphoneVolume) + + @property + def speakerVolume(self) -> float: + return self._speakerVolume + + @speakerVolume.setter + def speakerVolume(self, new: float) -> None: + if new is not None: + self._speakerVolume = min(max(new, 0.0), 1.0) + self._client.send_message("/lr/mix/fader", self._speakerVolume) diff --git a/web-project/src/solo_tool_web.py b/web-project/src/solo_tool_web.py index 4a3de3a..0315816 100644 --- a/web-project/src/solo_tool_web.py +++ b/web-project/src/solo_tool_web.py @@ -12,6 +12,7 @@ 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 solo_tool.external_volume import VolumeControllerXR18 from recording import recordingControls @@ -36,6 +37,7 @@ sessionManager = None midiPedal = ActitionController() recorder = None metronome = None +volumeController = None def makeKeyboardHandler(st: SoloTool): restartOrPrevious = handlers.restartOrPreviousSong(st, 0.01) @@ -87,10 +89,18 @@ def sessionPage(sessionId: str): with ui.header().classes('items-center justify-between'): with ui.row().classes('items-center justify-start'): 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') + def songName(index): + if index is None: + return "Select a song" + else: + names = fileName(st.songs[index]).split(" - ") + return names[1] if len(names) > 1 else names[0] + ui.label().bind_text_from(st, 'song', songName).classes('text-lg') + with ui.row().classes('items-center justify-start'): - buttonSize = "20px" - bpm = ui.number(value=90, precision=0, min=10, max=300) + buttonSize = "18px" + + bpm = ui.knob(value=90, min=10, max=300, step=1, show_value=True, color='white')#, track_color='white') def toggleMetronome(): if metronome.running: metronome.stop() else: metronome.start(bpm.value, 600) @@ -98,10 +108,15 @@ def sessionPage(sessionId: str): .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}') + 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(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}') + 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'): @@ -136,8 +151,15 @@ def sessionPage(sessionId: str): # Volume slider with ui.row().classes('w-full justify-between no-wrap items-center'): - volumeLabels = ",".join([f"{v}:'{int(v*100)}%'" for v in [0.0, 0.25, 0.5, 0.75, 1.0, 1.25]]) - ui.slider(min=0, max=1.25, step=0.01).bind_value(st, 'volume').props(f':marker-labels="{{{volumeLabels}}}"').classes('q-px-md') + iconSize = "20px" + ui.icon('music_note').props(f"size={iconSize}").classes('q-px-xs') + ui.slider(min=0, max=1.25, step=0.01).classes('q-px-xs').bind_value(st, 'volume') + + ui.icon('headphones').props(f"size={iconSize}").classes('q-px-xs') + ui.slider(value=0.0, min=0, max=1.0, step=0.01).classes('q-px-xs').bind_value(volumeController, 'headphoneVolume') + + ui.icon('speaker').props(f"size={iconSize}").classes('q-px-xs') + ui.slider(value=0.0, min=0, max=1.0, step=0.01).classes('q-px-xs').bind_value(volumeController, 'speakerVolume') # Playback rate and recording controls with ui.row().classes('w-full justify-between no-wrap items-center'): @@ -159,10 +181,11 @@ def landingPage(): ui.button(id, on_click=partial(ui.navigate.to, f"/{id}")) def start(port, refresh, reload, sessionPath, bufferSize, samplingRate): - global sessionManager, recorder, metronome + global sessionManager, recorder, metronome, volumeController sessionManager = SessionManager(sessionPath) recorder = Recorder(bufferSize, samplingRate) metronome = Metronome(bufferSize, samplingRate) + volumeController = VolumeControllerXR18("XR18-7EE3D3.0xf7.com") for id in sessionManager.getSessions(): songTool = sessionManager.loadSession(id) |
