2020-12-05 13:08:24 +01:00
# include "stdafx.h"
2020-10-30 21:26:22 +01:00
# include "Emu/System.h"
2023-04-26 19:21:26 +02:00
# include "Emu/system_config.h"
2025-01-01 13:42:12 +01:00
# include "Emu/Audio/audio_utils.h"
2016-03-21 20:43:03 +01:00
# include "Emu/Cell/PPUModule.h"
2025-02-11 03:00:37 +01:00
# include "Emu/Cell/timers.hpp"
2019-10-26 06:07:15 +02:00
# include "Emu/Cell/lv2/sys_process.h"
2016-03-21 20:43:03 +01:00
# include "Emu/Cell/lv2/sys_event.h"
# include "cellAudio.h"
2022-11-06 11:19:24 +01:00
# include "util/video_provider.h"
2020-12-21 15:12:05 +01:00
2018-12-21 03:13:22 +01:00
# include <cmath>
2016-03-21 20:43:03 +01:00
2018-08-25 14:39:00 +02:00
LOG_CHANNEL ( cellAudio ) ;
2016-03-21 20:43:03 +01:00
2023-11-29 19:12:06 +01:00
extern atomic_t < recording_mode > g_recording_mode ;
2023-08-18 07:54:57 +02:00
extern void lv2_sleep ( u64 timeout , ppu_thread * ppu = nullptr ) ;
2019-09-15 01:04:31 +02:00
vm : : gvar < char , AUDIO_PORT_OFFSET * AUDIO_PORT_COUNT > g_audio_buffer ;
2022-05-08 20:09:31 +02:00
struct alignas ( 16 ) aligned_index_t
{
be_t < u64 > index ;
u8 pad [ 8 ] ;
} ;
vm : : gvar < aligned_index_t , AUDIO_PORT_COUNT > g_audio_indices ;
2019-09-15 01:04:31 +02:00
2017-09-20 12:01:57 +02:00
template < >
void fmt_class_string < CellAudioError > : : format ( std : : string & out , u64 arg )
{
format_enum ( out , arg , [ ] ( CellAudioError value )
{
2025-04-05 21:50:45 +02:00
switch ( value )
{
STR_CASE ( CELL_AUDIO_ERROR_ALREADY_INIT ) ;
STR_CASE ( CELL_AUDIO_ERROR_AUDIOSYSTEM ) ;
STR_CASE ( CELL_AUDIO_ERROR_NOT_INIT ) ;
STR_CASE ( CELL_AUDIO_ERROR_PARAM ) ;
STR_CASE ( CELL_AUDIO_ERROR_PORT_FULL ) ;
STR_CASE ( CELL_AUDIO_ERROR_PORT_ALREADY_RUN ) ;
STR_CASE ( CELL_AUDIO_ERROR_PORT_NOT_OPEN ) ;
STR_CASE ( CELL_AUDIO_ERROR_PORT_NOT_RUN ) ;
STR_CASE ( CELL_AUDIO_ERROR_TRANS_EVENT ) ;
STR_CASE ( CELL_AUDIO_ERROR_PORT_OPEN ) ;
STR_CASE ( CELL_AUDIO_ERROR_SHAREDMEMORY ) ;
STR_CASE ( CELL_AUDIO_ERROR_MUTEX ) ;
STR_CASE ( CELL_AUDIO_ERROR_EVENT_QUEUE ) ;
STR_CASE ( CELL_AUDIO_ERROR_AUDIOSYSTEM_NOT_FOUND ) ;
STR_CASE ( CELL_AUDIO_ERROR_TAG_NOT_FOUND ) ;
}
2017-09-20 12:01:57 +02:00
2025-04-05 21:50:45 +02:00
return unknown ;
} ) ;
2017-09-20 12:01:57 +02:00
}
2018-12-21 03:13:22 +01:00
cell_audio_config : : cell_audio_config ( )
{
2020-07-06 17:38:40 +02:00
raw = audio : : get_raw_config ( ) ;
2020-06-20 02:44:32 +02:00
}
2021-11-24 19:41:05 +01:00
void cell_audio_config : : reset ( bool backend_changed )
2020-06-20 02:44:32 +02:00
{
2021-11-24 19:41:05 +01:00
if ( ! backend | | backend_changed )
{
backend . reset ( ) ;
backend = Emu . GetCallbacks ( ) . get_audio ( ) ;
}
2022-01-05 09:26:12 +01:00
cellAudio . notice ( " cellAudio initializing. Backend: %s " , backend - > GetName ( ) ) ;
2021-11-24 19:41:05 +01:00
const AudioFreq freq = AudioFreq : : FREQ_48K ;
2021-12-01 22:32:07 +01:00
const AudioSampleSize sample_size = raw . convert_to_s16 ? AudioSampleSize : : S16 : AudioSampleSize : : FLOAT ;
2022-07-08 17:13:38 +02:00
2022-11-06 11:19:24 +01:00
const auto & [ req_ch_cnt , downmix ] = AudioBackend : : get_channel_count_and_downmixer ( 0 ) ; // CELL_AUDIO_OUT_PRIMARY
2022-07-08 17:13:38 +02:00
f64 cb_frame_len = 0.0 ;
u32 ch_cnt = 2 ;
2024-03-26 19:39:37 +01:00
audio_channel_layout ch_layout = audio_channel_layout : : stereo ;
2022-07-08 17:13:38 +02:00
2024-03-26 19:39:37 +01:00
if ( backend - > Open ( raw . audio_device , freq , sample_size , req_ch_cnt , raw . channel_layout ) )
2022-07-08 17:13:38 +02:00
{
cb_frame_len = backend - > GetCallbackFrameLen ( ) ;
ch_cnt = backend - > get_channels ( ) ;
2024-03-26 19:39:37 +01:00
ch_layout = backend - > get_channel_layout ( ) ;
cellAudio . notice ( " Opened audio backend (sampling_rate=%d, sample_size=%d, channels=%d, layout=%s) " , backend - > get_sampling_rate ( ) , backend - > get_sample_size ( ) , backend - > get_channels ( ) , backend - > get_channel_layout ( ) ) ;
2022-07-08 17:13:38 +02:00
}
else
{
cellAudio . error ( " Failed to open audio backend. Make sure that no other application is running that might block audio access (e.g. Netflix). " ) ;
}
2020-06-20 02:44:32 +02:00
2022-06-06 18:18:13 +02:00
audio_downmix = downmix ;
2024-03-26 19:39:37 +01:00
backend_ch_cnt = ch_cnt ;
backend_channel_layout = ch_layout ;
2022-07-09 15:25:28 +02:00
audio_channels = static_cast < u32 > ( req_ch_cnt ) ;
2022-05-05 15:47:44 +02:00
audio_sampling_rate = static_cast < u32 > ( freq ) ;
2022-01-05 09:26:12 +01:00
audio_block_period = AUDIO_BUFFER_SAMPLES * 1'000'000 / audio_sampling_rate ;
2022-05-05 15:47:44 +02:00
audio_sample_size = static_cast < u32 > ( sample_size ) ;
audio_min_buffer_duration = cb_frame_len + u32 { AUDIO_BUFFER_SAMPLES } * 2.0 / audio_sampling_rate ; // Add 2 blocks to allow jitter compensation
2020-06-20 02:44:32 +02:00
audio_buffer_length = AUDIO_BUFFER_SAMPLES * audio_channels ;
2022-01-05 09:26:12 +01:00
desired_buffer_duration = std : : max ( static_cast < s64 > ( audio_min_buffer_duration * 1000 ) , raw . desired_buffer_duration ) * 1000llu ;
2021-11-29 12:52:02 +01:00
buffering_enabled = raw . buffering_enabled & & raw . renderer ! = audio_renderer : : null ;
2020-06-20 02:44:32 +02:00
minimum_block_period = audio_block_period / 2 ;
maximum_block_period = ( 6 * audio_block_period ) / 5 ;
desired_full_buffers = buffering_enabled ? static_cast < u32 > ( desired_buffer_duration / audio_block_period ) + 3 : 2 ;
2025-10-01 09:17:25 +02:00
if ( desired_full_buffers > MAX_AUDIO_BUFFERS + EXTRA_AUDIO_BUFFERS )
{
cellAudio . error ( " %s: desired_full_buffers truncation: value = %d, frame size = %f, output channel count = %d, input channel count = %d, desired buffer duration = %d " ,
backend - > GetName ( ) , desired_full_buffers , cb_frame_len , ch_cnt , audio_channels , desired_buffer_duration ) ;
desired_full_buffers = MAX_AUDIO_BUFFERS - EXTRA_AUDIO_BUFFERS ;
}
2020-06-20 02:44:32 +02:00
num_allocated_buffers = desired_full_buffers + EXTRA_AUDIO_BUFFERS ;
fully_untouched_timeout = static_cast < u64 > ( audio_block_period ) * 2 ;
partially_untouched_timeout = static_cast < u64 > ( audio_block_period ) * 4 ;
2020-07-06 17:38:40 +02:00
const bool raw_time_stretching_enabled = buffering_enabled & & raw . enable_time_stretching & & ( raw . time_stretching_threshold > 0 ) ;
2020-06-20 02:44:32 +02:00
2022-01-05 09:26:12 +01:00
time_stretching_enabled = raw_time_stretching_enabled ;
2020-07-06 17:38:40 +02:00
time_stretching_threshold = raw . time_stretching_threshold / 100.0f ;
2020-06-20 02:44:32 +02:00
2018-12-21 03:13:22 +01:00
// Warn if audio backend does not support all requested features
2020-07-06 17:38:40 +02:00
if ( raw . buffering_enabled & & ! buffering_enabled )
2018-12-21 03:13:22 +01:00
{
cellAudio . error ( " Audio backend %s does not support buffering, this option will be ignored. " , backend - > GetName ( ) ) ;
2022-01-05 09:26:12 +01:00
if ( raw . enable_time_stretching )
{
cellAudio . error ( " Audio backend %s does not support time stretching, this option will be ignored. " , backend - > GetName ( ) ) ;
}
2018-12-21 03:13:22 +01:00
}
}
2018-12-16 18:40:50 +01:00
audio_ringbuffer : : audio_ringbuffer ( cell_audio_config & _cfg )
2025-04-05 21:50:45 +02:00
: backend ( _cfg . backend ) , cfg ( _cfg ) , buf_sz ( AUDIO_BUFFER_SAMPLES * _cfg . audio_channels )
2016-03-21 20:43:03 +01:00
{
2018-12-20 23:35:49 +01:00
// Initialize buffers
2019-01-09 23:21:55 +01:00
if ( cfg . num_allocated_buffers > MAX_AUDIO_BUFFERS )
2018-12-20 23:35:49 +01:00
{
fmt : : throw_exception ( " MAX_AUDIO_BUFFERS is too small " ) ;
}
2018-12-16 18:40:50 +01:00
for ( u32 i = 0 ; i < cfg . num_allocated_buffers ; i + + )
2018-12-20 23:35:49 +01:00
{
buffer [ i ] . reset ( new float [ buf_sz ] { } ) ;
}
// Init audio dumper if enabled
2022-01-05 09:26:12 +01:00
if ( cfg . raw . dump_to_file )
2018-12-20 23:35:49 +01:00
{
2022-05-05 15:47:44 +02:00
m_dump . Open ( static_cast < AudioChannelCnt > ( cfg . audio_channels ) , static_cast < AudioFreq > ( cfg . audio_sampling_rate ) , AudioSampleSize : : FLOAT ) ;
2018-12-16 18:40:50 +01:00
}
2022-01-05 09:26:12 +01:00
// Configure resampler
2022-05-05 15:47:44 +02:00
resampler . set_params ( static_cast < AudioChannelCnt > ( cfg . audio_channels ) , static_cast < AudioFreq > ( cfg . audio_sampling_rate ) ) ;
2022-01-05 09:26:12 +01:00
resampler . set_tempo ( RESAMPLER_MAX_FREQ_VAL ) ;
2021-11-24 19:41:05 +01:00
const f64 buffer_dur_mult = [ & ] ( )
2018-12-16 18:40:50 +01:00
{
2022-01-05 09:26:12 +01:00
if ( cfg . buffering_enabled )
2021-11-24 19:41:05 +01:00
{
2022-01-05 09:26:12 +01:00
return cfg . desired_buffer_duration / 1'000'000 .0 + 0.02 ; // Add 20ms to buffer to keep buffering algorithm happy
2021-11-24 19:41:05 +01:00
}
2017-10-31 23:49:54 +01:00
2022-01-05 09:26:12 +01:00
return cfg . audio_min_buffer_duration ;
2021-11-24 19:41:05 +01:00
} ( ) ;
2018-12-16 18:40:50 +01:00
2024-03-26 19:39:37 +01:00
cb_ringbuf . set_buf_size ( static_cast < u32 > ( cfg . backend_ch_cnt * cfg . audio_sampling_rate * cfg . audio_sample_size * buffer_dur_mult ) ) ;
2021-11-24 19:41:05 +01:00
backend - > SetWriteCallback ( std : : bind ( & audio_ringbuffer : : backend_write_callback , this , std : : placeholders : : _1 , std : : placeholders : : _2 ) ) ;
2022-10-13 14:39:16 +02:00
backend - > SetStateCallback ( std : : bind ( & audio_ringbuffer : : backend_state_callback , this , std : : placeholders : : _1 ) ) ;
2018-12-20 23:35:49 +01:00
}
2016-03-21 20:43:03 +01:00
2018-12-20 23:35:49 +01:00
audio_ringbuffer : : ~ audio_ringbuffer ( )
{
2021-11-24 19:41:05 +01:00
if ( get_backend_playing ( ) )
2018-12-16 18:40:50 +01:00
{
2021-11-24 19:41:05 +01:00
flush ( ) ;
2018-12-16 18:40:50 +01:00
}
2016-03-21 20:43:03 +01:00
2018-12-20 23:35:49 +01:00
backend - > Close ( ) ;
}
2018-12-16 22:12:58 +01:00
f32 audio_ringbuffer : : set_frequency_ratio ( f32 new_ratio )
{
2022-05-05 15:47:44 +02:00
frequency_ratio = static_cast < f32 > ( resampler . set_tempo ( new_ratio ) ) ;
2022-01-05 09:26:12 +01:00
2018-12-16 22:12:58 +01:00
return frequency_ratio ;
}
2022-01-05 09:26:12 +01:00
float * audio_ringbuffer : : get_buffer ( u32 num ) const
{
AUDIT ( num < cfg . num_allocated_buffers ) ;
AUDIT ( buffer [ num ] ) ;
return buffer [ num ] . get ( ) ;
}
2025-04-05 21:50:45 +02:00
u32 audio_ringbuffer : : backend_write_callback ( u32 size , void * buf )
2021-11-24 19:41:05 +01:00
{
2025-04-05 21:50:45 +02:00
if ( ! backend_active . observe ( ) )
backend_active = true ;
2021-11-24 19:41:05 +01:00
2022-05-05 15:47:44 +02:00
return static_cast < u32 > ( cb_ringbuf . pop ( buf , size , true ) ) ;
2021-11-24 19:41:05 +01:00
}
2022-10-13 14:39:16 +02:00
void audio_ringbuffer : : backend_state_callback ( AudioStateEvent event )
{
if ( event = = AudioStateEvent : : DEFAULT_DEVICE_MAYBE_CHANGED )
{
backend_device_changed = true ;
}
}
2021-04-09 21:12:47 +02:00
u64 audio_ringbuffer : : get_timestamp ( )
2020-10-30 21:26:22 +01:00
{
2021-08-25 14:26:41 +02:00
return get_system_time ( ) ;
2020-10-30 21:26:22 +01:00
}
2022-01-05 09:26:12 +01:00
float * audio_ringbuffer : : get_current_buffer ( ) const
{
return get_buffer ( cur_pos ) ;
}
u64 audio_ringbuffer : : get_enqueued_samples ( ) const
{
AUDIT ( cfg . buffering_enabled ) ;
2024-03-26 19:39:37 +01:00
const u64 ringbuf_samples = cb_ringbuf . get_used_size ( ) / ( cfg . audio_sample_size * cfg . backend_ch_cnt ) ;
2022-01-05 09:26:12 +01:00
if ( cfg . time_stretching_enabled )
{
return ringbuf_samples + resampler . samples_available ( ) ;
}
return ringbuf_samples ;
}
u64 audio_ringbuffer : : get_enqueued_playtime ( ) const
{
AUDIT ( cfg . buffering_enabled ) ;
return get_enqueued_samples ( ) * 1'000'000 / cfg . audio_sampling_rate ;
}
void audio_ringbuffer : : enqueue ( bool enqueue_silence , bool force )
2018-12-20 23:35:49 +01:00
{
2018-12-16 18:40:50 +01:00
AUDIT ( cur_pos < cfg . num_allocated_buffers ) ;
2016-03-21 20:43:03 +01:00
2018-12-20 23:35:49 +01:00
// Prepare buffer
2022-01-05 09:26:12 +01:00
static float silence_buffer [ u32 { AUDIO_MAX_CHANNELS_COUNT } * u32 { AUDIO_BUFFER_SAMPLES } ] { } ;
float * buf = silence_buffer ;
2018-12-20 23:35:49 +01:00
2022-01-05 09:26:12 +01:00
if ( ! enqueue_silence )
2016-03-21 20:43:03 +01:00
{
2018-12-16 18:40:50 +01:00
buf = buffer [ cur_pos ] . get ( ) ;
cur_pos = ( cur_pos + 1 ) % cfg . num_allocated_buffers ;
2016-03-21 20:43:03 +01:00
}
2022-01-05 09:26:12 +01:00
if ( ! backend_active . observe ( ) & & ! force )
2016-03-21 20:43:03 +01:00
{
2021-11-24 19:41:05 +01:00
// backend is not ready yet
2018-12-20 23:35:49 +01:00
return ;
}
2021-11-24 19:41:05 +01:00
// Enqueue audio
2022-01-05 09:26:12 +01:00
if ( cfg . time_stretching_enabled )
{
resampler . put_samples ( buf , AUDIO_BUFFER_SAMPLES ) ;
}
else
2021-11-24 19:41:05 +01:00
{
2022-01-05 09:26:12 +01:00
// Since time stretching step is skipped, we can commit to buffer directly
commit_data ( buf , AUDIO_BUFFER_SAMPLES ) ;
2018-12-21 03:13:22 +01:00
}
2018-12-20 23:35:49 +01:00
}
2022-01-05 09:26:12 +01:00
void audio_ringbuffer : : enqueue_silence ( u32 buf_count , bool force )
2018-12-20 23:35:49 +01:00
{
for ( u32 i = 0 ; i < buf_count ; i + + )
{
2022-01-05 09:26:12 +01:00
enqueue ( true , force ) ;
2018-12-20 23:35:49 +01:00
}
}
2022-01-05 09:26:12 +01:00
void audio_ringbuffer : : process_resampled_data ( )
2018-12-20 23:35:49 +01:00
{
2025-04-05 21:50:45 +02:00
if ( ! cfg . time_stretching_enabled )
return ;
2022-01-05 09:26:12 +01:00
2024-03-26 19:39:37 +01:00
const auto & [ buffer , samples ] = resampler . get_samples ( static_cast < u32 > ( cb_ringbuf . get_free_size ( ) / ( cfg . audio_sample_size * cfg . backend_ch_cnt ) ) ) ;
2023-03-05 20:26:22 +01:00
commit_data ( buffer , samples ) ;
2022-01-05 09:26:12 +01:00
}
void audio_ringbuffer : : commit_data ( f32 * buf , u32 sample_cnt )
{
2022-11-06 11:19:24 +01:00
const u32 sample_cnt_in = sample_cnt * cfg . audio_channels ;
2024-03-26 19:39:37 +01:00
const u32 sample_cnt_out = sample_cnt * cfg . backend_ch_cnt ;
2022-01-05 09:26:12 +01:00
2022-05-05 15:47:44 +02:00
// Dump audio if enabled
2022-11-06 11:19:24 +01:00
m_dump . WriteData ( buf , sample_cnt_in * static_cast < u32 > ( AudioSampleSize : : FLOAT ) ) ;
// Record audio if enabled
2023-11-29 19:12:06 +01:00
if ( g_recording_mode ! = recording_mode : : stopped )
2022-11-06 11:19:24 +01:00
{
2023-11-29 19:12:06 +01:00
utils : : video_provider & provider = g_fxo - > get < utils : : video_provider > ( ) ;
2023-11-30 03:02:52 +01:00
provider . present_samples ( reinterpret_cast < u8 * > ( buf ) , sample_cnt , cfg . audio_channels ) ;
2022-11-06 11:19:24 +01:00
}
2022-05-05 15:47:44 +02:00
2023-11-15 22:10:19 +01:00
// Downmix if necessary
2024-03-26 19:39:37 +01:00
AudioBackend : : downmix ( sample_cnt_in , cfg . audio_channels , cfg . backend_channel_layout , buf , buf ) ;
2022-07-09 15:25:28 +02:00
2022-01-05 09:26:12 +01:00
if ( cfg . backend - > get_convert_to_s16 ( ) )
2018-12-21 03:13:22 +01:00
{
2022-07-09 15:25:28 +02:00
AudioBackend : : convert_to_s16 ( sample_cnt_out , buf , buf ) ;
2018-12-21 03:13:22 +01:00
}
2018-12-20 23:35:49 +01:00
2022-07-09 15:25:28 +02:00
cb_ringbuf . push ( buf , sample_cnt_out * cfg . audio_sample_size ) ;
2022-01-05 09:26:12 +01:00
}
2018-12-16 22:12:58 +01:00
2022-01-05 09:26:12 +01:00
void audio_ringbuffer : : play ( )
{
if ( playing )
{
return ;
}
2018-12-20 23:35:49 +01:00
2022-01-05 09:26:12 +01:00
playing = true ;
2018-12-20 23:35:49 +01:00
play_timestamp = get_timestamp ( ) ;
backend - > Play ( ) ;
}
void audio_ringbuffer : : flush ( )
{
backend - > Pause ( ) ;
2022-05-05 15:47:44 +02:00
cb_ringbuf . writer_flush ( ) ;
2022-01-05 09:26:12 +01:00
resampler . flush ( ) ;
backend_active = false ;
2018-12-20 23:35:49 +01:00
playing = false ;
2022-01-05 09:26:12 +01:00
if ( frequency_ratio ! = RESAMPLER_MAX_FREQ_VAL )
2018-12-16 22:12:58 +01:00
{
2022-01-05 09:26:12 +01:00
frequency_ratio = set_frequency_ratio ( RESAMPLER_MAX_FREQ_VAL ) ;
2018-12-16 22:12:58 +01:00
}
2018-12-20 23:35:49 +01:00
}
2022-01-05 09:26:12 +01:00
u64 audio_ringbuffer : : update ( bool emu_is_paused )
2018-12-20 23:35:49 +01:00
{
// Check emulator pause state
2022-01-05 09:26:12 +01:00
if ( emu_is_paused )
2018-12-20 23:35:49 +01:00
{
// Emulator paused
2021-11-24 19:41:05 +01:00
if ( playing )
2016-03-21 20:43:03 +01:00
{
2021-11-24 19:41:05 +01:00
flush ( ) ;
2016-03-21 20:43:03 +01:00
}
2018-12-20 23:35:49 +01:00
}
2022-01-05 09:26:12 +01:00
else
2018-12-20 23:35:49 +01:00
{
// Emulator unpaused
2022-01-05 09:26:12 +01:00
play ( ) ;
2018-12-20 23:35:49 +01:00
}
2016-03-21 20:43:03 +01:00
2022-01-05 09:26:12 +01:00
// Prepare timestamp
2018-12-20 23:35:49 +01:00
const u64 timestamp = get_timestamp ( ) ;
// Store and return timestamp
update_timestamp = timestamp ;
return timestamp ;
}
void audio_port : : tag ( s32 offset )
{
auto port_buf = get_vm_ptr ( offset ) ;
// This tag will be used to make sure that the game has finished writing the audio for the next audio period
// We use -0.0f in case games check if the buffer is empty. -0.0f == 0.0f evaluates to true, but std::signbit can be used to distinguish them
const f32 tag = - 0.0f ;
2022-05-08 20:09:31 +02:00
const u32 tag_first_pos = num_channels = = 2 ? PORT_BUFFER_TAG_FIRST_2CH : PORT_BUFFER_TAG_FIRST_8CH ;
const u32 tag_delta = num_channels = = 2 ? PORT_BUFFER_TAG_DELTA_2CH : PORT_BUFFER_TAG_DELTA_8CH ;
2018-12-20 23:35:49 +01:00
for ( u32 tag_pos = tag_first_pos , tag_nr = 0 ; tag_nr < PORT_BUFFER_TAG_COUNT ; tag_pos + = tag_delta , tag_nr + + )
{
port_buf [ tag_pos ] = tag ;
2018-12-21 03:13:22 +01:00
last_tag_value [ tag_nr ] = - 0.0f ;
2018-12-20 23:35:49 +01:00
}
2021-05-22 20:46:10 +02:00
prev_touched_tag_nr = - 1 ;
2018-12-20 23:35:49 +01:00
}
2022-07-04 15:02:17 +02:00
cell_audio_thread : : cell_audio_thread ( utils : : serial & ar )
: cell_audio_thread ( )
{
ar ( init ) ;
if ( ! init )
{
return ;
}
ar ( key_count , event_period ) ;
keys . resize ( ar ) ;
for ( key_info & k : keys )
{
ar ( k . start_period , k . flags , k . source ) ;
2023-10-03 18:10:52 +02:00
k . port = lv2_event_queue : : load_ptr ( ar , k . port , " audio " ) ;
2022-07-04 15:02:17 +02:00
}
ar ( ports ) ;
}
void cell_audio_thread : : save ( utils : : serial & ar )
{
ar ( init ) ;
if ( ! init )
{
return ;
}
USING_SERIALIZATION_VERSION ( cellAudio ) ;
ar ( key_count , event_period ) ;
ar ( keys . size ( ) ) ;
for ( const key_info & k : keys )
{
ar ( k . start_period , k . flags , k . source ) ;
lv2_event_queue : : save_ptr ( ar , k . port . get ( ) ) ;
}
ar ( ports ) ;
}
2018-12-20 23:35:49 +01:00
std : : tuple < u32 , u32 , u32 , u32 > cell_audio_thread : : count_port_buffer_tags ( )
{
2018-12-16 18:40:50 +01:00
AUDIT ( cfg . buffering_enabled ) ;
2018-12-20 23:35:49 +01:00
u32 active = 0 ;
u32 in_progress = 0 ;
u32 untouched = 0 ;
u32 incomplete = 0 ;
2023-03-05 20:26:22 +01:00
for ( audio_port & port : ports )
2018-12-20 23:35:49 +01:00
{
2025-04-05 21:50:45 +02:00
if ( port . state ! = audio_port_state : : started )
continue ;
2018-12-20 23:35:49 +01:00
active + + ;
auto port_buf = port . get_vm_ptr ( ) ;
// Find the last tag that has been touched
2022-05-08 20:09:31 +02:00
const u32 tag_first_pos = port . num_channels = = 2 ? PORT_BUFFER_TAG_FIRST_2CH : PORT_BUFFER_TAG_FIRST_8CH ;
const u32 tag_delta = port . num_channels = = 2 ? PORT_BUFFER_TAG_DELTA_2CH : PORT_BUFFER_TAG_DELTA_8CH ;
2018-12-20 23:35:49 +01:00
u32 last_touched_tag_nr = port . prev_touched_tag_nr ;
bool retouched = false ;
for ( u32 tag_pos = tag_first_pos , tag_nr = 0 ; tag_nr < PORT_BUFFER_TAG_COUNT ; tag_pos + = tag_delta , tag_nr + + )
{
2018-12-21 03:13:22 +01:00
const f32 val = port_buf [ tag_pos ] ;
f32 & last_val = port . last_tag_value [ tag_nr ] ;
2018-12-20 23:35:49 +01:00
2018-12-21 03:13:22 +01:00
if ( val ! = last_val | | ( last_val = = - 0.0f & & std : : signbit ( last_val ) & & ! std : : signbit ( val ) ) )
2018-12-20 23:35:49 +01:00
{
2018-12-21 03:13:22 +01:00
last_val = val ;
2018-12-20 23:35:49 +01:00
2021-05-22 20:46:10 +02:00
retouched | = ( tag_nr < = port . prev_touched_tag_nr ) & & port . prev_touched_tag_nr ! = umax ;
2018-12-20 23:35:49 +01:00
last_touched_tag_nr = tag_nr ;
}
}
// Decide whether the buffer is untouched, in progress, incomplete, or complete
2021-05-22 20:46:10 +02:00
if ( last_touched_tag_nr = = umax )
2018-12-20 23:35:49 +01:00
{
// no tag has been touched yet
untouched + + ;
}
else if ( last_touched_tag_nr = = PORT_BUFFER_TAG_COUNT - 1 )
{
if ( retouched )
{
// we retouched, so wait at least once more to make sure no more tags get touched
in_progress + + ;
}
2019-01-09 23:21:55 +01:00
2018-12-20 23:35:49 +01:00
// buffer has been completely filled
port . prev_touched_tag_nr = last_touched_tag_nr ;
}
else if ( last_touched_tag_nr = = port . prev_touched_tag_nr )
{
2019-01-09 23:21:55 +01:00
if ( retouched )
{
// we retouched, so wait at least once more to make sure no more tags get touched
in_progress + + ;
}
else
{
// hasn't been touched since the last call
incomplete + + ;
}
2018-12-20 23:35:49 +01:00
}
else
{
// the touched tag changed since the last call
in_progress + + ;
port . prev_touched_tag_nr = last_touched_tag_nr ;
}
}
return std : : make_tuple ( active , in_progress , untouched , incomplete ) ;
}
void cell_audio_thread : : reset_ports ( s32 offset )
{
2019-01-09 23:21:55 +01:00
// Memset buffer to 0 and tag
2023-03-05 20:26:22 +01:00
for ( audio_port & port : ports )
2018-12-20 23:35:49 +01:00
{
2025-04-05 21:50:45 +02:00
if ( port . state ! = audio_port_state : : started )
continue ;
2018-12-20 23:35:49 +01:00
memset ( port . get_vm_ptr ( offset ) , 0 , port . block_size ( ) * sizeof ( float ) ) ;
2018-12-16 18:40:50 +01:00
if ( cfg . buffering_enabled )
2018-12-20 23:35:49 +01:00
{
port . tag ( offset ) ;
}
}
}
2022-01-05 09:26:12 +01:00
void cell_audio_thread : : advance ( u64 timestamp )
2018-12-20 23:35:49 +01:00
{
2022-01-05 09:26:12 +01:00
ringbuffer - > process_resampled_data ( ) ;
2019-09-15 14:19:59 +02:00
std : : unique_lock lock ( mutex ) ;
2018-12-20 23:35:49 +01:00
// update ports
2022-01-05 09:26:12 +01:00
reset_ports ( 0 ) ;
2018-12-20 23:35:49 +01:00
2023-03-05 20:26:22 +01:00
for ( audio_port & port : ports )
2018-12-20 23:35:49 +01:00
{
2025-04-05 21:50:45 +02:00
if ( port . state ! = audio_port_state : : started )
continue ;
2018-12-20 23:35:49 +01:00
port . global_counter = m_counter ;
port . active_counter + + ;
port . timestamp = timestamp ;
port . cur_pos = port . position ( 1 ) ;
2022-05-08 20:09:31 +02:00
* port . index = port . cur_pos ;
2018-12-20 23:35:49 +01:00
}
2018-12-16 22:12:58 +01:00
if ( cfg . buffering_enabled )
{
// Calculate rolling average of enqueued playtime
2022-01-05 09:26:12 +01:00
m_average_playtime = cfg . period_average_alpha * ringbuffer - > get_enqueued_playtime ( ) + ( 1.0f - cfg . period_average_alpha ) * m_average_playtime ;
2018-12-16 22:12:58 +01:00
}
2018-12-20 23:35:49 +01:00
m_counter + + ;
m_last_period_end = timestamp ;
m_dynamic_period = 0 ;
// send aftermix event (normal audio event)
2024-12-22 19:59:48 +01:00
std : : array < shared_ptr < lv2_event_queue > , MAX_AUDIO_EVENT_QUEUES > queues ;
2018-12-20 23:35:49 +01:00
u32 queue_count = 0 ;
2020-02-09 06:47:02 +01:00
event_period + + ;
2023-03-05 20:26:22 +01:00
for ( const key_info & key_inf : keys )
2018-12-20 23:35:49 +01:00
{
2020-06-14 06:20:40 +02:00
if ( key_inf . flags & CELL_AUDIO_EVENTFLAG_NOMIX )
2018-12-20 23:35:49 +01:00
{
2020-06-14 06:20:40 +02:00
continue ;
}
2020-02-09 06:47:02 +01:00
2021-05-14 14:18:30 +02:00
if ( ( queues [ queue_count ] = key_inf . port ) )
2020-06-14 06:20:40 +02:00
{
2020-02-09 06:47:02 +01:00
u32 periods = 1 ;
if ( key_inf . flags & CELL_AUDIO_EVENTFLAG_DECIMATE_2 )
{
periods * = 2 ;
}
if ( key_inf . flags & CELL_AUDIO_EVENTFLAG_DECIMATE_4 )
{
// If both flags are set periods is set to x8
periods * = 4 ;
}
if ( ( event_period ^ key_inf . start_period ) & ( periods - 1 ) )
{
// The time has not come for this key to receive event
2020-06-04 19:54:31 +02:00
queues [ queue_count ] . reset ( ) ;
2020-02-09 06:47:02 +01:00
continue ;
}
event_sources [ queue_count ] = key_inf . source ;
2023-03-05 23:33:35 +01:00
event_data3 [ queue_count ] = ( key_inf . flags & CELL_AUDIO_EVENTFLAG_BEFOREMIX ) ? key_inf . source : 0 ;
2020-06-04 19:54:31 +02:00
queue_count + + ;
2016-03-21 20:43:03 +01:00
}
2018-12-20 23:35:49 +01:00
}
2019-09-15 14:19:59 +02:00
lock . unlock ( ) ;
2018-12-20 23:35:49 +01:00
for ( u32 i = 0 ; i < queue_count ; i + + )
{
2022-08-04 12:13:51 +02:00
lv2_obj : : notify_all_t notify ;
2023-03-05 23:33:35 +01:00
queues [ i ] - > send ( event_sources [ i ] , CELL_AUDIO_EVENT_MIX , 0 , event_data3 [ i ] ) ;
2018-12-20 23:35:49 +01:00
}
}
2016-03-21 20:43:03 +01:00
2020-06-20 02:44:32 +02:00
namespace audio
{
2020-07-06 17:38:40 +02:00
cell_audio_config : : raw_config get_raw_config ( )
{
2025-04-05 21:50:45 +02:00
return {
2022-07-08 17:13:38 +02:00
. audio_device = g_cfg . audio . audio_device ,
2020-07-06 17:38:40 +02:00
. buffering_enabled = static_cast < bool > ( g_cfg . audio . enable_buffering ) ,
. desired_buffer_duration = g_cfg . audio . desired_buffer_duration ,
. enable_time_stretching = static_cast < bool > ( g_cfg . audio . enable_time_stretching ) ,
. time_stretching_threshold = g_cfg . audio . time_stretching_threshold ,
2021-11-24 19:41:05 +01:00
. convert_to_s16 = static_cast < bool > ( g_cfg . audio . convert_to_s16 ) ,
2022-01-05 09:26:12 +01:00
. dump_to_file = static_cast < bool > ( g_cfg . audio . dump_to_file ) ,
2024-03-26 19:39:37 +01:00
. channel_layout = g_cfg . audio . channel_layout ,
2021-11-24 19:41:05 +01:00
. renderer = g_cfg . audio . renderer ,
2025-04-05 21:50:45 +02:00
. provider = g_cfg . audio . provider } ;
2020-07-06 17:38:40 +02:00
}
2022-06-18 17:39:33 +02:00
void configure_audio ( bool force_reset )
2020-06-20 02:44:32 +02:00
{
2021-11-24 19:41:05 +01:00
if ( g_cfg . audio . provider ! = audio_provider : : cell_audio )
{
return ;
}
2021-03-02 12:59:19 +01:00
if ( auto & g_audio = g_fxo - > get < cell_audio > ( ) ; g_fxo - > is_init < cell_audio > ( ) )
2020-06-20 02:44:32 +02:00
{
2020-07-06 17:38:40 +02:00
// Only reboot the audio renderer if a relevant setting changed
2023-03-05 20:26:22 +01:00
const cell_audio_config : : raw_config new_raw = get_raw_config ( ) ;
2020-07-06 17:38:40 +02:00
2023-03-05 20:26:22 +01:00
if ( const cell_audio_config : : raw_config raw = g_audio . cfg . raw ;
2022-06-18 17:39:33 +02:00
force_reset | |
2022-07-08 17:13:38 +02:00
raw . audio_device ! = new_raw . audio_device | |
2020-07-06 17:38:40 +02:00
raw . desired_buffer_duration ! = new_raw . desired_buffer_duration | |
raw . buffering_enabled ! = new_raw . buffering_enabled | |
raw . time_stretching_threshold ! = new_raw . time_stretching_threshold | |
raw . enable_time_stretching ! = new_raw . enable_time_stretching | |
2021-11-24 19:41:05 +01:00
raw . convert_to_s16 ! = new_raw . convert_to_s16 | |
2022-01-05 09:26:12 +01:00
raw . renderer ! = new_raw . renderer | |
raw . dump_to_file ! = new_raw . dump_to_file )
2020-07-06 17:38:40 +02:00
{
2022-05-05 15:47:44 +02:00
std : : lock_guard lock { g_audio . emu_cfg_upd_m } ;
g_audio . cfg . new_raw = new_raw ;
2021-11-24 19:41:05 +01:00
g_audio . m_update_configuration = raw . renderer ! = new_raw . renderer ? audio_backend_update : : ALL : audio_backend_update : : PARAM ;
2020-07-06 17:38:40 +02:00
}
2020-06-20 02:44:32 +02:00
}
}
2025-04-05 21:50:45 +02:00
} // namespace audio
2020-06-20 02:44:32 +02:00
2021-11-24 19:41:05 +01:00
void cell_audio_thread : : update_config ( bool backend_changed )
2020-06-20 02:44:32 +02:00
{
std : : lock_guard lock ( mutex ) ;
// Clear ringbuffer
ringbuffer . reset ( ) ;
// Reload config
2021-11-24 19:41:05 +01:00
cfg . reset ( backend_changed ) ;
2020-06-20 02:44:32 +02:00
// Allocate ringbuffer
ringbuffer . reset ( new audio_ringbuffer ( cfg ) ) ;
2021-11-24 19:41:05 +01:00
// Reset thread state
reset_counters ( ) ;
}
void cell_audio_thread : : reset_counters ( )
{
m_counter = 0 ;
m_start_time = ringbuffer - > get_timestamp ( ) ;
m_last_period_end = m_start_time ;
m_dynamic_period = 0 ;
2022-01-05 09:26:12 +01:00
m_audio_should_restart = true ;
2020-06-20 02:44:32 +02:00
}
2021-11-29 12:52:02 +01:00
cell_audio_thread : : cell_audio_thread ( )
2018-12-20 23:35:49 +01:00
{
2023-12-28 17:39:42 +01:00
}
2023-12-14 20:26:59 +01:00
2023-12-28 17:39:42 +01:00
void cell_audio_thread : : operator ( ) ( )
{
2022-05-08 01:44:29 +02:00
// Initialize loop variables (regardless of provider in order to initialize timestamps)
reset_counters ( ) ;
2021-11-24 19:41:05 +01:00
if ( cfg . raw . provider ! = audio_provider : : cell_audio )
{
return ;
}
// Init audio config
cfg . reset ( ) ;
2018-12-20 23:35:49 +01:00
// Allocate ringbuffer
2018-12-16 18:40:50 +01:00
ringbuffer . reset ( new audio_ringbuffer ( cfg ) ) ;
2021-11-29 12:52:02 +01:00
thread_ctrl : : scoped_priority high_prio ( + 1 ) ;
2018-12-20 23:35:49 +01:00
2025-01-23 06:59:33 +01:00
while ( Emu . IsPausedOrReady ( ) )
2022-07-04 15:02:17 +02:00
{
thread_ctrl : : wait_for ( 5000 ) ;
}
2018-12-20 23:35:49 +01:00
u32 untouched_expected = 0 ;
2022-10-03 08:58:36 +02:00
u32 loop_count = 0 ;
2018-12-20 23:35:49 +01:00
// Main cellAudio loop
2019-10-12 20:12:47 +02:00
while ( thread_ctrl : : state ( ) ! = thread_state : : aborting )
2018-12-20 23:35:49 +01:00
{
2022-10-03 08:58:36 +02:00
loop_count + + ;
2023-03-05 20:26:22 +01:00
const audio_backend_update update_req = m_update_configuration . observe ( ) ;
2021-11-24 19:41:05 +01:00
if ( update_req ! = audio_backend_update : : NONE )
2020-06-20 02:44:32 +02:00
{
2021-08-23 21:33:20 +02:00
cellAudio . warning ( " Updating cell_audio_thread configuration " ) ;
2022-05-05 15:47:44 +02:00
{
std : : lock_guard lock { emu_cfg_upd_m } ;
cfg . raw = cfg . new_raw ;
m_update_configuration = audio_backend_update : : NONE ;
}
2021-11-24 19:41:05 +01:00
update_config ( update_req = = audio_backend_update : : ALL ) ;
}
2022-01-05 09:26:12 +01:00
const bool emu_paused = Emu . IsPaused ( ) ;
2022-10-03 08:58:36 +02:00
const u64 timestamp = ringbuffer - > update ( emu_paused | | m_backend_failed ) ;
2016-03-21 20:43:03 +01:00
2022-01-05 09:26:12 +01:00
if ( emu_paused )
2016-03-21 20:43:03 +01:00
{
2022-01-05 09:26:12 +01:00
m_audio_should_restart = true ;
ringbuffer - > flush ( ) ;
2018-12-20 23:35:49 +01:00
thread_ctrl : : wait_for ( 10000 ) ;
continue ;
}
2016-03-21 20:43:03 +01:00
2023-03-05 23:33:35 +01:00
// TODO: (no idea how much of this is already implemented)
// The hardware heartbeat interval of libaudio is ~5.3ms.
// As soon as one interval starts, libaudio waits for ~2.6ms (half of the interval) before it mixes the audio.
// There are 2 different types of games:
// - Normal games:
// Once the audio was mixed, we send the CELL_AUDIO_EVENT_MIX event and the game can process audio.
// - Latency sensitive games:
// If CELL_AUDIO_EVENTFLAG_BEFOREMIX is specified, we immediately send the CELL_AUDIO_EVENT_MIX event and the game can process audio.
// We then have to wait for a maximum of ~2.6ms for cellAudioSendAck and then mix immediately.
2016-03-21 20:43:03 +01:00
2018-12-20 23:35:49 +01:00
const u64 time_since_last_period = timestamp - m_last_period_end ;
2016-03-21 20:43:03 +01:00
2022-01-05 09:26:12 +01:00
// Handle audio restart
if ( m_audio_should_restart )
{
// align to 5.(3)ms on global clock - some games seem to prefer this
const s64 audio_period_alignment_delta = ( timestamp - m_start_time ) % cfg . audio_block_period ;
if ( audio_period_alignment_delta > cfg . period_comparison_margin )
{
thread_ctrl : : wait_for ( audio_period_alignment_delta - cfg . period_comparison_margin ) ;
}
if ( cfg . buffering_enabled )
{
// Restart algorithm
cellAudio . trace ( " restarting audio " ) ;
ringbuffer - > enqueue_silence ( cfg . desired_full_buffers , true ) ;
finish_port_volume_stepping ( ) ;
m_average_playtime = static_cast < f32 > ( ringbuffer - > get_enqueued_playtime ( ) ) ;
untouched_expected = 0 ;
}
m_audio_should_restart = false ;
continue ;
}
2022-10-03 08:58:36 +02:00
bool operational = ringbuffer - > get_operational_status ( ) ;
if ( ! operational & & loop_count % 128 = = 0 )
{
update_config ( true ) ;
operational = ringbuffer - > get_operational_status ( ) ;
}
if ( ringbuffer - > device_changed ( ) )
{
cellAudio . warning ( " Default device changed, attempting to switch... " ) ;
update_config ( false ) ;
if ( operational ! = ringbuffer - > get_operational_status ( ) )
{
continue ;
}
}
if ( ! m_backend_failed & & ! operational )
{
cellAudio . error ( " Backend stopped unexpectedly (likely device change). Attempting to recover... " ) ;
m_backend_failed = true ;
}
else if ( m_backend_failed & & operational )
{
cellAudio . success ( " Backend recovered " ) ;
m_backend_failed = false ;
}
2022-10-13 14:39:16 +02:00
2018-12-16 18:40:50 +01:00
if ( ! cfg . buffering_enabled )
2018-12-20 23:35:49 +01:00
{
2018-12-16 18:40:50 +01:00
const u64 period_end = ( m_counter * cfg . audio_block_period ) + m_start_time ;
2018-12-20 23:35:49 +01:00
const s64 time_left = period_end - timestamp ;
2016-03-21 20:43:03 +01:00
2018-12-16 18:40:50 +01:00
if ( time_left > cfg . period_comparison_margin )
2016-03-21 20:43:03 +01:00
{
2024-10-04 11:44:24 +02:00
thread_ctrl : : wait_for ( time_left - cfg . period_comparison_margin ) ;
2018-12-20 23:35:49 +01:00
continue ;
}
}
else
{
2018-12-16 22:12:58 +01:00
const u64 enqueued_samples = ringbuffer - > get_enqueued_samples ( ) ;
2022-01-05 09:26:12 +01:00
const f32 frequency_ratio = ringbuffer - > get_frequency_ratio ( ) ;
const u64 enqueued_playtime = ringbuffer - > get_enqueued_playtime ( ) ;
2018-12-16 22:12:58 +01:00
const u64 enqueued_buffers = enqueued_samples / AUDIO_BUFFER_SAMPLES ;
2016-03-21 20:43:03 +01:00
2018-12-20 23:35:49 +01:00
const auto tag_info = count_port_buffer_tags ( ) ;
const u32 active_ports = std : : get < 0 > ( tag_info ) ;
2025-04-05 21:50:45 +02:00
const u32 in_progress = std : : get < 1 > ( tag_info ) ;
const u32 untouched = std : : get < 2 > ( tag_info ) ;
const u32 incomplete = std : : get < 3 > ( tag_info ) ;
2016-03-21 20:43:03 +01:00
2022-01-05 09:26:12 +01:00
// Ratio between the rolling average of the audio period, and the desired audio period
const f32 average_playtime_ratio = m_average_playtime / cfg . audio_buffer_length ;
// Use the above average ratio to decide how much buffer we should be aiming for
f32 desired_duration_adjusted = cfg . desired_buffer_duration + ( cfg . audio_block_period / 2.0f ) ;
if ( average_playtime_ratio < 1.0f )
2016-03-21 20:43:03 +01:00
{
2022-01-05 09:26:12 +01:00
desired_duration_adjusted / = std : : max ( average_playtime_ratio , 0.25f ) ;
2018-12-16 22:12:58 +01:00
}
2019-01-12 20:14:57 +01:00
2022-01-05 09:26:12 +01:00
if ( cfg . time_stretching_enabled )
{
2018-12-16 22:12:58 +01:00
// 1.0 means exactly as desired
// <1.0 means not as full as desired
// >1.0 means more full than desired
const f32 desired_duration_rate = enqueued_playtime / desired_duration_adjusted ;
2022-01-05 09:26:12 +01:00
// update frequency ratio if necessary
if ( desired_duration_rate < cfg . time_stretching_threshold )
2018-12-16 22:12:58 +01:00
{
2022-01-05 09:26:12 +01:00
const f32 normalized_desired_duration_rate = desired_duration_rate / cfg . time_stretching_threshold ;
// change frequency ratio in steps
2022-05-05 15:47:44 +02:00
const f32 req_time_stretching_step = ( normalized_desired_duration_rate + frequency_ratio ) / 2.0f ;
if ( std : : abs ( req_time_stretching_step - frequency_ratio ) > cfg . time_stretching_step )
2022-01-05 09:26:12 +01:00
{
ringbuffer - > set_frequency_ratio ( req_time_stretching_step ) ;
}
2018-12-16 22:12:58 +01:00
}
2022-01-05 09:26:12 +01:00
else if ( frequency_ratio ! = RESAMPLER_MAX_FREQ_VAL )
2018-12-16 22:12:58 +01:00
{
2022-01-05 09:26:12 +01:00
ringbuffer - > set_frequency_ratio ( RESAMPLER_MAX_FREQ_VAL ) ;
2016-03-21 20:43:03 +01:00
}
}
2018-12-20 23:35:49 +01:00
2022-01-05 09:26:12 +01:00
// 1.0 means exactly as desired
// <1.0 means not as full as desired
// >1.0 means more full than desired
const f32 desired_duration_rate = enqueued_playtime / desired_duration_adjusted ;
if ( desired_duration_rate > = 1.0f )
{
// more full than desired
const f32 multiplier = 1.0f / desired_duration_rate ;
m_dynamic_period = cfg . maximum_block_period - static_cast < u64 > ( ( cfg . maximum_block_period - cfg . audio_block_period ) * multiplier ) ;
}
else
{
// not as full as desired
const f32 multiplier = desired_duration_rate * desired_duration_rate ; // quite aggressive, but helps more times than it hurts
m_dynamic_period = cfg . minimum_block_period + static_cast < u64 > ( ( cfg . audio_block_period - cfg . minimum_block_period ) * multiplier ) ;
}
const s64 time_left = m_dynamic_period - time_since_last_period ;
2018-12-16 18:40:50 +01:00
if ( time_left > cfg . period_comparison_margin )
2016-03-21 20:43:03 +01:00
{
2024-10-04 11:44:24 +02:00
thread_ctrl : : wait_for ( time_left - cfg . period_comparison_margin ) ;
2018-12-20 23:35:49 +01:00
continue ;
}
2016-03-21 20:43:03 +01:00
2018-12-20 23:35:49 +01:00
// Fast path for 0 ports active
if ( active_ports = = 0 )
{
// no need to mix, just enqueue silence and advance time
2019-01-09 23:21:55 +01:00
cellAudio . trace ( " enqueuing silence: no active ports, enqueued_buffers=%llu " , enqueued_buffers ) ;
2022-01-05 09:26:12 +01:00
ringbuffer - > enqueue_silence ( ) ;
2018-12-20 23:35:49 +01:00
untouched_expected = 0 ;
advance ( timestamp ) ;
continue ;
}
2016-03-21 20:43:03 +01:00
2018-12-20 23:35:49 +01:00
// Wait until buffers have been touched
2025-04-05 21:50:45 +02:00
// cellAudio.error("active=%u, in_progress=%u, untouched=%u, incomplete=%u", active_ports, in_progress, untouched, incomplete);
2018-12-25 22:41:17 +01:00
if ( untouched > untouched_expected )
2018-12-20 23:35:49 +01:00
{
2019-01-09 23:21:55 +01:00
// Games may sometimes "skip" audio periods entirely if they're falling behind (a sort of "frameskip" for audio)
// As such, if the game doesn't touch buffers for too long we advance time hoping the game recovers
if (
( untouched = = active_ports & & time_since_last_period > cfg . fully_untouched_timeout ) | |
2025-04-05 21:50:45 +02:00
( time_since_last_period > cfg . partially_untouched_timeout ) | | g_cfg . audio . disable_sampling_skip )
2018-12-25 22:41:17 +01:00
{
2019-01-09 23:21:55 +01:00
// There's no audio in the buffers, simply advance time and hope the game recovers
2018-12-25 22:41:17 +01:00
cellAudio . trace ( " advancing time: untouched=%u/%u (expected=%u), enqueued_buffers=%llu " , untouched , active_ports , untouched_expected , enqueued_buffers ) ;
untouched_expected = untouched ;
advance ( timestamp ) ;
continue ;
2016-03-21 20:43:03 +01:00
}
2019-01-09 23:21:55 +01:00
cellAudio . trace ( " waiting: untouched=%u/%u (expected=%u), enqueued_buffers=%llu " , untouched , active_ports , untouched_expected , enqueued_buffers ) ;
thread_ctrl : : wait_for ( 1000 ) ;
continue ;
}
// Fast-path for when there is no audio in the buffers
if ( untouched = = active_ports )
{
// There's no audio in the buffers, simply advance time
cellAudio . trace ( " enqueuing silence: untouched=%u/%u (expected=%u), enqueued_buffers=%llu " , untouched , active_ports , untouched_expected , enqueued_buffers ) ;
2022-01-05 09:26:12 +01:00
ringbuffer - > enqueue_silence ( ) ;
2019-01-09 23:21:55 +01:00
untouched_expected = untouched ;
advance ( timestamp ) ;
continue ;
2016-03-21 20:43:03 +01:00
}
2019-01-12 20:14:57 +01:00
2018-12-20 23:35:49 +01:00
// Wait for buffer(s) to be completely filled
if ( in_progress > 0 )
2016-03-21 20:43:03 +01:00
{
2018-12-23 23:58:12 +01:00
cellAudio . trace ( " waiting: in_progress=%u/%u, enqueued_buffers=%u " , in_progress , active_ports , enqueued_buffers ) ;
2018-12-20 23:35:49 +01:00
thread_ctrl : : wait_for ( 500 ) ;
continue ;
2016-03-21 20:43:03 +01:00
}
2025-04-05 21:50:45 +02:00
// cellAudio.error("active=%u, untouched=%u, in_progress=%d, incomplete=%d, enqueued_buffers=%u", active_ports, untouched, in_progress, incomplete, enqueued_buffers);
2016-03-21 20:43:03 +01:00
2018-12-20 23:35:49 +01:00
// Store number of untouched buffers for future reference
untouched_expected = untouched ;
2016-03-21 20:43:03 +01:00
2019-01-09 23:21:55 +01:00
// Log if we enqueued untouched/incomplete buffers
2018-12-20 23:35:49 +01:00
if ( untouched > 0 | | incomplete > 0 )
2016-03-21 20:43:03 +01:00
{
2018-12-23 23:58:12 +01:00
cellAudio . trace ( " enqueueing: untouched=%u/%u (expected=%u), incomplete=%u/%u enqueued_buffers=%llu " , untouched , active_ports , untouched_expected , incomplete , active_ports , enqueued_buffers ) ;
2017-04-09 22:43:53 +02:00
}
2016-03-21 20:43:03 +01:00
}
2018-12-20 23:35:49 +01:00
// Mix
2022-06-06 18:18:13 +02:00
float * buf = ringbuffer - > get_current_buffer ( ) ;
2020-06-01 20:36:45 +02:00
2022-06-19 17:08:03 +02:00
switch ( cfg . audio_channels )
2018-12-20 23:35:49 +01:00
{
2022-06-19 17:08:03 +02:00
case 2 :
switch ( cfg . audio_downmix )
{
case AudioChannelCnt : : SURROUND_7_1 :
mix < AudioChannelCnt : : STEREO , AudioChannelCnt : : SURROUND_7_1 > ( buf ) ;
break ;
case AudioChannelCnt : : STEREO :
mix < AudioChannelCnt : : STEREO , AudioChannelCnt : : STEREO > ( buf ) ;
break ;
case AudioChannelCnt : : SURROUND_5_1 :
mix < AudioChannelCnt : : STEREO , AudioChannelCnt : : SURROUND_5_1 > ( buf ) ;
break ;
}
2020-06-01 20:36:45 +02:00
break ;
2022-06-19 17:08:03 +02:00
case 6 :
switch ( cfg . audio_downmix )
{
case AudioChannelCnt : : SURROUND_7_1 :
mix < AudioChannelCnt : : SURROUND_5_1 , AudioChannelCnt : : SURROUND_7_1 > ( buf ) ;
break ;
case AudioChannelCnt : : STEREO :
mix < AudioChannelCnt : : SURROUND_5_1 , AudioChannelCnt : : STEREO > ( buf ) ;
break ;
case AudioChannelCnt : : SURROUND_5_1 :
mix < AudioChannelCnt : : SURROUND_5_1 , AudioChannelCnt : : SURROUND_5_1 > ( buf ) ;
break ;
}
2020-06-01 20:36:45 +02:00
break ;
2022-06-19 17:08:03 +02:00
case 8 :
switch ( cfg . audio_downmix )
{
case AudioChannelCnt : : SURROUND_7_1 :
mix < AudioChannelCnt : : SURROUND_7_1 , AudioChannelCnt : : SURROUND_7_1 > ( buf ) ;
break ;
case AudioChannelCnt : : STEREO :
mix < AudioChannelCnt : : SURROUND_7_1 , AudioChannelCnt : : STEREO > ( buf ) ;
break ;
case AudioChannelCnt : : SURROUND_5_1 :
mix < AudioChannelCnt : : SURROUND_7_1 , AudioChannelCnt : : SURROUND_5_1 > ( buf ) ;
break ;
}
2020-06-01 20:36:45 +02:00
break ;
2022-06-19 17:08:03 +02:00
default :
2023-11-30 03:02:52 +01:00
fmt : : throw_exception ( " Unsupported channel count in cell_audio_config: %d " , cfg . audio_channels ) ;
2018-12-20 23:35:49 +01:00
}
// Enqueue
ringbuffer - > enqueue ( ) ;
// Advance time
advance ( timestamp ) ;
}
// Destroy ringbuffer
ringbuffer . reset ( ) ;
}
2021-12-01 22:36:01 +01:00
audio_port * cell_audio_thread : : open_port ( )
{
for ( u32 i = 0 ; i < AUDIO_PORT_COUNT ; i + + )
{
if ( ports [ i ] . state . compare_and_swap_test ( audio_port_state : : closed , audio_port_state : : opened ) )
{
return & ports [ i ] ;
}
}
return nullptr ;
}
2022-06-19 17:08:03 +02:00
template < AudioChannelCnt channels , AudioChannelCnt downmix >
2022-06-06 18:18:13 +02:00
void cell_audio_thread : : mix ( float * out_buffer , s32 offset )
2018-12-20 23:35:49 +01:00
{
AUDIT ( out_buffer ! = nullptr ) ;
2022-06-19 17:08:03 +02:00
constexpr u32 out_channels = static_cast < u32 > ( channels ) ;
constexpr u32 out_buffer_sz = out_channels * AUDIO_BUFFER_SAMPLES ;
2018-12-20 23:35:49 +01:00
2025-01-01 13:42:12 +01:00
const float master_volume = audio : : get_volume ( ) ;
2020-05-30 12:43:00 +02:00
2022-06-06 18:22:43 +02:00
// Reset out_buffer
std : : memset ( out_buffer , 0 , out_buffer_sz * sizeof ( float ) ) ;
2018-12-20 23:35:49 +01:00
// mixing
2023-03-05 20:26:22 +01:00
for ( audio_port & port : ports )
2018-12-20 23:35:49 +01:00
{
2025-04-05 21:50:45 +02:00
if ( port . state ! = audio_port_state : : started )
continue ;
2016-03-21 20:43:03 +01:00
2018-12-20 23:35:49 +01:00
auto buf = port . get_vm_ptr ( offset ) ;
2021-01-12 11:01:06 +01:00
2020-06-01 15:12:56 +02:00
static constexpr float minus_3db = 0.707f ; // value taken from https://www.dolby.com/us/en/technologies/a-guide-to-dolby-metadata.pdf
2020-05-30 12:43:00 +02:00
float m = master_volume ;
2018-12-20 23:35:49 +01:00
// part of cellAudioSetPortLevel functionality
// spread port volume changes over 13ms
2020-05-30 12:43:00 +02:00
auto step_volume = [ master_volume , & m ] ( audio_port & port )
2016-03-21 20:43:03 +01:00
{
2023-03-05 20:26:22 +01:00
const audio_port : : level_set_t param = port . level_set . load ( ) ;
2016-03-21 20:43:03 +01:00
2018-12-20 23:35:49 +01:00
if ( param . inc ! = 0.0f )
2016-03-21 20:43:03 +01:00
{
2018-12-20 23:35:49 +01:00
port . level + = param . inc ;
const bool dec = param . inc < 0.0f ;
2018-11-24 00:46:54 +01:00
2018-12-20 23:35:49 +01:00
if ( ( ! dec & & param . value - port . level < = 0.0f ) | | ( dec & & param . value - port . level > = 0.0f ) )
{
port . level = param . value ;
2025-04-05 21:50:45 +02:00
port . level_set . compare_and_swap ( param , { param . value , 0.0f } ) ;
2018-12-20 23:35:49 +01:00
}
}
2020-05-30 12:43:00 +02:00
m = port . level * master_volume ;
2018-12-20 23:35:49 +01:00
} ;
2016-03-21 20:43:03 +01:00
2018-12-20 23:35:49 +01:00
if ( port . num_channels = = 2 )
2016-03-21 20:43:03 +01:00
{
2022-06-19 17:08:03 +02:00
for ( u32 out = 0 , in = 0 ; out < out_buffer_sz ; out + = out_channels , in + = 2 )
2018-12-20 23:35:49 +01:00
{
2022-06-06 18:22:43 +02:00
step_volume ( port ) ;
2016-03-21 20:43:03 +01:00
2025-04-05 21:50:45 +02:00
const float left = buf [ in + 0 ] * m ;
2022-06-06 18:22:43 +02:00
const float right = buf [ in + 1 ] * m ;
2016-03-21 20:43:03 +01:00
2022-06-06 18:22:43 +02:00
out_buffer [ out + 0 ] + = left ;
out_buffer [ out + 1 ] + = right ;
2018-12-20 23:35:49 +01:00
}
}
else if ( port . num_channels = = 8 )
{
2022-06-19 17:08:03 +02:00
for ( u32 out = 0 , in = 0 ; out < out_buffer_sz ; out + = out_channels , in + = 8 )
2016-03-21 20:43:03 +01:00
{
2022-06-06 18:22:43 +02:00
step_volume ( port ) ;
2025-04-05 21:50:45 +02:00
const float left = buf [ in + 0 ] * m ;
const float right = buf [ in + 1 ] * m ;
const float center = buf [ in + 2 ] * m ;
const float low_freq = buf [ in + 3 ] * m ;
const float side_left = buf [ in + 4 ] * m ;
2022-06-06 18:22:43 +02:00
const float side_right = buf [ in + 5 ] * m ;
2025-04-05 21:50:45 +02:00
const float rear_left = buf [ in + 6 ] * m ;
2022-06-06 18:22:43 +02:00
const float rear_right = buf [ in + 7 ] * m ;
if constexpr ( downmix = = AudioChannelCnt : : STEREO )
2016-03-21 20:43:03 +01:00
{
2022-06-06 18:22:43 +02:00
// Don't mix in the lfe as per dolby specification and based on documentation
const float mid = center * 0.5f ;
out_buffer [ out + 0 ] + = left * minus_3db + mid + side_left * 0.5f + rear_left * 0.5f ;
out_buffer [ out + 1 ] + = right * minus_3db + mid + side_right * 0.5f + rear_right * 0.5f ;
2016-03-21 20:43:03 +01:00
}
2022-06-06 18:22:43 +02:00
else if constexpr ( downmix = = AudioChannelCnt : : SURROUND_5_1 )
2018-12-20 23:35:49 +01:00
{
2022-06-06 18:22:43 +02:00
out_buffer [ out + 0 ] + = left ;
out_buffer [ out + 1 ] + = right ;
2022-06-19 17:08:03 +02:00
if constexpr ( out_channels > = 6 ) // Only mix the surround channels into the output if surround output is configured
{
out_buffer [ out + 2 ] + = center ;
out_buffer [ out + 3 ] + = low_freq ;
if constexpr ( out_channels = = 6 )
{
out_buffer [ out + 4 ] + = side_left + rear_left ;
out_buffer [ out + 5 ] + = side_right + rear_right ;
}
else // When using 7.1 ouput, out_buffer[out + 4] and out_buffer[out + 5] are the rear channels, so the side channels need to be mixed into [out + 6] and [out + 7]
{
out_buffer [ out + 6 ] + = side_left + rear_left ;
out_buffer [ out + 7 ] + = side_right + rear_right ;
}
}
2022-06-06 18:22:43 +02:00
}
else
{
out_buffer [ out + 0 ] + = left ;
out_buffer [ out + 1 ] + = right ;
2022-06-19 17:08:03 +02:00
if constexpr ( out_channels > = 6 ) // Only mix the surround channels into the output if surround output is configured
{
out_buffer [ out + 2 ] + = center ;
out_buffer [ out + 3 ] + = low_freq ;
if constexpr ( out_channels = = 6 )
{
out_buffer [ out + 4 ] + = side_left ;
out_buffer [ out + 5 ] + = side_right ;
}
else
{
out_buffer [ out + 4 ] + = rear_left ;
out_buffer [ out + 5 ] + = rear_right ;
out_buffer [ out + 6 ] + = side_left ;
out_buffer [ out + 7 ] + = side_right ;
}
}
2018-12-20 23:35:49 +01:00
}
}
}
else
{
2020-12-09 16:04:52 +01:00
fmt : : throw_exception ( " Unknown channel count (port=%u, channel=%d) " , port . number , port . num_channels ) ;
2016-03-21 20:43:03 +01:00
}
2018-12-20 23:35:49 +01:00
}
}
2016-03-21 20:43:03 +01:00
2018-12-20 23:35:49 +01:00
void cell_audio_thread : : finish_port_volume_stepping ( )
{
// part of cellAudioSetPortLevel functionality
2023-03-05 20:26:22 +01:00
for ( audio_port & port : ports )
2018-12-20 23:35:49 +01:00
{
2025-04-05 21:50:45 +02:00
if ( port . state ! = audio_port_state : : started )
continue ;
2018-12-20 23:35:49 +01:00
2023-03-05 20:26:22 +01:00
const audio_port : : level_set_t param = port . level_set . load ( ) ;
2018-12-20 23:35:49 +01:00
port . level = param . value ;
2025-04-05 21:50:45 +02:00
port . level_set . compare_and_swap ( param , { param . value , 0.0f } ) ;
2016-03-21 20:43:03 +01:00
}
}
2017-09-20 12:01:57 +02:00
error_code cellAudioInit ( )
2016-03-21 20:43:03 +01:00
{
cellAudio . warning ( " cellAudioInit() " ) ;
2021-03-02 12:59:19 +01:00
auto & g_audio = g_fxo - > get < cell_audio > ( ) ;
2016-03-21 20:43:03 +01:00
2021-03-02 12:59:19 +01:00
std : : lock_guard lock ( g_audio . mutex ) ;
2019-09-15 14:19:59 +02:00
2021-03-02 12:59:19 +01:00
if ( g_audio . init )
2016-03-21 20:43:03 +01:00
{
return CELL_AUDIO_ERROR_ALREADY_INIT ;
}
2019-09-15 01:04:31 +02:00
std : : memset ( g_audio_buffer . get_ptr ( ) , 0 , g_audio_buffer . alloc_size ) ;
std : : memset ( g_audio_indices . get_ptr ( ) , 0 , g_audio_indices . alloc_size ) ;
for ( u32 i = 0 ; i < AUDIO_PORT_COUNT ; i + + )
{
2021-03-02 12:59:19 +01:00
g_audio . ports [ i ] . number = i ;
2025-04-05 21:50:45 +02:00
g_audio . ports [ i ] . addr = g_audio_buffer + AUDIO_PORT_OFFSET * i ;
g_audio . ports [ i ] . index = ( g_audio_indices + i ) . ptr ( & aligned_index_t : : index ) ;
g_audio . ports [ i ] . state = audio_port_state : : closed ;
2019-09-15 01:04:31 +02:00
}
2021-03-02 12:59:19 +01:00
g_audio . init = 1 ;
2019-09-15 14:19:59 +02:00
2016-03-21 20:43:03 +01:00
return CELL_OK ;
}
2021-03-05 20:05:37 +01:00
error_code cellAudioQuit ( )
2016-03-21 20:43:03 +01:00
{
cellAudio . warning ( " cellAudioQuit() " ) ;
2021-03-02 12:59:19 +01:00
auto & g_audio = g_fxo - > get < cell_audio > ( ) ;
2019-09-15 14:19:59 +02:00
2021-03-02 12:59:19 +01:00
std : : lock_guard lock ( g_audio . mutex ) ;
2016-03-21 20:43:03 +01:00
2021-03-02 12:59:19 +01:00
if ( ! g_audio . init )
2016-03-21 20:43:03 +01:00
{
return CELL_AUDIO_ERROR_NOT_INIT ;
}
2022-05-29 15:12:00 +02:00
// NOTE: Do not clear event queues here. They are handled independently.
2021-03-02 12:59:19 +01:00
g_audio . init = 0 ;
2018-10-02 10:40:37 +02:00
2016-03-21 20:43:03 +01:00
return CELL_OK ;
}
2017-09-20 12:01:57 +02:00
error_code cellAudioPortOpen ( vm : : ptr < CellAudioPortParam > audioParam , vm : : ptr < u32 > portNum )
2016-03-21 20:43:03 +01:00
{
cellAudio . warning ( " cellAudioPortOpen(audioParam=*0x%x, portNum=*0x%x) " , audioParam , portNum ) ;
2021-03-02 12:59:19 +01:00
auto & g_audio = g_fxo - > get < cell_audio > ( ) ;
2019-09-15 14:19:59 +02:00
2021-03-02 12:59:19 +01:00
if ( ! g_audio . init )
2016-03-21 20:43:03 +01:00
{
return CELL_AUDIO_ERROR_NOT_INIT ;
}
if ( ! audioParam | | ! portNum )
{
return CELL_AUDIO_ERROR_PARAM ;
}
2018-12-20 23:35:49 +01:00
const u64 num_channels = audioParam - > nChannel ;
const u64 num_blocks = audioParam - > nBlock ;
2016-03-21 20:43:03 +01:00
const u64 attr = audioParam - > attr ;
// check attributes
2018-12-20 23:35:49 +01:00
if ( num_channels ! = CELL_AUDIO_PORT_2CH & &
num_channels ! = CELL_AUDIO_PORT_8CH & &
2020-06-01 12:19:47 +02:00
num_channels ! = 0 )
2016-03-21 20:43:03 +01:00
{
return CELL_AUDIO_ERROR_PARAM ;
}
2020-06-01 12:19:47 +02:00
if ( num_blocks ! = 2 & &
2018-12-20 23:35:49 +01:00
num_blocks ! = 4 & &
2020-06-01 12:19:47 +02:00
num_blocks ! = CELL_AUDIO_BLOCK_8 & &
num_blocks ! = CELL_AUDIO_BLOCK_16 & &
num_blocks ! = CELL_AUDIO_BLOCK_32 )
2016-03-21 20:43:03 +01:00
{
return CELL_AUDIO_ERROR_PARAM ;
}
// list unsupported flags
if ( attr & CELL_AUDIO_PORTATTR_BGM )
{
cellAudio . todo ( " cellAudioPortOpen(): CELL_AUDIO_PORTATTR_BGM " ) ;
}
if ( attr & CELL_AUDIO_PORTATTR_OUT_SECONDARY )
{
cellAudio . todo ( " cellAudioPortOpen(): CELL_AUDIO_PORTATTR_OUT_SECONDARY " ) ;
}
if ( attr & CELL_AUDIO_PORTATTR_OUT_PERSONAL_0 )
{
cellAudio . todo ( " cellAudioPortOpen(): CELL_AUDIO_PORTATTR_OUT_PERSONAL_0 " ) ;
}
if ( attr & CELL_AUDIO_PORTATTR_OUT_PERSONAL_1 )
{
cellAudio . todo ( " cellAudioPortOpen(): CELL_AUDIO_PORTATTR_OUT_PERSONAL_1 " ) ;
}
if ( attr & CELL_AUDIO_PORTATTR_OUT_PERSONAL_2 )
{
cellAudio . todo ( " cellAudioPortOpen(): CELL_AUDIO_PORTATTR_OUT_PERSONAL_2 " ) ;
}
if ( attr & CELL_AUDIO_PORTATTR_OUT_PERSONAL_3 )
{
cellAudio . todo ( " cellAudioPortOpen(): CELL_AUDIO_PORTATTR_OUT_PERSONAL_3 " ) ;
}
if ( attr & CELL_AUDIO_PORTATTR_OUT_NO_ROUTE )
{
cellAudio . todo ( " cellAudioPortOpen(): CELL_AUDIO_PORTATTR_OUT_NO_ROUTE " ) ;
}
if ( attr & 0xFFFFFFFFF0EFEFEEULL )
{
cellAudio . todo ( " cellAudioPortOpen(): unknown attributes (0x%llx) " , attr ) ;
}
2023-08-18 07:54:57 +02:00
// Waiting for VSH and doing some more things
lv2_sleep ( 200 ) ;
std : : lock_guard lock ( g_audio . mutex ) ;
if ( ! g_audio . init )
{
return CELL_AUDIO_ERROR_NOT_INIT ;
}
2016-03-21 20:43:03 +01:00
// Open audio port
2023-03-05 20:26:22 +01:00
audio_port * port = g_audio . open_port ( ) ;
2016-03-21 20:43:03 +01:00
if ( ! port )
{
return CELL_AUDIO_ERROR_PORT_FULL ;
}
2020-10-30 15:43:09 +01:00
2020-06-01 12:19:47 +02:00
// TODO: is this necessary in any way? (Based on libaudio.prx)
2025-04-05 21:50:45 +02:00
// const u64 num_channels_non_0 = std::max<u64>(1, num_channels);
2016-03-21 20:43:03 +01:00
2025-04-05 21:50:45 +02:00
port - > num_channels = : : narrow < u32 > ( num_channels ) ;
port - > num_blocks = : : narrow < u32 > ( num_blocks ) ;
port - > attr = attr ;
port - > size = : : narrow < u32 > ( num_channels * num_blocks * AUDIO_BUFFER_SAMPLES * sizeof ( f32 ) ) ;
port - > cur_pos = 0 ;
2021-03-02 12:59:19 +01:00
port - > global_counter = g_audio . m_counter ;
2018-12-20 23:35:49 +01:00
port - > active_counter = 0 ;
2025-04-05 21:50:45 +02:00
port - > timestamp = get_guest_system_time ( g_audio . m_last_period_end ) ;
2016-03-21 20:43:03 +01:00
if ( attr & CELL_AUDIO_PORTATTR_INITLEVEL )
{
2020-05-30 12:43:00 +02:00
port - > level = audioParam - > level ;
2016-03-21 20:43:03 +01:00
}
else
{
2020-05-30 12:43:00 +02:00
port - > level = 1.0f ;
2016-03-21 20:43:03 +01:00
}
2025-04-05 21:50:45 +02:00
port - > level_set . store ( { port - > level , 0.0f } ) ;
2016-03-21 20:43:03 +01:00
2020-06-01 12:19:47 +02:00
if ( port - > level < = - 1.0f )
{
return CELL_AUDIO_ERROR_PORT_OPEN ;
}
2016-03-21 20:43:03 +01:00
* portNum = port - > number ;
return CELL_OK ;
}
2017-09-20 12:01:57 +02:00
error_code cellAudioGetPortConfig ( u32 portNum , vm : : ptr < CellAudioPortConfig > portConfig )
2016-03-21 20:43:03 +01:00
{
2018-12-20 23:35:49 +01:00
cellAudio . trace ( " cellAudioGetPortConfig(portNum=%d, portConfig=*0x%x) " , portNum , portConfig ) ;
2016-03-21 20:43:03 +01:00
2021-03-02 12:59:19 +01:00
auto & g_audio = g_fxo - > get < cell_audio > ( ) ;
2016-03-21 20:43:03 +01:00
2021-03-02 12:59:19 +01:00
std : : lock_guard lock ( g_audio . mutex ) ;
2019-09-15 14:19:59 +02:00
2021-03-02 12:59:19 +01:00
if ( ! g_audio . init )
2016-03-21 20:43:03 +01:00
{
return CELL_AUDIO_ERROR_NOT_INIT ;
}
if ( ! portConfig | | portNum > = AUDIO_PORT_COUNT )
{
return CELL_AUDIO_ERROR_PARAM ;
}
2021-03-02 12:59:19 +01:00
audio_port & port = g_audio . ports [ portNum ] ;
2016-03-21 20:43:03 +01:00
portConfig - > readIndexAddr = port . index ;
2023-03-05 20:26:22 +01:00
switch ( audio_port_state state = port . state . load ( ) )
2016-03-21 20:43:03 +01:00
{
2020-06-01 12:19:47 +02:00
case audio_port_state : : closed :
portConfig - > status = CELL_AUDIO_STATUS_CLOSE ;
break ;
case audio_port_state : : opened :
portConfig - > status = CELL_AUDIO_STATUS_READY ;
break ;
case audio_port_state : : started :
portConfig - > status = CELL_AUDIO_STATUS_RUN ;
break ;
default :
cellAudio . error ( " Invalid port state (%d: %d) " , portNum , static_cast < u32 > ( state ) ) ;
return CELL_AUDIO_ERROR_AUDIOSYSTEM ;
2016-03-21 20:43:03 +01:00
}
2018-12-20 23:35:49 +01:00
portConfig - > nChannel = port . num_channels ;
2025-04-05 21:50:45 +02:00
portConfig - > nBlock = port . num_blocks ;
2016-03-21 20:43:03 +01:00
portConfig - > portSize = port . size ;
portConfig - > portAddr = port . addr . addr ( ) ;
return CELL_OK ;
}
2017-09-20 12:01:57 +02:00
error_code cellAudioPortStart ( u32 portNum )
2016-03-21 20:43:03 +01:00
{
cellAudio . warning ( " cellAudioPortStart(portNum=%d) " , portNum ) ;
2021-03-02 12:59:19 +01:00
auto & g_audio = g_fxo - > get < cell_audio > ( ) ;
2019-09-15 14:19:59 +02:00
2021-03-02 12:59:19 +01:00
if ( ! g_audio . init )
2016-03-21 20:43:03 +01:00
{
return CELL_AUDIO_ERROR_NOT_INIT ;
}
if ( portNum > = AUDIO_PORT_COUNT )
{
return CELL_AUDIO_ERROR_PARAM ;
}
2023-08-18 07:54:57 +02:00
// Waiting for VSH
lv2_sleep ( 30 ) ;
std : : lock_guard lock ( g_audio . mutex ) ;
if ( ! g_audio . init )
{
return CELL_AUDIO_ERROR_NOT_INIT ;
}
2023-03-05 20:26:22 +01:00
switch ( audio_port_state state = g_audio . ports [ portNum ] . state . compare_and_swap ( audio_port_state : : opened , audio_port_state : : started ) )
2016-03-21 20:43:03 +01:00
{
case audio_port_state : : closed : return CELL_AUDIO_ERROR_PORT_NOT_OPEN ;
case audio_port_state : : started : return CELL_AUDIO_ERROR_PORT_ALREADY_RUN ;
case audio_port_state : : opened : return CELL_OK ;
2019-12-01 18:14:58 +01:00
default : fmt : : throw_exception ( " Invalid port state (%d: %d) " , portNum , static_cast < u32 > ( state ) ) ;
2016-03-21 20:43:03 +01:00
}
}
2017-09-20 12:01:57 +02:00
error_code cellAudioPortClose ( u32 portNum )
2016-03-21 20:43:03 +01:00
{
cellAudio . warning ( " cellAudioPortClose(portNum=%d) " , portNum ) ;
2021-03-02 12:59:19 +01:00
auto & g_audio = g_fxo - > get < cell_audio > ( ) ;
2016-03-21 20:43:03 +01:00
2021-03-02 12:59:19 +01:00
std : : lock_guard lock ( g_audio . mutex ) ;
2019-09-15 14:19:59 +02:00
2021-03-02 12:59:19 +01:00
if ( ! g_audio . init )
2016-03-21 20:43:03 +01:00
{
return CELL_AUDIO_ERROR_NOT_INIT ;
}
if ( portNum > = AUDIO_PORT_COUNT )
{
return CELL_AUDIO_ERROR_PARAM ;
}
2023-03-05 20:26:22 +01:00
switch ( audio_port_state state = g_audio . ports [ portNum ] . state . exchange ( audio_port_state : : closed ) )
2016-03-21 20:43:03 +01:00
{
case audio_port_state : : closed : return CELL_AUDIO_ERROR_PORT_NOT_OPEN ;
case audio_port_state : : started : return CELL_OK ;
case audio_port_state : : opened : return CELL_OK ;
2019-12-01 18:14:58 +01:00
default : fmt : : throw_exception ( " Invalid port state (%d: %d) " , portNum , static_cast < u32 > ( state ) ) ;
2016-03-21 20:43:03 +01:00
}
}
2017-09-20 12:01:57 +02:00
error_code cellAudioPortStop ( u32 portNum )
2016-03-21 20:43:03 +01:00
{
cellAudio . warning ( " cellAudioPortStop(portNum=%d) " , portNum ) ;
2021-03-02 12:59:19 +01:00
auto & g_audio = g_fxo - > get < cell_audio > ( ) ;
2019-09-15 14:19:59 +02:00
2021-03-02 12:59:19 +01:00
std : : lock_guard lock ( g_audio . mutex ) ;
2016-03-21 20:43:03 +01:00
2021-03-02 12:59:19 +01:00
if ( ! g_audio . init )
2016-03-21 20:43:03 +01:00
{
return CELL_AUDIO_ERROR_NOT_INIT ;
}
if ( portNum > = AUDIO_PORT_COUNT )
{
return CELL_AUDIO_ERROR_PARAM ;
}
2023-03-05 20:26:22 +01:00
switch ( audio_port_state state = g_audio . ports [ portNum ] . state . compare_and_swap ( audio_port_state : : started , audio_port_state : : opened ) )
2016-03-21 20:43:03 +01:00
{
case audio_port_state : : closed : return CELL_AUDIO_ERROR_PORT_NOT_RUN ;
case audio_port_state : : started : return CELL_OK ;
case audio_port_state : : opened : return CELL_AUDIO_ERROR_PORT_NOT_RUN ;
2019-12-01 18:14:58 +01:00
default : fmt : : throw_exception ( " Invalid port state (%d: %d) " , portNum , static_cast < u32 > ( state ) ) ;
2016-03-21 20:43:03 +01:00
}
}
2017-09-20 12:01:57 +02:00
error_code cellAudioGetPortTimestamp ( u32 portNum , u64 tag , vm : : ptr < u64 > stamp )
2016-03-21 20:43:03 +01:00
{
cellAudio . trace ( " cellAudioGetPortTimestamp(portNum=%d, tag=0x%llx, stamp=*0x%x) " , portNum , tag , stamp ) ;
2021-03-02 12:59:19 +01:00
auto & g_audio = g_fxo - > get < cell_audio > ( ) ;
2019-09-15 14:19:59 +02:00
2021-03-02 12:59:19 +01:00
std : : lock_guard lock ( g_audio . mutex ) ;
2016-03-21 20:43:03 +01:00
2021-03-02 12:59:19 +01:00
if ( ! g_audio . init )
2016-03-21 20:43:03 +01:00
{
return CELL_AUDIO_ERROR_NOT_INIT ;
}
if ( portNum > = AUDIO_PORT_COUNT )
{
return CELL_AUDIO_ERROR_PARAM ;
}
2021-03-02 12:59:19 +01:00
audio_port & port = g_audio . ports [ portNum ] ;
2016-03-21 20:43:03 +01:00
if ( port . state = = audio_port_state : : closed )
{
return CELL_AUDIO_ERROR_PORT_NOT_OPEN ;
}
2018-12-20 23:35:49 +01:00
if ( port . global_counter < tag )
{
return CELL_AUDIO_ERROR_TAG_NOT_FOUND ;
}
2016-03-21 20:43:03 +01:00
2020-06-01 12:19:47 +02:00
const u64 delta_tag = port . global_counter - tag ;
2021-03-02 12:59:19 +01:00
const u64 delta_tag_stamp = delta_tag * g_audio . cfg . audio_block_period ;
2020-06-01 12:19:47 +02:00
// Apparently no error is returned if stamp is null
2021-08-25 14:26:41 +02:00
* stamp = get_guest_system_time ( port . timestamp - delta_tag_stamp ) ;
2016-03-21 20:43:03 +01:00
return CELL_OK ;
}
2017-09-20 12:01:57 +02:00
error_code cellAudioGetPortBlockTag ( u32 portNum , u64 blockNo , vm : : ptr < u64 > tag )
2016-03-21 20:43:03 +01:00
{
cellAudio . trace ( " cellAudioGetPortBlockTag(portNum=%d, blockNo=0x%llx, tag=*0x%x) " , portNum , blockNo , tag ) ;
2021-03-02 12:59:19 +01:00
auto & g_audio = g_fxo - > get < cell_audio > ( ) ;
2016-03-21 20:43:03 +01:00
2021-03-02 12:59:19 +01:00
std : : lock_guard lock ( g_audio . mutex ) ;
2019-09-15 14:19:59 +02:00
2021-03-02 12:59:19 +01:00
if ( ! g_audio . init )
2016-03-21 20:43:03 +01:00
{
return CELL_AUDIO_ERROR_NOT_INIT ;
}
if ( portNum > = AUDIO_PORT_COUNT )
{
return CELL_AUDIO_ERROR_PARAM ;
}
2021-03-02 12:59:19 +01:00
audio_port & port = g_audio . ports [ portNum ] ;
2016-03-21 20:43:03 +01:00
if ( port . state = = audio_port_state : : closed )
{
return CELL_AUDIO_ERROR_PORT_NOT_OPEN ;
}
2018-12-20 23:35:49 +01:00
if ( blockNo > = port . num_blocks )
2016-03-21 20:43:03 +01:00
{
return CELL_AUDIO_ERROR_PARAM ;
}
2020-06-01 12:19:47 +02:00
// Apparently no error is returned if tag is null
2018-12-20 23:35:49 +01:00
* tag = port . global_counter + blockNo - port . cur_pos ;
2016-03-21 20:43:03 +01:00
return CELL_OK ;
}
2017-09-20 12:01:57 +02:00
error_code cellAudioSetPortLevel ( u32 portNum , float level )
2016-03-21 20:43:03 +01:00
{
cellAudio . trace ( " cellAudioSetPortLevel(portNum=%d, level=%f) " , portNum , level ) ;
2021-03-02 12:59:19 +01:00
auto & g_audio = g_fxo - > get < cell_audio > ( ) ;
2019-09-15 14:19:59 +02:00
2021-03-02 12:59:19 +01:00
std : : lock_guard lock ( g_audio . mutex ) ;
2016-03-21 20:43:03 +01:00
2021-03-02 12:59:19 +01:00
if ( ! g_audio . init )
2016-03-21 20:43:03 +01:00
{
return CELL_AUDIO_ERROR_NOT_INIT ;
}
if ( portNum > = AUDIO_PORT_COUNT )
{
return CELL_AUDIO_ERROR_PARAM ;
}
2021-03-02 12:59:19 +01:00
audio_port & port = g_audio . ports [ portNum ] ;
2016-03-21 20:43:03 +01:00
if ( port . state = = audio_port_state : : closed )
{
return CELL_AUDIO_ERROR_PORT_NOT_OPEN ;
}
if ( level > = 0.0f )
{
2025-04-05 21:50:45 +02:00
port . level_set . exchange ( { level , ( port . level - level ) / 624.0f } ) ;
2016-03-21 20:43:03 +01:00
}
else
{
cellAudio . todo ( " cellAudioSetPortLevel(%d): negative level value (%f) " , portNum , level ) ;
}
return CELL_OK ;
}
2020-10-30 15:43:09 +01:00
static error_code AudioCreateNotifyEventQueue ( ppu_thread & ppu , vm : : ptr < u32 > id , vm : : ptr < u64 > key , u32 queue_type )
2016-03-21 20:43:03 +01:00
{
2017-02-03 22:36:04 +01:00
vm : : var < sys_event_queue_attribute_t > attr ;
attr - > protocol = SYS_SYNC_FIFO ;
2025-04-05 21:50:45 +02:00
attr - > type = queue_type ;
2017-02-03 22:36:04 +01:00
attr - > name_u64 = 0 ;
2016-03-21 20:43:03 +01:00
2018-12-20 23:35:49 +01:00
for ( u64 i = 0 ; i < MAX_AUDIO_EVENT_QUEUES ; i + + )
2017-02-03 22:36:04 +01:00
{
2016-03-21 20:43:03 +01:00
// Create an event queue "bruteforcing" an available key
2017-02-03 22:36:04 +01:00
const u64 key_value = 0x80004d494f323221ull + i ;
2019-10-26 21:02:53 +02:00
// This originally reads from a global sdk value set by cellAudioInit
// So check initialization as well
2021-03-02 12:59:19 +01:00
const u32 queue_depth = g_fxo - > get < cell_audio > ( ) . init & & g_ps3_process_info . sdk_ver < = 0x35FFFF ? 2 : 8 ;
2019-10-26 21:02:53 +02:00
2020-10-30 15:43:09 +01:00
if ( CellError res { sys_event_queue_create ( ppu , id , attr , key_value , queue_depth ) + 0u } )
2017-02-03 22:36:04 +01:00
{
if ( res ! = CELL_EEXIST )
{
return res ;
}
}
else
2016-03-21 20:43:03 +01:00
{
* key = key_value ;
return CELL_OK ;
}
}
return CELL_AUDIO_ERROR_EVENT_QUEUE ;
}
2020-10-30 15:43:09 +01:00
error_code cellAudioCreateNotifyEventQueue ( ppu_thread & ppu , vm : : ptr < u32 > id , vm : : ptr < u64 > key )
2019-10-26 19:21:34 +02:00
{
cellAudio . warning ( " cellAudioCreateNotifyEventQueue(id=*0x%x, key=*0x%x) " , id , key ) ;
2020-10-30 15:43:09 +01:00
return AudioCreateNotifyEventQueue ( ppu , id , key , SYS_PPU_QUEUE ) ;
2019-10-26 19:21:34 +02:00
}
2020-10-30 15:43:09 +01:00
error_code cellAudioCreateNotifyEventQueueEx ( ppu_thread & ppu , vm : : ptr < u32 > id , vm : : ptr < u64 > key , u32 iFlags )
2016-03-21 20:43:03 +01:00
{
2019-10-26 19:21:34 +02:00
cellAudio . warning ( " cellAudioCreateNotifyEventQueueEx(id=*0x%x, key=*0x%x, iFlags=0x%x) " , id , key , iFlags ) ;
2016-03-21 20:43:03 +01:00
if ( iFlags & ~ CELL_AUDIO_CREATEEVENTFLAG_SPU )
{
return CELL_AUDIO_ERROR_PARAM ;
}
2019-10-26 19:21:34 +02:00
const u32 queue_type = ( iFlags & CELL_AUDIO_CREATEEVENTFLAG_SPU ) ? SYS_SPU_QUEUE : SYS_PPU_QUEUE ;
2020-10-30 15:43:09 +01:00
return AudioCreateNotifyEventQueue ( ppu , id , key , queue_type ) ;
2016-03-21 20:43:03 +01:00
}
2023-08-18 07:54:57 +02:00
error_code AudioSetNotifyEventQueue ( ppu_thread & ppu , u64 key , u32 iFlags )
2016-03-21 20:43:03 +01:00
{
2021-03-02 12:59:19 +01:00
auto & g_audio = g_fxo - > get < cell_audio > ( ) ;
2016-03-21 20:43:03 +01:00
2023-08-18 07:54:57 +02:00
if ( ! g_audio . init )
{
return CELL_AUDIO_ERROR_NOT_INIT ;
}
// Waiting for VSH
lv2_sleep ( 20 , & ppu ) ;
// Dirty hack for sound: confirm the creation of _mxr000 event queue by _cellsurMixerMain thread
constexpr u64 c_mxr000 = 0x8000cafe0246030 ;
if ( key = = c_mxr000 | | key = = 0 )
{
bool has_sur_mixer_thread = false ;
for ( usz count = 0 ; ! lv2_event_queue : : find ( c_mxr000 ) & & count < 100 ; count + + )
{
if ( has_sur_mixer_thread | | idm : : select < named_thread < ppu_thread > > ( [ & ] ( u32 id , named_thread < ppu_thread > & test_ppu )
2025-04-05 21:50:45 +02:00
{
// Confirm thread existence
if ( id = = ppu . id )
{
return false ;
}
const auto ptr = test_ppu . ppu_tname . load ( ) ;
if ( ! ptr )
{
return false ;
}
return * ptr = = " _cellsurMixerMain " sv ;
} )
. ret )
2023-08-18 07:54:57 +02:00
{
has_sur_mixer_thread = true ;
}
else
{
break ;
}
if ( ppu . is_stopped ( ) )
{
ppu . state + = cpu_flag : : again ;
return { } ;
}
cellAudio . error ( " AudioSetNotifyEventQueue(): Waiting for _mxr000. x%d " , count ) ;
lv2_sleep ( 50'000 , & ppu ) ;
}
if ( has_sur_mixer_thread & & lv2_event_queue : : find ( c_mxr000 ) )
{
key = c_mxr000 ;
}
}
2021-03-02 12:59:19 +01:00
std : : lock_guard lock ( g_audio . mutex ) ;
2019-09-15 14:19:59 +02:00
2021-03-02 12:59:19 +01:00
if ( ! g_audio . init )
2016-03-21 20:43:03 +01:00
{
return CELL_AUDIO_ERROR_NOT_INIT ;
}
2020-06-04 19:54:31 +02:00
auto q = lv2_event_queue : : find ( key ) ;
if ( ! q )
2019-09-26 21:51:17 +02:00
{
return CELL_AUDIO_ERROR_TRANS_EVENT ;
}
2021-03-02 12:59:19 +01:00
for ( auto i = g_audio . keys . cbegin ( ) ; i ! = g_audio . keys . cend ( ) ; ) // check for duplicates
2016-03-21 20:43:03 +01:00
{
2021-05-14 14:18:30 +02:00
if ( i - > port = = q )
2016-03-21 20:43:03 +01:00
{
return CELL_AUDIO_ERROR_TRANS_EVENT ;
}
2020-06-04 19:54:31 +02:00
2021-05-14 19:10:47 +02:00
if ( ! lv2_obj : : check ( i - > port ) )
2020-06-04 19:54:31 +02:00
{
// Cleanup, avoid cases where there are multiple ports with the same key
2021-03-02 12:59:19 +01:00
i = g_audio . keys . erase ( i ) ;
2020-06-04 19:54:31 +02:00
}
else
{
i + + ;
}
2016-03-21 20:43:03 +01:00
}
2019-10-26 06:07:15 +02:00
// Set unique source associated with the key
2025-04-05 21:50:45 +02:00
g_audio . keys . push_back ( { . start_period = g_audio . event_period ,
2023-03-05 23:33:35 +01:00
. flags = iFlags ,
. source = ( ( process_getpid ( ) + u64 { } ) < < 32 ) + lv2_event_port : : id_base + ( g_audio . key_count + + * lv2_event_port : : id_step ) ,
. ack_timestamp = 0 ,
2025-04-05 21:50:45 +02:00
. port = std : : move ( q ) } ) ;
2023-08-18 07:54:57 +02:00
2021-03-02 12:59:19 +01:00
g_audio . key_count % = lv2_event_port : : id_count ;
2016-03-21 20:43:03 +01:00
return CELL_OK ;
}
2023-08-18 07:54:57 +02:00
error_code cellAudioSetNotifyEventQueue ( ppu_thread & ppu , u64 key )
2020-02-09 06:47:02 +01:00
{
2023-08-18 07:54:57 +02:00
ppu . state + = cpu_flag : : wait ;
2020-02-09 06:47:02 +01:00
cellAudio . warning ( " cellAudioSetNotifyEventQueue(key=0x%llx) " , key ) ;
2023-08-18 07:54:57 +02:00
return AudioSetNotifyEventQueue ( ppu , key , 0 ) ;
2020-02-09 06:47:02 +01:00
}
2023-08-18 07:54:57 +02:00
error_code cellAudioSetNotifyEventQueueEx ( ppu_thread & ppu , u64 key , u32 iFlags )
2016-03-21 20:43:03 +01:00
{
2023-08-18 07:54:57 +02:00
ppu . state + = cpu_flag : : wait ;
2016-03-21 20:43:03 +01:00
cellAudio . todo ( " cellAudioSetNotifyEventQueueEx(key=0x%llx, iFlags=0x%x) " , key , iFlags ) ;
2020-02-09 06:47:02 +01:00
if ( iFlags & ( ~ 0u > > 5 ) )
{
return CELL_AUDIO_ERROR_PARAM ;
}
2016-03-21 20:43:03 +01:00
2023-08-18 07:54:57 +02:00
return AudioSetNotifyEventQueue ( ppu , key , iFlags ) ;
2016-03-21 20:43:03 +01:00
}
2020-02-09 06:47:02 +01:00
error_code AudioRemoveNotifyEventQueue ( u64 key , u32 iFlags )
2016-03-21 20:43:03 +01:00
{
2021-03-02 12:59:19 +01:00
auto & g_audio = g_fxo - > get < cell_audio > ( ) ;
2019-09-15 14:19:59 +02:00
2021-03-02 12:59:19 +01:00
std : : lock_guard lock ( g_audio . mutex ) ;
2016-03-21 20:43:03 +01:00
2021-03-02 12:59:19 +01:00
if ( ! g_audio . init )
2016-03-21 20:43:03 +01:00
{
return CELL_AUDIO_ERROR_NOT_INIT ;
}
2021-03-02 12:59:19 +01:00
for ( auto i = g_audio . keys . cbegin ( ) ; i ! = g_audio . keys . cend ( ) ; i + + )
2016-03-21 20:43:03 +01:00
{
2021-05-14 19:10:47 +02:00
if ( lv2_obj : : check ( i - > port ) & & i - > port - > key = = key )
2016-03-21 20:43:03 +01:00
{
2020-02-09 06:47:02 +01:00
if ( i - > flags ! = iFlags )
{
break ;
}
2021-03-02 12:59:19 +01:00
g_audio . keys . erase ( i ) ;
2018-08-25 14:39:00 +02:00
2016-03-21 20:43:03 +01:00
return CELL_OK ;
}
}
return CELL_AUDIO_ERROR_TRANS_EVENT ;
}
2020-02-09 06:47:02 +01:00
error_code cellAudioRemoveNotifyEventQueue ( u64 key )
{
cellAudio . warning ( " cellAudioRemoveNotifyEventQueue(key=0x%llx) " , key ) ;
return AudioRemoveNotifyEventQueue ( key , 0 ) ;
}
2017-09-20 12:01:57 +02:00
error_code cellAudioRemoveNotifyEventQueueEx ( u64 key , u32 iFlags )
2016-03-21 20:43:03 +01:00
{
cellAudio . todo ( " cellAudioRemoveNotifyEventQueueEx(key=0x%llx, iFlags=0x%x) " , key , iFlags ) ;
2020-02-09 06:47:02 +01:00
if ( iFlags & ( ~ 0u > > 5 ) )
{
return CELL_AUDIO_ERROR_PARAM ;
}
2016-03-21 20:43:03 +01:00
2020-02-09 06:47:02 +01:00
return AudioRemoveNotifyEventQueue ( key , iFlags ) ;
2016-03-21 20:43:03 +01:00
}
2017-09-20 12:01:57 +02:00
error_code cellAudioAddData ( u32 portNum , vm : : ptr < float > src , u32 samples , float volume )
2016-03-21 20:43:03 +01:00
{
cellAudio . trace ( " cellAudioAddData(portNum=%d, src=*0x%x, samples=%d, volume=%f) " , portNum , src , samples , volume ) ;
2021-03-02 12:59:19 +01:00
auto & g_audio = g_fxo - > get < cell_audio > ( ) ;
2016-03-21 20:43:03 +01:00
2021-03-02 12:59:19 +01:00
std : : unique_lock lock ( g_audio . mutex ) ;
2019-09-15 14:19:59 +02:00
2021-03-02 12:59:19 +01:00
if ( ! g_audio . init )
2016-03-21 20:43:03 +01:00
{
return CELL_AUDIO_ERROR_NOT_INIT ;
}
2020-06-01 12:19:47 +02:00
if ( portNum > = AUDIO_PORT_COUNT | | ! src | | ! src . aligned ( ) | | samples ! = CELL_AUDIO_BLOCK_SAMPLES )
2016-03-21 20:43:03 +01:00
{
return CELL_AUDIO_ERROR_PARAM ;
}
2021-03-02 12:59:19 +01:00
const audio_port & port = g_audio . ports [ portNum ] ;
2016-03-21 20:43:03 +01:00
2018-12-20 23:35:49 +01:00
const auto dst = port . get_vm_ptr ( ) ;
2016-03-21 20:43:03 +01:00
2019-09-15 14:19:59 +02:00
lock . unlock ( ) ;
2018-10-02 10:40:37 +02:00
2019-11-19 20:17:12 +01:00
volume = std : : isfinite ( volume ) ? std : : clamp ( volume , - 16.f , 16.f ) : 0.f ;
2018-12-20 23:35:49 +01:00
for ( u32 i = 0 ; i < samples * port . num_channels ; i + + )
2016-03-21 20:43:03 +01:00
{
dst [ i ] + = src [ i ] * volume ; // mix all channels
}
return CELL_OK ;
}
2017-09-20 12:01:57 +02:00
error_code cellAudioAdd2chData ( u32 portNum , vm : : ptr < float > src , u32 samples , float volume )
2016-03-21 20:43:03 +01:00
{
cellAudio . trace ( " cellAudioAdd2chData(portNum=%d, src=*0x%x, samples=%d, volume=%f) " , portNum , src , samples , volume ) ;
2021-03-02 12:59:19 +01:00
auto & g_audio = g_fxo - > get < cell_audio > ( ) ;
2019-09-15 14:19:59 +02:00
2021-03-02 12:59:19 +01:00
std : : unique_lock lock ( g_audio . mutex ) ;
2016-03-21 20:43:03 +01:00
2021-03-02 12:59:19 +01:00
if ( ! g_audio . init )
2016-03-21 20:43:03 +01:00
{
return CELL_AUDIO_ERROR_NOT_INIT ;
}
2020-06-01 12:19:47 +02:00
if ( portNum > = AUDIO_PORT_COUNT | | ! src | | ! src . aligned ( ) | | samples ! = CELL_AUDIO_BLOCK_SAMPLES )
2016-03-21 20:43:03 +01:00
{
return CELL_AUDIO_ERROR_PARAM ;
}
2021-03-02 12:59:19 +01:00
const audio_port & port = g_audio . ports [ portNum ] ;
2016-03-21 20:43:03 +01:00
2018-12-20 23:35:49 +01:00
const auto dst = port . get_vm_ptr ( ) ;
2016-03-21 20:43:03 +01:00
2019-09-15 14:19:59 +02:00
lock . unlock ( ) ;
2018-10-02 10:40:37 +02:00
2019-11-19 20:17:12 +01:00
volume = std : : isfinite ( volume ) ? std : : clamp ( volume , - 16.f , 16.f ) : 0.f ;
2018-12-20 23:35:49 +01:00
if ( port . num_channels = = 2 )
2016-03-21 20:43:03 +01:00
{
2017-04-26 22:05:49 +02:00
for ( u32 i = 0 ; i < samples ; i + + )
{
dst [ i * 2 + 0 ] + = src [ i * 2 + 0 ] * volume ; // mix L ch
dst [ i * 2 + 1 ] + = src [ i * 2 + 1 ] * volume ; // mix R ch
}
2016-03-21 20:43:03 +01:00
}
2018-12-20 23:35:49 +01:00
else if ( port . num_channels = = 8 )
2016-03-21 20:43:03 +01:00
{
for ( u32 i = 0 ; i < samples ; i + + )
{
dst [ i * 8 + 0 ] + = src [ i * 2 + 0 ] * volume ; // mix L ch
dst [ i * 8 + 1 ] + = src [ i * 2 + 1 ] * volume ; // mix R ch
2025-04-05 22:10:22 +02:00
// dst[i * 8 + 2] += 0.0f; // center
2025-04-05 21:50:45 +02:00
// dst[i * 8 + 3] += 0.0f; // LFE
// dst[i * 8 + 4] += 0.0f; // rear L
// dst[i * 8 + 5] += 0.0f; // rear R
// dst[i * 8 + 6] += 0.0f; // side L
// dst[i * 8 + 7] += 0.0f; // side R
2016-03-21 20:43:03 +01:00
}
}
else
{
2018-12-20 23:35:49 +01:00
cellAudio . error ( " cellAudioAdd2chData(portNum=%d): invalid port.channel value (%d) " , portNum , port . num_channels ) ;
2016-03-21 20:43:03 +01:00
}
return CELL_OK ;
}
2017-09-20 12:01:57 +02:00
error_code cellAudioAdd6chData ( u32 portNum , vm : : ptr < float > src , float volume )
2016-03-21 20:43:03 +01:00
{
cellAudio . trace ( " cellAudioAdd6chData(portNum=%d, src=*0x%x, volume=%f) " , portNum , src , volume ) ;
2021-03-02 12:59:19 +01:00
auto & g_audio = g_fxo - > get < cell_audio > ( ) ;
2019-09-15 14:19:59 +02:00
2021-03-02 12:59:19 +01:00
std : : unique_lock lock ( g_audio . mutex ) ;
2016-03-21 20:43:03 +01:00
2021-03-02 12:59:19 +01:00
if ( ! g_audio . init )
2016-03-21 20:43:03 +01:00
{
return CELL_AUDIO_ERROR_NOT_INIT ;
}
if ( portNum > = AUDIO_PORT_COUNT | | ! src | | ! src . aligned ( ) )
{
return CELL_AUDIO_ERROR_PARAM ;
}
2021-03-02 12:59:19 +01:00
const audio_port & port = g_audio . ports [ portNum ] ;
2016-03-21 20:43:03 +01:00
2018-12-20 23:35:49 +01:00
const auto dst = port . get_vm_ptr ( ) ;
2016-03-21 20:43:03 +01:00
2019-09-15 14:19:59 +02:00
lock . unlock ( ) ;
2018-10-02 10:40:37 +02:00
2019-11-19 20:17:12 +01:00
volume = std : : isfinite ( volume ) ? std : : clamp ( volume , - 16.f , 16.f ) : 0.f ;
2022-05-08 20:09:31 +02:00
if ( port . num_channels = = 8 )
2016-03-21 20:43:03 +01:00
{
2020-06-01 12:19:47 +02:00
for ( u32 i = 0 ; i < CELL_AUDIO_BLOCK_SAMPLES ; i + + )
2016-03-21 20:43:03 +01:00
{
2022-10-30 01:49:38 +02:00
// Channel order in src is Front Left, Center, Front Right, Surround Left, Surround Right, LFE
2016-03-21 20:43:03 +01:00
dst [ i * 8 + 0 ] + = src [ i * 6 + 0 ] * volume ; // mix L ch
2022-10-30 01:49:38 +02:00
dst [ i * 8 + 1 ] + = src [ i * 6 + 2 ] * volume ; // mix R ch
dst [ i * 8 + 2 ] + = src [ i * 6 + 1 ] * volume ; // mix center
dst [ i * 8 + 3 ] + = src [ i * 6 + 5 ] * volume ; // mix LFE
dst [ i * 8 + 4 ] + = src [ i * 6 + 3 ] * volume ; // mix rear L
dst [ i * 8 + 5 ] + = src [ i * 6 + 4 ] * volume ; // mix rear R
2025-04-05 22:10:22 +02:00
// dst[i * 8 + 6] += 0.0f; // side L
2025-04-05 21:50:45 +02:00
// dst[i * 8 + 7] += 0.0f; // side R
2016-03-21 20:43:03 +01:00
}
}
else
{
2018-12-20 23:35:49 +01:00
cellAudio . error ( " cellAudioAdd6chData(portNum=%d): invalid port.channel value (%d) " , portNum , port . num_channels ) ;
2016-03-21 20:43:03 +01:00
}
return CELL_OK ;
}
2017-09-20 12:01:57 +02:00
error_code cellAudioMiscSetAccessoryVolume ( u32 devNum , float volume )
2016-03-21 20:43:03 +01:00
{
cellAudio . todo ( " cellAudioMiscSetAccessoryVolume(devNum=%d, volume=%f) " , devNum , volume ) ;
2020-06-01 12:19:47 +02:00
2021-03-02 12:59:19 +01:00
auto & g_audio = g_fxo - > get < cell_audio > ( ) ;
2020-06-01 12:19:47 +02:00
2021-03-02 12:59:19 +01:00
std : : unique_lock lock ( g_audio . mutex ) ;
2020-06-01 12:19:47 +02:00
2021-03-02 12:59:19 +01:00
if ( ! g_audio . init )
2020-06-01 12:19:47 +02:00
{
return CELL_AUDIO_ERROR_NOT_INIT ;
}
// TODO
2016-03-21 20:43:03 +01:00
return CELL_OK ;
}
2017-09-20 12:01:57 +02:00
error_code cellAudioSendAck ( u64 data3 )
2016-03-21 20:43:03 +01:00
{
2023-03-05 23:33:35 +01:00
cellAudio . trace ( " cellAudioSendAck(data3=0x%llx) " , data3 ) ;
auto & g_audio = g_fxo - > get < cell_audio > ( ) ;
std : : unique_lock lock ( g_audio . mutex ) ;
if ( ! g_audio . init )
{
return CELL_AUDIO_ERROR_NOT_INIT ;
}
// TODO: error checks
for ( cell_audio_thread : : key_info & k : g_audio . keys )
{
if ( k . source = = data3 )
{
k . ack_timestamp = get_system_time ( ) ;
break ;
}
}
2016-03-21 20:43:03 +01:00
return CELL_OK ;
}
2017-09-20 12:01:57 +02:00
error_code cellAudioSetPersonalDevice ( s32 iPersonalStream , s32 iDevice )
2016-03-21 20:43:03 +01:00
{
cellAudio . todo ( " cellAudioSetPersonalDevice(iPersonalStream=%d, iDevice=%d) " , iPersonalStream , iDevice ) ;
2020-06-01 12:19:47 +02:00
2021-03-02 12:59:19 +01:00
auto & g_audio = g_fxo - > get < cell_audio > ( ) ;
2020-06-01 12:19:47 +02:00
2021-03-02 12:59:19 +01:00
std : : unique_lock lock ( g_audio . mutex ) ;
2020-06-01 12:19:47 +02:00
2021-03-02 12:59:19 +01:00
if ( ! g_audio . init )
2020-06-01 12:19:47 +02:00
{
return CELL_AUDIO_ERROR_NOT_INIT ;
}
if ( iPersonalStream < 0 | | iPersonalStream > = 4 | |
( iDevice ! = CELL_AUDIO_PERSONAL_DEVICE_PRIMARY & & ( iDevice < 0 | | iDevice > = 5 ) ) )
{
return CELL_AUDIO_ERROR_PARAM ;
}
// TODO
2016-03-21 20:43:03 +01:00
return CELL_OK ;
}
2017-09-20 12:01:57 +02:00
error_code cellAudioUnsetPersonalDevice ( s32 iPersonalStream )
2016-03-21 20:43:03 +01:00
{
cellAudio . todo ( " cellAudioUnsetPersonalDevice(iPersonalStream=%d) " , iPersonalStream ) ;
2020-06-01 12:19:47 +02:00
2021-03-02 12:59:19 +01:00
auto & g_audio = g_fxo - > get < cell_audio > ( ) ;
2020-06-01 12:19:47 +02:00
2021-03-02 12:59:19 +01:00
std : : unique_lock lock ( g_audio . mutex ) ;
2020-06-01 12:19:47 +02:00
2021-03-02 12:59:19 +01:00
if ( ! g_audio . init )
2020-06-01 12:19:47 +02:00
{
return CELL_AUDIO_ERROR_NOT_INIT ;
}
if ( iPersonalStream < 0 | | iPersonalStream > = 4 )
{
return CELL_AUDIO_ERROR_PARAM ;
}
// TODO
2016-03-21 20:43:03 +01:00
return CELL_OK ;
}
DECLARE ( ppu_module_manager : : cellAudio ) ( " cellAudio " , [ ] ( )
2025-04-05 21:50:45 +02:00
{
// Private variables
REG_VAR ( cellAudio , g_audio_buffer ) . flag ( MFF_HIDDEN ) ;
REG_VAR ( cellAudio , g_audio_indices ) . flag ( MFF_HIDDEN ) ;
REG_FUNC ( cellAudio , cellAudioInit ) ;
REG_FUNC ( cellAudio , cellAudioPortClose ) ;
REG_FUNC ( cellAudio , cellAudioPortStop ) ;
REG_FUNC ( cellAudio , cellAudioGetPortConfig ) ;
REG_FUNC ( cellAudio , cellAudioPortStart ) ;
REG_FUNC ( cellAudio , cellAudioQuit ) ;
REG_FUNC ( cellAudio , cellAudioPortOpen ) ;
REG_FUNC ( cellAudio , cellAudioSetPortLevel ) ;
REG_FUNC ( cellAudio , cellAudioCreateNotifyEventQueue ) ;
REG_FUNC ( cellAudio , cellAudioCreateNotifyEventQueueEx ) ;
REG_FUNC ( cellAudio , cellAudioMiscSetAccessoryVolume ) ;
REG_FUNC ( cellAudio , cellAudioSetNotifyEventQueue ) ;
REG_FUNC ( cellAudio , cellAudioSetNotifyEventQueueEx ) ;
REG_FUNC ( cellAudio , cellAudioGetPortTimestamp ) ;
REG_FUNC ( cellAudio , cellAudioAdd2chData ) ;
REG_FUNC ( cellAudio , cellAudioAdd6chData ) ;
REG_FUNC ( cellAudio , cellAudioAddData ) ;
REG_FUNC ( cellAudio , cellAudioGetPortBlockTag ) ;
REG_FUNC ( cellAudio , cellAudioRemoveNotifyEventQueue ) ;
REG_FUNC ( cellAudio , cellAudioRemoveNotifyEventQueueEx ) ;
REG_FUNC ( cellAudio , cellAudioSendAck ) ;
REG_FUNC ( cellAudio , cellAudioSetPersonalDevice ) ;
REG_FUNC ( cellAudio , cellAudioUnsetPersonalDevice ) ;
} ) ;