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