annotate 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
rev   line source
tim@30 1 #! /usr/bin/python
tim@30 2 '''
tim@3 3 grid_mapper.py - maintained by Tim.
tim@3 4
tim@3 5 This module implements a mapping from person positions (id,x,y,z) to generate
tim@3 6 pitch, velocities and channels for Joe's synthesiser and controller data for
tim@3 7 Ableton.
tim@3 8
tim@3 9 '''
tim@3 10
tim@3 11 from OSC import ThreadingOSCServer, OSCClient, OSCMessage, OSCClientError
tim@4 12 from threading import Thread
tim@4 13 from time import sleep
tim@30 14 import pdb
tim@3 15
tim@3 16 #### PUBLIC OPTIONS ####
tim@4 17 num_channels = 3 # number of instruments (and max number of people who can
tim@4 18 # make noise).
tim@3 19
tim@3 20 #### OSC OPTIONS - THESE NEED TO BE SET MANUALLY ####
tim@41 21 my_port = 12343 # to receive OSC messages from kinect
tim@41 22
tim@41 23 debugging = True # display incoming packets
tim@40 24
tim@40 25 # Both joe and ableton ip addresses are now read from the file ip_addresses.txt
tim@40 26 # which is written to by the set_ip_addresses() function.
tim@40 27
andrew@38 28 #joe = ('localhost', 12346)#changed by andrew - python sending to joe
tim@40 29 #ableton = ('localhost', 12345)#changed by andrew - max receiving from Joe
tim@40 30 ip_address_file = 'ip_addresses.txt'
tim@40 31 ableton = None ### THESE WILL BE SET AUTOMATICALLY BELOW
tim@40 32 joe = None ### DO NOT CHANGE THEM HERE
tim@3 33
tim@3 34 ### Constants for grid mapping:
tim@3 35 # The range of values that the input coordinates and output values may take:
tim@3 36 # (ranges are inclusive)
tim@3 37 MIN = {
tim@3 38 'x' : 0.,
tim@3 39 'y' : 0.,
tim@3 40 'z' : 0.,
tim@3 41 'pitch' : 0,
tim@3 42 'cc1' : 0,
tim@3 43 'cc2' : 0,
tim@22 44 'velocity' : 0,
tim@3 45 }
tim@3 46 MAX = {
tim@3 47 'x' : 1.,
tim@3 48 'y' : 1.,
tim@3 49 'z' : 1.,
tim@3 50 'pitch' : 15,
tim@3 51 'cc1' : 127,
tim@3 52 'cc2' : 127,
tim@22 53 'velocity' : 127,
tim@3 54 }
tim@3 55
tim@3 56
tim@3 57
tim@3 58 #### PRIVATE VARIABLES ####
tim@4 59
tim@4 60 # mapping from channel to the currently playing note (pitch values) or None
tim@4 61 # initialize each channel to None:
tim@4 62 currently_playing = [None] * num_channels
tim@4 63
tim@4 64 # mapping from personId to time of last update
tim@30 65 # ((not yet implemented))
tim@30 66 #last_update_times = {}
tim@30 67
tim@30 68 # mapping from (channel, CC_number) to last CC value sent:
tim@30 69 last_value_sent = {
tim@30 70 (1, 1) : 0,
tim@30 71 (1, 2) : 0,
tim@30 72 (2, 1) : 0,
tim@30 73 (2, 2) : 0,
tim@30 74 (3, 1) : 0,
tim@30 75 (3, 2) : 0,
tim@30 76 }
tim@30 77
tim@3 78
tim@3 79 # OSC OBJECTS
tim@4 80 server = None # Initialised when start() is run
tim@3 81 client = OSCClient()
tim@3 82
tim@3 83
tim@4 84
tim@4 85
tim@4 86
tim@4 87 ### MAPPING
tim@4 88
tim@3 89 def send_to_joe(data, address='/test'):
tim@3 90 '''Sends `data` to Joe directly as an OSC message.
tim@3 91 '''
tim@22 92 message = OSCMessage(address)
tim@3 93 message.extend(data)
tim@3 94 client.sendto(message, joe)
tim@30 95 print_d('\n==OSC Output "%s" to Joe %s:==\n %s' % (address, joe, data))
tim@30 96
tim@30 97
tim@30 98 def send_to_ableton(data, address='/cc'):
tim@30 99 '''Sends `data` to Ableton (via Max) as an OSC message.
tim@30 100 '''
tim@30 101 #pdb.set_trace()
tim@30 102 message = OSCMessage(address)
tim@30 103 message.extend(data)
tim@30 104 client.sendto(message, ableton)
andrew@38 105 # print('\n==OSC Output "%s" to Ableton %s:==\n %s' % (address, ableton, data))
tim@30 106
tim@30 107
tim@3 108
tim@4 109 def flush(channel):
tim@4 110 '''Sends note off messages for whatever note is currently playing on
tim@4 111 `channel`.
tim@4 112 '''
tim@4 113 pitch = currently_playing[channel]
tim@4 114 if pitch:
tim@4 115 #print_d('Sending note-off for note %i on channel %i.' % (pitch, channel))
tim@4 116 send_to_joe([
tim@4 117 'Turn off note %i on channel %i' % (pitch, channel),
tim@4 118 # first string is ignored
tim@22 119 int(pitch), # pitch to turn off
tim@30 120 0, # 0 to turn note off
tim@30 121 127, # doesn't matter for note-off (but never send 0)
tim@22 122 int(channel),
tim@4 123 ])
tim@30 124 currently_playing[channel] = None
tim@3 125
tim@3 126
tim@41 127 def stopperson_handler(address, tags, data, clinet_address):
tim@41 128 ''' Handles OSC input matching 'stopperson'
tim@41 129
tim@41 130 Called when a person leaves the screen.
tim@41 131 '''
tim@41 132 flush(data[0])
tim@41 133
tim@3 134 def person_handler(address, tags, data, client_address):
tim@3 135 ''' Handles OSC input matching the 'person' tag.
tim@3 136
tim@4 137 `data` should be in form [person_id, x, y, z]
tim@3 138 '''
tim@41 139 if debugging:
tim@41 140 print('Received packet:')
tim@41 141 print(str(data))
tim@22 142 pitch, velocity, channel, cc1, cc2 = grid_map(*data)
andrew@38 143 channel = channel + 1
tim@30 144 cc1, cc2 = int(round(cc1)), int(round(cc2))
tim@30 145 if cc1 != last_value_sent[(channel, 1)]:
tim@30 146 send_to_ableton([channel, 1, cc1], '/cc')
tim@30 147 last_value_sent[(channel, 1)] = cc1
tim@30 148 if cc2 != last_value_sent[(channel, 2)]:
tim@30 149 send_to_ableton([channel, 2, cc2], '/cc')
tim@30 150 last_value_sent[(channel, 2)] = cc2
tim@41 151
tim@3 152
tim@3 153 ## Format data for Joe - done using Specification.txt on 2011-02-15
tim@3 154
tim@3 155 # constrain and round off pitch and velocity
tim@3 156 pitch = max(min(round(pitch), MAX['pitch']), MIN['pitch'])
tim@4 157 velocity = max(min(round(velocity), MAX['velocity']), MIN['velocity'])
tim@3 158
tim@4 159 if velocity and pitch == currently_playing[channel]:
tim@4 160 return # keep playing current note
tim@3 161
tim@4 162 # otherwise turn note off:
tim@4 163 flush(channel)
tim@4 164
tim@4 165 if velocity: # if there is a new note to play
tim@4 166 send_to_joe([
tim@4 167 'Turn on note %i on channel %i' % (pitch, channel),
tim@4 168 # first value is string which is ignored
tim@22 169 int(pitch),
tim@30 170 1, # 1 to turn note on
tim@22 171 int(velocity),
tim@22 172 int(channel)
tim@4 173 ])
tim@30 174 currently_playing[channel] = pitch
tim@30 175
tim@30 176
tim@3 177
tim@3 178
tim@3 179
tim@3 180 def grid_map(person_id, x, y, z):
tim@3 181 '''This function maps from a person's location to MIDI data
tim@3 182 returning a tuple (pitch, velocity, channel, cc1, cc2).
tim@3 183
tim@3 184 The current mapping creates higher pitch values as the person moves
tim@3 185 closer to the Kinect (i.e. z decreases). x and y values are mapped to cc1
tim@3 186 and cc2 (to be sent straight to Ableton and determined by a particular
tim@3 187 synth)
tim@3 188
tim@3 189 NB. channel == person_id and velocity==0 when note is off.
tim@3 190 Midi-Velocity is currently unimplemented but will use Person-velocity data
tim@3 191 when that becomes available.
tim@3 192 This function does not guarantee that the output will be in range if the
tim@3 193 input goes out of range.
tim@3 194 '''
tim@3 195 pitch = round(interpolate(z,
tim@3 196 MIN['z'], MAX['z'],
tim@3 197 MIN['pitch'], MAX['pitch'])
tim@3 198 )
tim@3 199 cc1 = round(interpolate(x,
tim@3 200 MIN['x'], MAX['x'],
tim@3 201 MIN['cc1'], MAX['cc1'])
tim@3 202 )
tim@30 203 cc2 = round(interpolate(y,
tim@3 204 MIN['y'], MAX['y'],
tim@3 205 MIN['cc2'], MAX['cc2'])
tim@3 206 )
tim@3 207 velocity = 127
tim@3 208 return (pitch, velocity, person_id, cc1, cc2)
tim@3 209
tim@3 210
tim@3 211
tim@3 212
tim@3 213 def interpolate(x, a, b, A, B):
tim@3 214 ''' Interpolates x from the range [a, b] to the range [A, B].
tim@3 215
tim@3 216
tim@3 217 Interpolation is linear. From [a,b] to [A,B] this would be:
tim@3 218 (B-A)*(x-a)/(b-a) + A
tim@3 219 '''
tim@3 220 return (B-A)*(x-a)/(b-a) + A
tim@4 221
tim@4 222
tim@4 223 def print_d(string):
tim@4 224 '''Function to print out debug method. Disable if processing power is in
tim@4 225 demand.
tim@4 226 '''
tim@4 227 print(string)
tim@4 228
tim@4 229
tim@4 230
tim@4 231
tim@4 232
tim@4 233
tim@4 234 #### CONTROL
tim@4 235
tim@4 236 def start():
tim@4 237 '''Set up OSC servers and start the program running.
tim@4 238 '''
tim@40 239 global server
tim@40 240 set_ip_addresses()
tim@4 241 server = ThreadingOSCServer(('localhost', my_port))
tim@4 242 # Register OSC callbacks:
tim@4 243 server.addMsgHandler('/person', person_handler)
tim@41 244 server.addMsgHandler('/stopperson', stopperson_handler)
tim@4 245 t = Thread(target=server.serve_forever)
tim@4 246 t.start()
tim@4 247 if server.running and t.is_alive():
tim@4 248 print('OSC Server running on port %i.' % my_port)
tim@4 249 print("Use 'stop()' to close it.")
tim@4 250 else:
tim@4 251 print('Error: Either server thread died or server is not reported as running.')
tim@4 252
tim@4 253
tim@4 254 def stop():
tim@4 255 '''Close the OSC server.
tim@4 256 '''
tim@4 257 if server:
tim@4 258 server.close()
tim@4 259 sleep(0.3)
tim@4 260 if not server.running:
tim@4 261 print("\n\nSuccessfully closed the OSC server. Ignore a 'Bad file descriptor' Exception - there's nothing I can do about that.")
tim@4 262 print("Type 'quit()' to exit.")
tim@4 263 else:
tim@4 264 print('Error: server has been told to close but is still running.')
tim@4 265
tim@40 266
tim@40 267 def get_ip_addresses_from_file(filename=ip_address_file):
tim@40 268 '''Load up ip addresses from a file in the format saved by the
tim@40 269 set_ip_addresses function.
tim@40 270 '''
tim@40 271 f = open(filename, 'r')
tim@40 272 lines = f.readlines()
tim@40 273 global ableton, joe
tim@40 274 ableton = eval(lines[1].split('#')[0].strip()) # set ableton
tim@40 275 joe = eval(lines[2].split('#')[0].strip()) # set joe
tim@40 276 if not ableton:
tim@40 277 print('Failed to set IP for Ableton from file.')
tim@40 278
tim@40 279 if not joe:
tim@40 280 print('Failed to set IP for Joe from file.')
tim@40 281 f.close()
tim@40 282
tim@40 283
tim@40 284
tim@40 285 def set_ip_addresses(save_file=ip_address_file):
tim@40 286 '''Manually set IP addresses based on input from user. IPs are then saved to
tim@40 287 a file.
tim@40 288 '''
tim@40 289 global joe, ableton
tim@40 290 if not joe: joe = ('localhost', 12346)
tim@40 291 if not ableton: ableton = ('localhost', 12345)
tim@40 292 # set joe
tim@40 293 try:
tim@40 294 new_joe = eval(raw_input("Enter IP address and port for Joe in form: "+str(joe)+' or leave blank to keep that address.\n'))
tim@40 295 except:
tim@40 296 new_joe = None
tim@40 297 if new_joe: joe = new_joe
tim@40 298 print("Address for Joe: "+str(joe)+"\n")
tim@40 299 # set ableton
tim@40 300 try:
tim@40 301 new_ableton = eval(raw_input('Enter IP address and port for Ableton in form: '+str(ableton)+' or leave blank to keep that address.\n'))
tim@40 302 except:
tim@40 303 new_ableton = None
tim@40 304 if new_ableton: ableton = new_ableton
tim@40 305 print('Address for Ableton: '+str(ableton)+'\n')
tim@40 306 # save to file
tim@40 307 if new_ableton or new_joe:
tim@40 308 print('Writing new addresses to file %s.' % save_file)
tim@40 309 try:
tim@40 310 f = open(save_file, 'w')
tim@40 311 f.write(
tim@40 312 '''# 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
tim@41 313 %s # set Ableton IP and port
tim@41 314 %s # set Joe's processing IP and port
tim@40 315 ''' % (str(ableton),str(joe))
tim@40 316 )
tim@40 317 f.flush()
tim@40 318 f.close()
tim@40 319 except IOError:
tim@40 320 print('SAVE FAILED.')
tim@40 321
tim@40 322
tim@40 323
tim@40 324
tim@40 325
tim@40 326 get_ip_addresses_from_file(ip_address_file)
tim@40 327
tim@40 328
tim@4 329 if __name__=='__main__':
tim@4 330 start()
tim@4 331 while True:
tim@4 332 try:
tim@41 333 print(eval(input()))
tim@4 334 except Exception as e:
tim@4 335 print('Caught %s:' % repr(e))
tim@4 336 exception = e
tim@4 337 trace = traceback.format_exc()
tim@4 338 print('Exception saved as `exception`. Stack trace saved as `trace`.')
tim@40 339