Revision 3:7fb593d53361 CollidoscopeApp
| CollidoscopeApp/include/DrawInfo.h | ||
|---|---|---|
| 2 | 2 |
|
| 3 | 3 |
#include "cinder/Area.h" |
| 4 | 4 |
|
| 5 |
/** |
|
| 6 |
* The DrawInfo class holds size information for drawing the waves in the screen. |
|
| 7 |
* Every time the screen is resized the draw info is updated with the new information about the window size. |
|
| 8 |
* |
|
| 9 |
* Every wave has its own drawInfo. |
|
| 10 |
* |
|
| 11 |
*/ |
|
| 5 | 12 |
class DrawInfo |
| 6 | 13 |
{
|
| 7 | 14 |
public: |
| 8 | 15 |
|
| 16 |
/** |
|
| 17 |
* Constructor. Takes the index of the wave as argument. |
|
| 18 |
*/ |
|
| 9 | 19 |
DrawInfo( size_t waveIndex ): |
| 10 | 20 |
mWaveIndex( waveIndex ), |
| 11 | 21 |
mWindowWidth(0), |
| ... | ... | |
| 14 | 24 |
mShrinkFactor(1) |
| 15 | 25 |
{}
|
| 16 | 26 |
|
| 27 |
/** |
|
| 28 |
* Reset this DrawInfo using the new bounding area for the wave. \a shrinkFactor |
|
| 29 |
* makes the wave shrink on the y axis with respect to the area. A factor 1 makes the wave as big as the area, whereas a factor >1 makes it shrink. |
|
| 30 |
*/ |
|
| 17 | 31 |
void reset( const ci::Area &bounds, float shrinkFactor ) |
| 18 | 32 |
{
|
| 19 | 33 |
mWindowWidth = bounds.getWidth(); |
| ... | ... | |
| 22 | 36 |
mShrinkFactor = shrinkFactor; |
| 23 | 37 |
} |
| 24 | 38 |
|
| 39 |
/** |
|
| 40 |
* Maps a value in the audio space [-1.0, 1.0] to a position on the y axis of this DrawInf's bounding area. |
|
| 41 |
* |
|
| 42 |
*/ |
|
| 25 | 43 |
float audioToHeigt(float audioSample) const {
|
| 26 | 44 |
/* clip into range [-1.1] */ |
| 27 | 45 |
if (audioSample < -1.0f) {
|
| ... | ... | |
| 48 | 66 |
return mSelectionBarHeight; |
| 49 | 67 |
} |
| 50 | 68 |
|
| 69 |
/** |
|
| 70 |
* Returns the center position on the y axis of this DrawInfo's the bounding area. |
|
| 71 |
*/ |
|
| 51 | 72 |
int32_t getWaveCenterY() const |
| 52 | 73 |
{
|
| 53 | 74 |
if ( mWaveIndex == 0 ) |
| ... | ... | |
| 56 | 77 |
return mWindowHeight / (NUM_WAVES * 2); |
| 57 | 78 |
} |
| 58 | 79 |
|
| 80 |
/** |
|
| 81 |
* Flips y according to the index of the wave. It is needed because the second wave in collidoscope is upside down from the orientation oftthe screen. |
|
| 82 |
*/ |
|
| 59 | 83 |
int flipY(int y) const |
| 60 | 84 |
{
|
| 61 | 85 |
if ( mWaveIndex == 0) |
| 62 |
return mWindowHeight - y /*+ 24*/;
|
|
| 86 |
return mWindowHeight - y; |
|
| 63 | 87 |
else |
| 64 |
return y /*- 24*/;
|
|
| 88 |
return y; |
|
| 65 | 89 |
} |
| 66 | 90 |
|
| 91 |
/** |
|
| 92 |
* Returns x. not used at he moment. |
|
| 93 |
* |
|
| 94 |
*/ |
|
| 67 | 95 |
int flipX(int x) const |
| 68 | 96 |
{
|
| 69 | 97 |
return x; |
| ... | ... | |
| 86 | 114 |
return mWindowHeight; |
| 87 | 115 |
} |
| 88 | 116 |
|
| 117 |
/** |
|
| 118 |
* Draw infos cannot be copied and should be passed as const reference. |
|
| 119 |
*/ |
|
| 89 | 120 |
DrawInfo( const DrawInfo &original ) = delete; |
| 90 | 121 |
DrawInfo & operator=( const DrawInfo &original ) = delete; |
| 91 | 122 |
|
| CollidoscopeApp/include/EnvASR.h | ||
|---|---|---|
| 17 | 17 |
{
|
| 18 | 18 |
public: |
| 19 | 19 |
|
| 20 |
/** Possible states of the envelope. Idle means the envelope ouputs 0 */ |
|
| 20 | 21 |
enum class State {
|
| 21 | 22 |
eAttack, |
| 22 | 23 |
eSustain, |
| ... | ... | |
| 40 | 41 |
mReleaseRate = T( 1.0 ) / (releaseTime * sampleRate); |
| 41 | 42 |
} |
| 42 | 43 |
|
| 44 |
/** Produces one sample worth of envelope */ |
|
| 43 | 45 |
T tick() |
| 44 | 46 |
{
|
| 45 | 47 |
|
| CollidoscopeApp/include/Log.h | ||
|---|---|---|
| 1 | 1 |
#pragma once |
| 2 | 2 |
|
| 3 | 3 |
|
| 4 |
|
|
| 4 |
/** |
|
| 5 |
* Utility function to log errors using the cinder::log library. |
|
| 6 |
* Errors are logged to collidoscope_error.log file. |
|
| 7 |
* |
|
| 8 |
*/ |
|
| 5 | 9 |
void logError( const std::string &errorMsg ); |
| 6 | 10 |
|
| 7 | 11 |
|
| 8 |
void logInfo( const std::string &infoMsg ); |
|
| 12 |
/** |
|
| 13 |
* Utility function to log info using the cinder::log library. |
|
| 14 |
* Errors are logged to the terminal. Used only for debugging. |
|
| 15 |
* |
|
| 16 |
*/ |
|
| 17 |
void logInfo( const std::string &infoMsg ); |
|
| CollidoscopeApp/include/MIDI.h | ||
|---|---|---|
| 29 | 29 |
std::string mMessage; |
| 30 | 30 |
}; |
| 31 | 31 |
|
| 32 |
/** |
|
| 33 |
* A MIDI message |
|
| 34 |
*/ |
|
| 32 | 35 |
class MIDIMessage |
| 33 | 36 |
{
|
| 34 | 37 |
friend class MIDI; |
| ... | ... | |
| 40 | 43 |
|
| 41 | 44 |
unsigned char getChannel() { return mChannel; }
|
| 42 | 45 |
|
| 46 |
/** |
|
| 47 |
* First byte of MIDI data |
|
| 48 |
*/ |
|
| 43 | 49 |
unsigned char getData_1() { return mData1; }
|
| 44 | 50 |
|
| 51 |
/** |
|
| 52 |
* Second byte of MIDI data |
|
| 53 |
*/ |
|
| 45 | 54 |
unsigned char getData_2() { return mData2; }
|
| 46 | 55 |
|
| 47 | 56 |
private: |
| ... | ... | |
| 54 | 63 |
|
| 55 | 64 |
}; |
| 56 | 65 |
|
| 57 |
|
|
| 66 |
/** |
|
| 67 |
* Handles MIDI messages from the keyboards and Teensy. It uses RtMidi library. |
|
| 68 |
* |
|
| 69 |
*/ |
|
| 58 | 70 |
class MIDI |
| 59 | 71 |
{
|
| 60 | 72 |
|
| ... | ... | |
| 65 | 77 |
|
| 66 | 78 |
void setup( const Config& ); |
| 67 | 79 |
|
| 80 |
/** |
|
| 81 |
* Check new incoming messages and stores them into the vector passed as argument by reference. |
|
| 82 |
*/ |
|
| 68 | 83 |
void checkMessages( std::vector< MIDIMessage >& ); |
| 69 | 84 |
|
| 70 | 85 |
private: |
| 71 | 86 |
|
| 87 |
// callback passed to RtMidi library |
|
| 72 | 88 |
static void RtMidiInCallback( double deltatime, std::vector<unsigned char> *message, void *userData ); |
| 73 | 89 |
|
| 90 |
// parse RtMidi messages and turns them into more readable collidoscope::MIDIMessages |
|
| 74 | 91 |
MIDIMessage parseRtMidiMessage( std::vector<unsigned char> *message ); |
| 75 | 92 |
|
| 76 |
// messages to pass to checkMessages caller |
|
| 93 |
// messages to pass to checkMessages caller
|
|
| 77 | 94 |
std::vector< MIDIMessage > mMIDIMessages; |
| 95 |
// use specific variables for pitch bend messages. Pitch bend messages are coming |
|
| 96 |
// from the strip sensors that are very jerky and send a lot of values. So instead |
|
| 97 |
// of saving all the messages in mMIDIMessages just save the last received in mPitchBendMessages |
|
| 98 |
// and optimize away redundant messages. |
|
| 78 | 99 |
std::array< MIDIMessage, NUM_WAVES > mPitchBendMessages; |
| 100 |
// Same principle of pitch bend messages |
|
| 79 | 101 |
std::array< MIDIMessage, NUM_WAVES > mFilterMessages; |
| 80 | 102 |
|
| 81 |
|
|
| 82 |
|
|
| 103 |
// vecotr containing all the MIDI input devices detected. |
|
| 83 | 104 |
std::vector< std::unique_ptr <RtMidiIn> > mInputs; |
| 105 |
// Used for mutual access to the MIDI messages by the MIDI thread and the graphic thread. |
|
| 84 | 106 |
std::mutex mMutex; |
| 85 | 107 |
}; |
| 86 | 108 |
|
| CollidoscopeApp/include/Messages.h | ||
|---|---|---|
| 1 | 1 |
#pragma once |
| 2 | 2 |
|
| 3 |
|
|
| 3 |
/** |
|
| 4 |
* Enumeration of all the possible commands exchanged between audio thread and graphic thread. |
|
| 5 |
* |
|
| 6 |
*/ |
|
| 4 | 7 |
enum class Command {
|
| 5 | 8 |
// message carrying info about one chunk of recorder audio. |
| 6 | 9 |
WAVE_CHUNK, |
| ... | ... | |
| 17 | 20 |
LOOP_OFF |
| 18 | 21 |
}; |
| 19 | 22 |
|
| 20 |
/* Messages sent from the audio thread to the graphic wave. |
|
| 21 |
This includes the wave chunks when the audio is recorder in the buffer and |
|
| 22 |
the cursor position when the grains are reset. |
|
| 23 |
*/ |
|
| 23 |
/** Message sent from the audio thread to the graphic wave when a new wave is recorded. |
|
| 24 |
* |
|
| 25 |
* The graphic thread set the chunks of the wave to reflect the level of the recorded audio. |
|
| 26 |
* The algorithm takes the maximum and minimum value of a group of samples and this becomes the top and bottom of the samples. |
|
| 27 |
* It contains the inde |
|
| 28 |
* the cursor position when the grains are reset. |
|
| 29 |
*/ |
|
| 24 | 30 |
struct RecordWaveMsg |
| 25 | 31 |
{
|
| 26 |
Command cmd; |
|
| 32 |
Command cmd; // WAVE_CHUNK or WAVE_START
|
|
| 27 | 33 |
std::size_t index; |
| 28 | 34 |
float arg1; |
| 29 | 35 |
float arg2; |
| 30 | 36 |
}; |
| 31 | 37 |
|
| 32 |
|
|
| 38 |
/** |
|
| 39 |
* Utility function to create a new RecordWaveMsg. |
|
| 40 |
*/ |
|
| 33 | 41 |
inline RecordWaveMsg makeRecordWaveMsg( Command cmd, std::size_t index, float arg1, float arg2 ) |
| 34 | 42 |
{
|
| 35 | 43 |
RecordWaveMsg msg; |
| ... | ... | |
| 41 | 49 |
return msg; |
| 42 | 50 |
} |
| 43 | 51 |
|
| 44 |
|
|
| 52 |
/** |
|
| 53 |
* Message sent from the audio thread to the graphic thread when a new grain is triggered in the granular synthesizer. |
|
| 54 |
* This creates a new cursor that travels from the beginning to the end of the selection to graphically represent the evolution of the grain in time. |
|
| 55 |
* |
|
| 56 |
*/ |
|
| 45 | 57 |
struct CursorTriggerMsg |
| 46 | 58 |
{
|
| 47 |
Command cmd; |
|
| 59 |
Command cmd; // TRIGGER_UPDATE or TRIGGER_END
|
|
| 48 | 60 |
int synthID; |
| 49 | 61 |
}; |
| 50 | 62 |
|
| 63 |
/** |
|
| 64 |
* Utility function to create a new CursorTriggerMsg. |
|
| 65 |
*/ |
|
| 51 | 66 |
inline CursorTriggerMsg makeCursorTriggerMsg( Command cmd, std::uint8_t synthID ) |
| 52 | 67 |
{
|
| 53 | 68 |
CursorTriggerMsg msg; |
| ... | ... | |
| 58 | 73 |
return msg; |
| 59 | 74 |
} |
| 60 | 75 |
|
| 76 |
/** |
|
| 77 |
* Message sent from the graphic (main) thread to the audio thread to start a new voice of the granular synthesizer. |
|
| 78 |
*/ |
|
| 61 | 79 |
struct NoteMsg |
| 62 | 80 |
{
|
| 63 |
Command cmd; |
|
| 81 |
Command cmd; // NOTE_ON/OFF ot LOOP_ON/OFF
|
|
| 64 | 82 |
int midiNote; |
| 65 | 83 |
double rate; |
| 66 | 84 |
}; |
| 67 | 85 |
|
| 86 |
/** |
|
| 87 |
* Utility function to create a new NoteMsg. |
|
| 88 |
*/ |
|
| 68 | 89 |
inline NoteMsg makeNoteMsg( Command cmd, int midiNote, double rate ) |
| 69 | 90 |
{
|
| 70 | 91 |
NoteMsg msg; |
| ... | ... | |
| 74 | 95 |
msg.rate = rate; |
| 75 | 96 |
|
| 76 | 97 |
return msg; |
| 77 |
} |
|
| 98 |
} |
|
| CollidoscopeApp/include/PGranular.h | ||
|---|---|---|
| 10 | 10 |
|
| 11 | 11 |
using std::size_t; |
| 12 | 12 |
|
| 13 |
/** |
|
| 14 |
* The very core of the Collidoscope audio engine: the granular synthesizer. |
|
| 15 |
* Based on SuperCollider's TGrains and Ross Becina's "Implementing Real-Time Granular Synthesis" |
|
| 16 |
* |
|
| 17 |
* It implements Collidoscope's selection-based approach to granular synthesis. |
|
| 18 |
* A grain is basically a selection of a recorded sample of audio. |
|
| 19 |
* Grains are played in a loop: they are retriggered each time they reach the end of the selection. |
|
| 20 |
* However, if the duration coefficient is greater than one, a new grain is re-triggered before the previous one is done. |
|
| 21 |
* The grains start to overlap with each other and create the typical eerie sound of grnular synthesis. |
|
| 22 |
* Also every time a new grain is triggered, it is offset of a few samples from the initial position to make the timbre more interesting. |
|
| 23 |
* |
|
| 24 |
* |
|
| 25 |
* PGranular uses a linear ASR envelope with 10 milliseconds attack and 50 milliseconds release. |
|
| 26 |
* |
|
| 27 |
* Note that PGranular is header based and only depends on std library and on "EnvASR.h" (also header based). |
|
| 28 |
* This means you can embedd it in two your project just by copying these two files over. |
|
| 29 |
* |
|
| 30 |
* Template arguments: |
|
| 31 |
* T: type of the audio samples (normally float or double) |
|
| 32 |
* RandOffsetFunc: type of the callable passed as argument to the contructor |
|
| 33 |
* TriggerCallbackFunc: type of the callable passed as argument to the contructor |
|
| 34 |
* |
|
| 35 |
*/ |
|
| 13 | 36 |
template <typename T, typename RandOffsetFunc, typename TriggerCallbackFunc> |
| 14 | 37 |
class PGranular |
| 15 | 38 |
{
|
| ... | ... | |
| 24 | 47 |
return static_cast<T> ((1 - decimal) * xn + decimal * xn_1); |
| 25 | 48 |
} |
| 26 | 49 |
|
| 50 |
/** |
|
| 51 |
* A single grain of the granular synthesis |
|
| 52 |
*/ |
|
| 27 | 53 |
struct PGrain |
| 28 | 54 |
{
|
| 29 | 55 |
double phase; // read pointer to mBuffer of this grain |
| ... | ... | |
| 32 | 58 |
size_t age; // age of this grain in samples |
| 33 | 59 |
size_t duration; // duration of this grain in samples. minimum = 4 |
| 34 | 60 |
|
| 35 |
double b1; // hann envelope from Ross Becina "Implementing real time Granular Synthesis" |
|
| 61 |
double b1; // hann envelope from Ross Becina's "Implementing real time Granular Synthesis"
|
|
| 36 | 62 |
double y1; |
| 37 | 63 |
double y2; |
| 38 | 64 |
}; |
| 39 | 65 |
|
| 40 | 66 |
|
| 41 | 67 |
|
| 68 |
/** |
|
| 69 |
* Constructor. |
|
| 70 |
* |
|
| 71 |
* \param buffer a pointer to an array of T that contains the original sample that will be granulized |
|
| 72 |
* \param bufferLen length of buffer in samples |
|
| 73 |
* \rand function returning of type size_t ()(void) that is called back each time a new grain is generated. The returned value is used |
|
| 74 |
* to offset the starting sample of the grain. This adds more colour to the sound especially with small selections. |
|
| 75 |
* \triggerCallback function of type void ()(char, int) that is called back each time a new grain is triggered. |
|
| 76 |
* The function is passed the character 't' as first parameter when a new grain is triggered and the characted 't' when the synths becomes idle. |
|
| 77 |
* \ID id of this PGrain is passed to the triggerCallback function as second parameter to identify this PGranular as the caller. |
|
| 78 |
*/ |
|
| 42 | 79 |
PGranular( const T* buffer, size_t bufferLen, size_t sampleRate, RandOffsetFunc & rand, TriggerCallbackFunc & triggerCallback, int ID ) : |
| 43 | 80 |
mBuffer( buffer ), |
| 44 | 81 |
mBufferLen( bufferLen ), |
| ... | ... | |
| 71 | 108 |
|
| 72 | 109 |
~PGranular(){}
|
| 73 | 110 |
|
| 74 |
/* sets multiplier of duration of grains in seconds */
|
|
| 111 |
/** Sets multiplier of duration of grains in seconds */
|
|
| 75 | 112 |
void setGrainsDurationCoeff( double coeff ) |
| 76 | 113 |
{
|
| 77 | 114 |
mGrainsDurationCoeff = coeff; |
| 78 | 115 |
|
| 79 |
mGrainsDuration = std::lround( mTriggerRate * coeff ); // FIXME check if right rounding
|
|
| 116 |
mGrainsDuration = std::lround( mTriggerRate * coeff ); |
|
| 80 | 117 |
|
| 81 | 118 |
if ( mGrainsDuration < kMinGrainsDuration ) |
| 82 | 119 |
mGrainsDuration = kMinGrainsDuration; |
| 83 | 120 |
} |
| 84 | 121 |
|
| 85 |
/* sets rate of grains. e.g rate = 2 means one octave higer */
|
|
| 122 |
/** Sets rate of grains. e.g rate = 2 means one octave higer */
|
|
| 86 | 123 |
void setGrainsRate( double rate ) |
| 87 | 124 |
{
|
| 88 | 125 |
mGrainsRate = rate; |
| 89 | 126 |
} |
| 90 | 127 |
|
| 91 |
// sets trigger rate in samples
|
|
| 128 |
/** sets the selection start in samples */
|
|
| 92 | 129 |
void setSelectionStart( size_t start ) |
| 93 | 130 |
{
|
| 94 | 131 |
mGrainsStart = start; |
| 95 | 132 |
} |
| 96 | 133 |
|
| 134 |
/** Sets the selection size ( and therefore the trigger rate) in samples */ |
|
| 97 | 135 |
void setSelectionSize( size_t size ) |
| 98 | 136 |
{
|
| 99 | 137 |
|
| ... | ... | |
| 107 | 145 |
|
| 108 | 146 |
} |
| 109 | 147 |
|
| 148 |
/** Sets the attenuation of the grains with respect to the level of the recorded sample |
|
| 149 |
* attenuation is in amp value and defaule value is 0.25118864315096 (-12dB) */ |
|
| 110 | 150 |
void setAttenuation( T attenuation ) |
| 111 | 151 |
{
|
| 112 | 152 |
mAttenuation = attenuation; |
| 113 | 153 |
} |
| 114 | 154 |
|
| 155 |
/** Starts the synthesis engine */ |
|
| 115 | 156 |
void noteOn( double rate ) |
| 116 | 157 |
{
|
| 117 | 158 |
if ( mEnvASR.getState() == EnvASR<T>::State::eIdle ){
|
| ... | ... | |
| 125 | 166 |
} |
| 126 | 167 |
} |
| 127 | 168 |
|
| 169 |
/** Stops the synthesis engine */ |
|
| 128 | 170 |
void noteOff() |
| 129 | 171 |
{
|
| 130 | 172 |
if ( mEnvASR.getState() != EnvASR<T>::State::eIdle ){
|
| ... | ... | |
| 132 | 174 |
} |
| 133 | 175 |
} |
| 134 | 176 |
|
| 177 |
/** Whether the synthesis engine is active or not. After noteOff is called the synth stays active until the envelope decays to 0 */ |
|
| 135 | 178 |
bool isIdle() |
| 136 | 179 |
{
|
| 137 | 180 |
return mEnvASR.getState() == EnvASR<T>::State::eIdle; |
| 138 | 181 |
} |
| 139 | 182 |
|
| 183 |
/** |
|
| 184 |
* Runs the granular engine and stores the output in \a audioOut |
|
| 185 |
* |
|
| 186 |
* \param pointer to an array of T. This will be filled with the output of PGranular. It needs to be at least \a numSamples lond |
|
| 187 |
* \param tempBuffer a temporary buffer used to store the envelope value. It needs to be at leas \a numSamples long |
|
| 188 |
* \param numSamples number of samples to be processed |
|
| 189 |
*/ |
|
| 140 | 190 |
void process( T* audioOut, T* tempBuffer, size_t numSamples ) |
| 141 | 191 |
{
|
| 142 | 192 |
|
| ... | ... | |
| 144 | 194 |
size_t envSamples = 0; |
| 145 | 195 |
bool becameIdle = false; |
| 146 | 196 |
|
| 147 |
// do the envelope first and store it in the tempBuffer
|
|
| 197 |
// process the envelope first and store it in the tempBuffer
|
|
| 148 | 198 |
for ( size_t i = 0; i < numSamples; i++ ){
|
| 149 | 199 |
tempBuffer[i] = mEnvASR.tick(); |
| 150 | 200 |
envSamples++; |
| ... | ... | |
| 156 | 206 |
} |
| 157 | 207 |
} |
| 158 | 208 |
|
| 209 |
// does the actual grains processing |
|
| 159 | 210 |
processGrains( audioOut, tempBuffer, envSamples ); |
| 160 | 211 |
|
| 212 |
// becomes idle if the envelope goes to idle state |
|
| 161 | 213 |
if ( becameIdle ){
|
| 162 | 214 |
mTriggerCallback( 'e', mID ); |
| 163 | 215 |
reset(); |
| ... | ... | |
| 174 | 226 |
synthesizeGrain( mGrains[grainIdx], audioOut, envelopeValues, numSamples ); |
| 175 | 227 |
|
| 176 | 228 |
if ( !mGrains[grainIdx].alive ){
|
| 177 |
// this grain is dead so copyu the last of the active grains here
|
|
| 229 |
// this grain is dead so copy the last of the active grains here |
|
| 178 | 230 |
// so as to keep all active grains at the beginning of the array |
| 179 | 231 |
// don't increment grainIdx so the last active grain is processed next cycle |
| 180 | 232 |
// if this grain is the last active grain then mNumAliveGrains is decremented |
| ... | ... | |
| 243 | 295 |
} |
| 244 | 296 |
} |
| 245 | 297 |
|
| 298 |
// synthesize a single grain |
|
| 246 | 299 |
// audioOut = pointer to audio block to fill |
| 247 | 300 |
// numSamples = numpber of samples to process for this block |
| 248 | 301 |
void synthesizeGrain( PGrain &grain, T* audioOut, T* envelopeValues, size_t numSamples ) |
| CollidoscopeApp/include/PGranularNode.h | ||
|---|---|---|
| 30 | 30 |
explicit PGranularNode( ci::audio::Buffer *grainBuffer, CursorTriggerMsgRingBuffer &triggerRingBuffer ); |
| 31 | 31 |
~PGranularNode(); |
| 32 | 32 |
|
| 33 |
// set selection size in samples
|
|
| 33 |
/** Set selection size in samples */
|
|
| 34 | 34 |
void setSelectionSize( size_t size ) |
| 35 | 35 |
{
|
| 36 | 36 |
mSelectionSize.set( size ); |
| 37 | 37 |
} |
| 38 | 38 |
|
| 39 |
/** Set selection start in samples */ |
|
| 39 | 40 |
void setSelectionStart( size_t start ) |
| 40 | 41 |
{
|
| 41 | 42 |
mSelectionStart.set( start ); |
| ... | ... | |
| 46 | 47 |
mGrainDurationCoeff.set( coeff ); |
| 47 | 48 |
} |
| 48 | 49 |
|
| 49 |
// used for trigger callback in PGRanular
|
|
| 50 |
/* PGranularNode passes itself as trigger callback in PGranular */
|
|
| 50 | 51 |
void operator()( char msgType, int ID ); |
| 51 | 52 |
|
| 52 | 53 |
ci::audio::dsp::RingBufferT<NoteMsg>& getNoteRingBuffer() { return mNoteMsgRingBufferPack.getBuffer(); }
|
| ... | ... | |
| 59 | 60 |
|
| 60 | 61 |
private: |
| 61 | 62 |
|
| 63 |
// Wraps a std::atomic but get() returns a boost::optional that is set to a real value only when the atomic has changed. |
|
| 64 |
// It is used to avoid calling PGranulat setter methods with * the same value at each audio callback. |
|
| 62 | 65 |
template< typename T> |
| 63 | 66 |
class LazyAtomic |
| 64 | 67 |
{
|
| ... | ... | |
| 90 | 93 |
T mPreviousVal; |
| 91 | 94 |
}; |
| 92 | 95 |
|
| 96 |
// creates or re-start a PGranular and sets the pitch according to the MIDI note passed as argument |
|
| 93 | 97 |
void handleNoteMsg( const NoteMsg &msg ); |
| 94 | 98 |
|
| 95 |
// pointer to PGranular object
|
|
| 99 |
// pointers to PGranular objects
|
|
| 96 | 100 |
std::unique_ptr < collidoscope::PGranular<float, RandomGenerator, PGranularNode > > mPGranularLoop; |
| 97 | 101 |
std::array<std::unique_ptr < collidoscope::PGranular<float, RandomGenerator, PGranularNode > >, kMaxVoices> mPGranularNotes; |
| 102 |
// maps midi notes to pgranulars. When a noteOff is received maks sure the right PGranular is turned off |
|
| 98 | 103 |
std::array<int, kMaxVoices> mMidiNotes; |
| 99 | 104 |
|
| 100 | 105 |
// pointer to the random generator struct passed over to PGranular |
| 101 | 106 |
std::unique_ptr< RandomGenerator > mRandomOffset; |
| 102 | 107 |
|
| 103 |
/* buffer containing the recorder audio, to pass to PGranular in initialize() */
|
|
| 108 |
// buffer containing the recorder audio, to pass to PGranular in initialize()
|
|
| 104 | 109 |
ci::audio::Buffer *mGrainBuffer; |
| 105 | 110 |
|
| 106 | 111 |
ci::audio::BufferRef mTempBuffer; |
| CollidoscopeApp/include/ParticleController.h | ||
|---|---|---|
| 3 | 3 |
#include "cinder/gl/gl.h" |
| 4 | 4 |
#include <vector> |
| 5 | 5 |
|
| 6 |
|
|
| 6 |
/** |
|
| 7 |
* The ParticleController creates/updates/draws and destroys particles |
|
| 8 |
*/ |
|
| 7 | 9 |
class ParticleController {
|
| 8 | 10 |
|
| 9 | 11 |
struct Particle {
|
| ... | ... | |
| 23 | 25 |
std::vector<Particle> mParticles; |
| 24 | 26 |
std::vector< ci::vec2 > mParticlePositions; |
| 25 | 27 |
|
| 28 |
// current number of active particles |
|
| 26 | 29 |
size_t mNumParticles; |
| 27 | 30 |
|
| 28 | 31 |
ci::gl::VboRef mParticleVbo; |
| 29 | 32 |
ci::gl::BatchRef mParticleBatch; |
| 30 | 33 |
|
| 31 | 34 |
public: |
| 35 |
/** |
|
| 36 |
* Every time addParticles is run, up to kMaxParticleAdd are added at once |
|
| 37 |
*/ |
|
| 32 | 38 |
static const int kMaxParticleAdd = 22; |
| 33 | 39 |
|
| 34 | 40 |
ParticleController(); |
| 41 |
|
|
| 42 |
/** |
|
| 43 |
* Adds \a amount particles and places them in \a initialLocation. |
|
| 44 |
* \cloudSize determines how far the particles can go |
|
| 45 |
*/ |
|
| 35 | 46 |
void addParticles(int amount, const ci::vec2 &initialLocation, const float cloudSize); |
| 36 | 47 |
|
| 48 |
/** |
|
| 49 |
* Updates position and age of the particles |
|
| 50 |
*/ |
|
| 37 | 51 |
void updateParticles(); |
| 38 | 52 |
|
| 53 |
/** |
|
| 54 |
* Draws all the particles |
|
| 55 |
*/ |
|
| 39 | 56 |
inline void draw() |
| 40 | 57 |
{
|
| 41 | 58 |
mParticleBatch->draw(); |
| CollidoscopeApp/include/RingBufferPack.h | ||
|---|---|---|
| 3 | 3 |
#include "cinder/audio/dsp/RingBuffer.h" |
| 4 | 4 |
|
| 5 | 5 |
|
| 6 |
/* Packs together a RingBuffer and the erlated array used to exchange data (read/write) with the ring buffer |
|
| 7 |
*/ |
|
| 6 |
/** Packs together a cinder::audio::dsp::RingBuffer and the related array used passed as argument to exchange data (read/write) with the ring buffer */ |
|
| 8 | 7 |
template <typename T> |
| 9 | 8 |
class RingBufferPack {
|
| 10 | 9 |
|
| ... | ... | |
| 40 | 39 |
T* mArray; |
| 41 | 40 |
|
| 42 | 41 |
|
| 43 |
}; |
|
| 42 |
}; |
|
| CollidoscopeApp/include/Wave.h | ||
|---|---|---|
| 30 | 30 |
using ci::Color; |
| 31 | 31 |
using ci::ColorA; |
| 32 | 32 |
|
| 33 |
/** |
|
| 34 |
* A Cursor is the white thingy that loops through the selection when Collidoscope is played. |
|
| 35 |
*/ |
|
| 33 | 36 |
struct Cursor {
|
| 34 | 37 |
static const int kNoPosition = -100; |
| 35 | 38 |
int pos; |
| 36 | 39 |
double lastUpdate; |
| 37 | 40 |
}; |
| 38 | 41 |
|
| 39 |
|
|
| 42 |
/** |
|
| 43 |
* Collidoscope's graphical wave |
|
| 44 |
* |
|
| 45 |
*/ |
|
| 40 | 46 |
class Wave |
| 41 | 47 |
{
|
| 42 | 48 |
friend class ParticleController; |
| 43 | 49 |
|
| 44 | 50 |
public: |
| 45 | 51 |
|
| 52 |
/** |
|
| 53 |
* The selection of the wave that is controlled by the big horizontal knob |
|
| 54 |
* |
|
| 55 |
*/ |
|
| 46 | 56 |
class Selection {
|
| 47 | 57 |
|
| 48 | 58 |
public: |
| 49 | 59 |
|
| 50 | 60 |
Selection( Wave * w, Color color ); |
| 51 | 61 |
|
| 62 |
/** Sets the start of selection. start is the index of the first chunk of the selection */ |
|
| 52 | 63 |
void setStart( size_t start ); |
| 53 | 64 |
|
| 65 |
/** Sets the size of selection. size is the number of chunks the selection is made of */ |
|
| 54 | 66 |
void setSize( size_t size ); |
| 55 | 67 |
|
| 68 |
/** Particle spread is used to calculate the size of the cloud of particles */ |
|
| 56 | 69 |
void inline setParticleSpread( float spread ){
|
| 57 | 70 |
mParticleSpread = spread; |
| 58 | 71 |
} |
| ... | ... | |
| 70 | 83 |
|
| 71 | 84 |
float inline getParticleSpread() const { return mParticleSpread; }
|
| 72 | 85 |
|
| 86 |
/** When selection is null no selection is showed on the wave */ |
|
| 73 | 87 |
inline void setToNull(){
|
| 74 | 88 |
mParticleSpread = 1.0f; |
| 75 | 89 |
mNull = true; |
| ... | ... | |
| 107 | 121 |
|
| 108 | 122 |
|
| 109 | 123 |
|
| 110 |
/* there is one cursor for each Synth being played */
|
|
| 124 |
/* Maps id of the synth to cursor. There is one cursor for each Synth being played */
|
|
| 111 | 125 |
std::map < SynthID, Cursor > mCursors; |
| 126 |
/** Holds the positions of the cursor, namely on which chunk the cursor is currently */ |
|
| 112 | 127 |
std::vector<int> mCursorsPos; |
| 113 | 128 |
|
| 114 | 129 |
public: |
| 115 | 130 |
|
| 116 |
// note used to identify the loop for cursor position
|
|
| 131 |
// value used to identify the loop for cursor position
|
|
| 117 | 132 |
static const int kLoopNote = -1; |
| 118 | 133 |
static const cinder::Color CURSOR_CLR; |
| 119 | 134 |
/* must be in sync with supercollider durationFactor ControlSpec max */ |
| ... | ... | |
| 122 | 137 |
static const int PARTICLESIZE_COEFF = 40; |
| 123 | 138 |
#endif |
| 124 | 139 |
|
| 140 |
/** Resetting a wave makes it shrink until it disappears. Each time a new sample is recorder the wave is reset |
|
| 141 |
* \param onlyChunks if false the selection is also set to null, if true only the chunks are reset |
|
| 142 |
*/ |
|
| 125 | 143 |
void reset(bool onlyChunks); |
| 126 | 144 |
|
| 145 |
/** sets top and bottom values for the chunk. |
|
| 146 |
* \a bottom and \a top are in audio coordinates [-1.0, 1.0] |
|
| 147 |
*/ |
|
| 127 | 148 |
void setChunk(size_t index, float bottom, float top); |
| 128 | 149 |
|
| 129 | 150 |
const Chunk & getChunk(size_t index); |
| 130 | 151 |
|
| 152 |
/** places the cursor on the wave. Every cursor is associated to a synth voice of the audio engine. |
|
| 153 |
* The synth id identifies uniquely the cursor in the internal map of the wave. |
|
| 154 |
* If the cursor doesn't exist it is created */ |
|
| 131 | 155 |
inline void setCursorPos( SynthID id, int pos, const DrawInfo& di ){
|
| 132 | 156 |
|
| 133 | 157 |
Cursor & cursor = mCursors[id]; |
| ... | ... | |
| 135 | 159 |
cursor.lastUpdate = ci::app::getElapsedSeconds(); |
| 136 | 160 |
|
| 137 | 161 |
#ifdef USE_PARTICLES |
| 138 |
/* if the duration is greater than 1.0 carry on the cursor as a particle |
|
| 139 |
the smaller the selection the more particles |
|
| 140 |
the bigger the duration the more particles */ |
|
| 162 |
// The idea is that, if the duration is greater than 1.0, the cursor continues in form of particles |
|
| 163 |
// The smaller the selection the more particles; the bigger the duration the more particles |
|
| 141 | 164 |
if (mSelection.getParticleSpread() > 1.0f){
|
| 142 | 165 |
/* amountCoeff ranges from 1/8 to 1 */ |
| 143 | 166 |
const float amountCoeff = (mSelection.getParticleSpread() / MAX_DURATION); |
| ... | ... | |
| 167 | 190 |
|
| 168 | 191 |
void removeCursor( SynthID id ) { mCursors.erase( id ); }
|
| 169 | 192 |
|
| 170 |
// parameter ranges from 0 to 1
|
|
| 193 |
/** Sets the transparency of this wave. \a alpha ranges from 0 to 1 */
|
|
| 171 | 194 |
inline void setselectionAlpha(float alpha){ mFilterCoeff = alpha;}
|
| 172 | 195 |
|
| 173 | 196 |
void draw( const DrawInfo& di ); |
| ... | ... | |
| 180 | 203 |
|
| 181 | 204 |
Wave( size_t numChunks, Color selectionColor ); |
| 182 | 205 |
|
| 183 |
// no copies
|
|
| 206 |
/** no copies */
|
|
| 184 | 207 |
Wave( const Wave © ) = delete; |
| 185 | 208 |
Wave & operator=(const Wave ©) = delete; |
| 186 | 209 |
|
| ... | ... | |
| 197 | 220 |
|
| 198 | 221 |
float mFilterCoeff; |
| 199 | 222 |
|
| 223 |
// cinder gl batch for batch drawing |
|
| 200 | 224 |
ci::gl::BatchRef mChunkBatch; |
| 201 | 225 |
|
| 202 | 226 |
}; |
| CollidoscopeApp/src/PGranularNode.cpp | ||
|---|---|---|
| 59 | 59 |
void PGranularNode::process (ci::audio::Buffer *buffer ) |
| 60 | 60 |
{
|
| 61 | 61 |
// only update PGranular if the atomic value has changed from the previous time |
| 62 |
|
|
| 63 | 62 |
const boost::optional<size_t> selectionSize = mSelectionSize.get(); |
| 64 | 63 |
if ( selectionSize ){
|
| 65 | 64 |
mPGranularLoop->setSelectionSize( *selectionSize ); |
| ... | ... | |
| 112 | 111 |
} |
| 113 | 112 |
} |
| 114 | 113 |
|
| 114 |
// Called back when new grnular is triggered of turned off. Sends notification message to graphic thread. |
|
| 115 | 115 |
void PGranularNode::operator()( char msgType, int ID ) {
|
| 116 | 116 |
|
| 117 | 117 |
switch ( msgType ){
|
| CollidoscopeApp/src/Wave.cpp | ||
|---|---|---|
| 96 | 96 |
|
| 97 | 97 |
|
| 98 | 98 |
const float wavePixelLen = ( mNumChunks * ( 2 + Chunk::kWidth ) ); |
| 99 |
/* scale the x-axis for the wave to fit the window */ |
|
| 99 |
/* scale the x-axis for the wave to fit the window precisely */
|
|
| 100 | 100 |
gl::scale( ((float)di.getWindowWidth() ) / wavePixelLen , 1.0f); |
| 101 | 101 |
/* draw the chunks */ |
| 102 | 102 |
if (mSelection.isNull()){
|
Also available in: Unified diff