How to write a SC plugin / Biquad filter!!!

The tutorials in the SC distro are not really great on how to do plugins, so here’s my version. After you get the SC source code (an exercize left to the reader), you’re going to want to build your own copy of SC. This should not be the same copy that you use for doing your music, because development tends to break things and you don’t want to break your instrument. First build the Server, then the Plugins, then the Lang.
Once you’ve done that, open the plugin project with Xcode. In the Project Window, pick a target and ctrl-click on it to duplicate it. Then option click the new target to rename it to [whatever]. Double click on it to bring up the target inspector. In the summary, rename Base Product Name to [whatever].scx .  Then click on “Settings” in the list on the left. Change Product Name to [whatever].scx .  Close the target inspector menu and go back to the project menu. Drag your new target into the list for All, so it gets built when you build all.

Ctrl-click on your new target again to Add. Add a new C++ file. Don’t generate a header file for it. This is my example, a biquad filter:

/*
 *  LesUGens.cpp
 *  xSC3plugins
 *
 *  Created by Celeste Hutchins on 16/10/06.

    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  USA
*/

// all plugins should include SC_Plugin.h
#include "SC_PlugIn.h"

// and this line
static InterfaceTable *ft;

// here you define the data that your plugin will need to keep around
// biquads have two delayed samples, so that's what we save

struct Biquad : public Unit
{

    // delayed samples
    float m_sa1;
    float m_sa2;
};


// declare the functions that your UGen will need
extern "C"
{
 // this line is required
 void load(InterfaceTable *inTable);

 // calculate the next batch of samples
 void Biquad_next(Biquad *unit, int numsamples);
 // constructor
 void Biquad_Ctor(Biquad* unit);
 
}

//////////////////////////////////////////////////////////////////////////////////////////////////


// Calculation function for the UGen.  This gets called once per x samples (usually 66)

void Biquad_next(Biquad *unit, int numsamples) 
{

 // pointers to in and out
 float *out = ZOUT(0);
 float *in = ZIN(0);
    
 // load delayed samples from our struct
 float delay1 = unit->m_sa1;
 float delay2 = unit->m_sa2;

 // the filter co-efficients are passed in.  These might change at the control rate,
 // so we re-read them every time.
 // the optimizer will stick these in registers
 float amp0 = ZIN0(1);
 float amp1 = ZIN0(2);
 float amp2 = ZIN0(3);
 float amp3 = ZIN0(4);
 float amp4 = ZIN0(5);
 
 
 float next_delay;
 
 // This loop actually does the calculation
 LOOP(numsamples,

  // read in the next sample
     float samp = ZXP(in);

  // calculate
     next_delay = (amp0 * samp) + (amp1 * delay1) + (amp2 * delay2);

  //write out result
     ZXP(out) = next_delay - (amp3 *delay1) - (amp4 * delay2);

  // keep track of data
     delay2 = delay1;
     delay1 = next_delay;
     
 );
 
 // write data back into the struct for the next time
 unit->m_sa1 = delay1;
 unit->m_sa2 = delay2;
 
}


// The constructor function
// This only runs once
// It initializes the struct
// Sets the calculation function
// And, for reasons I don't understand, calculates one sample of output

void Biquad_Ctor(Biquad *unit)
{

 // set the calculation function
    SETCALC(Biquad_next);
    
 // initialize data
    unit->m_sa1 = 0.f;
    unit->m_sa2 = 0.f;

 // 1 sample of output
    Biquad_next(unit, 1);

}

// This function gets called when the plugin is loaded
void load(InterfaceTable *inTable)
{

 // don't forget this line
    ft = inTable;

 // Nor this line
    DefineSimpleUnit(Biquad);
}

Ok, when you build this, it will get copied into the plugin directory. But that’s not enough. The SCLang also needs to know about your new UGen. Create a new class file called [whatever].sc .&nbsp You can stick this in your ~/Library, it won’t mess up your other copies of SuperCollider. This is my file:

