Skip to content

Commit 0db33d5

Browse files
committed
Revert "Get rid of workspaces"
This reverts commit 7f16827.
1 parent 7f16827 commit 0db33d5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+3708
-209
lines changed

Cargo.lock

+885-151
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+6-57
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,9 @@
1-
[package]
2-
name = "tori"
3-
description = "The frictionless music player for the terminal"
4-
authors = ["Leonardo Riether <[email protected]>"]
5-
readme = "README.md"
6-
license = "GPL-3.0-or-later"
7-
repository = "https://github.com/LeoRiether/tori"
8-
homepage = "https://github.com/LeoRiether/tori"
9-
keywords = ["music", "player", "tui", "terminal"]
10-
exclude = ["assets", "docs"]
11-
version = "0.2.3"
12-
edition = "2021"
13-
build = "build.rs"
14-
15-
[package.metadata]
16-
depends = ["mpv", "pipewire"]
17-
optdepends = ["yt-dlp", "cava"]
1+
[workspace]
2+
resolver = "2"
3+
members = [
4+
"tori",
5+
"tori-player"
6+
]
187

198
# The profile that 'cargo dist' will build with
209
[profile.dist]
@@ -34,43 +23,3 @@ installers = ["shell", "powershell"]
3423
# Target platforms to build apps for (Rust target-triple syntax)
3524
targets = ["x86_64-apple-darwin", "x86_64-pc-windows-msvc", "aarch64-apple-darwin"]
3625

37-
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
38-
39-
[package.metadata.docs.rs]
40-
no-default-features = true # do not build with `clipboard` because that breaks the docs.rs build...
41-
42-
[features]
43-
default = ["clip", "mpv"]
44-
clip = ["clipboard"]
45-
mpv = ["mpv034", "mpv035", "libmpv-sys"]
46-
47-
[dependencies]
48-
tui = { version = "0.21", package = "ratatui" }
49-
crossterm = "0.26"
50-
51-
# clipboard is optional because docs.rs doesn't build the xcb="0.8" dependency
52-
# also because I couldn't make any other clipboard crate work
53-
clipboard = { version = "0.5.0", optional = true }
54-
55-
serde_json = "1.0.94"
56-
unicode-width = "0.1.10"
57-
dirs = "5.0.0"
58-
serde_yaml = "0.9.19"
59-
webbrowser = "0.8.8"
60-
serde = { version = "1.0.159", features = ["derive"] }
61-
once_cell = "1.17.1"
62-
argh = "0.1.10"
63-
lofty = "0.13.0"
64-
rand = "0.8.5"
65-
66-
log = "0.4.19"
67-
pretty_env_logger = "0.5.0"
68-
69-
# Player: mpv
70-
libmpv-sys = { version = "3.1.0", optional = true }
71-
mpv034 = { version = "2.0.1", package = "libmpv", optional = true } # Works with mpv <= v0.34
72-
mpv035 = { version = "2.0.2-fork.1", package = "libmpv-sirno", optional = true } # Works with mpv v0.35
73-
74-
[build-dependencies]
75-
winres = "0.1"
76-

tori-player/Cargo.toml

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
[package]
2+
name = "tori-player"
3+
description = "Audio player for tori"
4+
authors = ["Leonardo Riether <[email protected]>"]
5+
license = "GPL-3.0-or-later"
6+
repository = "https://github.com/LeoRiether/tori"
7+
homepage = "https://github.com/LeoRiether/tori"
8+
keywords = ["music", "player", "audio"]
9+
version = "0.1.0"
10+
edition = "2021"
11+
12+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
13+
14+
[dependencies]
15+
symphonia = { version = "0.5.3", features = ["all-codecs"] }
16+
cpal = { version = "0.15.2" }
17+
rb = { version = "0.4.1" }
18+
rubato = { version = "0.13.0" }
19+
arrayvec = { version = "0.7.2" }
20+
ac-ffmpeg = { version = "0.18.1" }
21+
crossbeam-channel = { version = "0.5.8" }
22+
log = "0.4.19"

tori-player/src/controller/mod.rs

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
use super::source;
2+
use crate::Result;
3+
4+
#[derive(Debug, Default)]
5+
pub struct Controller {}
6+
7+
impl Controller {
8+
pub fn play(&mut self, path: &str) -> Result<()> {
9+
source::start_player_thread(path);
10+
Ok(())
11+
}
12+
}

