From 8504e47390723cc05b2171f87976bd8fae89dd64 Mon Sep 17 00:00:00 2001
From: Graham Northup <grissess@nexusg.org>
Date: Sat, 16 Nov 2019 02:50:16 -0500
Subject: Added bind addr, drums can use render

---
 drums.py | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 68 insertions(+), 1 deletion(-)

(limited to 'drums.py')

diff --git a/drums.py b/drums.py
index 1fa0653..89faca9 100644
--- a/drums.py
+++ b/drums.py
@@ -7,6 +7,11 @@ import cStringIO as StringIO
 import array
 import time
 import colorsys
+import struct
+import mmap
+import threading
+import os
+import atexit
 
 from packet import Packet, CMD, stoi, OBLIGATE_POLYPHONE
 
@@ -18,6 +23,8 @@ parser.add_option('-r', '--rate', dest='rate', type='int', default=44100, help='
 parser.add_option('-u', '--uid', dest='uid', default='', help='User identifier of this client')
 parser.add_option('-p', '--port', dest='port', default=13677, type='int', help='UDP port to listen on')
 parser.add_option('-c', '--clamp', dest='clamp', action='store_true', help='Clamp over-the-wire amplitudes to 0.0-1.0')
+parser.add_option('-B', '--bind', dest='bind_addr', default='', help='Bind to this address')
+parser.add_option('-G', '--gui', dest='gui', help='Use a GUI')
 parser.add_option('--amp-exp', dest='amp_exp', default=2.0, type='float', help='Raise floating amplitude to this power before computing raw amplitude')
 parser.add_option('--repeat', dest='repeat', action='store_true', help='If a note plays longer than a sample length, keep playing the sample')
 parser.add_option('--cut', dest='cut', action='store_true', help='If a note ends within a sample, stop playing that sample immediately')
@@ -25,6 +32,9 @@ parser.add_option('-n', '--max-voices', dest='max_voices', default=-1, type='int
 parser.add_option('--pg-low-freq', dest='low_freq', type='int', default=40, help='Low frequency for colored background')
 parser.add_option('--pg-high-freq', dest='high_freq', type='int', default=1500, help='High frequency for colored background')
 parser.add_option('--pg-log-base', dest='log_base', type='int', default=2, help='Logarithmic base for coloring (0 to make linear)')
+parser.add_option('--map-file', dest='map_file', default='client_map', help='File mapped by -G mapped (contains u32 frequency, f32 amplitude pairs for each voice)')
+parser.add_option('--map-interval', dest='map_interval', type='float', default=0.02, help='Period in seconds between refreshes of the map')
+parser.add_option('--map-samples', dest='map_samples', type='int', default=4096, help='Number of samples in the map file (MUST agree with renderer)')
 parser.add_option('--counter-modulus', dest='counter_modulus', type='int', default=16, help='Number of packet events in period of the terminal color scroll on the left margin')
 
 options, args = parser.parse_args()
@@ -33,6 +43,11 @@ MAX = 0x7fffffff
 MIN = -0x80000000
 IDENT = 'DRUM'
 
+LAST_SAMPLES = []
+FREQS = [0]
+AMPS = [0]
+STREAMS = 1
+
 if not args:
     print 'Need at least one drumpack (.tar.bz2) as an argument!'
     parser.print_usage()
@@ -49,6 +64,52 @@ def rgb_for_freq_amp(f, a):
     bgcol = colorsys.hls_to_rgb(min((1.0, max((0.0, pitchval)))), 0.5 * (a ** 2), 1.0)
     return [int(i*255) for i in bgcol]
 
+# GUIs
+
+GUIs = {}
+
+def GUI(f):
+    GUIs[f.__name__] = f
+    return f
+
+@GUI
+def mapped():
+    if os.path.exists(options.map_file):
+        raise ValueError('Refusing to map file--already exists!')
+    ms = options.map_samples
+    stm = options.map_interval
+    fixfmt = '>f'
+    fixfmtsz = struct.calcsize(fixfmt)
+    sigfmt = '>' + 'f' * ms
+    sigfmtsz = struct.calcsize(sigfmt)
+    strfmt = '>' + 'Lf' * STREAMS
+    strfmtsz = struct.calcsize(strfmt)
+    sz = sum((fixfmtsz, sigfmtsz, strfmtsz))
+    print 'Reserving', sz, 'in map file'
+    print 'Size triple:', fixfmtsz, sigfmtsz, strfmtsz
+    f = open(options.map_file, 'w+')
+    f.seek(sz - 1)
+    f.write('\0')
+    f.flush()
+    mapping = mmap.mmap(f.fileno(), sz, access=mmap.ACCESS_WRITE)
+    f.close()
+    atexit.register(os.unlink, options.map_file)
+    def unzip2(i):
+        for a, b in i:
+            yield a
+            yield b
+    while True:
+        mapping[:fixfmtsz] = struct.pack(fixfmt, 0.0)
+        mapping[fixfmtsz:fixfmtsz+sigfmtsz] = struct.pack(sigfmt, *(float(LAST_SAMPLES[i])/MAX if i < len(LAST_SAMPLES) else 0.0 for i in xrange(ms)))
+        mapping[fixfmtsz+sigfmtsz:] = struct.pack(strfmt, *unzip2((FREQS[i], AMPS[i]) for i in xrange(STREAMS)))
+        del LAST_SAMPLES[:]
+        time.sleep(stm)
+
+if options.gui:
+    guithread = threading.Thread(target=GUIs[options.gui])
+    guithread.setDaemon(True)
+    guithread.start()
+
 DRUMS = {}
 
 for fname in args:
@@ -131,6 +192,8 @@ def gen_data(data, frames, tm, status):
     for i in range(frames):
         fdata[i] = max(MIN, min(MAX, fdata[i]))
     fdata = array.array('i', fdata)
+    AMPS[0] = (float(sum(abs(i) for i in fdata)) / (len(fdata) * MAX))**0.25
+    LAST_SAMPLES.extend(fdata)
     return (fdata.tostring(), pyaudio.paContinue)
 
 pa = pyaudio.PyAudio()
@@ -141,6 +204,8 @@ if options.test:
         print 'Current playing:', PLAYING
         print 'Playing:', frq
         data = DRUMS[frq]
+        FREQS[0] = frq
+        AMPS[0] = 1.0
         PLAYING.append(SampleReader(data, len(data), options.volume))
         time.sleep(len(data) / (4.0 * options.rate))
     print 'Done'
@@ -148,7 +213,7 @@ if options.test:
 
 
 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
-sock.bind(('', options.port))
+sock.bind((options.bind_addr, options.port))
 
 #signal.signal(signal.SIGALRM, sigalrm)
 
@@ -189,6 +254,8 @@ while True:
         amp = options.volume * pkt.as_float(3)
         if options.clamp:
             amp = max(min(amp, 1.0), 0.0)
+        FREQS[0] = frq
+        AMPS[0] = amp
         PLAYING.append(SampleReader(rdata, dframes * 4, amp**options.amp_exp))
         if options.max_voices >= 0:
             while len(PLAYING) > options.max_voices:
-- 
cgit v1.2.3-70-g09d2