comparison CollidoscopeApp/src/CollidoscopeApp.cpp @ 0:02467299402e

First import CollidoscopeApp for Raspberry Pi JackDevice Teensy code for Collidoscope
author Fiore Martin <f.martin@qmul.ac.uk>
date Thu, 30 Jun 2016 14:50:06 +0200
parents
children 75b744078d66
comparison
equal deleted inserted replaced
-1:000000000000 0:02467299402e
1 #include "cinder/app/App.h"
2 #include "cinder/app/RendererGl.h"
3 #include "cinder/gl/gl.h"
4 #include "cinder/Exception.h"
5
6
7 #include "Config.h"
8 #include "Wave.h"
9 #include "DrawInfo.h"
10 #include "Log.h"
11 #include "AudioEngine.h"
12 #include "Oscilloscope.h"
13 #include "Messages.h"
14 #include "MIDI.h"
15
16 using namespace ci;
17 using namespace ci::app;
18
19 using namespace std;
20
21
22 class CollidoscopeApp : public App {
23 public:
24
25 void setup() override;
26 void setupGraphics();
27
28 void receiveCommands();
29
30 void keyDown( KeyEvent event ) override;
31 void update() override;
32 void draw() override;
33 void resize() override;
34
35 Config mConfig;
36 collidoscope::MIDI mMIDI;
37 AudioEngine mAudioEngine;
38
39 array< shared_ptr< Wave >, NUM_WAVES > mWaves;
40 array< shared_ptr< DrawInfo >, NUM_WAVES > mDrawInfos;
41 array< shared_ptr< Oscilloscope >, NUM_WAVES > mOscilloscopes;
42 // buffers to read the wave messages as a new wave gets recorded
43 array< RecordWaveMsg*, NUM_WAVES> mRecordWaveMessageBuffers;
44 array< vector< CursorTriggerMsg >, NUM_WAVES > mCursorTriggerMessagesBuffers;
45
46 double mSecondsPerChunk;
47
48 ~CollidoscopeApp();
49
50 };
51
52
53 void CollidoscopeApp::setup()
54 {
55 hideCursor();
56 /* setup is logged: setup steps and errors */
57
58 /*try {
59 mConfig.loadFromFile( "./collidoscope_config.xml" );
60 }
61 catch ( const Exception &e ){
62 logError( string("Exception loading config from file:") + e.what() );
63 }*/
64
65 // setup buffers to read messages from audio thread
66 for ( size_t i = 0; i < NUM_WAVES; i++ ){
67 mRecordWaveMessageBuffers[i] = new RecordWaveMsg[mConfig.getNumChunks()];
68 mCursorTriggerMessagesBuffers[i].reserve( mConfig.getCursorTriggerMessageBufSize() );
69 }
70
71 mAudioEngine.setup( mConfig );
72
73 setupGraphics();
74
75 mSecondsPerChunk = mConfig.getWaveLen() / mConfig.getNumChunks();
76
77 try {
78 mMIDI.setup( mConfig );
79 }
80 catch ( const collidoscope::MIDIException &e ){
81 logError( string( "Exception opening MIDI input device: " ) + e.getMessage() );
82 }
83
84 }
85
86 void CollidoscopeApp::setupGraphics()
87 {
88 for ( size_t i = 0; i < NUM_WAVES; i++ ){
89
90 mDrawInfos[i] = make_shared< DrawInfo >( i );
91 mWaves[i] = make_shared< Wave >(mConfig.getNumChunks(), mConfig.getWaveSelectionColor(i) );
92 mOscilloscopes[i] = make_shared< Oscilloscope >( mAudioEngine.getAudioOutputBuffer( i ).getNumFrames() / mConfig.getOscilloscopeNumPointsDivider() );
93
94 }
95 }
96
97 void CollidoscopeApp::keyDown( KeyEvent event )
98 {
99 char c = event.getChar();
100
101 switch (c){
102 case 'r' :
103 mAudioEngine.record( 0 );
104 mAudioEngine.record( 1 );
105 break;
106
107 case 'w': {
108 mWaves[0]->getSelection().setSize(mWaves[0]->getSelection().getSize() + 1);
109
110 size_t numSelectionChunks = mWaves[0]->getSelection().getSize();
111 // how many samples in one selection ?
112 size_t selectionSize = numSelectionChunks * (mConfig.getWaveLen() * mAudioEngine.getSampleRate() / mConfig.getNumChunks());
113
114 mAudioEngine.setSelectionSize(0, selectionSize);
115 };
116 break;
117
118 case 'e': {
119 mWaves[1]->getSelection().setSize(mWaves[1]->getSelection().getSize() + 1);
120
121 size_t numSelectionChunks = mWaves[1]->getSelection().getSize();
122 // how many samples in one selection ?
123 size_t selectionSize = numSelectionChunks * (mConfig.getWaveLen() * mAudioEngine.getSampleRate() / mConfig.getNumChunks());
124
125 mAudioEngine.setSelectionSize(1, selectionSize);
126 };
127 break;
128
129 case 's': {
130
131 mWaves[0]->getSelection().setSize( mWaves[0]->getSelection().getSize() - 1 );
132
133 size_t selectionSize = mWaves[0]->getSelection().getSize() *(mConfig.getWaveLen() * mAudioEngine.getSampleRate() / mConfig.getNumChunks());
134 mAudioEngine.setSelectionSize( 0, selectionSize );
135 };
136 break;
137
138 case 'd': {
139
140 for( size_t waveIdx = 0; waveIdx < NUM_WAVES; waveIdx++){
141 size_t selectionStart = mWaves[waveIdx]->getSelection().getStart();
142 mWaves[waveIdx]->getSelection().setStart( selectionStart + 1 );
143
144 selectionStart = mWaves[waveIdx]->getSelection().getStart();
145 mAudioEngine.setSelectionStart( waveIdx, selectionStart * (mConfig.getWaveLen() * mAudioEngine.getSampleRate() / mConfig.getNumChunks()) );
146 }
147 };
148
149 break;
150
151 case 'a': {
152 size_t selectionStart = mWaves[0]->getSelection().getStart();
153
154 if ( selectionStart == 0 )
155 return;
156
157 mWaves[0]->getSelection().setStart( selectionStart - 1 );
158
159 selectionStart = mWaves[0]->getSelection().getStart();
160
161 mAudioEngine.setSelectionStart( 0, selectionStart * (mConfig.getWaveLen() * mAudioEngine.getSampleRate() / mConfig.getNumChunks()) );
162 };
163 break;
164
165
166 case 'p':
167
168 mWaves[0]->setCursorPos( 4, mWaves[0]->getSelection().getStart(), *mDrawInfos[0] ) ;
169 break;
170
171 case 'f':
172 setFullScreen( !isFullScreen() );
173 break;
174
175 case ' ': {
176 static bool isOn = false;
177 isOn = !isOn;
178 if ( isOn ){
179 mAudioEngine.loopOn( 0 );
180 mAudioEngine.loopOn( 1 );
181 }
182 else{
183 mAudioEngine.loopOff( 0 );
184 mAudioEngine.loopOff( 1 );
185 }
186 };
187 break;
188
189 case 'm' :
190 mAudioEngine.setGrainDurationCoeff(0, 8);
191 break;
192
193 case 'n': {
194 mAudioEngine.setGrainDurationCoeff( 0, 1 );
195 };
196 break;
197
198 case '9': {
199 int c = mWaves[0]->getSelection().getParticleSpread();
200 if ( c == 1 )
201 return;
202 else
203 c -= 1;
204
205 mAudioEngine.setGrainDurationCoeff( 0, c );
206 mWaves[0]->getSelection().setParticleSpread( float( c ) );
207 mAudioEngine.setGrainDurationCoeff( 1, c );
208 mWaves[1]->getSelection().setParticleSpread( float( c ) );
209 }; break;
210
211 case '0': {
212 int c = mWaves[0]->getSelection().getParticleSpread();
213 if ( c == 8 )
214 return;
215 else
216 c += 1;
217
218 mAudioEngine.setGrainDurationCoeff( 0, c );
219 mWaves[0]->getSelection().setParticleSpread( float( c ) );
220 }; break;
221
222 }
223
224 }
225
226 void CollidoscopeApp::update()
227 {
228 // check incoming commands
229 receiveCommands();
230
231 // check new wave chunks from recorder buffer
232 for ( size_t i = 0; i < NUM_WAVES; i++ ){
233 size_t availableRead = mAudioEngine.getRecordWaveAvailable( i );
234 mAudioEngine.readRecordWave( i, mRecordWaveMessageBuffers[i], availableRead );
235
236 for ( size_t msgIndex = 0; msgIndex < availableRead; msgIndex++ ){
237 const RecordWaveMsg & msg = mRecordWaveMessageBuffers[i][msgIndex];
238
239 if ( msg.cmd == Command::WAVE_CHUNK ){
240 mWaves[i]->setChunk( msg.index, msg.arg1, msg.arg2 );
241 }
242 else if ( msg.cmd == Command::WAVE_START ){
243 mWaves[i]->reset( true ); // reset only chunks but leave selection
244 }
245
246 }
247 }
248
249 // check if new cursors have been triggered
250 for ( size_t i = 0; i < NUM_WAVES; i++ ){
251
252 mAudioEngine.checkCursorTriggers( i, mCursorTriggerMessagesBuffers[i] );
253 for ( auto & trigger : mCursorTriggerMessagesBuffers[i] ){
254 const int nodeID = trigger.synthID;
255
256 switch ( trigger.cmd ){
257
258 case Command::TRIGGER_UPDATE: {
259 mWaves[i]->setCursorPos( nodeID, mWaves[i]->getSelection().getStart(), *mDrawInfos[i] );
260 };
261 break;
262
263 case Command::TRIGGER_END: {
264 mWaves[i]->removeCursor( nodeID );
265 };
266 break;
267
268 }
269
270 }
271 mCursorTriggerMessagesBuffers[i].clear();
272 }
273
274 // update cursors
275 for ( size_t i = 0; i < NUM_WAVES; i++ ){
276 mWaves[i]->update( mSecondsPerChunk, *mDrawInfos[i] );
277 }
278
279 // update oscilloscope
280
281 for ( size_t i = 0; i < NUM_WAVES; i++ ){
282 const audio::Buffer &audioOutBuffer = mAudioEngine.getAudioOutputBuffer( i );
283 // one oscilloscope sample
284
285 for ( size_t j = 0; j < mOscilloscopes[i]->getNumPoints(); j++ ){
286 mOscilloscopes[i]->setPoint( j, audioOutBuffer.getData()[j], *mDrawInfos[i] );
287 }
288 }
289
290
291
292 }
293
294 void CollidoscopeApp::draw()
295 {
296 gl::clear( Color( 0, 0, 0 ) );
297
298 for ( int i = 0; i < NUM_WAVES; i++ ){
299 if ( i == 1 ){
300 /* for the upper wave flip the x over the center of the screen which is
301 the composition of rotate on the y-axis and translate by -screenwidth*/
302 gl::pushModelMatrix();
303 gl::rotate( float(M_PI), ci::vec3( 0, 1, 0 ) );
304 gl::translate( float( -getWindowWidth() ), 0.0f );
305 mOscilloscopes[i]->draw();
306 mWaves[i]->draw( *mDrawInfos[i] );
307 gl::popModelMatrix();
308 }
309 else{
310
311 mOscilloscopes[i]->draw();
312 mWaves[i]->draw( *mDrawInfos[i] );
313 }
314 }
315 }
316
317 void CollidoscopeApp::resize()
318 {
319 App::resize();
320
321 for ( int i = 0; i < NUM_WAVES; i++ ){
322 // reset the drawing information with the new windows size and same shrink factor
323 mDrawInfos[i]->reset( getWindow()->getBounds(), 3.0f / 5.0f );
324
325 /* reset the oscilloscope points to zero */
326 for ( int j = 0; j < mOscilloscopes[i]->getNumPoints(); j++ ){
327 mOscilloscopes[i]->setPoint(j, 0.0f, *mDrawInfos[i] );
328 }
329 }
330 }
331
332
333
334 void CollidoscopeApp::receiveCommands()
335 {
336 // check new midi messages
337 static std::vector<collidoscope::MIDIMessage> midiMessages;
338 mMIDI.checkMessages( midiMessages );
339
340
341 for ( auto &m : midiMessages ){
342
343 const size_t waveIdx = mConfig.getWaveForMIDIChannel( m.getChannel() );
344 if ( waveIdx >= NUM_WAVES )
345 continue;
346
347 if ( m.getVoice() == collidoscope::MIDIMessage::Voice::eNoteOn ){
348 int midiNote = m.getData_1();
349 mAudioEngine.noteOn( waveIdx, midiNote );
350 }
351 else if ( m.getVoice() == collidoscope::MIDIMessage::Voice::eNoteOff ){
352 int midiNote = m.getData_1();
353 mAudioEngine.noteOff( waveIdx, midiNote );
354 }
355 else if ( m.getVoice() == collidoscope::MIDIMessage::Voice::ePitchBend ){
356 const uint16_t MSB = m.getData_2() << 7;
357 uint16_t value = m.getData_1(); // LSB
358
359 value |= MSB;
360
361
362 // value ranges from 0 to 1050. check boundaries in case sensor gives bad values
363 if ( value > 149 ){ // FIXME pareametrizer
364 continue;
365 }
366
367 size_t startChunk = value;
368
369 const size_t selectionSizeBeforeStartUpdate = mWaves[waveIdx]->getSelection().getSize();
370 mWaves[waveIdx]->getSelection().setStart( startChunk );
371
372 mAudioEngine.setSelectionStart( waveIdx, startChunk * (mConfig.getWaveLen() * mAudioEngine.getSampleRate() / mConfig.getNumChunks()) );
373
374 const size_t newSelectionSize = mWaves[waveIdx]->getSelection().getSize();
375 if ( selectionSizeBeforeStartUpdate != newSelectionSize ){
376 mAudioEngine.setSelectionSize( waveIdx, newSelectionSize * (mConfig.getWaveLen() * mAudioEngine.getSampleRate() / mConfig.getNumChunks()) );
377 }
378
379
380 }
381 else if ( m.getVoice() == collidoscope::MIDIMessage::Voice::eControlChange ){
382
383 switch ( m.getData_1() ){ //controller number
384 case 1: { // selection size
385 const size_t midiVal = m.getData_2();
386 size_t numSelectionChunks = ci::lmap<size_t>( midiVal, 0, 127, 1, mConfig.getMaxSelectionNumChunks() );
387
388 mWaves[waveIdx]->getSelection().setSize( numSelectionChunks );
389
390 // how many samples in one selection ?
391 size_t selectionSize = mWaves[waveIdx]->getSelection().getSize() * (mConfig.getWaveLen() * mAudioEngine.getSampleRate() / mConfig.getNumChunks());
392 mAudioEngine.setSelectionSize( waveIdx, selectionSize );
393
394 };
395 break;
396
397 case 4: { // loop on off
398 unsigned char midiVal = m.getData_2();
399
400 if ( midiVal > 0 )
401 mAudioEngine.loopOn( waveIdx );
402 else
403 mAudioEngine.loopOff( waveIdx );
404 };
405 break;
406
407 case 5: // trigger record
408 mAudioEngine.record( waveIdx );
409 break;
410
411 case 2: { // duration
412 const double midiVal = m.getData_2(); // 0-127
413 const double coeff = ci::lmap<double>( midiVal, 0.0, 127, 1.0, mConfig.getMaxGrainDurationCoeff() );
414 mAudioEngine.setGrainDurationCoeff( waveIdx, coeff );
415 mWaves[waveIdx]->getSelection().setParticleSpread( float( coeff ) );
416 };
417 break;
418
419 case 7: { // filter
420 const double midiVal = m.getData_2(); // 0-127
421 const double minCutoff = mConfig.getMinFilterCutoffFreq();
422 const double maxCutoff = mConfig.getMaxFilterCutoffFreq();
423 const double cutoff = pow( maxCutoff / 200., midiVal / 127.0 ) * minCutoff;
424 mAudioEngine.setFilterCutoff( waveIdx, cutoff );
425 const float alpha = ci::lmap<double>( midiVal, 0.0f, 127.0f, 0.f, 1.f );
426 mWaves[waveIdx]->setselectionAlpha( alpha );
427 };
428 break;
429
430
431
432 }
433 }
434 }
435
436 midiMessages.clear();
437 }
438
439
440
441 CollidoscopeApp::~CollidoscopeApp()
442 {
443 for ( int chan = 0; chan < NUM_WAVES; chan++ ){
444 /* delete the array for wave messages from audio thread */
445 delete[] mRecordWaveMessageBuffers[chan];
446 }
447 }
448
449 CINDER_APP( CollidoscopeApp, RendererGl, [] ( App::Settings *settings) {
450 settings->setWindowSize( 1920, 1080 );
451 settings->setMultiTouchEnabled( false );
452 settings->disableFrameRate();
453
454 } )