we have audio rendering working! :D

This commit is contained in:
2026-01-13 20:06:28 +11:00
parent 0beb40d701
commit f8f41212f3
7 changed files with 102 additions and 6 deletions

4
requirements.txt Normal file
View File

@@ -0,0 +1,4 @@
pymp3
textual
textual-slider
numpy

BIN
src/cool sample 2.mp3 Normal file

Binary file not shown.

BIN
src/cool sample.mp3 Normal file

Binary file not shown.

View File

@@ -6,6 +6,10 @@ from ui.widgets.timeline import Timeline
class AppUI(App):
def __init__(self):
super().__init__()
self.zoom_level = 0.1
def compose(self) -> ComposeResult:
yield Sidebar()
yield Timeline()

View File

@@ -0,0 +1,67 @@
import librosa
import pyloudnorm as pyln
import math
from textual.containers import Vertical
from textual.widgets import Sparkline
from ui.widgets.chunk_types.chunk import Chunk
class AudioChunk(Chunk):
DEFAULT_CSS = """
AudioChunk {
Sparkline {
margin: 1;
}
}
"""
def __init__(self, file_path: str, chunk_name: str = "Sample"):
super().__init__(chunk_name)
self.file_path = file_path
self.audio, self.sample_rate = librosa.load(self.file_path, sr=None, mono=False)
self.num_channels = None
if len(self.audio.shape) == 1:
self.num_samples = self.audio.shape[0]
self.num_channels = 1
else:
self.num_samples = self.audio.shape[1]
self.num_channels = self.audio.shape[0]
self.notify(str(self.num_samples))
self.meter = pyln.Meter(self.sample_rate)
self.loudness_values = []
self.styles.width = (self.num_samples / self.sample_rate) / self.app.zoom_level
def compose(self) -> ComposeResult:
# account for multi-track audio and mono audio
if self.num_channels > 1:
# loop over each channel in the audio and display it seperately
for channel in range(self.num_channels):
samples = []
for sample in range(0, self.num_samples, int(self.sample_rate*0.1)):
samples.append(self.audio[channel, sample])
yield Sparkline(data=samples)
else:
# just display the one channel
samples = []
for sample in range(0, self.num_samples, int(self.sample_rate*0.1)):
samples.append(self.audio[sample])
yield Sparkline(data=samples)

View File

@@ -0,0 +1,19 @@
from textual.containers import Container
class Chunk(Container):
"""
base class for creating draggable views in the timeline for MIDI, Audio, etc...
"""
DEFAULT_CSS = """
Chunk {
border: panel $secondary;
background: $surface-darken-1;
}
"""
def __init__(self, chunk_name: str = "Chunk"):
super().__init__()
self.chunk_name = chunk_name
self.border_title = chunk_name

View File

@@ -1,6 +1,9 @@
from textual.containers import VerticalScroll, Horizontal
from textual.containers import Vertical, VerticalScroll, Horizontal, VerticalGroup
from textual.widgets import Sparkline
from textual.app import ComposeResult
from ui.widgets.chunk_types.audio import AudioChunk
class TimelineRow(Horizontal):
DEFAULT_CSS = """
@@ -10,9 +13,6 @@ class TimelineRow(Horizontal):
margin-bottom: 1;
}
"""
def compose(self) -> ComposeResult:
yield from ()
class Timeline(VerticalScroll):
DEFAULT_CSS = """
@@ -23,5 +23,7 @@ class Timeline(VerticalScroll):
"""
def compose(self) -> ComposeResult:
yield TimelineRow()
yield TimelineRow()
with TimelineRow():
yield AudioChunk("cool sample.mp3")
with TimelineRow():
yield AudioChunk("cool sample 2.mp3")