annotate 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
rev   line source
andrew@50 1 #! /usr/bin/python
andrew@50 2 '''
andrew@50 3 grid_mapper.py - maintained by Tim.
andrew@50 4
andrew@50 5 This module implements a mapping from person positions (id,x,y,z) to generate
andrew@50 6 pitch, velocities and channels for Joe's synthesiser and controller data for
andrew@50 7 Ableton.
andrew@50 8
andrew@50 9 '''
andrew@50 10
andrew@50 11 from OSC import ThreadingOSCServer, OSCClient, OSCMessage, OSCClientError
andrew@50 12 from threading import Thread
andrew@50 13 from time import sleep
andrew@50 14 import pdb
andrew@50 15
andrew@50 16 #### PUBLIC OPTIONS ####
andrew@50 17 num_channels = 3 # number of instruments (and max number of people who can
andrew@50 18 # make noise).
andrew@50 19
andrew@50 20 #### OSC OPTIONS - THESE NEED TO BE SET MANUALLY ####
andrew@50 21 my_port = 12344 # to receive OSC messages
andrew@50 22 joe = ('localhost', 12345)
andrew@50 23 ableton = ('localhost', 12346)
andrew@50 24
andrew@50 25 ### Constants for grid mapping:
andrew@50 26 # The range of values that the input coordinates and output values may take:
andrew@50 27 # (ranges are inclusive)
andrew@50 28 MIN = {
andrew@50 29 'x' : 0.,
andrew@50 30 'y' : 0.,
andrew@50 31 'z' : 0.,
andrew@50 32 'pitch' : 0,
andrew@50 33 'cc1' : 0,
andrew@50 34 'cc2' : 0,
andrew@50 35 'velocity' : 0,
andrew@50 36 }
andrew@50 37 MAX = {
andrew@50 38 'x' : 1.,
andrew@50 39 'y' : 1.,
andrew@50 40 'z' : 1.,
andrew@50 41 'pitch' : 15,
andrew@50 42 'cc1' : 127,
andrew@50 43 'cc2' : 127,
andrew@50 44 'velocity' : 127,
andrew@50 45 }
andrew@50 46
andrew@50 47
andrew@50 48
andrew@50 49 #### PRIVATE VARIABLES ####
andrew@50 50
andrew@50 51 # mapping from channel to the currently playing note (pitch values) or None
andrew@50 52 # initialize each channel to None:
andrew@50 53 currently_playing = [None] * num_channels
andrew@50 54
andrew@50 55 # mapping from personId to time of last update
andrew@50 56 # ((not yet implemented))
andrew@50 57 #last_update_times = {}
andrew@50 58
andrew@50 59 # mapping from (channel, CC_number) to last CC value sent:
andrew@50 60 last_value_sent = {
andrew@50 61 (1, 1) : 0,
andrew@50 62 (1, 2) : 0,
andrew@50 63 (2, 1) : 0,
andrew@50 64 (2, 2) : 0,
andrew@50 65 (3, 1) : 0,
andrew@50 66 (3, 2) : 0,
andrew@50 67 }
andrew@50 68
andrew@50 69
andrew@50 70 # OSC OBJECTS
andrew@50 71 server = None # Initialised when start() is run
andrew@50 72 client = OSCClient()
andrew@50 73
andrew@50 74
andrew@50 75
andrew@50 76
andrew@50 77
andrew@50 78 ### MAPPING
andrew@50 79
andrew@50 80 def send_to_joe(data, address='/test'):
andrew@50 81 '''Sends `data` to Joe directly as an OSC message.
andrew@50 82 '''
andrew@50 83 message = OSCMessage(address)
andrew@50 84 message.extend(data)
andrew@50 85 client.sendto(message, joe)
andrew@50 86 print_d('\n==OSC Output "%s" to Joe %s:==\n %s' % (address, joe, data))
andrew@50 87
andrew@50 88
andrew@50 89 def send_to_ableton(data, address='/cc'):
andrew@50 90 '''Sends `data` to Ableton (via Max) as an OSC message.
andrew@50 91 '''
andrew@50 92 #pdb.set_trace()
andrew@50 93 message = OSCMessage(address)
andrew@50 94 message.extend(data)
andrew@50 95 client.sendto(message, ableton)
andrew@50 96 print('\n==OSC Output "%s" to Ableton %s:==\n %s' % (address, ableton, data))
andrew@50 97
andrew@50 98
andrew@50 99
andrew@50 100 def flush(channel):
andrew@50 101 '''Sends note off messages for whatever note is currently playing on
andrew@50 102 `channel`.
andrew@50 103 '''
andrew@50 104 pitch = currently_playing[channel]
andrew@50 105 if pitch:
andrew@50 106 #print_d('Sending note-off for note %i on channel %i.' % (pitch, channel))
andrew@50 107 send_to_joe([
andrew@50 108 'Turn off note %i on channel %i' % (pitch, channel),
andrew@50 109 # first string is ignored
andrew@50 110 int(pitch), # pitch to turn off
andrew@50 111 0, # 0 to turn note off
andrew@50 112 127, # doesn't matter for note-off (but never send 0)
andrew@50 113 int(channel),
andrew@50 114 ])
andrew@50 115 currently_playing[channel] = None
andrew@50 116
andrew@50 117
andrew@50 118 def person_handler(address, tags, data, client_address):
andrew@50 119 ''' Handles OSC input matching the 'person' tag.
andrew@50 120
andrew@50 121 `data` should be in form [person_id, x, y, z]
andrew@50 122 '''
andrew@50 123 pitch, velocity, channel, cc1, cc2 = grid_map(*data)
andrew@50 124
andrew@50 125 cc1, cc2 = int(round(cc1)), int(round(cc2))
andrew@50 126 if cc1 != last_value_sent[(channel, 1)]:
andrew@50 127 send_to_ableton([channel, 1, cc1], '/cc')
andrew@50 128 last_value_sent[(channel, 1)] = cc1
andrew@50 129 if cc2 != last_value_sent[(channel, 2)]:
andrew@50 130 send_to_ableton([channel, 2, cc2], '/cc')
andrew@50 131 last_value_sent[(channel, 2)] = cc2
andrew@50 132
andrew@50 133
andrew@50 134 ## Format data for Joe - done using Specification.txt on 2011-02-15
andrew@50 135
andrew@50 136 # constrain and round off pitch and velocity
andrew@50 137 pitch = max(min(round(pitch), MAX['pitch']), MIN['pitch'])
andrew@50 138 velocity = max(min(round(velocity), MAX['velocity']), MIN['velocity'])
andrew@50 139
andrew@50 140 if velocity and pitch == currently_playing[channel]:
andrew@50 141 return # keep playing current note
andrew@50 142
andrew@50 143 # otherwise turn note off:
andrew@50 144 flush(channel)
andrew@50 145
andrew@50 146 if velocity: # if there is a new note to play
andrew@50 147 send_to_joe([
andrew@50 148 'Turn on note %i on channel %i' % (pitch, channel),
andrew@50 149 # first value is string which is ignored
andrew@50 150 int(pitch),
andrew@50 151 1, # 1 to turn note on
andrew@50 152 int(velocity),
andrew@50 153 int(channel)
andrew@50 154 ])
andrew@50 155 currently_playing[channel] = pitch
andrew@50 156
andrew@50 157
andrew@50 158
andrew@50 159
andrew@50 160
andrew@50 161 def grid_map(person_id, x, y, z):
andrew@50 162 '''This function maps from a person's location to MIDI data
andrew@50 163 returning a tuple (pitch, velocity, channel, cc1, cc2).
andrew@50 164
andrew@50 165 The current mapping creates higher pitch values as the person moves
andrew@50 166 closer to the Kinect (i.e. z decreases). x and y values are mapped to cc1
andrew@50 167 and cc2 (to be sent straight to Ableton and determined by a particular
andrew@50 168 synth)
andrew@50 169
andrew@50 170 NB. channel == person_id and velocity==0 when note is off.
andrew@50 171 Midi-Velocity is currently unimplemented but will use Person-velocity data
andrew@50 172 when that becomes available.
andrew@50 173 This function does not guarantee that the output will be in range if the
andrew@50 174 input goes out of range.
andrew@50 175 '''
andrew@50 176 pitch = round(interpolate(z,
andrew@50 177 MIN['z'], MAX['z'],
andrew@50 178 MIN['pitch'], MAX['pitch'])
andrew@50 179 )
andrew@50 180 cc1 = round(interpolate(x,
andrew@50 181 MIN['x'], MAX['x'],
andrew@50 182 MIN['cc1'], MAX['cc1'])
andrew@50 183 )
andrew@50 184 cc2 = round(interpolate(y,
andrew@50 185 MIN['y'], MAX['y'],
andrew@50 186 MIN['cc2'], MAX['cc2'])
andrew@50 187 )
andrew@50 188 velocity = 127
andrew@50 189 return (pitch, velocity, person_id, cc1, cc2)
andrew@50 190
andrew@50 191
andrew@50 192
andrew@50 193
andrew@50 194 def interpolate(x, a, b, A, B):
andrew@50 195 ''' Interpolates x from the range [a, b] to the range [A, B].
andrew@50 196
andrew@50 197
andrew@50 198 Interpolation is linear. From [a,b] to [A,B] this would be:
andrew@50 199 (B-A)*(x-a)/(b-a) + A
andrew@50 200 '''
andrew@50 201 return (B-A)*(x-a)/(b-a) + A
andrew@50 202
andrew@50 203
andrew@50 204 def print_d(string):
andrew@50 205 '''Function to print out debug method. Disable if processing power is in
andrew@50 206 demand.
andrew@50 207 '''
andrew@50 208 print(string)
andrew@50 209
andrew@50 210
andrew@50 211
andrew@50 212
andrew@50 213
andrew@50 214
andrew@50 215 #### CONTROL
andrew@50 216
andrew@50 217 def start():
andrew@50 218 '''Set up OSC servers and start the program running.
andrew@50 219 '''
andrew@50 220 global joe, ableton, server
andrew@50 221 if joe[1] == "THIS_MUST_BE_SET":
andrew@50 222 joe_port = input("Enter port number on %s for Joe's synth software: " % joe[0])
andrew@50 223 joe = (joe[0], joe_port)
andrew@50 224
andrew@50 225 if ableton[1] == "THIS_MUST_BE_SET":
andrew@50 226 ableton_port = input("Enter port number on %s for Ableton: " % ableton[0])
andrew@50 227 ableton = (ableton[0], ableton_port)
andrew@50 228
andrew@50 229 server = ThreadingOSCServer(('localhost', my_port))
andrew@50 230 # Register OSC callbacks:
andrew@50 231 server.addMsgHandler('/person', person_handler)
andrew@50 232 t = Thread(target=server.serve_forever)
andrew@50 233 t.start()
andrew@50 234 if server.running and t.is_alive():
andrew@50 235 print('OSC Server running on port %i.' % my_port)
andrew@50 236 print("Use 'stop()' to close it.")
andrew@50 237 else:
andrew@50 238 print('Error: Either server thread died or server is not reported as running.')
andrew@50 239
andrew@50 240
andrew@50 241 def stop():
andrew@50 242 '''Close the OSC server.
andrew@50 243 '''
andrew@50 244 if server:
andrew@50 245 server.close()
andrew@50 246 sleep(0.3)
andrew@50 247 if not server.running:
andrew@50 248 print("\n\nSuccessfully closed the OSC server. Ignore a 'Bad file descriptor' Exception - there's nothing I can do about that.")
andrew@50 249 print("Type 'quit()' to exit.")
andrew@50 250 else:
andrew@50 251 print('Error: server has been told to close but is still running.')
andrew@50 252
andrew@50 253 if __name__=='__main__':
andrew@50 254 start()
andrew@50 255 while True:
andrew@50 256 try:
andrew@50 257 print(repr(input()))
andrew@50 258 except Exception as e:
andrew@50 259 print('Caught %s:' % repr(e))
andrew@50 260 exception = e
andrew@50 261 trace = traceback.format_exc()
andrew@50 262 print('Exception saved as `exception`. Stack trace saved as `trace`.')