pre pipewire-rs effects
This commit is contained in:
@@ -29,6 +29,7 @@ use crate::{
|
|||||||
osc::{self, OscAppCmd},
|
osc::{self, OscAppCmd},
|
||||||
tui,
|
tui,
|
||||||
};
|
};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
pub struct App {
|
pub struct App {
|
||||||
pub decks: Vec<Deck>,
|
pub decks: Vec<Deck>,
|
||||||
@@ -38,10 +39,11 @@ pub struct App {
|
|||||||
audio: AudioEngine,
|
audio: AudioEngine,
|
||||||
chains: Vec<EffectChain>, // one per deck, in order
|
chains: Vec<EffectChain>, // one per deck, in order
|
||||||
osc_rx: crossbeam_channel::Receiver<OscAppCmd>,
|
osc_rx: crossbeam_channel::Receiver<OscAppCmd>,
|
||||||
|
audio_dir: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
pub fn new() -> Result<Self> {
|
pub fn new(audio_dir: Option<PathBuf>) -> Result<Self> {
|
||||||
// 1. Load filter-chain modules for all 4 decks
|
// 1. Load filter-chain modules for all 4 decks
|
||||||
tracing::info!("Loading PipeWire filter-chain modules…");
|
tracing::info!("Loading PipeWire filter-chain modules…");
|
||||||
let mut chains = Vec::new();
|
let mut chains = Vec::new();
|
||||||
@@ -79,6 +81,8 @@ impl App {
|
|||||||
.map(|i| Deck::new(DeckId::from_index(i).unwrap()))
|
.map(|i| Deck::new(DeckId::from_index(i).unwrap()))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
let audio_dir = audio_dir.unwrap_or_else(|| std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")));
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
decks,
|
decks,
|
||||||
focused_deck: 0,
|
focused_deck: 0,
|
||||||
@@ -86,6 +90,7 @@ impl App {
|
|||||||
audio,
|
audio,
|
||||||
chains,
|
chains,
|
||||||
osc_rx,
|
osc_rx,
|
||||||
|
audio_dir,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -312,7 +317,7 @@ impl App {
|
|||||||
|
|
||||||
fn open_selector(&mut self, idx: usize) {
|
fn open_selector(&mut self, idx: usize) {
|
||||||
if let Some(id) = DeckId::from_index(idx) {
|
if let Some(id) = DeckId::from_index(idx) {
|
||||||
self.file_selector = Some(FileSelector::new(id));
|
self.file_selector = Some(FileSelector::new(id, &self.audio_dir));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
123
src/audio.rs
123
src/audio.rs
@@ -14,8 +14,8 @@ use std::{
|
|||||||
thread,
|
thread,
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{Result, anyhow};
|
||||||
use crossbeam_channel::{bounded, Receiver, Sender};
|
use crossbeam_channel::{Receiver, Sender, bounded};
|
||||||
use symphonia::core::{
|
use symphonia::core::{
|
||||||
audio::{AudioBufferRef, Signal},
|
audio::{AudioBufferRef, Signal},
|
||||||
codecs::DecoderOptions,
|
codecs::DecoderOptions,
|
||||||
@@ -54,10 +54,20 @@ pub enum AudioStatus {
|
|||||||
/// PipeWire object ID of the stream node (for wpctl volume)
|
/// PipeWire object ID of the stream node (for wpctl volume)
|
||||||
stream_node_id: u32,
|
stream_node_id: u32,
|
||||||
},
|
},
|
||||||
Position { deck: DeckId, position: u64 },
|
Position {
|
||||||
Vu { deck: DeckId, l: f32, r: f32 },
|
deck: DeckId,
|
||||||
|
position: u64,
|
||||||
|
},
|
||||||
|
Vu {
|
||||||
|
deck: DeckId,
|
||||||
|
l: f32,
|
||||||
|
r: f32,
|
||||||
|
},
|
||||||
TrackEnded(DeckId),
|
TrackEnded(DeckId),
|
||||||
Error { deck: DeckId, msg: String },
|
Error {
|
||||||
|
deck: DeckId,
|
||||||
|
msg: String,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Per-deck internal state ──────────────────────────────────────────────────
|
// ─── Per-deck internal state ──────────────────────────────────────────────────
|
||||||
@@ -65,7 +75,7 @@ pub enum AudioStatus {
|
|||||||
struct DeckState {
|
struct DeckState {
|
||||||
id: DeckId,
|
id: DeckId,
|
||||||
playing: bool,
|
playing: bool,
|
||||||
pcm: Vec<f32>, // interleaved stereo f32
|
pcm: Vec<f32>, // interleaved stereo f32
|
||||||
sample_rate: u32,
|
sample_rate: u32,
|
||||||
read_pos: usize, // byte index into pcm (always even)
|
read_pos: usize, // byte index into pcm (always even)
|
||||||
cue_pos: usize,
|
cue_pos: usize,
|
||||||
@@ -84,7 +94,7 @@ impl DeckState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Public handle ──────────────────────────────────────────────────────────
|
// ─── Public handle ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
pub struct AudioEngine {
|
pub struct AudioEngine {
|
||||||
pub cmd_tx: Sender<AudioCmd>,
|
pub cmd_tx: Sender<AudioCmd>,
|
||||||
@@ -108,7 +118,7 @@ impl AudioEngine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Engine thread ─────────────────────────────────────────────────────────
|
// ─── Engine thread ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
fn engine_thread(cmd_rx: Receiver<AudioCmd>, status_tx: Sender<AudioStatus>) -> Result<()> {
|
fn engine_thread(cmd_rx: Receiver<AudioCmd>, status_tx: Sender<AudioStatus>) -> Result<()> {
|
||||||
use pipewire::{context::ContextRc, main_loop::MainLoopRc, stream::StreamRc};
|
use pipewire::{context::ContextRc, main_loop::MainLoopRc, stream::StreamRc};
|
||||||
@@ -155,19 +165,20 @@ fn engine_thread(cmd_rx: Receiver<AudioCmd>, status_tx: Sender<AudioStatus>) ->
|
|||||||
.process(move |stream: &pipewire::stream::Stream, _| {
|
.process(move |stream: &pipewire::stream::Stream, _| {
|
||||||
let mut ds_guard = ds_cb.lock();
|
let mut ds_guard = ds_cb.lock();
|
||||||
let ds = ds_guard.as_mut().unwrap();
|
let ds = ds_guard.as_mut().unwrap();
|
||||||
|
|
||||||
if let Some(mut buf) = stream.dequeue_buffer() {
|
if let Some(mut buf) = stream.dequeue_buffer() {
|
||||||
let datas = buf.datas_mut();
|
let datas = buf.datas_mut();
|
||||||
if datas.is_empty() { return; }
|
if datas.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let data = &mut datas[0];
|
let data = &mut datas[0];
|
||||||
let chunk = data.chunk_mut();
|
|
||||||
let n_frames = (chunk.size() / 8) as usize; // f32 stereo = 8 bytes
|
|
||||||
let out_bytes = data.data().unwrap();
|
let out_bytes = data.data().unwrap();
|
||||||
|
let n_frames = out_bytes.len() / 8; // f32 stereo
|
||||||
|
|
||||||
let out = unsafe {
|
let out = unsafe {
|
||||||
std::slice::from_raw_parts_mut(
|
std::slice::from_raw_parts_mut(out_bytes.as_ptr() as *mut f32, n_frames * 2)
|
||||||
out_bytes.as_ptr() as *mut f32,
|
|
||||||
n_frames * 2,
|
|
||||||
)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut vu_l = 0.0f32;
|
let mut vu_l = 0.0f32;
|
||||||
@@ -176,18 +187,20 @@ fn engine_thread(cmd_rx: Receiver<AudioCmd>, status_tx: Sender<AudioStatus>) ->
|
|||||||
if ds.playing && !ds.pcm.is_empty() {
|
if ds.playing && !ds.pcm.is_empty() {
|
||||||
for frame in 0..n_frames {
|
for frame in 0..n_frames {
|
||||||
if ds.read_pos + 1 >= ds.pcm.len() {
|
if ds.read_pos + 1 >= ds.pcm.len() {
|
||||||
// End of track
|
|
||||||
ds.playing = false;
|
ds.playing = false;
|
||||||
out[frame * 2] = 0.0;
|
out[frame * 2] = 0.0;
|
||||||
out[frame * 2 + 1] = 0.0;
|
out[frame * 2 + 1] = 0.0;
|
||||||
let _ = status_tx_cb.try_send(AudioStatus::TrackEnded(ds.id));
|
let _ = status_tx_cb.try_send(AudioStatus::TrackEnded(ds.id));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let l = ds.pcm[ds.read_pos];
|
let l = ds.pcm[ds.read_pos];
|
||||||
let r = ds.pcm[ds.read_pos + 1];
|
let r = ds.pcm[ds.read_pos + 1];
|
||||||
ds.read_pos += 2;
|
ds.read_pos += 2;
|
||||||
out[frame * 2] = l;
|
|
||||||
|
out[frame * 2] = l;
|
||||||
out[frame * 2 + 1] = r;
|
out[frame * 2 + 1] = r;
|
||||||
|
|
||||||
vu_l = vu_l.max(l.abs());
|
vu_l = vu_l.max(l.abs());
|
||||||
vu_r = vu_r.max(r.abs());
|
vu_r = vu_r.max(r.abs());
|
||||||
}
|
}
|
||||||
@@ -195,18 +208,26 @@ fn engine_thread(cmd_rx: Receiver<AudioCmd>, status_tx: Sender<AudioStatus>) ->
|
|||||||
out.fill(0.0);
|
out.fill(0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
*chunk.offset_mut() = 0;
|
{
|
||||||
*chunk.stride_mut() = 8;
|
let chunk = data.chunk_mut();
|
||||||
*chunk.size_mut() = (n_frames * 8) as u32;
|
*chunk.offset_mut() = 0;
|
||||||
|
*chunk.stride_mut() = 8;
|
||||||
|
*chunk.size_mut() = (n_frames * 8) as u32;
|
||||||
|
}
|
||||||
|
|
||||||
let pos = (ds.read_pos / 2) as u64;
|
let pos = (ds.read_pos / 2) as u64;
|
||||||
let _ = status_tx_cb.try_send(AudioStatus::Position { deck: ds.id, position: pos });
|
let _ = status_tx_cb.try_send(AudioStatus::Position {
|
||||||
let _ = status_tx_cb.try_send(AudioStatus::Vu { deck: ds.id, l: vu_l, r: vu_r });
|
deck: ds.id,
|
||||||
|
position: pos,
|
||||||
|
});
|
||||||
|
let _ = status_tx_cb.try_send(AudioStatus::Vu {
|
||||||
|
deck: ds.id,
|
||||||
|
l: vu_l,
|
||||||
|
r: vu_r,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.register()?;
|
.register()?; // Connect the stream with F32LE stereo format
|
||||||
|
|
||||||
// Connect the stream with F32LE stereo format
|
|
||||||
let pod_bytes = make_f32_stereo_pod()?;
|
let pod_bytes = make_f32_stereo_pod()?;
|
||||||
let mut params = [pipewire::spa::pod::Pod::from_bytes(&pod_bytes).unwrap()];
|
let mut params = [pipewire::spa::pod::Pod::from_bytes(&pod_bytes).unwrap()];
|
||||||
stream.connect(
|
stream.connect(
|
||||||
@@ -235,14 +256,11 @@ fn engine_thread(cmd_rx: Receiver<AudioCmd>, status_tx: Sender<AudioStatus>) ->
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_cmd(
|
fn handle_cmd(cmd: AudioCmd, states: &[Arc<Mutex<DeckState>>], status_tx: &Sender<AudioStatus>) {
|
||||||
cmd: AudioCmd,
|
|
||||||
states: &[Arc<Mutex<DeckState>>],
|
|
||||||
status_tx: &Sender<AudioStatus>,
|
|
||||||
) {
|
|
||||||
match cmd {
|
match cmd {
|
||||||
AudioCmd::Load { deck, path } => {
|
AudioCmd::Load { deck, path } => {
|
||||||
let name = path.file_name()
|
let name = path
|
||||||
|
.file_name()
|
||||||
.and_then(|n| n.to_str())
|
.and_then(|n| n.to_str())
|
||||||
.unwrap_or("unknown")
|
.unwrap_or("unknown")
|
||||||
.to_string();
|
.to_string();
|
||||||
@@ -269,7 +287,10 @@ fn handle_cmd(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let _ = status_tx.send(AudioStatus::Error { deck, msg: e.to_string() });
|
let _ = status_tx.send(AudioStatus::Error {
|
||||||
|
deck,
|
||||||
|
msg: e.to_string(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -333,8 +354,8 @@ fn decode_file(path: &PathBuf) -> Result<(Vec<f32>, u32)> {
|
|||||||
|
|
||||||
let sr = track.codec_params.sample_rate.unwrap_or(44100);
|
let sr = track.codec_params.sample_rate.unwrap_or(44100);
|
||||||
let channels = track.codec_params.channels.map(|c| c.count()).unwrap_or(2);
|
let channels = track.codec_params.channels.map(|c| c.count()).unwrap_or(2);
|
||||||
let mut decoder = symphonia::default::get_codecs()
|
let mut decoder =
|
||||||
.make(&track.codec_params, &DecoderOptions::default())?;
|
symphonia::default::get_codecs().make(&track.codec_params, &DecoderOptions::default())?;
|
||||||
|
|
||||||
let track_id = track.id;
|
let track_id = track.id;
|
||||||
let mut pcm: Vec<f32> = Vec::new();
|
let mut pcm: Vec<f32> = Vec::new();
|
||||||
@@ -346,7 +367,9 @@ fn decode_file(path: &PathBuf) -> Result<(Vec<f32>, u32)> {
|
|||||||
Err(symphonia::core::errors::Error::ResetRequired) => break,
|
Err(symphonia::core::errors::Error::ResetRequired) => break,
|
||||||
Err(e) => return Err(e.into()),
|
Err(e) => return Err(e.into()),
|
||||||
};
|
};
|
||||||
if packet.track_id() != track_id { continue; }
|
if packet.track_id() != track_id {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
let decoded = decoder.decode(&packet)?;
|
let decoded = decoder.decode(&packet)?;
|
||||||
push_samples(&mut pcm, decoded, channels);
|
push_samples(&mut pcm, decoded, channels);
|
||||||
@@ -373,11 +396,11 @@ fn push_samples(out: &mut Vec<f32>, buf: AudioBufferRef, channels: usize) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
match buf {
|
match buf {
|
||||||
AudioBufferRef::F32(b) => push!(b, 1.0_f32),
|
AudioBufferRef::F32(b) => push!(b, 1.0_f32),
|
||||||
AudioBufferRef::F64(b) => push!(b, 1.0_f32),
|
AudioBufferRef::F64(b) => push!(b, 1.0_f32),
|
||||||
AudioBufferRef::S16(b) => push!(b, 1.0 / 32768.0_f32),
|
AudioBufferRef::S16(b) => push!(b, 1.0 / 32768.0_f32),
|
||||||
AudioBufferRef::S32(b) => push!(b, 1.0 / 2147483648.0_f32),
|
AudioBufferRef::S32(b) => push!(b, 1.0 / 2147483648.0_f32),
|
||||||
AudioBufferRef::U8(b) => push!(b, 1.0 / 128.0_f32),
|
AudioBufferRef::U8(b) => push!(b, 1.0 / 128.0_f32),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -385,11 +408,11 @@ fn push_samples(out: &mut Vec<f32>, buf: AudioBufferRef, channels: usize) {
|
|||||||
// ─── PipeWire format pod helper ───────────────────────────────────────────────
|
// ─── PipeWire format pod helper ───────────────────────────────────────────────
|
||||||
|
|
||||||
fn make_f32_stereo_pod() -> Result<Vec<u8>> {
|
fn make_f32_stereo_pod() -> Result<Vec<u8>> {
|
||||||
use pipewire::spa::pod::serialize::PodSerializer;
|
use pipewire::spa::param::ParamType;
|
||||||
use pipewire::spa::pod::{object, property, Value};
|
|
||||||
use pipewire::spa::param::audio::AudioFormat;
|
use pipewire::spa::param::audio::AudioFormat;
|
||||||
use pipewire::spa::param::format::{FormatProperties, MediaSubtype, MediaType};
|
use pipewire::spa::param::format::{FormatProperties, MediaSubtype, MediaType};
|
||||||
use pipewire::spa::param::ParamType;
|
use pipewire::spa::pod::serialize::PodSerializer;
|
||||||
|
use pipewire::spa::pod::{Value, object, property};
|
||||||
use pipewire::spa::utils::SpaTypes;
|
use pipewire::spa::utils::SpaTypes;
|
||||||
|
|
||||||
let bytes = PodSerializer::serialize(
|
let bytes = PodSerializer::serialize(
|
||||||
@@ -397,16 +420,10 @@ fn make_f32_stereo_pod() -> Result<Vec<u8>> {
|
|||||||
&Value::Object(object!(
|
&Value::Object(object!(
|
||||||
SpaTypes::ObjectParamFormat,
|
SpaTypes::ObjectParamFormat,
|
||||||
ParamType::EnumFormat,
|
ParamType::EnumFormat,
|
||||||
|
property!(FormatProperties::MediaType, Id, MediaType::Audio),
|
||||||
let bytes = PodSerializer::serialize(
|
|
||||||
std::io::Cursor::new(Vec::new()),
|
|
||||||
&Value::Object(object!(
|
|
||||||
SpaTypes::ObjectParamFormat,
|
|
||||||
ParamType::EnumFormat,
|
|
||||||
property!(FormatProperties::MediaType, Id, MediaType::Audio),
|
|
||||||
property!(FormatProperties::MediaSubtype, Id, MediaSubtype::Raw),
|
property!(FormatProperties::MediaSubtype, Id, MediaSubtype::Raw),
|
||||||
property!(FormatProperties::AudioFormat, Id, AudioFormat::F32LE),
|
property!(FormatProperties::AudioFormat, Id, AudioFormat::F32LE),
|
||||||
property!(FormatProperties::AudioRate, Int, 44100i32),
|
property!(FormatProperties::AudioRate, Int, 44100i32),
|
||||||
property!(FormatProperties::AudioChannels, Int, 2i32),
|
property!(FormatProperties::AudioChannels, Int, 2i32),
|
||||||
)),
|
)),
|
||||||
)?
|
)?
|
||||||
|
|||||||
@@ -19,9 +19,8 @@ pub struct FileSelector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl FileSelector {
|
impl FileSelector {
|
||||||
pub fn new(deck: DeckId) -> Self {
|
pub fn new(deck: DeckId, audio_dir: &PathBuf) -> Self {
|
||||||
let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
|
let all_files = cache::get_audio_files(audio_dir).unwrap_or_default();
|
||||||
let all_files = cache::get_audio_files(&cwd).unwrap_or_default();
|
|
||||||
let filtered = all_files.clone();
|
let filtered = all_files.clone();
|
||||||
let mut ls = ListState::default();
|
let mut ls = ListState::default();
|
||||||
if !filtered.is_empty() { ls.select(Some(0)); }
|
if !filtered.is_empty() { ls.select(Some(0)); }
|
||||||
|
|||||||
10
src/main.rs
10
src/main.rs
@@ -8,6 +8,7 @@ mod osc;
|
|||||||
mod tui;
|
mod tui;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
@@ -23,6 +24,13 @@ async fn main() -> Result<()> {
|
|||||||
|
|
||||||
tracing::info!("djdeck starting");
|
tracing::info!("djdeck starting");
|
||||||
|
|
||||||
let mut app = app::App::new()?;
|
// Parse command line argument for audio directory
|
||||||
|
let audio_dir = if std::env::args().len() > 1 {
|
||||||
|
Some(PathBuf::from(std::env::args().nth(1).unwrap()))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut app = app::App::new(audio_dir)?;
|
||||||
app.run().await
|
app.run().await
|
||||||
}
|
}
|
||||||
|
|||||||
12
src/tui.rs
12
src/tui.rs
@@ -109,7 +109,7 @@ fn draw_deck(f: &mut Frame, deck: &Deck, area: Rect, focused: bool) {
|
|||||||
.borders(Borders::ALL)
|
.borders(Borders::ALL)
|
||||||
.title(title)
|
.title(title)
|
||||||
.border_style(border_style)
|
.border_style(border_style)
|
||||||
.style(Style::default().bg(Color::Black));
|
.style(Style::default().bg(Color::Indexed(0)));
|
||||||
f.render_widget(outer, area);
|
f.render_widget(outer, area);
|
||||||
|
|
||||||
let inner = area.inner(ratatui::layout::Margin { horizontal: 1, vertical: 1 });
|
let inner = area.inner(ratatui::layout::Margin { horizontal: 1, vertical: 1 });
|
||||||
@@ -142,7 +142,7 @@ fn draw_deck(f: &mut Frame, deck: &Deck, area: Rect, focused: bool) {
|
|||||||
// Progress
|
// Progress
|
||||||
f.render_widget(
|
f.render_widget(
|
||||||
Gauge::default()
|
Gauge::default()
|
||||||
.gauge_style(Style::default().fg(color).bg(Color::Black))
|
.gauge_style(Style::default().fg(color).bg(Color::Indexed(0)))
|
||||||
.percent((deck.progress() * 100.0) as u16)
|
.percent((deck.progress() * 100.0) as u16)
|
||||||
.label(""),
|
.label(""),
|
||||||
rows[1],
|
rows[1],
|
||||||
@@ -154,7 +154,7 @@ fn draw_deck(f: &mut Frame, deck: &Deck, area: Rect, focused: bool) {
|
|||||||
Gauge::default()
|
Gauge::default()
|
||||||
.gauge_style(Style::default()
|
.gauge_style(Style::default()
|
||||||
.fg(if fader_pct > 10 { Color::White } else { Color::DarkGray })
|
.fg(if fader_pct > 10 { Color::White } else { Color::DarkGray })
|
||||||
.bg(Color::Black))
|
.bg(Color::Indexed(0)))
|
||||||
.percent(fader_pct.min(100))
|
.percent(fader_pct.min(100))
|
||||||
.label(format!("VOL {:3}%", fader_pct)),
|
.label(format!("VOL {:3}%", fader_pct)),
|
||||||
rows[3],
|
rows[3],
|
||||||
@@ -191,8 +191,8 @@ fn draw_vu(f: &mut Frame, vu: (f32, f32), area: Rect, color: Color) {
|
|||||||
let lp = (vu.0 * 100.0) as u16;
|
let lp = (vu.0 * 100.0) as u16;
|
||||||
let rp = (vu.1 * 100.0) as u16;
|
let rp = (vu.1 * 100.0) as u16;
|
||||||
|
|
||||||
f.render_widget(Gauge::default().gauge_style(Style::default().fg(vc(lp)).bg(Color::Black)).percent(lp.min(100)).label("L"), la);
|
f.render_widget(Gauge::default().gauge_style(Style::default().fg(vc(lp)).bg(Color::Indexed(0))).percent(lp.min(100)).label("L"), la);
|
||||||
f.render_widget(Gauge::default().gauge_style(Style::default().fg(vc(rp)).bg(Color::Black)).percent(rp.min(100)).label("R"), ra);
|
f.render_widget(Gauge::default().gauge_style(Style::default().fg(vc(rp)).bg(Color::Indexed(0))).percent(rp.min(100)).label("R"), ra);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_help(f: &mut Frame, area: Rect) {
|
fn draw_help(f: &mut Frame, area: Rect) {
|
||||||
@@ -225,7 +225,7 @@ fn draw_help(f: &mut Frame, area: Rect) {
|
|||||||
.block(Block::default()
|
.block(Block::default()
|
||||||
.borders(Borders::TOP)
|
.borders(Borders::TOP)
|
||||||
.border_style(Style::default().fg(Color::DarkGray)))
|
.border_style(Style::default().fg(Color::DarkGray)))
|
||||||
.style(Style::default().bg(Color::Black));
|
.style(Style::default().bg(Color::Indexed(0)));
|
||||||
|
|
||||||
f.render_widget(help, area);
|
f.render_widget(help, area);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user