Files
psysonic/src-tauri/patches/cpal-0.15.3/src/samples_formats.rs
T
Psychotoxical 34b4445c13 feat(lyrics): YouLyPlus karaoke + fix(macos): suppress mic prompt
Two independent user-facing fixes rolled together.

── Lyrics: YouLyPlus provider (issue #172) ──
Adds word-by-word synced (karaoke) lyrics as an alternative lyrics mode.
Backed by the public lyricsplus aggregator (Apple Music / Spotify /
Musixmatch / QQ Music) — no API keys on our side, subscription costs are
borne by the backend operator. Five mirrors are tried on network failure.

Settings → Lyrics now exposes a mode radio (Standard vs YouLyPlus) plus
a static-only toggle. YouLyPlus misses silently fall back to the existing
server + LRCLIB + Netease pipeline so obscure tracks still resolve. The
drag-to-reorder source list is hidden in YouLyPlus mode since the
external aggregator manages its own source priority.

Rendering: per-word spans on each line, active word highlighted with
accent-tinted glow. Subscribed imperatively via usePlayerStore.subscribe
so 500 ms progress ticks do not re-render the whole lyrics block. The
Fullscreen Player reuses the same pattern; the 5-line rail layout is
unchanged. white-space: pre on .fs-lyric-word preserves the trailing
spaces the API embeds in each syllabus token (flex context would
otherwise collapse them).

i18n: new keys in all 8 locales (de, en, fr, nl, nb, ru, es, zh).

── macOS mic-prompt suppression ──
Ships a vendored cpal 0.15.3 at patches/cpal-0.15.3/ wired via
[patch.crates-io]. The only change is in src/host/coreaudio/macos/mod.rs:
audio_unit_from_device now unconditionally uses IOType::DefaultOutput for
output streams instead of conditionally choosing HalOutput.

AUHAL (HalOutput) is classified as input-capable by macOS TCC and
triggers the microphone-permission dialog at first launch / after each
update even for playback-only apps. Previous attempts (removing
NSMicrophoneUsageDescription, setting com.apple.security.device.audio-input
false in Entitlements.plist) did not suppress the prompt because TCC
fires at AudioUnit instantiation, not at plist level. The upstream fix
in cpal PR #1070 / cpal 0.17 only helps when the pinned device equals
the system default; always-DefaultOutput covers all cases.

Tradeoff: per-device output selection is a no-op on macOS — the stream
always follows the system default. Settings.tsx surfaces this via
audioOutputDeviceMacNotice (hides the CustomSelect on macOS) and skips
audio_list_devices + device-watcher effects there. Matches the behaviour
of Apple Music / Spotify on macOS.

Also removes the now-obsolete com.apple.security.device.audio-input
entitlement (sandbox is disabled anyway, so it was ignored).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 21:30:31 +02:00

168 lines
5.1 KiB
Rust

use std::{fmt::Display, mem};
#[cfg(target_os = "emscripten")]
use wasm_bindgen::prelude::*;
pub use dasp_sample::{FromSample, Sample, I24, I48, U24, U48};
/// Format that each sample has.
#[cfg_attr(target_os = "emscripten", wasm_bindgen)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum SampleFormat {
/// `i8` with a valid range of 'u8::MIN..=u8::MAX' with `0` being the origin
I8,
/// `i16` with a valid range of 'u16::MIN..=u16::MAX' with `0` being the origin
I16,
// /// `I24` with a valid range of '-(1 << 23)..(1 << 23)' with `0` being the origin
// I24,
/// `i32` with a valid range of 'u32::MIN..=u32::MAX' with `0` being the origin
I32,
// /// `I24` with a valid range of '-(1 << 47)..(1 << 47)' with `0` being the origin
// I48,
/// `i64` with a valid range of 'u64::MIN..=u64::MAX' with `0` being the origin
I64,
/// `u8` with a valid range of 'u8::MIN..=u8::MAX' with `1 << 7 == 128` being the origin
U8,
/// `u16` with a valid range of 'u16::MIN..=u16::MAX' with `1 << 15 == 32768` being the origin
U16,
// /// `U24` with a valid range of '0..16777216' with `1 << 23 == 8388608` being the origin
// U24,
/// `u32` with a valid range of 'u32::MIN..=u32::MAX' with `1 << 31` being the origin
U32,
// /// `U48` with a valid range of '0..(1 << 48)' with `1 << 47` being the origin
// U48,
/// `u64` with a valid range of 'u64::MIN..=u64::MAX' with `1 << 63` being the origin
U64,
/// `f32` with a valid range of `-1.0..1.0` with `0.0` being the origin
F32,
/// `f64` with a valid range of -1.0..1.0 with 0.0 being the origin
F64,
}
impl SampleFormat {
/// Returns the size in bytes of a sample of this format.
#[inline]
#[must_use]
pub fn sample_size(&self) -> usize {
match *self {
SampleFormat::I8 | SampleFormat::U8 => mem::size_of::<i8>(),
SampleFormat::I16 | SampleFormat::U16 => mem::size_of::<i16>(),
// SampleFormat::I24 | SampleFormat::U24 => 3,
SampleFormat::I32 | SampleFormat::U32 => mem::size_of::<i32>(),
// SampleFormat::I48 | SampleFormat::U48 => 6,
SampleFormat::I64 | SampleFormat::U64 => mem::size_of::<i64>(),
SampleFormat::F32 => mem::size_of::<f32>(),
SampleFormat::F64 => mem::size_of::<f64>(),
}
}
#[inline]
#[must_use]
pub fn is_int(&self) -> bool {
//matches!(*self, SampleFormat::I8 | SampleFormat::I16 | SampleFormat::I24 | SampleFormat::I32 | SampleFormat::I48 | SampleFormat::I64)
matches!(
*self,
SampleFormat::I8 | SampleFormat::I16 | SampleFormat::I32 | SampleFormat::I64
)
}
#[inline]
#[must_use]
pub fn is_uint(&self) -> bool {
//matches!(*self, SampleFormat::U8 | SampleFormat::U16 | SampleFormat::U24 | SampleFormat::U32 | SampleFormat::U48 | SampleFormat::U64)
matches!(
*self,
SampleFormat::U8 | SampleFormat::U16 | SampleFormat::U32 | SampleFormat::U64
)
}
#[inline]
#[must_use]
pub fn is_float(&self) -> bool {
matches!(*self, SampleFormat::F32 | SampleFormat::F64)
}
}
impl Display for SampleFormat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match *self {
SampleFormat::I8 => "i8",
SampleFormat::I16 => "i16",
// SampleFormat::I24 => "i24",
SampleFormat::I32 => "i32",
// SampleFormat::I48 => "i48",
SampleFormat::I64 => "i64",
SampleFormat::U8 => "u8",
SampleFormat::U16 => "u16",
// SampleFormat::U24 => "u24",
SampleFormat::U32 => "u32",
// SampleFormat::U48 => "u48",
SampleFormat::U64 => "u64",
SampleFormat::F32 => "f32",
SampleFormat::F64 => "f64",
}
.fmt(f)
}
}
pub trait SizedSample: Sample {
const FORMAT: SampleFormat;
}
impl SizedSample for i8 {
const FORMAT: SampleFormat = SampleFormat::I8;
}
impl SizedSample for i16 {
const FORMAT: SampleFormat = SampleFormat::I16;
}
// impl SizedSample for I24 { const FORMAT: SampleFormat = SampleFormat::I24; }
impl SizedSample for i32 {
const FORMAT: SampleFormat = SampleFormat::I32;
}
// impl SizedSample for I48 { const FORMAT: SampleFormat = SampleFormat::I48; }
impl SizedSample for i64 {
const FORMAT: SampleFormat = SampleFormat::I64;
}
impl SizedSample for u8 {
const FORMAT: SampleFormat = SampleFormat::U8;
}
impl SizedSample for u16 {
const FORMAT: SampleFormat = SampleFormat::U16;
}
// impl SizedSample for U24 { const FORMAT: SampleFormat = SampleFormat::U24; }
impl SizedSample for u32 {
const FORMAT: SampleFormat = SampleFormat::U32;
}
// impl SizedSample for U48 { const FORMAT: SampleFormat = SampleFormat::U48; }
impl SizedSample for u64 {
const FORMAT: SampleFormat = SampleFormat::U64;
}
impl SizedSample for f32 {
const FORMAT: SampleFormat = SampleFormat::F32;
}
impl SizedSample for f64 {
const FORMAT: SampleFormat = SampleFormat::F64;
}