Advertisement

How-To Control Csound with a Custom Midi Controller: Hardware (2 of 2)

In last week's installment, we showed how to get started with Csound. This week we take it to the next step by constructing a homemade MIDI controller circuit and use the new device to control Csound in real time.

What you will need:
a computer on which you have Csound up and running
a MIDI adapter for your computer (usb to midi adapters are the norm here)
a microcontroller / breadboard / microcontroller programmer (in this example we will show some BX24 sample code)
a MIDI female connector (either a cable or circuit board mount type, also known as 5 pin din)
a 2N2222 NPN transistor
some resistors (10Kohm and 220ohm)
some sort of sensor or button or potentiometer or any combination of the above


Circuit

First we’ll start by programming a microcontroller to grab data from your sensors. In this case, we used a BX24 microcontroller from BasicX. You may choose to use an Atmel or PIC microcontroller instead, excellent instructions for MIDI on a PIC are found on Tom Igoe’s Physical Computing pages.

Wire up your microcontroller to power and ground according to its circuit diagram. To the circuit you’ll now add the MIDI circuit. For this you’ll need the 2N2222 transistor. Hold the transistor by the legs with the tab facing you, the pinout for the transistor is: C, B, E or Collector, Base, Emitter.

pnp

On your breadboard connect the MIDI circuit as follows: The collector is connected to a 220 ohm resistor which is connected to the second pin from the left on the back of the female midi connector (labelled pin 5). The base is connected to a 10K ohm reistor which is connected to the serial data output pin of your microcontroller (ours is pin 1 aka TX aka the pin on the upper left). The emitter is connected to ground. Now connect the middle pin (pin 2) on the female midi connector to ground. On the female MIDI connector’s second from the left pin (pin 4) put a 220 ohm resistor. Connect that 220 ohm resistor to 5V on your breadboard.

circuit

This image shows the MIDI circuit with the breadboard and microcontroller:

midicircuit

Now connect up your sensors. In our example we will connect two continuous controllers (think: something that sends continuous data, not a switch that sends an on/off) to our microcontroller. On pin 15 (third up from the bottom right) we connected a potentiometer. On pin 14 (second up from the bottom right) we chose to put a flex sensor. Notice in this image the MIDI connector at the bottom and the two continuous controllers on the bottom right hand side of the microcontroller:

circuitdiagram

These two continuous controllers now need some code to grab their data and send it over MIDI. The BX24 is programmed in Basic with free software from BasicX. Open your programming environment and open your code editor. Here’s our code for grabbing data from the two continuous controllers:


Option Explicit

'basic sensor to MIDI example
'Fabienne Serriere
'www.engadget.com
'feel free to reproduce, tweak, pass on, use and re-use
'potentiometer on pin 15, flex sensor on pin 14

dim outputBuffer(1 To 13) as byte
dim inputBuffer(1 To 10) as byte

dim note as byte
dim pitch as byte
dim velocity as byte

dim pot as integer
dim flex as integer

sub main()

'start the input and output buffers
call openQueue(outputBuffer, 13)
call openQueue(inputBuffer, 10)

call openCom(1, 9600, inputBuffer, outputBuffer)
register.ubrr = 14

do
pot = getadc(15) \ 8
flex = getadc(14) \ 8
'grab analog to digital data off of pins 15 and 14

'debug.Print "pot = "; CStr(pot) 'uncomment to see pot value in the bx environment
'debug.Print "flex = "; CStr(flex) 'uncomment to see flex value in bx environment

note = 144 'MIDI command for note on channel 1
pitch = cByte(pot) 'get pitch from pot value
velocity = 64 'how hard the note is struck
call putQueue(outputBuffer, note, 1)
call putQueue(outputBuffer, pitch, 1)
call putQueue(outputBuffer, velocity, 1)

note = 145 'MIDI command for note on channel 2
pitch = cByte(flex) 'get pitch from pot value
velocity = 64 '
how hard the note is struck
call putQueue(outputBuffer, note, 1)
call putQueue(outputBuffer, pitch, 1)
call putQueue(outputBuffer, velocity, 1)

delay(1.0)
note = 144 'MIDI command for note on channel 1
pitch = cByte(pot) 'value from potentiometer on pin 15
velocity = 0 'a value of zero stops the note started above
call putQueue(outputBuffer, note, 1)
call putQueue(outputBuffer, pitch, 1)
call putQueue(outputBuffer, velocity, 1)

note = 145 'MIDI command for note on channel 2'
pitch = cByte(flex) 'value from flex sensor on pin 14
velocity = 0 'a value of zero stops the note started above
call putQueue(outputBuffer, note, 1)
call putQueue(outputBuffer, pitch, 1)
call putQueue(outputBuffer, velocity, 1)

