f@5: /* f@5: f@5: Copyright (C) 2016 Queen Mary University of London f@5: Author: Fiore Martin f@5: f@5: This file is part of Collidoscope. f@5: f@5: Collidoscope is free software: you can redistribute it and/or modify f@5: it under the terms of the GNU General Public License as published by f@5: the Free Software Foundation, either version 3 of the License, or f@5: (at your option) any later version. f@5: f@5: This program is distributed in the hope that it will be useful, f@5: but WITHOUT ANY WARRANTY; without even the implied warranty of f@5: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the f@5: GNU General Public License for more details. f@5: f@5: You should have received a copy of the GNU General Public License f@5: along with this program. If not, see . f@5: */ f@5: f@0: #include "PGranularNode.h" f@0: f@0: #include "cinder/audio/Context.h" f@0: f@0: #include "cinder/Rand.h" f@0: f@0: // generate random numbers from 0 to max f@0: // it's passed to PGranular to randomize the phase offset at grain creation f@0: struct RandomGenerator f@0: { f@0: f@0: RandomGenerator( size_t max ) : mMax( max ) f@0: {} f@0: f@0: size_t operator()() const { f@0: return ci::Rand::randUint( mMax ); f@0: } f@0: f@0: size_t mMax; f@0: }; f@0: // FIXME maybe use only one random gen f@0: f@0: PGranularNode::PGranularNode( ci::audio::Buffer *grainBuffer, CursorTriggerMsgRingBuffer &triggerRingBuffer ) : f@0: Node( Format().channels( 1 ) ), f@0: mGrainBuffer(grainBuffer), f@0: mSelectionStart( 0 ), f@0: mSelectionSize( 0 ), f@0: mGrainDurationCoeff( 1 ), f@0: mTriggerRingBuffer( triggerRingBuffer ), f@0: mNoteMsgRingBufferPack( 128 ) f@0: { f@0: for ( int i = 0; i < kMaxVoices; i++ ){ f@0: mMidiNotes[i] = kNoMidiNote; f@0: f@0: } f@0: } f@0: f@0: f@0: PGranularNode::~PGranularNode() f@0: { f@0: } f@0: f@0: void PGranularNode::initialize() f@0: { f@0: mTempBuffer = std::make_shared< ci::audio::Buffer >( getFramesPerBlock() ); f@0: f@16: mRandomOffset.reset( new RandomGenerator( getSampleRate() / 100 ) ); // divided by 100 corresponds to multiplied by 0.01 in the time domain f@0: f@0: /* create the PGranular object for looping */ f@0: mPGranularLoop.reset( new collidoscope::PGranular( mGrainBuffer->getData(), mGrainBuffer->getNumFrames(), getSampleRate(), *mRandomOffset, *this, -1 ) ); f@0: f@0: /* create the PGranular object for notes */ f@0: for ( size_t i = 0; i < kMaxVoices; i++ ){ f@0: mPGranularNotes[i].reset( new collidoscope::PGranular( mGrainBuffer->getData(), mGrainBuffer->getNumFrames(), getSampleRate(), *mRandomOffset, *this, i ) ); f@0: } f@0: f@0: } f@0: f@0: void PGranularNode::process (ci::audio::Buffer *buffer ) f@0: { f@0: // only update PGranular if the atomic value has changed from the previous time f@0: const boost::optional selectionSize = mSelectionSize.get(); f@0: if ( selectionSize ){ f@0: mPGranularLoop->setSelectionSize( *selectionSize ); f@0: for ( size_t i = 0; i < kMaxVoices; i++ ){ f@0: mPGranularNotes[i]->setSelectionSize( *selectionSize ); f@0: } f@0: } f@0: f@0: const boost::optional selectionStart = mSelectionStart.get(); f@0: if ( selectionStart ){ f@0: mPGranularLoop->setSelectionStart( *selectionStart ); f@0: for ( size_t i = 0; i < kMaxVoices; i++ ){ f@0: mPGranularNotes[i]->setSelectionStart( *selectionStart ); f@0: } f@0: } f@0: f@0: const boost::optional grainDurationCoeff = mGrainDurationCoeff.get(); f@0: if ( grainDurationCoeff ){ f@0: mPGranularLoop->setGrainsDurationCoeff( *grainDurationCoeff ); f@0: for ( size_t i = 0; i < kMaxVoices; i++ ){ f@0: mPGranularNotes[i]->setGrainsDurationCoeff( *grainDurationCoeff ); f@0: } f@0: } f@0: f@0: // check messages to start/stop notes or loop f@0: size_t availableRead = mNoteMsgRingBufferPack.getBuffer().getAvailableRead(); f@0: mNoteMsgRingBufferPack.getBuffer().read( mNoteMsgRingBufferPack.getExchangeArray(), availableRead ); f@0: for ( size_t i = 0; i < availableRead; i++ ){ f@0: handleNoteMsg( mNoteMsgRingBufferPack.getExchangeArray()[i] ); f@0: } f@0: f@0: // process loop if not idle f@0: if ( !mPGranularLoop->isIdle() ){ f@0: /* buffer is one channel only so I can use getData */ f@0: mPGranularLoop->process( buffer->getData(), mTempBuffer->getData(), buffer->getSize() ); f@0: } f@0: f@0: // process notes if not idle f@0: for ( size_t i = 0; i < kMaxVoices; i++ ){ f@0: if ( mPGranularNotes[i]->isIdle() ) f@0: continue; f@0: f@0: mPGranularNotes[i]->process( buffer->getData(), mTempBuffer->getData(), buffer->getSize() ); f@0: f@0: if ( mPGranularNotes[i]->isIdle() ){ f@0: // this note became idle so update mMidiNotes f@0: mMidiNotes[i] = kNoMidiNote; f@0: } f@0: f@0: } f@0: } f@0: f@16: // Called back when new PGranular is triggered or turned off. Sends notification message to graphic thread. f@0: void PGranularNode::operator()( char msgType, int ID ) { f@0: f@0: switch ( msgType ){ f@0: case 't': { // trigger f@0: CursorTriggerMsg msg = makeCursorTriggerMsg( Command::TRIGGER_UPDATE, ID ); // put ID f@0: mTriggerRingBuffer.write( &msg, 1 ); f@0: }; f@0: break; f@0: f@0: case 'e': // end envelope f@0: CursorTriggerMsg msg = makeCursorTriggerMsg( Command::TRIGGER_END, ID ); // put ID f@0: mTriggerRingBuffer.write( &msg, 1 ); f@0: break; f@0: } f@0: f@0: f@0: } f@0: f@0: void PGranularNode::handleNoteMsg( const NoteMsg &msg ) f@0: { f@0: switch ( msg.cmd ){ f@0: case Command::NOTE_ON: { f@0: bool synthFound = false; f@0: f@0: for ( int i = 0; i < kMaxVoices; i++ ){ f@0: // note was already on, so re-attack f@0: if ( mMidiNotes[i] == msg.midiNote ){ f@0: mPGranularNotes[i]->noteOn( msg.rate ); f@0: synthFound = true; f@0: break; f@0: } f@0: } f@0: f@0: if ( !synthFound ){ f@16: // then look for a free voice f@0: for ( int i = 0; i < kMaxVoices; i++ ){ f@0: f@0: if ( mMidiNotes[i] == kNoMidiNote ){ f@0: mPGranularNotes[i]->noteOn( msg.rate ); f@0: mMidiNotes[i] = msg.midiNote; f@0: synthFound = true; f@0: break; f@0: } f@0: } f@0: } f@0: }; f@0: break; f@0: f@0: case Command::NOTE_OFF: { f@0: for ( int i = 0; i < kMaxVoices; i++ ){ f@0: if ( !mPGranularNotes[i]->isIdle() && mMidiNotes[i] == msg.midiNote ){ f@0: mPGranularNotes[i]->noteOff(); f@0: break; f@0: } f@0: } f@0: }; f@0: break; f@0: f@0: case Command::LOOP_ON: { f@0: mPGranularLoop->noteOn( 1.0 ); f@0: }; f@0: break; f@0: f@0: case Command::LOOP_OFF: { f@0: mPGranularLoop->noteOff(); f@0: }; f@0: break; f@0: default: f@0: break; f@0: } f@0: }