/*---------------------------------------------------------------------------* Project: NintendoWare File: snd_SequenceSoundPlayer.cpp Copyright (C)2009-2010 Nintendo Co., Ltd./HAL Laboratory, Inc. All rights reserved. These coded instructions, statements, and computer programs contain proprietary information of Nintendo of America Inc. and/or Nintendo Company Ltd., and are protected by Federal copyright law. 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. $Revision: 27883 $ *---------------------------------------------------------------------------*/ #include "precompiled.h" #include #include #include #include #include #include #include namespace { // DSP プロセスの割り込み間隔の分母・分子。 // NUMERATOR / DENOMINATOR [msec] (約 4.888 ...) が実際の割り込み間隔。 const u32 INTERVAL_MSEC_NUMERATOR = NN_SND_SAMPLES_PER_FRAME * 1000 * (32 * 256); const u32 INTERVAL_MSEC_DENOMINATOR = NN_SND_HW_SYSTEM_CLOCK; } namespace nw { namespace snd { namespace internal { namespace driver { /* ======================================================================== static variable ======================================================================== */ vs16 SequenceSoundPlayer::m_GlobalVariable[ GLOBAL_VARIABLE_NUM ]; /* ======================================================================== public function ======================================================================== */ void SequenceSoundPlayer::InitSequenceSoundPlayer() { for( int variableNo = 0 ; variableNo < GLOBAL_VARIABLE_NUM ; variableNo++ ) { m_GlobalVariable[ variableNo ] = VARIABLE_DEFAULT_VALUE; } } /*---------------------------------------------------------------------------* Name: SequenceSoundPlayer Description: コンストラクタ Arguments: None. Returns: None. *---------------------------------------------------------------------------*/ SequenceSoundPlayer::SequenceSoundPlayer() { for ( int trackNo = 0; trackNo < TRACK_NUM_PER_PLAYER ; trackNo++ ) { m_pTracks[trackNo] = NULL; } } /*---------------------------------------------------------------------------* Name: ~SequenceSoundPlayer Description: デストラクタ Arguments: None. Returns: None. *---------------------------------------------------------------------------*/ SequenceSoundPlayer::~SequenceSoundPlayer() { Finalize(); } /*---------------------------------------------------------------------------* Name: Initialize Description: パラメータを初期化します Arguments: None. Returns: None. *---------------------------------------------------------------------------*/ void SequenceSoundPlayer::Initialize() { BasicSoundPlayer::Initialize(); m_StartedFlag = false; m_PauseFlag = false; m_ReleasePriorityFixFlag = false; m_TempoRatio = 1.0f; m_TickFraction = 0.0f; m_SkipTickCounter = 0; m_SkipTimeCounter = 0.0f; m_PanRange = 1.0f; m_TickCounter = 0; m_TickFraction = 0.0f; m_SequenceUserprocCallback = NULL; m_pSequenceUserprocCallbackArg = NULL; m_ParserParam.tempo = DEFAULT_TEMPO; m_ParserParam.timebase = DEFAULT_TIMEBASE; m_ParserParam.volume = 127; m_ParserParam.priority = 64; m_ParserParam.callback = NULL; for ( int varNo = 0; varNo < PLAYER_VARIABLE_NUM ; varNo++ ) { m_LocalVariable[ varNo ] = VARIABLE_DEFAULT_VALUE; } for ( int trackNo = 0; trackNo < TRACK_NUM_PER_PLAYER ; trackNo++ ) { m_pTracks[trackNo] = NULL; } for ( int bankIdx = 0; bankIdx < SoundArchive::SEQ_BANK_MAX; bankIdx++ ) { m_pBankFiles[ bankIdx ] = NULL; } } /*---------------------------------------------------------------------------* Name: Finalize Description: シーケンスプレイヤーのシャットダウン Arguments: None. Returns: None. *---------------------------------------------------------------------------*/ void SequenceSoundPlayer::Finalize() { m_FinishFlag = true; FinishPlayer(); // 無効化リストから削除 if ( m_ActiveFlag ) { DisposeCallbackManager::GetInstance().UnregisterDisposeCallback( this ); m_ActiveFlag = false; } // NN_LOG( "track rest %d\n",m_pSequenceTrackAllocator->GetAllocatableTrackCount()); BasicSoundPlayer::Finalize(); } /*---------------------------------------------------------------------------* Name: Setup Description: シーケンスの初期化を行います Arguments: Returns: None. *---------------------------------------------------------------------------*/ void SequenceSoundPlayer::Setup( SequenceTrackAllocator* trackAllocator, u32 allocTracks, NoteOnCallback* callback ) { //----------------------------------------------------------------------------- // プレイヤー初期化 m_ParserParam.callback = callback; { //----------------------------------------------------------------------------- // 全トラックを確保することができるかどうか { int trackCount = 0; for( u32 trackBitMask = allocTracks; trackBitMask != 0; trackBitMask >>= 1 ) { if ( trackBitMask & 0x01 ) trackCount++; } if ( trackCount > trackAllocator->GetAllocatableTrackCount() ) { #if 0 return SETUP_ERR_CANNOT_ALLOCATE_TRACK; #else NW_WARNING(false, "Failed to start sequence sound for not enough sequence track instance." ); Finalize(); return; #endif } } //----------------------------------------------------------------------------- // トラック確保 { u32 trackBitMask = allocTracks; for( int trackNo = 0; trackBitMask != 0; trackNo++, trackBitMask >>= 1 ) { if ( ( trackBitMask & 0x01 ) == 0 ) continue; SequenceTrack* track = trackAllocator->AllocTrack( this ); NW_NULL_ASSERT( track ); SetPlayerTrack( trackNo, track ); // NN_LOG("AllocTrack %08x\n",track); } } } // NN_LOG( "track rest %d\n",trackAllocator->GetAllocatableTrackCount()); // 無効化リストに追加 DisposeCallbackManager::GetInstance().RegisterDisposeCallback( this ); m_pSequenceTrackAllocator = trackAllocator; m_ActiveFlag = true; #ifdef NW_PLATFORM_CTR #if 0 // TODO: perf for ( int i = 0; i < PERF_NUM; i++ ) { s_PerfParam[ i ].Reset(); s_PerfParam[ i ].isCalcTick = true; } #endif #endif // SoundThread::GetInstance().SetSoundPlay( true ); // return SETUP_SUCCESS; } /*---------------------------------------------------------------------------* Name: SetSeqData Description: シーケンスの再生準備を行います Arguments: seqBase - シーケンスデータベースアドレス seqOffset - シーケンスデータオフセット Returns: None. *---------------------------------------------------------------------------*/ void SequenceSoundPlayer::SetSeqData( const void* seqBase, s32 seqOffset ) { SequenceTrack* seqTrack = GetPlayerTrack( 0 ); if ( seqTrack == NULL ) { return; } if ( seqBase != NULL ) { seqTrack->SetSeqData( seqBase, seqOffset ); seqTrack->Open(); } } void SequenceSoundPlayer::SetBankData( const void* bankFiles[], u32 bankFileCount ) { NW_ASSERT( bankFileCount <= SoundArchive::SEQ_BANK_MAX ); if ( bankFiles != NULL ) { for ( int i = 0; i < bankFileCount; i++ ) { m_pBankFiles[ i ] = bankFiles[ i ]; } } } /*---------------------------------------------------------------------------* Name: Start Description: 準備完了したシーケンスの再生を開始します Arguments: playerNo - プレイヤー番号 Returns: None. *---------------------------------------------------------------------------*/ void SequenceSoundPlayer::Start() { // プレイヤーリストに追加 SoundThread::GetInstance().RegisterPlayerCallback( this ); m_StartedFlag = true; } /*---------------------------------------------------------------------------* Name: Stop Description: シーケンスを止めます Arguments: playerNo - プレイヤー番号 Returns: None. *---------------------------------------------------------------------------*/ void SequenceSoundPlayer::Stop() { FinishPlayer(); } /*---------------------------------------------------------------------------* Name: Pause Description: シーケンスを一時停止または再開します Arguments: playerNo - プレイヤー番号 flag - trueで一時停止、falseで再開します Returns: None. *---------------------------------------------------------------------------*/ void SequenceSoundPlayer::Pause( bool flag ) { m_PauseFlag = flag; // 全トラックのチャンネルを停止 SequenceTrack* track; for( int trackNo = 0; trackNo < TRACK_NUM_PER_PLAYER ; trackNo++ ) { track = GetPlayerTrack( trackNo ); if ( track == NULL ) continue; track->PauseAllChannel( flag ); } } /*---------------------------------------------------------------------------* Name: Skip Description: シーケンスをスキップします 注意:サウンドフレームを生成するタイマーを一時的に止めています Arguments: playerNo - プレイヤー番号 tick - スキップ量をティックで指定します Returns: None. *---------------------------------------------------------------------------*/ void SequenceSoundPlayer::Skip( OffsetType offsetType, int offset ) { if ( !m_ActiveFlag ) { return; } switch ( offsetType ) { case OFFSET_TYPE_TICK: m_SkipTickCounter += offset; break; case OFFSET_TYPE_MILLISEC: m_SkipTimeCounter += static_cast( offset ); break; } } void SequenceSoundPlayer::SetTempoRatio( float tempoRatio ) { NW_ASSERT( tempoRatio >= 0.0f ); m_TempoRatio = tempoRatio; } void SequenceSoundPlayer::SetPanRange( float panRange ) { m_PanRange = panRange; } void SequenceSoundPlayer::SetChannelPriority( int priority ) { NW_MINMAX_ASSERT( priority, 0, 127 ); m_ParserParam.priority = static_cast( priority ); } void SequenceSoundPlayer::SetReleasePriorityFix( bool fix ) { m_ReleasePriorityFixFlag = fix; } void SequenceSoundPlayer::SetSequenceUserprocCallback( SequenceUserprocCallback callback, void* arg ) { m_SequenceUserprocCallback = callback; m_pSequenceUserprocCallbackArg = arg; } void SequenceSoundPlayer::CallSequenceUserprocCallback( u16 procId, SequenceTrack* track ) { if ( m_SequenceUserprocCallback == NULL ) { return; } NW_NULL_ASSERT( track ); SequenceTrack::ParserTrackParam& trackParam = track->GetParserTrackParam(); SequenceUserprocCallbackParam param; param.localVariable = GetVariablePtr( 0 ); param.globalVariable = GetVariablePtr( 16 ); param.trackVariable = track->GetVariablePtr( 0 ); param.cmpFlag = trackParam.cmpFlag; m_SequenceUserprocCallback( procId, ¶m, m_pSequenceUserprocCallbackArg ); trackParam.cmpFlag = param.cmpFlag; } /*---------------------------------------------------------------------------* Name: GetLocalVariable Description: シーケンスローカル変数を取得します。 Arguments: player - プレイヤー varNo - 変数番号 Returns: 変数の値 *---------------------------------------------------------------------------*/ s16 SequenceSoundPlayer::GetLocalVariable( int varNo ) const { NW_MINMAXLT_ASSERT( varNo, 0, PLAYER_VARIABLE_NUM ); return m_LocalVariable[ varNo ]; } /*---------------------------------------------------------------------------* Name: GetGlobalVariable Description: シーケンスグローバル変数を取得します。 Arguments: varNo - 変数番号 Returns: 変数の値 *---------------------------------------------------------------------------*/ s16 SequenceSoundPlayer::GetGlobalVariable( int varNo ) { NW_MINMAXLT_ASSERT( varNo, 0, GLOBAL_VARIABLE_NUM ); return m_GlobalVariable[ varNo ]; } /*---------------------------------------------------------------------------* Name: SetLocalVariable Description: シーケンスローカル変数を設定します。 Arguments: player - プレイヤー varNo - 変数番号 var - 変数値 Returns: 変数の値 *---------------------------------------------------------------------------*/ void SequenceSoundPlayer::SetLocalVariable( int varNo, s16 var ) { NW_MINMAXLT_ASSERT( varNo, 0, PLAYER_VARIABLE_NUM ); m_LocalVariable[ varNo ] = var; } /*---------------------------------------------------------------------------* Name: SetGlobalVariable Description: シーケンスグローバル変数を設定します。 Arguments: varNo - 変数番号 var - 変数値 Returns: 変数の値 *---------------------------------------------------------------------------*/ void SequenceSoundPlayer::SetGlobalVariable( int varNo, s16 var ) { NW_MINMAXLT_ASSERT( varNo, 0, GLOBAL_VARIABLE_NUM ); m_GlobalVariable[ varNo ] = var; } /*---------------------------------------------------------------------------* Name: SetTrackMute Description: トラックをミュートまたはミュート解除します Arguments: trackBitFlag - ミュート設定するトラックのビットマスク flag - trueでミュート、falseでミュート解除します Returns: None. *---------------------------------------------------------------------------*/ void SequenceSoundPlayer::SetTrackMute( u32 trackBitFlag, SeqMute mute ) { SetTrackParam( trackBitFlag, &SequenceTrack::SetMute, mute ); } void SequenceSoundPlayer::SetTrackSilence( unsigned long trackBitFlag, bool silenceFlag, int fadeTimes ) { SetTrackParam( trackBitFlag, &SequenceTrack::SetSilence, silenceFlag, fadeTimes ); } void SequenceSoundPlayer::SetTrackVolume( u32 trackBitFlag, float volume ) { NW_ASSERT( volume >= 0.0f ); SetTrackParam( trackBitFlag, &SequenceTrack::SetVolume, volume ); } void SequenceSoundPlayer::SetTrackPitch( u32 trackBitFlag, float pitch ) { NW_ASSERT( pitch >= 0.0f ); SetTrackParam( trackBitFlag, &SequenceTrack::SetPitch, pitch ); } void SequenceSoundPlayer::SetTrackPan( u32 trackBitFlag, float pan ) { SetTrackParam( trackBitFlag, &SequenceTrack::SetPan, pan ); } void SequenceSoundPlayer::SetTrackSurroundPan( u32 trackBitFlag, float surroundPan ) { SetTrackParam( trackBitFlag, &SequenceTrack::SetSurroundPan, surroundPan ); } void SequenceSoundPlayer::SetTrackLpfFreq( u32 trackBitFlag, float lpfFreq ) { SetTrackParam( trackBitFlag, &SequenceTrack::SetLpfFreq, lpfFreq ); } void SequenceSoundPlayer::SetTrackBiquadFilter( u32 trackBitFlag, int type, float value ) { SetTrackParam( trackBitFlag, &SequenceTrack::SetBiquadFilter, type, value ); } void SequenceSoundPlayer::SetTrackPanRange( u32 trackBitFlag, float panRange ) { SetTrackParam( trackBitFlag, &SequenceTrack::SetPanRange, panRange ); } void SequenceSoundPlayer::SetTrackModDepth( u32 trackBitFlag, float depth ) { SetTrackParam( trackBitFlag, &SequenceTrack::SetModDepth, depth ); } void SequenceSoundPlayer::SetTrackModSpeed( u32 trackBitFlag, float speed ) { SetTrackParam( trackBitFlag, &SequenceTrack::SetModSpeed, speed ); } bool SequenceSoundPlayer::SetTrackBankIndex( u32 trackBitFlag, int bankIndex ) { // SequenceSoundHandle::SetTrackBankIndex でも範囲チェックしているが、念のため。 NW_MINMAXLT_ASSERT( bankIndex, 0, SoundArchive::SEQ_BANK_MAX ); if ( m_pBankFiles[ bankIndex ] == NULL ) { return false; } SetTrackParam( trackBitFlag, &SequenceTrack::SetBankIndex, bankIndex ); return true; } /*---------------------------------------------------------------------------* Name: InvalidateData Description: 指定シーケンスデータを使用しているシーケンスを無効化します Arguments: start - シーケンスデータ開始アドレス end - シーケンスデータ終了アドレス Returns: None. *---------------------------------------------------------------------------*/ void SequenceSoundPlayer::InvalidateData( const void* start, const void* end ) { if ( m_ActiveFlag ) { for( int trackNo = 0; trackNo < TRACK_NUM_PER_PLAYER ; trackNo++ ) { SequenceTrack* track = GetPlayerTrack( trackNo ); if ( track == NULL ) continue; const u8* cur = track->GetParserTrackParam().baseAddr; if ( start <= cur && cur <= end ) { Finalize(); break; } } for ( int i = 0; i < SoundArchive::SEQ_BANK_MAX; i++ ) { const void* cur = m_pBankFiles[ i ]; if ( start <= cur && cur <= end ) { m_pBankFiles[ i ] = NULL; } } } } /*---------------------------------------------------------------------------* Name: GetPlayerTrack Description: プレイヤーが保持しているトラックを取得します Arguments: trackNo - トラック番号 Returns: トラックポインタ *---------------------------------------------------------------------------*/ SequenceTrack* SequenceSoundPlayer::GetPlayerTrack( int trackNo ) { if ( trackNo > TRACK_NUM_PER_PLAYER - 1 ) { return NULL; } return m_pTracks[ trackNo ]; } /*---------------------------------------------------------------------------* Name: GetPlayerTrack Description: プレイヤーが保持しているトラックを取得します Arguments: trackNo - トラック番号 Returns: トラックポインタ *---------------------------------------------------------------------------*/ const SequenceTrack* SequenceSoundPlayer::GetPlayerTrack( int trackNo ) const { if ( trackNo > TRACK_NUM_PER_PLAYER - 1 ) { return NULL; } return m_pTracks[ trackNo ]; } /*---------------------------------------------------------------------------* Name: CloseTrack Description: プレイヤーのトラックを閉じます。 トラックシーケンス完了時、シーケンス2重オープン時、 プレイヤーの完了時に呼びだされます。 Arguments: player : プレイヤー Returns: None *---------------------------------------------------------------------------*/ void SequenceSoundPlayer::CloseTrack( int trackNo ) { NW_MINMAXLT_ASSERT( trackNo, 0, TRACK_NUM_PER_PLAYER ); SequenceTrack* track = GetPlayerTrack( trackNo ); if ( track == NULL ) { return; } track->Close(); m_pSequenceTrackAllocator->FreeTrack( m_pTracks[ trackNo ] ); // NN_LOG("FreeTrack %08x\n",m_pTracks[trackNo]); m_pTracks[ trackNo ] = NULL; } /*---------------------------------------------------------------------------* Name: SetPlayerTrack Description: プレイヤーが管理するトラックを追加します Arguments: trackNo - トラック番号 trackID - トラックID Returns: なし *---------------------------------------------------------------------------*/ void SequenceSoundPlayer::SetPlayerTrack( int trackNo, SequenceTrack* track ) { if ( trackNo > TRACK_NUM_PER_PLAYER - 1 ) { return; } m_pTracks[ trackNo ] = track; track->SetPlayerTrackNo( trackNo ); // トラックパラメータのフロントバイパスフラグに「サウンド」のフロントバイパスフラグを反映 // (その後、frontbypass_on/_off コマンドで上書きされ得ます) track->GetParserTrackParam().frontBypassFlag = IsFrontBypass(); } /*---------------------------------------------------------------------------* Name: FinishPlayer Description: プレイヤーの完了処理を行います。 Arguments: player : プレイヤーポインタ Returns: None *---------------------------------------------------------------------------*/ void SequenceSoundPlayer::FinishPlayer() { // プレイヤーリストから削除 if ( m_StartedFlag ) { SoundThread::GetInstance().UnregisterPlayerCallback( this ); m_StartedFlag = false; } // 全トラック解放 for( int trackNo = 0; trackNo < TRACK_NUM_PER_PLAYER ; trackNo++ ) { CloseTrack( trackNo ); } } /*---------------------------------------------------------------------------* Name: UpdateChannelParam Description: プレイヤーの全トラックのチャンネルパラメータを更新します Arguments: player - プレイヤーポインタ Returns: None. *---------------------------------------------------------------------------*/ void SequenceSoundPlayer::UpdateChannelParam() { SequenceTrack* track; for( int trackNo = 0; trackNo < TRACK_NUM_PER_PLAYER ; trackNo++ ) { track = GetPlayerTrack( trackNo ); if ( track == NULL ) continue; track->UpdateChannelParam(); } } /*---------------------------------------------------------------------------* Name: ParseNextTick Description: プレイヤーのシーケンス処理を行います Arguments: player - プレイヤーポインタ doNoteOn - ノートオンするかどうか Returns: 継続時には0を、完了時には1を返します *---------------------------------------------------------------------------*/ int SequenceSoundPlayer::ParseNextTick( bool doNoteOn ) { bool activeFlag = false; for( int trackNo = 0; trackNo < TRACK_NUM_PER_PLAYER ; trackNo++ ) { SequenceTrack* track = GetPlayerTrack( trackNo ); if ( track == NULL ) { continue; } track->UpdateChannelLength(); if ( track->ParseNextTick( doNoteOn ) < 0 ) { // トラックのシーケンス終了 CloseTrack( trackNo ); } if ( track->IsOpened() ) { activeFlag = true; } } if ( ! activeFlag ) { return 1; } return 0; } /*---------------------------------------------------------------------------* Name: GetVariablePtr Description: 変数のポインタを取得します Arguments: player - プレイヤーポインタ varNo - 変数番号 Returns: 変数のポインタ *---------------------------------------------------------------------------*/ vs16* SequenceSoundPlayer::GetVariablePtr( int varNo ) { NW_MINMAX_ASSERT( varNo, 0, PLAYER_VARIABLE_NUM + GLOBAL_VARIABLE_NUM ); if ( varNo < PLAYER_VARIABLE_NUM ) { return & m_LocalVariable[ varNo ] ; } else if ( varNo < PLAYER_VARIABLE_NUM + GLOBAL_VARIABLE_NUM ) { return & m_GlobalVariable[ varNo - PLAYER_VARIABLE_NUM ]; } else { return NULL; } } /*---------------------------------------------------------------------------* Name: Update Description: プレイヤーのフレーム処理を行います Arguments: None. Returns: None. *---------------------------------------------------------------------------*/ void SequenceSoundPlayer::Update() { if ( ! m_ActiveFlag ) { return; } if ( ! m_StartedFlag ) { return; } if ( ( m_SkipTickCounter > 0 ) || ( m_SkipTimeCounter > 0.0f ) ) { SkipTick(); } else if ( ! m_PauseFlag ) { UpdateTick(); } UpdateChannelParam(); } /*---------------------------------------------------------------------------* Name: UpdateTick Description: プレイヤーのティック処理を行います Returns: None. *---------------------------------------------------------------------------*/ void SequenceSoundPlayer::UpdateTick() { f32 tickPerMsec = CalcTickPerMsec(); if ( tickPerMsec == 0.0f ) { return; } u64 restMsec = INTERVAL_MSEC_NUMERATOR; u64 nextMsec = static_cast(INTERVAL_MSEC_DENOMINATOR * m_TickFraction / tickPerMsec); while ( nextMsec < restMsec ) { restMsec -= nextMsec; bool result = ( ParseNextTick( true ) != 0 ); if ( result ) { FinishPlayer(); m_FinishFlag = true; return; } ++m_TickCounter; tickPerMsec = CalcTickPerMsec(); if ( tickPerMsec == 0.0f ) { return; } nextMsec = static_cast(INTERVAL_MSEC_DENOMINATOR / tickPerMsec); } nextMsec -= restMsec; m_TickFraction = nextMsec * tickPerMsec / INTERVAL_MSEC_DENOMINATOR; } /*---------------------------------------------------------------------------* Name: SkipTick Description: ティックのスキップ処理を行います Arguments: None. Returns: None. *---------------------------------------------------------------------------*/ void SequenceSoundPlayer::SkipTick() { // 全トラックの全チャンネルをリリース for( int trackNo = 0; trackNo < TRACK_NUM_PER_PLAYER ; trackNo++ ) { SequenceTrack* track = GetPlayerTrack( trackNo ); if ( track == NULL ) continue; track->ReleaseAllChannel( SequenceTrack::PAUSE_RELEASE_VALUE ); track->FreeAllChannel(); } // スキップ処理 int skipCount = 0; while ( m_SkipTickCounter > 0 || m_SkipTimeCounter * CalcTickPerMsec() >= 1.0f ) { if ( skipCount >= MAX_SKIP_TICK_PER_FRAME ) { return; } if ( m_SkipTickCounter > 0 ) { --m_SkipTickCounter; } else { f32 tickPerMsec = CalcTickPerMsec(); NW_ASSERT( tickPerMsec > 0.0f ); // 0.0fの時、スキップは行われない f32 msecPerTick = 1.0f / tickPerMsec; m_SkipTimeCounter -= msecPerTick; } if ( ParseNextTick( false ) != 0 ) { FinishPlayer(); m_FinishFlag = true; return; } ++skipCount; ++m_TickCounter; } m_SkipTimeCounter = 0.0f; } Channel* SequenceSoundPlayer::NoteOn( u8 bankIndex, const NoteOnInfo& noteOnInfo ) { Channel* channel = m_ParserParam.callback->NoteOn( this, bankIndex, noteOnInfo ); return channel; } } // namespace nw::snd::internal::driver } // namespace nw::snd::internal } // namespace nw::snd } // namespace nw