From 62e18f0871161cc0481e700aacc5fdc7d2e32a0d Mon Sep 17 00:00:00 2001
From: Grissess <grissess@nexusg.org>
Date: Thu, 20 Aug 2015 23:06:11 -0400
Subject: Partial commit (sorry :( )

---
 broadcast.py |   4 ++-
 mkarduino.py |  29 +++++++++++++++
 shiv.py      |  44 +++++++++++++++++++++++
 voice.py     | 116 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 192 insertions(+), 1 deletion(-)
 create mode 100644 mkarduino.py
 create mode 100644 shiv.py
 create mode 100644 voice.py

diff --git a/broadcast.py b/broadcast.py
index f0b987b..70e6e98 100644
--- a/broadcast.py
+++ b/broadcast.py
@@ -142,6 +142,8 @@ if options.live or options.list_live:
         ev = S.event_input(seq.client)
         event = None
         if ev:
+            if options.verbose:
+                print 'SEQ:', ev
             if ev < 0:
                 seq._error(ev)
             if ev.type == S.SND_SEQ_EVENT_NOTEON:
@@ -159,7 +161,7 @@ if options.live or options.list_live:
                 continue
         if event is not None:
             if isinstance(event, midi.NoteOnEvent) and event.velocity == 0:
-                ev.__class__ = midi.NoteOffEvent
+                event.__class__ = midi.NoteOffEvent
             if options.verbose:
                 print 'EVENT:', event
             if isinstance(event, midi.NoteOnEvent):
diff --git a/mkarduino.py b/mkarduino.py
new file mode 100644
index 0000000..39926c5
--- /dev/null
+++ b/mkarduino.py
@@ -0,0 +1,29 @@
+#IV to arduino array computer
+
+import xml.etree.ElementTree as ET
+import sys
+
+iv = ET.parse(sys.argv[1]).getroot()
+
+streams = iv.findall('./streams/stream[@type="ns"]')
+if len(streams) > 3:
+    print 'WARNING: Too many streams'
+
+for i in xrange(min(3, len(streams))):
+    stream = streams[i]
+    notes = stream.findall('note')
+
+# First, the header
+    sys.stdout.write('const uint16_t track%d[] PROGMEM = {\n'%(i,))
+
+# For the first note, write out the delay needed to get there
+    if notes[0].get('time') > 0:
+        sys.stdout.write('%d, 0,\n'%(int(float(notes[0].get('time'))*1000),))
+
+    for idx, note in enumerate(notes):
+        sys.stdout.write('%d, FREQ(%d),\n'%(int(float(note.get('dur'))*1000), int(440.0 * 2**((int(note.get('pitch'))-69)/12.0))))
+        if idx < len(notes)-1 and float(note.get('time'))+float(note.get('dur')) < float(notes[idx+1].get('time')):
+            sys.stdout.write('%d, 0,\n'%(int(1000*(float(notes[idx+1].get('time')) - (float(note.get('time')) + float(note.get('dur'))))),))
+
+# Finish up the stream
+    sys.stdout.write('};\n\n')
diff --git a/shiv.py b/shiv.py
new file mode 100644
index 0000000..96d8ea2
--- /dev/null
+++ b/shiv.py
@@ -0,0 +1,44 @@
+# IV file viewer
+
+import xml.etree.ElementTree as ET
+import optparse
+import sys
+
+parser = optparse.OptionParser()
+parser.add_option('-n', '--number', dest='number', action='store_true', help='Show number of tracks')
+parser.add_option('-g', '--groups', dest='groups', action='store_true', help='Show group names')
+parser.add_option('-N', '--notes', dest='notes', action='store_true', help='Show number of notes')
+parser.add_option('-m', '--meta', dest='meta', action='store_true', help='Show meta track information')
+parser.add_option('-h', '--histogram', dest='histogram', action='store_true', help='Show a histogram distribution of pitches')
+parser.add_option('-H', '--histogram-tracks', dest='histogram_tracks', action='store_true', help='Show a histogram distribution of pitches per track')
+parser.add_option('-d', '--duration', dest='duration', action='store_true', help='Show the duration of the piece')
+parser.add_option('-D', '--duty-cycle', dest='duty_cycle', action='store_true', help='Show the duration of the notes within tracks, and as a percentage of the piece duration')
+
+parser.add_option('-a', '--almost-all', dest='almost_all', action='store_true', help='Show useful information')
+parser.add_option('-A', '--all', dest='all', action='store_true', help='Show everything')
+
+options, args = parser.parse_args()
+
+if options.almost_all or options.all:
+    options.number = True
+    options.groups = True
+    options.notes = True
+    options.histogram = True
+    options.duration = True
+    if options.all:
+        options.meta = True
+        options.histogram_tracks= True
+        options.duty_cycle = True
+
+for fname in args:
+    try:
+        iv = ET.parse(fname).getroot()
+    except IOError:
+        import traceback
+        traceback.print_exc()
+        print 'Bad file :', fname, ', skipping...'
+        continue
+    print
+    print 'File :', fname
+    print '\t<computing...>'
+
diff --git a/voice.py b/voice.py
new file mode 100644
index 0000000..eb0b43f
--- /dev/null
+++ b/voice.py
@@ -0,0 +1,116 @@
+'''
+voice -- Voices
+
+A voice is a simple, singular unit of sound generation that encompasses the following
+properties:
+-A *generator*: some function that generates a waveform. As an input, it receives theta,
+ the phase of the signal it is to generate (in [0, 2pi)) and, as an output, it produces
+ the sample at that point, a normalized amplitude value in [-1, 1].
+-An *envelope*: a function that receives a boolean (the status of whether or not a note
+ is playing now) and the change in time, and outputs a factor in [0, 1] that represents
+ a modification to the volume of the generator (pre-output mix).
+All of these functions may internally store state or other data, usually by being 
+implemented as a class with a __call__ method.
+
+Voices are meant to generate audio data. This can be done in a number of ways, least to
+most abstracted:
+-A sample at a certain phase (theta) may be gotten from the generator; this can be done
+ by calling the voice outright;
+-A set of samples can be generated via the .samples() method, which receives the number
+ of samples to generate and the phase velocity (a function of the sample rate and the
+ desired frequency of the waveform's period; this can be calculated using the static
+ method .phase_vel());
+-Audio data with enveloping can be generated using the .data() method, which calls the
+ envelope function as if the note is depressed at the given phase velocity; if the
+ freq is specified as None, then the note is treated as released. Note that
+ this will often be necessary for envelopes, as many of them are stateful (as they
+ depend on the first derivative of time). Also, at this level, the Voice will maintain
+ some state (namely, the phase at the end of generation) which will ensure (C0) smooth
+ transitions between already smooth generator functions, even if the frequency changes.
+-Finally, a pyaudio-compatible stream callback can be provided with .pyaudio_scb(), a
+ method that returns a function that arranges to call .data() with the appropriate values.
+ The freq input to .data() will be taken from the .freq member of the voice in a possibly
+ non-atomic manner.
+'''
+
+import math
+import pyaudio
+import struct
+
+def norm_theta(theta):
+    return theta % (2*math.pi)
+
+def norm_amp(amp):
+    return min(1.0, max(-1.0, amp))
+
+def theta2lin(theta):
+    return theta / (2*math.pi)
+
+def lin2theta(lin):
+    return lin * 2*math.pi
+
+class ParamInfo(object):
+    PT_ANY =       0x0000
+    PT_CONST =     0x0001
+    PT_SPECIAL =   0x0002
+    PT_INT =       0x0100
+    PT_FLOAT =     0x0200
+    PT_STR =       0x0400
+    PT_THETA =     0x0102
+    PT_TIME_SEC =  0x0202
+    PT_SAMPLES =   0x0302
+    PT_REALTIME =  0x0402
+    def __init__(self, name, tp=PT_ANY):
+        self.name = name
+        self.tp = tp
+
+class GenInfo(object):
+    def __init__(self, name, *params):
+        self.name = name
+        self.params = list(params)
+
+class Generator(object):
+    class __metaclass__(type):
+        def __init__(self
+
+class Voice(object):
+    @classmethod
+    def register_gen(cls, name, params):
+    def __init__(self, generator=None, envelope=None):
+        self.generator = generator or self.DEFAULT_GENERATOR
+        self.envelope = envelope or self.DEFAULT_ENVELOPE
+        self.phase = 0
+        self.freq = None
+    def __call__(self, theta):
+        return norm_amp(self.generator(norm_theta(theta)))
+    @staticmethod
+    def phase_vel(freq, samp_rate):
+        return 2 * math.pi * freq / samp_rate
+    def samples(self, frames, pvel):
+        for i in xrange(frames):
+            yield self(self.phase)
+            self.phase = norm_theta(self.phase + pvel)
+    def data(self, frames, freq, samp_rate):
+        period = 1.0/samp_rate
+        status = freq is not None
+        for samp in self.samples(frames, self.phase_vel(freq, samp_rate)):
+            yield samp * self.envelope(status, period)
+    def pyaudio_scb(self, rate, fmt=pyaudio.paInt16):
+        samp_size = pyaudio.get_sample_size(fmt)
+        maxint = (1 << (8*samp_size)) - 1
+        dtype = ['!', 'h', 'i', '!', 'l', '!', '!', '!', 'q'][samp_size]
+        def __callback(data, frames, time, status, self=self, rate=rate, maxint=maxint, dtype=dtype):
+            return struct.pack(dtype*frames, *[maxint*int(i) for i in self.data(frames, self.freq, rate)])
+        return __callback
+
+class VMeanMixer(Voice):
+    def __init__(self, *voices):
+        self.voices = list(voices)
+    def __call__(self, theta):
+        return sum([i(theta)/len(self.voices) for i in self.voices])
+
+class VSumMixer(Voice):
+    def __init__(self, *voices):
+        self.voices = list(voices)
+    def __call__(self, theta):
+        return sum([i(theta) for i in self.voices])
-- 
cgit v1.2.3-70-g09d2