Archive for the ‘ Programming ’ Category

Using OSC, Processing, and a Microcontroller to connect Max/MSP and a DAC IC.

My most recent project, the USB-Octomod, uses Processing to create an OpenSoundControl (OSC) interface between any OSC-ready software and a hardware DAC device I built. I’m going to break down the connections between different pieces of software and hardware, in order to explain how the system works and to provide the basis for a future tutorial on how one might use the device.

You can read more about the Octomod here, but it essentially allows computer control over the analog control voltages commonly used in analog synthesizers. Input a number 0 – 1023, and the device will output an analog voltage from -5V to +5V.

The OSC interface presents the inputs to the device, in the form of 8 numbered channels. The user sends an OSC message from their software of choice, and the interface program receives, processes, and communicates the data to the microcontroller in the device. I used a Teensy 2.0, which is very similar to Arduino, and any of this information should easily translate to the Arduino.

The OSC Interface

The OSC interface is simple. In your host program, you need to create a message formatted as follows:

/dac chanOne chanTwo chanThree chanFour chanFive chanSix chanSeven chanEight

For example:

/dac 256 273 50 1020 756 902 840 111

The trick here is that instead of sending an individual message whenever a channel changes, you can reduce network traffic by packaging all of the channels in one message, and updating that message at the rate of the most rapidly changing channel.

Sending OSC from Max/MSP

Here’s what it looks like in Max:

Screen shot 2010-08-25 at 8.55.57 AM

What is this code doing?

  1. The pak object outputs all eight of its inputs as a list whenever any one of the inputs changes.
  2. The message box below appends the /dac prefix to the list. Now the OSC message is formatted correctly.
  3. We don’t want the message to send automatically whenever a channel updates, so we buffer it with the second message box. This is done by sending the first message to the right inlet of the second message box.
  4. Finally, the metro object triggers the full OSC message to be sent once every 10ms.
The OSC interface application is expecting data on port 9999, and we’re going to be using the software locally, so we use the localhost address: 127.0.0.1. The Max udpsend object takes those two numbers as arguments, and transmits the OSC message.

Receiving OSC in Processing

The OSC interface program is written in Processing. OSC is easy to use in Processing as well. With a couple of lines of code, we’re ready to go:

import oscP5.*; // import the oscP5 library
import netP5.*; // the netP5 library is also required for the osc library
OscP5 oscP5;
oscP5 = new OscP5(this, 9999); // all you need to start oscp5 listening on port 9999

Now all we have to do is tell our program what to do when an OSC message is received. This is done by defining the oscEvent function.

After parsing out each of the eight input numbers, we check if a given channel needs to update its state. If so, we pass it to the writeValue() function. If not, we ignore it and don’t have to waste processor time sending the redundant data over the serial port. In my experience, this allows update rates of up to (possibly beyond) 1ms.

void oscEvent(OscMessage theOscMessage){
 if(theOscMessage.checkAddrPattern("/dac")==true){
   for(int i = 0; i < 8; i++){
     data[i] = theOscMessage.get(i).intValue();
     channelData[i] = data[i];
   }
   for(int i = 0; i < 8; i++){
     writeValue(i, data[i]);
   }
 }
}

Writing Serial Data to the Teensy

Serial teensy;
teensy = new Serial(this, Serial.list()[0], 19200);

The above lines are used in Processing to initialize a Serial object, allowing both read and write operations. The Serial.list()[0] argument indicates which actual serial port we want to write to. On my system, the Teensy always shows up as port 0 – this might be different on yours. Finally, the baud rate of 19200 is specified. Baud rate is the number of distinct signal events per second, and is a measure of data transfer speed.

Below is our writeValue() function, which was referenced above. The function is called repeatedly, once for each new sample to be written. First, we have to choose which of our two DAC chips should receive the data. Channels 0 – 3 go to chip A, 4 – 7 to chip B.

The MAX5250 is expecting a two byte word, which is assembled in the next section of code.

The SPI data expected by the MAX5250 DAC is as follows: Screen shot 2010-08-27 at 10.48.25 AM

The first two bits select which of the four-per-chip channels to use, the second two bits allow us to write data with or without updating the actual voltage outputs, the next 10 bits are the actual data to be assigned, and the last two bits are unused. So, to write a data value of 512 to channel 3 and immediately output a voltage, we would send 1011001110110100.

