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 @ 18:f1ff1a81be20

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