comparison CollidoscopeApp/src/PGranularNode.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 7fb593d53361
comparison
equal deleted inserted replaced
-1:000000000000 0:02467299402e
1 #include "PGranularNode.h"
2
3 #include "cinder/audio/Context.h"
4
5 #include "cinder/Rand.h"
6
7 // generate random numbers from 0 to max
8 // it's passed to PGranular to randomize the phase offset at grain creation
9 struct RandomGenerator
10 {
11
12 RandomGenerator( size_t max ) : mMax( max )
13 {}
14
15 size_t operator()() const {
16 return ci::Rand::randUint( mMax );
17 }
18
19 size_t mMax;
20 };
21 // FIXME maybe use only one random gen
22
23 PGranularNode::PGranularNode( ci::audio::Buffer *grainBuffer, CursorTriggerMsgRingBuffer &triggerRingBuffer ) :
24 Node( Format().channels( 1 ) ),
25 mGrainBuffer(grainBuffer),
26 mSelectionStart( 0 ),
27 mSelectionSize( 0 ),
28 mGrainDurationCoeff( 1 ),
29 mTriggerRingBuffer( triggerRingBuffer ),
30 mNoteMsgRingBufferPack( 128 )
31 {
32 for ( int i = 0; i < kMaxVoices; i++ ){
33 mMidiNotes[i] = kNoMidiNote;
34
35 }
36 }
37
38
39 PGranularNode::~PGranularNode()
40 {
41 }
42
43 void PGranularNode::initialize()
44 {
45 mTempBuffer = std::make_shared< ci::audio::Buffer >( getFramesPerBlock() );
46
47 mRandomOffset.reset( new RandomGenerator( getSampleRate() / 100 ) ); // divided by 100 corresponds to times 0.01 in the time domain
48
49 /* create the PGranular object for looping */
50 mPGranularLoop.reset( new collidoscope::PGranular<float, RandomGenerator, PGranularNode>( mGrainBuffer->getData(), mGrainBuffer->getNumFrames(), getSampleRate(), *mRandomOffset, *this, -1 ) );
51
52 /* create the PGranular object for notes */
53 for ( size_t i = 0; i < kMaxVoices; i++ ){
54 mPGranularNotes[i].reset( new collidoscope::PGranular<float, RandomGenerator, PGranularNode>( mGrainBuffer->getData(), mGrainBuffer->getNumFrames(), getSampleRate(), *mRandomOffset, *this, i ) );
55 }
56
57 }
58
59 void PGranularNode::process (ci::audio::Buffer *buffer )
60 {
61 // only update PGranular if the atomic value has changed from the previous time
62
63 const boost::optional<size_t> selectionSize = mSelectionSize.get();
64 if ( selectionSize ){
65 mPGranularLoop->setSelectionSize( *selectionSize );
66 for ( size_t i = 0; i < kMaxVoices; i++ ){
67 mPGranularNotes[i]->setSelectionSize( *selectionSize );
68 }
69 }
70
71 const boost::optional<size_t> selectionStart = mSelectionStart.get();
72 if ( selectionStart ){
73 mPGranularLoop->setSelectionStart( *selectionStart );
74 for ( size_t i = 0; i < kMaxVoices; i++ ){
75 mPGranularNotes[i]->setSelectionStart( *selectionStart );
76 }
77 }
78
79 const boost::optional<double> grainDurationCoeff = mGrainDurationCoeff.get();
80 if ( grainDurationCoeff ){
81 mPGranularLoop->setGrainsDurationCoeff( *grainDurationCoeff );
82 for ( size_t i = 0; i < kMaxVoices; i++ ){
83 mPGranularNotes[i]->setGrainsDurationCoeff( *grainDurationCoeff );
84 }
85 }
86
87 // check messages to start/stop notes or loop
88 size_t availableRead = mNoteMsgRingBufferPack.getBuffer().getAvailableRead();
89 mNoteMsgRingBufferPack.getBuffer().read( mNoteMsgRingBufferPack.getExchangeArray(), availableRead );
90 for ( size_t i = 0; i < availableRead; i++ ){
91 handleNoteMsg( mNoteMsgRingBufferPack.getExchangeArray()[i] );
92 }
93
94 // process loop if not idle
95 if ( !mPGranularLoop->isIdle() ){
96 /* buffer is one channel only so I can use getData */
97 mPGranularLoop->process( buffer->getData(), mTempBuffer->getData(), buffer->getSize() );
98 }
99
100 // process notes if not idle
101 for ( size_t i = 0; i < kMaxVoices; i++ ){
102 if ( mPGranularNotes[i]->isIdle() )
103 continue;
104
105 mPGranularNotes[i]->process( buffer->getData(), mTempBuffer->getData(), buffer->getSize() );
106
107 if ( mPGranularNotes[i]->isIdle() ){
108 // this note became idle so update mMidiNotes
109 mMidiNotes[i] = kNoMidiNote;
110 }
111
112 }
113 }
114
115 void PGranularNode::operator()( char msgType, int ID ) {
116
117 switch ( msgType ){
118 case 't': { // trigger
119 CursorTriggerMsg msg = makeCursorTriggerMsg( Command::TRIGGER_UPDATE, ID ); // put ID
120 mTriggerRingBuffer.write( &msg, 1 );
121 };
122 break;
123
124 case 'e': // end envelope
125 CursorTriggerMsg msg = makeCursorTriggerMsg( Command::TRIGGER_END, ID ); // put ID
126 mTriggerRingBuffer.write( &msg, 1 );
127 break;
128 }
129
130
131 }
132
133 void PGranularNode::handleNoteMsg( const NoteMsg &msg )
134 {
135 switch ( msg.cmd ){
136 case Command::NOTE_ON: {
137 bool synthFound = false;
138
139 for ( int i = 0; i < kMaxVoices; i++ ){
140 // note was already on, so re-attack
141 if ( mMidiNotes[i] == msg.midiNote ){
142 mPGranularNotes[i]->noteOn( msg.rate );
143 synthFound = true;
144 break;
145 }
146 }
147
148 if ( !synthFound ){
149 // then look for a free synth
150 for ( int i = 0; i < kMaxVoices; i++ ){
151
152 if ( mMidiNotes[i] == kNoMidiNote ){
153 mPGranularNotes[i]->noteOn( msg.rate );
154 mMidiNotes[i] = msg.midiNote;
155 synthFound = true;
156 break;
157 }
158 }
159 }
160 };
161 break;
162
163 case Command::NOTE_OFF: {
164 for ( int i = 0; i < kMaxVoices; i++ ){
165 if ( !mPGranularNotes[i]->isIdle() && mMidiNotes[i] == msg.midiNote ){
166 mPGranularNotes[i]->noteOff();
167 break;
168 }
169 }
170 };
171 break;
172
173 case Command::LOOP_ON: {
174 mPGranularLoop->noteOn( 1.0 );
175 };
176 break;
177
178 case Command::LOOP_OFF: {
179 mPGranularLoop->noteOff();
180 };
181 break;
182 default:
183 break;
184 }
185 }