Mercurial > hg > movesynth
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`.') |