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
|