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: }