Biquad : UGen {

 *ar { arg in, a0 = 1, a1 = 0, a2 =0, a3 =0, a4 =0, mul = 1.0, add = 0.0;
  ^this.multiNew('audio', in, a0, a1, a2, a3, a4).madd(mul, add)
 }
 
 *kr { arg in, a0 = 1, a1 = 0, a2 =0, a3 =0, a4 =0, mul = 1.0, add = 0.0;
  ^this.multiNew('control', in, a0, a1, a2, a3, a4).madd(mul, add)
 }
 
}

The multiNew part handles multiple channel expansion for you. The .madd ads the convenience variables mul and add. Your users like to have those.
I don’t know if a biquad filter comes with SC or not. I couldn’t find one. They’re useful for Karplus-Strong and a few other things. For more information, check out the wikipedia article on Filter Design
Tags: ,

HID, SuperCollider and whatnot

My HID classes are now in version 1.0 and I’m not going to change them again without good reason. The change is that the call back action passes the changed element back as the first parameter, followed by all the other parameters. It’s redundant, but it matches how the subclasses work. This should be usable for HID applications. a helpfile will be forthcoming.
I am trying to write a Biquad filter UGen and also to compile a FFTW UGen that my friend wrote. Jam fails when I try to compile, on both files. I think there is a step missing in the howto document on Ugen writing. My code is simple and the syntax looks ok. I’ve done every step (as far as I know) in the howto. bah. I could use a better error message than “jam failed.” sheesh. I like XCode, but it’s got nothing on the Boreland compilers I used to use back in the 90’s as far as usability and usefulness of error messages.
Workaround: Take an existing target and duplicate it and use that for your target. Rename it and replace the files in it with your own. Ctrl-click on the target to bring up a menu with the option to duplicate.
Tags: ,

Tutorials

The world has been crying out for my old SuperCollider tutorial. Well, not crying out, exactly. Some of you may recall that I had the idea of a doing a tutorial as a thesis project. My advisor said it was disorganized and error-riddled, and so the project was abandoned. However, some stranger on the internet convinced me to send it to him.
This stranger was my host for my first two weeks here (the house with no hot water). His name is Jeremiah and he’s cool. Anyway, he told me that he liked the tutorial and I should put it on the internet. So here you go. It’s incomplete and disorganized. The errors aren’t serious. (Lines of code are separated by semicolons, not terminated: that means that the last line in any block doesn’t need a semi colon, but can have one anyway if you want. Blocks are not defined by parenthesis, but rather by curly brackets or by highlighting code with the mouse. These are the two most glaring errors. All the examples should work.)

Edit

Tutorials have moved to http://www.berkeleynoise.com/celesteh/podcast/?page_id=65 . Please update your links.

HID Update

Download a new version of my HID classes.
So I think some of my original features were overzealous, so some of them have been removed. For instance, no more specifying a ControlSpec for an HIDElementExt. If you want to use a ControlSpec, you should attach the HIDElementExt (or HIDElement Group) to a CV by means of the action. Remember that CVs are really useful objects. See their help file for more information. For example of how to attach one:

element.action_({arg vendorID, productID, locID, cookie, value;

 var scaled, mycv;
     
 scaled = element.scale(value);
         
 mycv = cvDictionary.at(element.usage.asSymbol);
 mycv.input_(scaled);
});

Note the name and order of arguments, as this is a change. In this example, cvDictionary is an IdentityDictionary where CVs are stored according to the element.usage as a key. scale is a message you can pass to an element in which a value is scaled according to the min and max to be a number between 0 and 1. You can still set the min and max of an element based on what you’ve discovered to be the range of your HID, regardless of what lies the device may whisper in your ear about it’s ranges.
The HIDDeviceExt.configure message was not really needed, so it’s gone.
Here is some code to automatically generate a GUI for every HID that you have attached. Note that you will need the Conductor class for it, which is part of the Wesleyan nightly build. This GUI will give you one line for every reported piece of the device. You can then try moving everything around to see what their actual ranges are (if they are smaller than reported) and the correlation between reported names and the elements.

