//----------------------------------------------------------------------------- /* Copyright (c) 2007 Dan Trueman. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 U.S.A. ----------------------------------------------------------------------------- The Cyclotron Lives! This is a simple class engine for working with the Cyclotron GUI (written in Processing). Should ultimately be usable without the GUI (need file I/O in ChucK first) Inspired by -the original Cyclotron (http://www.music.princeton.edu/~dan/cyclotronPage/cyclotron.html) -and Justin London's work with entrainment, maximal unevenness, etc.... http://people.carleton.edu/~jlondon/UK%20PPT/HTTAM%20Web%20Version.htm -and the crazy Norwegian springar dance rhythms. -and various African drum ensembles, like the drummers of Dagbon, Burundi.... and later, found out about these cool things: http://raymondscott.com/circle.html http://www.future-retro.com/REVOLUTION.html http://www.rlmusic.co.uk/mals_site/buchla/249e.html http://pbskids.org/arthur/games/crankitup/crankitup.html http://www-tc.pbskids.org/arthur/games/crankitup/gb_17.swf?mii=1 could make a pretty wicked interface for this with the Lemur. Things to add: -be able to use lookup table to warp timing, in addition to current warp formula -would allow for ritards and accels and other unusual warping.... dan trueman, 2007 */ //----------------------------------------------------------------------------- /*** Cycler Class ***/ //so we can have multiple cyclers open private class Cycler { 100 => int maxSpokes; 4 => int numSpokes; 0 => int reversed; //state variable //spoke data float times[maxSpokes]; float lens[maxSpokes]; float caps[maxSpokes]; float incs[maxSpokes]; float warps[maxSpokes]; /*** init vars ***/ for (0=>int i;i times[i]; 0.001 => incs[i]; 1. => warps[i]; } public void setCycler(float ntimes[], float nlens[], float ncaps[], float nincs[], float nwarps[], int nspokes, float offset) { for (0=>int i;i times[i] ; if(times[i] >= 1.) (times[i] - 1.) => times[i]; if(times[i] < 0.) times[i] + 1. => times[i]; nlens[i] => lens[i]; ncaps[i] => caps[i]; nincs[i] => incs[i]; nwarps[i] => warps[i]; nspokes => numSpokes; } sortCycler(); } public void postCycler() { for (0=>int i;i>>; << cyclers[0].times[", i,"]">>>; << cyclers[0].lens[", i,"]">>>; << cyclers[0].caps[", i,"]">>>; << cyclers[0].incs[", i,"]">>>; << cyclers[0].warps[", i,"]">>>; } << number of spokes">>>; <<<"_________">>>; } public void sortCycler() { int minm; float ftemp; for(0 => int i;i minm; for(i+1 => int j;j minm; times[i]=>ftemp; times[minm]=>times[i];ftemp=>times[minm]; lens[i] =>ftemp; lens[minm] =>lens[i]; ftemp=>lens[minm]; caps[i] =>ftemp; caps[minm] =>caps[i]; ftemp=>caps[minm]; incs[i] =>ftemp; incs[minm] =>incs[i]; ftemp=>incs[minm]; warps[i]=>ftemp; warps[minm]=>warps[i];ftemp=>warps[minm]; } } public void reverseCycler() { for(0=>int i;i times[i]; if(times[i] == 1.) 0. => times[i]; } sortCycler(); //maintain state var if(reversed == 1) 0 => reversed; else 1 => reversed; } public void setDirection(int dir) { if(reversed != dir) reverseCycler(); } } /*** the Cyclotron Class ***/ public class Cyclotron { /*** var declarations ***/ //cycle status vars 0. => float ctime; 0. => float phase; 1. => float oldphase; 0.2 => float loopInc; 2. => float loopDur; loopInc/loopDur => float tempoMult; //default 2 second cycle length 1. => float globalTempo; //for controlling tempo of all cyclers, regardless of length //spoke data 20 => int maxCyclers; Cycler c[maxCyclers]; 0 => int cc; //current cycler id SpokeEvent outClik; Event halfCycleClik; Event fullCycleClik; OscRecv recvAll; OscRecv recv; int sendPort; OscSend xmit; //set some default values float timeInc; float phaseWarp; //1 => constant speed 0 => int nextSpoke; 0 => int currentSpoke; 0 => int playToggle; 1 => int creset; //set when wrapping phase /*** main loop ***/ public void go() { //<<<">>>>>>>>>>>>>>>>>>>>> about to run main loop <<<<<<<<<<<<<<<<<<<<<<">>>; me.yield(); c[ cc ].incs[ c[ cc ].numSpokes - 1 ] => timeInc; c[ cc ].warps[ c[ cc ].numSpokes - 1 ] => phaseWarp; while(true) { if(playToggle) { (timeInc*tempoMult*globalTempo) +=> ctime => phase; //reset spokes when wrapping phase if (phase >= 1.) { 0 => nextSpoke; 1 => creset; ctime % 1. => phase => ctime; //wrapping phase fullCycleClik.signal(); //<<<"***cycle wrapping***">>>; //c[cc].postCycler(); } c[cc].incs[currentSpoke] => timeInc; c[cc].warps[currentSpoke] => phaseWarp; //Math.pow(phase, phaseWarp) => phase; //warping the phase speed here //y = (k^x - 1) / (k - 1); gives continuous velocity across warp inversions if (phaseWarp != 1.) (Math.pow(phaseWarp, phase) - 1.) / (phaseWarp - 1.) => phase; //checking for bang if(phase >= c[cc].times[nextSpoke]) { //<<<"bang", nextSpoke, c[cc].times[nextSpoke], phase, oldphase, cc>>>; nextSpoke => outClik.spoke => currentSpoke; outClik.signal(); nextSpoke++; } //send 1/2-cycle message //probably best to make this more general, so the user could specify any number of //phase points to receives msgs for (in addition to the spoke msgs). if(phase >= 0.49 && creset) { halfCycleClik.signal(); 0 => creset; //<<<"half cycle wrap!">>>; } phase => oldphase; } (loopInc * 10.)::ms => now; } } public void setPort(int newportnum) { /*** create our OSC receivers ***/ //for msgs to just this chucker newportnum => sendPort; //change this as needed for multiple cyclers sendPort + 1 => recv.port; recv.listen(); //for msgs to all chuckers, if we've got >1 cycler going 1999 => recvAll.port; //don't change this recvAll.listen(); xmit.setHost( "localhost", sendPort ); //port should equal what we sent to cyc1 //OSC shred listeners spork ~ spoke_receive(); spork ~ spokeNum_receive(); spork ~ tempo_receive(); spork ~ play_receive(); spork ~ playAll_receive(); spork ~ restart_receive(); spork ~ restartAll_receive(); spork ~ msg_send(); //for testing //spork ~ cycler_receive(); } public void setCurrentCycler(int newcc) { if(newcc > maxCyclers) 0 => cc; else newcc => cc; 0. => ctime; 0. => oldphase; //0 => currentSpoke; 1 => nextSpoke; c[cc].warps[0] => phaseWarp; c[cc].incs[0] => timeInc; sendCycler(); //<<>>; } public float[] getSpoke() { float spoke[6]; nextSpoke - 1 => int currentSpoke; if(currentSpoke<0) 0 => currentSpoke; //<<>>; currentSpoke => spoke[0]; c[cc].times[currentSpoke] => spoke[1]; c[cc].lens[currentSpoke] => spoke[2]; c[cc].caps[currentSpoke] => spoke[3]; c[cc].incs[currentSpoke] => spoke[4]; c[cc].times[nextSpoke] - c[cc].times[currentSpoke] => spoke[5]; return spoke; } public float[] getSpoke(int spokeNum) { float spoke[6]; spokeNum => int currentSpoke; if(currentSpoke<0) 0 => currentSpoke; //<<>>; currentSpoke => spoke[0]; c[cc].times[currentSpoke] => spoke[1]; c[cc].lens[currentSpoke] => spoke[2]; c[cc].caps[currentSpoke] => spoke[3]; c[cc].incs[currentSpoke] => spoke[4]; c[cc].times[nextSpoke] - c[cc].times[currentSpoke] => spoke[5]; return spoke; } public void setSpoke(float newspoke[]) { newspoke[0] $ int => int whichSpoke; newspoke[1] => c[cc].times[whichSpoke]; newspoke[2] => c[cc].lens[whichSpoke]; newspoke[3] => c[cc].caps[whichSpoke]; newspoke[4] => c[cc].incs[whichSpoke]; //need to send to GUI } public void reverse() { c[cc].reverseCycler(); spork ~ sendCycler(); //reset current spoke 0 => int tempSpoke; while(phase > c[cc].times[tempSpoke]) { tempSpoke++; } tempSpoke => nextSpoke => outClik.spoke => currentSpoke; } public void setDirection(int dir) { c[cc].setDirection(dir); spork ~ sendCycler(); } public void playToggle_ctl() { if(playToggle) 0 => playToggle; else 1 => playToggle; } public void restart() { 1. => ctime => phase; } public void setPhase(float newPhase) { //reset spoke 0 => int tempSpoke; while(newPhase > c[cc].times[tempSpoke]) { tempSpoke++; } tempSpoke => nextSpoke => outClik.spoke => currentSpoke; nextSpoke - 1 => currentSpoke; if(currentSpoke < 0) c[cc].numSpokes - 1 => currentSpoke; if(newPhase > 0.49) 0 => creset; else 1 => creset; //if (phaseWarp!= 1.) (Math.pow(phaseWarp, phase) - 1.) / (phaseWarp - 1.) => ctime => phase; //inverse of warping func above if (phaseWarp!= 1.) { //<<<"setting phase, adjusting for warp">>>; Math.log(newPhase*(phaseWarp - 1.) + 1.) / Math.log(phaseWarp) => ctime => phase; } else newPhase => ctime => phase; } public float getPhase() { return phase; } //all of these need to send to GUI public void setSpokeTime(int whichSpoke, float newtime) { newtime => c[cc].times[whichSpoke]; } public void setSpokeLen(int whichSpoke, float newlen) { newlen => c[cc].lens[whichSpoke]; } public void setSpokeCap(int whichSpoke, float newcap) { newcap => c[cc].caps[whichSpoke]; } public void setSpokeInc(int whichSpoke, float newinc) { newinc => c[cc].incs[whichSpoke]; } public dur getLoopDur() { return loopDur * 1::second; } public void setLoopDur(float newloopdur) { newloopdur => loopDur; loopInc/loopDur => tempoMult; } public void setPhaseWarp(float newwarp) { newwarp => phaseWarp; for(0=>int i;i c[cc].warps[i]; spork ~ sendCycler(); sendWarp(newwarp); } private void setEvent(SpokeEvent e) { e @=> outClik; } private void setHalfCycleEvent(Event e) { e @=> halfCycleClik; } private void setFullCycleEvent(Event e) { e @=> fullCycleClik; } /*** OSC Functions ***/ private void spoke_receive() { // create an address in the receiver, store in new variable recv.event( "/spoke, f f f f f i" ) @=> OscEvent oe; // infinite event loop while ( true ) { // wait for event to arrive oe => now; // grab the next message from the queue. while( oe.nextMsg() != 0 ) { oe.getFloat() => float intheta; oe.getFloat() => float inlen; oe.getFloat() => float insize; oe.getFloat() => float ininc; oe.getFloat() => float inwarp; oe.getInt() => int which; //<<>>; intheta => c[cc].times[which]; inlen => c[cc].lens[which]; insize => c[cc].caps[which]; ininc => c[cc].incs[which]; inwarp => c[cc].warps[which]; //need to sort here? so spokes times are all ascending.... } //c[cc].postCycler(); } } private void spokeNum_receive() { // create an address in the receiver, store in new variable recv.event( "/spokeNum, i" ) @=> OscEvent oe; // infinite event loop while ( true ) { // wait for event to arrive oe => now; //for (0=>int i;i c[cc].times[i]; // grab the next message from the queue. while( oe.nextMsg() != 0 ) { oe.getInt() => int temp; if(temp<1) 1 => temp; if(tempint i;i c[cc].times[i]; } } temp => c[cc].numSpokes; <<<"got new numSpokes: ", c[cc].numSpokes>>>; } //c[cc].postCycler(); } } private void tempo_receive() { // create an address in the receiver, store in new variable recv.event( "/tempo, f" ) @=> OscEvent oe; // infinite event loop while ( true ) { // wait for event to arrive oe => now; // grab the next message from the queue. //need to project against zeros here while( oe.nextMsg() != 0 ) { oe.getFloat() => loopDur; loopInc / loopDur => tempoMult; //<<<"got new tempo multiplier: ", tempoMult>>>; } } } private void globalTempo_receive() { // create an address in the receiver, store in new variable recv.event( "/gtempo, f" ) @=> OscEvent oe; // infinite event loop while ( true ) { // wait for event to arrive oe => now; // grab the next message from the queue. //need to project against zeros here while( oe.nextMsg() != 0 ) { oe.getFloat() => globalTempo; //<<<"got new global tempo multiplier: ", globalTempo>>>; } } } private void play_receive() { // create an address in the receiver, store in new variable recv.event( "/play, i" ) @=> OscEvent oe; // infinite event loop while ( true ) { // wait for event to arrive oe => now; // grab the next message from the queue. //need to project against zeros here while( oe.nextMsg() != 0 ) { oe.getInt() => playToggle; //0. => ctime; do this to reset //<<<"play: ", playToggle>>>; } } } private void playAll_receive() { // create an address in the receiver, store in new variable recvAll.event( "/play, i" ) @=> OscEvent oe; // infinite event loop while ( true ) { // wait for event to arrive oe => now; // grab the next message from the queue. //need to project against zeros here while( oe.nextMsg() != 0 ) { oe.getInt() => playToggle; //<<<"play: ", playToggle>>>; } } } private void restart_receive() { // create an address in the receiver, store in new variable recv.event( "/restart, i" ) @=> OscEvent oe; // infinite event loop while ( true ) { // wait for event to arrive oe => now; // grab the next message from the queue. //need to project against zeros here while( oe.nextMsg() != 0 ) { oe.getInt(); 1. => ctime => phase; //<<<"restart">>>; } } } private void restartAll_receive() { // create an address in the receiver, store in new variable recvAll.event( "/restart, i" ) @=> OscEvent oe; // infinite event loop while ( true ) { // wait for event to arrive oe => now; // grab the next message from the queue. //need to project against zeros here while( oe.nextMsg() != 0 ) { oe.getInt(); 1. => ctime => phase; //<<<"restart">>>; } } } private void msg_send() { xmit.startMsg("/cwarp", "f"); phaseWarp => xmit.addFloat; while(true) { xmit.startMsg( "/ctime", "f i"); ctime => xmit.addFloat; 1 =>xmit.addInt; xmit.startMsg("/cwarp", "f"); phaseWarp => xmit.addFloat; 10::ms => now; } } //send reverse msg public void reverse_send() { xmit.startMsg("/reverse", "i"); 1 => xmit.addInt; } //send load cycler message public void loadCycler(string filename) { xmit.startMsg("/load", "s"); filename => xmit.addString; } //send load cycler message public void loadCycler(string filename, int whichCycler) { // send object //OscSend xmit; if(whichCycler >= maxCyclers) { <<<"too many cyclers!">>>; return; } whichCycler => cc; //xmit.setHost( "localhost", sendPort ); //port should equal what we sent to cyc1 xmit.startMsg("/load", "s"); filename => xmit.addString; //<<<"sent message to Cyclotron for loading">>>; } private void sendWarp(float newWarp) { xmit.startMsg("/setwarp", "f"); newWarp => xmit.addFloat; } //send message to make GUI background change color private void flashSend(int val) { xmit.startMsg("/flash", "i"); val => xmit.addInt; } public void sendDphase(float newdphase) { xmit.startMsg("/setdphase", "f"); newdphase => xmit.addFloat; } public void sendCycler() { me.yield(); for(0=>int i; i xmit.addInt; c[cc].times[i] => xmit.addFloat; xmit.startMsg("/setspokelen", "i f"); i => xmit.addInt; c[cc].lens[i] => xmit.addFloat; xmit.startMsg("/setspokecap", "i f"); i => xmit.addInt; c[cc].caps[i] => xmit.addFloat; xmit.startMsg("/setspokeinc", "i f"); i => xmit.addInt; c[cc].incs[i] => xmit.addFloat; xmit.startMsg("/setspokewarp", "i f"); i => xmit.addInt; c[cc].warps[i] => xmit.addFloat; xmit.startMsg("/setspokenum", "i"); c[cc].numSpokes => xmit.addInt; xmit.closeBundle(); } //<<<"############### sent bundle #################">>>; } private void cycler_receive() { // create an address in the receiver, store in new variable OscRecv recvOut; //to monitor what we are sending out, testing.... sendPort + 2 => recvOut.port; recvOut.listen(); recvOut.event( "/setspoke, fffffii" ) @=> OscEvent oe; // infinite event loop while ( true ) { // wait for event to arrive oe => now; // grab the next message from the queue. while( oe.nextMsg() != 0 ) { // get x and y oe.getFloat() => float intheta; oe.getFloat() => float inlen; oe.getFloat() => float insize; oe.getFloat() => float ininc; oe.getFloat() => float inwarp; oe.getInt() => int which; oe.getInt() => int newNumSpokes; //<<>>; } } } }