|
|
(4 intermediate revisions by the same user not shown) |
Line 6: |
Line 6: |
|
| |
|
| === Installation === | | === Installation === |
| * Copy the file [[#ChucK Code|intervalis.ck]] to any folder you like | | * Copy the file intervalis.ck (it should be under SVN control, in the folder users/jorgeh/) to any folder you like |
| * Done! | | * Done! |
|
| |
|
Line 13: |
Line 13: |
| * On the terminal window type: ''(replace N with the number of output channels of the audio card)'' <pre>chuck -cN intervalis</pre> | | * On the terminal window type: ''(replace N with the number of output channels of the audio card)'' <pre>chuck -cN intervalis</pre> |
| * Play! | | * Play! |
| | |
| | ==== Optional Arguments ==== |
| | Optionally you can type the following to run Intervalis: |
| | <pre>chuck -cN intervalis:arg1:arg2:arg3:arg4</pre> |
| | Where: |
| | * ''arg1'': intial spining frequency [Hz] |
| | * ''arg2'': spining frequency increment/decrement step [Hz] |
| | * ''arg3'': side tilt mode (1 => vibrato; 0 => interval) [Boolean] |
| | * ''arg4'': number of channels to implement the LeSLOrklie [int] |
| | '''''This list of parameters may vary in the future''''' |
|
| |
|
| === Execution === | | === Execution === |
Line 37: |
Line 47: |
|
| |
|
| ===== Interval selection ===== | | ===== Interval selection ===== |
| Number keys from 1 to 8
| | * To select the interval name use the number keys from 1 to 8. |
| | | * To select the quality of the interval: |
| ===== Interval quality selection =====
| | ** M : Major/Perfect |
| * M (Major/Perfect) | | ** N : Minor/Tritone |
| * N (Minor/Tritone) | | * To specify the direction of the interval: |
| ''(no diminished or augmented)''
| | ** - : Descending |
| | ** = : Ascending (default) |
|
| |
|
| ===== Other control keys ===== | | ===== Other control keys ===== |
Line 69: |
Line 80: |
| |Right SHIFT|| Interval note | | |Right SHIFT|| Interval note |
| |} | | |} |
|
| |
| == Advanced users ==
| |
|
| |
| === ChucK Code ===
| |
|
| |
| If you just want to look at the code, here it is:
| |
|
| |
| <pre>
| |
| // filename: intervalis.ck
| |
| // INTERVALIS - Play intervals
| |
| //
| |
| // MUSIC 128 - HW1
| |
| // By: Jorge Herrera
| |
| //
| |
|
| |
| /************************************
| |
| * MAIN CONTROL PARAMETERS
| |
| *************************************/
| |
| 10 => int _init_harmonics; // Control over the intial timbre
| |
| 1 => float _init_spin_freq; // LeSLOrklie initial speed [Hz]
| |
| 2 => int numChan; // LeSLOrklie is implemented over this many channels
| |
| 440 => float _a440; // frequency associated to the A key
| |
| 0 => int _init_quality; // Initial quality of the interval (0 -> Major, 1 -> minor)
| |
| 5 => int _init_interval; // Initial interval
| |
| 0.15 => float _tilt_threshold; // [0 - 1): threshold for silence (mute) level
| |
|
| |
|
| |
|
| |
| /************************************
| |
| * THE CODE
| |
| *************************************/
| |
|
| |
| // safety checks
| |
| if(_tilt_threshold >= 1) 0.999 => _tilt_threshold;
| |
|
| |
|
| |
| // The synthesis
| |
| Blit tonic => PitShift choir => JCRev rev => PoleZero mainOut;
| |
| Blit interval => choir;
| |
| mainOut.blockZero(0.99);
| |
|
| |
|
| |
| // Synth. initialization
| |
| _a440 => tonic.freq;
| |
| _init_harmonics => tonic.harmonics => interval.harmonics;
| |
| 0 => tonic.gain => interval.gain;
| |
| 0.12 => rev.mix;
| |
| 1.0 => choir.shift;
| |
| 0.2 => choir.mix;
| |
|
| |
|
| |
| // Interval definition array:
| |
| int intervals[2][8];
| |
|
| |
| // Major&Perfect intervals (defined in semitones)
| |
| 0 => intervals[0][0];
| |
| 2 => intervals[0][1];
| |
| 4 => intervals[0][2];
| |
| 5 => intervals[0][3];
| |
| 7 => intervals[0][4];
| |
| 9 => intervals[0][5];
| |
| 11 => intervals[0][6];
| |
| 12 => intervals[0][7];
| |
|
| |
| // Minor&Tritone intervals (defined in semitones)
| |
| 0 => intervals[1][0];
| |
| 1 => intervals[1][1];
| |
| 3 => intervals[1][2];
| |
| 5 => intervals[1][3];
| |
| 6 => intervals[1][4]; /* m5 == tritone */
| |
| 8 => intervals[1][5];
| |
| 10 => intervals[1][6];
| |
| 12 => intervals[1][7];
| |
|
| |
| // Interval initialization
| |
| 0 => float tonicTarget;
| |
| _init_quality => int gQuality; /* 0 = major, 1 = minor */
| |
| _init_interval - 1 => int gInterval => float intervalTarget;
| |
| updateInterval(gQuality, gInterval);
| |
|
| |
| // Flags to determine if the sounds are muted
| |
| 0 => int tonicMuted => int intervalMuted;
| |
|
| |
|
| |
| /**********************
| |
| * FUNCTIONS
| |
| *
| |
| * HANDLE WITH CARE!!!
| |
| ***********************
| |
|
| |
| /**************************
| |
| * The "LeSLOrklie"
| |
| **************************/
| |
| _init_spin_freq => float spinFreq;
| |
| Gain outGains[numChan];
| |
|
| |
| fun void spin() {
| |
| for(0 => int i; i<numChan; i++)
| |
| {
| |
| mainOut => outGains[i] => dac.chan(i);
| |
| }
| |
| 0 => float angle;
| |
| 0.001 => float step; // must be in seconds
| |
| while(step::second => now)
| |
| {
| |
| 2.0*Math.PI*spinFreq*step +=> angle;
| |
| for(0 => int i; i<numChan; i++) {
| |
| (Math.sin(angle + (2*Math.PI*i)$float/numChan) +
| |
| 1.0)/2.0 => outGains[i].gain;
| |
| }
| |
| }
| |
| }
| |
|
| |
|
| |
| /**************************
| |
| * GET MOTION INPUT
| |
| **************************/
| |
|
| |
| // infinite while loop
| |
| fun void getMotion()
| |
| {
| |
| // instantiate a HidIn object
| |
| Hid accel;
| |
| HidMsg movementMsg;
| |
|
| |
| // open tilt sensor
| |
| if( !accel.openTiltSensor() ) {
| |
| <<< "", "tilt sensor unavailable", "" >>>;
| |
| me.exit();
| |
| }
| |
|
| |
| // print
| |
| <<< "", "tilt sensor ready", "" >>>;
| |
|
| |
| while( true )
| |
| {
| |
| // poll the tilt sensor, expect to get back 3 element array of
| |
| // (9 for now means accelerometer, 0 selects 0th accelerometer)
| |
| accel.read( 9, 0, movementMsg );
| |
| if( !tonicMuted ) {
| |
| //<<< movementMsg.x, movementMsg.y, movementMsg.z >>>;
| |
| if(Math.fabs(movementMsg.y$float/300.0) < _tilt_threshold) 0 => tonicTarget;
| |
| else (Math.fabs(movementMsg.y$float/300.0) - _tilt_threshold)/(1 - _tilt_threshold) => tonicTarget;
| |
| }
| |
|
| |
| if( !intervalMuted ) {
| |
| if(Math.fabs(movementMsg.x$float/300.0) < _tilt_threshold) 0 => intervalTarget;
| |
| else (Math.fabs(movementMsg.x$float/300.0) - _tilt_threshold)/(1 - _tilt_threshold) => intervalTarget;
| |
| }
| |
| // advance time
| |
| 50::ms => now;
| |
| }
| |
| }
| |
|
| |
| // Smooth gain changes
| |
| fun void rampGains()
| |
| {
| |
| .5 => float slew;
| |
| while(20::ms => now) {
| |
| (tonicTarget - tonic.gain())*slew + tonic.gain() => tonic.gain;
| |
| (intervalTarget - interval.gain())*slew + interval.gain() =>
| |
| interval.gain;
| |
| }
| |
| }
| |
|
| |
|
| |
| /**************************
| |
| * GET KEYBOARD INPUT
| |
| **************************/
| |
|
| |
| // infinite event loop
| |
| fun void getKeys()
| |
| {
| |
| Hid hiKbd;
| |
| HidMsg msgKbd;
| |
|
| |
| // which keyboard
| |
| 0 => int device;
| |
| // get from command line
| |
| if( me.args() ) me.arg(0) => Std.atoi => device;
| |
|
| |
| // open keyboard (get device number from command line)
| |
| if( !hiKbd.openKeyboard( device ) ) me.exit();
| |
| <<< "", "keyboard '" + hiKbd.name() + "' ready", "" >>>;
| |
|
| |
| 0 => int gRegister;
| |
|
| |
| while( true )
| |
| {
| |
| // wait on event
| |
| hiKbd => now;
| |
| // get one or more messages
| |
| while( hiKbd.recv( msgKbd ) )
| |
| {
| |
| // check for action type
| |
| if( msgKbd.isButtonDown() )
| |
| {
| |
| //<<< "", "down:", msgKbd.which, "(code)", msgKbd.key, "(usb key)", msgKbd.ascii, "(ascii)" >>>;
| |
| //<<< "", "down:", msgKbd.which >>>;
| |
|
| |
| // Effect keys
| |
|
| |
| // Spin frequency control
| |
| if(msgKbd.which == 79) {
| |
| 0.2 +=> spinFreq;
| |
| }
| |
| if(msgKbd.which == 80 && spinFreq > 0.0000000001) {
| |
| 0.2 -=> spinFreq;
| |
| }
| |
|
| |
| // Harmonics control
| |
| if(msgKbd.which == 81 && tonic.harmonics() > 0) {
| |
| tonic.harmonics() - 1 => tonic.harmonics;
| |
| interval.harmonics() - 1 => interval.harmonics;
| |
| if(tonic.harmonics() < 5) {
| |
| <<< "***** ", tonic.harmonics(), " harmonics *****" >>>;
| |
| }
| |
| }
| |
| if(msgKbd.which == 82) {
| |
| tonic.harmonics() + 1 => tonic.harmonics;
| |
| interval.harmonics() + 1 => interval.harmonics;
| |
| if(tonic.harmonics() < 5) {
| |
| <<< "***** ", tonic.harmonics(), " harmonics *****" >>>;
| |
| }
| |
| }
| |
|
| |
| // PitShift
| |
| if(msgKbd.which == 38 && choir.shift() > 0.8) {
| |
| choir.shift() - 0.01 => choir.shift;
| |
| if(choir.shift() == 1.0)
| |
| <<< "***** IN TUNE *****" >>>;
| |
| }
| |
| if(msgKbd.which == 39 && choir.shift() < 1.2) {
| |
| choir.shift() + 0.01 => choir.shift;
| |
| if(choir.shift() == 1.0)
| |
| <<< "***** IN TUNE *****" >>>;
| |
| }
| |
|
| |
| // Interval selection
| |
| if(msgKbd.which >= 30 && msgKbd.which <= 37) {
| |
| msgKbd.which - 30 => gInterval;
| |
| }
| |
|
| |
| // Octave change
| |
| // ","
| |
| if(msgKbd.which == 54) {
| |
| 2 /=> _a440;
| |
| tonic.freq()/2 => tonic.freq;
| |
| gRegister - 1 => gRegister;
| |
| <<< "Register: ", gRegister >>>;
| |
| }
| |
| // "."
| |
| if(msgKbd.which == 55) {
| |
| 2 *=> _a440;
| |
| tonic.freq()*2 => tonic.freq;
| |
| gRegister + 1 => gRegister;
| |
| <<< "Register: ", gRegister >>>;
| |
| }
| |
|
| |
|
| |
| // Base pitch selection
| |
| if(msgKbd.which == 43) { // tab = F#
| |
| _a440*Math.pow(2,-3/12.0) => tonic.freq;
| |
| }
| |
| if(msgKbd.which == 57) { // CAPS = G
| |
| _a440*Math.pow(2,-2/12.0) => tonic.freq;
| |
| }
| |
| if(msgKbd.which == 20) { // Q = G#
| |
| _a440*Math.pow(2,-1/12.0) => tonic.freq;
| |
| }
| |
| if(msgKbd.which == 4) {
| |
| _a440*Math.pow(2,0/12.0) => tonic.freq;
| |
| }
| |
| if(msgKbd.which == 26) {
| |
| _a440*Math.pow(2,1/12.0) => tonic.freq;
| |
| }
| |
| if(msgKbd.which == 22) {
| |
| _a440*Math.pow(2,2/12.0) => tonic.freq;
| |
| }
| |
| if(msgKbd.which == 7) {
| |
| _a440*Math.pow(2,3/12.0) => tonic.freq;
| |
| }
| |
| if(msgKbd.which == 21) {
| |
| _a440*Math.pow(2,4/12.0) => tonic.freq;
| |
| }
| |
| if(msgKbd.which == 9) {
| |
| _a440*Math.pow(2,5/12.0) => tonic.freq;
| |
| }
| |
| if(msgKbd.which == 23) {
| |
| _a440*Math.pow(2,6/12.0) => tonic.freq;
| |
| }
| |
| if(msgKbd.which == 10) {
| |
| _a440*Math.pow(2,7/12.0) => tonic.freq;
| |
| }
| |
| if(msgKbd.which == 11) {
| |
| _a440*Math.pow(2,8/12.0) => tonic.freq;
| |
| }
| |
| if(msgKbd.which == 24) {
| |
| _a440*Math.pow(2,9/12.0) => tonic.freq;
| |
| }
| |
| if(msgKbd.which == 13) {
| |
| _a440*Math.pow(2,10/12.0) => tonic.freq;
| |
| }
| |
| if(msgKbd.which == 12) {
| |
| _a440*Math.pow(2,11/12.0) => tonic.freq;
| |
| }
| |
| if(msgKbd.which == 14) {
| |
| _a440*Math.pow(2,12/12.0) => tonic.freq;
| |
| }
| |
| if(msgKbd.which == 18) {
| |
| _a440*Math.pow(2,13/12.0) => tonic.freq;
| |
| }
| |
| if(msgKbd.which == 15) {
| |
| _a440*Math.pow(2,14/12.0) => tonic.freq;
| |
| }
| |
| if(msgKbd.which == 51) {
| |
| _a440*Math.pow(2,15/12.0) => tonic.freq;
| |
| }
| |
| if(msgKbd.which == 47) { // "[" = C#
| |
| _a440*Math.pow(2,16/12.0) => tonic.freq;
| |
| }
| |
| if(msgKbd.which == 52) { // "'" = D
| |
| _a440*Math.pow(2,17/12.0) => tonic.freq;
| |
| }
| |
| if(msgKbd.which == 48) { // "]" = D#
| |
| _a440*Math.pow(2,18/12.0) => tonic.freq;
| |
| }
| |
| if(msgKbd.which == 40) { // RETURN = E
| |
| _a440*Math.pow(2,19/12.0) => tonic.freq;
| |
| }
| |
|
| |
| // Major(perfect) || minor(tritone) gQuality selection
| |
| if(msgKbd.which == 16) {
| |
| 0 => gQuality;
| |
| <<< "", "Major mode" >>>;
| |
| }
| |
| if(msgKbd.which == 17) {
| |
| 1 => gQuality;
| |
| <<< "", "Minor mode" >>>;
| |
| }
| |
|
| |
| updateInterval(gQuality, gInterval);
| |
|
| |
|
| |
| // Mute controls
| |
|
| |
| // Space bar mute both sounds
| |
| if(msgKbd.which == 44) {
| |
| 1 => tonicMuted => intervalMuted;
| |
| 0 => tonicTarget => intervalTarget;
| |
| }
| |
|
| |
| // Left shift mute root (tonic) note
| |
| if(msgKbd.which == 225) {
| |
| 1 => tonicMuted;
| |
| 0 => tonicTarget;
| |
| }
| |
|
| |
| // Right shift mute the interval note
| |
| if(msgKbd.which == 229) {
| |
| 1 => intervalMuted;
| |
| 0 => intervalTarget;
| |
| }
| |
|
| |
| }
| |
|
| |
| // check for action type
| |
| if( msgKbd.isButtonUp() )
| |
| {
| |
| // Mute controls
| |
|
| |
| // Space bar mute both sounds
| |
| if(msgKbd.which == 44) {
| |
| 0 => tonicMuted => intervalMuted;
| |
| }
| |
|
| |
| // Left shift mute root (tonic) note
| |
| if(msgKbd.which == 225) {
| |
| 0 => tonicMuted;
| |
| }
| |
|
| |
| // Right shift mute the interval note
| |
| if(msgKbd.which == 229) {
| |
| 0 => intervalMuted;
| |
| }
| |
| }
| |
| }
| |
| }
| |
| }
| |
|
| |
| fun void updateInterval(int m, int i)
| |
| {
| |
| Math.pow(2,intervals[m][i]/12.0)*tonic.freq() => interval.freq;
| |
| }
| |
|
| |
|
| |
| /**************************
| |
| * GET OSC MESSAGES
| |
| **************************/
| |
|
| |
| // infinite event loop
| |
| fun void receiveOSC()
| |
| {
| |
| // create our OSC receiver
| |
| OscRecv recv;
| |
| // use port 6449 (or whatever)
| |
| 6449 => recv.port;
| |
| // start listening (launch thread)
| |
| recv.listen();
| |
|
| |
| // create an address in the receiver, store in new variable
| |
| recv.event( "/message, s i s s" ) @=> OscEvent @ oe;
| |
|
| |
| while( true )
| |
| {
| |
| // wait for event to arrive
| |
| oe => now;
| |
|
| |
| // grab the next message from the queue.
| |
| while( oe.nextMsg() )
| |
| {
| |
| string _base;
| |
| int _interval;
| |
| string _quality;
| |
| string _tilt;
| |
|
| |
| // getFloat fetches the expected float (as indicated by "i f")
| |
| oe.getString() => _base;
| |
| oe.getInt() => _interval;
| |
| oe.getString() => _quality;
| |
| oe.getString() => _tilt;
| |
|
| |
| // print
| |
| <<< "", "" >>>;
| |
|
| |
| <<< "*********** NEW MESSAGE RECEIVED ************", "" >>>;
| |
| <<< "Root key", _base >>>;
| |
| <<< "Interval", _interval >>>;
| |
| <<< "Quality", _quality >>>;
| |
| <<< "Tilt", _interval >>>;
| |
| }
| |
| }
| |
| }
| |
|
| |
|
| |
| fun void printInstructions()
| |
| {
| |
| <<< "", "" >>>;
| |
| <<< "", "" >>>;
| |
| <<< "", "This instrument play intervals, where the gain relationship" >>>;
| |
| <<< "", "between the 2 playing pitches is controlled by the performer" >>>;
| |
| <<< "", "using the tilt sensor." >>>;
| |
| <<< "", "" >>>;
| |
| <<< "", "There is separate control over the pitch, using the keyboard." >>>;
| |
| <<< "", "" >>>;
| |
| <<< "", "- Use keys [ A - L ] as a piano keyboard starting at A (pitch) = A (key)" >>>;
| |
| <<< "", "- Use numbers to set the interval (1: Prime, 2: Second, ..., 8: Octave)" >>>;
| |
| <<< "", "- Use keys M (major/perfect) & N (minor/tritone) to control the quality of the interval" >>>;
| |
| <<< "", "- Use UP/DOWN arrow keys to change the number of harmonics of the sound" >>>;
| |
| <<< "", "- Use LEFT/RIGHT arrow keys to change speed of rotation of the 'LeSLOrklie'" >>>;
| |
| <<< "", "- To change octave use ',' (down) or '.' (up)" >>>;
| |
| <<< "", "- '9' & '0' play with de-tuning" >>>;
| |
| <<< "", "- Use 'space bar' or 'shift keys' to mute the sounds" >>>;
| |
| }
| |
|
| |
|
| |
|
| |
| // Run the threads
| |
| spork ~ getMotion(); // Get motion input
| |
| spork ~ getKeys(); // Get keyboard input
| |
| spork ~ spin(); // Make the sound spee
| |
| spork ~ rampGains(); // Make the gain ratio change smooth
| |
| spork ~ receiveOSC(); // Listen to OSC messages
| |
|
| |
| printInstructions();
| |
|
| |
|
| |
| // The infinite loop
| |
| while(true)
| |
| {
| |
| 100::ms => now;
| |
| }
| |
|
| |
| </pre>
| |