Skip to content

Commit

Permalink
qt bridge GUI working
Browse files Browse the repository at this point in the history
  • Loading branch information
dctucker committed Mar 27, 2024
1 parent 5fa47fd commit 88f098a
Show file tree
Hide file tree
Showing 7 changed files with 206 additions and 26 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ doc/_build
site-packages
pyvenv.cfg
bridge/share
bridge/lib64
3 changes: 3 additions & 0 deletions bridge/bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,9 @@ def check_connections(client, port):

capture.disconnect()
view.cursor_release()
print("\033[2JClosing")
model.close()
print("\033[2JClosed")

if __name__ == '__main__':
main()
15 changes: 13 additions & 2 deletions bridge/capture.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
def addr_channel(addr):
return ((addr & 0x0f00) >> 8) + 1

def addr_monitor(addr):
return ((addr & 0x0f000) >> 12)

class Capture:
def __init__(self, name='STUDIO-CAPTURE MIDI 2'):
capmix.set_model(4)
Expand All @@ -20,19 +23,25 @@ def listener(self, event):
dirty = True
if '.mute' in addr:
ch = addr_channel(event.addr)
mon = self.model.monitors[(event.addr & 0xf000) >> 12]
mon = self.model.monitors[addr_monitor(event.addr)]
mute = value.unpacked.discrete
self.model.mutes[ch][mon] = mute
#self.model.queue.put([ord(mon) - ord('a'), ch, 0 if mute == 0 else 127])
if mon == 'd':
# TODO may not be needed
self.model.queue.put([int((ch-1)/2), 82, 0 if mute == 0 else 127])
elif '.solo' in addr:
ch = addr_channel(event.addr)
mon = self.model.monitors[addr_monitor(event.addr)]
solo = value.unpacked.discrete
self.model.solos[ch][mon] = solo
elif '.stereo' in addr:
ch = addr_channel(event.addr)
self.model.stereo[ch] = value.unpacked.discrete
elif '.pan' in addr:
ch = addr_channel(event.addr)
self.model.pans[ch] = value #capmix.format_type(Type.Pan, value.unpacked) #.unpacked.discrete >> 24
mon = self.model.monitors[addr_monitor(event.addr)]
self.model.pans[ch][mon] = self.model.pan_to_int(value) #capmix.format_type(Type.Pan, value.unpacked) #.unpacked.discrete >> 24

log("addr=%x=%s type=%s v=%s" % (event.addr, addr, event.type_name(), value))

Expand Down Expand Up @@ -62,5 +71,7 @@ def get_mixer_data(self):
for ch in range(0,16):
for mon in self.model.monitors:
self.query("input_monitor.{}.channel.{}.mute".format(mon, ch+1))
self.query("input_monitor.{}.channel.{}.solo".format(mon, ch+1))
self.query("input_monitor.{}.channel.{}.volume".format(mon, ch+1))
self.query("input_monitor.a.channel.{}.pan".format(ch+1))

17 changes: 8 additions & 9 deletions bridge/control.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,15 +128,14 @@ def do_knob(self, val):
pan_l = Value.parse(Type.Pan, pan_l)
pan_r = Value.parse(Type.Pan, pan_r)

self.model.mix('input_monitor.a.channel.%d.pan' % (ch) , pan_l.unpacked)
self.model.mix('input_monitor.a.channel.%d.pan' % (ch+1), pan_r.unpacked)
self.model.mix('input_monitor.c.channel.%d.pan' % (ch) , pan_l.unpacked)
self.model.mix('input_monitor.c.channel.%d.pan' % (ch+1), pan_r.unpacked)
self.model.mix('input_monitor.d.channel.%d.pan' % (ch) , pan_l.unpacked)
self.model.mix('input_monitor.d.channel.%d.pan' % (ch+1), pan_r.unpacked)

self.model.pans[ch] = pan_l
self.model.pans[ch+1] = pan_r
model_l = self.model.pan_to_int(pan_l)
model_r = self.model.pan_to_int(pan_r)

for m in ['a','c','d']:
self.model.mix('input_monitor.%s.channel.%d.pan' % (m, ch) , pan_l.unpacked)
self.model.mix('input_monitor.%s.channel.%d.pan' % (m, ch+1), pan_r.unpacked)
self.model.pans[ch+0][m] = model_l
self.model.pans[ch+1][m] = model_r

