making channel controls interactable
This commit is contained in:
@@ -4,3 +4,5 @@ textual-slider
|
|||||||
textual-plot
|
textual-plot
|
||||||
numpy
|
numpy
|
||||||
msgpack-numpy
|
msgpack-numpy
|
||||||
|
pedalboard
|
||||||
|
sounddevice
|
||||||
@@ -1,3 +1,8 @@
|
|||||||
App {
|
App {
|
||||||
layers: top bottom;
|
layers: top bottom;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#top-menu {
|
||||||
|
dock: top;
|
||||||
|
layout: horizontal;
|
||||||
|
}
|
||||||
12
src/main.py
12
src/main.py
@@ -5,16 +5,14 @@ import librosa
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
print("Loading project...")
|
print("Loading project...")
|
||||||
test_project = Project(channels=[
|
"""test_project = Project(channels=[
|
||||||
ProjectChannel(chunks=[
|
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=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"),
|
], name="drums"),
|
||||||
ProjectChannel(chunks=[
|
])
|
||||||
AudioChannelChunk(*librosa.load("piano chords - Bmin 120BPM.wav", mono=False), name="piano chords - Bmin 120BPM.wav")
|
#test_project.write_to_file("test_project.tdp")"""
|
||||||
], name="piano")
|
test_project = Project.from_file("test_project.tdp")
|
||||||
])#.from_file("test_project.tdp")
|
|
||||||
|
|
||||||
# start the ui
|
# start the ui
|
||||||
print("Starting UI...")
|
print("Starting UI...")
|
||||||
|
|||||||
13
src/song_player.py
Normal file
13
src/song_player.py
Normal file
@@ -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
|
||||||
Binary file not shown.
@@ -1,22 +1,36 @@
|
|||||||
from textual.app import App, ComposeResult
|
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.sidebar import Sidebar
|
||||||
from ui.widgets.timeline import Timeline
|
from ui.widgets.timeline import Timeline
|
||||||
from ui.widgets.project_settings import ProjectSettings
|
from ui.widgets.project_settings import ProjectSettings
|
||||||
|
|
||||||
|
from song_player import SongPlayer
|
||||||
|
|
||||||
|
|
||||||
class AppUI(App):
|
class AppUI(App):
|
||||||
CSS_PATH = "../assets/style.tcss"
|
CSS_PATH = "../assets/style.tcss"
|
||||||
|
|
||||||
|
theme = "tokyo-night"
|
||||||
|
#ENABLE_COMMAND_PALETTE = False
|
||||||
|
|
||||||
def __init__(self, project):
|
def __init__(self, project):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.zoom_level = 0.05
|
self.zoom_level = 0.05
|
||||||
self.last_zoom_level = self.zoom_level
|
self.last_zoom_level = self.zoom_level
|
||||||
self.project = project
|
self.project = project
|
||||||
|
|
||||||
|
self.song_player = SongPlayer(self)
|
||||||
|
|
||||||
|
def on_mount(self):
|
||||||
|
self.song_player.play_song(self.app.project)
|
||||||
|
|
||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
|
with Tabs(id="top-menu"):
|
||||||
|
yield Tab("File")
|
||||||
|
yield Tab("Edit")
|
||||||
|
yield Tab("View")
|
||||||
|
yield Tab("Help")
|
||||||
yield Sidebar()
|
yield Sidebar()
|
||||||
yield Timeline()
|
yield Timeline()
|
||||||
yield ProjectSettings()
|
yield ProjectSettings()
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ from textual.widgets import Checkbox, Input, Static
|
|||||||
|
|
||||||
from textual_slider import Slider
|
from textual_slider import Slider
|
||||||
|
|
||||||
|
from textual import on, events
|
||||||
|
|
||||||
|
|
||||||
class Channel(VerticalGroup):
|
class Channel(VerticalGroup):
|
||||||
DEFAULT_CSS = """
|
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__()
|
super().__init__()
|
||||||
|
self.channel_index = channel_index
|
||||||
self.channel_name = channel_name
|
self.channel_name = channel_name
|
||||||
self.muted = muted
|
self.muted = muted
|
||||||
self.solo = solo
|
self.solo = solo
|
||||||
self.pan = pan
|
self.pan = pan
|
||||||
self.volume = volume
|
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:
|
def compose(self) -> ComposeResult:
|
||||||
yield Input(placeholder="Channel Name", compact=True, id="name", value=self.channel_name)
|
yield Input(placeholder="Channel Name", compact=True, id="name", value=self.channel_name)
|
||||||
|
|
||||||
with Horizontal(classes="channel-slider"):
|
with Horizontal(classes="channel-slider"):
|
||||||
yield Static("Pan (center):")
|
yield Static("Pan (center):", id="pan-text")
|
||||||
yield Slider(-100, 100, step=1, value=self.pan)
|
yield Slider(-100, 100, step=1, value=self.pan, id="pan")
|
||||||
with Horizontal(classes="channel-slider"):
|
with Horizontal(classes="channel-slider"):
|
||||||
yield Static("Volume (+ 0Db):")
|
yield Static("Volume (0Db):", id="volume-text")
|
||||||
yield Slider(-15, 15, step=0.1, value=self.volume)
|
yield Slider(-15, 15, step=0.1, value=self.volume, id="volume")
|
||||||
|
|
||||||
with Horizontal(id="channel-buttons"):
|
with Horizontal(id="channel-buttons"):
|
||||||
yield Checkbox("Mute", compact=True, id="mute", tooltip="Mute this track", value=self.muted)
|
yield Checkbox("Mute", compact=True, id="mute", tooltip="Mute this track", value=self.muted)
|
||||||
|
|||||||
@@ -10,9 +10,10 @@ class Sidebar(Vertical):
|
|||||||
Sidebar {
|
Sidebar {
|
||||||
dock: left;
|
dock: left;
|
||||||
background: $surface;
|
background: $surface;
|
||||||
width: 40;
|
width: 43;
|
||||||
border-right: tall $surface-lighten-1;
|
border-right: tall $surface-lighten-1;
|
||||||
padding: 1;
|
padding: 1;
|
||||||
|
margin-top: 1;
|
||||||
|
|
||||||
#add-channel {
|
#add-channel {
|
||||||
min-width: 100%;
|
min-width: 100%;
|
||||||
@@ -23,8 +24,9 @@ class Sidebar(Vertical):
|
|||||||
|
|
||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
with VerticalScroll(id="channels"):
|
with VerticalScroll(id="channels"):
|
||||||
for channel in self.app.project.channels:
|
for i, channel in enumerate(self.app.project.channels):
|
||||||
yield Channel(
|
yield Channel(
|
||||||
|
i,
|
||||||
channel.name,
|
channel.name,
|
||||||
channel.mute,
|
channel.mute,
|
||||||
channel.solo,
|
channel.solo,
|
||||||
|
|||||||
@@ -95,8 +95,6 @@ class Timeline(Vertical):
|
|||||||
|
|
||||||
|
|
||||||
for channel in self.app.project.channels:
|
for channel in self.app.project.channels:
|
||||||
self.notify(str(channel))
|
|
||||||
|
|
||||||
with TimelineRow():
|
with TimelineRow():
|
||||||
for chunk in channel.chunks:
|
for chunk in channel.chunks:
|
||||||
if chunk.chunk_type == ChunkType.CHUNK:
|
if chunk.chunk_type == ChunkType.CHUNK:
|
||||||
|
|||||||
1
src/ui/widgets/top_menu.py
Normal file
1
src/ui/widgets/top_menu.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from textual.widgets import Tabs, Tab
|
||||||
Reference in New Issue
Block a user