(

 var elems, names, cv, conductor;

 HIDDeviceServiceExt.buildDeviceList;
 
 HIDDeviceServiceExt.devices.do({ arg dev;
 
  elems = [];
  names = [];

  dev.queueDevice;

  dev.elements.do ({ arg elm;
  
   cv = CV.new.sp(0, elm.min, elm.max, 0, 'linear');
   elems = elems.add(cv);
   names = names.add(elm.usage.asSymbol);
   elm.action_({ arg vendorID, productID, locID, cookie, value;
     
    var scaled, mycv;
     
    scaled = elm.scale(value);
         
    mycv = conductor.at(elm.usage.asSymbol);
    mycv.input_(scaled);
   });

  });
 
  conductor = Conductor.make({ arg cond; 
  
   elems.do({ arg item, index;
   
    cond.put(names[index], item);
   });
   
   cond.argList = cond.argList ++ elems;
   cond.argNames = cond.argNames ++ names;
   cond.valueItems = cond.valueItems ++ names;
   cond.guiItems = cond.valueItems;
  });
  
  conductor.show;
  
 });
 
 HIDDeviceServiceExt.runEventLoop;

)

Then stop it with:

HIDDeviceServiceExt.stopEventLoop;

But what if the returned values are outside of the range? And what are the cookies? You should then generate a little report to correlate everything.

(
 HIDDeviceServiceExt.devices.do({arg dev;
  [dev.manufacturer, dev.product, dev.vendorID, dev.productID, dev.locID].postln;
  dev.elements.do({arg ele;
   [ele.type, ele.usage, ele.cookie, ele.min, ele.max, ele.minFound, ele.maxFound].postln;
  });
 });
)

You can use the minFound and maxFound data to calibrate the device.
This is my current code to configure my (crappy) joystick:

HIDDeviceServiceExt.buildDeviceList;
HIDDeviceServiceExt.queueDeviceByName('USB Joystick STD');

(

 var thisHID, simpleButtons, buttons, stick;
 
 thisHID = HIDDeviceServiceExt.deviceDict.at('USB Joystick STD');
 simpleButtons = HIDElementGroup.new;
 buttons = HIDElementGroup.new;
 stick = HIDElementGroup.new;
  
 thisHID.deviceSpec = IdentityDictionary[
   // buttons
   trig->7, left->9, right->10, down->8, hat->11,
   // stick
   x->12, y->13,
   wheel->14,
   throttle->15
  ]; 
  
  
 simpleButtons.add(thisHID.get(trig));
 simpleButtons.add(thisHID.get(left));
 simpleButtons.add(thisHID.get(right));
 simpleButtons.add(thisHID.get(down));
  
 buttons.add(simpleButtons);
 buttons.add(thisHID.get(hat));
  
 stick.add(thisHID.get(x));
 stick.add(thisHID.get(y));
  
 // make sure each element is only lsted once in the nodes list, or else it
 // will run it's action once for each time a value arrives for it
  
 thisHID.nodes = [ buttons, stick, thisHID.get(wheel), thisHID.get(throttle)];
  
 // When I was testing my joystick, I noticed that it lied a bit about ranges
  
 thisHID.get(hat).min = -1;  // it gives back -12 for center, but -1 is close enough
  
 // none of the analog inputs ever went below 60
  
  
 thisHID.get(wheel).min = 60;
 thisHID.get(throttle).min = 60;
 // can set all stick elements at once
 stick.min = 60;
)

Setting the min and the max for each element is used for the scale message. If you don’t want to bother scaling the values to be between 0 and 1 (or some other range) and you set the threshold to 0, then you can skip setting the min and the max.
Here is a silly example of using the x and y axis of my joystick to control the frequency and amplitude of a Pbind in a GUI. Note that it would work almost identically without the GUI.

(

 HIDDeviceServiceExt.runEventLoop;

 Conductor.make({arg cond, freq, amp;
 
  var joystick;
 
  freq.spec_(freq);
  amp.spec_(amp);
  
  joystick = HIDDeviceServiceExt.deviceDict.at('USB Joystick STD');
  
  joystick.get(y).action_({ arg vendorID, productID, locID, cookie, value;
  
   freq.input_(joystick.get(y).scale(value));
  });
  
  joystick.get(x).action_({ arg vendorID, productID, locID, cookie, value;
  
   amp.input_(joystick.get(x).scale(value));
  });
  
  cond.name_("silly demo");
  
  cond.pattern_( Pbind (
  
   freq, freq,
   amp, amp
  ));
  
 }).show;
  
)

