diff --git a/src/main.py b/src/main.py index c7a4c61..c4b4fd1 100644 --- a/src/main.py +++ b/src/main.py @@ -8,23 +8,23 @@ import mp3 if __name__ == "__main__": - print("Loading project...") - test_project = Project(song_length=64,bpm=120) + """print("Loading project...") + test_project = Project(song_length=64,bpm=120,sample_rate=24000) screams = ProjectChannel( test_project, - name="screams of the damned", + name="Drums", ) sample = AudioChannelChunk( screams, - *librosa.load("cool sample 2.mp3", sr=None, mono=False), - name="cool sample 2.mp3" + *librosa.load("120 bpm amen break.mp3", sr=None, mono=False), + name="120 bpm amen break.mp3" ) screams.chunks.append(sample) test_project.channels.append(screams) - test_project.write_to_file("test_project.tdp") + test_project.write_to_file("test_project.tdp")""" #test_project = Project.from_file("test_project.tdp") # start the ui diff --git a/src/song_player.py b/src/song_player.py index 59af3f5..9cbf2fd 100644 --- a/src/song_player.py +++ b/src/song_player.py @@ -1,6 +1,7 @@ import sounddevice as sd from project import Project from ui.widgets.play_head import PlayHead +from textual.timer import Timer class SongPlayer: @@ -13,6 +14,13 @@ class SongPlayer: self.timeline = timeline + # set up our timer for updating the ui playhead + self.timer = Timer( + timeline, + 0.1, + pause=True + ) + def play_callback(self, out, frames, time, status): if self.paused or self.audio is None: out.fill(0) @@ -57,6 +65,7 @@ class SongPlayer: def pause(self): self.paused = True self.stream.close() + self.timer.pause() play_btn = self.timeline.app.query_one("#play-button") play_btn.variant = "success" @@ -67,30 +76,30 @@ class SongPlayer: self.project = project self.audio = project.render() + self.timer.resume() + play_btn = self.timeline.app.query_one("#play-button") + play_btn.variant = "error" + play_btn.label = "⏸" + self.paused = False try: + + if not self.stream.closed: self.stream.close() self.stream = sd.OutputStream( - samplerate=project.sample_rate, channels=self.audio.shape[1] if self.audio.ndim > 1 else 1, callback=self.play_callback, - blocksize=256 + blocksize=256, + device=7 ) - self.paused = False - self.stream.start() - - play_btn = self.timeline.app.query_one("#play-button") - play_btn.variant = "error" - play_btn.label = "⏸" - return True except sd.PortAudioError as e: # woopsies - self.paused = True + self.pause() self.timeline.app.notify(f"Error: \"{e}\"\n\nSometimes errors can occur if the device you selected in your settings doesn't exist, or it's already in use.", title="Error while playing song", timeout=60, severity="error") return False \ No newline at end of file diff --git a/src/test_project.tdp b/src/test_project.tdp index 188de2d..1b36e63 100644 Binary files a/src/test_project.tdp and b/src/test_project.tdp differ diff --git a/src/ui/widgets/channel.py b/src/ui/widgets/channel.py index 292e84f..c976e5d 100644 --- a/src/ui/widgets/channel.py +++ b/src/ui/widgets/channel.py @@ -71,6 +71,8 @@ class Channel(VerticalGroup): if event.slider.id == "volume": self.volume = round(event.value, 2) self.query_one("#volume-text").update(f"Volume ({self.volume}Db):") + + self.project_channel.volume = self.volume elif event.slider.id == "pan": self.pan = round(event.value) pan_text = self.query_one("#pan-text") @@ -81,6 +83,8 @@ class Channel(VerticalGroup): pan_text.update(f"Pan ({-self.pan}% left):") elif self.pan > 0: pan_text.update(f"Pan ({self.pan}% right):") + + self.project_channel.pan = self.pan def compose(self) -> ComposeResult: yield Input(placeholder="Channel Name", compact=True, id="name", value=self.channel_name) diff --git a/src/ui/widgets/timeline.py b/src/ui/widgets/timeline.py index 9657a5e..90e6f40 100644 --- a/src/ui/widgets/timeline.py +++ b/src/ui/widgets/timeline.py @@ -78,6 +78,10 @@ class Timeline(Vertical): for row in self.query(TimelineRow): row.styles.width = self.bar_offset * self.app.project.song_length + @on(events.Timer) + async def playhead_timer_tick(self, event: events.Timer): + await self.song_player.update_visual_playhead() + @on(events.MouseScrollDown) async def mouse_scroll_down(self, event: events.MouseScrollDown): self.app.zoom_level += (self.app.scroll_sensitivity_x / 200)