annotate CollidoscopeApp/src/AudioEngine.cpp @ 18:f1ff1a81be20 tip

Changed licenses names. Fixed one comment and usage text in CollidoscopeApp.cpp.
author Fiore Martin <f.martin@qmul.ac.uk>
date Thu, 25 Aug 2016 12:07:50 +0200
parents 4dad0b810f18
children
rev   line source
f@5 1 /*
f@5 2
f@5 3 Copyright (C) 2016 Queen Mary University of London
f@5 4 Author: Fiore Martin
f@5 5
f@5 6 This file is part of Collidoscope.
f@5 7
f@5 8 Collidoscope is free software: you can redistribute it and/or modify
f@5 9 it under the terms of the GNU General Public License as published by
f@5 10 the Free Software Foundation, either version 3 of the License, or
f@5 11 (at your option) any later version.
f@5 12
f@5 13 This program is distributed in the hope that it will be useful,
f@5 14 but WITHOUT ANY WARRANTY; without even the implied warranty of
f@5 15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
f@5 16 GNU General Public License for more details.
f@5 17
f@5 18 You should have received a copy of the GNU General Public License
f@5 19 along with this program. If not, see <http://www.gnu.org/licenses/>.
f@5 20
f@5 21 */
f@5 22
f@0 23 #include "AudioEngine.h"
f@16 24 // app.h include not used
f@0 25 #include "cinder/app/App.h"
f@0 26 #include "Log.h"
f@0 27
f@0 28 using namespace ci::audio;
f@0 29
f@2 30 /* Frequency ratios in the chromatic scale */
f@0 31 double chromaticRatios[] = {
f@0 32 1,
f@0 33 1.0594630943591,
f@0 34 1.1224620483089,
f@0 35 1.1892071150019,
f@0 36 1.2599210498937,
f@0 37 1.3348398541685,
f@0 38 1.4142135623711,
f@0 39 1.4983070768743,
f@0 40 1.5874010519653,
f@0 41 1.6817928305039,
f@0 42 1.7817974362766,
f@0 43 1.8877486253586
f@0 44 };
f@0 45
f@2 46
f@2 47 /*
f@2 48 * Calculates the ratio between the frequency of the midi note passed as argument and middle C note ( MIDI value = 60 ).
f@2 49 * This is used for pitch shifting the granular synth output, according to the key pressed by the user.
f@2 50 * The middle C is taken as reference in pitch in the pitch shifting of Collidoscope output.
f@2 51 * 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 52 *
f@2 53 */
f@0 54 inline double calculateMidiNoteRatio( int midiNote )
f@0 55 {
f@0 56 int distanceFromCenter = midiNote - 60; // 60 is the central midi note
f@0 57
f@0 58 if ( distanceFromCenter < 0 ){
f@0 59 int diffAmount = -distanceFromCenter;
f@0 60 int octaves = diffAmount / 12;
f@0 61 int intervals = diffAmount % 12;
f@0 62
f@0 63 return std::pow( 0.5, octaves ) / chromaticRatios[intervals];
f@0 64 }
f@0 65 else{
f@0 66 int octaves = distanceFromCenter / 12;
f@0 67 int intervals = distanceFromCenter % 12;
f@0 68
f@0 69 return std::pow( 2, octaves ) * chromaticRatios[intervals];
f@0 70 }
f@0 71 }
f@0 72
f@0 73
f@0 74 AudioEngine::AudioEngine()
f@0 75 {}
f@0 76
f@0 77 AudioEngine::~AudioEngine()
f@0 78 {}
f@0 79
f@0 80 void AudioEngine::setup(const Config& config)
f@0 81 {
f@0 82
f@0 83 for ( int i = 0; i < NUM_WAVES; i++ ){
f@0 84 mCursorTriggerRingBufferPacks[i].reset( new RingBufferPack<CursorTriggerMsg>( 512 ) ); // FIXME
f@0 85 }
f@0 86
f@0 87 /* audio context */
f@0 88 auto ctx = Context::master();
f@0 89
f@16 90 /* audio input device */
f@0 91 auto inputDeviceNode = ctx->createInputDeviceNode( Device::getDefaultInput() );
f@0 92
f@0 93
f@0 94 /* route the audio input, which is two channels, to one wave graph for each channel */
f@0 95 for ( int chan = 0; chan < NUM_WAVES; chan++ ){
f@0 96
f@0 97 /* one channel router */
f@0 98 mInputRouterNodes[chan] = ctx->makeNode( new ChannelRouterNode( Node::Format().channels( 1 ) ) );
f@0 99
f@0 100 /* buffer recorders */
f@0 101 mBufferRecorderNodes[chan] = ctx->makeNode( new BufferToWaveRecorderNode( config.getNumChunks(), config.getWaveLen() ) );
f@0 102 /* this prevents the node from recording before record is pressed */
f@0 103 mBufferRecorderNodes[chan]->setAutoEnabled( false );
f@0 104
f@16 105 // route the input part of the audio graph. Two channels input goes into one channel route
f@16 106 // and from one channel route to one channel buffer recorder
f@0 107 inputDeviceNode >> mInputRouterNodes[chan]->route( chan, 0, 1 ) >> mBufferRecorderNodes[chan];
f@0 108
f@0 109
f@0 110 // create PGranular loops passing the buffer of the RecorderNode as argument to the contructor
f@16 111 // use -1 as ID as the loop corresponds to no midi note
f@0 112 mPGranularNodes[chan] = ctx->makeNode( new PGranularNode( mBufferRecorderNodes[chan]->getRecorderBuffer(), mCursorTriggerRingBufferPacks[chan]->getBuffer() ) );
f@0 113
f@0 114 // create filter nodes
f@0 115 mLowPassFilterNodes[chan] = ctx->makeNode( new FilterLowPassNode( MonitorNode::Format().channels( 1 ) ) );
f@0 116 mLowPassFilterNodes[chan]->setCutoffFreq( config.getMaxFilterCutoffFreq() );
f@0 117 mLowPassFilterNodes[chan]->setQ( 0.707f );
f@0 118 // create monitor nodes for oscilloscopes
f@0 119 mOutputMonitorNodes[chan] = ctx->makeNode( new MonitorNode( MonitorNode::Format().channels( 1 ) ) );
f@0 120
f@0 121 // all output goes to the filter
f@0 122 mPGranularNodes[chan] >> mLowPassFilterNodes[chan];
f@0 123
f@0 124 mOutputRouterNodes[chan] = ctx->makeNode( new ChannelRouterNode( Node::Format().channels( 2 ) ) );
f@0 125
f@0 126 // filter goes to output
f@0 127 mLowPassFilterNodes[chan] >> mOutputRouterNodes[chan]->route( 0, chan, 1 ) >> ctx->getOutput();
f@0 128
f@16 129 // what goes to output goes to oscilloscope as well
f@0 130 mLowPassFilterNodes[chan] >> mOutputMonitorNodes[chan];
f@0 131
f@0 132 }
f@0 133
f@0 134 ctx->getOutput()->enableClipDetection( false );
f@0 135 /* enable the whole audio graph */
f@0 136 inputDeviceNode->enable();
f@0 137 ctx->enable();
f@0 138 }
f@0 139
f@0 140 size_t AudioEngine::getSampleRate()
f@0 141 {
f@0 142 return Context::master()->getSampleRate();
f@0 143 }
f@0 144
f@0 145 void AudioEngine::loopOn( size_t waveIdx )
f@0 146 {
f@0 147 NoteMsg msg = makeNoteMsg( Command::LOOP_ON, 1, 1.0 );
f@0 148 mPGranularNodes[waveIdx]->getNoteRingBuffer().write( &msg, 1 );
f@0 149 }
f@0 150
f@0 151 void AudioEngine::loopOff( size_t waveIdx )
f@0 152 {
f@0 153 NoteMsg msg = makeNoteMsg( Command::LOOP_OFF, 0, 0.0 );
f@0 154 mPGranularNodes[waveIdx]->getNoteRingBuffer().write( &msg, 1 );
f@0 155 }
f@0 156
f@0 157 void AudioEngine::record( size_t waveIdx )
f@0 158 {
f@0 159 mBufferRecorderNodes[waveIdx]->start();
f@0 160 }
f@0 161
f@0 162 void AudioEngine::noteOn( size_t waveIdx, int midiNote )
f@0 163 {
f@0 164
f@0 165 double midiAsRate = calculateMidiNoteRatio(midiNote);
f@0 166 NoteMsg msg = makeNoteMsg( Command::NOTE_ON, midiNote, midiAsRate );
f@0 167
f@0 168 mPGranularNodes[waveIdx]->getNoteRingBuffer().write( &msg, 1 );
f@0 169 }
f@0 170
f@0 171 void AudioEngine::noteOff( size_t waveIdx, int midiNote )
f@0 172 {
f@0 173 NoteMsg msg = makeNoteMsg( Command::NOTE_OFF, midiNote, 0.0 );
f@0 174 mPGranularNodes[waveIdx]->getNoteRingBuffer().write( &msg, 1 );
f@0 175 }
f@0 176
f@0 177
f@0 178
f@0 179 void AudioEngine::setSelectionSize( size_t waveIdx, size_t size )
f@0 180 {
f@0 181 mPGranularNodes[waveIdx]->setSelectionSize( size );
f@0 182 }
f@0 183
f@0 184 void AudioEngine::setSelectionStart( size_t waveIdx, size_t start )
f@0 185 {
f@0 186 mPGranularNodes[waveIdx]->setSelectionStart( start );
f@0 187 }
f@0 188
f@0 189 void AudioEngine::setGrainDurationCoeff( size_t waveIdx, double coeff )
f@0 190 {
f@0 191 mPGranularNodes[waveIdx]->setGrainsDurationCoeff( coeff );
f@0 192 }
f@0 193
f@0 194 void AudioEngine::setFilterCutoff( size_t waveIdx, double cutoff )
f@0 195 {
f@0 196 mLowPassFilterNodes[waveIdx]->setCutoffFreq( cutoff );
f@0 197 }
f@0 198
f@0 199 // ------------------------------------------------------
f@0 200 // ----- methods for communication with main thread -----
f@0 201 // ------------------------------------------------------
f@0 202
f@0 203 size_t AudioEngine::getRecordWaveAvailable( size_t waveIdx )
f@0 204 {
f@0 205 return mBufferRecorderNodes[waveIdx]->getRingBuffer().getAvailableRead();
f@0 206 }
f@0 207
f@2 208
f@0 209 bool AudioEngine::readRecordWave( size_t waveIdx, RecordWaveMsg* buffer, size_t count )
f@0 210 {
f@0 211 return mBufferRecorderNodes[waveIdx]->getRingBuffer().read( buffer, count );
f@0 212 }
f@0 213
f@0 214 void AudioEngine::checkCursorTriggers( size_t waveIdx, std::vector<CursorTriggerMsg>& cursorTriggers )
f@0 215 {
f@0 216 ci::audio::dsp::RingBufferT<CursorTriggerMsg> &ringBuffer = mCursorTriggerRingBufferPacks[waveIdx]->getBuffer();
f@0 217 CursorTriggerMsg* ringBufferReadArray = mCursorTriggerRingBufferPacks[waveIdx]->getExchangeArray();
f@0 218
f@0 219 size_t availableRead = ringBuffer.getAvailableRead();
f@0 220 bool successfulRead = ringBuffer.read( ringBufferReadArray, availableRead );
f@0 221
f@0 222 if ( successfulRead ){
f@0 223 for ( size_t i = 0; i < availableRead; i++ ){
f@0 224 cursorTriggers.push_back( ringBufferReadArray[i] );
f@0 225 }
f@0 226 }
f@0 227 }
f@0 228
f@0 229 const ci::audio::Buffer& AudioEngine::getAudioOutputBuffer( size_t waveIdx ) const
f@0 230 {
f@0 231 return mOutputMonitorNodes[waveIdx]->getBuffer();
f@0 232 }
f@0 233