Don’t forget to stop the loop when you’re done:

HIDDeviceServiceExt.stopEventLoop;

As you may have guessed from this EventLoop business, HIDs are polled. Their timing, therefore, may not be as fast as you’d like for gaming. They may be more suited to controlling Pbinds like in the silly example, rather than Synth objects. You will need to experiment on your own system to see what will work for you.
The HIDElementExt all have a threshold of change below which they will not call their actions. This defaults to 5%, which is a good value for my very crappy joystick. You can set your own threshold. It’s a floating point number representing a percent change, so you can put it between 0 and 1. Again, you may want to experiment with this. Most HIDs have a little bit of noise and flutter where they report tiny actions where none occurred. Also, do not buy a 5€ joystick from a sketchy shop. It’s range will suck and it will will flutter like crazy and it will be slow and you will end up buying a more expensive joystick anyway.

joystick.threshold_(0.01); // 1% 

Tags: , ,

HID, Joysticks and SuperCollider

So, game controllers and joysticks and the like are called Human Interface Devices (HID). There is some HID stuff built into SuperCollider, but it seemed sort of half implemented. They let you specify things, but not do much with it. So I wrote an extension yesterday, which maybe I should submit as something official or a replacement, although I didn’t bother checking first if anybody else had already done anything. No internet access at home sucks.

To use it, download HID.sc and put it in ~/Library/Application Support/SuperCollider/Extensions. Plug in your joystick or controller. Start SuperCollider.
First, you want to know some specifications for your device, so execute:


HIDDeviceServiceExt.buildDeviceList;

  
(
HIDDeviceServiceExt.devices.do({arg dev;
  [dev.manufacturer, dev.product, dev.vendorID, dev.productID, dev.locID].postln;
  dev.elements.do({arg ele;
   [ele.type, ele.usage, ele.cookie, ele.min, ele.max].postln;
  });
});
)

