Mercurial > hg > movesynth
view tim_grid_mapper/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 | 5b6f63b6f76d |
children |
line wrap: on
line source
#! /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 = 12343 # to receive OSC messages from kinect debugging = True # display incoming packets # Both joe and ableton ip addresses are now read from the file ip_addresses.txt # which is written to by the set_ip_addresses() function. #joe = ('localhost', 12346)#changed by andrew - python sending to joe #ableton = ('localhost', 12345)#changed by andrew - max receiving from Joe ip_address_file = 'ip_addresses.txt' ableton = None ### THESE WILL BE SET AUTOMATICALLY BELOW joe = None ### DO NOT CHANGE THEM HERE ### 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 stopperson_handler(address, tags, data, clinet_address): ''' Handles OSC input matching 'stopperson' Called when a person leaves the screen. ''' flush(data[0]) 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] ''' if debugging: print('Received packet:') print(str(data)) pitch, velocity, channel, cc1, cc2 = grid_map(*data) channel = channel + 1 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 server set_ip_addresses() server = ThreadingOSCServer(('localhost', my_port)) # Register OSC callbacks: server.addMsgHandler('/person', person_handler) server.addMsgHandler('/stopperson', stopperson_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.') def get_ip_addresses_from_file(filename=ip_address_file): '''Load up ip addresses from a file in the format saved by the set_ip_addresses function. ''' f = open(filename, 'r') lines = f.readlines() global ableton, joe ableton = eval(lines[1].split('#')[0].strip()) # set ableton joe = eval(lines[2].split('#')[0].strip()) # set joe if not ableton: print('Failed to set IP for Ableton from file.') if not joe: print('Failed to set IP for Joe from file.') f.close() def set_ip_addresses(save_file=ip_address_file): '''Manually set IP addresses based on input from user. IPs are then saved to a file. ''' global joe, ableton if not joe: joe = ('localhost', 12346) if not ableton: ableton = ('localhost', 12345) # set joe try: new_joe = eval(raw_input("Enter IP address and port for Joe in form: "+str(joe)+' or leave blank to keep that address.\n')) except: new_joe = None if new_joe: joe = new_joe print("Address for Joe: "+str(joe)+"\n") # set ableton try: new_ableton = eval(raw_input('Enter IP address and port for Ableton in form: '+str(ableton)+' or leave blank to keep that address.\n')) except: new_ableton = None if new_ableton: ableton = new_ableton print('Address for Ableton: '+str(ableton)+'\n') # save to file if new_ableton or new_joe: print('Writing new addresses to file %s.' % save_file) try: f = open(save_file, 'w') f.write( '''# This is a cache. These addresses will be overridden by Python if you set something different. Do not leave comments here they will be overridden %s # set Ableton IP and port %s # set Joe's processing IP and port ''' % (str(ableton),str(joe)) ) f.flush() f.close() except IOError: print('SAVE FAILED.') get_ip_addresses_from_file(ip_address_file) if __name__=='__main__': start() while True: try: print(eval(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`.')