And another one

Auction 3 got a bid, so I put up auction 5. Auction 4 is still bidless.

The bidder for #3 wanted to know if s/he could remix the track when done. What a fabulous idea! I told hir yes. It feels so collaborative. I love it.
The whole eBay process, though is kind of nerve wracking. What if nobody bids in the next 4 days and 22 hours? ack!. Note to self: eBay bids are not a good measure of self-worth.
I contacted an arts blog today about buying advertising space. I’m running out of ideas for free publicity, since I’ve gotten mention on most of the New Music blogs that I know of, I’ve posted on most of my email lists and I listed on Tribe. The publicity part of this project is kind of weird, but I see it as part of the project in a sort of conceptual-everythng-is-art kind of way.

OSC -> CV

In other news, I’ve been investigating interfaces between computers and analog information. I’ve ordered a nifty joystick brain and I’ve just been informed of a cool-looking open source device which can create control voltages to send to a synthesizer. DIY electronics are really big right now. And this is good for lazy people like me, because it means that people are designing and selling little boutique devices. So I don’t have to do my own designing. The Arduino is cheap, open source(!), and made by workers getting a living wage. It’s perfect and somebody has already written a SuperCollider interface. W00t. Now all I need is to decide whether to go with USB or bluetooth. Wireless synthesizer control with a modular might be a little silly, but is still tempting.

Subliminals, Timbre and Convolution

Recently, in Boing Boing, there was a post about a company marketing a subliminal message to gamers. They would hear the message 10000 – 20000 times a second. That’s 10 kHz – 20 kHz. Those repetitions are almost too high to be in the audio range! I can’t hear 20 kHz all that well. Also, what about scaling? To keep from peaking, the maximum amplitude of
each message would have to be between 0.00005 – 0.0001 of the total amplitude range. That’s pretty subliminal, all right.

I went to work trying to play a short aiff file over and over at that rate. My processor crapped out really fast. That’s a lot of addition. As I was falling asleep that night, I calculated that on a CD, each new message would start every 4 – 10 bytes! Why at that rate, it’s practically convolution.
Indeed, it is more than “practically” convolution, it is convolution and as such it doesn’t need to be done via real-time additions, but can be done via free software like SoundHack. The first step is getting a series of impulses. To try to create a “subliminal” message, you need a series of positive impulses that vary randomly between 10000 – 20000 times per second. I wrote a short SuperCollider program to produce such impulses.

SynthDef("subliminal-impulse", {arg out = 0;

 var white, ir;
 white = WhiteNoise.kr;
 white = white.abs;
 white = white * 10000;
 white = white + 10000;
 ir = Dust.ar(white);
 Out.ar(out, ir);
 
}).play 

