/*---------------------------------------------------------------------------* Project: NintendoWare File: snd_StreamSoundPlayer.cpp Copyright (C)2009-2011 Nintendo/HAL Laboratory, Inc. All rights reserved. These coded instructions, statements, and computer programs contain proprietary information of Nintendo and/or its licensed developers and are protected by national and international copyright laws. They may not be disclosed to third parties or copied or duplicated in any form, in whole or in part, without the prior written consent of Nintendo. The content herein is highly confidential and should be handled accordingly. $Revision: 31311 $ *---------------------------------------------------------------------------*/ #include "precompiled.h" #include #include #include #include // #define DEBUG_STRM namespace { #ifdef DEBUG_STRM FILE* s_pFile; const char* DUMP_FILE = "_StreamSoundPlayer.dump"; #endif } // anonymous namespace namespace nw { namespace snd { namespace internal { namespace driver { NN_ATTRIBUTE_ALIGN(32) u8 StreamSoundPlayer::s_LoadBuffer[ StreamSoundPlayer::LOAD_BUFFER_SIZE ]; StreamSoundPlayer::StreamSoundPlayer() { u32 taskCount = m_StreamDataLoadTaskPool.Create( m_StreamDataLoadTaskArea, sizeof(m_StreamDataLoadTaskArea) ); NW_ASSERT( taskCount == BUFFER_BLOCK_COUNT_MAX ); } StreamSoundPlayer::~StreamSoundPlayer() { Finalize(); NW_ASSERT( m_StreamDataLoadTaskPool.Count() == BUFFER_BLOCK_COUNT_MAX ); m_StreamDataLoadTaskPool.Destroy( m_StreamDataLoadTaskArea, sizeof(m_StreamDataLoadTaskArea) ); } void StreamSoundPlayer::Initialize() { BasicSoundPlayer::Initialize(); m_IsInitialized = false; m_DataOffsetFromFileHead = 0; m_pFileStream = NULL; m_IsPrepared = false; m_LoadFinishFlag = false; m_PauseStatus = false; m_LoadWaitFlag = false; m_IsNoRealtimeLoad = false; m_PlayFinishFlag = false; m_SkipUpdateAdpcmLoop = false; m_ValidAdpcmLoop = false; m_LoopCounter = 0; m_LoadWaitCount = 0; // トラックの初期化 for ( int trackIndex = 0; trackIndex < STRM_TRACK_NUM; trackIndex++ ) { StreamTrack& track = m_Tracks[ trackIndex ]; track.m_ActiveFlag = false; track.m_Volume = 1.0f; track.m_Pan = 0.0f; track.m_SurroundPan = 0.0f; #ifdef NW_PLATFORM_CTRWIN track.m_pVoice = NULL; #endif } // チャンネルの初期化 for ( int channelIndex = 0; channelIndex < STRM_CHANNEL_NUM; channelIndex++ ) { StreamChannel& channel = m_Channels[ channelIndex ]; channel.m_pBufferAddress = NULL; #ifdef NW_PLATFORM_CTR channel.m_pVoice = NULL; #endif } } /*---------------------------------------------------------------------------* Name: Setup Description: ストリームプレイヤーのセットアップ Arguments: pBufferPool - ストリームバッファのメモリプール Returns: セットアップに成功したかどうか *---------------------------------------------------------------------------*/ StreamSoundPlayer::SetupResult StreamSoundPlayer::Setup( StreamBufferPool* pBufferPool, u32 allocChannelCount, u16 allocTrackFlag ) { NW_NULL_ASSERT( pBufferPool ); m_ChannelCount = ut::Min( allocChannelCount, STRM_CHANNEL_NUM ); u32 bitMask = allocTrackFlag; u32 trackIndex = 0; while ( bitMask != 0 ) { if ( bitMask & 0x01 ) { if ( trackIndex >= STRM_TRACK_NUM ) { NW_WARNING( false, "Too large track index (%d). Max track index is %d.", trackIndex, STRM_TRACK_NUM-1 ); break; } m_Tracks[ trackIndex ].m_ActiveFlag = true; } bitMask >>= 1; trackIndex++; } m_TrackCount = ut::Min( trackIndex, STRM_TRACK_NUM ); if ( m_TrackCount == 0 ) { return SETUP_ERR_UNKNOWN; } m_pBufferPool = pBufferPool; // NN_LOG("m_pBufferPool %08x\n",m_pBufferPool); { // ストリームバッファ確保 if ( ! AllocStreamBuffers() ) { NW_WARNING(false, "Failed to start stream sound for not enough stream channel instance." ); return SETUP_ERR_CANNOT_ALLOCATE_BUFFER; } } #ifdef NW_PLATFORM_CTR for ( int ch = 0; ch < m_ChannelCount; ch++ ) { StreamChannel& channel = m_Channels[ ch ]; } #endif m_IsInitialized = true; #ifdef DEBUG_STRM s_pFile = std::fopen( DUMP_FILE, "wb" ); #endif return SETUP_SUCCESS; } /*---------------------------------------------------------------------------* Name: Finalize Description: ストリームプレイヤーのシャットダウン Arguments: None. Returns: None. *---------------------------------------------------------------------------*/ void StreamSoundPlayer::Finalize() { if ( ! m_IsInitialized ) { return; } // タスクのキャンセル TaskManager::GetInstance().CancelTaskById( reinterpret_cast(this) ); // タスクの完了待ち m_StreamHeaderLoadTask.Wait(); for ( StreamDataLoadTaskList::Iterator itr = m_StreamDataLoadTaskList.GetBeginIter(); itr != m_StreamDataLoadTaskList.GetEndIter(); ) { StreamDataLoadTaskList::Iterator curItr = itr++; StreamDataLoadTask* task = &*curItr; task->Wait(); m_StreamDataLoadTaskList.Erase( task ); m_StreamDataLoadTaskPool.Free( task ); // NN_LOG( "Task Cancel %08x\n", task ); } // ボイスとチャンネルの解放 FreeStreamBuffers(); FreeVoices(); // ファイルストリームを閉じる if ( m_pFileStream != NULL ) { m_pFileStream->Close(); m_pFileStream = NULL; } m_pBufferPool = NULL; #ifdef DEBUG_STRM std::fclose( s_pFile ); #endif m_ActiveFlag = false; m_IsInitialized = false; BasicSoundPlayer::Finalize(); } /*---------------------------------------------------------------------------* Name: Prepare Description: ストリーム再生の準備開始 Arguments: pFileStream - ストリームファイルのファイルストリーム startOffsetType - 開始オフセットタイプ startOffset - 開始オフセット Returns: 準備開始に成功したかどうか 注:準備に成功したかどうかは、返り値では判断できない *---------------------------------------------------------------------------*/ bool StreamSoundPlayer::Prepare( io::FileStream* pFileStream, StartOffsetType startOffsetType, int startOffset ) { NW_ASSERT( m_IsInitialized ); NW_NULL_ASSERT( pFileStream ); NW_ASSERT( pFileStream->CanRead() ); NW_ASSERT( pFileStream->CanSeek() ); m_pFileStream = pFileStream; m_StartOffsetType = startOffsetType; m_StartOffset = startOffset; m_IsTaskError = false; m_IsLoadingDelay = false; m_ActiveFlag = true; // ヘッダロードタスクの発行 m_StreamHeaderLoadTask.m_PlayerHandle = this; m_StreamHeaderLoadTask.m_pFileStream = m_pFileStream; m_StreamHeaderLoadTask.m_StartOffsetType = m_StartOffsetType; m_StreamHeaderLoadTask.m_StartOffset = m_StartOffset; m_StreamHeaderLoadTask.SetId(reinterpret_cast(this)); internal::TaskManager::GetInstance().AppendTask( &m_StreamHeaderLoadTask, internal::TaskManager::PRIORITY_MIDDLE ); return true; } /*---------------------------------------------------------------------------* Description: ストリームの再生開始 Returns: 再生に成功したかどうか *---------------------------------------------------------------------------*/ void StreamSoundPlayer::Start() { if ( ! m_IsPrepared ) { return; } if ( ! m_StartedFlag ) { s32 blockIndex = 0; u32 blockOffset = 0; s32 loopCount = 0; if ( ! CalcStartOffset( &blockIndex, &blockOffset, &loopCount ) ) { NW_ASSERT( false ); return; } m_LoopCounter += loopCount; const size_t sampleBufferLen = static_cast( m_DataBlockSize * m_PlayingBufferBlockCount ); const u32 sampleCount = Util::GetSampleByByte( sampleBufferLen, WaveFileReader::GetSampleFormat(m_StreamInfo.encodeMethod) ); for ( int trackIndex = 0; trackIndex < m_TrackCount; trackIndex++ ) { StreamTrack& track = m_Tracks[ trackIndex ]; if ( ! track.m_ActiveFlag ) continue; // ボイスセットアップ WaveInfo waveInfo; waveInfo.sampleFormat = WaveFileReader::GetSampleFormat( m_StreamInfo.encodeMethod ); waveInfo.channelCount = track.m_TrackInfo.channelCount; waveInfo.sampleRate = m_StreamInfo.sampleRate; #ifdef NW_PLATFORM_CTRWIN waveInfo.loopFlag = true; if ( m_IsNoRealtimeLoad ) { if ( m_StreamInfo.isLoop ) { waveInfo.loopStartFrame = m_StreamInfo.loopStart; } else { waveInfo.loopStartFrame = 0; } waveInfo.loopEndFrame = m_StreamInfo.frameCount; } else { // ふだんはコチラ waveInfo.loopStartFrame = 0; waveInfo.loopEndFrame = sampleCount; } for( int channelIndex = 0; channelIndex < track.m_TrackInfo.channelCount; channelIndex++ ) { StreamChannel* channel = GetTrackChannel( track, channelIndex ); if ( channel == NULL ) continue; WaveInfo::ChannelParam& channelParam = waveInfo.channelParam[ channelIndex ]; channelParam.dataAddress = channel->m_pBufferAddress; channelParam.adpcmParam = channel->m_AdpcmParam; channelParam.adpcmLoopParam = channel->m_AdpcmLoopParam; // NN_LOG("### LOOP CONTEXT PS(0x%04X) yn1(0x%04X) yn2(0x%04X)\n", // channelParam.m_AdpcmLoopParam.loopPredScale, // channelParam.m_AdpcmLoopParam.loopYn1, // channelParam.m_AdpcmLoopParam.loopYn2 ); // ADPCMのパラメータをブロックの先頭のものに書き換える // (ブロック内のオフセットスタートはVoiceクラスで処理する) channelParam.adpcmParam.predScale = *reinterpret_cast( channel->m_pBufferAddress ); } if ( track.m_pVoice != NULL ) { track.m_pVoice->Initialize( waveInfo, blockOffset ); track.m_pVoice->SetVolume( 1.0f ); // TODO: 1.0, 1.0 を入れないといけない // が、対応としてはよろしくない。 // (NW4C-0.1.0~) track.m_pVoice->Start(); } if ( m_IsNoRealtimeLoad && ! m_StreamInfo.isLoop ) { SetLoopEndToZeroBuffer( m_StreamInfo.blockCount - 1 ); } #else for ( int ch = 0; ch < track.m_TrackInfo.channelCount; ch++ ) { StreamChannel* channel = GetTrackChannel( track, ch ); if ( channel == NULL ) continue; if ( channel->m_pVoice != NULL ) { channel->m_pVoice->Initialize( waveInfo, blockOffset ); channel->m_pVoice->Start(); } } #endif } #ifdef NW_PLATFORM_CTRWIN // 途中再生のためのループエンド設定 if ( blockIndex == m_StreamInfo.blockCount - 2 ) { UpdateDataLoopAddress( 1 ); } else if ( blockIndex == m_StreamInfo.blockCount - 1 ) { UpdateDataLoopAddress( 0 ); } #endif UpdatePauseStatus(); // プレイヤー登録 SoundThread::GetInstance().RegisterPlayerCallback( this ); m_StartedFlag = true; } } bool StreamSoundPlayer::IsDspAdpcm() const { if ( WaveFileReader::GetSampleFormat( m_StreamInfo.encodeMethod ) == SAMPLE_FORMAT_DSP_ADPCM ) { return true; } return false; } /*---------------------------------------------------------------------------* Name: Stop Description: ストリームの再生停止 Arguments: None. Returns: None. *---------------------------------------------------------------------------*/ void StreamSoundPlayer::Stop() { // ボイスの停止 #ifdef NW_PLATFORM_CTRWIN for ( int trackIndex = 0; trackIndex < STRM_TRACK_NUM; trackIndex++ ) { if ( ! m_Tracks[ trackIndex ].m_ActiveFlag ) { continue; } Voice* voice = m_Tracks[ trackIndex ].m_pVoice; if ( voice != NULL ) { voice->Stop(); } } #else for ( int ch = 0; ch < m_ChannelCount; ch++ ) { Voice* voice = m_Channels[ch].m_pVoice; if ( voice != NULL ) { voice->Stop(); } } #endif // プレイヤー登録の解除 if ( m_StartedFlag ) { SoundThread::GetInstance().UnregisterPlayerCallback( this ); m_StartedFlag = false; } // 停止完了 m_IsPrepared = false; } /*---------------------------------------------------------------------------* Name: Pause Description: ストリームの一時停止/再開 Arguments: flag - trueで停止、falseで再開 Returns: None. *---------------------------------------------------------------------------*/ void StreamSoundPlayer::Pause( bool flag ) { m_PauseFlag = flag; if ( flag ) m_LoadWaitFlag = true; UpdatePauseStatus(); } /*---------------------------------------------------------------------------* Name: GetStreamDataInfo Description: ストリームデータの情報取得 Arguments: info - ストリームデータ情報構造体 Returns: 現在の再生しているストリームデータの情報を取得する *---------------------------------------------------------------------------*/ bool StreamSoundPlayer::ReadStreamDataInfo( StreamDataInfo* info ) const { SoundThreadLock lock; if ( ! m_IsPrepared ) { return false; } info->loopFlag = m_StreamInfo.isLoop != 0; info->sampleRate = m_StreamInfo.sampleRate; info->loopStart = m_StreamInfo.loopStart; info->loopEnd = m_StreamInfo.frameCount; return true; } /*---------------------------------------------------------------------------* Name: GetPlaySamplePosition Description: 現在の再生位置の取得 Arguments: なし Returns: 現在の再生位置をサンプル単位で返す 再生していない場合は、負の値を返す *---------------------------------------------------------------------------*/ long StreamSoundPlayer::GetPlaySamplePosition() const { SoundThreadLock lock; if ( ! m_ActiveFlag ) { return -1; } if ( ! m_Tracks[ 0 ].m_ActiveFlag ) { return -1; } if ( ! m_IsPrepared ) { return 0; } s32 bufOffset = 0; #ifdef NW_PLATFORM_CTRWIN if ( m_Tracks[ 0 ].m_pVoice != NULL ) { bufOffset = static_cast( m_Tracks[ 0 ].m_pVoice->GetCurrentPlayingSample() ); } s32 pos = ( m_PlayingDataBlockIndex - m_PlayingBufferBlockIndex ) * static_cast( m_StreamInfo.oneBlockSamples ); #else if ( m_Channels[ 0 ].m_pVoice != NULL ) { bufOffset = static_cast( m_Channels[ 0 ].m_pVoice->GetCurrentPlayingSample() ); } s32 pos = ( m_PlayingDataBlockIndex ) * static_cast( m_StreamInfo.oneBlockSamples ); #endif return pos + bufOffset; } /*---------------------------------------------------------------------------* Name: GetFilledBufferRate Description: ストリームバッファ中でデータがロードされている割合を取得する Arguments: なし Returns: データがロードされている割合を百分率で返す *---------------------------------------------------------------------------*/ f32 StreamSoundPlayer::GetFilledBufferPercentage() const { SoundThreadLock lock; if ( ! m_ActiveFlag ) { return 0.0f; } if ( ! m_Tracks[ 0 ].m_ActiveFlag ) { return 0.0f; } if ( ! m_IsPrepared ) { return static_cast( m_BufferBlockCountBase - m_PrepareCounter ) * 100.f / m_BufferBlockCountBase; } #ifdef NW_PLATFORM_CTRWIN s32 bufOffset = 0; if ( m_Tracks[ 0 ].m_pVoice != NULL ) { bufOffset = static_cast( m_Tracks[ 0 ].m_pVoice->GetCurrentPlayingSample() ); } f32 nonFilledBufferBlock = m_StreamDataLoadTaskList.GetSize() + ( static_cast( bufOffset - ( m_PlayingBufferBlockIndex * static_cast( m_StreamInfo.oneBlockSamples ) ) ) / m_StreamInfo.oneBlockSamples ) ; int limit = m_BufferBlockCountBase - 2 + 1; f32 percentage = ( limit - nonFilledBufferBlock ) * 100.0f / limit; percentage = ut::Max( percentage, 0.0f ); return percentage; #else s32 bufferPlayPosition = 0; if ( m_Channels[ 0 ].m_pVoice != NULL ) { bufferPlayPosition = static_cast( m_Channels[ 0 ].m_pVoice->GetCurrentPlayingSample() ); } int allBufferSamples = m_LoadingBufferBlockCount * m_StreamInfo.oneBlockSamples; int prePlayBufferBlocks = m_LoadingBufferBlockCount - m_StreamDataLoadTaskList.GetSize() - 1; f32 percentage = ( ( prePlayBufferBlocks * m_StreamInfo.oneBlockSamples ) + ( m_StreamInfo.oneBlockSamples - bufferPlayPosition ) ) / static_cast( allBufferSamples ); percentage *= 100.f; return percentage; #endif } /* ------------------------------------------------------------------------ private functions ------------------------------------------------------------------------ */ /*---------------------------------------------------------------------------* Name: LoadHeader Description: ストリームファイルヘッダの読み込みと 再生開始のための、セットアップ処理 Arguments: pFileStream - ファイルストリーム startOffsetType - 開始オフセットタイプ startOffset - 開始オフセット位置 Returns: 成功したかどうか *---------------------------------------------------------------------------*/ bool StreamSoundPlayer::LoadHeader( const StreamSoundFile::StreamSoundInfo& streamInfo, const StreamSoundFileReader::TrackInfo trackInfos[], const DspAdpcmParam dspAdpcmParam[], const DspAdpcmLoopParam dspAdpcmLoopParam[], u32 dataBlockOffset, u32 trackCount, u32 channelCount ) { (void)trackCount; (void)channelCount; if ( ! m_IsInitialized ) { // 終了処理後に、コマンドバッファにたまっていたコマンドが処理され // ここに来る場合がある return false; } m_StreamInfo = streamInfo; #if 0 // DEBUG: ストリーム情報ダンプ { NN_LOG("*** STREAM INFO ***\n"); NN_LOG(" enc(%d) loop(%d) ch(%d) rate(%d)\n", m_StreamInfo.encodeMethod, m_StreamInfo.isLoop, m_StreamInfo.channelCount, m_StreamInfo.sampleRate ); NN_LOG(" LS(%d[0x%08X]) LE(%d[0x%08X] block(%d)\n", m_StreamInfo.loopStart, m_StreamInfo.loopStart, m_StreamInfo.frameCount, m_StreamInfo.frameCount, m_StreamInfo.blockCount ); NN_LOG(" [1-Block] bytes(%d) samples(%d)\n", m_StreamInfo.oneBlockBytes, m_StreamInfo.oneBlockSamples ); NN_LOG(" [LastBlock] bytes(%d) samples(%d) paddedBytes(%d)\n", m_StreamInfo.lastBlockBytes, m_StreamInfo.lastBlockSamples, m_StreamInfo.lastBlockPaddedBytes ); NN_LOG(" [SEEK] size4one(%d) intervalSamples(%d)\n", m_StreamInfo.sizeofSeekInfoAtom, m_StreamInfo.seekInfoIntervalSamples ); } #endif m_DataOffsetFromFileHead = dataBlockOffset // ファイル先頭から DATA ブロックへのオフセット + sizeof( ut::BinaryBlockHeader ) // DATA ブロックヘッダーサイズ + m_StreamInfo.sampleDataOffset.offset; // DATA ブロックボディから、実データが始まる地点までのオフセット // Setup時に渡された値と同じかどうか NW_ASSERT( m_TrackCount == ut::Min( trackCount, STRM_TRACK_NUM ) ); NW_ASSERT( m_ChannelCount == ut::Min( channelCount, STRM_CHANNEL_NUM ) ); // トラックの情報を読み取る for ( int i = 0; i < m_TrackCount; i++ ) { m_Tracks[i].m_TrackInfo = trackInfos[i]; #ifdef NW_PLATFORM_CTR // StreamChannel を StreamTrack に割り当て for ( int j = 0; j < trackInfos[i].channelCount; j++ ) { m_Tracks[i].m_pChannels[j] = &m_Channels[ trackInfos[i].globalChannelIndex[j] ]; } #endif } if ( IsDspAdpcm() ) { // ADPCMの情報を読み取る for ( int ch = 0; ch < m_ChannelCount; ch++ ) { m_Channels[ch].m_AdpcmParam = dspAdpcmParam[ch]; m_Channels[ch].m_AdpcmLoopParam = dspAdpcmLoopParam[ch]; #ifdef NW_PLATFORM_CTR // コンテキストの保持 (ボイスへの係数設定は、AllocVoices の中でやる。) nn::snd::AdpcmContext& context0 = m_Channels[ch].m_AdpcmContext[StreamChannel::ADPCM_CONTEXT_HEAD]; context0.pred_scale = m_Channels[ch].m_AdpcmParam.predScale; context0.yn1 = m_Channels[ch].m_AdpcmParam.yn1; context0.yn2 = m_Channels[ch].m_AdpcmParam.yn2; if ( m_StreamInfo.isLoop ) { nn::snd::AdpcmContext& context1 = m_Channels[ch].m_AdpcmContext[StreamChannel::ADPCM_CONTEXT_LOOP]; context1.pred_scale = m_Channels[ch].m_AdpcmLoopParam.loopPredScale; context1.yn1 = m_Channels[ch].m_AdpcmLoopParam.loopYn1; context1.yn2 = m_Channels[ch].m_AdpcmLoopParam.loopYn2; } #endif } } // ストリームプレイヤーのセットアップ if ( ! SetupPlayer() ) { return false; } // セットアップコールバック呼び出し m_PrepareCounter = 0; for ( int i = 0; i < m_BufferBlockCountBase; i++ ) { UpdateLoadingBlockIndex(); m_PrepareCounter++; if ( m_LoadFinishFlag ) break; } #ifdef NW_PLATFORM_CTRWIN // ブロック数が小さい時は、このタイミングでワンショットに設定する if ( ( m_StreamInfo.blockCount <= 2 ) && !m_StreamInfo.isLoop ) // TODO: Magic Number { SetLoopEndToZeroBuffer( static_cast( m_StreamInfo.blockCount ) - 1 ); } #endif // ボイス確保 if ( ! AllocVoices() ) { FreeStreamBuffers(); return false; } return true; } /*---------------------------------------------------------------------------* Name: LoadStreamData Description: ストリームデータのロード Arguments: pFileStream - ファイルストリーム offset - 読み込みオフセット位置 blockSize - ブロックサイズ bufferBlockIndex - バッファブロックインデックス needUpdateAdpcmLoop - ADPCMループ情報の更新が必要かどうか Returns: ロードに成功したかどうか *---------------------------------------------------------------------------*/ bool StreamSoundPlayer::LoadStreamData( int bufferBlockIndex, int dataBlockIndex, u32 blockSamples, bool isDataLoopBlock, bool lastBlockFlag ) { if ( ! m_IsInitialized ) { // 終了処理後に、コマンドバッファにたまっていたコマンドが処理され // ここに来る場合がある return false; } int currentChannel = 0; // 0 ~ m_ChannelCount でインクリメント while ( currentChannel < m_ChannelCount ) { int loadChannelCount = LOAD_BUFFER_CHANNEL_NUM; if ( currentChannel + loadChannelCount > m_ChannelCount ) { loadChannelCount = m_ChannelCount - currentChannel; } for ( int i = 0; i < loadChannelCount; i++ ) { void* dest = ut::AddOffsetToPtr( m_Channels[ currentChannel ].m_pBufferAddress, m_DataBlockSize * bufferBlockIndex ); #ifdef NW_PLATFORM_CTR // WaveBuffer への割り付け nn::snd::WaveBuffer* pBuffer = &m_Channels[currentChannel].m_WaveBuffer[bufferBlockIndex]; nn::snd::InitializeWaveBuffer( pBuffer ); pBuffer->bufferAddress = dest; pBuffer->sampleLength = blockSamples; // ADPCM コンテキストの設定 if ( IsDspAdpcm() ) { if ( dataBlockIndex == 0 ) { // データ先頭用のコンテキスト設定 pBuffer->pAdpcmContext = &m_Channels[currentChannel].m_AdpcmContext[ StreamChannel::ADPCM_CONTEXT_HEAD]; } else if ( isDataLoopBlock ) { // データループ用のコンテキスト設定 pBuffer->pAdpcmContext = &m_Channels[currentChannel].m_AdpcmContext[ StreamChannel::ADPCM_CONTEXT_LOOP]; } else { // コンテキストを NULL に pBuffer->pAdpcmContext = NULL; } } // Voice へのアペンド m_Channels[currentChannel].AppendWaveBuffer( pBuffer, lastBlockFlag ); #endif // NN_LOG("bufBlockIdx(%d) currentCh(%d) loadChCnt(%d) mCh(%d)\n", // bufferBlockIndex, currentChannel, // loadChannelCount, m_ChannelCount ); ++currentChannel; } } // バッファ書き換え完了 if ( ! m_IsPrepared ) { m_PrepareCounter--; if ( m_PrepareCounter == 0 ) { m_IsPrepared = true; } } --m_LoadWaitCount; return true; } /*---------------------------------------------------------------------------* Name: SetupPlayer Description: ストリームのセットアップ Arguments: header - ストリームファイルヘッダ Returns: 成功したかどうか *---------------------------------------------------------------------------*/ bool StreamSoundPlayer::SetupPlayer() { NW_NULL_ASSERT( m_pBufferPool ); const size_t strmBufferSize = m_pBufferPool->GetBlockSize(); // 1ch 分のバッファサイズ // 開始オフセットを求める s32 blockIndex = 0; u32 blockOffset = 0; s32 loopCount = 0; if ( ! CalcStartOffset( &blockIndex, &blockOffset, &loopCount ) ) { // 再生できない開始オフセット return false; } m_LoopStartBlockIndex = static_cast( m_StreamInfo.loopStart / m_StreamInfo.oneBlockSamples ); m_LastBlockIndex = static_cast( m_StreamInfo.blockCount ) - 1; // ブロックサイズの取得 m_DataBlockSize = static_cast( m_StreamInfo.oneBlockBytes ); if ( m_DataBlockSize > DATA_BLOCK_SIZE_MAX ) { NW_WARNING( false, "Too large stream data block size." ); return false; } m_BufferBlockCount = strmBufferSize / m_DataBlockSize; if ( m_BufferBlockCount < 4 ) { NW_WARNING( false, "Too small stream buffer size." ); return false; } if ( m_BufferBlockCount > BUFFER_BLOCK_COUNT_MAX ) { // 非同期ロードコマンド発行のため、上限を設定 m_BufferBlockCount = BUFFER_BLOCK_COUNT_MAX; } m_BufferBlockCountBase = m_BufferBlockCount - 1; m_ChangeNumBlocks = m_BufferBlockCountBase; m_PlayingDataBlockIndex = blockIndex; m_LoadingDataBlockIndex = blockIndex; // #ifdef NW_PLATFORM_CTRWIN // 全データがバッファに入る場合は、非リアルタイムロードモードで処理する // m_IsNoRealtimeLoad = ( m_StreamInfo.blockCount <= m_BufferBlockCount ) ? true : false; // #endif m_LoadingBufferBlockIndex = 0; m_PlayingBufferBlockIndex = 0; if ( m_IsNoRealtimeLoad ) { m_LoadingBufferBlockCount = static_cast( m_StreamInfo.blockCount ); } else { m_LoadingBufferBlockCount = CalcLoadingBufferBlockCount(); } m_PlayingBufferBlockCount = m_LoadingBufferBlockCount; return true; } /*---------------------------------------------------------------------------* Name: AllocStreamBuffers Description: 全チャンネルのストリームバッファを確保 Arguments: なし Returns: 成功したらtrue *---------------------------------------------------------------------------*/ bool StreamSoundPlayer::AllocStreamBuffers() { // 全チャンネル分のストリームバッファ確保 for( int index = 0; index < m_ChannelCount; index++ ) { // 1 ブロック分 (1ch 分) のバッファを確保 void* strmBuffer = m_pBufferPool->Alloc(); if ( strmBuffer == NULL ) { // ひとつでも失敗したら、すべてのバッファを返却し false を返す for( int i = 0 ; i < index ; i++ ) { m_pBufferPool->Free( m_Channels[i].m_pBufferAddress ); m_Channels[i].m_pBufferAddress = NULL; } return false; } m_Channels[ index ].m_pBufferAddress = strmBuffer; } return true; } /*---------------------------------------------------------------------------* Name: FreeStreamBuffers Description: 全チャンネルのストリームバッファを開放 Arguments: なし Returns: なし *---------------------------------------------------------------------------*/ void StreamSoundPlayer::FreeStreamBuffers() { // ストリームバッファ開放 for( int index = 0; index < m_ChannelCount; index++ ) { if ( m_Channels[ index ].m_pBufferAddress == NULL ) { continue; } m_pBufferPool->Free( m_Channels[index].m_pBufferAddress ); m_Channels[ index ].m_pBufferAddress = NULL; } } // 全 StreamTrack に対し、Voice を割り当てる bool StreamSoundPlayer::AllocVoices() { #ifdef NW_PLATFORM_CTRWIN for ( int trackIndex = 0; trackIndex < m_TrackCount; trackIndex++ ) { StreamTrack& track = m_Tracks[ trackIndex ]; if ( ! track.m_ActiveFlag ) { continue; } // Voice 確保 Voice* voice = VoiceManager::GetInstance().AllocVoice( track.m_TrackInfo.channelCount, Voice::PRIORITY_NODROP, VoiceCallbackFunc, &track ); if ( voice == NULL ) { for ( int i = 0; i < trackIndex; i++ ) { StreamTrack& t = m_Tracks[ i ]; if ( t.m_pVoice != NULL ) { t.m_pVoice->Free(); t.m_pVoice = NULL; } } return false; } voice->SetFrontBypass( IsFrontBypass() ); track.m_pVoice = voice; } #else for ( int channelIndex = 0; channelIndex < m_ChannelCount; channelIndex++ ) { StreamChannel& channel = m_Channels[ channelIndex ]; // Voice 確保 Voice* voice = VoiceManager::GetInstance().AllocVoice( 1, // channelCount Voice::PRIORITY_NODROP, VoiceCallbackFunc, &channel ); if ( voice == NULL ) { for ( int i = 0; i < channelIndex; i++ ) { StreamChannel& c = m_Channels[ i ]; if ( c.m_pVoice != NULL ) { c.m_pVoice->Free(); c.m_pVoice = NULL; } } return false; } voice->SetFrontBypass( IsFrontBypass() ); channel.m_pVoice = voice; // ADPCM 係数の設定 if ( IsDspAdpcm() ) { // 係数の用意 nn::snd::AdpcmParam param; for ( int i = 0; i < 16; i++ ) { param.coef[i] = channel.m_AdpcmParam.coef[i]; } // 係数設定 channel.m_pVoice->SetAdpcmParam( 0, param ); } } #endif return true; } /*---------------------------------------------------------------------------* Name: FreeAllTrackVoice Description: ストリーム用チャンネルの解放 Arguments: player - ストリームオブジェクト Returns: None. *---------------------------------------------------------------------------*/ void StreamSoundPlayer::FreeVoices() { #ifdef NW_PLATFORM_CTRWIN // AXボイス開放 for ( int trackIndex = 0; trackIndex < m_TrackCount; trackIndex++ ) { StreamTrack& track = m_Tracks[ trackIndex ]; if ( ! track.m_ActiveFlag ) { continue; } if ( track.m_pVoice != NULL ) { track.m_pVoice->Free(); track.m_pVoice = NULL; } } #else for ( int ch = 0; ch < m_ChannelCount; ch++ ) { StreamChannel& channel = m_Channels[ ch ]; if ( channel.m_pVoice != NULL ) { channel.m_pVoice->Free(); channel.m_pVoice = NULL; } } #endif } /*---------------------------------------------------------------------------* Name: UpdateTask Description: タスクの状態をチェックし、完了したものの解放処理を行う Arguments: None. Returns: None. *---------------------------------------------------------------------------*/ void StreamSoundPlayer::UpdateTask() { for ( StreamDataLoadTaskList::Iterator itr = m_StreamDataLoadTaskList.GetBeginIter(); itr != m_StreamDataLoadTaskList.GetEndIter(); ) { StreamDataLoadTaskList::Iterator curItr = itr++; StreamDataLoadTask* task = &*curItr; if ( task->GetStatus() != Task::STATUS_DONE ) { break; } m_StreamDataLoadTaskList.Erase( task ); m_StreamDataLoadTaskPool.Free( task ); // NN_LOG("Free %08x %d\n",task,m_StreamDataLoadTaskList.size()); } } /*---------------------------------------------------------------------------* Name: Update Description: パラメータ更新 (サウンドスレッドから呼びだされる) Arguments: None. Returns: None. *---------------------------------------------------------------------------*/ void StreamSoundPlayer::Update() { // タスクエラーのため、停止 if ( m_IsTaskError ) { NW_WARNING( false, "Task error is occured." ); Stop(); return; } UpdateTask(); // ストリームチャンネルの停止チェック if ( m_StartedFlag ) { #ifdef NW_PLATFORM_CTRWIN for ( int trackIndex = 0; trackIndex < m_TrackCount; trackIndex++ ) { StreamTrack& track = m_Tracks[ trackIndex ]; if ( ! track.m_ActiveFlag ) { continue; } if ( track.m_pVoice == NULL ) { Stop(); m_FinishFlag = true; return; } } #else for ( int ch = 0; ch < m_ChannelCount; ch++ ) { StreamChannel& channel = m_Channels[ ch ]; if ( channel.m_pVoice == NULL ) { Stop(); m_FinishFlag = true; return; } } #endif // パラメータのアップデート for ( int trackIndex = 0; trackIndex < m_TrackCount; trackIndex++ ) { UpdateVoiceParams( &m_Tracks[ trackIndex ] ); } } // ロード待ちが解消されたかをチェック if ( m_LoadWaitFlag ) { if ( m_StreamDataLoadTaskList.IsEmpty() && ( ! CheckDiskDriveError() ) ) { m_LoadWaitFlag = false; UpdatePauseStatus(); } } // ロード遅延メッセージ出力 if ( m_IsLoadingDelay ) { NW_WARNING( false, "Pause stream because of loading delay." ); m_IsLoadingDelay = false; } } void StreamSoundPlayer::UpdateVoiceParams( StreamTrack* track ) { if ( ! track->m_ActiveFlag ) { return; } // volume f32 volume = 1.0f; volume *= GetVolume(); volume *= static_cast( track->m_TrackInfo.volume ) / 127.0f; volume *= track->m_Volume; // pitch f32 pitchRatio = 1.0f; pitchRatio *= GetPitch(); // pan f32 pan = 0.0f; pan += GetPan(); // m_TrackInfo.pan の範囲は 0-127。1 と 2 は結果的に同じ値として扱う。 if ( track->m_TrackInfo.pan <= 1 ) { pan += static_cast( static_cast( track->m_TrackInfo.pan ) - 63 ) / 63.0f; } else { pan += static_cast( static_cast( track->m_TrackInfo.pan ) - 64 ) / 63.0f; } pan += track->m_Pan; // surround pan f32 span = 0.0f; span += track->m_SurroundPan; span += GetSurroundPan(); // lpf freq f32 lpfFreq = 1.0f; lpfFreq += GetLpfFreq(); // biquad filter int biquadType = GetBiquadFilterType(); f32 biquadValue = GetBiquadFilterValue(); // main send f32 mainSend = 0.0f; mainSend += GetMainSend(); // fx send f32 fxsend[ AUX_BUS_NUM ]; for ( int i=0; im_pVoice; if ( voice != NULL ) { voice->SetVolume( volume ); voice->SetPitch( pitchRatio ); voice->SetPan( pan ); voice->SetSurroundPan( span ); voice->SetLpfFreq( lpfFreq ); voice->SetBiquadFilter( biquadType, biquadValue ); voice->SetMainSend( mainSend ); for ( int i=0; i( i ); voice->SetFxSend( bus, fxsend[ i ] ); } } #else for ( int ch = 0; ch < track->m_TrackInfo.channelCount; ch++ ) { Voice* voice = track->m_pChannels[ch]->m_pVoice; if ( voice != NULL ) { voice->SetVolume( volume ); voice->SetPitch( pitchRatio ); voice->SetLpfFreq( lpfFreq ); voice->SetBiquadFilter( biquadType, biquadValue ); voice->SetMainSend( mainSend ); for ( int i=0; i( i ); voice->SetFxSend( bus, fxsend[ i ] ); } if ( track->m_TrackInfo.channelCount == 1 ) { voice->SetPan( pan ); } else if ( track->m_TrackInfo.channelCount == 2 ) { #if 0 // 1trk == 2ch の場合は、パンモード別のパン計算を行う switch ( GetPanMode() ) { case nw::snd::PAN_MODE_BALANCE: if ( ch == 0 ) { pan = -1.0f; } // L に振る if ( ch == 1 ) { pan = 1.0f; } // R に振る break; case nw::snd::PAN_MODE_DUAL: if ( ch == 0 ) { pan -= 1.0f; } // L に寄せる if ( ch == 1 ) { pan += 1.0f; } // R に寄せる break; } voice->SetPan( pan ); #else // TODO: DualPan, BalancePan に対応するには、 // StreamTrack に Voiceを持たせる形にする必要がある。 // 上記の #if 0 の部分は、実装イメージ。 // BalancePan で pan を -1.0f や 1.0f に固定してしまうと、 // GetPan() やトラックごとのデータパン・API パンが反映されなく鳴る。 register f32 voicePan = pan; if ( ch == 0 ) { voicePan -= 1.0f; } if ( ch == 1 ) { voicePan += 1.0f; } voice->SetPan( voicePan ); #endif } voice->SetSurroundPan( span ); } } #endif } /*---------------------------------------------------------------------------* Name: StreamSoundPlayer::CheckDiskDriveError Description: ディスクドライブのエラーが発生しているかどうかチェックします。 Arguments: None. Returns: エラーが発生しているかどうかを返します。 DVDストリームでない場合は、falseを返します。 *---------------------------------------------------------------------------*/ bool StreamSoundPlayer::CheckDiskDriveError() const { #if defined( NW_PLATFORM_CTRWIN ) || defined( NW_PLATFORM_CTR ) // TODO: 関数名と挙動があっていない return nw::snd::SoundSystem::detail_IsStreamLoadWait(); #else ut::DvdFileStream* dvdFileStream = ut::DynamicCast( m_pFileStream ); if ( dvdFileStream == NULL ) { return false; } s32 driveStatus = DVDGetDriveStatus(); if ( driveStatus == DVD_STATE_END || driveStatus == DVD_STATE_BUSY ) { // リクエストは終了しました / リクエストはありません。 // リクエストは、現在処理中です。 return false; } else { return true; } #endif } /*---------------------------------------------------------------------------* Name: UpdateBuffer Description: 再生バッファの更新 (AXコールバックから呼びだされる) Arguments: None. Returns: None. *---------------------------------------------------------------------------*/ void StreamSoundPlayer::UpdateBuffer() { if ( ! m_StartedFlag ) { return; } if ( ! m_Tracks[ 0 ].m_ActiveFlag ) { return; } #ifdef NW_PLATFORM_CTRWIN Voice* voice = m_Tracks[ 0 ].m_pVoice; #else Voice* voice = m_Channels[ 0 ].m_pVoice; #endif if ( voice == NULL ) { return; } if ( ! voice->IsRun() ) { return; } if ( CheckDiskDriveError() ) { m_LoadWaitFlag = true; UpdatePauseStatus(); } // ------------------------- // バッファのアップデート // バッファブロックの境界を越えたかチェックし、ファイルロードを発行する if ( ( !m_PlayFinishFlag ) && ( !m_IsNoRealtimeLoad ) && ( !m_LoadWaitFlag ) ) { #ifdef NW_PLATFORM_CTRWIN const u32 playingSample = voice->GetCurrentPlayingSample(); const int currentBlockIndex = static_cast( playingSample / m_StreamInfo.oneBlockSamples ); while ( m_PlayingBufferBlockIndex != currentBlockIndex ) #else while ( m_Channels[0].m_WaveBuffer[m_PlayingBufferBlockIndex].status == nn::snd::WaveBuffer::STATUS_DONE ) #endif { if ( m_PlayFinishFlag ) break; // NN_LOG("DONE(%d) 0(%d) 1(%d) 2(%d) 3(%d) 4(%d)\n", // m_PlayingBufferBlockIndex, // m_Channels[0].m_WaveBuffer[0].status, // m_Channels[0].m_WaveBuffer[1].status, // m_Channels[0].m_WaveBuffer[2].status, // m_Channels[0].m_WaveBuffer[3].status, // m_Channels[0].m_WaveBuffer[4].status // ); if ( ! m_LoadWaitFlag ) { // ------------------------- // ロードが追いついているかをチェック if ( !m_StreamDataLoadTaskList.IsEmpty() && m_LoadWaitCount >= m_BufferBlockCountBase - 2 ) { // ロードが追いついてない m_IsLoadingDelay = true; m_LoadWaitFlag = true; UpdatePauseStatus(); break; } } // 再生ブロックの更新 UpdatePlayingBlockIndex(); // ロードブロックの更新 UpdateLoadingBlockIndex(); } } } #ifdef NW_PLATFORM_CTRWIN /*---------------------------------------------------------------------------* Name: UpdateLoopAddress Description: ボイスのループアドレスを更新 Arguments: loopStartSamples - ループ開始サンプル位置 loopEndSamples - ループ終端サンプル位置 Returns: None. *---------------------------------------------------------------------------*/ void StreamSoundPlayer::UpdateLoopAddress( unsigned long loopStartSamples, unsigned long loopEndSamples ) { for ( int trackIndex = 0; trackIndex < m_TrackCount; trackIndex++ ) { StreamTrack& track = m_Tracks[ trackIndex ]; if ( ! track.m_ActiveFlag ) { continue; } Voice* voice = track.m_pVoice; if ( voice == NULL ) { continue; } for ( int channelIndex = 0; channelIndex < track.m_TrackInfo.channelCount; channelIndex++ ) { StreamChannel* channel = GetTrackChannel( m_Tracks[ trackIndex ], channelIndex ); voice->SetLoopStart( channelIndex, channel->m_pBufferAddress, loopStartSamples ); voice->SetLoopEnd( channelIndex, channel->m_pBufferAddress, loopEndSamples ); } voice->SetLoopFlag( true ); } } #endif /*---------------------------------------------------------------------------* Name: UpdatePlayingBlockIndex Description: 再生位置のブロックインデックスを更新 Arguments: None. Returns: None. *---------------------------------------------------------------------------*/ void StreamSoundPlayer::UpdatePlayingBlockIndex() { if ( m_PlayFinishFlag ) { return; } u32 playDataIdx = m_PlayingDataBlockIndex; u32 playBufIdx = m_PlayingBufferBlockIndex; m_PlayingDataBlockIndex++; if ( m_PlayingDataBlockIndex > m_LastBlockIndex ) { // 最終ブロックを再生し終えた if ( m_StreamInfo.isLoop ) { // ループスタートへジャンプした m_PlayingDataBlockIndex = m_LoopStartBlockIndex; if ( m_LoopCounter < INT_MAX ) m_LoopCounter++; #ifdef NW_PLATFORM_CTRWIN UpdateLoopAddress( 0, m_PlayingBufferBlockCount * m_StreamInfo.oneBlockSamples ); #endif } } // バッファブロック番号更新 m_PlayingBufferBlockIndex++; if ( m_PlayingBufferBlockIndex >= m_PlayingBufferBlockCount ) { // バッファループ発生 m_PlayingBufferBlockIndex = 0; // バッファブロック数を、ロード情報に合わせて更新 m_PlayingBufferBlockCount = m_LoadingBufferBlockCount; #ifdef NW_PLATFORM_CTRWIN UpdateLoopAddress( 0, m_PlayingBufferBlockCount * m_StreamInfo.oneBlockSamples ); #endif } if ( m_PlayingBufferBlockIndex == m_PlayingBufferBlockCount-1 ) { m_ValidAdpcmLoop = false; m_SkipUpdateAdpcmLoop = false; } // NN_LOG("### PlayingBufferBlockIdx(%d) PlayingDataBlockIdx(%2d)\n", // m_PlayingBufferBlockIndex, m_PlayingDataBlockIndex ); if ( m_PlayingDataBlockIndex >= m_LastBlockIndex - 1 ) { #ifdef NW_PLATFORM_CTRWIN const s32 endBufferBlockIndex = m_PlayingBufferBlockIndex + 1; UpdateDataLoopAddress( endBufferBlockIndex ); #else if ( ! m_StreamInfo.isLoop ) { m_PlayFinishFlag = true; } #endif } } #ifdef NW_PLATFORM_CTRWIN /*---------------------------------------------------------------------------* Name: UpdateDataLoopAddress Description: データループのため、ボイスのアドレスを更新 Arguments: endBufferBlockIndex - 終端バッファのブロックインデックス Returns: None. *---------------------------------------------------------------------------*/ void StreamSoundPlayer::UpdateDataLoopAddress( s32 endBufferBlockIndex ) { // データループ直前 if ( m_StreamInfo.isLoop ) { // ループのためにバッファのループスタートとループエンドを動かす s32 startBlockNum = endBufferBlockIndex + 1; if ( startBlockNum >= m_PlayingBufferBlockCount ) { startBlockNum -= m_PlayingBufferBlockCount; } #ifdef NW_PLATFORM_CTRWIN UpdateLoopAddress( startBlockNum * m_StreamInfo.oneBlockSamples, endBufferBlockIndex * m_StreamInfo.oneBlockSamples + m_StreamInfo.lastBlockSamples ); #endif if ( IsDspAdpcm() ) { for ( int trackIndex = 0; trackIndex < m_TrackCount; trackIndex++ ) { StreamTrack& track = m_Tracks[ trackIndex ]; if ( ! track.m_ActiveFlag ) { continue; } Voice* voice = track.m_pVoice; if ( voice == NULL ) { continue; } if ( voice->GetFormat() == SAMPLE_FORMAT_DSP_ADPCM ) { #ifdef NW_PLATFORM_CTRWIN for( int channelIndex = 0; channelIndex < track.m_TrackInfo.channelCount; channelIndex++ ) { StreamChannel* channel = GetTrackChannel( track, channelIndex ); voice->SetDspAdpcmLoop( channelIndex, &channel->m_AdpcmLoopParam ); } #endif } } if ( endBufferBlockIndex == m_PlayingBufferBlockCount - 1 ) { m_SkipUpdateAdpcmLoop = true; } } } else { // ループエンドを変更してゼロバッファへ飛ばし、ワンショットモードにする SetLoopEndToZeroBuffer( endBufferBlockIndex ); } } /*---------------------------------------------------------------------------* Name: SetLoopEndToZeroBuffer Description: データ終端のため、ボイスのアドレスをゼロバッファに飛ばす Arguments: endBufferBlockIndex - 終端バッファのブロックインデックス Returns: None. *---------------------------------------------------------------------------*/ void StreamSoundPlayer::SetLoopEndToZeroBuffer( int endBufferBlockIndex ) { { // ループエンドを変更してゼロバッファへ飛ばし、ワンショットモードにする for ( int trackIndex = 0; trackIndex < m_TrackCount; trackIndex++ ) { StreamTrack& track = m_Tracks[ trackIndex ]; if ( ! track.m_ActiveFlag ) { continue; } Voice* voice = track.m_pVoice; if ( voice == NULL ) { continue; } for( int channelIndex = 0; channelIndex < track.m_TrackInfo.channelCount; channelIndex++ ) { StreamChannel* channel = GetTrackChannel( track, channelIndex ); voice->StopAtPoint( channelIndex, channel->m_pBufferAddress, endBufferBlockIndex * m_StreamInfo.oneBlockSamples + m_StreamInfo.lastBlockSamples ); } } } m_PlayFinishFlag = true; } #endif /*---------------------------------------------------------------------------* Name: UpdateLoadingBlockIndex Description: ロードブロックインデックスの更新 Arguments: None. Returns: None. *---------------------------------------------------------------------------*/ void StreamSoundPlayer::UpdateLoadingBlockIndex() { if ( m_LoadFinishFlag ) { return; } ++m_LoadWaitCount; // コールバックパラメータの設定 size_t blockSize = 0; u32 blockSamples = 0; bool lastBlockFlag = false; if ( m_LoadingDataBlockIndex == m_LastBlockIndex ) { // 最終ブロック blockSize = m_StreamInfo.lastBlockPaddedBytes; blockSamples = m_StreamInfo.lastBlockSamples; if ( ! m_StreamInfo.isLoop ) { lastBlockFlag = true; } } else { // 通常ブロック blockSize = m_StreamInfo.oneBlockBytes; blockSamples = m_StreamInfo.oneBlockSamples; } s32 loadOffset = CalcLoadOffset(); NW_ALIGN32_ASSERT( blockSize ); NW_ALIGN4_ASSERT( loadOffset ); // ロードタスクの発行 StreamDataLoadTask* task = m_StreamDataLoadTaskPool.Alloc(); NW_NULL_ASSERT( task ); task->m_PlayerHandle = this; task->m_pFileStream = m_pFileStream; task->m_ChannelCount = m_StreamInfo.channelCount; task->m_Offset = loadOffset; task->m_BlockBytes = blockSize; // 1ブロックあたりのバイト数 task->m_BlockSamples = blockSamples; // 1ブロックあたりのフレーム数 task->m_BufferBlockIndex = m_LoadingBufferBlockIndex; task->m_DataBlockSize = m_DataBlockSize; task->m_LoadingDataBlockIndex = m_LoadingDataBlockIndex; task->m_LastBlockFlag = lastBlockFlag; #ifdef NW_PLATFORM_CTRWIN #else task->m_IsDataLoopBlock = ( m_LoadingDataBlockIndex == m_LoopStartBlockIndex ) && m_StreamInfo.isLoop; #endif task->SetId(reinterpret_cast(this)); for(int ch=0;chm_BufferAddress[ch] = m_Channels[ ch ].m_pBufferAddress; } m_StreamDataLoadTaskList.PushBack( task ); internal::TaskManager::GetInstance().AppendTask( task, m_StartedFlag ? internal::TaskManager::PRIORITY_HIGH : internal::TaskManager::PRIORITY_MIDDLE ); // NN_LOG("loadtask append buf(%d)\n", m_LoadingBufferBlockIndex); // ロードデータブロックインデックスの更新 m_LoadingDataBlockIndex++; if ( m_LoadingDataBlockIndex > m_LastBlockIndex ) { if ( m_StreamInfo.isLoop ) { m_LoadingDataBlockIndex = m_LoopStartBlockIndex; } else { // データ終端 m_LoadFinishFlag = true; return; } } // ロードバッファブロックインデックスの更新 m_LoadingBufferBlockIndex++; if ( m_LoadingBufferBlockIndex >= m_LoadingBufferBlockCount ) { // ロードバッファループ m_LoadingBufferBlockIndex = 0; m_LoadingBufferBlockCount = CalcLoadingBufferBlockCount(); } } /*---------------------------------------------------------------------------* Name: UpdatePauseStatus Description: ボイスのポーズステータスを更新 Arguments: None. Returns: None. *---------------------------------------------------------------------------*/ void StreamSoundPlayer::UpdatePauseStatus() { bool pauseStatus = false; if ( m_PauseFlag ) { pauseStatus = true; } if ( m_LoadWaitFlag ) { pauseStatus = true; } if ( pauseStatus != m_PauseStatus ) { #ifdef NW_PLATFORM_CTRWIN for ( int trackIndex = 0; trackIndex < m_TrackCount; trackIndex++ ) { if ( ! m_Tracks[ trackIndex ].m_ActiveFlag ) { continue; } Voice* voice = m_Tracks[ trackIndex ].m_pVoice; if ( voice != NULL ) { voice->Pause( pauseStatus ); } } #else for ( int ch = 0; ch < m_ChannelCount; ch++ ) { Voice* voice = m_Channels[ ch ].m_pVoice; if ( voice != NULL ) { voice->Pause( pauseStatus ); } } #endif m_PauseStatus = pauseStatus; } } /*---------------------------------------------------------------------------* Name: CalcLoadingBufferBlockCount Description: ロードバッファブロック数の計算 データ終端が、バッファの先頭に来ないようにするため、 必要に応じて、ブロック数を1増やす Arguments: None. Returns: ロードバッファブロック数 *---------------------------------------------------------------------------*/ int StreamSoundPlayer::CalcLoadingBufferBlockCount() const { #ifdef NW_PLATFORM_CTRWIN const int restBlockCount = m_LastBlockIndex - m_LoadingDataBlockIndex + 1; const int loopBlockCount = m_LastBlockIndex - m_LoopStartBlockIndex + 1; // restBlockCount + loopBlockCount * { 0...n } == m_BufferBlockCountBase + 1 // を満たすとき、バッファブロック数を一時的増やして、 // データループエンドがブロック先頭に来ないようにする if ( ( ( m_BufferBlockCountBase + 1 ) - restBlockCount ) % loopBlockCount == 0 ) { return m_BufferBlockCountBase + 1; } else { return m_BufferBlockCountBase; } #else // TODO: [SDK] AppendNextBuffer が実装されたらここはなし return m_BufferBlockCount; #endif } /*---------------------------------------------------------------------------* Name: CalcStartOffset Description: 開始オフセット位置の計算 Arguments: pStartBlockIndex - 開始ブロックインデックスを格納するアドレス pStartBlockOffset - 開始ブロックオフセットを格納するアドレス pLoopCount - ループカウントを格納するアドレス Returns: 計算に成功したかどうか *---------------------------------------------------------------------------*/ bool StreamSoundPlayer::CalcStartOffset( s32* pStartBlockIndex, u32* pStartBlockOffset, s32* pLoopCount ) { // 再生開始ブロックの計算 // 設定されている m_StreamInfo と m_StartOffset の値を使って計算する if ( m_StreamInfo.oneBlockSamples <= 0 ) { return false; } u32 startOffsetSamples = 0; if ( m_StartOffsetType == START_OFFSET_TYPE_SAMPLE ) { startOffsetSamples = m_StartOffset; } else if ( m_StartOffsetType == START_OFFSET_TYPE_MILLISEC ) { startOffsetSamples = static_cast( static_cast( m_StartOffset ) * m_StreamInfo.sampleRate / 1000 ); } *pLoopCount = 0; if ( startOffsetSamples >= m_StreamInfo.frameCount ) { if ( m_StreamInfo.isLoop ) { // ループして折り返す s32 loopStart = static_cast( m_StreamInfo.loopStart ); s32 loopEnd = static_cast( m_StreamInfo.frameCount ); s32 loopLen = loopEnd - loopStart; s32 startOffset2 = startOffsetSamples - loopEnd; *pLoopCount = 1 + startOffset2 / loopLen; startOffsetSamples = loopStart + startOffset2 % loopLen; } else { // ワンショットで開始位置が波形の終端以降なら再生できない return false; } } *pStartBlockIndex = startOffsetSamples / static_cast( m_StreamInfo.oneBlockSamples ); *pStartBlockOffset = startOffsetSamples % m_StreamInfo.oneBlockSamples; return true; } /*---------------------------------------------------------------------------* Name: VoiceCallbackFunc Description: ボイスから呼びだされるコールバック関数 Arguments: voice - ボイス statuc - コールバックステータス arg - ユーザー引数 Returns: None. *---------------------------------------------------------------------------*/ void StreamSoundPlayer::VoiceCallbackFunc( Voice* voice, Voice::VoiceCallbackStatus status, void* arg ) { #ifdef NW_PLATFORM_CTRWIN NW_NULL_ASSERT( arg ); StreamTrack* track = reinterpret_cast( arg ); NW_ASSERT( track->m_pVoice == voice ); switch ( status ) { case Voice::CALLBACK_STATUS_FINISH_WAVE: case Voice::CALLBACK_STATUS_CANCEL: voice->Free(); track->m_pVoice = NULL; break; case Voice::CALLBACK_STATUS_DROP_VOICE: case Voice::CALLBACK_STATUS_DROP_DSP: track->m_pVoice = NULL; break; default: NW_ASSERTMSG( false, "Unknown Voice callback status %d", status ); return; } #else NW_NULL_ASSERT( arg ); StreamChannel* channel = reinterpret_cast( arg ); NW_ASSERT( channel->m_pVoice == voice ); switch ( status ) { case Voice::CALLBACK_STATUS_FINISH_WAVE: case Voice::CALLBACK_STATUS_CANCEL: voice->Free(); channel->m_pVoice = NULL; break; case Voice::CALLBACK_STATUS_DROP_VOICE: case Voice::CALLBACK_STATUS_DROP_DSP: channel->m_pVoice = NULL; break; default: NW_ASSERTMSG( false, "Unknown Voice callback status %d", status ); return; } #endif } #ifdef NW_PLATFORM_CTRWIN /*---------------------------------------------------------------------------* Name: SetAdpcmLoopContext Description: ADPCMループ情報の設定 Arguments: channelNum - チャンネル数 predScale - プレディクタ/スケール値の配列 要素数は、channelNum Returns: None. *---------------------------------------------------------------------------*/ void StreamSoundPlayer::SetAdpcmLoopContext( int channelNum, u16 predScale[] ) { if ( ! IsDspAdpcm() ) { return; } for ( int channelIndex = 0; channelIndex < channelNum && channelIndex < STRM_CHANNEL_NUM; channelIndex++ ) { m_Channels[ channelIndex ].m_AdpcmPredScale = predScale[ channelIndex ]; } m_ValidAdpcmLoop = true; } #endif /*---------------------------------------------------------------------------* Name: GetTrackChannel Description: トラック内のチャンネル番号からチャンネルを取得する Arguments: track - トラック channelIndex - トラック内のチャンネル番号 Returns: *---------------------------------------------------------------------------*/ StreamChannel* StreamSoundPlayer::GetTrackChannel( const StreamTrack& track, int channelIndex ) { if ( channelIndex >= STRM_CHANNEL_NUM_PER_TRACK ) { return NULL; } int index = track.m_TrackInfo.globalChannelIndex[ channelIndex ]; if ( index >= STRM_CHANNEL_NUM ) { return NULL; } return &m_Channels[ index ]; } void StreamSoundPlayer::SetTrackVolume( u32 trackBitFlag, f32 volume ) { for( int trackNo = 0; trackNo < m_TrackCount && trackBitFlag != 0; trackNo++, trackBitFlag >>= 1 ) { if ( ( trackBitFlag & 0x01 ) == 0 ) { continue; } m_Tracks[ trackNo ].m_Volume = volume; } } void StreamSoundPlayer::SetTrackPan( u32 trackBitFlag, f32 pan ) { for( int trackNo = 0; trackNo < m_TrackCount && trackBitFlag != 0; trackNo++, trackBitFlag >>= 1 ) { if ( ( trackBitFlag & 0x01 ) == 0 ) { continue; } m_Tracks[ trackNo ].m_Pan = pan; } } void StreamSoundPlayer::SetTrackSurroundPan( u32 trackBitFlag, f32 span ) { for( int trackNo = 0; trackNo < m_TrackCount && trackBitFlag != 0; trackNo++, trackBitFlag >>= 1 ) { if ( ( trackBitFlag & 0x01 ) == 0 ) { continue; } m_Tracks[ trackNo ].m_SurroundPan = span; } } StreamTrack* StreamSoundPlayer::GetPlayerTrack( int trackNo ) { if ( trackNo > STRM_TRACK_NUM - 1 ) { return NULL; } return &m_Tracks[ trackNo ]; } const StreamTrack* StreamSoundPlayer::GetPlayerTrack( int trackNo ) const { if ( trackNo > STRM_TRACK_NUM - 1 ) { return NULL; } return &m_Tracks[ trackNo ]; } s32 StreamSoundPlayer::CalcLoadOffset() const { s32 loadOffset = static_cast( m_DataOffsetFromFileHead + m_LoadingDataBlockIndex * ( m_StreamInfo.oneBlockBytes * m_StreamInfo.channelCount ) ); return loadOffset; } /* ======================================================================== StreamSoundPlayer::StreamHeaderLoadTask class ======================================================================== */ StreamSoundPlayer::StreamHeaderLoadTask::StreamHeaderLoadTask() : m_PlayerHandle( NULL ), m_pFileStream( NULL ), m_StartOffset( 0 ) { } void StreamSoundPlayer::StreamHeaderLoadTask::Execute() { bool result = LoadHeader(); if ( ! result ) { m_PlayerHandle->SetTaskErrorFlag(); return; } } bool StreamSoundPlayer::StreamHeaderLoadTask::LoadHeader() { // ファイルヘッダのロード internal::StreamSoundFileLoader loader( *m_pFileStream ); if ( ! loader.LoadFileHeader( s_LoadBuffer, StreamSoundPlayer::LOAD_BUFFER_SIZE ) ) { return false; } // ストリーム情報の読み取り DriverCommandManager& cmdmgr = DriverCommandManager::GetInstanceForTaskThread(); DriverCommandStreamSoundLoadHeader* command = cmdmgr.AllocCommand(); command->id = DRIVER_COMMAND_STRM_LOADHEADER; command->player = m_PlayerHandle; if ( ! loader.ReadStreamInfo( &command->streamInfo ) ) { return false; } command->dataBlockOffset = loader.GetDataBlockOffset(); command->trackCount = loader.GetTrackCount(); command->channelCount = loader.GetChannelCount(); // トラックの情報を読み取る NW_ASSERT( command->trackCount <= DriverCommandStreamSoundLoadHeader::TRACK_MAX ); for ( unsigned int i = 0; i < command->trackCount; i++ ) { if ( ! loader.ReadStreamTrackInfo( &command->trackInfoArray[i], i ) ) { return false; } } if ( WaveFileReader::GetSampleFormat( command->streamInfo.encodeMethod ) == SAMPLE_FORMAT_DSP_ADPCM ) { // ADPCMの情報を読み取る NW_ASSERT( command->channelCount <= DriverCommandStreamSoundLoadHeader::CHANNEL_MAX ); for ( unsigned int ch = 0; ch < command->channelCount; ch++ ) { if ( ! loader.ReadDspAdpcmChannelInfo( &command->dspAdpcmParam[ch], &command->dspAdpcmLoopParam[ch], ch ) ) { return false; } } // ADPCMで途中再生するための情報を取得する if ( m_StartOffset != 0 ) { int startOffsetSamples = 0; if ( m_StartOffsetType == START_OFFSET_TYPE_SAMPLE ) { startOffsetSamples = m_StartOffset; } else if ( m_StartOffsetType == START_OFFSET_TYPE_MILLISEC ) { startOffsetSamples = m_StartOffset * command->streamInfo.sampleRate / 1000; } s32 blockIndex = startOffsetSamples / static_cast( command->streamInfo.oneBlockSamples ); u16 yn1[ STRM_CHANNEL_NUM ]; u16 yn2[ STRM_CHANNEL_NUM ]; if ( ! loader.ReadAdpcBlockData( yn1, yn2, blockIndex, command->channelCount ) ) { return false; } for ( unsigned int i=0; ichannelCount; i++ ) { command->dspAdpcmParam[i].yn1 = yn1[i]; command->dspAdpcmParam[i].yn2 = yn2[i]; } } } cmdmgr.PushCommand(command); cmdmgr.FlushCommand(true); return true; } /* ======================================================================== StreamSoundPlayer::StreamDataLoadTask class ======================================================================== */ StreamSoundPlayer::StreamDataLoadTask::StreamDataLoadTask() : m_PlayerHandle( NULL ), m_pFileStream( NULL ), // m_Size( 0 ), m_Offset( 0 ), m_BlockBytes( 0 ), m_BufferBlockIndex( -1 ), m_BlockSamples( 0 ), m_IsDataLoopBlock( false ) { } void StreamSoundPlayer::StreamDataLoadTask::Execute() { // NN_LOG("exec(0x%p): bufIdx(%d)\n", this, m_BufferBlockIndex ); bool result = LoadStreamData(); if ( ! result ) { m_PlayerHandle->SetTaskErrorFlag(); } } bool StreamSoundPlayer::StreamDataLoadTask::LoadStreamData() { NW_ALIGN4_ASSERT( m_Offset ); NW_ALIGN32_ASSERT( m_BlockBytes ); #ifdef NW_PLATFORM_CTR nn::snd::InvalidateDataCache( reinterpret_cast( s_LoadBuffer ), sizeof( s_LoadBuffer ) ); #endif int loadOffset = m_Offset; unsigned int currentChannel = 0; // 0 ~ m_ChannelCount でインクリメント while ( currentChannel < m_ChannelCount ) { NW_ALIGN4_ASSERT( loadOffset ); int loadChannelCount = LOAD_BUFFER_CHANNEL_NUM; if ( currentChannel + loadChannelCount > m_ChannelCount ) { loadChannelCount = m_ChannelCount - currentChannel; } size_t loadSize = m_BlockBytes * loadChannelCount; NW_ASSERT( loadSize <= LOAD_BUFFER_SIZE ); // データのロード m_pFileStream->Seek( loadOffset, io::FILE_STREAM_SEEK_BEGIN ); s32 resultSize = m_pFileStream->Read( s_LoadBuffer, loadSize ); loadOffset += loadSize; #ifdef DEBUG_STRM std::fwrite( s_LoadBuffer, loadSize, 1, s_pFile ); #endif if ( resultSize != loadSize ) { return false; } for ( int i = 0; i < loadChannelCount; i++ ) { // ロード用バッファ (s_LoadBuffer) から、ストリームバッファにコピー u32 len = static_cast( m_BlockBytes ); const void* source = ut::AddOffsetToPtr( s_LoadBuffer, m_BlockBytes * i ); void* dest = ut::AddOffsetToPtr( m_BufferAddress[ currentChannel ], m_DataBlockSize * m_BufferBlockIndex ); std::memcpy( dest, source, len ); #ifndef NW_PLATFORM_CTRWIN nn::snd::FlushDataCache( reinterpret_cast(dest), len ); #endif // NN_LOG("bufBlockIdx(%d) currentCh(%d) loadChCnt(%d) mCh(%d) loadSize(%d)\n", // bufferBlockIndex, currentChannel, // loadChannelCount, m_ChannelCount, loadSize ); ++currentChannel; } } DriverCommandManager& cmdmgr = DriverCommandManager::GetInstanceForTaskThread(); DriverCommandStreamSoundLoadData* command = cmdmgr.AllocCommand(); command->id = DRIVER_COMMAND_STRM_LOADDATA; command->player = m_PlayerHandle; command->bufferBlockIndex = m_BufferBlockIndex; command->blockSamples = m_BlockSamples; command->dataBlockIndex = m_LoadingDataBlockIndex; command->isDataLoopBlock = m_IsDataLoopBlock; command->lastBlockFlag = m_LastBlockFlag; cmdmgr.PushCommand(command); cmdmgr.FlushCommand(true); return true; } } // namespace nw::snd::internal::driver } // namespace nw::snd::internal } // namespace nw::snd } // namespace nw