use crate::audio_source::{Ym2149AudioSource, Ym2149Metadata};
use crate::song_player::{SharedSongPlayer, YmSongPlayer};
use crate::synth::YmSynthController;
use bevy::prelude::*;
use parking_lot::RwLock;
use std::sync::Arc;
use ym2149_common::DEFAULT_SAMPLE_RATE;
pub const YM2149_SAMPLE_RATE: u32 = DEFAULT_SAMPLE_RATE;
pub const YM2149_SAMPLE_RATE_F32: f32 = YM2149_SAMPLE_RATE as f32;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct PlaybackMetrics {
pub frame_count: usize,
pub samples_per_frame: u32,
}
impl PlaybackMetrics {
pub fn total_samples(&self) -> usize {
self.frame_count
.saturating_mul(self.samples_per_frame as usize)
}
pub fn duration_seconds(&self) -> f32 {
self.total_samples() as f32 / YM2149_SAMPLE_RATE_F32
}
}
impl From<&ym2149_ym_replayer::LoadSummary> for PlaybackMetrics {
fn from(summary: &ym2149_ym_replayer::LoadSummary) -> Self {
Self {
frame_count: summary.frame_count,
samples_per_frame: summary.samples_per_frame,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ToneSettings {
pub saturation: f32,
pub accent: f32,
pub widen: f32,
pub color_filter: bool,
}
impl Default for ToneSettings {
fn default() -> Self {
Self {
saturation: 0.0,
accent: 0.0,
widen: 0.0,
color_filter: true,
}
}
}
#[derive(Clone)]
pub(crate) enum TrackSource {
File(String),
Asset(Handle<Ym2149AudioSource>),
Bytes(Arc<Vec<u8>>),
}
#[derive(Clone)]
pub(crate) struct CrossfadeRequest {
pub source: TrackSource,
pub duration: f32,
pub target_index: usize,
}
pub(crate) struct ActiveCrossfade {
pub player: SharedSongPlayer,
pub metrics: PlaybackMetrics,
pub song_title: String,
pub song_author: String,
pub elapsed: f32,
pub duration: f32,
pub target_index: usize,
pub audio_handle: Handle<crate::audio_source::Ym2149AudioSource>,
pub data: Arc<Vec<u8>>,
pub crossfade_entity: Option<Entity>,
}
#[derive(Component)]
pub struct Ym2149Playback {
pub source_path: Option<String>,
pub source_bytes: Option<Arc<Vec<u8>>>,
pub source_asset: Option<Handle<crate::audio_source::Ym2149AudioSource>>,
pub state: PlaybackState,
pub(crate) frame_position: u32,
pub volume: f32,
pub(crate) left_gain: f32,
pub(crate) right_gain: f32,
pub(crate) stereo_gain: Arc<RwLock<(f32, f32)>>,
pub(crate) player: Option<SharedSongPlayer>,
pub(crate) needs_reload: bool,
pub song_title: String,
pub song_author: String,
pub tone_settings: Arc<RwLock<ToneSettings>>,
pub(crate) metrics: Option<PlaybackMetrics>,
pub(crate) pending_playlist_index: Option<usize>,
pub(crate) pending_crossfade: Option<CrossfadeRequest>,
pub(crate) crossfade: Option<ActiveCrossfade>,
pub(crate) inline_player: bool,
pub(crate) inline_audio_ready: bool,
pub(crate) inline_metadata: Option<Ym2149Metadata>,
pub(crate) pending_subsong: Option<usize>,
pub(crate) cached_subsong_count: usize,
pub(crate) cached_current_subsong: usize,
pub(crate) audio_stream_state: Option<Arc<crate::streaming::AudioStreamState>>,
pub(crate) audio_player: Option<SharedSongPlayer>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PlaybackState {
Idle,
Playing,
Paused,
Finished,
}
impl Ym2149Playback {
pub fn new(source_path: impl Into<String>) -> Self {
let path = source_path.into();
debug_assert!(!path.is_empty(), "source_path should not be empty");
Self {
source_path: Some(path),
..Default::default()
}
}
pub fn from_asset(handle: Handle<crate::audio_source::Ym2149AudioSource>) -> Self {
Self {
source_asset: Some(handle),
..Default::default()
}
}
pub fn from_bytes(bytes: impl Into<Vec<u8>>) -> Self {
let data = bytes.into();
debug_assert!(!data.is_empty(), "bytes should not be empty");
Self {
source_bytes: Some(Arc::new(data)),
..Default::default()
}
}
pub fn synth(controller: YmSynthController) -> Self {
let synth_player = YmSongPlayer::new_synth(controller);
let metadata = synth_player.metadata().clone();
let metrics = synth_player.metrics().unwrap_or(PlaybackMetrics {
frame_count: metadata.frame_count,
samples_per_frame: YM2149_SAMPLE_RATE / 50,
});
let player = Arc::new(RwLock::new(synth_player));
Self {
source_path: None,
source_bytes: None,
source_asset: None,
state: PlaybackState::Idle,
frame_position: 0,
volume: 1.0,
left_gain: 1.0,
right_gain: 1.0,
stereo_gain: Arc::new(RwLock::new((1.0, 1.0))),
player: Some(player),
needs_reload: false,
song_title: metadata.title.clone(),
song_author: metadata.author.clone(),
metrics: Some(metrics),
pending_playlist_index: None,
pending_crossfade: None,
crossfade: None,
inline_player: true,
inline_audio_ready: false,
inline_metadata: Some(metadata),
pending_subsong: None,
cached_subsong_count: 1,
cached_current_subsong: 1,
tone_settings: Arc::new(RwLock::new(ToneSettings::default())),
audio_stream_state: None,
audio_player: None,
}
}
pub fn play(&mut self) {
if matches!(self.state, PlaybackState::Playing) {
return;
}
self.state = PlaybackState::Playing;
self.frame_position = 0;
}
pub fn resume(&mut self) {
if matches!(self.state, PlaybackState::Paused) {
self.state = PlaybackState::Playing;
}
}
pub fn pause(&mut self) {
if matches!(self.state, PlaybackState::Playing) {
self.state = PlaybackState::Paused;
}
}
pub fn stop(&mut self) {
self.state = PlaybackState::Idle;
self.frame_position = 0;
self.crossfade = None;
self.pending_crossfade = None;
}
pub fn restart(&mut self) {
self.state = PlaybackState::Idle;
self.frame_position = 0;
self.metrics = None;
self.player = None;
self.inline_audio_ready = false;
self.needs_reload = true;
self.crossfade = None;
self.pending_crossfade = None;
}
pub fn seek(&mut self, frame: u32) {
self.frame_position = frame;
}
pub fn seek_percentage(&mut self, position: f32) -> bool {
let mut success = false;
if let Some(audio_player) = &self.audio_player {
let mut guard = audio_player.write();
if guard.seek_percentage(position) {
self.frame_position = guard.current_frame() as u32;
success = true;
}
}
if let Some(player) = &self.player {
let mut guard = player.write();
let _ = guard.seek_percentage(position);
}
if success {
if let Some(state) = &self.audio_stream_state {
state.buffer.flush();
state.notify_seek();
}
}
success
}
pub fn duration_seconds(&self) -> f32 {
if let Some(player) = &self.player {
player.read().duration_seconds()
} else if let Some(metrics) = self.metrics {
metrics.duration_seconds()
} else {
0.0
}
}
pub fn has_duration_info(&self) -> bool {
self.player
.as_ref()
.map(|p| p.read().has_duration_info())
.unwrap_or(true)
}
pub fn playback_position(&self) -> f32 {
let frame_count = self.metrics.map(|m| m.frame_count).unwrap_or(0);
if frame_count > 0 {
(self.frame_position as f32 / frame_count as f32).min(1.0)
} else {
0.0
}
}
pub fn set_volume(&mut self, volume: f32) {
self.volume = volume.max(0.0);
}
pub fn tone_settings(&self) -> ToneSettings {
*self.tone_settings.read()
}
pub fn set_tone_settings(&mut self, settings: ToneSettings) {
*self.tone_settings.write() = settings;
}
pub fn set_stereo_gain(&mut self, left: f32, right: f32) {
let clamped_left = left.clamp(0.0, 1.0);
let clamped_right = right.clamp(0.0, 1.0);
{
let mut gains = self.stereo_gain.write();
*gains = (clamped_left, clamped_right);
}
self.left_gain = clamped_left;
self.right_gain = clamped_right;
}
pub fn set_source_path(&mut self, path: impl Into<String>) {
self.source_path = Some(path.into());
self.source_bytes = None;
self.source_asset = None;
self.needs_reload = true;
self.metrics = None;
self.pending_playlist_index = None;
self.pending_crossfade = None;
self.crossfade = None;
}
pub fn set_source_asset(&mut self, handle: Handle<crate::audio_source::Ym2149AudioSource>) {
self.source_asset = Some(handle);
self.source_path = None;
self.source_bytes = None;
self.needs_reload = true;
self.metrics = None;
self.pending_playlist_index = None;
self.pending_crossfade = None;
self.crossfade = None;
}
pub fn set_source_bytes(&mut self, bytes: impl Into<Vec<u8>>) {
self.source_bytes = Some(Arc::new(bytes.into()));
self.source_path = None;
self.source_asset = None;
self.needs_reload = true;
self.metrics = None;
self.pending_playlist_index = None;
self.pending_crossfade = None;
self.crossfade = None;
}
pub fn source_path(&self) -> Option<&str> {
self.source_path.as_deref()
}
pub fn source_asset(&self) -> Option<&Handle<crate::audio_source::Ym2149AudioSource>> {
self.source_asset.as_ref()
}
pub fn source_bytes(&self) -> Option<Arc<Vec<u8>>> {
self.source_bytes.as_ref().map(Arc::clone)
}
pub fn is_playing(&self) -> bool {
self.state == PlaybackState::Playing
}
pub fn frame_position(&self) -> u32 {
self.frame_position
}
pub fn player_handle(&self) -> Option<SharedSongPlayer> {
self.player.as_ref().map(Arc::clone)
}
pub fn audio_buffer_fill(&self) -> Option<f32> {
None
}
pub(crate) fn metrics(&self) -> Option<PlaybackMetrics> {
self.metrics
}
pub(crate) fn is_crossfade_pending(&self) -> bool {
self.pending_crossfade.is_some() || self.crossfade.is_some()
}
pub(crate) fn is_crossfade_active(&self) -> bool {
self.crossfade.is_some()
}
pub(crate) fn set_crossfade_request(&mut self, request: CrossfadeRequest) {
self.pending_crossfade = Some(request);
}
pub(crate) fn clear_crossfade_request(&mut self) {
self.pending_crossfade = None;
}
pub(crate) fn take_pending_playlist_index(&mut self) -> Option<usize> {
self.pending_playlist_index.take()
}
pub(crate) fn has_pending_playlist_index(&self) -> bool {
self.pending_playlist_index.is_some()
}
pub fn subsong_count(&self) -> usize {
self.player
.as_ref()
.map(|p| p.read().subsong_count())
.unwrap_or(self.cached_subsong_count)
}
pub fn current_subsong(&self) -> usize {
if let Some(pending) = self.pending_subsong {
return pending;
}
self.player
.as_ref()
.map(|p| p.read().current_subsong())
.unwrap_or(self.cached_current_subsong)
}
pub(crate) fn update_subsong_cache(&mut self) {
if let Some(player) = &self.player {
let player_guard = player.read();
self.cached_subsong_count = player_guard.subsong_count();
self.cached_current_subsong = player_guard.current_subsong();
}
}
pub fn set_subsong(&mut self, index: usize) -> bool {
let count = self.cached_subsong_count;
if index < 1 || index > count {
return false;
}
self.cached_current_subsong = index;
self.pending_subsong = Some(index);
self.player = None;
self.metrics = None;
self.needs_reload = true;
true
}
pub fn next_subsong(&mut self) -> Option<usize> {
let count = self.cached_subsong_count;
if count <= 1 {
return None;
}
let current = self.current_subsong();
let next = if current >= count { 1 } else { current + 1 };
if self.set_subsong(next) {
Some(next)
} else {
None
}
}
pub fn prev_subsong(&mut self) -> Option<usize> {
let count = self.cached_subsong_count;
if count <= 1 {
return None;
}
let current = self.current_subsong();
let prev = if current <= 1 { count } else { current - 1 };
if self.set_subsong(prev) {
Some(prev)
} else {
None
}
}
pub fn has_subsongs(&self) -> bool {
self.cached_subsong_count > 1
}
}
impl Default for Ym2149Playback {
fn default() -> Self {
Self {
source_path: None,
source_bytes: None,
source_asset: None,
state: PlaybackState::Idle,
frame_position: 0,
volume: 1.0,
left_gain: 1.0,
right_gain: 1.0,
stereo_gain: Arc::new(RwLock::new((1.0, 1.0))),
player: None,
needs_reload: false,
song_title: String::new(),
song_author: String::new(),
metrics: None,
pending_playlist_index: None,
pending_crossfade: None,
crossfade: None,
inline_player: false,
inline_audio_ready: false,
inline_metadata: None,
pending_subsong: None,
cached_subsong_count: 1,
cached_current_subsong: 1,
tone_settings: Arc::new(RwLock::new(ToneSettings::default())),
audio_stream_state: None,
audio_player: None,
}
}
}
#[derive(Resource)]
pub struct Ym2149Settings {
pub master_volume: f32,
pub loop_enabled: bool,
}
impl Default for Ym2149Settings {
fn default() -> Self {
Self {
master_volume: 1.0,
loop_enabled: false,
}
}
}