Mercurial > hg > movesynth
diff tim_grid_mapper/grid_mapper.app/Contents/Resources/grid_mapper.py @ 50:f4c6999ecfe9 tip
added the files on my computer that aren't aiff s> these shoudl be everything for the big bang fair 2011 - heresy, and tim's file's also here
author | Andrew N Robertson <andrew.robertson@eecs.qmul.ac.uk> |
---|---|
date | Sat, 08 Oct 2011 22:12:49 +0100 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tim_grid_mapper/grid_mapper.app/Contents/Resources/grid_mapper.py Sat Oct 08 22:12:49 2011 +0100 @@ -0,0 +1,262 @@ +#! /usr/bin/python +''' +grid_mapper.py - maintained by Tim. + +This module implements a mapping from person positions (id,x,y,z) to generate +pitch, velocities and channels for Joe's synthesiser and controller data for +Ableton. + +''' + +from OSC import ThreadingOSCServer, OSCClient, OSCMessage, OSCClientError +from threading import Thread +from time import sleep +import pdb + +#### PUBLIC OPTIONS #### +num_channels = 3 # number of instruments (and max number of people who can + # make noise). + +#### OSC OPTIONS - THESE NEED TO BE SET MANUALLY #### +my_port = 12344 # to receive OSC messages +joe = ('localhost', 12345) +ableton = ('localhost', 12346) + +### Constants for grid mapping: +# The range of values that the input coordinates and output values may take: +# (ranges are inclusive) +MIN = { + 'x' : 0., + 'y' : 0., + 'z' : 0., + 'pitch' : 0, + 'cc1' : 0, + 'cc2' : 0, + 'velocity' : 0, +} +MAX = { + 'x' : 1., + 'y' : 1., + 'z' : 1., + 'pitch' : 15, + 'cc1' : 127, + 'cc2' : 127, + 'velocity' : 127, +} + + + +#### PRIVATE VARIABLES #### + +# mapping from channel to the currently playing note (pitch values) or None +# initialize each channel to None: +currently_playing = [None] * num_channels + +# mapping from personId to time of last update +# ((not yet implemented)) +#last_update_times = {} + +# mapping from (channel, CC_number) to last CC value sent: +last_value_sent = { + (1, 1) : 0, + (1, 2) : 0, + (2, 1) : 0, + (2, 2) : 0, + (3, 1) : 0, + (3, 2) : 0, +} + + +# OSC OBJECTS +server = None # Initialised when start() is run +client = OSCClient() + + + + + +### MAPPING + +def send_to_joe(data, address='/test'): + '''Sends `data` to Joe directly as an OSC message. + ''' + message = OSCMessage(address) + message.extend(data) + client.sendto(message, joe) + print_d('\n==OSC Output "%s" to Joe %s:==\n %s' % (address, joe, data)) + + +def send_to_ableton(data, address='/cc'): + '''Sends `data` to Ableton (via Max) as an OSC message. + ''' + #pdb.set_trace() + message = OSCMessage(address) + message.extend(data) + client.sendto(message, ableton) + print('\n==OSC Output "%s" to Ableton %s:==\n %s' % (address, ableton, data)) + + + +def flush(channel): + '''Sends note off messages for whatever note is currently playing on + `channel`. + ''' + pitch = currently_playing[channel] + if pitch: + #print_d('Sending note-off for note %i on channel %i.' % (pitch, channel)) + send_to_joe([ + 'Turn off note %i on channel %i' % (pitch, channel), + # first string is ignored + int(pitch), # pitch to turn off + 0, # 0 to turn note off + 127, # doesn't matter for note-off (but never send 0) + int(channel), + ]) + currently_playing[channel] = None + + +def person_handler(address, tags, data, client_address): + ''' Handles OSC input matching the 'person' tag. + + `data` should be in form [person_id, x, y, z] + ''' + pitch, velocity, channel, cc1, cc2 = grid_map(*data) + + cc1, cc2 = int(round(cc1)), int(round(cc2)) + if cc1 != last_value_sent[(channel, 1)]: + send_to_ableton([channel, 1, cc1], '/cc') + last_value_sent[(channel, 1)] = cc1 + if cc2 != last_value_sent[(channel, 2)]: + send_to_ableton([channel, 2, cc2], '/cc') + last_value_sent[(channel, 2)] = cc2 + + + ## Format data for Joe - done using Specification.txt on 2011-02-15 + + # constrain and round off pitch and velocity + pitch = max(min(round(pitch), MAX['pitch']), MIN['pitch']) + velocity = max(min(round(velocity), MAX['velocity']), MIN['velocity']) + + if velocity and pitch == currently_playing[channel]: + return # keep playing current note + + # otherwise turn note off: + flush(channel) + + if velocity: # if there is a new note to play + send_to_joe([ + 'Turn on note %i on channel %i' % (pitch, channel), + # first value is string which is ignored + int(pitch), + 1, # 1 to turn note on + int(velocity), + int(channel) + ]) + currently_playing[channel] = pitch + + + + + +def grid_map(person_id, x, y, z): + '''This function maps from a person's location to MIDI data + returning a tuple (pitch, velocity, channel, cc1, cc2). + + The current mapping creates higher pitch values as the person moves + closer to the Kinect (i.e. z decreases). x and y values are mapped to cc1 + and cc2 (to be sent straight to Ableton and determined by a particular + synth) + + NB. channel == person_id and velocity==0 when note is off. + Midi-Velocity is currently unimplemented but will use Person-velocity data + when that becomes available. + This function does not guarantee that the output will be in range if the + input goes out of range. + ''' + pitch = round(interpolate(z, + MIN['z'], MAX['z'], + MIN['pitch'], MAX['pitch']) + ) + cc1 = round(interpolate(x, + MIN['x'], MAX['x'], + MIN['cc1'], MAX['cc1']) + ) + cc2 = round(interpolate(y, + MIN['y'], MAX['y'], + MIN['cc2'], MAX['cc2']) + ) + velocity = 127 + return (pitch, velocity, person_id, cc1, cc2) + + + + +def interpolate(x, a, b, A, B): + ''' Interpolates x from the range [a, b] to the range [A, B]. + + + Interpolation is linear. From [a,b] to [A,B] this would be: + (B-A)*(x-a)/(b-a) + A + ''' + return (B-A)*(x-a)/(b-a) + A + + +def print_d(string): + '''Function to print out debug method. Disable if processing power is in + demand. + ''' + print(string) + + + + + + +#### CONTROL + +def start(): + '''Set up OSC servers and start the program running. + ''' + global joe, ableton, server + if joe[1] == "THIS_MUST_BE_SET": + joe_port = input("Enter port number on %s for Joe's synth software: " % joe[0]) + joe = (joe[0], joe_port) + + if ableton[1] == "THIS_MUST_BE_SET": + ableton_port = input("Enter port number on %s for Ableton: " % ableton[0]) + ableton = (ableton[0], ableton_port) + + server = ThreadingOSCServer(('localhost', my_port)) + # Register OSC callbacks: + server.addMsgHandler('/person', person_handler) + t = Thread(target=server.serve_forever) + t.start() + if server.running and t.is_alive(): + print('OSC Server running on port %i.' % my_port) + print("Use 'stop()' to close it.") + else: + print('Error: Either server thread died or server is not reported as running.') + + +def stop(): + '''Close the OSC server. + ''' + if server: + server.close() + sleep(0.3) + if not server.running: + print("\n\nSuccessfully closed the OSC server. Ignore a 'Bad file descriptor' Exception - there's nothing I can do about that.") + print("Type 'quit()' to exit.") + else: + print('Error: server has been told to close but is still running.') + +if __name__=='__main__': + start() + while True: + try: + print(repr(input())) + except Exception as e: + print('Caught %s:' % repr(e)) + exception = e + trace = traceback.format_exc() + print('Exception saved as `exception`. Stack trace saved as `trace`.')