From 7ca336c321a67c42f80568dff0e653098f169e55 Mon Sep 17 00:00:00 2001 From: SpookyDervish Date: Wed, 14 Jan 2026 14:43:57 +1100 Subject: [PATCH] making channel controls interactable --- requirements.txt | 4 +++- src/assets/style.tcss | 5 +++++ src/audio.py | 0 src/main.py | 12 +++++------- src/song_player.py | 13 +++++++++++++ src/test_project.tdp | Bin 353011 -> 353011 bytes src/ui/app.py | 16 +++++++++++++++- src/ui/widgets/channel.py | 28 +++++++++++++++++++++++----- src/ui/widgets/sidebar.py | 6 ++++-- src/ui/widgets/timeline.py | 2 -- src/ui/widgets/top_menu.py | 1 + 11 files changed, 69 insertions(+), 18 deletions(-) delete mode 100644 src/audio.py create mode 100644 src/song_player.py create mode 100644 src/ui/widgets/top_menu.py diff --git a/requirements.txt b/requirements.txt index d62e70c..c03e97e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,4 +3,6 @@ textual textual-slider textual-plot numpy -msgpack-numpy \ No newline at end of file +msgpack-numpy +pedalboard +sounddevice \ No newline at end of file diff --git a/src/assets/style.tcss b/src/assets/style.tcss index cc9d1de..4b15a66 100644 --- a/src/assets/style.tcss +++ b/src/assets/style.tcss @@ -1,3 +1,8 @@ App { layers: top bottom; +} + +#top-menu { + dock: top; + layout: horizontal; } \ No newline at end of file diff --git a/src/audio.py b/src/audio.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/main.py b/src/main.py index b054c6e..990dea1 100644 --- a/src/main.py +++ b/src/main.py @@ -5,16 +5,14 @@ import librosa if __name__ == "__main__": print("Loading project...") - test_project = Project(channels=[ + """test_project = Project(channels=[ ProjectChannel(chunks=[ AudioChannelChunk(*librosa.load("120 bpm amen break.mp3", mono=False), position=0, name="120 bpm amen break.mp3"), - AudioChannelChunk(*librosa.load("120 bpm amen break.mp3", mono=False), position=1, name="120 bpm amen break.mp3"), - AudioChannelChunk(*librosa.load("120 bpm amen break.mp3", mono=False), position=2, name="120 bpm amen break.mp3") ], name="drums"), - ProjectChannel(chunks=[ - AudioChannelChunk(*librosa.load("piano chords - Bmin 120BPM.wav", mono=False), name="piano chords - Bmin 120BPM.wav") - ], name="piano") - ])#.from_file("test_project.tdp") + ]) + #test_project.write_to_file("test_project.tdp")""" + test_project = Project.from_file("test_project.tdp") + # start the ui print("Starting UI...") diff --git a/src/song_player.py b/src/song_player.py new file mode 100644 index 0000000..43b0cb8 --- /dev/null +++ b/src/song_player.py @@ -0,0 +1,13 @@ +from project import Project +import pedalboard +import sounddevice as sd +import numpy as np +from textual.app import App + + +class SongPlayer: + def __init__(self, app: App): + self.app = app + + def play_song(self, project: Project): + pass \ No newline at end of file diff --git a/src/test_project.tdp b/src/test_project.tdp index 1b5fe57b8137e5cefecf2bab001162ad014dffda..4bed220f4f29c65582ad3dd8e86854360a9cba2a 100644 GIT binary patch delta 65 zcmV-H0KWh8#TE0#6_7;%c#%d!Aht0wFd$-ZZ6IN7Wo{s1a%Ev_E^Tl#sBmv{X>@6C XZh@(VsR4zl0)?pqg{cI!sRc@6V7?j$ delta 65 zcmex-RP^&v(Fv}MA11mvE38Y*P0dqCDoRbvR>;ZESy7N*oLQ2YpLd!80vcDgu4HUo R$<(@%xpgJW)|ISYNdSH;8Cd`T diff --git a/src/ui/app.py b/src/ui/app.py index 7127e0c..37acaba 100644 --- a/src/ui/app.py +++ b/src/ui/app.py @@ -1,22 +1,36 @@ from textual.app import App, ComposeResult -from textual.widgets import Footer +from textual.widgets import Footer, Tab, Tabs, Header from ui.widgets.sidebar import Sidebar from ui.widgets.timeline import Timeline from ui.widgets.project_settings import ProjectSettings +from song_player import SongPlayer + class AppUI(App): CSS_PATH = "../assets/style.tcss" + theme = "tokyo-night" + #ENABLE_COMMAND_PALETTE = False + def __init__(self, project): super().__init__() self.zoom_level = 0.05 self.last_zoom_level = self.zoom_level self.project = project + self.song_player = SongPlayer(self) + + def on_mount(self): + self.song_player.play_song(self.app.project) def compose(self) -> ComposeResult: + with Tabs(id="top-menu"): + yield Tab("File") + yield Tab("Edit") + yield Tab("View") + yield Tab("Help") yield Sidebar() yield Timeline() yield ProjectSettings() diff --git a/src/ui/widgets/channel.py b/src/ui/widgets/channel.py index ef75094..8a630e7 100644 --- a/src/ui/widgets/channel.py +++ b/src/ui/widgets/channel.py @@ -4,6 +4,8 @@ from textual.widgets import Checkbox, Input, Static from textual_slider import Slider +from textual import on, events + class Channel(VerticalGroup): DEFAULT_CSS = """ @@ -44,23 +46,39 @@ class Channel(VerticalGroup): } """ - def __init__(self, channel_name: str = "", muted: bool = False, solo: bool = False, pan: float = 0, volume: float = 0): + def __init__(self, channel_index: int, channel_name: str = "", muted: bool = False, solo: bool = False, pan: float = 0, volume: float = 0): super().__init__() + self.channel_index = channel_index self.channel_name = channel_name self.muted = muted self.solo = solo self.pan = pan self.volume = volume + + def on_slider_changed(self, event: Slider.Changed): + if event.slider.id == "volume": + self.volume = round(event.value, 2) + self.query_one("#volume-text").update(f"Volume ({self.volume}Db):") + elif event.slider.id == "pan": + self.pan = round(event.value) + pan_text = self.query_one("#pan-text") + + if self.pan == 0: + pan_text.update("Pan (center):") + elif self.pan < 0: + pan_text.update(f"Pan ({-self.pan}% left):") + elif self.pan > 0: + pan_text.update(f"Pan ({self.pan}% right):") def compose(self) -> ComposeResult: yield Input(placeholder="Channel Name", compact=True, id="name", value=self.channel_name) with Horizontal(classes="channel-slider"): - yield Static("Pan (center):") - yield Slider(-100, 100, step=1, value=self.pan) + yield Static("Pan (center):", id="pan-text") + yield Slider(-100, 100, step=1, value=self.pan, id="pan") with Horizontal(classes="channel-slider"): - yield Static("Volume (+ 0Db):") - yield Slider(-15, 15, step=0.1, value=self.volume) + yield Static("Volume (0Db):", id="volume-text") + yield Slider(-15, 15, step=0.1, value=self.volume, id="volume") with Horizontal(id="channel-buttons"): yield Checkbox("Mute", compact=True, id="mute", tooltip="Mute this track", value=self.muted) diff --git a/src/ui/widgets/sidebar.py b/src/ui/widgets/sidebar.py index 00c704e..3256d3c 100644 --- a/src/ui/widgets/sidebar.py +++ b/src/ui/widgets/sidebar.py @@ -10,9 +10,10 @@ class Sidebar(Vertical): Sidebar { dock: left; background: $surface; - width: 40; + width: 43; border-right: tall $surface-lighten-1; padding: 1; + margin-top: 1; #add-channel { min-width: 100%; @@ -23,8 +24,9 @@ class Sidebar(Vertical): def compose(self) -> ComposeResult: with VerticalScroll(id="channels"): - for channel in self.app.project.channels: + for i, channel in enumerate(self.app.project.channels): yield Channel( + i, channel.name, channel.mute, channel.solo, diff --git a/src/ui/widgets/timeline.py b/src/ui/widgets/timeline.py index db42d29..fe23c73 100644 --- a/src/ui/widgets/timeline.py +++ b/src/ui/widgets/timeline.py @@ -95,8 +95,6 @@ class Timeline(Vertical): for channel in self.app.project.channels: - self.notify(str(channel)) - with TimelineRow(): for chunk in channel.chunks: if chunk.chunk_type == ChunkType.CHUNK: diff --git a/src/ui/widgets/top_menu.py b/src/ui/widgets/top_menu.py new file mode 100644 index 0000000..38216b0 --- /dev/null +++ b/src/ui/widgets/top_menu.py @@ -0,0 +1 @@ +from textual.widgets import Tabs, Tab