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 const boost::optional<size_t> selectionSize = mSelectionSize.get();
|
f@0
|
63 if ( selectionSize ){
|
f@0
|
64 mPGranularLoop->setSelectionSize( *selectionSize );
|
f@0
|
65 for ( size_t i = 0; i < kMaxVoices; i++ ){
|
f@0
|
66 mPGranularNotes[i]->setSelectionSize( *selectionSize );
|
f@0
|
67 }
|
f@0
|
68 }
|
f@0
|
69
|
f@0
|
70 const boost::optional<size_t> selectionStart = mSelectionStart.get();
|
f@0
|
71 if ( selectionStart ){
|
f@0
|
72 mPGranularLoop->setSelectionStart( *selectionStart );
|
f@0
|
73 for ( size_t i = 0; i < kMaxVoices; i++ ){
|
f@0
|
74 mPGranularNotes[i]->setSelectionStart( *selectionStart );
|
f@0
|
75 }
|
f@0
|
76 }
|
f@0
|
77
|
f@0
|
78 const boost::optional<double> grainDurationCoeff = mGrainDurationCoeff.get();
|
f@0
|
79 if ( grainDurationCoeff ){
|
f@0
|
80 mPGranularLoop->setGrainsDurationCoeff( *grainDurationCoeff );
|
f@0
|
81 for ( size_t i = 0; i < kMaxVoices; i++ ){
|
f@0
|
82 mPGranularNotes[i]->setGrainsDurationCoeff( *grainDurationCoeff );
|
f@0
|
83 }
|
f@0
|
84 }
|
f@0
|
85
|
f@0
|
86 // check messages to start/stop notes or loop
|
f@0
|
87 size_t availableRead = mNoteMsgRingBufferPack.getBuffer().getAvailableRead();
|
f@0
|
88 mNoteMsgRingBufferPack.getBuffer().read( mNoteMsgRingBufferPack.getExchangeArray(), availableRead );
|
f@0
|
89 for ( size_t i = 0; i < availableRead; i++ ){
|
f@0
|
90 handleNoteMsg( mNoteMsgRingBufferPack.getExchangeArray()[i] );
|
f@0
|
91 }
|
f@0
|
92
|
f@0
|
93 // process loop if not idle
|
f@0
|
94 if ( !mPGranularLoop->isIdle() ){
|
f@0
|
95 /* buffer is one channel only so I can use getData */
|
f@0
|
96 mPGranularLoop->process( buffer->getData(), mTempBuffer->getData(), buffer->getSize() );
|
f@0
|
97 }
|
f@0
|
98
|
f@0
|
99 // process notes if not idle
|
f@0
|
100 for ( size_t i = 0; i < kMaxVoices; i++ ){
|
f@0
|
101 if ( mPGranularNotes[i]->isIdle() )
|
f@0
|
102 continue;
|
f@0
|
103
|
f@0
|
104 mPGranularNotes[i]->process( buffer->getData(), mTempBuffer->getData(), buffer->getSize() );
|
f@0
|
105
|
f@0
|
106 if ( mPGranularNotes[i]->isIdle() ){
|
f@0
|
107 // this note became idle so update mMidiNotes
|
f@0
|
108 mMidiNotes[i] = kNoMidiNote;
|
f@0
|
109 }
|
f@0
|
110
|
f@0
|
111 }
|
f@0
|
112 }
|
f@0
|
113
|
f@3
|
114 // Called back when new grnular is triggered of turned off. Sends notification message to graphic thread.
|
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 }
|