I got back:

  [ , USB Joystick STD, 1539, 26742, 454033408 ]
  [ Collection, Joystick, 1, 0, 0 ]
  [ Collection, Pointer, 2, 0, 0 ]
  [ Button Input, Button #5, 3, 0, 1 ]
  [ Button Input, Button #6, 4, 0, 1 ]
  [ Button Input, Button #7, 5, 0, 1 ]
  [ Button Input, Button #8, 6, 0, 1 ]
  [ Button Input, Button #1, 7, 0, 1 ]
  [ Button Input, Button #2, 8, 0, 1 ]
  [ Button Input, Button #3, 9, 0, 1 ]
  [ Button Input, Button #4, 10, 0, 1 ]
  [ Miscellaneous Input, Hatswitch, 11, 0, 3 ]
  [ Miscellaneous Input, X-Axis, 12, 0, 127 ]
  [ Miscellaneous Input, Y-Axis, 13, 0, 127 ]
  [ Miscellaneous Input, Z-Rotation, 14, 0, 127 ]
  [ Miscellaneous Input, Slider, 15, 0, 127 ]
  [ a HIDDevice ]

Ok, so I know the name of the device and something about all the buttons and ranges it claims to have. (Some of them are lies!) So, I want to assign it to a variable. I use the name.

j = HIDDeviceServiceExt.deviceDict.at('USB Joystick STD');

Ok, I can set the action for the whole joystick to tell me what it’s doing, or I can use the cookie to set the action for each individual element. I want to print the results. In either case, the action would look like:

x.action = { arg value, cookie, vendorID, productID, locID, val;
 [ value, cookie, vendorID, productID, locID, val].postln;
};

You need to queue the device and start the polling loop before the action will execute.

HIDDeviceServiceExt.queueDeviceByName('USB Joystick STD');
HIDDeviceServiceExt.runEventLoop;

It will start printing a bunch of stuff as you move the controls. When you want it to stop, you can stop the loop.

 HIDDeviceServiceExt.stopEventLoop; 

Ok, so what is all that data. The first is the output of the device scaled to be between 0-1 according to the min and max range it reported by the specification. The next is very important. It’s the cookie. The cookie is the ID number for each element. You can use that to figure out which button and widget is which. Once you know this information, you can come up with an IdentityDictionary to refer to every element by a name that you want. Mine looks like:

  j.deviceSpec = IdentityDictionary[
   // buttons
   a->7, left->9, right->10, down->8, hat->11,
   // stick
   x->12, y->13,
   wheel->14,
   throttle->15
  ]; 

The last argument in our action function was the raw data actually produced by the device. You can use all your printed data to figure out actual ranges of highs and lows, or you can set the action to an empty function and then restart the loop. Push each control as far in every direction that it will go. Then stop the loop. Each element will recall the minimum and maximum numbers actually recorded.

(
j.elements.do({arg ele;
   [ele.type, ele.usage, ele.cookie, ele.min, ele.max, ele.minFound, ele.maxFound].postln;
  });
)

This gives us back a table like the one we saw earlier but with two more numbers, the actual min and the actual max. This should improve scaling quite a bit, if you set the elements min and max to those values.   ele.min = ele.minFound;   Speaking of scaling, there are times when you want non-linear or within a different range, so you can set a ControlSpec for every element. Using my identity dictionary:

j.get(throttle).spec = ControlSpec(-1, 1, step: 0, default: 0);

Every element can have an action, as can the device as a whole and the HIDService as a whole. You can also create HIDElementGroup ‘s which contain one or more elements and have an action. For example, you could have an action specific for buttons. (Note that if you have an action for an individual button, one for a button group and one for the device, three actions will be evaluated when push the button.) Currently, an element can only belong to one group, although that might change if somebody gives me a good reason.
This is a lot of stuff to configure, so luckily HIDDeviceExt has a config method that looks a lot like make in the conductor class. HIDDeviceExt.config takes a function as an argument. The function you write has as many arguments as you want. The first refers to the HIDDeviceExt that you are configuring. The subsequent ones refer to HIDElementGroup ‘s. The HIDDeviceExt will create the groups for you and you can manipulate them in your function. You can also set min and max ranges for an entire group and apply a ControlSpec to an entire group. My config is below.

 j.config ({arg thisHID, simpleButtons, buttons, stick;
  
  thisHID.deviceSpec = IdentityDictionary[
   // buttons
   a->7, left->9, right->10, down->8, hat->11,
   // stick
   x->12, y->13,
   wheel->14,
   throttle->15
  ]; 
  
  
  simpleButtons.add(thisHID.get(a));
  simpleButtons.add(thisHID.get(left));
  simpleButtons.add(thisHID.get(right));
  simpleButtons.add(thisHID.get(down));
  
  buttons.add(simpleButtons);
  buttons.add(thisHID.get(hat));
  
  stick.add(thisHID.get(x));
  stick.add(thisHID.get(y));
  
  // make sure each element is only lsted once in the nodes list, or else it
  // will run it's action once for each time a value arrives for it
  
  thisHID.nodes = [ buttons, stick, thisHID.get(wheel), thisHID.get(throttle)];
  
  // When I was testing my joystick, I noticed that it lied a bit about ranges
  
  thisHID.get(hat).min = -1;  // it gives back -12 for center, but -1 is close enough
  
  // none of the analog inputs ever went below 60
  
  
  thisHID.get(wheel).min = 60;
  thisHID.get(throttle).min = 60;
  // can set all stick elements at once
  stick.min = 60;
      
  // ok, now set some ControlSpecs
  
  thisHID.get(hat).spec = ControlSpec(0, 4, setp:1, default: 0);
  // set all binary buttons at once
  simpleButtons.spec = ControlSpec(0, 1, step: 1, default: 1);
  
  // all stick elements at once
  stick.spec = ControlSpec(-1, 1, step: 0, default: 0);
  thisHID.get(wheel).spec = ControlSpec(-1, 1, step: 0, default: 0);
  thisHID.get(throttle).spec = ControlSpec(-1, 1, step: 0, default: 0);
  
  thisHID.action = {arg value, cookie, vendorID, productID, locID, val;
  
   [value, cookie, vendorID, productID, locID, val].postln;
  };
   
 });

I wrote all of this yesterday, so this is very demo/pre-release. If something doesn’t work, let me know and I’ll fix it. If people have interest, I’ll post better versions in the future.
Tags: , ,

Scitilopolitics

Celeste Hutchins
Music 222
Final Project Notes

Scitilopolitics

Program notes:

Researchers have discovered that if they take the syllables of a word and play them backwards, but in the correct order, people will be unable to hear the reversal. This piece explores how backwards things have to be, before you can hear it.
The male voice is George Bush. The female voice is Jessica Feldman reading text from Jeffner Allen, Lesbian Philosophy: Explorations (Palo Alto: Institute of Lesbian Studies, 1987)

Essay:

I wanted to write something that could only be done with a computer, so granular synthesis seemed link an obvious choice. My friend posted about the syllable reversing thing in his blog several weeks ago, so I thought I should try that. I decided to use Bush, because everything he says is so very backwards. I searched CNN.com for aiff files of Bush speaking and only found two good ones. One was him speaking about the ABM treaty, but my wife just wrote a piece using that one (also premiering 8:00 p.m. Dec 10th, but in Paris), and didn’t want me to use it. The other one is the one I am using, where he gives a speech about terrorism and destroying American culture. One of the students in MUSC 220 used the same audio clip for a different sort of tape project. I had been thinking about the subtext of the speech since hearing that project and about how to make Bush’s real message – his desire to destroy pop culture – clear. Repeated listening, which this piece contains, helps get people hear the real message behind the seeming non-sequiturs of the presidential speech. To make it clearer, I splatter key phrases, using the same reversal algorithm, out to any one of the 4 speakers. As the piece progresses, I add additional sound-bites, from the ABM treaty speech and from press conferences where Bush talks more about foreign policy.
After Bush winds down, I launch the contrary text from Allen’s book. I run the algorithm in the opposite direction, because I take the opposite view of the words. Allen also talks about violence, terrorism and victimhood, but unlike Bush, everything she says is true and real. Her words are ultimately empowering to her reader, giving her readers freedom instead of taking it away. Her viewpoint is equally extremist, but exists in reaction to the sort of evil that Bush proposes.
Also, I find that listening to Bush talk about destroying culture for 5 minutes makes me very tense and Jessica’s soothing voice talking about women uprising against men is an antidote to Bush’s evil rhetoric.

Technical:

I put the splattering in a routine, because I found it hard to fight my impulse to send out bushisms in all directions as key words popped up. The texture was always too dense. and I thought it would better to not necessarily have the highlighted text match what was just said. Doing a computer implementation was much easier than teaching myself to play the piece. The Allen quotes at the end are still manually triggered, as it’s easier to manually put them in the right spot than to get the computer to do it.
I always have an instinct to generalize software that I’ve written so it could take any audio files and do the same piece, or make it very general so it could do a number of related pieces. This is not always a good instinct, although the reversing routine might come in useful. It is already stand-alone. The maxTimesThroughLoop variable may not be useful going in reverse. Would you want your loop to start from the largest possible grain and run N times? Or start from N loops from the smallest possible grain? My instinct is that the second case would be more useful, but the first case is what would happen currently.
The weighted averages of buf2 in the splatter routine are kludgy. While the splattering code works, I wouldn’t want to invite it to dinner parties. The three while loops are especially awful. If I want to do more with this piece, I would fix the splattering. But for now, it works. My old boss used to say, “worse is better,” as in, it was better to release something that worked than work forever to make the most pristine thing in the world. You could fix it later. He also used to go through and remove comments from code, saying that the code itself was truth and comments were distorting, so I don’t think he always gave the best advice on programming. If someone took out the comment around figuring out what number should reside in timesthroughLoop, for example, I would be hopelessly confused.

postmortem – blog comments

The concert was sparsely attended. only around 4 spectators came. my piece crashed right near the end of the George Bush section, so the radical feminist text was not played at all. Ron, the prof, said that Ashcroft had gotten in my computer. I was using a different computer than i had used to test and develop (and compile the intrepretter on) the piece, and i think that may have been a factor. so i’m going to get a laptop this week. as a student, i won’t have to pay for it for a long time. i think i can pay it off over a year with an interest free loan.
and christi did Working Girl instead of the ABM tresty piece, perhaps due to a shortage of elephant samples. I will be going to the library on monday to return my interlibrary loan books, so if i take my brain with me, i’ll check out some elephant tapes.

More Moo junk

My advisor was not particularly thrilled with my midterm project. he said it was more of a technical demonstration than a piece of music. he had many many many suggestions for insanely complicated revisions. for some reason, instead of doing important homework, I implemented one of these.

He said that setting up mp3 streaming (something i intend to do soon) would not build community, it would just sort of be out there on the internet. The way to get users excited it to allow them to run their own copies of supercollider on their own machines.

so are you excited? you can now download a copy of supercollider and use it to play the same sounds that you would hear via mp3 streaming but as somewhat higher quality. Instructions are located here: http://www.xkey.com/~celesteh/computers/moosong.html
For those of you who have no experience with the technology: you will require mac osx. download the software and then download my program. Start the software. Use it to open my program. At the stop of the program, you will see a single open parenthesis. double click to the right of the parenthesis. The block of code encased by that set of parents with be highlighted. Hit the enter key while the text is highlighted. NOT return. enter. Ok, something should happen. after it settles down again, go to the next open parenthesis below it. It’s around a synthdef statement. double click to the right of that open paren and hit enter. the window that has text messages sometimes printing in it will say something like “a synthdef.” Ok, now go to the next open paren and do the same thing to send the next synthdef to the server.
Now you are ready to run the program. highlight all the text in the entire file below the closed parenthesis of the second synthdef. hit enter. my program is now running. now log into the moo and register your ip address. (instructions for this are in the link above)
Isn’t this much better than mp3 streaming?

Writing MOO themes

Ok, so my moo theme playing thingee works. it’s not mp3 streaming yet, but it will be very soon. (i hope. depending on how hard it is.) why not prepare for the web radio launch by writing your themes now?

Notes

Notes look like “4c#3”. the first number is the length of the note. ‘4’ indicates a quarter note. ‘8’ and eighth note. you can use any number at all that you want for the duration. bigger numbers are shorter than smaller numbers.
the ‘c#’ is the pitch. The ‘#’ can go before or after the letter. You can’t currently specify a flat, so use a sharp if you want an accidental. using sharps, is, of course, optional. note names go from a-g.
The ‘3’ is the octave. the lowest octave is ‘0’. the highest octave is as high as you want to make it, but if you get too high, you won’t be able to hear it. Octaves start with c, so the note below 4c3, is 4b2.
What notes to use? Just make some up. Or you could have your theme say something about your player or object in SolReSol. AOL_USER’s theme is SolReSol for “girl, I want your body.” seriously. Also, astute cell phone users will note that this is the same format for notes used by nokia, so you could go to a ring tones web site and copy some notes from a pop song or something

Adding the notes

you’ve got yer notes all picked out, now you want to add them.

@property me.theme "4c#3, 4c3, 3b2, 4c#3"
@chmod me.theme +r

That example puts a theme on your player object and marks it readable. you need to make it readable, or the program won’t be able to read it. If you want to add a theme to some other object, just replace “me” with the name of the object.
If you finally hear your theme and can’t stand it, you can change it.

@set smoothie.theme to "8a#2, 8#a2, 4c#3, 8a#2, 2d#3, 2f#3, 4c#3"

You could also write some verb to change your themes periodically. in the future, there will be support for a get_theme verb, so that you can modify your theme every time the verb is called.

More moo documentation

I have docs! check out http://www.xkey.com/~celesteh/computers

Moo themes

Ok, so I was looking at modifying the source code of SuperCollider to make network connections . . . what a nightmare! So instead, there will be a perl program, that SC will execute that will handle all of the text stuff and the network stuff and everything that supercollider isn’t good at. so the perl thing will do everything but make sound. (Note: maybe someone ought to add sound capablities to Perl. An OSC lib, maybe?)
Players will have the options of attaching arrays to themselves caled themes. So you would need to create a property on yourself called a theme and then put stuff in it. This theme will look *cough* just like a ring tone on a Nokia phone. Some of you have experience with cell phone ring composers and will be able to hear your theme on a phone before comiting it to yourself. I just wrote a ring tone. It goes 4d1 4a1 4b1 2c2 32d2 32#f2 8a2 8f2 8f2 16d2 16f2. It’s pretty jazzy at the end.

Anatomy of a Nokia Composition

the first number is the note duration. It is the denonominator of a fraction whose numerator is 1. So smaller numbers are faster. In musical terms, a 1 is equivalent to a whole note, 2 a half note, 4 a quarter note, 8 and 8th note, etc.
Then comes an optional # and then the note name. There are no flats. but that’s ok because in equal temperment Bb = A#. but since it’s backwards, in this case it equals #a.
The last number indicates the octave. A low number is a low octave. A d2 is an octave higher than a d1.

Garunteed support changes

You will be able to use any number, not just a power of 2, to denote duration, but it will still be in denominator format. therefore a starting number of 3 will be meaningful (in musical terms, it would be a half note triplet, since our notation isn’t so good at divisions by 3). You will be able to specify b for flat or # for sharp. Equal temperment may not be supported. Because an optional ‘b’ means flat, all the note names must be in upper case. and the final integer specifyin octave will be optional (if you don’t specify, then the program will pick an octave and your theme may get moved around, from high to low). you can pick an arbitrarily large or small octave number. the low threshold of human hearing is around 20 Hz, so octave 0 will be down there someplace. the high threshold of human hearing is around 20000 Hz, so figure out what octave number that is and that’s you upper lmit. Extremely high pitched sounds are likely to be played at a much lower volume.

yeah, yeah, ringtones

I’m not crazy about cell phone, or their ringtones, but I have to admit that this has cross-fertalization possibilities. Some phones allow you to specify different rings for different people in your addressbook. Also, some websites have the ring tones for different songs up on them. My phone plays the Indiana Jones theme song, for example. there are Ives-ian possibilities lurking danegerously around.
telnet happy.xkey.com 3333

Moo song

I’ve decided that my midterm SuperCollider project will be a program that logs into the MOO (telnet xkey.com:3333) and interprets MOO text somehow into sounds. for this, I will need to create a $room, which will be called the Music Studio and a $player for my SuperCollider program to use. It would be better if the MOO and SuperCollider could communicate through some other means, besides a fake player. But I don’t think MOO objects are allowed to make network connections and I can’t think of another way to log in. (If you know otherwise, please let me know.)
anyway, the SuperCollider program will log into the MOO and from the text, create sounds. There’s a few ways to do this. I could scan the text for particular key words, which would cause particular actions. for instance, the word “teleport” usually signifies that a new player has come in, and so when “teleport” comes across the network connection, some particular responce could occur. Or, I could do statistical analysis on the network text, changing the texture of the music based on the frequency and length of the received text. for instance, a lot of text arriving quickly would indicate that there’s a lot of activity on the MOO and perhaps the program could respond by creating a dense texture of sounds. another thing that could happen is that $plays (and other objects) could have an optional “themes” property, which would contain information meaningful to the SuperCollider patch, so it could look at your theme property and, in Wagnerian style, play your theme while you are in the room.
If you have any suggestions or ideas about how you would musically represent MOO communications, you should pass them along. to be fair, the patch output will have t be streamed, so $players can listen. so think also of what musical noises might convey useful information. If you have a desk job and can sit with headphones on, would you like to hear it play something that tells you that somebody just logged on or one of your friends is talking?
One of the local guys is giving me and old PC with linux on it, so I can set up a test streaming server. Alas, it will probably not have a fixed IP address until it passes a certain amount of testing, since xkey must be stable. Or, Mitch could put a stable RedHat PC in the CN kitchen that could get nightly builds put up on it. The PC currently there is having some sort of disk problem.
This project probably wouldn’t work well for Stony Brook, but that’s ok. I have a final project after this one.