To check out this repository please hg clone the following URL, or open the URL using EasyMercurial or your preferred Mercurial client.

Statistics Download as Zip
| Branch: | Tag: | Revision:

root / CollidoscopeApp / src / CollidoscopeApp.cpp @ 6:4c0e82b725d9

History | View | Annotate | Download (15.1 KB)

1 5:75b744078d66 f
/*
2

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

6
 This file is part of Collidoscope.
7

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

13
 This program is distributed in the hope that it will be useful,
14
 but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
 GNU General Public License for more details.
17

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