As you can see, it’s a bit involved, and that’s why we want to avoid running all of this code unless the data has actually changed. We end up with three bytes to send to the Teensy 2.0, a one byte digit to indicate which DAC we want to write to, and the two additional SPI bytes. These are put into a buffer (really just an array) which is only transmitted when the buffer is full. This is to circumvent some timing weirdness in the USB to Serial conversion hardware.

void writeValue(int _channel, int _data){
 if(_channel > 3) { // assign one of two dac chips to respond
    dacChip = 1;
  } else {
    dacChip = 0;
  }

/* bit shifting and masking to assemble proper list of bits for the DAC */ _channel = _channel << 14; updateBits = 3 << 12; _channel = _channel | updateBits; _data = _data << 2; spiWord = _channel | _data; binaryString = binary(spiWord, 16); // at this point, we've assembled our proper list of 16 bits outputData.add(byte(dacChip)); // so we'll throw them into an array, to facilitate transfer over serial outputData.add(byte(unbinary(binaryString.substring(0, 8)))); outputData.add(byte(unbinary(binaryString.substring(8, 16)))); if(outputData.size() >= 24){ outputBytes = new byte[outputData.size()]; for(int i = 0; i < outputData.size(); i++){ outputBytes[i] = outputData.get(i); } teensy.write(outputBytes); dataIndex = 0; outputData = new ArrayList(); previousUpdate = currentTime; } }

Initializing SPI on the Teensy 2.0

Here’s an explanation of SPI from Wikipedia:

The SPI bus specifies four logic signals.

  • SCLK — Serial Clock (output from master)
  • MOSI/SIMO — Master Output, Slave Input (output from master)
  • MISO/SOMI — Master Input, Slave Output (output from slave)
  • SS — Slave Select (active low; output from master)

Essentially, the Master (Teensy 2.0 here) triggers the Slave chip by setting the SS pin low. Then the SCLK pin outputs a periodic clock pulse while the MOSI pin transmits the data (holding the SS pin low for the entire transfer). Here’s an image of the transmission from the MAX5250 datasheet – note that they use DIN (Data In) instead of MOSI, but it’s the same thing.

Screen shot 2010-08-27 at 10.24.55 AM

The first bit of code here is just a couple of statements to simplify our SPI communication. The DACs have a “Slave Select” pin, which allows them to either receive or ignore incoming data. This allows for easier wiring, you can connect all of the SPI lines to each chip, and just select which chip should respond at a given moment. Our DAC select byte (from above, in the writeValue() function) interfaces with the Slave Select code on the Teensy, and allows us to route data to the appropriate chip.Below, in the setup() function, we set the SS pins to output and set them both HIGH, so that no data is accidentally received by the DACs.

Finally, we call the setup_spi() function, found in Andrew Smallbone’s SPI library. These settings define how the Teensy should handle SPI, whether the DACs read the data on the rising or falling edge of the clock pulse, the SPI transmission rate as related to the Teensy clock, and a couple of other settings. You might notice that the serial interface is being initialized with a baud rate of 9600. The Teensy 2.0 actually ignores any baud rate argument and runs at full USB 2.0 speed.

#define SELECT_DAC_ONE digitalWrite(PORTB0, LOW);
#define DESELECT_DAC_ONE digitalWrite(PORTB0, HIGH);
#define SELECT_DAC_TWO digitalWrite(PORTD0, LOW);
#define DESELECT_DAC_TWO digitalWrite(PORTD0, HIGH);

void setup(){ CPU_PRESCALE(CPU_4MHz); pinMode(PORTB0, OUTPUT); pinMode(PORTD0, OUTPUT); Serial.begin(9600); DESELECT_DAC_ONE; DESELECT_DAC_TWO; setup_spi(SPI_MODE_0, SPI_MSB, SPI_NO_INTERRUPT, SPI_MSTR_CLK2); }

The last bit of code here reads incoming serial data, and immediate sends it out to the proper DAC. The serial buffering on the Teensy is a little bit different than the Arduino, in that it receives an entire USB packet at a time. The timing of the calls to Serial.read() can then be an issue. We want to make sure that we’re reading our three bytes in the proper order, and not getting out of phase with the host app, so we check that our first byte is either a 1 or a 0. Since the SPI interface packs data into the first and last bits of our data word (the second two bytes), a byte with the value of 1 or 0 will only appear as the first byte in the series. Timing is also important here, we need to introduce some brief delays so that we’re not reading or writing data too quickly.

void loop(){
  pollAndWrite();
}

void pollAndWrite(){
 data = false;
 while(!data){
  if(Serial.available()) { // look into the receive buffering - not receiving from Max properly
    firstByte = Serial.read();
    delayMicroseconds(100);
    if(firstByte == B00000000) {
      secondByte = Serial.read();
      delayMicroseconds(100);
      thirdByte = Serial.read();
      SELECT_DAC_ONE;
      send_spi(secondByte);
      send_spi(thirdByte);
      delayMicroseconds(10);
      DESELECT_DAC_ONE;
      data = true;
    }
      if(firstByte == B00000001){
        secondByte = Serial.read();
        delayMicroseconds(100);
        thirdByte = Serial.read();
        SELECT_DAC_TWO;
        send_spi(secondByte);
        send_spi(thirdByte);
        delayMicroseconds(10);
        DESELECT_DAC_TWO;
        data = true;
    }
  }
 }
}

So that’s the software side of the USB-Octomod. Although it’s fairly involved, there are only a few tricky spots, and the OSC interface greatly simplifies what the end-user actually has to think about during composition or performance.  Once the Processing and Teeny code is compiled and loaded, it becomes a plug-and-play device.

RadioGamelan on Cycling74.com

Just a note that “RadioGamelan” is featured on the Projects section of Cycling74.com. Check it out here.

New RadioGamelan Page

On the eve of my second semester of grad school, I’ve created a page for the second of the pieces completed over break, RadioGamelan. The piece is for networked laptops, and is a blast to play. I plan on posting the software score (made in Max/MSP, using OSC to communicate over wi-fi), as well as a recording which will be released on Petcord in March, 2010.

The new release will be made up of improvisations and compositions performed by the trio of David Collins, Steve Schlei, and myself.

Screenshot – New Piece – “RadioGamelan”

Screen shot 2010-01-11 at 9.01.19 AM

A screenshot of a piece I’m working on, entitled RadioGamelan. It’s for networked laptops, and involves sharing rhythms across a wireless network. Each performer chooses an independent tempo, and the multiple tempi can be combined to create varying polyrhythms. Pitch is controlled either using a step-sequencer or the graphical piano keyboard.

Write your own Pure Data External! (Part 4)

In this section of the tutorial, I’m going to write about the randomwalk_new() function.

As you can see (in the code available here), the function takes three arguments. These are generic arguments, used in the *_new() function of every external you’ll write. The arguments are as follows:

t_symbol *s – a pointer to the symbolic representation of the objects name. You don’t have to worry about this, it’s for PD.

int argc – the number of arguments the user has entered, following the name of the object.

t_atom *argv – a pointer to the first of these arguments.

You’ll use argc to tell you how many arguments to read, starting from the argv pointer. This allows you to create an object that uses default values if a user doesn’t enter all of the required arguments.

In randomwalk.c, I’m using a switch statement to check the number of arguments provided, and then read the user-provided arguments into their proper variables. The atom_getfloat() function simply takes one of the arguments (of the PD type atom) and returns a C/C++ float, which can be assigned to a variable.

Below that, I reuse the argc count to determine which variables have to be assigned to defaults. Finally, I do some error checking to make sure that the highbound is actually a higher number than the lowbound, and swap if needed. This kind of error checking is very important to avoid crashes when actually using the object.

Finally, we have to assign inlets and outlets. The three calls to floatinlet_new() assign inlets which allow direct assignment of the lower, upper, and step variables from PD. There should probably be some error checking here too, because what would happen if a user input a list or a symbol instead? We also define f_out to be an outlet, using outlet_new() which takes two arguments, a reference to the object itself, and the type of value that will be output.

The return statement simply returns the initialized object to PD, ready for use.

New Gig.

So, I’ve got a new job. I’m working as a programmer for Bruce Charlesworth, a video/installation artist who teaches in the Peck School of the Arts. You can check out his page here – the current project is going to be a multi-room installation, involving break-beam tracking of audience members, and interactive visuals/audio.

Most of the code base I’m working with is Java-based, with Max/MSP/Jitter serving as glue between an Arduino, Java, and the projector. It’s a good opportunity to use the Arduino, something I’ve been excited about for a while now.

So, you can see why I haven’t been posting as much lately… quite busy. I will continue to post on this as it develops, over the year.

Lilypond

I’ve been using GNU Lilypond to notate my latest stuff. These are examples from the flute/cello duo I’m working on.

Screen shot 2009-10-06 at 3.39.00 PM

Looks pretty good, and the syntax isn’t too hard either. Anyway, this is some of the flute part.

Write your own Pure Data external! (Part 3)

In this part of the tutorial (Part 1, Part 2), I’m going to talk about the first of two functions that PD calls when you ask it to create an instance of your object.

The first is the setup function. In my example file, it’s called:

void randomwalk_setup()

Here, you can see I’m finally defining the randomwalkclass that was declared above. The classnew function provides PD with some important information about the object. The interface is as follows:

tclass *classnew(tsymbol *name, tnewmethod newmethod, tmethod freemethod, sizet size, int flags, t_atomtype arg1, ...);

Obviously, I’m defining the name of the object here, with the gensym("randomwalk") code.

Below that, we allocate some memory with sizeof(t_randomwalk) (this returns the size in memory of our struct from above). You’ll probably never have to worry about the int flags argument, it’s mainly for the GUI representation of the object.

Finally, the tatomtype arg1 stuff lets you define what kinds of arguments the object should expect. Here, the AGIMME lets you provide a list of atoms of arbitrary length and types. You can check out Johannes’ tutorial for the full list of possible arguments here.

The last thing in the randomwalksetup() function is classaddbang(randomwalkclass, randomwalkbang);

This is where you declare the function your object will call when it receives a particular type of message in it’s inlet. The first argument should be the name of your class, and the second should be the name of a function which will be called.

In the next part, I’ll talk about the *randomwalk_new() function, which handles all of the arguments to your object.

Write your own Pure Data External! (Part 2)

So, the first thing you’ll want to do is create a folder for your project. In the folder, you’ll want a copy of the files in this .zip archive. m_pd.h is a header file which you will use in every external you write. It allows your external to use the functions defined for use by PD. The makefile will automate the compilation process. You’ll want to edit it (using a text editor) to make sure the path to your PD.app is correct.

The .zip also contains a file called randomwalk.c that I’ll be using as the example file for these tutorials. Open it in your text editor. Skipping over the comment at the top, you’ll notice that I’m #include-ing m_pd.h. You’ll have to do that for every external, assuming you want it to compile.

Skip down a bit to this chunk of code:

static tclass *randomwalkclass; // declares a pointer to the object

typedef struct randomwalk { tobject xobj; // the required tobject tfloat current; // the current value tfloat step; // step width tfloat lower, upper; // bounds toutlet *f_out; // outlet pointer } t_randomwalk;

The first line declares a pointer to your object, which will allow PD to reference it. We’ll define it below.

This defines the variables that your object will have access to. Every object needs to have the tobject xobj; line, this allows the object to store PD-specific information, which we don’t have to worry about right now. You’ll notice that the next three variables are all of tfloat type. This is essentially the same as a float variable, but PD defines it’s own atomic types, which are used to facilitate portability cross-platform. Here, I’ve defined three variables for the random-walk procedure. Finally, the toutlet *f_out; is a pointer to an outlet. The name f_out is just a convention so that we remember that this outlet is going to output float's.

In the next segment, I’ll talk about our setup function, found in the

void randomwalk_setup() function below.

Write your own Pure Data external! (Part 1)

This document will (hopefully) serve as an easy to read and understand introduction to the process of writing an external for Pure Data. Pure Data, or PD, is a visual programming environment designed for real-time computer music applications. PD is open-source software, under the BSD license, and expansion of the software in the form of externals is strongly encouraged.

PD and its externals are written in the C programming language. When PD is running an external, it makes no distinction between the ‘core’ functionality and the external code. There are hundreds of user-written externals freely available for use and study. As you learn to write externals yourself, I strongly encourage studying other people’s code. For example, I learned about timing issues from studying the “pipe” object.

To get started coding an external, you’ll need a text editor. I use Textwrangler, but there are a few good, free editors out there. Get one with syntax highlighting. You’ll also need to install GCC, the Gnu C Compiler, and Make. If you’re on OS X, you can just install Apple’s XCode Developer Tools, and you’ll have both of these programs installed. I’m going to assume some knowledge of using the command line here, so I won’t explain how to use GCC or run Make. (Hint: type “make”.) You’ll also need an installation of PD, so that you can link and run your externals.

In the next post, I’ll talk about the basic concepts and chunks of code that you’ll need to write your external.

 

Search engine optimization by SEO Design Solutions