[chuck-users] chuck MIDI handling

Bruce Murphy pack-chucklist at rattus.net
Sun Jul 30 15:10:38 EDT 2006


On Mon, Jul 31, 2006 at 12:10:36AM +0800, Bruce Murphy wrote:
> On Sat, Jul 29, 2006 at 01:13:24PM -0400, Ge Wang wrote:
> > 
> > What, in particular, do you have in mind?  If you can send some
> > pseudocode that demonstrates the desired API functionalities,
> > we can go from there.
> 
> Well, unless I'm missing some vast cornucopia of MIDI functionality,
> the following would probably be nice.

And even if I have missed the implementation of it I've polished my
ChucK writing up a bit by implementing everything I wanted in the 
attached public class file. I should probably clean it up a little more
and there are some other functions I have in mind, but it should be
useful for anyone using MIDI output in ChucK right now. It would be
useful to have in the main distribution once it's finished.

In particular it implements:
 * a settable default channel for MIDI comms
 * all MIDI commmands, including note on and off with velocity, 
   aftertouch, controllers, channe pressure, pitch wheel, program
   change.
 * bank and subbank select.  
 * polymorphic functions with sensible defaults where useful.
 * note counting (on by default), so an instance can send noteoffs for
   outstanding notes on one or all channels.
 * some convenience functions for volume, all_note_off controllers

What I really want to work on is getting the chuck midi port names 
that I wrote that patch for available within chuck programs so that 
devices can be found like that. Not sure how the the interface between
underlying code and the user-visible language in Chuck works, so I
probably have more digging to do.

B>
-- 
Packrat (BSc/BE;COSO;Wombat Implementor)
Nihil illegitemi carbovndvm
-------------- next part --------------
//
// Basic midi wrapper class. This will act as a wrapper for the MidiOut
// object to provide all the functionality I want to do more usefriendly
// MIDI handling in ChucK
//
// Bruce Murphy
// rattus.net, July 2006.


public class MIDIsender
{
	MidiOut midiport;
	MidiMsg midimessage;

	// so we don't have to distinguish external from internal
	// channels, we'll keep this in the people friendly 1-16 format
	1 => int default_channel;

	// this will be used for our notecounting. We'll turn notecounting on by
	// default for now.
	// Note that we have to do it per channel.
	int notecount[16][128];
	1=> int countingp;

	// internally generated MIDI streams will have this spacing to avoid crashing
	// older hardware.
	5::ms => dur midigap;

	// direct wrapper to the midiport's open function.
	fun int open(int portid)
	{
		return midiport.open(portid);
	}

	// sets the default channel for polymorphic functions.
	fun void set_channel(int channel)
	{
		if (channel < 1 || channel > 16) {
			1 => default_channel;
		} else {
			channel => default_channel;
		}
	}

	///////////////////
	// Utility functions
	//
	// for notecounting etc.

	fun void count_note(int notenum, int channel, int increment)
	{
		notenum & 0x7f => notenum;
		(channel - 1) & 0xf => channel;
		increment +=> notecount[channel][notenum]; 
	}

	// issues noteoffs for all the outstaning notes on a particular channel which 
	// this object has sent, but which haven't been called in yet.
	//
	// as a special case here, if we hand it the channel -1, it will stop the 
	// notes for all the channels. Once finished, it will clear the notecount array.
	fun void stop_hanging_notes()
	{
		stop_hanging_notes(default_channel);
	}
	fun void stop_hanging_notes(int channel)
	{
		if (channel != -1) {
			(channel - 1) & 0xf => channel;
			for (0 => int ii; ii < 128; ii++) {
				if (notecount[channel][ii] > 0) {
					noteoff(ii, channel + 1);
					midigap => now;
				}
			}
		} else { // -1 behavior
			for (0 => int cc; cc < 16; cc++) {
				for (0 => int ii; ii < 128; ii++) {
					if (notecount[cc][ii] > 0) {
						noteoff(ii, cc + 1);
						midigap => now;
					}
				}
			}
		}

		clear_note_counts(channel);
	}
	// This just clears the hanging note count arrays. Has the same -1 channel behavior
	// as clear_hanging_notes().
	fun void clear_note_counts()
	{
		clear_note_counts(default_channel);
	}

	fun void clear_note_counts(int channel)
	{
		if (channel != -1) {
			(channel - 1) & 0xf => channel;
			for (0 => int ii; ii < 128; ii++) {
				0 => notecount[channel][ii];
			}
		} else { // -1 behavior
			for (0 => int cc; cc < 16; cc++) {
				for (0 => int ii; ii < 128; ii++) {
					0 => notecount[cc][ii];
				}
			}
		}
		
	}


	///////////////////
	// Interface functions.
	//
	// These functions map pretty simply onto the MIDI specification messages.


	// polymorphic set of functions supplying default values for the MIDI
	// noteon() function.
	fun void noteon(int notenum)
	{
		noteon(notenum, 127, default_channel);
	}
	fun void noteon(int notenum, int velocity)
	{
		noteon(notenum, velocity, default_channel);
	}
	fun void noteon(int notenum, int velocity, int channel)
	{
		send_3bytes(0x9, channel, notenum, velocity);
	
		if (countingp) {
			velocity & 0x7f => velocity;
			if (velocity != 0) {
				count_note(notenum, channel, 1);
			} else {
				count_note(notenum, channel, -1);
			}
			
		}
	}