loop
end Sub


If you want to test your controllers directly in your programming environment, you can uncomment the debug.Print lines by deleting the apostrophe at the beginning of the line. Note that this code is for the BasicX environment only and will have to be modified if you're using another type of controller. A great resource for PIC microcontrollers and MIDI are Tom Igoe's Physical Computing pages.

Now connect up your new circuit to your midi adapter on your computer using… wait for it… a MIDI cable. We used a Midisport 4x4 but any MIDI adapter will work.

An easy way to test your MIDI data is integrated into the free and tasty puredata. You can get puredata builds and source from here (windows, mac or linux). If you can, grab a build that is already compiled (we use build 0.38-3 for PowerPC on Mac OS X.4.2). Once installed, in PD go to Media > Test Audio and MIDI.

pd1



When that window loads, right click on the "note in" object and choose "help" in the drop down menu. In the next window that loads, notice the note in objects on the upper left. These contain the universal note in (labelled omni) and the specific note in for channel 1 (labelled, appropriately, channel 1). In the boxes connected to those you'll see from left to right, note number, velocity, and channel number.

pd2



The upside of testing MIDI in Puredata is that you can be sure your new MIDI device works with PD, max/msp, Supercollider, and most commercial music/audio software that has MIDI inputs. Shout yourself a little congratulatory hurrah: Yay! I've got MIDI!

Now we are going to write some very simple instruments in Csound that our little MIDI circuit can control in real time.

Csound

In Csound open a new project and in the orc portion put this:

instr 1
ifreq cpsmidi ;note number of current MIDI event in cycles per second
iamp ampmidi 2000 ;velocity of current MIDI event
ikoct = 4 ;2 octave range

kfratio midic7 1, 1, ikoct ;mod.wheel controls transposition
a1 oscili iamp, ifreq, 1
a2 oscili iamp, ifreq*kfratio, 1 ;2nd oscil is transposed by kfratio
out a1 a2
endin

In the sco tab type:

f1 0 128 10 1 ;sine table
f0 3600 ;allows 1 hour of playing time
e

In this example the frequency (pitch) of the Csound instrument will be controlled by our two continuous controllers. Since we have a one second length for each note in our microcontroller code above, each note will play for 1 second and then be stopped. Try running this Csound score now by clicking on the small green play triangle on the upper right of the Csound environment. Move the controllers and you should see notes being printed in the Csound window like so:

SECTION 1:
ftable 1:
rtevent: T 0.784 TT 0.784 M:
midi channel 2 using instr 401
rtevent: T 0.787 TT 0.787 M:
rtevent: T 0.787 TT 0.787 M:
new alloc for instr 401:
rtevent: T 0.787 TT 0.787 M:
new alloc for instr 401:
rtevent: T 1.788 TT 1.788 M:
rtevent: T 1.788 TT 1.788 M:

If you don't see any realtime events (rtevent) being printed, try saving, quitting, and reopening your Csound application. If you are on a linux version of Csound you will have to enable the realtime flag and the midi flag before performing.

Now try to change the microcontroller code above by commenting out these lines with apostrophes (or simply delete them):

'delay(1.0)
'note = 144 'MIDI command for note on channel 1
'pitch = cByte(pot) 'value from potentiometer on pin 15
'velocity = 0 'a value of zero stops the note started above
'call putQueue(outputBuffer, note, 1)
'call putQueue(outputBuffer, pitch, 1)
'call putQueue(outputBuffer, velocity, 1)

'note = 145 'MIDI command for note on channel 2'
'pitch = cByte(flex) 'value from flex sensor on pin 14
'velocity = 0 'a value of zero stops the note started above
'call putQueue(outputBuffer, note, 1)
'call putQueue(outputBuffer, pitch, 1)
'call putQueue(outputBuffer, velocity, 1)

Reload your microcontroller with the new code and run the Csound patch once again by clicking on the small green triangle on the upper right hand corner. Now you should hear that none of the notes stop, creating a huge layered chord. The next step for your experimentation should be to try to create a note off or sustain that is more musical. You can do this by adding a button to your microcontroller circuit or using some creative methods and subroutines in your microcontroller or Csound code.

Another fun direction for further experimentation would be to try to convert last week’s Csound code to be able to be played by a MIDI controller. A good stepping stone to Csound is of course their site and the Csound documentation.

Congrats — you’ve now made some audio with a homemade controller in a home-coded language. Consider yourself on the path to achieving Moog-ness. May all your controllers be hackable and all your code open source.