view CollidoscopeApp/src/PGranularNode.cpp @ 16:4dad0b810f18

Comment tidy up + attributions
author Fiore Martin <f.martin@qmul.ac.uk>
date Tue, 16 Aug 2016 14:27:53 +0100
parents 75b744078d66
children
line wrap: on
line source
/*

 Copyright (C) 2016  Queen Mary University of London 
 Author: Fiore Martin

 This file is part of Collidoscope.
 
 Collidoscope is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 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 for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "PGranularNode.h"

#include "cinder/audio/Context.h"

#include "cinder/Rand.h"

// generate random numbers from 0 to max 
// it's passed to PGranular to randomize the phase offset at grain creation 
struct RandomGenerator
{

    RandomGenerator( size_t max ) : mMax( max )
    {}

    size_t operator()() const {
        return ci::Rand::randUint( mMax );
    }

    size_t mMax;
};
// FIXME maybe use only one random gen 

PGranularNode::PGranularNode( ci::audio::Buffer *grainBuffer, CursorTriggerMsgRingBuffer &triggerRingBuffer ) :
    Node( Format().channels( 1 ) ),
    mGrainBuffer(grainBuffer),
    mSelectionStart( 0 ),
    mSelectionSize( 0 ),
    mGrainDurationCoeff( 1 ),
    mTriggerRingBuffer( triggerRingBuffer ),
    mNoteMsgRingBufferPack( 128 )
{
    for ( int i = 0; i < kMaxVoices; i++ ){
        mMidiNotes[i] = kNoMidiNote;

    }
}


PGranularNode::~PGranularNode()
{
}

void PGranularNode::initialize()
{
    mTempBuffer = std::make_shared< ci::audio::Buffer >( getFramesPerBlock() );

    mRandomOffset.reset( new RandomGenerator( getSampleRate() / 100 ) ); // divided by 100 corresponds to multiplied by 0.01 in the time domain 

    /* create the PGranular object for looping */
    mPGranularLoop.reset( new collidoscope::PGranular<float, RandomGenerator, PGranularNode>( mGrainBuffer->getData(), mGrainBuffer->getNumFrames(), getSampleRate(), *mRandomOffset, *this, -1 ) );

    /* create the PGranular object for notes */
    for ( size_t i = 0; i < kMaxVoices; i++ ){
        mPGranularNotes[i].reset( new collidoscope::PGranular<float, RandomGenerator, PGranularNode>( mGrainBuffer->getData(), mGrainBuffer->getNumFrames(), getSampleRate(), *mRandomOffset, *this, i ) );
    }

}

void PGranularNode::process (ci::audio::Buffer *buffer )
{
    // only update PGranular if the atomic value has changed from the previous time
    const boost::optional<size_t> selectionSize = mSelectionSize.get();
    if ( selectionSize ){
        mPGranularLoop->setSelectionSize( *selectionSize );
        for ( size_t i = 0; i < kMaxVoices; i++ ){
            mPGranularNotes[i]->setSelectionSize( *selectionSize );
        }
    }

    const boost::optional<size_t> selectionStart = mSelectionStart.get();
    if ( selectionStart ){
        mPGranularLoop->setSelectionStart( *selectionStart );
        for ( size_t i = 0; i < kMaxVoices; i++ ){
            mPGranularNotes[i]->setSelectionStart( *selectionStart );
        }
    }

    const boost::optional<double> grainDurationCoeff = mGrainDurationCoeff.get();
    if ( grainDurationCoeff ){
        mPGranularLoop->setGrainsDurationCoeff( *grainDurationCoeff );
        for ( size_t i = 0; i < kMaxVoices; i++ ){
            mPGranularNotes[i]->setGrainsDurationCoeff( *grainDurationCoeff );
        }
    }

    // check messages to start/stop notes or loop 
    size_t availableRead = mNoteMsgRingBufferPack.getBuffer().getAvailableRead();
    mNoteMsgRingBufferPack.getBuffer().read( mNoteMsgRingBufferPack.getExchangeArray(), availableRead );
    for ( size_t i = 0; i < availableRead; i++ ){
        handleNoteMsg( mNoteMsgRingBufferPack.getExchangeArray()[i] );
    }

    // process loop if not idle 
    if ( !mPGranularLoop->isIdle() ){
        /* buffer is one channel only so I can use getData */
        mPGranularLoop->process( buffer->getData(), mTempBuffer->getData(), buffer->getSize() );
    }

    // process notes if not idle 
    for ( size_t i = 0; i < kMaxVoices; i++ ){
        if ( mPGranularNotes[i]->isIdle() )
            continue;

        mPGranularNotes[i]->process( buffer->getData(), mTempBuffer->getData(), buffer->getSize() );

        if ( mPGranularNotes[i]->isIdle() ){
            // this note became idle so update mMidiNotes
            mMidiNotes[i] = kNoMidiNote;
        }
            
    }
}

// Called back when new PGranular is triggered or turned off. Sends notification message to graphic thread.
void PGranularNode::operator()( char msgType, int ID ) {

    switch ( msgType ){
    case 't':  { // trigger 
        CursorTriggerMsg msg = makeCursorTriggerMsg( Command::TRIGGER_UPDATE, ID ); // put ID 
        mTriggerRingBuffer.write( &msg, 1 );
    };
        break;

    case 'e': // end envelope 
        CursorTriggerMsg msg = makeCursorTriggerMsg( Command::TRIGGER_END, ID ); // put ID 
        mTriggerRingBuffer.write( &msg, 1 );
        break;
    }

    
}

void PGranularNode::handleNoteMsg( const NoteMsg &msg )
{
    switch ( msg.cmd ){
    case Command::NOTE_ON: {
        bool synthFound = false;

        for ( int i = 0; i < kMaxVoices; i++ ){
            // note was already on, so re-attack
            if ( mMidiNotes[i] == msg.midiNote ){
                mPGranularNotes[i]->noteOn( msg.rate );
                synthFound = true;
                break;
            }
        }

        if ( !synthFound ){
            // then look for a free voice 
            for ( int i = 0; i < kMaxVoices; i++ ){

                if ( mMidiNotes[i] == kNoMidiNote ){
                    mPGranularNotes[i]->noteOn( msg.rate );
                    mMidiNotes[i] = msg.midiNote;
                    synthFound = true;
                    break;
                }
            }
        }
    };
        break;

    case Command::NOTE_OFF: {
        for ( int i = 0; i < kMaxVoices; i++ ){
            if ( !mPGranularNotes[i]->isIdle() && mMidiNotes[i] == msg.midiNote ){
                mPGranularNotes[i]->noteOff();
                break;
            }
        }
    };
        break;

    case Command::LOOP_ON: {
        mPGranularLoop->noteOn( 1.0 );
    };
        break;

    case Command::LOOP_OFF: {
        mPGranularLoop->noteOff();
    };
        break;
    default:
        break;
    }
}