The WhiteNoise.kr produces random values between -1 and 1. We take the absolute value of that to just get numbers between 0 – 1. Then we multiply, to make them numbers between 0 – 10000 and add to put them in the range 10k – 20k.
Dust makes impulses at random intervals. The impulses are between 0 – 1. The argument is the average number of impulses per second. So Dust makes 10k – 20k impulses per second. Record the output of that to disk and you’ve got some noise, but it’s noise with some important characteristics – all the impulses are positive and they have zeros between them. This is what we need if we’re going to be subliminal at gamers.
Ok, so I’m going to take that file and open it SoundHack and save a copy of it as a 16bit file, rather and a 32 bit file. Then I’ll split the copy into separate mono files. (This is all under the file menu.) then, to save disk space, I’ll throw away the 32 bit file and the silent right channel. So now I have a 16bit mono file full of impulses open in SoundHack
Under the Hack menu, there’s an option called “Convolution.” Pick that. Check the box that says “Normalize” (that will handle the amplitude for you so the result is neither too quiet or too loud) and then hit the button that says “Pick Impulse.” This will be our recording of spoken text that we want made subliminal. (Fortunately, I had such a message at hand.) In actuality, it doesn’t matter which file is the one with the clicks and which is the one with the text. Convolution treats both files as equal partners. Then it asks us to name the output file. Then it goes, then we’re done. Here’s my result.
If you suddenly feel like forming a militia or running in fear, then it worked. If not, well, the sonic result is still kind of interesting. The timbres are all totally present but the actual sound events are unintelligible (at least to the conscious mind). For every one of our little impulses created by Dust.ar, we’ve got a new copy of Jessica plotting revolution. (The text is actually from Lesbian Philosophy: Explorations by Jeffner Allen (Palo Alto: Institute of Lesbian Studies, 1987) and the piece I originally made with it is here.)
This is actually a lot like granular synthesis, if you think about it. Imagine that instead of convolving the whole audio file, we just did 50ms bits of it. Every impulse would start a new copy of the 50 ms grain, but instead of with additions, with FFTs, which are faster – we can have many, many more grains. And they could be smaller and still be meaningful. Heck, they could be the size of the FFT window.
The FFT version of a convolution involves taking a window of the impulse and another of the IR (our subliminal message – normally known as an impulse response). You add the phases together and multiply the amplitudes. The amplitudes multiplications give us the right pitch and the phase addition gives us the right timing – almost. Some additions will be too big for the window and wrap around to the beginning. You can avoid that by adding zero padding. You double the size of the window, but only put input in the first half. Then none of your phases will wrap around.
We can get some very granular like processes, but with nicer sound and better efficiency. For example, time stretching. We could only update the IR half as often as the impulse stream and do window-by window convolutions. There are other applications here. I need to spend time thinking of what to do with this. Aside from sublimating revolution.

PlayPlay

Time domain frequency tracking for voice and tuba

A voice-like waveform, lovingly rendered in drawing software
There are certain timbres which are most easily pitch tracked in the time domain, rather than the frequency domain. The tuba is one such timbre. It, like the human voice, tends to have one large impulse followed by several smaller impulses. The large impulse is the fundamental frequency. The following little hills are overtones and formants and what not. For the tuba, every large impulse is caused by a single buzz of the lips. Lips flap open, impulse happens, impulse echos, lips flap open again, another impulse, more echoes inside the horn. Similarly, your vocal chords work in the same way. The vibrate like buzzing lips and echo in your head. So the fundamental frequency of your voice is the frequency of the large impulses, not the smaller ones that follow.

So to know the pitch, all you have to do is know how often those peaks come. How do you recognize the peaks? Well, they certainly spike up above the average amplitude, while also raising the amplitude. A good amplitude following algorithm is the Root Mean Square. Since the tuba and the voice have low fundamental frequencies (220 Hz for a typical female voice and the tuba gets down around 40 Hz and possibly below), it’s important to have a long enough window length for the RMS. You want to get enough samples such that you don’t have false positives.
Then, you can subtract the RMS from the original frequency. All but the high peaks will drop below 0. Then count the zero crossings. You’ll have to divide by two, since each peak crosses zero twice: once on the way up and once on the way down.
If you suspect that all of your pulse energy is negative for some reason, you have two options: You can try multiplying your original signal by -1 before subtracting. Or you can take the absolute value of the signal and use that.
Here’s some sample code for SuperCollider. Use headphones to prevent feedback and then sing into your computer. If your voice is low, you may need to adjust the window size. Note that it’s given in samples.

  SynthDef("test-time-domain-freq-tracker", { arg in, out, rmswindow = 200;
 
  var rms, xings, inner, peaks, sin;
  
  inner = AudioIn.ar(in, 1);
  
  rms = (RunningSum.ar(inner.squared, rmswindow)/rmswindow).sqrt;
  
  peaks = inner - rms;
  
  xings = ZeroCrossing.ar(peaks);
  
  sin = SinOsc.ar(xings/2);
  
  Out.ar(out, sin);
  
 }).send(s);

Tags: ,

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?