self.view.print_pan(ch)
self.view.print_pan(ch+1)
Expand Down
73 changes: 68 additions & 5 deletions bridge/model.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,82 @@
from multiprocessing import shared_memory
from multiprocessing.resource_tracker import unregister
from os.path import exists
import numpy as np
from bindings.capmix import capmix, Type, Value
from queue import Queue

class ShmMonitors:
def __init__(self, data):
self.data = data

@classmethod
def ord(cls, k):
return (ord(k) - 1) % 4

def __getitem__(self, k):
return self.data[self.ord(k)]

def __setitem__(self, k, v):
self.data[self.ord(k)] = v

class ShmStereoLink:
def __init__(self, data):
self.data = data

def __getitem__(self, k):
return self.data[k-1][0]

def __setitem__(self, k, v):
self.data[k-1][0] = v

class ShmChannels:
def __init__(self, data):
self.data = data
self.monitors = [ ShmMonitors(data[c]) for c in range(16) ]

def __getitem__(self, k):
return self.monitors[k-1]

class Model:
def __init__(self):
def __init__(self, create=True):
self.num_channels = 16
self.monitors = ['a','b','c','d']
self.mutes = { (ch+1): { mon: 0 for mon in self.monitors } for ch in range(0, self.num_channels) }
self.solos = { (ch+1): { mon: 0 for mon in self.monitors } for ch in range(0, self.num_channels) }
self.stereo = { (ch+1): 0 for ch in range(0,16) }
self.pans = { (ch+1): Value.parse(Type.Pan, "C") for ch in range(0, 16) }
#self.stereo = { (ch+1): 0 for ch in range(0,16) }
#self.mutes = { (ch+1): { mon: 0 for mon in self.monitors } for ch in range(0, self.num_channels) }
#self.solos = { (ch+1): { mon: 0 for mon in self.monitors } for ch in range(0, self.num_channels) }
#self.pans = { (ch+1): Value.parse(Type.Pan, "C") for ch in range(0, 16) }

shm_path = 'pimix'
if create:
create = not exists('/dev/shm/' + shm_path)
if create:
shm = shared_memory.SharedMemory(shm_path, create=True, size=16*self.num_channels)
else:
shm = shared_memory.SharedMemory(shm_path, create=False)
unregister(shm._name, 'shared_memory')

self.data = np.ndarray(shape=(16,4,4), dtype=np.int8, buffer=shm.buf)
self.stereo = ShmStereoLink( np.ndarray(shape=(16,4), dtype=np.int8, buffer=shm.buf[0:]) )
self.mutes = ShmChannels( np.ndarray(shape=(16,4), dtype=np.int8, buffer=shm.buf[64:]) )
self.solos = ShmChannels( np.ndarray(shape=(16,4), dtype=np.int8, buffer=shm.buf[128:]) )
self.pans = ShmChannels( np.ndarray(shape=(16,4), dtype=np.int8, buffer=shm.buf[192:]) )
self.shm = shm

self.capture_hash = {}
self.cache = {}
self.queue = Queue()

@classmethod
def pan_to_int(cls, value):
return int(str(value).replace('R','').replace('L','-').replace('C','0'))

def close(self):
print("Closing shared memory")
self.shm.close()
print("Closed shared memory")

def reset(self):
self.data.fill(0)
for c in range(0, self.num_channels):
for m in self.monitors:
self.mutes[c+1][m] = 0
Expand Down
103 changes: 103 additions & 0 deletions bridge/qt.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
#!/usr/bin/env python3

from model import Model

import sys
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *

model = Model(create=False)

class GLWidget(QOpenGLWidget):
def __init__(self, parent=None):
QOpenGLWidget.__init__(self, parent)
self.setFixedSize(800,400)
self.glrect = QRect(0,0,800,400)
self.background = QBrush(QColor(1,1,1))
self.foreground = QBrush(QColor(200,200,0))
self.stereo_on = QBrush(QColor(0,200,200))
self.button_on = QBrush(QColor(200,0,0))
self.button_off = QBrush(QColor(50,50,50))
self.pan_brush = QBrush(QColor(224,128,0))

self.timer = QTimer(self)
self.timer.timeout.connect(self.timeout)
self.timer.start(16)
self.iteration = 0

