2012-11-01 16:19:01 +01:00
// Copyright (c) 2012- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
2012-11-04 23:01:49 +01:00
// the Free Software Foundation, version 2.0 or later versions.
2012-11-01 16:19:01 +01:00
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
2013-06-04 23:54:37 +02:00
# include "base/mutex.h"
2013-12-30 10:17:11 +01:00
# include "Globals.h" // only for clamp_s16
# include "Common/CommonTypes.h"
# include "Common/ChunkFile.h"
# include "Common/FixedSizeQueue.h"
2013-10-15 11:36:37 +05:30
# include "Common/Atomics.h"
2013-12-30 10:17:11 +01:00
2014-12-07 22:00:30 +01:00
# ifdef _M_SSE
# include <emmintrin.h>
# endif
2013-12-30 10:17:11 +01:00
# include "Core/CoreTiming.h"
# include "Core/MemMap.h"
# include "Core/Host.h"
# include "Core/Config.h"
# include "Core/HLE/__sceAudio.h"
# include "Core/HLE/sceAudio.h"
# include "Core/HLE/sceKernel.h"
# include "Core/HLE/sceKernelThread.h"
2012-11-01 16:19:01 +01:00
2014-12-07 22:00:30 +01:00
2013-05-19 10:58:06 -07:00
// Should be used to lock anything related to the outAudioQueue.
2013-12-30 10:17:11 +01:00
// atomic locks are used on the lock. TODO: make this lock-free
2013-10-15 11:36:37 +05:30
atomic_flag atomicLock_ ;
recursive_mutex mutex_ ;
2012-11-01 16:19:01 +01:00
2014-04-27 22:05:53 +08:00
enum latency {
LOW_LATENCY = 0 ,
MEDIUM_LATENCY = 1 ,
HIGH_LATENCY = 2 ,
} ;
2012-11-01 16:19:01 +01:00
int eventAudioUpdate = - 1 ;
2013-10-15 11:36:37 +05:30
int eventHostAudioUpdate = - 1 ;
2012-12-17 19:38:04 +01:00
int mixFrequency = 44100 ;
2012-12-18 11:40:14 +01:00
2013-07-06 23:23:17 +02:00
const int hwSampleRate = 44100 ;
2013-07-07 00:06:30 +02:00
2013-08-24 02:16:10 +02:00
int hwBlockSize = 64 ;
int hostAttemptBlockSize = 512 ;
2014-07-01 23:08:38 -07:00
static int audioIntervalCycles ;
static int audioHostIntervalCycles ;
2013-07-07 00:06:30 +02:00
2013-08-24 02:16:10 +02:00
static s32 * mixBuffer ;
2012-11-17 14:20:04 +01:00
2013-05-19 12:31:47 -07:00
// High and low watermarks, basically. For perfect emulation, the correct values are 0 and 1, respectively.
2013-08-24 11:51:00 +02:00
// TODO: Tweak. Hm, there aren't actually even used currently...
static int chanQueueMaxSizeFactor ;
static int chanQueueMinSizeFactor ;
2012-11-17 14:20:04 +01:00
2013-06-19 12:00:24 +02:00
// TODO: Need to replace this with something lockless. Mutexes in the audio pipeline
// is bad mojo.
2013-08-24 02:16:10 +02:00
FixedSizeQueue < s16 , 512 * 16 > outAudioQueue ;
2012-11-01 16:19:01 +01:00
2013-10-15 11:36:37 +05:30
bool __gainAudioQueueLock ( ) ;
void __releaseAcquiredLock ( ) ;
void __blockForAudioQueueLock ( ) ;
2013-06-05 16:16:53 +08:00
static inline s16 adjustvolume ( s16 sample , int vol ) {
2013-10-29 00:16:37 +10:00
# ifdef ARM
register int r ;
asm volatile ( " smulwb %0, %1, %2 \n \t " \
" ssat %0, #16, %0 " \
: " =r " ( r ) : " r " ( vol ) , " r " ( sample ) ) ;
return r ;
# else
return clamp_s16 ( ( sample * vol ) > > 16 ) ;
# endif
2013-06-05 16:16:53 +08:00
}
2014-12-07 21:14:09 +01:00
inline void AdjustVolumeBlock ( s16 * out , s16 * in , int size , int leftVol , int rightVol ) {
# ifdef _M_SSE
__m128i volume = _mm_set_epi16 ( leftVol , rightVol , leftVol , rightVol , leftVol , rightVol , leftVol , rightVol ) ;
while ( size > = 16 ) {
__m128i indata1 = _mm_loadu_si128 ( ( __m128i * ) in ) ;
__m128i indata2 = _mm_loadu_si128 ( ( __m128i * ) ( in + 8 ) ) ;
_mm_storeu_si128 ( ( __m128i * ) out , _mm_mulhi_epi16 ( indata1 , volume ) ) ;
_mm_storeu_si128 ( ( __m128i * ) ( out + 8 ) , _mm_mulhi_epi16 ( indata2 , volume ) ) ;
in + = 16 ;
out + = 16 ;
size - = 16 ;
}
# endif
for ( int i = 0 ; i < size ; i + = 2 ) {
out [ i ] = adjustvolume ( in [ i ] , leftVol ) ;
out [ i + 1 ] = adjustvolume ( in [ i + 1 ] , rightVol ) ;
}
}
2013-08-24 02:16:10 +02:00
void hleAudioUpdate ( u64 userdata , int cyclesLate ) {
2014-07-01 00:25:20 -07:00
// Schedule the next cycle first. __AudioUpdate() may consume cycles.
2014-07-01 23:08:38 -07:00
CoreTiming : : ScheduleEvent ( audioIntervalCycles - cyclesLate , eventAudioUpdate , 0 ) ;
2014-07-01 00:25:20 -07:00
__AudioUpdate ( ) ;
2012-11-17 14:20:04 +01:00
}
2013-08-24 02:16:10 +02:00
void hleHostAudioUpdate ( u64 userdata , int cyclesLate ) {
2014-07-01 23:08:38 -07:00
CoreTiming : : ScheduleEvent ( audioHostIntervalCycles - cyclesLate , eventHostAudioUpdate , 0 ) ;
2014-07-01 00:25:20 -07:00
2013-06-19 12:00:24 +02:00
// Not all hosts need this call to poke their audio system once in a while, but those that don't
// can just ignore it.
2012-11-17 14:20:04 +01:00
host - > UpdateSound ( ) ;
2012-11-01 16:19:01 +01:00
}
2014-07-01 23:08:38 -07:00
void __AudioCPUMHzChange ( ) {
audioIntervalCycles = ( int ) ( usToCycles ( 1000000ULL ) * hwBlockSize / hwSampleRate ) ;
audioHostIntervalCycles = ( int ) ( usToCycles ( 1000000ULL ) * hostAttemptBlockSize / hwSampleRate ) ;
}
2013-08-24 02:16:10 +02:00
void __AudioInit ( ) {
2012-12-17 19:38:04 +01:00
mixFrequency = 44100 ;
2012-11-17 14:20:04 +01:00
2014-08-03 21:00:51 -07:00
switch ( g_Config . iAudioLatency ) {
2014-04-27 22:05:53 +08:00
case LOW_LATENCY :
2013-08-24 11:51:00 +02:00
chanQueueMaxSizeFactor = 1 ;
2013-08-25 13:03:57 +02:00
chanQueueMinSizeFactor = 1 ;
2013-08-24 02:16:10 +02:00
hwBlockSize = 16 ;
hostAttemptBlockSize = 256 ;
2014-04-27 22:05:53 +08:00
break ;
case MEDIUM_LATENCY :
2013-08-24 11:51:00 +02:00
chanQueueMaxSizeFactor = 2 ;
chanQueueMinSizeFactor = 1 ;
2013-08-24 02:16:10 +02:00
hwBlockSize = 64 ;
hostAttemptBlockSize = 512 ;
2014-04-27 22:05:53 +08:00
break ;
case HIGH_LATENCY :
chanQueueMaxSizeFactor = 4 ;
chanQueueMinSizeFactor = 2 ;
hwBlockSize = 64 ;
hostAttemptBlockSize = 512 ;
break ;
2013-08-24 02:16:10 +02:00
}
2014-07-01 23:08:38 -07:00
__AudioCPUMHzChange ( ) ;
2013-08-24 02:16:10 +02:00
2012-11-01 16:19:01 +01:00
eventAudioUpdate = CoreTiming : : RegisterEvent ( " AudioUpdate " , & hleAudioUpdate ) ;
2012-11-17 14:20:04 +01:00
eventHostAudioUpdate = CoreTiming : : RegisterEvent ( " AudioUpdateHost " , & hleHostAudioUpdate ) ;
2012-11-01 16:19:01 +01:00
2014-07-01 23:08:38 -07:00
CoreTiming : : ScheduleEvent ( audioIntervalCycles , eventAudioUpdate , 0 ) ;
CoreTiming : : ScheduleEvent ( audioHostIntervalCycles , eventHostAudioUpdate , 0 ) ;
2013-05-31 22:40:50 -07:00
for ( u32 i = 0 ; i < PSP_AUDIO_CHANNEL_MAX + 1 ; i + + )
2012-11-01 16:19:01 +01:00
chans [ i ] . clear ( ) ;
2013-08-24 02:16:10 +02:00
mixBuffer = new s32 [ hwBlockSize * 2 ] ;
memset ( mixBuffer , 0 , hwBlockSize * 2 * sizeof ( s32 ) ) ;
2013-10-15 11:36:37 +05:30
2013-10-19 15:11:06 -07:00
__blockForAudioQueueLock ( ) ;
outAudioQueue . clear ( ) ;
__releaseAcquiredLock ( ) ;
2014-07-01 23:08:38 -07:00
CoreTiming : : RegisterMHzChangeCallback ( & __AudioCPUMHzChange ) ;
2012-11-01 16:19:01 +01:00
}
2014-07-02 08:11:26 -07:00
2013-08-24 11:51:00 +02:00
void __AudioDoState ( PointerWrap & p ) {
2013-09-14 20:23:03 -07:00
auto s = p . Section ( " sceAudio " , 1 ) ;
if ( ! s )
return ;
2012-12-28 00:05:54 -08:00
p . Do ( eventAudioUpdate ) ;
CoreTiming : : RestoreRegisterEvent ( eventAudioUpdate , " AudioUpdate " , & hleAudioUpdate ) ;
p . Do ( eventHostAudioUpdate ) ;
2013-01-30 22:48:35 -08:00
CoreTiming : : RestoreRegisterEvent ( eventHostAudioUpdate , " AudioUpdateHost " , & hleHostAudioUpdate ) ;
2012-12-28 00:05:54 -08:00
p . Do ( mixFrequency ) ;
2013-05-19 10:58:06 -07:00
2013-10-15 11:36:37 +05:30
{
//block until a lock is achieved. Not a good idea at all, but
//can't think of a better one...
__blockForAudioQueueLock ( ) ;
2013-06-04 23:54:37 +02:00
outAudioQueue . DoState ( p ) ;
2013-10-15 11:36:37 +05:30
//release the atomic lock
__releaseAcquiredLock ( ) ;
2013-06-04 23:54:37 +02:00
}
2012-12-28 00:05:54 -08:00
int chanCount = ARRAY_SIZE ( chans ) ;
p . Do ( chanCount ) ;
if ( chanCount ! = ARRAY_SIZE ( chans ) )
{
2013-09-07 21:19:21 +02:00
ERROR_LOG ( SCEAUDIO , " Savestate failure: different number of audio channels. " ) ;
2012-12-28 00:05:54 -08:00
return ;
}
for ( int i = 0 ; i < chanCount ; + + i )
chans [ i ] . DoState ( p ) ;
2014-07-02 08:11:26 -07:00
__AudioCPUMHzChange ( ) ;
2012-12-28 00:05:54 -08:00
}
2013-08-24 02:16:10 +02:00
void __AudioShutdown ( ) {
delete [ ] mixBuffer ;
2013-10-15 11:36:37 +05:30
2013-08-24 02:16:10 +02:00
mixBuffer = 0 ;
2013-05-31 22:40:50 -07:00
for ( u32 i = 0 ; i < PSP_AUDIO_CHANNEL_MAX + 1 ; i + + )
2012-11-17 14:20:04 +01:00
chans [ i ] . clear ( ) ;
2012-11-01 16:19:01 +01:00
}
2013-08-24 02:16:10 +02:00
u32 __AudioEnqueue ( AudioChannel & chan , int chanNum , bool blocking ) {
2013-05-19 12:31:47 -07:00
u32 ret = chan . sampleCount ;
if ( chan . sampleAddress = = 0 ) {
// For some reason, multichannel audio lies and returns the sample count here.
if ( chanNum = = PSP_AUDIO_CHANNEL_SRC | | chanNum = = PSP_AUDIO_CHANNEL_OUTPUT2 ) {
ret = 0 ;
}
}
// If there's anything on the queue at all, it should be busy, but we try to be a bit lax.
2013-06-06 06:09:42 +08:00
//if (chan.sampleQueue.size() > chan.sampleCount * 2 * chanQueueMaxSizeFactor || chan.sampleAddress == 0) {
2013-09-05 00:57:53 -07:00
if ( chan . sampleQueue . size ( ) > 0 ) {
2012-11-01 16:19:01 +01:00
if ( blocking ) {
2013-05-19 12:31:47 -07:00
// TODO: Regular multichannel audio seems to block for 64 samples less? Or enqueue the first 64 sync?
2013-05-31 22:15:38 -07:00
int blockSamples = ( int ) chan . sampleQueue . size ( ) / 2 / chanQueueMinSizeFactor ;
2013-05-19 12:31:47 -07:00
2013-08-28 08:13:44 -07:00
if ( __KernelIsDispatchEnabled ( ) ) {
AudioChannelWaitInfo waitInfo = { __KernelGetCurThread ( ) , blockSamples } ;
chan . waitingThreads . push_back ( waitInfo ) ;
// Also remember the value to return in the waitValue.
__KernelWaitCurThread ( WAITTYPE_AUDIOCHANNEL , ( SceUID ) chanNum + 1 , ret , 0 , false , " blocking audio " ) ;
} else {
// TODO: Maybe we shouldn't take this audio after all?
ret = SCE_KERNEL_ERROR_CAN_NOT_WAIT ;
}
2013-05-19 12:31:47 -07:00
2012-12-17 20:15:23 +01:00
// Fall through to the sample queueing, don't want to lose the samples even though
2013-05-19 12:31:47 -07:00
// we're getting full. The PSP would enqueue after blocking.
} else {
// Non-blocking doesn't even enqueue, but it's not commonly used.
2012-11-01 16:19:01 +01:00
return SCE_ERROR_AUDIO_CHANNEL_BUSY ;
}
}
2013-05-19 12:31:47 -07:00
if ( chan . sampleAddress = = 0 ) {
return ret ;
}
2013-10-29 00:16:37 +10:00
int leftVol = chan . leftVolume ;
int rightVol = chan . rightVolume ;
2013-11-07 11:49:40 +01:00
if ( leftVol = = ( 1 < < 15 ) & & rightVol = = ( 1 < < 15 ) & & chan . format = = PSP_AUDIO_FORMAT_STEREO & & IS_LITTLE_ENDIAN ) {
// TODO: Add mono->stereo conversion to this path.
2013-10-29 08:24:55 -07:00
// Good news: the volume doesn't affect the values at all.
// We can just do a direct memory copy.
const u32 totalSamples = chan . sampleCount * ( chan . format = = PSP_AUDIO_FORMAT_STEREO ? 2 : 1 ) ;
s16 * buf1 = 0 , * buf2 = 0 ;
size_t sz1 , sz2 ;
chan . sampleQueue . pushPointers ( totalSamples , & buf1 , & sz1 , & buf2 , & sz2 ) ;
if ( Memory : : IsValidAddress ( chan . sampleAddress + ( totalSamples - 1 ) * sizeof ( s16_le ) ) ) {
Memory : : Memcpy ( buf1 , chan . sampleAddress , ( u32 ) sz1 * sizeof ( s16 ) ) ;
if ( buf2 )
Memory : : Memcpy ( buf2 , chan . sampleAddress + ( u32 ) sz1 * sizeof ( s16 ) , ( u32 ) sz2 * sizeof ( s16 ) ) ;
}
} else {
// Remember that maximum volume allowed is 0xFFFFF so left shift is no issue.
// This way we can optimally shift by 16.
leftVol < < = 1 ;
rightVol < < = 1 ;
if ( chan . format = = PSP_AUDIO_FORMAT_STEREO ) {
const u32 totalSamples = chan . sampleCount * 2 ;
2013-01-27 13:40:57 -08:00
2013-10-29 08:24:55 -07:00
s16_le * sampleData = ( s16_le * ) Memory : : GetPointer ( chan . sampleAddress ) ;
2013-01-27 13:40:57 -08:00
// Walking a pointer for speed. But let's make sure we wouldn't trip on an invalid ptr.
2013-10-29 08:24:55 -07:00
if ( Memory : : IsValidAddress ( chan . sampleAddress + ( totalSamples - 1 ) * sizeof ( s16_le ) ) ) {
2013-06-19 12:00:24 +02:00
s16 * buf1 = 0 , * buf2 = 0 ;
size_t sz1 , sz2 ;
chan . sampleQueue . pushPointers ( totalSamples , & buf1 , & sz1 , & buf2 , & sz2 ) ;
2014-12-07 21:14:09 +01:00
AdjustVolumeBlock ( buf1 , sampleData , sz1 , leftVol , rightVol ) ;
2013-06-19 12:00:24 +02:00
if ( buf2 ) {
2014-12-07 21:14:09 +01:00
AdjustVolumeBlock ( buf2 , sampleData + sz1 , sz2 , leftVol , rightVol ) ;
2013-06-19 12:00:24 +02:00
}
2013-01-27 13:40:57 -08:00
}
2013-10-29 08:24:55 -07:00
} else if ( chan . format = = PSP_AUDIO_FORMAT_MONO ) {
2014-12-07 21:14:09 +01:00
// Rare, so unoptimized. Expands to stereo.
2013-10-29 08:24:55 -07:00
for ( u32 i = 0 ; i < chan . sampleCount ; i + + ) {
s16 sample = ( s16 ) Memory : : Read_U16 ( chan . sampleAddress + 2 * i ) ;
chan . sampleQueue . push ( adjustvolume ( sample , leftVol ) ) ;
chan . sampleQueue . push ( adjustvolume ( sample , rightVol ) ) ;
2013-06-05 16:16:53 +08:00
}
2012-11-01 16:19:01 +01:00
}
}
2013-02-01 08:07:58 -08:00
return ret ;
2012-11-01 16:19:01 +01:00
}
2013-08-24 11:51:00 +02:00
inline void __AudioWakeThreads ( AudioChannel & chan , int result , int step ) {
2013-05-19 12:31:47 -07:00
u32 error ;
2014-07-01 08:02:34 -07:00
bool wokeThreads = false ;
2013-08-24 11:51:00 +02:00
for ( size_t w = 0 ; w < chan . waitingThreads . size ( ) ; + + w ) {
2013-05-19 12:31:47 -07:00
AudioChannelWaitInfo & waitInfo = chan . waitingThreads [ w ] ;
2013-06-05 12:13:52 -07:00
waitInfo . numSamples - = step ;
2013-05-19 12:31:47 -07:00
// If it's done (there will still be samples on queue) and actually still waiting, wake it up.
2013-06-08 23:42:59 -07:00
u32 waitID = __KernelGetWaitID ( waitInfo . threadID , WAITTYPE_AUDIOCHANNEL , error ) ;
2013-08-24 11:51:00 +02:00
if ( waitInfo . numSamples < = 0 & & waitID ! = 0 ) {
2013-09-07 21:19:21 +02:00
// DEBUG_LOG(SCEAUDIO, "Woke thread %i for some buffer filling", waitingThread);
2013-06-05 12:20:07 -07:00
u32 ret = result = = 0 ? __KernelGetWaitValue ( waitInfo . threadID , error ) : SCE_ERROR_AUDIO_CHANNEL_NOT_RESERVED ;
2013-05-19 12:31:47 -07:00
__KernelResumeThreadFromWait ( waitInfo . threadID , ret ) ;
2014-07-01 08:02:34 -07:00
wokeThreads = true ;
2013-05-19 12:31:47 -07:00
chan . waitingThreads . erase ( chan . waitingThreads . begin ( ) + w - - ) ;
}
2013-06-08 23:42:59 -07:00
// This means the thread stopped waiting, so stop trying to wake it.
else if ( waitID = = 0 )
chan . waitingThreads . erase ( chan . waitingThreads . begin ( ) + w - - ) ;
2013-05-19 12:31:47 -07:00
}
2014-07-01 08:02:34 -07:00
if ( wokeThreads ) {
__KernelReSchedule ( " audio drain " ) ;
}
2013-05-19 12:31:47 -07:00
}
2013-08-24 11:51:00 +02:00
void __AudioWakeThreads ( AudioChannel & chan , int result ) {
2013-06-05 12:20:07 -07:00
__AudioWakeThreads ( chan , result , 0x7FFFFFFF ) ;
2013-05-19 12:31:47 -07:00
}
2013-08-24 11:51:00 +02:00
void __AudioSetOutputFrequency ( int freq ) {
2013-09-07 21:19:21 +02:00
WARN_LOG ( SCEAUDIO , " Switching audio frequency to %i " , freq ) ;
2013-06-19 12:00:24 +02:00
mixFrequency = freq ;
}
2014-12-07 20:23:47 +01:00
inline void ClampBufferToS16 ( s16 * out , s32 * in , int size ) {
# ifdef _M_SSE
// Size will always be 16-byte aligned as the hwBlockSize is.
while ( size > = 8 ) {
__m128i in1 = _mm_loadu_si128 ( ( __m128i * ) in ) ;
__m128i in2 = _mm_loadu_si128 ( ( __m128i * ) ( in + 4 ) ) ;
__m128i packed = _mm_packs_epi32 ( in1 , in2 ) ;
_mm_storeu_si128 ( ( __m128i * ) out , packed ) ;
out + = 8 ;
in + = 8 ;
size - = 8 ;
}
for ( int i = 0 ; i < size ; i + + ) {
out [ i ] = clamp_s16 ( in [ i ] ) ;
}
# else
for ( int i = 0 ; i < size ; i + + ) {
out [ i ] = clamp_s16 ( in [ i ] ) ;
}
# endif
}
2012-11-17 14:20:04 +01:00
// Mix samples from the various audio channels into a single sample queue.
// This single sample queue is where __AudioMix should read from. If the sample queue is full, we should
// just sleep the main emulator thread a little.
2013-06-19 12:00:24 +02:00
void __AudioUpdate ( ) {
2012-11-23 10:33:19 +01:00
// Audio throttle doesn't really work on the PSP since the mixing intervals are so closely tied
// to the CPU. Much better to throttle the frame rate on frame display and just throw away audio
// if the buffer somehow gets full.
2013-06-19 12:00:24 +02:00
bool firstChannel = true ;
2012-11-01 16:19:01 +01:00
2013-06-19 12:00:24 +02:00
for ( u32 i = 0 ; i < PSP_AUDIO_CHANNEL_MAX + 1 ; i + + ) {
2012-11-17 14:20:04 +01:00
if ( ! chans [ i ] . reserved )
continue ;
2013-06-19 12:00:24 +02:00
2013-06-05 12:20:07 -07:00
__AudioWakeThreads ( chans [ i ] , 0 , hwBlockSize ) ;
2013-05-19 12:31:47 -07:00
2012-11-17 14:20:04 +01:00
if ( ! chans [ i ] . sampleQueue . size ( ) ) {
continue ;
}
2013-10-05 10:31:57 -07:00
if ( hwBlockSize * 2 > ( int ) chans [ i ] . sampleQueue . size ( ) ) {
2013-09-07 21:19:21 +02:00
ERROR_LOG ( SCEAUDIO , " Channel %i buffer underrun at %i of %i " , i , ( int ) chans [ i ] . sampleQueue . size ( ) / 2 , hwBlockSize ) ;
2013-06-19 12:00:24 +02:00
}
const s16 * buf1 = 0 , * buf2 = 0 ;
size_t sz1 , sz2 ;
chans [ i ] . sampleQueue . popPointers ( hwBlockSize * 2 , & buf1 , & sz1 , & buf2 , & sz2 ) ;
if ( firstChannel ) {
2013-06-25 09:54:42 -04:00
for ( size_t s = 0 ; s < sz1 ; s + + )
2013-06-19 12:00:24 +02:00
mixBuffer [ s ] = buf1 [ s ] ;
if ( buf2 ) {
2013-06-25 09:54:42 -04:00
for ( size_t s = 0 ; s < sz2 ; s + + )
2013-06-19 12:00:24 +02:00
mixBuffer [ s + sz1 ] = buf2 [ s ] ;
}
firstChannel = false ;
} else {
2014-12-07 20:23:47 +01:00
// Surprisingly hard to SIMD efficiently on SSE2 due to lack of 16-to-32-bit sign extension. NEON should be straight-forward though, and SSE4.1 can do it nicely.
2013-06-25 09:54:42 -04:00
for ( size_t s = 0 ; s < sz1 ; s + + )
2013-06-19 12:00:24 +02:00
mixBuffer [ s ] + = buf1 [ s ] ;
if ( buf2 ) {
2013-06-25 09:54:42 -04:00
for ( size_t s = 0 ; s < sz2 ; s + + )
2013-06-19 12:00:24 +02:00
mixBuffer [ s + sz1 ] + = buf2 [ s ] ;
2012-11-17 14:20:04 +01:00
}
}
}
2013-06-19 12:00:24 +02:00
if ( firstChannel ) {
2014-12-07 20:23:47 +01:00
// Nothing was written above, let's memset.
2013-08-26 21:06:26 -07:00
memset ( mixBuffer , 0 , hwBlockSize * 2 * sizeof ( s32 ) ) ;
2013-06-19 12:00:24 +02:00
}
2012-12-18 11:40:14 +01:00
if ( g_Config . bEnableSound ) {
2013-10-15 11:36:37 +05:30
__blockForAudioQueueLock ( ) ;
/*
if ( ! __gainAudioQueueLock ( ) ) {
return ;
}
*/
2012-12-18 11:40:14 +01:00
if ( outAudioQueue . room ( ) > = hwBlockSize * 2 ) {
2013-06-19 13:13:02 +02:00
s16 * buf1 = 0 , * buf2 = 0 ;
size_t sz1 , sz2 ;
outAudioQueue . pushPointers ( hwBlockSize * 2 , & buf1 , & sz1 , & buf2 , & sz2 ) ;
2014-12-07 20:23:47 +01:00
ClampBufferToS16 ( buf1 , mixBuffer , sz1 ) ;
2013-06-19 13:13:02 +02:00
if ( buf2 ) {
2014-12-07 20:23:47 +01:00
ClampBufferToS16 ( buf2 , mixBuffer + sz1 , sz2 ) ;
2012-12-18 11:40:14 +01:00
}
2013-01-11 00:59:17 +01:00
} else {
// This happens quite a lot. There's still something slightly off
// about the amount of audio we produce.
2012-11-17 14:20:04 +01:00
}
2013-10-15 11:36:37 +05:30
//release the atomic lock
__releaseAcquiredLock ( ) ;
2012-11-17 14:20:04 +01:00
}
}
// numFrames is number of stereo frames.
2013-05-19 10:58:06 -07:00
// This is called from *outside* the emulator thread.
2012-11-17 14:20:04 +01:00
int __AudioMix ( short * outstereo , int numFrames )
{
// TODO: if mixFrequency != the actual output frequency, resample!
int underrun = - 1 ;
s16 sampleL = 0 ;
s16 sampleR = 0 ;
2013-06-19 13:13:02 +02:00
const s16 * buf1 = 0 , * buf2 = 0 ;
size_t sz1 , sz2 ;
{
2013-10-15 11:36:37 +05:30
//TODO: do rigorous testing to see whether just blind locking will improve speed.
if ( ! __gainAudioQueueLock ( ) ) {
memset ( outstereo , 0 , numFrames * 2 * sizeof ( short ) ) ;
return 0 ;
}
2013-06-19 13:13:02 +02:00
outAudioQueue . popPointers ( numFrames * 2 , & buf1 , & sz1 , & buf2 , & sz2 ) ;
2013-10-15 11:36:37 +05:30
2013-06-19 13:13:02 +02:00
memcpy ( outstereo , buf1 , sz1 * sizeof ( s16 ) ) ;
if ( buf2 ) {
memcpy ( outstereo + sz1 , buf2 , sz2 * sizeof ( s16 ) ) ;
2012-11-01 16:19:01 +01:00
}
2013-10-15 11:36:37 +05:30
//release the atomic lock
__releaseAcquiredLock ( ) ;
2012-11-01 16:19:01 +01:00
}
2013-06-19 13:13:02 +02:00
int remains = ( int ) ( numFrames * 2 - sz1 - sz2 ) ;
if ( remains > 0 )
2013-11-04 14:03:36 +01:00
memset ( outstereo + numFrames * 2 - remains , 0 , remains * sizeof ( s16 ) ) ;
2013-06-19 13:13:02 +02:00
2013-07-06 11:09:08 +02:00
if ( sz1 + sz2 < ( size_t ) numFrames ) {
2013-06-19 13:13:02 +02:00
underrun = ( int ) ( sz1 + sz2 ) / 2 ;
2013-09-07 21:19:21 +02:00
VERBOSE_LOG ( SCEAUDIO , " Audio out buffer UNDERRUN at %i of %i " , underrun , numFrames ) ;
2012-11-17 14:20:04 +01:00
}
2013-03-17 11:29:22 -07:00
return underrun > = 0 ? underrun : numFrames ;
2012-11-01 16:19:01 +01:00
}
2013-10-15 11:36:37 +05:30
/*returns whether the lock was successfully gained or not.
i . e - whether the lock belongs to you
*/
inline bool __gainAudioQueueLock ( ) {
if ( g_Config . bAtomicAudioLocks ) {
/*if the previous state was 0, that means the lock was "unlocked". So,
we return ! 0 , which is true thanks to C ' s int to bool conversion
One the other hand , if it was locked , then the lock would return 1.
so , ! 1 = 0 = false .
*/
return atomicLock_ . test_and_set ( ) = = 0 ;
} else {
mutex_ . lock ( ) ;
return true ;
}
} ;
inline void __releaseAcquiredLock ( ) {
if ( g_Config . bAtomicAudioLocks ) {
atomicLock_ . clear ( ) ;
} else {
mutex_ . unlock ( ) ;
}
}
inline void __blockForAudioQueueLock ( ) {
if ( g_Config . bAtomicAudioLocks ) {
while ( ( atomicLock_ . test_and_set ( ) = = 0 ) ) { }
} else {
mutex_ . lock ( ) ;
}
}