auto generating bar lines, still buggy tho :/
This commit is contained in:
BIN
src/120 bpm amen break.mp3
Normal file
BIN
src/120 bpm amen break.mp3
Normal file
Binary file not shown.
3
src/assets/style.tcss
Normal file
3
src/assets/style.tcss
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
App {
|
||||||
|
layers: bottom top;
|
||||||
|
}
|
||||||
15
src/main.py
15
src/main.py
@@ -1,19 +1,12 @@
|
|||||||
from ui.app import AppUI
|
from ui.app import AppUI
|
||||||
from project import Project, ProjectChannel, ChannelChunk, AudioChannelChunk
|
from project import Project
|
||||||
from ui.widgets.chunk_types.audio import AudioChunk
|
|
||||||
import librosa
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
test_project = Project(
|
print("Loading project...")
|
||||||
[
|
test_project = Project.from_file("test_project.tdp")
|
||||||
ProjectChannel("my channel", chunks=[
|
|
||||||
ChannelChunk(name="hi"),
|
|
||||||
AudioChannelChunk(librosa.load("cool sample.mp3", sr=None, mono=False))
|
|
||||||
])
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
# start the ui
|
# start the ui
|
||||||
|
print("Starting UI...")
|
||||||
app = AppUI(test_project)
|
app = AppUI(test_project)
|
||||||
app.run()
|
app.run()
|
||||||
@@ -38,15 +38,17 @@ class ChannelChunk:
|
|||||||
}
|
}
|
||||||
|
|
||||||
class AudioChannelChunk(ChannelChunk):
|
class AudioChannelChunk(ChannelChunk):
|
||||||
def __init__(self, audio_data: np.ndarray, position: float = 0.0, name: str = "Sample"):
|
def __init__(self, audio_data: np.ndarray, sample_rate: int, position: float = 0.0, name: str = "Sample"):
|
||||||
super().__init__(position, name, chunk_type=ChunkType.AUDIO)
|
super().__init__(position, name, chunk_type=ChunkType.AUDIO)
|
||||||
self.audio_data = audio_data
|
self.audio_data = audio_data
|
||||||
|
self.sample_rate = sample_rate
|
||||||
|
|
||||||
def from_json(json: dict) -> ChannelChunk:
|
def from_json(json: dict) -> ChannelChunk:
|
||||||
return AudioChannelChunk(
|
return AudioChannelChunk(
|
||||||
name = json["name"],
|
name = json["name"],
|
||||||
position = json["position"],
|
position = json["position"],
|
||||||
audio_data = json["audio_data"]
|
audio_data = json["audio_data"],
|
||||||
|
sample_rate = json["sample_rate"]
|
||||||
)
|
)
|
||||||
|
|
||||||
def to_json(self):
|
def to_json(self):
|
||||||
@@ -54,7 +56,8 @@ class AudioChannelChunk(ChannelChunk):
|
|||||||
"type": self.chunk_type.value,
|
"type": self.chunk_type.value,
|
||||||
"name": self.name,
|
"name": self.name,
|
||||||
"position": self.position,
|
"position": self.position,
|
||||||
"audio_data": self.audio_data
|
"audio_data": self.audio_data,
|
||||||
|
"sample_rate": self.sample_rate
|
||||||
}
|
}
|
||||||
|
|
||||||
chunk_type_associations = {
|
chunk_type_associations = {
|
||||||
|
|||||||
BIN
src/test_project.tdp
Normal file
BIN
src/test_project.tdp
Normal file
Binary file not shown.
@@ -7,6 +7,8 @@ from ui.widgets.project_settings import ProjectSettings
|
|||||||
|
|
||||||
|
|
||||||
class AppUI(App):
|
class AppUI(App):
|
||||||
|
CSS_PATH = "../assets/style.tcss"
|
||||||
|
|
||||||
def __init__(self, project):
|
def __init__(self, project):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.zoom_level = 0.05
|
self.zoom_level = 0.05
|
||||||
|
|||||||
@@ -25,11 +25,11 @@ class AudioChunk(Chunk):
|
|||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, file_path: str, chunk_name: str = "Sample"):
|
def __init__(self, audio_data: np.ndarray, sample_rate: int, chunk_name: str = "Sample"):
|
||||||
super().__init__(chunk_name)
|
super().__init__(chunk_name)
|
||||||
|
|
||||||
self.file_path = file_path
|
self.audio = audio_data
|
||||||
self.audio, self.sample_rate = librosa.load(self.file_path, sr=None, mono=False)
|
self.sample_rate = sample_rate
|
||||||
|
|
||||||
self.num_channels = None
|
self.num_channels = None
|
||||||
if len(self.audio.shape) == 1:
|
if len(self.audio.shape) == 1:
|
||||||
@@ -42,7 +42,7 @@ class AudioChunk(Chunk):
|
|||||||
self.meter = pyln.Meter(self.sample_rate)
|
self.meter = pyln.Meter(self.sample_rate)
|
||||||
self.loudness_values = []
|
self.loudness_values = []
|
||||||
|
|
||||||
self.styles.width = (self.num_samples / self.sample_rate) / self.app.zoom_level
|
self.styles.width = int((self.num_samples / self.sample_rate) / self.app.zoom_level)
|
||||||
|
|
||||||
def on_mount(self):
|
def on_mount(self):
|
||||||
for plot in self.query(PlotWidget):
|
for plot in self.query(PlotWidget):
|
||||||
|
|||||||
14
src/ui/widgets/play_head.py
Normal file
14
src/ui/widgets/play_head.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
from textual.widgets import Rule
|
||||||
|
|
||||||
|
|
||||||
|
class PlayHead(Rule):
|
||||||
|
DEFAULT_CSS = """
|
||||||
|
PlayHead.-vertical {
|
||||||
|
layer: top;
|
||||||
|
margin: 0;
|
||||||
|
color: $accent;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(orientation="vertical")
|
||||||
@@ -23,6 +23,12 @@ class Sidebar(Vertical):
|
|||||||
|
|
||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
with VerticalScroll(id="channels"):
|
with VerticalScroll(id="channels"):
|
||||||
yield Channel()
|
for channel in self.app.project.channels:
|
||||||
yield Channel()
|
yield Channel(
|
||||||
|
channel.name,
|
||||||
|
channel.mute,
|
||||||
|
channel.solo,
|
||||||
|
channel.pan,
|
||||||
|
channel.volume
|
||||||
|
)
|
||||||
yield Button("+ New Channel", variant="success", id="add-channel")
|
yield Button("+ New Channel", variant="success", id="add-channel")
|
||||||
@@ -1,29 +1,84 @@
|
|||||||
from textual.containers import Vertical, VerticalScroll, Horizontal, VerticalGroup
|
from textual.containers import Vertical, VerticalScroll, Horizontal, VerticalGroup, HorizontalGroup
|
||||||
from textual.widgets import Sparkline
|
from textual.widgets import Rule
|
||||||
from textual.app import ComposeResult
|
from textual.app import ComposeResult
|
||||||
|
|
||||||
from ui.widgets.chunk_types.audio import AudioChunk
|
from ui.widgets.chunk_types.audio import AudioChunk, Chunk
|
||||||
|
from ui.widgets.play_head import PlayHead
|
||||||
|
from project import ChunkType
|
||||||
|
|
||||||
|
|
||||||
class TimelineRow(Horizontal):
|
class TimelineRow(Horizontal):
|
||||||
DEFAULT_CSS = """
|
DEFAULT_CSS = """
|
||||||
TimelineRow {
|
TimelineRow {
|
||||||
background: $surface-lighten-1;
|
background: $surface-lighten-1;
|
||||||
height: 8;
|
max-height: 8;
|
||||||
margin-bottom: 1;
|
margin-bottom: 1;
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Timeline(VerticalScroll):
|
class Timeline(Vertical):
|
||||||
DEFAULT_CSS = """
|
DEFAULT_CSS = """
|
||||||
Timeline {
|
Timeline {
|
||||||
padding: 1 0;
|
|
||||||
hatch: "-" $surface-lighten-1;
|
#rows {
|
||||||
|
hatch: "-" $surface-lighten-1;
|
||||||
|
padding: 1 0;
|
||||||
|
|
||||||
|
.beat-line {
|
||||||
|
color: $surface-lighten-1;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar-line {
|
||||||
|
color: $surface-lighten-2;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.beat-line, .bar-line {
|
||||||
|
dock: left;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayHead {
|
||||||
|
layer: top;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self.bar_offset = self.app.project.bpm / 8 * (0.0333 / self.app.zoom_level)
|
||||||
|
|
||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
with TimelineRow():
|
|
||||||
yield AudioChunk("cool sample.mp3")
|
|
||||||
with TimelineRow():
|
with VerticalScroll(id="rows"):
|
||||||
yield AudioChunk("cool sample 2.mp3")
|
|
||||||
|
for i in range(1, 17):
|
||||||
|
bar = None
|
||||||
|
if (i) % 4 == 0:
|
||||||
|
bar = Rule.vertical(classes="bar-line", line_style="double")
|
||||||
|
else:
|
||||||
|
bar = Rule.vertical(classes="beat-line")
|
||||||
|
|
||||||
|
bar.offset = (self.bar_offset * i, 0)
|
||||||
|
|
||||||
|
yield bar
|
||||||
|
|
||||||
|
for channel in self.app.project.channels:
|
||||||
|
|
||||||
|
|
||||||
|
with TimelineRow():
|
||||||
|
for chunk in channel.chunks:
|
||||||
|
if chunk.chunk_type == ChunkType.CHUNK:
|
||||||
|
yield Chunk(chunk_name=chunk.name)
|
||||||
|
elif chunk.chunk_type == ChunkType.AUDIO:
|
||||||
|
yield AudioChunk(chunk.audio_data, chunk.sample_rate, chunk.name)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#yield PlayHead()
|
||||||
Reference in New Issue
Block a user