2018-12-20 23:35:49 +01:00
# include " stdafx.h "
2016-03-21 20:43:03 +01:00
# include "Emu/System.h"
# include "Emu/IdManager.h"
# include "Emu/Cell/PPUModule.h"
# include "Emu/Cell/lv2/sys_event.h"
# include "cellAudio.h"
2018-12-20 23:35:49 +01:00
# include <atomic>
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
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 )
{
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 ) ;
}
return unknown ;
} ) ;
}
2018-12-21 03:13:22 +01:00
cell_audio_config : : cell_audio_config ( )
{
// Warn if audio backend does not support all requested features
if ( raw_buffering_enabled & & ! buffering_enabled )
{
cellAudio . error ( " Audio backend %s does not support buffering, this option will be ignored. " , backend - > GetName ( ) ) ;
}
if ( raw_time_stretching_enabled & & ! time_stretching_enabled )
{
cellAudio . error ( " Audio backend %s does not support time stretching, this option will be ignored. " , backend - > GetName ( ) ) ;
}
}
2018-12-16 18:40:50 +01:00
audio_ringbuffer : : audio_ringbuffer ( cell_audio_config & _cfg )
: cfg ( _cfg )
2018-12-21 03:13:22 +01:00
, backend ( _cfg . backend )
, buf_sz ( AUDIO_BUFFER_SAMPLES * _cfg . audio_channels )
2018-12-20 23:35:49 +01:00
, emu_paused ( Emu . IsPaused ( ) )
2016-03-21 20:43:03 +01:00
{
2018-12-20 23:35:49 +01:00
// Initialize buffers
2018-12-16 18:40:50 +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
if ( g_cfg . audio . dump_to_file )
{
2018-12-16 18:40:50 +01:00
m_dump . reset ( new AudioDumper ( cfg . audio_channels ) ) ;
}
2018-12-16 22:12:58 +01:00
// Initialize backend
2018-12-16 18:40:50 +01:00
{
2018-12-16 22:12:58 +01:00
std : : string str ;
backend - > dump_capabilities ( str ) ;
2018-12-21 03:13:22 +01:00
cellAudio . notice ( " cellAudio initializing. Backend: %s, Capabilities: %s " , backend - > GetName ( ) , str . c_str ( ) ) ;
2018-12-20 23:35:49 +01:00
}
2017-10-31 23:49:54 +01:00
2018-12-21 02:16:54 +01:00
backend - > Open ( cfg . num_allocated_buffers ) ;
2018-12-20 23:35:49 +01:00
backend_open = true ;
2018-12-16 18:40:50 +01:00
2018-12-21 03:13:22 +01:00
ASSERT ( ! get_backend_playing ( ) ) ;
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 ( )
{
if ( ! backend_open )
2018-12-16 18:40:50 +01:00
{
2018-12-20 23:35:49 +01:00
return ;
2018-12-16 18:40:50 +01:00
}
2016-03-21 20:43:03 +01:00
2018-12-21 03:13:22 +01:00
if ( get_backend_playing ( ) & & has_capability ( AudioBackend : : PLAY_PAUSE_FLUSH ) )
2018-12-16 18:40:50 +01:00
{
2018-12-20 23:35:49 +01:00
backend - > Pause ( ) ;
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 )
{
if ( ! has_capability ( AudioBackend : : SET_FREQUENCY_RATIO ) )
{
ASSERT ( new_ratio = = 1.0f ) ;
frequency_ratio = 1.0f ;
}
else
{
frequency_ratio = backend - > SetFrequencyRatio ( new_ratio ) ;
2018-12-21 03:13:22 +01:00
//cellAudio.trace("set_frequency_ratio(%1.2f) -> %1.2f", new_ratio, frequency_ratio);
2018-12-16 22:12:58 +01:00
}
return frequency_ratio ;
}
2018-12-20 23:35:49 +01:00
void audio_ringbuffer : : enqueue ( const float * in_buffer )
{
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
const void * buf = in_buffer ;
if ( buf = = nullptr )
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
}
2018-12-20 23:35:49 +01:00
// Dump audio if enabled
if ( m_dump )
{
2018-12-21 03:13:22 +01:00
m_dump - > WriteData ( buf , cfg . audio_buffer_size ) ;
2018-12-20 23:35:49 +01:00
}
2016-03-21 20:43:03 +01:00
2018-12-20 23:35:49 +01:00
// Enqueue audio
2018-12-21 03:13:22 +01:00
bool success = backend - > AddData ( buf , AUDIO_BUFFER_SAMPLES * cfg . audio_channels ) ;
2018-12-20 23:35:49 +01:00
if ( ! success )
2016-03-21 20:43:03 +01:00
{
2018-12-20 23:35:49 +01:00
cellAudio . error ( " Could not enqueue buffer onto audio backend. Attempting to recover... " ) ;
flush ( ) ;
return ;
}
2018-12-21 03:13:22 +01:00
if ( has_capability ( AudioBackend : : PLAY_PAUSE_FLUSH ) )
{
enqueued_samples + = AUDIO_BUFFER_SAMPLES ;
2018-12-20 23:35:49 +01:00
2018-12-21 03:13:22 +01:00
// Start playing audio
play ( ) ;
}
2018-12-20 23:35:49 +01:00
}
void audio_ringbuffer : : enqueue_silence ( u32 buf_count )
{
2018-12-21 03:13:22 +01:00
AUDIT ( has_capability ( AudioBackend : : PLAY_PAUSE_FLUSH ) ) ;
2018-12-20 23:35:49 +01:00
for ( u32 i = 0 ; i < buf_count ; i + + )
{
enqueue ( silence_buffer ) ;
}
}
void audio_ringbuffer : : play ( )
{
2018-12-21 03:13:22 +01:00
if ( ! has_capability ( AudioBackend : : PLAY_PAUSE_FLUSH ) )
{
playing = true ;
return ;
}
2018-12-21 02:16:54 +01:00
if ( has_capability ( AudioBackend : : IS_PLAYING ) & & playing )
2018-12-21 03:13:22 +01:00
{
2018-12-20 23:35:49 +01:00
return ;
2018-12-21 03:13:22 +01:00
}
2018-12-20 23:35:49 +01:00
2018-12-16 22:12:58 +01:00
if ( frequency_ratio ! = 1.0f )
{
set_frequency_ratio ( 1.0f ) ;
}
2018-12-20 23:35:49 +01:00
playing = true ;
ASSERT ( enqueued_samples > 0 ) ;
play_timestamp = get_timestamp ( ) ;
backend - > Play ( ) ;
}
void audio_ringbuffer : : flush ( )
{
2018-12-21 03:13:22 +01:00
if ( ! has_capability ( AudioBackend : : PLAY_PAUSE_FLUSH ) )
{
playing = false ;
return ;
}
//cellAudio.trace("Flushing an estimated %llu enqueued samples", enqueued_samples);
2018-12-20 23:35:49 +01:00
backend - > Pause ( ) ;
playing = false ;
backend - > Flush ( ) ;
2018-12-16 22:12:58 +01:00
if ( frequency_ratio ! = 1.0f )
{
set_frequency_ratio ( 1.0f ) ;
}
2018-12-20 23:35:49 +01:00
enqueued_samples = 0 ;
}
u64 audio_ringbuffer : : update ( )
{
// Check emulator pause state
if ( Emu . IsPaused ( ) )
{
// Emulator paused
2018-12-21 03:13:22 +01:00
if ( has_capability ( AudioBackend : : PLAY_PAUSE_FLUSH ) & & playing )
2016-03-21 20:43:03 +01:00
{
2018-12-20 23:35:49 +01:00
backend - > Pause ( ) ;
2016-03-21 20:43:03 +01:00
}
2018-12-20 23:35:49 +01:00
emu_paused = true ;
}
else if ( emu_paused )
{
// Emulator unpaused
play ( ) ;
emu_paused = false ;
}
2016-03-21 20:43:03 +01:00
2018-12-20 23:35:49 +01:00
const u64 timestamp = get_timestamp ( ) ;
2018-12-21 03:13:22 +01:00
const bool new_playing = ! emu_paused & & get_backend_playing ( ) ;
2016-03-21 20:43:03 +01:00
2018-12-20 23:35:49 +01:00
// Calculate how many audio samples have played since last time
2018-12-16 18:40:50 +01:00
if ( cfg . buffering_enabled & & ( playing | | new_playing ) )
2018-12-20 23:35:49 +01:00
{
2018-12-16 22:12:58 +01:00
if ( has_capability ( AudioBackend : : GET_NUM_ENQUEUED_SAMPLES ) )
2018-12-16 18:40:50 +01:00
{
// Backend supports querying for the remaining playtime, so just ask it
enqueued_samples = backend - > GetNumEnqueuedSamples ( ) ;
}
else
2016-03-21 20:43:03 +01:00
{
2018-12-16 18:40:50 +01:00
const u64 play_delta = timestamp - ( play_timestamp > update_timestamp ? play_timestamp : update_timestamp ) ;
2018-12-20 23:35:49 +01:00
2018-12-16 18:40:50 +01:00
// NOTE: Only works with a fixed sampling rate
2018-12-16 22:12:58 +01:00
const u64 delta_samples_tmp = play_delta * static_cast < u64 > ( cfg . audio_sampling_rate * frequency_ratio ) + last_remainder ;
2018-12-16 18:40:50 +01:00
last_remainder = delta_samples_tmp % 1'000'000 ;
const u64 delta_samples = delta_samples_tmp / 1'000'000 ;
2018-12-20 23:35:49 +01:00
2018-12-16 18:40:50 +01:00
//cellAudio.error("play_delta=%llu delta_samples=%llu", play_delta, delta_samples);
if ( delta_samples > 0 )
2018-12-20 23:35:49 +01:00
{
2018-12-16 18:40:50 +01:00
if ( enqueued_samples < delta_samples )
{
enqueued_samples = 0 ;
}
else
{
enqueued_samples - = delta_samples ;
}
if ( enqueued_samples = = 0 )
{
2018-12-21 03:13:22 +01:00
cellAudio . trace ( " Audio buffer about to underrun! " ) ;
2018-12-16 18:40:50 +01:00
}
2018-12-20 23:35:49 +01:00
}
}
}
// Update playing state
if ( playing ! = new_playing )
{
if ( ! new_playing )
{
2018-12-21 03:13:22 +01:00
cellAudio . warning ( " Audio backend stopped unexpectedly, likely due to a buffer underrun " ) ;
2018-12-20 23:35:49 +01:00
flush ( ) ;
playing = false ;
}
else
{
playing = true ;
}
}
// Store and return timestamp
update_timestamp = timestamp ;
return timestamp ;
}
void audio_port : : tag ( s32 offset )
{
auto port_pos = position ( 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 ;
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 ;
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
}
prev_touched_tag_nr = UINT32_MAX ;
}
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 ;
for ( auto & port : ports )
{
if ( port . state ! = audio_port_state : : started ) continue ;
active + + ;
auto port_buf = port . get_vm_ptr ( ) ;
u32 port_pos = port . position ( ) ;
// Find the last tag that has been touched
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 ;
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 + + )
{
// grab current value and re-tag atomically
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
retouched | = tag_nr < port . prev_touched_tag_nr & & port . prev_touched_tag_nr ! = UINT32_MAX ;
last_touched_tag_nr = tag_nr ;
}
}
// Decide whether the buffer is untouched, in progress, incomplete, or complete
if ( last_touched_tag_nr = = UINT32_MAX )
{
// 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 + + ;
}
// 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 )
{
// hasn't been touched since the last call
incomplete + + ;
}
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 )
{
2018-12-21 03:13:22 +01:00
// Memset previous buffer to 0 and tag
2018-12-20 23:35:49 +01:00
for ( auto & port : ports )
{
if ( port . state ! = audio_port_state : : started ) continue ;
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 ) ;
}
}
}
void cell_audio_thread : : advance ( u64 timestamp , bool reset )
{
auto _locked = g_idm - > lock < named_thread < cell_audio_thread > > ( 0 ) ;
// update ports
if ( reset )
{
reset_ports ( 0 ) ;
}
for ( auto & port : ports )
{
if ( port . state ! = audio_port_state : : started ) continue ;
port . global_counter = m_counter ;
port . active_counter + + ;
port . timestamp = timestamp ;
port . cur_pos = port . position ( 1 ) ;
m_indexes [ port . number ] = port . cur_pos ;
}
2018-12-16 22:12:58 +01:00
if ( cfg . buffering_enabled )
{
// Calculate rolling average of enqueued playtime
2018-12-21 03:13:22 +01:00
const u64 enqueued_playtime = ringbuffer - > get_enqueued_playtime ( /* raw */ true ) ;
2018-12-16 22:12:58 +01:00
m_average_playtime = cfg . period_average_alpha * enqueued_playtime + ( 1.0f - cfg . period_average_alpha ) * m_average_playtime ;
//cellAudio.error("m_average_playtime=%4.2f, enqueued_playtime=%u", m_average_playtime, enqueued_playtime);
}
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)
std : : array < std : : shared_ptr < lv2_event_queue > , MAX_AUDIO_EVENT_QUEUES > queues ;
u32 queue_count = 0 ;
for ( u64 key : keys )
{
if ( auto queue = lv2_event_queue : : find ( key ) )
{
queues [ queue_count + + ] = queue ;
2016-03-21 20:43:03 +01:00
}
2018-12-20 23:35:49 +01:00
}
_locked . unlock ( ) ;
for ( u32 i = 0 ; i < queue_count ; i + + )
{
queues [ i ] - > send ( 0 , 0 , 0 , 0 ) ; // TODO: check arguments
}
}
2016-03-21 20:43:03 +01:00
2018-12-20 23:35:49 +01:00
void cell_audio_thread : : operator ( ) ( )
{
thread_ctrl : : set_native_priority ( 1 ) ;
2016-03-21 20:43:03 +01:00
2018-12-20 23:35:49 +01:00
// Allocate ringbuffer
2018-12-16 18:40:50 +01:00
ringbuffer . reset ( new audio_ringbuffer ( cfg ) ) ;
2016-03-21 20:43:03 +01:00
2018-12-20 23:35:49 +01:00
// Initialize loop variables
m_counter = 0 ;
m_start_time = ringbuffer - > get_timestamp ( ) ;
m_last_period_end = m_start_time ;
m_dynamic_period = 0 ;
u32 untouched_expected = 0 ;
u32 in_progress_expected = 0 ;
// Main cellAudio loop
while ( thread_ctrl : : state ( ) ! = thread_state : : aborting & & ! Emu . IsStopped ( ) )
{
const u64 timestamp = ringbuffer - > update ( ) ;
2016-03-21 20:43:03 +01:00
2018-12-20 23:35:49 +01:00
if ( Emu . IsPaused ( ) )
2016-03-21 20:43:03 +01:00
{
2018-12-20 23:35:49 +01:00
thread_ctrl : : wait_for ( 10000 ) ;
continue ;
}
2016-03-21 20:43:03 +01:00
2018-12-20 23:35:49 +01:00
// TODO: send beforemix event (in ~2,6 ms before mixing)
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
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
{
2018-12-20 23:35:49 +01:00
thread_ctrl : : wait_for ( get_thread_wait_delay ( time_left ) ) ;
continue ;
}
}
else
{
2018-12-16 22:12:58 +01:00
const u64 enqueued_samples = ringbuffer - > get_enqueued_samples ( ) ;
f32 frequency_ratio = ringbuffer - > get_frequency_ratio ( ) ;
u64 enqueued_playtime = ringbuffer - > get_enqueued_playtime ( ) ;
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 bool playing = ringbuffer - > is_playing ( ) ;
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 ) ;
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
2018-12-20 23:35:49 +01:00
// Wait for a dynamic period - try to maintain an average as close as possible to 5.(3)ms
2018-12-16 22:12:58 +01:00
if ( ! playing )
2016-03-21 20:43:03 +01:00
{
2018-12-16 22:12:58 +01:00
// When the buffer is empty, always use the correct block period
m_dynamic_period = cfg . audio_block_period ;
}
else
{
// 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 adjusted 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
{
2018-12-21 03:13:22 +01:00
desired_duration_adjusted / = std : : max ( average_playtime_ratio , 0.25f ) ;
2016-03-21 20:43:03 +01:00
}
2018-12-16 22:12:58 +01:00
if ( cfg . time_stretching_enabled )
2016-03-21 20:43:03 +01:00
{
2018-12-16 22:12:58 +01:00
// Calculate what the playtime is without a frequency ratio
const u64 raw_enqueued_playtime = ringbuffer - > get_enqueued_playtime ( /* raw= */ true ) ;
2018-12-20 23:35:49 +01:00
// 1.0 means exactly as desired
// <1.0 means not as full as desired
// >1.0 means more full than desired
2018-12-16 22:12:58 +01:00
const f32 desired_duration_rate = raw_enqueued_playtime / desired_duration_adjusted ;
2016-03-21 20:43:03 +01:00
2018-12-16 22:12:58 +01:00
// update frequency ratio if necessary
f32 new_ratio = frequency_ratio ;
2018-12-21 03:13:22 +01:00
if (
( desired_duration_rate < cfg . time_stretching_threshold & & desired_duration_rate < frequency_ratio - cfg . time_stretching_step ) | | // Reduce frequency ratio below threshold in 0.1f steps
( desired_duration_rate > frequency_ratio + cfg . time_stretching_step ) // Increase frequency ratio in 0.1f steps
)
2018-12-20 23:35:49 +01:00
{
2018-12-21 03:13:22 +01:00
new_ratio = ringbuffer - > set_frequency_ratio ( std : : min ( desired_duration_rate , 1.0f ) ) ;
2016-03-21 20:43:03 +01:00
}
2018-12-16 22:12:58 +01:00
if ( new_ratio ! = frequency_ratio )
{
// ratio changed, calculate new dynamic period
frequency_ratio = new_ratio ;
enqueued_playtime = ringbuffer - > get_enqueued_playtime ( ) ;
m_dynamic_period = 0 ;
}
}
// 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 ;
m_dynamic_period = cfg . minimum_block_period + static_cast < u64 > ( ( cfg . audio_block_period - cfg . minimum_block_period ) * multiplier ) ;
2016-03-21 20:43:03 +01:00
}
}
2018-12-20 23:35:49 +01:00
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
{
2018-12-20 23:35:49 +01:00
thread_ctrl : : wait_for ( get_thread_wait_delay ( time_left ) ) ;
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
2018-12-23 23:58:12 +01:00
cellAudio . trace ( " advancing time: no active ports, enqueued_buffers=%llu " , enqueued_buffers ) ;
2018-12-20 23:35:49 +01:00
if ( playing )
{
ringbuffer - > enqueue_silence ( 1 ) ;
}
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
//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
{
if ( ! playing )
{
// We ran out of buffer, probably because we waited too long
// Don't enqueue anything, just advance time
2018-12-23 23:58:12 +01:00
cellAudio . trace ( " advancing time: untouched=%u/%u (expected=%u), enqueued_buffers=0 " , untouched , active_ports , untouched_expected ) ;
2018-12-20 23:35:49 +01:00
untouched_expected = untouched ;
advance ( timestamp ) ;
2018-12-25 22:41:17 +01:00
continue ;
2016-03-21 20:43:03 +01:00
}
2018-12-25 22:41:17 +01:00
// We allow untouched buffers to go through after a timeout
// While we should always wait for in-progress buffers, games may sometimes "skip" audio periods entirely if they're falling behind
if ( time_since_last_period < cfg . untouched_timeout )
2016-03-21 20:43:03 +01:00
{
2018-12-23 23:58:12 +01:00
cellAudio . trace ( " waiting: untouched=%u/%u (expected=%u), enqueued_buffers=%llu " , untouched , active_ports , untouched_expected , enqueued_buffers ) ;
2018-12-20 23:35:49 +01:00
thread_ctrl : : wait_for ( 1000 ) ;
2018-12-25 22:41:17 +01:00
continue ;
}
else if ( untouched = = active_ports )
{
// There's no audio in the buffers, simply advance time
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
}
}
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
}
2018-12-16 22:12:58 +01:00
/*cellAudio.error("time_since_last=%llu, dynamic_period=%llu => %3.2f%%, average_period=%4.2f => %3.2f%%", time_since_last_period,
m_dynamic_period , ( ( ( f32 ) m_dynamic_period ) / cfg . audio_block_period ) * 100.0f ,
m_average_period , ( m_average_period / cfg . audio_block_period ) * 100.0f ) ; */
2018-12-20 23:35:49 +01: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
2018-12-20 23:35:49 +01:00
// Warn if we enqueued untouched/incomplete buffers
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
}
2018-12-20 23:35:49 +01:00
// Handle audio restart
if ( ! playing )
2017-04-09 22:43:53 +02:00
{
2018-12-20 23:35:49 +01:00
// We are not playing (likely buffer underrun)
// align to 5.(3)ms on global clock
2018-12-16 18:40:50 +01:00
const s64 audio_period_alignment_delta = ( timestamp - m_start_time ) % cfg . audio_block_period ;
if ( audio_period_alignment_delta > cfg . period_comparison_margin )
2017-04-09 22:43:53 +02:00
{
2018-12-16 18:40:50 +01:00
thread_ctrl : : wait_for ( audio_period_alignment_delta - cfg . period_comparison_margin ) ;
2017-04-09 22:43:53 +02:00
}
2018-12-20 23:35:49 +01:00
// Flush, add silence, restart algorithm
2018-12-23 23:58:12 +01:00
cellAudio . trace ( " play/resume audio: received first audio buffer " ) ;
2018-12-20 23:35:49 +01:00
ringbuffer - > flush ( ) ;
2018-12-16 18:40:50 +01:00
ringbuffer - > enqueue_silence ( cfg . desired_full_buffers ) ;
2018-12-20 23:35:49 +01:00
finish_port_volume_stepping ( ) ;
2018-12-21 02:16:54 +01:00
m_average_playtime = static_cast < f32 > ( ringbuffer - > get_enqueued_playtime ( ) ) ;
2016-03-21 20:43:03 +01:00
}
}
2018-12-20 23:35:49 +01:00
// Mix
float * buf = ringbuffer - > get_current_buffer ( ) ;
2018-12-16 18:40:50 +01:00
if ( cfg . audio_channels = = 2 )
2018-12-20 23:35:49 +01:00
{
mix < true > ( buf ) ;
}
2018-12-16 18:40:50 +01:00
else if ( cfg . audio_channels = = 8 )
2018-12-20 23:35:49 +01:00
{
mix < false > ( buf ) ;
}
else
{
2018-12-16 18:40:50 +01:00
fmt : : throw_exception ( " Unsupported number of audio channels: %u " , cfg . audio_channels ) ;
2018-12-20 23:35:49 +01:00
}
// Enqueue
ringbuffer - > enqueue ( ) ;
// Advance time
advance ( timestamp ) ;
}
// Destroy ringbuffer
ringbuffer . reset ( ) ;
}
template < bool downmix_to_2ch >
void cell_audio_thread : : mix ( float * out_buffer , s32 offset )
{
AUDIT ( out_buffer ! = nullptr ) ;
constexpr u32 channels = downmix_to_2ch ? 2 : 8 ;
constexpr u32 out_buffer_sz = channels * AUDIO_BUFFER_SAMPLES ;
bool first_mix = true ;
// mixing
for ( auto & port : ports )
{
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 ) ;
static const float k = 1.0f ;
float & m = port . level ;
// part of cellAudioSetPortLevel functionality
// spread port volume changes over 13ms
auto step_volume = [ ] ( audio_port & port )
2016-03-21 20:43:03 +01:00
{
2018-12-20 23:35:49 +01:00
const auto 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 ;
port . level_set . compare_and_swap ( param , { param . value , 0.0f } ) ;
}
}
} ;
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
{
2018-12-20 23:35:49 +01:00
if ( first_mix )
2016-03-21 20:43:03 +01:00
{
2018-12-20 23:35:49 +01:00
for ( u32 out = 0 , in = 0 ; out < out_buffer_sz ; out + = channels , in + = 2 )
{
step_volume ( port ) ;
2016-03-21 20:43:03 +01:00
2018-12-20 23:35:49 +01:00
const float left = buf [ in + 0 ] * m ;
const float right = buf [ in + 1 ] * m ;
2016-03-21 20:43:03 +01:00
2018-12-20 23:35:49 +01:00
out_buffer [ out + 0 ] = left ;
out_buffer [ out + 1 ] = right ;
2016-03-21 20:43:03 +01:00
2018-12-20 23:35:49 +01:00
if ( ! downmix_to_2ch )
{
out_buffer [ out + 2 ] = 0.0f ;
out_buffer [ out + 3 ] = 0.0f ;
out_buffer [ out + 4 ] = 0.0f ;
out_buffer [ out + 5 ] = 0.0f ;
out_buffer [ out + 6 ] = 0.0f ;
out_buffer [ out + 7 ] = 0.0f ;
}
}
first_mix = false ;
}
else
{
for ( u32 out = 0 , in = 0 ; out < out_buffer_sz ; out + = channels , in + = 2 )
{
step_volume ( port ) ;
2016-03-21 20:43:03 +01:00
2018-12-20 23:35:49 +01:00
const float left = buf [ in + 0 ] * m ;
const float right = buf [ in + 1 ] * m ;
2016-03-21 20:43:03 +01:00
2018-12-20 23:35:49 +01:00
out_buffer [ out + 0 ] + = left ;
out_buffer [ out + 1 ] + = right ;
}
}
}
else if ( port . num_channels = = 8 )
{
if ( first_mix )
2016-03-21 20:43:03 +01:00
{
2018-12-20 23:35:49 +01:00
for ( u32 out = 0 , in = 0 ; out < out_buffer_sz ; out + = channels , in + = 8 )
2016-03-21 20:43:03 +01:00
{
2018-12-20 23:35:49 +01:00
step_volume ( port ) ;
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 rear_left = buf [ in + 4 ] * m ;
const float rear_right = buf [ in + 5 ] * m ;
const float side_left = buf [ in + 6 ] * m ;
const float side_right = buf [ in + 7 ] * m ;
if ( downmix_to_2ch )
{
const float mid = ( center + low_freq ) * 0.708f ;
out_buffer [ out + 0 ] = ( left + rear_left + side_left + mid ) * k ;
out_buffer [ out + 1 ] = ( right + rear_right + side_right + mid ) * k ;
}
else
{
out_buffer [ out + 0 ] = left ;
out_buffer [ out + 1 ] = right ;
out_buffer [ out + 2 ] = center ;
out_buffer [ out + 3 ] = low_freq ;
out_buffer [ out + 4 ] = rear_left ;
out_buffer [ out + 5 ] = rear_right ;
out_buffer [ out + 6 ] = side_left ;
out_buffer [ out + 7 ] = side_right ;
}
2016-03-21 20:43:03 +01:00
}
2018-12-20 23:35:49 +01:00
first_mix = false ;
2016-03-21 20:43:03 +01:00
}
2018-12-20 23:35:49 +01:00
else
{
for ( u32 out = 0 , in = 0 ; out < out_buffer_sz ; out + = channels , in + = 8 )
{
step_volume ( port ) ;
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 rear_left = buf [ in + 4 ] * m ;
const float rear_right = buf [ in + 5 ] * m ;
const float side_left = buf [ in + 6 ] * m ;
const float side_right = buf [ in + 7 ] * m ;
if ( downmix_to_2ch )
{
const float mid = ( center + low_freq ) * 0.708f ;
out_buffer [ out + 0 ] + = ( left + rear_left + side_left + mid ) * k ;
out_buffer [ out + 1 ] + = ( right + rear_right + side_right + mid ) * k ;
}
else
{
out_buffer [ out + 0 ] + = left ;
out_buffer [ out + 1 ] + = right ;
out_buffer [ out + 2 ] + = center ;
out_buffer [ out + 3 ] + = low_freq ;
out_buffer [ out + 4 ] + = rear_left ;
out_buffer [ out + 5 ] + = rear_right ;
out_buffer [ out + 6 ] + = side_left ;
out_buffer [ out + 7 ] + = side_right ;
}
}
}
}
else
{
fmt : : throw_exception ( " Unknown channel count (port=%u, channel=%d) " HERE , 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
// Nothing was mixed, memset out_buffer to 0
if ( first_mix )
{
std : : memset ( out_buffer , 0 , out_buffer_sz * sizeof ( float ) ) ;
}
else if ( g_cfg . audio . convert_to_u16 )
{
// convert the data from float to u16 with clipping:
// 2x MULPS
// 2x MAXPS (optional)
// 2x MINPS (optional)
// 2x CVTPS2DQ (converts float to s32)
// PACKSSDW (converts s32 to s16 with signed saturation)
2016-03-21 20:43:03 +01:00
2018-12-20 23:35:49 +01:00
for ( size_t i = 0 ; i < out_buffer_sz ; i + = 8 )
2016-03-21 20:43:03 +01:00
{
2018-12-20 23:35:49 +01:00
const auto scale = _mm_set1_ps ( 0x8000 ) ;
_mm_store_ps ( out_buffer + i / 2 , _mm_castsi128_ps ( _mm_packs_epi32 (
_mm_cvtps_epi32 ( _mm_mul_ps ( _mm_load_ps ( out_buffer + i ) , scale ) ) ,
_mm_cvtps_epi32 ( _mm_mul_ps ( _mm_load_ps ( out_buffer + i + 4 ) , scale ) ) ) ) ) ;
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
for ( auto & port : ports )
{
if ( port . state ! = audio_port_state : : started ) continue ;
const auto param = port . level_set . load ( ) ;
port . level = param . value ;
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() " ) ;
2018-10-01 19:58:34 +02:00
const auto buf = vm : : cast ( vm : : alloc ( AUDIO_PORT_OFFSET * AUDIO_PORT_COUNT , vm : : main ) ) ;
const auto ind = vm : : cast ( vm : : alloc ( sizeof ( u64 ) * AUDIO_PORT_COUNT , vm : : main ) ) ;
2016-03-21 20:43:03 +01:00
// Start audio thread
2018-12-20 23:35:49 +01:00
auto g_audio = g_idm - > lock < named_thread < cell_audio_thread > > ( id_new ) ;
2016-03-21 20:43:03 +01:00
if ( ! g_audio )
{
2018-10-01 19:58:34 +02:00
vm : : dealloc ( buf ) ;
vm : : dealloc ( ind ) ;
2016-03-21 20:43:03 +01:00
return CELL_AUDIO_ERROR_ALREADY_INIT ;
}
2018-12-20 23:35:49 +01:00
g_audio . create ( " cellAudio Thread " , buf , ind ) ;
2016-03-21 20:43:03 +01:00
return CELL_OK ;
}
2018-10-11 00:17:19 +02:00
error_code cellAudioQuit ( ppu_thread & ppu )
2016-03-21 20:43:03 +01:00
{
cellAudio . warning ( " cellAudioQuit() " ) ;
// Stop audio thread
2018-12-20 23:35:49 +01:00
auto g_audio = g_idm - > lock < named_thread < cell_audio_thread > > ( 0 ) ;
2016-03-21 20:43:03 +01:00
if ( ! g_audio )
{
return CELL_AUDIO_ERROR_NOT_INIT ;
}
2018-10-02 10:40:37 +02:00
// Signal to abort, release lock
* g_audio . get ( ) = thread_state : : aborting ;
g_audio . unlock ( ) ;
while ( true )
{
2018-10-11 00:17:19 +02:00
if ( ppu . is_stopped ( ) )
{
return 0 ;
}
2018-10-02 10:40:37 +02:00
thread_ctrl : : wait_for ( 1000 ) ;
2018-12-20 23:35:49 +01:00
auto g_audio = g_idm - > lock < named_thread < cell_audio_thread > > ( 0 ) ;
2018-10-02 10:40:37 +02:00
if ( * g_audio . get ( ) = = thread_state : : finished )
{
2018-10-06 16:35:54 +02:00
const auto buf = g_audio - > ports [ 0 ] . addr ;
const auto ind = g_audio - > ports [ 0 ] . index ;
2018-10-02 10:40:37 +02:00
g_audio . destroy ( ) ;
2018-10-06 16:35:54 +02:00
g_audio . unlock ( ) ;
vm : : dealloc ( buf . addr ( ) ) ;
vm : : dealloc ( ind . addr ( ) ) ;
2018-10-02 10:40:37 +02:00
break ;
}
}
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 ) ;
2018-12-20 23:35:49 +01:00
const auto g_audio = g_idm - > lock < named_thread < cell_audio_thread > > ( 0 ) ;
2016-03-21 20:43:03 +01:00
if ( ! g_audio )
{
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 & &
num_channels )
2016-03-21 20:43:03 +01:00
{
return CELL_AUDIO_ERROR_PARAM ;
}
2018-12-20 23:35:49 +01:00
if ( num_blocks ! = CELL_AUDIO_BLOCK_8 & &
num_blocks ! = CELL_AUDIO_BLOCK_16 & &
num_blocks ! = 2 & &
num_blocks ! = 4 & &
num_blocks ! = 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 ) ;
}
// Open audio port
const auto port = g_audio - > open_port ( ) ;
if ( ! port )
{
return CELL_AUDIO_ERROR_PORT_FULL ;
}
2018-12-20 23:35:49 +01: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 * port - > block_size ( ) ) ;
port - > cur_pos = 0 ;
port - > global_counter = g_audio - > m_counter ;
port - > active_counter = 0 ;
port - > timestamp = g_audio - > m_last_period_end ;
2016-03-21 20:43:03 +01:00
if ( attr & CELL_AUDIO_PORTATTR_INITLEVEL )
{
2018-10-20 08:05:02 +02:00
port - > level = audioParam - > level * g_cfg . audio . volume / 100.0f ;
2016-03-21 20:43:03 +01:00
}
else
{
2018-10-20 08:05:02 +02:00
port - > level = g_cfg . audio . volume / 100.0f ;
2016-03-21 20:43:03 +01:00
}
port - > level_set . store ( { port - > level , 0.0f } ) ;
* 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
2018-12-20 23:35:49 +01:00
const auto g_audio = g_idm - > lock < named_thread < cell_audio_thread > > ( 0 ) ;
2016-03-21 20:43:03 +01:00
if ( ! g_audio )
{
return CELL_AUDIO_ERROR_NOT_INIT ;
}
if ( ! portConfig | | portNum > = AUDIO_PORT_COUNT )
{
return CELL_AUDIO_ERROR_PARAM ;
}
audio_port & port = g_audio - > ports [ portNum ] ;
portConfig - > readIndexAddr = port . index ;
switch ( auto state = port . state . load ( ) )
{
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 ;
2016-08-08 18:01:06 +02:00
default : fmt : : throw_exception ( " Invalid port state (%d: %d) " , portNum , ( u32 ) state ) ;
2016-03-21 20:43:03 +01:00
}
2018-12-20 23:35:49 +01:00
portConfig - > nChannel = port . num_channels ;
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 ) ;
2018-12-20 23:35:49 +01:00
const auto g_audio = g_idm - > lock < named_thread < cell_audio_thread > > ( 0 ) ;
2016-03-21 20:43:03 +01:00
if ( ! g_audio )
{
return CELL_AUDIO_ERROR_NOT_INIT ;
}
if ( portNum > = AUDIO_PORT_COUNT )
{
return CELL_AUDIO_ERROR_PARAM ;
}
switch ( auto state = g_audio - > ports [ portNum ] . state . compare_and_swap ( audio_port_state : : opened , audio_port_state : : started ) )
{
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 ;
2016-08-08 18:01:06 +02:00
default : fmt : : throw_exception ( " Invalid port state (%d: %d) " , portNum , ( 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 ) ;
2018-12-20 23:35:49 +01:00
const auto g_audio = g_idm - > lock < named_thread < cell_audio_thread > > ( 0 ) ;
2016-03-21 20:43:03 +01:00
if ( ! g_audio )
{
return CELL_AUDIO_ERROR_NOT_INIT ;
}
if ( portNum > = AUDIO_PORT_COUNT )
{
return CELL_AUDIO_ERROR_PARAM ;
}
switch ( auto state = g_audio - > ports [ portNum ] . state . exchange ( audio_port_state : : closed ) )
{
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 ;
2016-08-08 18:01:06 +02:00
default : fmt : : throw_exception ( " Invalid port state (%d: %d) " , portNum , ( 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 ) ;
2018-12-20 23:35:49 +01:00
const auto g_audio = g_idm - > lock < named_thread < cell_audio_thread > > ( 0 ) ;
2016-03-21 20:43:03 +01:00
if ( ! g_audio )
{
return CELL_AUDIO_ERROR_NOT_INIT ;
}
if ( portNum > = AUDIO_PORT_COUNT )
{
return CELL_AUDIO_ERROR_PARAM ;
}
switch ( auto state = g_audio - > ports [ portNum ] . state . compare_and_swap ( audio_port_state : : started , audio_port_state : : opened ) )
{
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 ;
2016-08-08 18:01:06 +02:00
default : fmt : : throw_exception ( " Invalid port state (%d: %d) " , portNum , ( 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 ) ;
2018-12-20 23:35:49 +01:00
const auto g_audio = g_idm - > lock < named_thread < cell_audio_thread > > ( 0 ) ;
2016-03-21 20:43:03 +01:00
if ( ! g_audio )
{
return CELL_AUDIO_ERROR_NOT_INIT ;
}
if ( portNum > = AUDIO_PORT_COUNT )
{
return CELL_AUDIO_ERROR_PARAM ;
}
audio_port & port = g_audio - > ports [ portNum ] ;
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 )
{
cellAudio . error ( " cellAudioGetPortTimestamp(portNum=%d, tag=0x%llx, stamp=*0x%x) -> CELL_AUDIO_ERROR_TAG_NOT_FOUND " , portNum , tag , stamp ) ;
return CELL_AUDIO_ERROR_TAG_NOT_FOUND ;
}
2016-03-21 20:43:03 +01:00
2018-12-20 23:35:49 +01:00
u64 delta_tag = port . global_counter - tag ;
2018-12-16 18:40:50 +01:00
u64 delta_tag_stamp = delta_tag * g_audio - > cfg . audio_block_period ;
2018-12-20 23:35:49 +01:00
* stamp = 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 ) ;
2018-12-20 23:35:49 +01:00
const auto g_audio = g_idm - > lock < named_thread < cell_audio_thread > > ( 0 ) ;
2016-03-21 20:43:03 +01:00
if ( ! g_audio )
{
return CELL_AUDIO_ERROR_NOT_INIT ;
}
if ( portNum > = AUDIO_PORT_COUNT )
{
return CELL_AUDIO_ERROR_PARAM ;
}
audio_port & port = g_audio - > ports [ portNum ] ;
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 ;
}
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 ) ;
2018-12-20 23:35:49 +01:00
const auto g_audio = g_idm - > lock < named_thread < cell_audio_thread > > ( 0 ) ;
2016-03-21 20:43:03 +01:00
if ( ! g_audio )
{
return CELL_AUDIO_ERROR_NOT_INIT ;
}
if ( portNum > = AUDIO_PORT_COUNT )
{
return CELL_AUDIO_ERROR_PARAM ;
}
audio_port & port = g_audio - > ports [ portNum ] ;
if ( port . state = = audio_port_state : : closed )
{
return CELL_AUDIO_ERROR_PORT_NOT_OPEN ;
}
2018-10-20 08:05:02 +02:00
level * = g_cfg . audio . volume / 100.0f ;
2016-03-21 20:43:03 +01:00
if ( level > = 0.0f )
{
port . level_set . exchange ( { level , ( port . level - level ) / 624.0f } ) ;
}
else
{
cellAudio . todo ( " cellAudioSetPortLevel(%d): negative level value (%f) " , portNum , level ) ;
}
return CELL_OK ;
}
2017-09-20 12:01:57 +02:00
error_code cellAudioCreateNotifyEventQueue ( vm : : ptr < u32 > id , vm : : ptr < u64 > key )
2016-03-21 20:43:03 +01:00
{
cellAudio . warning ( " cellAudioCreateNotifyEventQueue(id=*0x%x, key=*0x%x) " , id , key ) ;
2017-02-03 22:36:04 +01:00
vm : : var < sys_event_queue_attribute_t > attr ;
attr - > protocol = SYS_SYNC_FIFO ;
attr - > type = SYS_PPU_QUEUE ;
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 ;
if ( const s32 res = sys_event_queue_create ( id , attr , key_value , 32 ) )
{
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 ;
}
2017-09-20 12:01:57 +02:00
error_code cellAudioCreateNotifyEventQueueEx ( vm : : ptr < u32 > id , vm : : ptr < u64 > key , u32 iFlags )
2016-03-21 20:43:03 +01:00
{
cellAudio . todo ( " cellAudioCreateNotifyEventQueueEx(id=*0x%x, key=*0x%x, iFlags=0x%x) " , id , key , iFlags ) ;
if ( iFlags & ~ CELL_AUDIO_CREATEEVENTFLAG_SPU )
{
return CELL_AUDIO_ERROR_PARAM ;
}
// TODO
return CELL_AUDIO_ERROR_EVENT_QUEUE ;
}
2017-09-20 12:01:57 +02:00
error_code cellAudioSetNotifyEventQueue ( u64 key )
2016-03-21 20:43:03 +01:00
{
cellAudio . warning ( " cellAudioSetNotifyEventQueue(key=0x%llx) " , key ) ;
2018-12-20 23:35:49 +01:00
const auto g_audio = g_idm - > lock < named_thread < cell_audio_thread > > ( 0 ) ;
2016-03-21 20:43:03 +01:00
if ( ! g_audio )
{
return CELL_AUDIO_ERROR_NOT_INIT ;
}
for ( auto k : g_audio - > keys ) // check for duplicates
{
if ( k = = key )
{
return CELL_AUDIO_ERROR_TRANS_EVENT ;
}
}
g_audio - > keys . emplace_back ( key ) ;
return CELL_OK ;
}
2017-09-20 12:01:57 +02:00
error_code cellAudioSetNotifyEventQueueEx ( u64 key , u32 iFlags )
2016-03-21 20:43:03 +01:00
{
cellAudio . todo ( " cellAudioSetNotifyEventQueueEx(key=0x%llx, iFlags=0x%x) " , key , iFlags ) ;
// TODO
return CELL_OK ;
}
2017-09-20 12:01:57 +02:00
error_code cellAudioRemoveNotifyEventQueue ( u64 key )
2016-03-21 20:43:03 +01:00
{
cellAudio . warning ( " cellAudioRemoveNotifyEventQueue(key=0x%llx) " , key ) ;
2018-12-20 23:35:49 +01:00
const auto g_audio = g_idm - > lock < named_thread < cell_audio_thread > > ( 0 ) ;
2016-03-21 20:43:03 +01:00
if ( ! g_audio )
{
return CELL_AUDIO_ERROR_NOT_INIT ;
}
for ( auto i = g_audio - > keys . begin ( ) ; i ! = g_audio - > keys . end ( ) ; i + + )
{
if ( * i = = key )
{
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 ;
}
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 ) ;
// TODO
return CELL_OK ;
}
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 ) ;
2018-12-20 23:35:49 +01:00
auto g_audio = g_idm - > lock < named_thread < cell_audio_thread > > ( 0 ) ;
2016-03-21 20:43:03 +01:00
if ( ! g_audio )
{
return CELL_AUDIO_ERROR_NOT_INIT ;
}
if ( portNum > = AUDIO_PORT_COUNT | | ! src | | ! src . aligned ( ) )
{
return CELL_AUDIO_ERROR_PARAM ;
}
if ( samples ! = 256 )
{
// despite the docs, seems that only fixed value is supported
cellAudio . error ( " cellAudioAddData(): invalid samples value (%d) " , samples ) ;
return CELL_AUDIO_ERROR_PARAM ;
}
const audio_port & port = g_audio - > ports [ portNum ] ;
2018-12-20 23:35:49 +01:00
const auto dst = port . get_vm_ptr ( ) ;
2016-03-21 20:43:03 +01:00
2018-10-02 10:40:37 +02:00
g_audio . unlock ( ) ;
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 ) ;
2018-12-20 23:35:49 +01:00
auto g_audio = g_idm - > lock < named_thread < cell_audio_thread > > ( 0 ) ;
2016-03-21 20:43:03 +01:00
if ( ! g_audio )
{
return CELL_AUDIO_ERROR_NOT_INIT ;
}
if ( portNum > = AUDIO_PORT_COUNT | | ! src | | ! src . aligned ( ) )
{
return CELL_AUDIO_ERROR_PARAM ;
}
if ( samples ! = 256 )
{
// despite the docs, seems that only fixed value is supported
cellAudio . error ( " cellAudioAdd2chData(): invalid samples value (%d) " , samples ) ;
return CELL_AUDIO_ERROR_PARAM ;
}
const audio_port & port = g_audio - > ports [ portNum ] ;
2018-12-20 23:35:49 +01:00
const auto dst = port . get_vm_ptr ( ) ;
2016-03-21 20:43:03 +01:00
2018-10-02 10:40:37 +02:00
g_audio . unlock ( ) ;
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 = = 6 )
2016-03-21 20:43:03 +01:00
{
for ( u32 i = 0 ; i < samples ; i + + )
{
dst [ i * 6 + 0 ] + = src [ i * 2 + 0 ] * volume ; // mix L ch
dst [ i * 6 + 1 ] + = src [ i * 2 + 1 ] * volume ; // mix R ch
//dst[i * 6 + 2] += 0.0f; // center
//dst[i * 6 + 3] += 0.0f; // LFE
//dst[i * 6 + 4] += 0.0f; // rear L
//dst[i * 6 + 5] += 0.0f; // rear R
}
}
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
//dst[i * 8 + 2] += 0.0f; // center
//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
}
}
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 ) ;
2018-12-20 23:35:49 +01:00
auto g_audio = g_idm - > lock < named_thread < cell_audio_thread > > ( 0 ) ;
2016-03-21 20:43:03 +01:00
if ( ! g_audio )
{
return CELL_AUDIO_ERROR_NOT_INIT ;
}
if ( portNum > = AUDIO_PORT_COUNT | | ! src | | ! src . aligned ( ) )
{
return CELL_AUDIO_ERROR_PARAM ;
}
const audio_port & port = g_audio - > ports [ portNum ] ;
2018-12-20 23:35:49 +01:00
const auto dst = port . get_vm_ptr ( ) ;
2016-03-21 20:43:03 +01:00
2018-10-02 10:40:37 +02:00
g_audio . unlock ( ) ;
2018-12-20 23:35:49 +01:00
if ( port . num_channels = = 6 )
2016-03-21 20:43:03 +01:00
{
2017-04-26 22:05:49 +02:00
for ( u32 i = 0 ; i < 256 ; i + + )
{
dst [ i * 6 + 0 ] + = src [ i * 6 + 0 ] * volume ; // mix L ch
dst [ i * 6 + 1 ] + = src [ i * 6 + 1 ] * volume ; // mix R ch
dst [ i * 6 + 2 ] + = src [ i * 6 + 2 ] * volume ; // mix center
dst [ i * 6 + 3 ] + = src [ i * 6 + 3 ] * volume ; // mix LFE
dst [ i * 6 + 4 ] + = src [ i * 6 + 4 ] * volume ; // mix rear L
dst [ i * 6 + 5 ] + = src [ i * 6 + 5 ] * volume ; // mix rear R
}
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 < 256 ; i + + )
{
dst [ i * 8 + 0 ] + = src [ i * 6 + 0 ] * volume ; // mix L ch
dst [ i * 8 + 1 ] + = src [ i * 6 + 1 ] * volume ; // mix R ch
dst [ i * 8 + 2 ] + = src [ i * 6 + 2 ] * volume ; // mix center
dst [ i * 8 + 3 ] + = src [ i * 6 + 3 ] * volume ; // mix LFE
dst [ i * 8 + 4 ] + = src [ i * 6 + 4 ] * volume ; // mix rear L
dst [ i * 8 + 5 ] + = src [ i * 6 + 5 ] * volume ; // mix rear R
//dst[i * 8 + 6] += 0.0f; // side L
//dst[i * 8 + 7] += 0.0f; // side R
}
}
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 ) ;
return CELL_OK ;
}
2017-09-20 12:01:57 +02:00
error_code cellAudioSendAck ( u64 data3 )
2016-03-21 20:43:03 +01:00
{
cellAudio . todo ( " cellAudioSendAck(data3=0x%llx) " , data3 ) ;
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 ) ;
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 ) ;
return CELL_OK ;
}
DECLARE ( ppu_module_manager : : cellAudio ) ( " cellAudio " , [ ] ( )
{
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 ) ;
} ) ;