	// The basic MIDI noteoff uses noteons with zero velocity.
	fun void noteoff(int notenum)
	{
		noteon(notenum, 0, default_channel);
	}
	fun void noteoff(int notenum, int channel)
	{
		noteon(notenum, 0, channel);
	}

	// Some rare synths make use of release velocity. For those we use a different
	// MIDI message type.
	fun void noteoff_velocity(int notenum, int velocity)
	{
		noteoff_velocity(notenum, velocity, default_channel);
	}
	fun void noteoff_velocity(int notenum, int velocity, int channel)
	{
		send_3bytes(0x8, channel, notenum, velocity);
		
		if (countingp) {
			count_note(notenum, channel, -1);
		}
	}

	// 
	// Polyphonic aftertouch message. This controls aftertouch level for individual
	// notes.
	fun void aftertouch(int notenum, int level)
	{
		aftertouch(notenum, level, default_channel);
	}
	fun void aftertouch(int notenum, int level, int channel)
	{
		send_3bytes(0xa, channel, notenum, level);
	}


	fun void controller(int controllernum, int value)
	{
		controller(controllernum, value, default_channel);
	}
	fun void controller(int controllernum, int value, int channel)
	{
		send_3bytes(0xb, channel, controllernum, value);
	}

	// basic program change functionality. We need to have bank and subbank changes
	// available as well, but they're convenience functions.
	fun void program_change(int prognum)
	{
		program_change(prognum, default_channel);
	}
	fun void program_change(int program, int channel)
	{
		send_2bytes(0xc, channel, program);
	}

	// channel pressure is what most synths implement to do aftertouch. It isn't
	// specific to any one note.
	fun void channel_pressure(int pressure)
	{
		channel_pressure(pressure, default_channel);
	}
	fun void channel_pressure(int pressure, int channel)
	{
		send_2bytes(0xd, channel, pressure);
	}

	// pitchbending is actually complicated. We'll take a value of -64 to 63
	// for now. This will only send coarse bends, to avoid overwhelming MIDI.
	fun void pitchbend(int bend)
	{
		pitchbend(bend, default_channel);
	}
	fun void pitchbend(int bend, int channel)
	{
		bend + 64 => bend;
		if (bend > 127) { 127 => bend; }

		send_3bytes(0xe, channel, 0, bend);
	}
	// We'll use these to generate fine bends. Input is a float varying from -1
	// to 1.
	fun void pitchbend_fine(float bend)
	{
		pitchbend_fine(bend, default_channel);
	}
	fun void pitchbend_fine(float bend, int channel)
	{
		((bend + 1) * 8192) $ int => int bendval;
		if (bendval < 0) {
			0 => bendval;
		}
		if (bend > 0x3fff) {
			0x3fff => bendval;
		}
		
		send_3bytes(0xe, channel, bendval & 0x7f, (bendval >> 7) & 0x7f);
	}

	/////////////////////////////
	// Convenience functions.
	//
	// These functions are just calls to the main interface functions with certain
	// magic parameters. They are handy to have so you don't have to remember the
	// magic parameters.


	// This is the MIDI panic message that should turn all MIDI generated notes off
	// on the device.
	fun void all_notes_off()
	{
		all_notes_off(default_channel);
	}
	fun void all_notes_off(int channel)
	{
		controller(123, 0, channel);
	}

	// Another panic, this one is supposed to kill sound, not just emulate
	// note offs and so ignore sustain and long envelopes.
	// Less typically implemented.
	fun void all_sound_off()
	{
		all_sound_off(default_channel);
	}
	fun void all_sound_off(int channel)
	{
		controller(120, 0, channel);
	}

	// Bank select is magic. Consult synth documentation to figure out which 
	// order of bank and program changes your device requires.
	//	
	// It'll usually be Bank, Sub, Prog. or Sub, Bank, Prog, but some use only one
	// of bank or sub :)
	fun void select_bank(int bank) 
	{
		select_bank(bank, default_channel);
	}
	fun void select_bank(int bank, int channel)
	{
		controller(0, bank, channel);
	}
	fun void select_subbank(int bank) 
	{
		select_subbank(bank, default_channel);
	}
	fun void select_subbank(int bank, int channel)
	{
		controller(32, bank, channel);
	}


	// Uses only the coarse volume since few things both to implement fine.
	fun void volume(int level)
	{
		volume(level, default_channel);
	}
	fun void volume(int level, int channel)
	{
		controller(7, level, channel);
	}
	
	
	
	////////////////////////////
	// basic interface functions.
	//
	// These generate the low-level MIDI messages.
	
	// Basically all the MIDI functions break down into sending one of these
	// two things.
	fun void send_3bytes(int command, int channel, int byte1, int byte2)
	{
		((command & 0xf) << 4) | ((channel - 1) & 0xf) => midimessage.data1;
		command | channel => command;
		byte1 & 0x7f  => midimessage.data2;
		byte2 & 0x7f => midimessage.data3;
		midiport.send(midimessage);

	}
	// not sure what dance is required to make this only send two bytes.
	fun void send_2bytes(int command, int channel, int byte1)
	{
		((command & 0x0f) << 4) | ((channel - 1) & 0xf) => midimessage.data1;
		byte1 & 0x7f => midimessage.data2;
		midiport.send(midimessage);
	}

}


More information about the chuck-users mailing list