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 1b5fe57..4bed220 100644 Binary files a/src/test_project.tdp and b/src/test_project.tdp differ 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