tori-player/src/lib.rs

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
pub mod controller;
2+
mod output;
3+
mod resampler;
4+
pub mod source;
5+
6+
use controller::Controller;
7+
8+
pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
9+
10+
pub struct Player {
11+
pub controller: Controller,
12+
}

tori-player/src/output.rs

+225
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
// Mostly copied from symphonia-play
2+
3+
// Copyright (c) 2019-2022 The Project Symphonia Developers.
4+
//
5+
// This Source Code Form is subject to the terms of the Mozilla Public
6+
// License, v. 2.0. If a copy of the MPL was not distributed with this
7+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
8+
9+
//! Platform-dependant Audio Outputs
10+
11+
use std::{result, time};
12+
13+
use super::resampler::Resampler;
14+
use symphonia::core::audio::{AudioBufferRef, RawSample, SampleBuffer, SignalSpec};
15+
use symphonia::core::conv::{ConvertibleSample, IntoSample};
16+
use symphonia::core::units::Duration;
17+
18+
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
19+
use rb::*;
20+
21+
use log::{error, info};
22+
23+
pub trait AudioOutput {
24+
fn write(&mut self, decoded: AudioBufferRef<'_>) -> Result<()>;
25+
fn flush(&mut self);
26+
}
27+
28+
#[allow(dead_code)]
29+
#[allow(clippy::enum_variant_names)]
30+
#[derive(Debug)]
31+
pub enum AudioOutputError {
32+
OpenStreamError,
33+
PlayStreamError,
34+
StreamClosedError,
35+
}
36+
37+
pub type Result<T> = result::Result<T, AudioOutputError>;
38+
39+
pub struct CpalAudioOutput;
40+
41+
trait AudioOutputSample:
42+
cpal::SizedSample + ConvertibleSample + IntoSample<f32> + RawSample + std::marker::Send + 'static
43+
{
44+
}
45+
46+
impl AudioOutputSample for f32 {}
47+
impl AudioOutputSample for i16 {}
48+
impl AudioOutputSample for u16 {}
49+
50+
impl CpalAudioOutput {
51+
pub fn try_open(spec: SignalSpec, duration: Duration) -> Result<Box<dyn AudioOutput>> {
52+
// Get default host.
53+
let host = cpal::default_host();
54+
55+
// Get the default audio output device.
56+
let device = match host.default_output_device() {
57+
Some(device) => device,
58+
_ => {
59+
error!("failed to get default audio output device");
60+
return Err(AudioOutputError::OpenStreamError);
61+
}
62+
};
63+
64+
let config = match device.default_output_config() {
65+
Ok(config) => config,
66+
Err(err) => {
67+
error!("failed to get default audio output device config: {}", err);
68+
return Err(AudioOutputError::OpenStreamError);
69+
}
70+
};
71+
72+
// Select proper playback routine based on sample format.
73+
match config.sample_format() {
74+
cpal::SampleFormat::F32 => {
75+
CpalAudioOutputImpl::<f32>::try_open(spec, duration, &device)
76+
}
77+
cpal::SampleFormat::I16 => {
78+
CpalAudioOutputImpl::<i16>::try_open(spec, duration, &device)
79+
}
80+
cpal::SampleFormat::U16 => {
81+
CpalAudioOutputImpl::<u16>::try_open(spec, duration, &device)
82+
}
83+
_ => unimplemented!(),
84+
}
85+
}
86+
}
87+
88+
struct CpalAudioOutputImpl<T: AudioOutputSample>
89+
where
90+
T: AudioOutputSample,
91+
{
92+
ring_buf_producer: rb::Producer<T>,
93+
sample_buf: SampleBuffer<T>,
94+
stream: cpal::Stream,
95+
resampler: Option<Resampler<T>>,
96+
}
97+
98+
impl<T: AudioOutputSample> CpalAudioOutputImpl<T> {
99+
pub fn try_open(
100+
spec: SignalSpec,
101+
duration: Duration,
102+
device: &cpal::Device,
103+
) -> Result<Box<dyn AudioOutput>> {
104+
let num_channels = spec.channels.count();
105+
106+
// Output audio stream config.
107+
let config = if cfg!(not(target_os = "windows")) {
108+
cpal::StreamConfig {
109+
channels: num_channels as cpal::ChannelCount,
110+
sample_rate: cpal::SampleRate(spec.rate),
111+
buffer_size: cpal::BufferSize::Default,
112+
}
113+
} else {
114+
// Use the default config for Windows.
115+
device
116+
.default_output_config()
117+
.expect("Failed to get the default output config.")
118+
.config()
119+
};
120+
121+
// Create a ring buffer with a capacity for up-to 200ms of audio.
122+
let ring_len = ((200 * config.sample_rate.0 as usize) / 1000) * num_channels;
123+
124+
let ring_buf = SpscRb::new(ring_len);
125+
let (ring_buf_producer, ring_buf_consumer) = (ring_buf.producer(), ring_buf.consumer());
126+
127+
let stream_result = device.build_output_stream(
128+
&config,
129+
move |data: &mut [T], _: &cpal::OutputCallbackInfo| {
130+
// Write out as many samples as possible from the ring buffer to the audio
131+
// output.
132+
let written = ring_buf_consumer.read(data).unwrap_or(0);
133+
134+
// Mute any remaining samples.
135+
data[written..].iter_mut().for_each(|s| *s = T::MID);
136+
},
137+
move |err| error!("audio output error: {}", err),
138+
Some(time::Duration::from_secs(1)),
139+
);
140+
141+
if let Err(err) = stream_result {
142+
error!("audio output stream open error: {}", err);
143+
144+
return Err(AudioOutputError::OpenStreamError);
145+
}
146+
147+
let stream = stream_result.unwrap();
148+
149+
// Start the output stream.
150+
if let Err(err) = stream.play() {
151+
error!("audio output stream play error: {}", err);
152+
153+
return Err(AudioOutputError::PlayStreamError);
154+
}
155+
156+
let sample_buf = SampleBuffer::<T>::new(duration, spec);
157+
158+
let resampler = if spec.rate != config.sample_rate.0 {
159+
info!("resampling {} Hz to {} Hz", spec.rate, config.sample_rate.0);
160+
Some(Resampler::new(
161+
spec,
162+
config.sample_rate.0 as usize,
163+
duration,
164+
))
165+
} else {
166+
None
167+
};
168+
169+
Ok(Box::new(CpalAudioOutputImpl {
170+
ring_buf_producer,
171+
sample_buf,
172+
stream,
173+
resampler,
174+
}))
175+
}
176+
}
177+
178+
impl<T: AudioOutputSample> AudioOutput for CpalAudioOutputImpl<T> {
179+
fn write(&mut self, decoded: AudioBufferRef<'_>) -> Result<()> {
180+
// Do nothing if there are no audio frames.
181+
if decoded.frames() == 0 {
182+
return Ok(());
183+
}
184+
185+
let mut samples = if let Some(resampler) = &mut self.resampler {
186+
// Resampling is required. The resampler will return interleaved samples in the
187+
// correct sample format.
188+
match resampler.resample(decoded) {
189+
Some(resampled) => resampled,
190+
None => return Ok(()),
191+
}
192+
} else {
193+
// Resampling is not required. Interleave the sample for cpal using a sample buffer.
194+
self.sample_buf.copy_interleaved_ref(decoded);
195+
196+
self.sample_buf.samples()
197+
};
198+
199+
// Write all samples to the ring buffer.
200+
while let Some(written) = self.ring_buf_producer.write_blocking(samples) {
201+
samples = &samples[written..];
202+
}
203+
204+
Ok(())
205+
}
206+
207+
fn flush(&mut self) {
208+
// If there is a resampler, then it may need to be flushed
209+
// depending on the number of samples it has.
210+
if let Some(resampler) = &mut self.resampler {
211+
let mut remaining_samples = resampler.flush().unwrap_or_default();
212+
213+
while let Some(written) = self.ring_buf_producer.write_blocking(remaining_samples) {
214+
remaining_samples = &remaining_samples[written..];
215+
}
216+
}
217+
218+
// Flush is best-effort, ignore the returned result.
219+
let _ = self.stream.pause();
220+
}
221+
}
222+
223+
pub fn try_open(spec: SignalSpec, duration: Duration) -> Result<Box<dyn AudioOutput>> {
224+
CpalAudioOutput::try_open(spec, duration)
225+
}

0 commit comments

Comments
 (0)