def repaint(self):
self.iteration += 1
painter = QPainter()
painter.begin(self)
size = self.glrect
painter.fillRect(QRect(0, 0, size.width(), size.height()), self.background)
width = int(size.width())
col_width = width // 16
col_spacing = 5
row_height = 30

for ch in range(0, 16, 2):
left = ch * col_width
top = row_height

if model.stereo[ch+1] > 0:
color = self.stereo_on
rect = QRect(left+2*col_spacing, top, col_width * 2 - 2*col_spacing, 20)
painter.fillRect(rect, color)
painter.setPen(QPen(QColor(0,0,0)))
painter.drawText(rect, Qt.AlignHCenter, "%d / %d" % (ch+1, ch+2))
else:
color = self.button_off
rect = QRect(left+2*col_spacing, top, col_width - 2*col_spacing, 20)
painter.fillRect(rect, color)
painter.setPen(QPen(QColor(192,192,192)))
painter.drawText(rect, Qt.AlignHCenter, "%d" % (ch+1))

left = (ch+1) * col_width
rect = QRect(left+2*col_spacing, top, col_width - 2*col_spacing, 20)
painter.fillRect(rect, color)
painter.drawText(rect, Qt.AlignHCenter, "%d" % (ch+2))

for ch in range(0, 16):
left = ch * col_width
center = left + col_width // 2
top = 3 * row_height
p = int((col_width - col_spacing) * model.pans[ch+1]['a'] / 200.0)
painter.fillRect(QRect(center-1, top, 3, 20), self.pan_brush)
painter.fillRect(QRect(center, top, p, 20), self.pan_brush)

for i, m in enumerate(model.monitors):
top = 5 * row_height + i * row_height
color = self.button_on if model.mutes[ch+1][m] > 0 else self.button_off
if model.solos[ch+1][m]:
color = QColor(self.stereo_on)
fg = QColor(0,0,0)
else:
fg = QColor(0,0,0) if model.mutes[ch+1][m] > 0 else self.foreground.color()
painter.fillRect(QRect(left + col_spacing, top, col_width - col_spacing, 20), color)
painter.setPen(QPen(fg))
painter.drawText(center - col_spacing//2, top+15, m.upper())

#painter.fillRect(QRect(0, 0, self.iteration % size.width(), 20), self.foreground)
painter.end()

def paintEvent(self, event):
self.glrect = event.rect()
self.repaint()

def timeout(self):
self.update()

app = QApplication(sys.argv)
gl = GLWidget()
gl.show()

try:
code = app.exec_()
except KeyboardInterrupt:
code = 0
pass

model.close()
sys.exit(code)
20 changes: 10 additions & 10 deletions bridge/view.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import os
from time import strftime

logged = False

def log(*msg):
import os
from time import strftime
logged = False
def log(*msg):
global logged
now = strftime("%H:%M:%S")
logged = True
Expand Down Expand Up @@ -83,7 +83,7 @@ def dim(self, dark=True, force=False):

def pan_graphic(self, pan, width=4):
eighths = self.eighths
p = int(str(pan).replace('R','').replace('L','-').replace('C','0'))
p = pan
x = p * (width / 2) / 100.0
i = int(abs(x))
f = int(abs(x * 8)) % 8
Expand All @@ -102,9 +102,9 @@ def print_pan(self, ch):
row = 5 if not self.compact else 3
col = 6 * (ch-1) + 3
print("\033[?25l\033[%d;%df" % (row, col), end='')
pan = self.model.pans[ch]
pan = self.model.pans[ch]['a']
graphic = self.pan_graphic(pan)
if ch % 2 == 1 or str(pan) == 'C':
if ch % 2 == 1 or pan == 0:
print(" ", end='')
#graphic = " " + graphic
print("\033[33m%s" % graphic, end='')
Expand Down Expand Up @@ -150,7 +150,7 @@ def print_monitor_mutes(self):
if not compact: print("\033[2K")
print("\033[2K ", end='')
for ch in range(0, 16):
pan = pans[ch+1]
pan = pans[ch+1]['a']
graphic = self.pan_graphic(pan)
print(" \033[33m%s" % graphic, end='')
print("\033[0m")
Expand Down

0 comments on commit 88f098a

Please sign in to comment.