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