f@5
|
1 /*
|
f@5
|
2
|
f@5
|
3 Copyright (C) 2016 Queen Mary University of London
|
f@5
|
4 Author: Fiore Martin
|
f@5
|
5
|
f@5
|
6 This file is part of Collidoscope.
|
f@5
|
7
|
f@5
|
8 Collidoscope is free software: you can redistribute it and/or modify
|
f@5
|
9 it under the terms of the GNU General Public License as published by
|
f@5
|
10 the Free Software Foundation, either version 3 of the License, or
|
f@5
|
11 (at your option) any later version.
|
f@5
|
12
|
f@5
|
13 This program is distributed in the hope that it will be useful,
|
f@5
|
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
|
f@5
|
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
f@5
|
16 GNU General Public License for more details.
|
f@5
|
17
|
f@5
|
18 You should have received a copy of the GNU General Public License
|
f@5
|
19 along with this program. If not, see <http://www.gnu.org/licenses/>.
|
f@5
|
20 */
|
f@5
|
21
|
f@0
|
22 #include "PGranularNode.h"
|
f@0
|
23
|
f@0
|
24 #include "cinder/audio/Context.h"
|
f@0
|
25
|
f@0
|
26 #include "cinder/Rand.h"
|
f@0
|
27
|
f@0
|
28 // generate random numbers from 0 to max
|
f@0
|
29 // it's passed to PGranular to randomize the phase offset at grain creation
|
f@0
|
30 struct RandomGenerator
|
f@0
|
31 {
|
f@0
|
32
|
f@0
|
33 RandomGenerator( size_t max ) : mMax( max )
|
f@0
|
34 {}
|
f@0
|
35
|
f@0
|
36 size_t operator()() const {
|
f@0
|
37 return ci::Rand::randUint( mMax );
|
f@0
|
38 }
|
f@0
|
39
|
f@0
|
40 size_t mMax;
|
f@0
|
41 };
|
f@0
|
42 // FIXME maybe use only one random gen
|
f@0
|
43
|
f@0
|
44 PGranularNode::PGranularNode( ci::audio::Buffer *grainBuffer, CursorTriggerMsgRingBuffer &triggerRingBuffer ) :
|
f@0
|
45 Node( Format().channels( 1 ) ),
|
f@0
|
46 mGrainBuffer(grainBuffer),
|
f@0
|
47 mSelectionStart( 0 ),
|
f@0
|
48 mSelectionSize( 0 ),
|
f@0
|
49 mGrainDurationCoeff( 1 ),
|
f@0
|
50 mTriggerRingBuffer( triggerRingBuffer ),
|
f@0
|
51 mNoteMsgRingBufferPack( 128 )
|
f@0
|
52 {
|
f@0
|
53 for ( int i = 0; i < kMaxVoices; i++ ){
|
f@0
|
54 mMidiNotes[i] = kNoMidiNote;
|
f@0
|
55
|
f@0
|
56 }
|
f@0
|
57 }
|
f@0
|
58
|
f@0
|
59
|
f@0
|
60 PGranularNode::~PGranularNode()
|
f@0
|
61 {
|
f@0
|
62 }
|
f@0
|
63
|
f@0
|
64 void PGranularNode::initialize()
|
f@0
|
65 {
|
f@0
|
66 mTempBuffer = std::make_shared< ci::audio::Buffer >( getFramesPerBlock() );
|
f@0
|
67
|
f@16
|
68 mRandomOffset.reset( new RandomGenerator( getSampleRate() / 100 ) ); // divided by 100 corresponds to multiplied by 0.01 in the time domain
|
f@0
|
69
|
f@0
|
70 /* create the PGranular object for looping */
|
f@0
|
71 mPGranularLoop.reset( new collidoscope::PGranular<float, RandomGenerator, PGranularNode>( mGrainBuffer->getData(), mGrainBuffer->getNumFrames(), getSampleRate(), *mRandomOffset, *this, -1 ) );
|
f@0
|
72
|
f@0
|
73 /* create the PGranular object for notes */
|
f@0
|
74 for ( size_t i = 0; i < kMaxVoices; i++ ){
|
f@0
|
75 mPGranularNotes[i].reset( new collidoscope::PGranular<float, RandomGenerator, PGranularNode>( mGrainBuffer->getData(), mGrainBuffer->getNumFrames(), getSampleRate(), *mRandomOffset, *this, i ) );
|
f@0
|
76 }
|
f@0
|
77
|
f@0
|
78 }
|
f@0
|
79
|
f@0
|
80 void PGranularNode::process (ci::audio::Buffer *buffer )
|
f@0
|
81 {
|
f@0
|
82 // only update PGranular if the atomic value has changed from the previous time
|
f@0
|
83 const boost::optional<size_t> selectionSize = mSelectionSize.get();
|
f@0
|
84 if ( selectionSize ){
|
f@0
|
85 mPGranularLoop->setSelectionSize( *selectionSize );
|
f@0
|
86 for ( size_t i = 0; i < kMaxVoices; i++ ){
|
f@0
|
87 mPGranularNotes[i]->setSelectionSize( *selectionSize );
|
f@0
|
88 }
|
f@0
|
89 }
|
f@0
|
90
|
f@0
|
91 const boost::optional<size_t> selectionStart = mSelectionStart.get();
|
f@0
|
92 if ( selectionStart ){
|
f@0
|
93 mPGranularLoop->setSelectionStart( *selectionStart );
|
f@0
|
94 for ( size_t i = 0; i < kMaxVoices; i++ ){
|
f@0
|
95 mPGranularNotes[i]->setSelectionStart( *selectionStart );
|
f@0
|
96 }
|
f@0
|
97 }
|
f@0
|
98
|
f@0
|
99 const boost::optional<double> grainDurationCoeff = mGrainDurationCoeff.get();
|
f@0
|
100 if ( grainDurationCoeff ){
|
f@0
|
101 mPGranularLoop->setGrainsDurationCoeff( *grainDurationCoeff );
|
f@0
|
102 for ( size_t i = 0; i < kMaxVoices; i++ ){
|
f@0
|
103 mPGranularNotes[i]->setGrainsDurationCoeff( *grainDurationCoeff );
|
f@0
|
104 }
|
f@0
|
105 }
|
f@0
|
106
|
f@0
|
107 // check messages to start/stop notes or loop
|
f@0
|
108 size_t availableRead = mNoteMsgRingBufferPack.getBuffer().getAvailableRead();
|
f@0
|
109 mNoteMsgRingBufferPack.getBuffer().read( mNoteMsgRingBufferPack.getExchangeArray(), availableRead );
|
f@0
|
110 for ( size_t i = 0; i < availableRead; i++ ){
|
f@0
|
111 handleNoteMsg( mNoteMsgRingBufferPack.getExchangeArray()[i] );
|
f@0
|
112 }
|
f@0
|
113
|
f@0
|
114 // process loop if not idle
|
f@0
|
115 if ( !mPGranularLoop->isIdle() ){
|
f@0
|
116 /* buffer is one channel only so I can use getData */
|
f@0
|
117 mPGranularLoop->process( buffer->getData(), mTempBuffer->getData(), buffer->getSize() );
|
f@0
|
118 }
|
f@0
|
119
|
f@0
|
120 // process notes if not idle
|
f@0
|
121 for ( size_t i = 0; i < kMaxVoices; i++ ){
|
f@0
|
122 if ( mPGranularNotes[i]->isIdle() )
|
f@0
|
123 continue;
|
f@0
|
124
|
f@0
|
125 mPGranularNotes[i]->process( buffer->getData(), mTempBuffer->getData(), buffer->getSize() );
|
f@0
|
126
|
f@0
|
127 if ( mPGranularNotes[i]->isIdle() ){
|
f@0
|
128 // this note became idle so update mMidiNotes
|
f@0
|
129 mMidiNotes[i] = kNoMidiNote;
|
f@0
|
130 }
|
f@0
|
131
|
f@0
|
132 }
|
f@0
|
133 }
|
f@0
|
134
|
f@16
|
135 // Called back when new PGranular is triggered or turned off. Sends notification message to graphic thread.
|
f@0
|
136 void PGranularNode::operator()( char msgType, int ID ) {
|
f@0
|
137
|
f@0
|
138 switch ( msgType ){
|
f@0
|
139 case 't': { // trigger
|
f@0
|
140 CursorTriggerMsg msg = makeCursorTriggerMsg( Command::TRIGGER_UPDATE, ID ); // put ID
|
f@0
|
141 mTriggerRingBuffer.write( &msg, 1 );
|
f@0
|
142 };
|
f@0
|
143 break;
|
f@0
|
144
|
f@0
|
145 case 'e': // end envelope
|
f@0
|
146 CursorTriggerMsg msg = makeCursorTriggerMsg( Command::TRIGGER_END, ID ); // put ID
|
f@0
|
147 mTriggerRingBuffer.write( &msg, 1 );
|
f@0
|
148 break;
|
f@0
|
149 }
|
f@0
|
150
|
f@0
|
151
|
f@0
|
152 }
|
f@0
|
153
|
f@0
|
154 void PGranularNode::handleNoteMsg( const NoteMsg &msg )
|
f@0
|
155 {
|
f@0
|
156 switch ( msg.cmd ){
|
f@0
|
157 case Command::NOTE_ON: {
|
f@0
|
158 bool synthFound = false;
|
f@0
|
159
|
f@0
|
160 for ( int i = 0; i < kMaxVoices; i++ ){
|
f@0
|
161 // note was already on, so re-attack
|
f@0
|
162 if ( mMidiNotes[i] == msg.midiNote ){
|
f@0
|
163 mPGranularNotes[i]->noteOn( msg.rate );
|
f@0
|
164 synthFound = true;
|
f@0
|
165 break;
|
f@0
|
166 }
|
f@0
|
167 }
|
f@0
|
168
|
f@0
|
169 if ( !synthFound ){
|
f@16
|
170 // then look for a free voice
|
f@0
|
171 for ( int i = 0; i < kMaxVoices; i++ ){
|
f@0
|
172
|
f@0
|
173 if ( mMidiNotes[i] == kNoMidiNote ){
|
f@0
|
174 mPGranularNotes[i]->noteOn( msg.rate );
|
f@0
|
175 mMidiNotes[i] = msg.midiNote;
|
f@0
|
176 synthFound = true;
|
f@0
|
177 break;
|
f@0
|
178 }
|
f@0
|
179 }
|
f@0
|
180 }
|
f@0
|
181 };
|
f@0
|
182 break;
|
f@0
|
183
|
f@0
|
184 case Command::NOTE_OFF: {
|
f@0
|
185 for ( int i = 0; i < kMaxVoices; i++ ){
|
f@0
|
186 if ( !mPGranularNotes[i]->isIdle() && mMidiNotes[i] == msg.midiNote ){
|
f@0
|
187 mPGranularNotes[i]->noteOff();
|
f@0
|
188 break;
|
f@0
|
189 }
|
f@0
|
190 }
|
f@0
|
191 };
|
f@0
|
192 break;
|
f@0
|
193
|
f@0
|
194 case Command::LOOP_ON: {
|
f@0
|
195 mPGranularLoop->noteOn( 1.0 );
|
f@0
|
196 };
|
f@0
|
197 break;
|
f@0
|
198
|
f@0
|
199 case Command::LOOP_OFF: {
|
f@0
|
200 mPGranularLoop->noteOff();
|
f@0
|
201 };
|
f@0
|
202 break;
|
f@0
|
203 default:
|
f@0
|
204 break;
|
f@0
|
205 }
|
f@0
|
206 }
|