Browse Source

First (terrible) commit

csguest 10 năm trước cách đây
commit
b49521e6a1
4 tập tin đã thay đổi với 338 bổ sung0 xóa
  1. 3 0
      .gitignore
  2. 90 0
      broadcast.py
  3. 89 0
      client.c
  4. 156 0
      mkiv.py

+ 3 - 0
.gitignore

@@ -0,0 +1,3 @@
+client
+*.iv
+*.xml

+ 90 - 0
broadcast.py

@@ -0,0 +1,90 @@
+import socket
+import sys
+import struct
+import time
+import xml.etree.ElementTree as ET
+import threading
+
+PORT = 13676
+if len(sys.argv) > 2:
+	factor = float(sys.argv[2])
+else:
+	factor = 1
+
+print 'Factor:', factor
+
+class Packet(object):
+	def __init__(self, cmd, *data):
+		self.cmd = cmd
+		self.data = data
+		if len(data) >= 8:
+			raise ValueError('Too many data')
+		self.data = list(self.data) + [0] * (8-len(self.data))
+	def __str__(self):
+		return struct.pack('>L'+('L'*len(self.data)), self.cmd, *self.data)
+
+class CMD:
+	KA = 0 # No important data
+	PING = 1 # Data are echoed exactly
+	QUIT = 2 # No important data
+	PLAY = 3 # seconds, microseconds, frequency (Hz), amplitude (0-255)
+
+s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
+
+clients = []
+
+s.sendto(str(Packet(CMD.PING)), ('255.255.255.255', PORT))
+s.settimeout(0.5)
+
+try:
+	while True:
+		data, src = s.recvfrom(4096)
+		clients.append(src)
+except socket.timeout:
+	pass
+
+print 'Clients:'
+for cl in clients:
+	print cl
+	if sys.argv[1] == '-t':
+		s.sendto(str(Packet(CMD.PLAY, 0, 250000, 440, 255)), cl)
+		time.sleep(0.25)
+		s.sendto(str(Packet(CMD.PLAY, 0, 250000, 880, 255)), cl)
+	if sys.argv[1] == '-q':
+		s.sendto(str(Packet(CMD.QUIT)), cl)
+
+try:
+	iv = ET.parse(sys.argv[1]).getroot()
+except IOError:
+	print 'Bad file'
+	exit()
+
+notestreams = iv.findall("./streams/stream[@type='ns']")
+
+class NSThread(threading.Thread):
+	def run(self):
+		nsq, cl = self._Thread__args
+		for note in nsq:
+			ttime = float(note.get('time'))
+			pitch = int(note.get('pitch'))
+			vel = int(note.get('vel'))
+			dur = factor*float(note.get('dur'))
+			while time.time() - BASETIME < factor*ttime:
+				time.sleep(factor*ttime - (time.time() - BASETIME))
+			s.sendto(str(Packet(CMD.PLAY, int(dur), int((dur*1000000)%1000000), int(440.0 * 2**((pitch-69)/12.0)), vel*2)), cl)
+			time.sleep(dur)
+
+threads = []
+for ns in notestreams:
+	if not clients:
+		print 'WARNING: Out of clients!'
+		break
+	nsq = ns.findall('note')
+	threads.append(NSThread(args=(nsq, clients.pop(0))))
+
+BASETIME = time.time()
+for thr in threads:
+	thr.start()
+for thr in threads:
+	thr.join()

+ 89 - 0
client.c

