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@5:
f@0: #include "AudioEngine.h"
f@16: // app.h include not used
f@0: #include "cinder/app/App.h"
f@0: #include "Log.h"
f@0:
f@0: using namespace ci::audio;
f@0:
f@2: /* Frequency ratios in the chromatic scale */
f@0: double chromaticRatios[] = {
f@0: 1,
f@0: 1.0594630943591,
f@0: 1.1224620483089,
f@0: 1.1892071150019,
f@0: 1.2599210498937,
f@0: 1.3348398541685,
f@0: 1.4142135623711,
f@0: 1.4983070768743,
f@0: 1.5874010519653,
f@0: 1.6817928305039,
f@0: 1.7817974362766,
f@0: 1.8877486253586
f@0: };
f@0:
f@2:
f@2: /*
f@2: * Calculates the ratio between the frequency of the midi note passed as argument and middle C note ( MIDI value = 60 ).
f@2: * This is used for pitch shifting the granular synth output, according to the key pressed by the user.
f@2: * The middle C is taken as reference in pitch in the pitch shifting of Collidoscope output.
f@2: * That is, with the middle C the output is not pitch shifted at all and is equal in frequency to the recorder sample.
f@2: *
f@2: */
f@0: inline double calculateMidiNoteRatio( int midiNote )
f@0: {
f@0: int distanceFromCenter = midiNote - 60; // 60 is the central midi note
f@0:
f@0: if ( distanceFromCenter < 0 ){
f@0: int diffAmount = -distanceFromCenter;
f@0: int octaves = diffAmount / 12;
f@0: int intervals = diffAmount % 12;
f@0:
f@0: return std::pow( 0.5, octaves ) / chromaticRatios[intervals];
f@0: }
f@0: else{
f@0: int octaves = distanceFromCenter / 12;
f@0: int intervals = distanceFromCenter % 12;
f@0:
f@0: return std::pow( 2, octaves ) * chromaticRatios[intervals];
f@0: }
f@0: }
f@0:
f@0:
f@0: AudioEngine::AudioEngine()
f@0: {}
f@0:
f@0: AudioEngine::~AudioEngine()
f@0: {}
f@0:
f@0: void AudioEngine::setup(const Config& config)
f@0: {
f@0:
f@0: for ( int i = 0; i < NUM_WAVES; i++ ){
f@0: mCursorTriggerRingBufferPacks[i].reset( new RingBufferPack( 512 ) ); // FIXME
f@0: }
f@0:
f@0: /* audio context */
f@0: auto ctx = Context::master();
f@0:
f@16: /* audio input device */
f@0: auto inputDeviceNode = ctx->createInputDeviceNode( Device::getDefaultInput() );
f@0:
f@0:
f@0: /* route the audio input, which is two channels, to one wave graph for each channel */
f@0: for ( int chan = 0; chan < NUM_WAVES; chan++ ){
f@0:
f@0: /* one channel router */
f@0: mInputRouterNodes[chan] = ctx->makeNode( new ChannelRouterNode( Node::Format().channels( 1 ) ) );
f@0:
f@0: /* buffer recorders */
f@0: mBufferRecorderNodes[chan] = ctx->makeNode( new BufferToWaveRecorderNode( config.getNumChunks(), config.getWaveLen() ) );
f@0: /* this prevents the node from recording before record is pressed */
f@0: mBufferRecorderNodes[chan]->setAutoEnabled( false );
f@0:
f@16: // route the input part of the audio graph. Two channels input goes into one channel route
f@16: // and from one channel route to one channel buffer recorder
f@0: inputDeviceNode >> mInputRouterNodes[chan]->route( chan, 0, 1 ) >> mBufferRecorderNodes[chan];
f@0:
f@0:
f@0: // create PGranular loops passing the buffer of the RecorderNode as argument to the contructor
f@16: // use -1 as ID as the loop corresponds to no midi note
f@0: mPGranularNodes[chan] = ctx->makeNode( new PGranularNode( mBufferRecorderNodes[chan]->getRecorderBuffer(), mCursorTriggerRingBufferPacks[chan]->getBuffer() ) );
f@0:
f@0: // create filter nodes
f@0: mLowPassFilterNodes[chan] = ctx->makeNode( new FilterLowPassNode( MonitorNode::Format().channels( 1 ) ) );
f@0: mLowPassFilterNodes[chan]->setCutoffFreq( config.getMaxFilterCutoffFreq() );
f@0: mLowPassFilterNodes[chan]->setQ( 0.707f );
f@0: // create monitor nodes for oscilloscopes
f@0: mOutputMonitorNodes[chan] = ctx->makeNode( new MonitorNode( MonitorNode::Format().channels( 1 ) ) );
f@0:
f@0: // all output goes to the filter
f@0: mPGranularNodes[chan] >> mLowPassFilterNodes[chan];
f@0:
f@0: mOutputRouterNodes[chan] = ctx->makeNode( new ChannelRouterNode( Node::Format().channels( 2 ) ) );
f@0:
f@0: // filter goes to output
f@0: mLowPassFilterNodes[chan] >> mOutputRouterNodes[chan]->route( 0, chan, 1 ) >> ctx->getOutput();
f@0:
f@16: // what goes to output goes to oscilloscope as well
f@0: mLowPassFilterNodes[chan] >> mOutputMonitorNodes[chan];
f@0:
f@0: }
f@0:
f@0: ctx->getOutput()->enableClipDetection( false );
f@0: /* enable the whole audio graph */
f@0: inputDeviceNode->enable();
f@0: ctx->enable();
f@0: }
f@0:
f@0: size_t AudioEngine::getSampleRate()
f@0: {
f@0: return Context::master()->getSampleRate();
f@0: }
f@0:
f@0: void AudioEngine::loopOn( size_t waveIdx )
f@0: {
f@0: NoteMsg msg = makeNoteMsg( Command::LOOP_ON, 1, 1.0 );
f@0: mPGranularNodes[waveIdx]->getNoteRingBuffer().write( &msg, 1 );
f@0: }
f@0:
f@0: void AudioEngine::loopOff( size_t waveIdx )
f@0: {
f@0: NoteMsg msg = makeNoteMsg( Command::LOOP_OFF, 0, 0.0 );
f@0: mPGranularNodes[waveIdx]->getNoteRingBuffer().write( &msg, 1 );
f@0: }
f@0:
f@0: void AudioEngine::record( size_t waveIdx )
f@0: {
f@0: mBufferRecorderNodes[waveIdx]->start();
f@0: }
f@0:
f@0: void AudioEngine::noteOn( size_t waveIdx, int midiNote )
f@0: {
f@0:
f@0: double midiAsRate = calculateMidiNoteRatio(midiNote);
f@0: NoteMsg msg = makeNoteMsg( Command::NOTE_ON, midiNote, midiAsRate );
f@0:
f@0: mPGranularNodes[waveIdx]->getNoteRingBuffer().write( &msg, 1 );
f@0: }
f@0:
f@0: void AudioEngine::noteOff( size_t waveIdx, int midiNote )
f@0: {
f@0: NoteMsg msg = makeNoteMsg( Command::NOTE_OFF, midiNote, 0.0 );
f@0: mPGranularNodes[waveIdx]->getNoteRingBuffer().write( &msg, 1 );
f@0: }
f@0:
f@0:
f@0:
f@0: void AudioEngine::setSelectionSize( size_t waveIdx, size_t size )
f@0: {
f@0: mPGranularNodes[waveIdx]->setSelectionSize( size );
f@0: }
f@0:
f@0: void AudioEngine::setSelectionStart( size_t waveIdx, size_t start )
f@0: {
f@0: mPGranularNodes[waveIdx]->setSelectionStart( start );
f@0: }
f@0:
f@0: void AudioEngine::setGrainDurationCoeff( size_t waveIdx, double coeff )
f@0: {
f@0: mPGranularNodes[waveIdx]->setGrainsDurationCoeff( coeff );
f@0: }
f@0:
f@0: void AudioEngine::setFilterCutoff( size_t waveIdx, double cutoff )
f@0: {
f@0: mLowPassFilterNodes[waveIdx]->setCutoffFreq( cutoff );
f@0: }
f@0:
f@0: // ------------------------------------------------------
f@0: // ----- methods for communication with main thread -----
f@0: // ------------------------------------------------------
f@0:
f@0: size_t AudioEngine::getRecordWaveAvailable( size_t waveIdx )
f@0: {
f@0: return mBufferRecorderNodes[waveIdx]->getRingBuffer().getAvailableRead();
f@0: }
f@0:
f@2:
f@0: bool AudioEngine::readRecordWave( size_t waveIdx, RecordWaveMsg* buffer, size_t count )
f@0: {
f@0: return mBufferRecorderNodes[waveIdx]->getRingBuffer().read( buffer, count );
f@0: }
f@0:
f@0: void AudioEngine::checkCursorTriggers( size_t waveIdx, std::vector& cursorTriggers )
f@0: {
f@0: ci::audio::dsp::RingBufferT &ringBuffer = mCursorTriggerRingBufferPacks[waveIdx]->getBuffer();
f@0: CursorTriggerMsg* ringBufferReadArray = mCursorTriggerRingBufferPacks[waveIdx]->getExchangeArray();
f@0:
f@0: size_t availableRead = ringBuffer.getAvailableRead();
f@0: bool successfulRead = ringBuffer.read( ringBufferReadArray, availableRead );
f@0:
f@0: if ( successfulRead ){
f@0: for ( size_t i = 0; i < availableRead; i++ ){
f@0: cursorTriggers.push_back( ringBufferReadArray[i] );
f@0: }
f@0: }
f@0: }
f@0:
f@0: const ci::audio::Buffer& AudioEngine::getAudioOutputBuffer( size_t waveIdx ) const
f@0: {
f@0: return mOutputMonitorNodes[waveIdx]->getBuffer();
f@0: }
f@0: