changeset 4:5f9ad838d417

Completed grid_mapper.py
author Tim MB <tim.murraybrowne@eecs.qmul.ac.uk>
date Thu, 17 Feb 2011 12:46:52 +0000
parents 7bd1f07044ab
children 149328b21d06
files Specification.txt tim_grid_mapper/grid_mapper.py
diffstat 2 files changed, 113 insertions(+), 18 deletions(-) [+]
line wrap: on
line diff
--- a/Specification.txt	Tue Feb 15 17:31:11 2011 +0000
+++ b/Specification.txt	Thu Feb 17 12:46:52 2011 +0000
@@ -8,7 +8,7 @@
 s0 - ignored
 i1 - pitch value: 0 <= i1 < 16
 i2 - on/off: i2==0 or i2==1
-i3 - velocity: 1 <= i3 <= 127
+i3 - velocity: 1 <= i3 <= 127 (never send 0)
 i4 - channel: anything
 
 /chord
--- a/tim_grid_mapper/grid_mapper.py	Tue Feb 15 17:31:11 2011 +0000
+++ b/tim_grid_mapper/grid_mapper.py	Thu Feb 17 12:46:52 2011 +0000
@@ -8,14 +8,17 @@
 '''
 
 from OSC import ThreadingOSCServer, OSCClient, OSCMessage, OSCClientError
+from threading import Thread
+from time import sleep
 
 #### PUBLIC OPTIONS ####
-num_instruments = 3 # number of output channels
+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 = 2001 # to receive OSC messages
-joe     = ('localhost', " PUT JOE's PORT NUMBER IN HERE")
-ableton = ('localhost', " PUT ABLETON'S PORT NUMBER IN HERE ")
+joe     = ('localhost', "THIS_MUST_BE_SET")
+ableton = ('localhost', "THIS_MUST_BE_SET")
 
 ### Constants for grid mapping:
 # The range of values that the input coordinates and output values may take:
@@ -40,27 +43,53 @@
 
 
 #### PRIVATE VARIABLES ####
-person_positions  = {} # mapping from personId to x,y,z
-last_update_times = {} # mapping from personId to time of last update
+
+# 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
+last_update_times = {} 
 
 # OSC OBJECTS
-server = ThreadingOSCServer(('localhost'), my_port)
+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 = OSCMesssage(address)
 	message.extend(data)
 	client.sendto(message, joe)
+	print_d('==OSC Output to Joe %s:==\n    %s' % (joe, 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
+			pitch,       # pitch to turn off
+			0,                                # 0 to turn note off
+			127,                # doesn't matter for note-off (but never send 0)
+			channel,
+		])
 
 
 def person_handler(address, tags, data, client_address):
 	''' Handles OSC input matching the 'person' tag.
 	
-	`data` is [person_id, x, y, z]
+	`data` should be in form [person_id, x, y, z]
 	'''
 	pitch, velocity, channel, cc1, cc2 = grid_map(data)
 	
@@ -68,18 +97,23 @@
 	
 	# constrain and round off pitch and velocity
 	pitch = max(min(round(pitch), MAX['pitch']), MIN['pitch'])
-	velocity = max(min(round(pitch), MAX['velocity']), MIN['velocity'])
+	velocity = max(min(round(velocity), MAX['velocity']), MIN['velocity'])
 	
-	# turn integer pitch into a single 1 in a boolean array of 0s
-	boolean_note_array = [0] * (MAX['pitch'] - MIN['pitch'] + 1)
-	boolean_note_array[pitch] = 1
+	if velocity and pitch == currently_playing[channel]:
+		return # keep playing current note
 	
-	# seperate boolean for note-on and note-off
-	note_on = velocity > 0
-	# Never send velocity == 0
-	if velocity==0: 
-		velocity = 127
-	THIS NEEDS FINISHING.
+	# 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
+			pitch,    
+			1,        # 1 to turn note on
+			velocity, 
+			channel
+		])
 	
 
 
@@ -125,3 +159,64 @@
 	(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 joe, ableton, server
+	if joe[1] == "THIS_MUST_BE_SET":
+		joe_port = input("Enter port number on %s for Joe's synth software: " % joe[0])
+		joe = (joe[0], joe_port)
+	
+	if ableton[1] == "THIS_MUST_BE_SET":
+		ableton_port = input("Enter port number on %s for Ableton: " % ableton[0])
+		ableton = (ableton[0], ableton_port)
+
+	server = ThreadingOSCServer(('localhost', my_port))
+	# Register OSC callbacks:
+	server.addMsgHandler('/person', person_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.')
+
+if __name__=='__main__':
+	start()
+	while True:
+		try:
+			print(repr(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`.')