@@ -0,0 +1,89 @@
+#include <stdio.h>
+#include <signal.h>
+#include <string.h>
+#include <fcntl.h>
+
+#include <sys/time.h>
+#include <sys/socket.h>
+
+#include <netinet/in.h>
+
+#include <linux/kd.h>
+
+#define CLK_FREQ 1193180
+
+int term;
+
+enum cmd_t {CMD_KA, CMD_PING, CMD_QUIT, CMD_PLAY};
+
+struct cmd_buffer {
+	int cmd;
+	int data[8];
+};
+
+void sigalrm(int sig) {
+	ioctl(term, KIOCSOUND, 0);
+}
+
+int main(int argc, char **argv) {
+	struct sockaddr_in addr, remote;
+	int sock, rlen = sizeof(remote), i;
+	struct itimerval tmr;
+	struct cmd_buffer cmd;
+	struct sigaction sa;
+
+	if((term = open("/dev/console", O_WRONLY)) < 0) {
+		perror("open");
+		return 1;
+	}
+	if((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
+		perror("socket");
+		return 1;
+	}
+
+	memset(&addr, 0, sizeof(addr));
+	addr.sin_family = AF_INET;
+	addr.sin_addr.s_addr = htonl(INADDR_ANY);
+	addr.sin_port = htons(13676);
+	if((bind(sock, (struct sockaddr *) &addr, sizeof(addr))) < 0) {
+		perror("bind");
+		return 1;
+	}
+
+	sa.sa_handler = sigalrm;
+	sa.sa_flags = SA_NODEFER | SA_RESTART;
+	sigemptyset(&sa.sa_mask);
+	sigaction(SIGALRM, &sa, NULL);
+
+	printf("Ready to begin (listening on 13676)\n");
+	memset(&tmr, 0, sizeof(tmr));
+	while(1) {
+		if(recvfrom(sock, &cmd, sizeof(cmd), 0, (struct sockaddr *) &remote, &rlen) < 0) {
+			perror("recvfrom");
+			return 1;
+		}
+		cmd.cmd = ntohl(cmd.cmd);
+		for(i = 0; i < 8; i++) cmd.data[i] = ntohl(cmd.data[i]);
+		/* printf("From %s:%d, cmd %d\n", inet_ntoa(remote.sin_addr.s_addr), remote.sin_port, cmd.cmd); */
+		switch((enum cmd_t) cmd.cmd) {
+			case CMD_QUIT:
+				return 0;
+				break;
+
+			case CMD_PING:
+				sendto(sock, &cmd, sizeof(cmd), 0, (struct sockaddr *) &remote, rlen);
+				break;
+
+			case CMD_PLAY:
+				tmr.it_value.tv_sec = cmd.data[0];
+				tmr.it_value.tv_usec = cmd.data[1];
+				setitimer(ITIMER_REAL, &tmr, NULL);
+				ioctl(term, KIOCSOUND, (int) (CLK_FREQ / cmd.data[2]));
+		
+			default:
+				printf("WARNING: Unknown cmd %d\n", cmd.cmd);
+			case CMD_KA: 
+				break;
+		}
+	}
+}

+ 156 - 0
mkiv.py

@@ -0,0 +1,156 @@
+'''
+itl_chorus -- ITL Chorus Suite
+mkiv -- Make Intervals
+
+This simple script (using python-midi) reads a MIDI file and makes an interval
+(.iv) file (actually XML) that contains non-overlapping notes.
+
+TODO:
+-Reserve channels by track
+-Reserve channels by MIDI channel
+-Pitch limits for channels
+-MIDI Control events
+'''
+
+import xml.etree.ElementTree as ET
+import midi
+import sys
+import os
+
+pat = midi.read_midifile(sys.argv[1])
+iv = ET.Element('iv')
+iv.set('version', '1')
+iv.set('src', os.path.basename(sys.argv[1]))
+
+##### Merge events from all tracks into one master list, annotated with track and absolute times #####
+print 'Merging events...'
+
+class MergeEvent(object):
+	__slots__ = ['ev', 'tidx', 'abstime']
+	def __init__(self, ev, tidx, abstime):
+		self.ev = ev
+		self.tidx = tidx
+		self.abstime = abstime
+	def __repr__(self):
+		return '<ME %r in %d @%f>'%(self.ev, self.tidx, self.abstime)
+
+events = []
+bpm_at = {0: 120}
+
+for tidx, track in enumerate(pat):
+	abstime = 0
+	absticks = 0
+	for ev in track:
+		if isinstance(ev, midi.SetTempoEvent):
+			absticks += ev.tick
+			bpm_at[absticks] = ev.bpm
+		else:
+			if isinstance(ev, midi.NoteOnEvent) and ev.velocity == 0:
+				ev.__class__ = midi.NoteOffEvent #XXX Oww
+			bpm = filter(lambda pair: pair[0] <= absticks, bpm_at.items())[-1][1]
+			abstime += (60.0 * ev.tick) / (bpm * pat.resolution)
+			absticks += ev.tick
+			events.append(MergeEvent(ev, tidx, abstime))
+
+print 'Sorting events...'
+
+events.sort(key = lambda ev: ev.abstime)
+
+##### Use merged events to construct a set of streams with non-overlapping durations #####
+print 'Generating streams...'
+
+class DurationEvent(MergeEvent):
+	__slots__ = ['duration']
+	def __init__(self, me, dur):
+		MergeEvent.__init__(self, me.ev, me.tidx, me.abstime)
+		self.duration = dur
+
+class NoteStream(object):
+	__slots__ = ['history', 'active']
+	def __init__(self):
+		self.history = []
+		self.active = None
+	def IsActive(self):
+		return self.active is not None
+	def Activate(self, mev):
+		self.active = mev
+	def Deactivate(self, mev):
+		self.history.append(DurationEvent(self.active, mev.abstime - self.active.abstime))
+		self.active = None
+	def WouldDeactivate(self, mev):
+		if not self.IsActive():
+			return False
+		return mev.ev.pitch == self.active.ev.pitch and mev.tidx == self.active.tidx
+
+notestreams = []
+auxstream = []
+
+for mev in events:
+	if isinstance(mev.ev, midi.NoteOnEvent):
+		for stream in notestreams:
+			if not stream.IsActive():
+				stream.Activate(mev)
+				break
+		else:
+			stream = NoteStream()
+			notestreams.append(stream)
+			stream.Activate(mev)
+	elif isinstance(mev.ev, midi.NoteOffEvent):
+		for stream in notestreams:
+			if stream.WouldDeactivate(mev):
+				stream.Deactivate(mev)
+				break
+		else:
+			print 'WARNING: Did not match %r with any stream deactivation.'%(mev,)
+	else:
+		auxstream.append(mev)
+
+lastabstime = events[-1].abstime
+
+for ns in notestreams:
+	if not ns:
+		print 'WARNING: Active notes at end of playback.'
+		ns.Deactivate(MergeEvent(ns.active, ns.active.tidx, lastabstime))
+
+print 'Generated %d streams'%(len(notestreams),)
+
+##### Write to XML and exit #####
+
+ivmeta = ET.SubElement(iv, 'meta')
+ivbpms = ET.SubElement(ivmeta, 'bpms')
+abstime = 0
+prevticks = 0
+prev_bpm = 120
+for absticks, bpm in sorted(bpm_at.items(), key = lambda pair: pair[0]):
+	abstime += ((absticks - prevticks) * 60.0) / (prev_bpm * pat.resolution)
+	prevticks = absticks
+	ivbpm = ET.SubElement(ivbpms, 'bpm')
+	ivbpm.set('bpm', str(bpm))
+	ivbpm.set('ticks', str(absticks))
+	ivbpm.set('time', str(abstime))
+
+ivstreams = ET.SubElement(iv, 'streams')
+
+for ns in notestreams:
+	ivns = ET.SubElement(ivstreams, 'stream')
+	ivns.set('type', 'ns')
+	for note in ns.history:
+		ivnote = ET.SubElement(ivns, 'note')
+		ivnote.set('pitch', str(note.ev.pitch))
+		ivnote.set('vel', str(note.ev.velocity))
+		ivnote.set('time', str(note.abstime))
+		ivnote.set('dur', str(note.duration))
+
+ivaux = ET.SubElement(ivstreams, 'stream')
+ivaux.set('type', 'aux')
+
+fw = midi.FileWriter()
+fw.RunningStatus = None # XXX Hack
+
+for mev in auxstream:
+	ivev = ET.SubElement(ivaux, 'ev')
+	ivev.set('time', str(mev.abstime))
+	ivev.set('data', repr(fw.encode_midi_event(mev.ev)))
+
+print 'Done.'
+open(os.path.splitext(os.path.basename(sys.argv[1]))[0]+'.iv', 'w').write(ET.tostring(iv))