~martijnbraam/pyatem

7f421b35f41d33257966fbcd227ed5233660c76f — Martijn Braam 5 months ago 2565317 feature/fairlight-eq
WIP
3 files changed, 318 insertions(+), 1 deletions(-)

M gtk_switcher/audio.py
A gtk_switcher/eqcurve.py
M gtk_switcher/ui/style.css
M gtk_switcher/audio.py => gtk_switcher/audio.py +2 -1
@@ 2,6 2,7 @@ import gi

from gtk_switcher.adjustmententry import AdjustmentEntry
from gtk_switcher.dial import Dial
from gtk_switcher.eqcurve import EqCurve
from pyatem.command import FairlightStripPropertiesCommand

gi.require_version('Gtk', '3.0')


@@ 343,7 344,7 @@ class AudioPage:
                input_frame.add(gain_box)
                self.audio_channels.attach(input_frame, left + c, 2, 1, 1)

                eq_frame = Gtk.Frame()
                eq_frame = EqCurve()
                eq_frame.get_style_context().add_class('view')
                eq_frame.set_size_request(0, 64)
                self.audio_channels.attach(eq_frame, left + c, 3, 1, 1)

A gtk_switcher/eqcurve.py => gtk_switcher/eqcurve.py +308 -0
@@ 0,0 1,308 @@
import math

from gi.repository import Gtk, GObject, Gdk

from pyatem.command import KeyPropertiesDveCommand


class BiQuad:
    def __init__(self):
        self.b0 = 1
        self.b1 = 1
        self.b2 = 1
        self.a0 = 1
        self.a1 = 1
        self.a2 = 1

    def calculate(self, frequency):
        LN10_10 = 10 / 2.302585092994046
        pi = math.pi / 40000
        Ra = abs(((self.a0 + self.a1 + self.a2) * (self.a0 + self.a1 + self.a2)) / 4)
        Rb = abs(((self.b0 + self.b1 + self.b2) * (self.b0 + self.b1 + self.b2)) / 4)
        Xa = abs(4 * self.a0 * self.a2)
        Ya = abs(self.a1 * (self.a0 + self.a2))
        Xb = abs(4 * self.b0 * self.b2)
        Yb = abs(self.b1 * (self.b0 + self.b2))

        if Ra == Rb and Ya == Yb and Xa == Xb:
            return 0

        S = abs(math.sin(pi * frequency))
        S *= S
        val = abs((Rb - S * (Xb * (1 - S) + Yb)) / (Ra - S * (Xa * (1 - S) + Ya)))
        if val == 0:
            return 0
        return LN10_10 * math.log(val)


class HighPass(BiQuad):
    def __init__(self, frequency):
        w0 = (2 * math.pi * frequency) / 40000
        s0 = math.sin(w0)
        c0 = math.cos(w0)
        self.b0 = s0
        self.b1 = 0
        self.b2 = -s0
        self.a0 = 1 - c0 + s0
        self.a1 = 2 * (1 - c0)
        self.a2 = 1 - c0 - s0
        print("EHLP")


class Peaking(BiQuad):
    def __init__(self, frequency, gain, q):
        A = math.pow(10, gain / 40)
        w0 = (2 * math.pi * frequency) / 40000
        if q == 0:
            q = 1
        alpha = math.sin(w0) / (2 * q)
        self.b0 = 1 + alpha * A
        self.b1 = -2 * math.cos(w0)
        self.b2 = 1 - alpha * A
        self.a0 = 1 + alpha / A
        self.a1 = -2 * math.cos(w0)
        self.a2 = 1 - alpha / A
        print("Peak")


class EqCurve(Gtk.Frame):
    __gtype_name__ = 'EqCurve'

    def __init__(self):
        super(Gtk.Frame, self).__init__()
        self.connection = None
        self.set_size_request(640, 480)

        self.da = Gtk.DrawingArea()
        self.add(self.da)

        self.da.set_events(
            self.da.get_events()
            | Gdk.EventMask.LEAVE_NOTIFY_MASK
            | Gdk.EventMask.BUTTON_PRESS_MASK
            | Gdk.EventMask.BUTTON_RELEASE_MASK
            | Gdk.EventMask.POINTER_MOTION_MASK
            | Gdk.EventMask.POINTER_MOTION_HINT_MASK
        )

        self.area_width = 0
        self.area_height = 0
        self.area_top = 0
        self.area_left = 0

        self.da.connect("draw", self.on_draw)
        self.da.connect("button-press-event", self.on_mouse_down)
        self.da.connect("button-release-event", self.on_mouse_up)
        self.da.connect("motion-notify-event", self.on_mouse_move)

        self.show_all()

        self.selected = None
        self.handle = None
        self.offset_x = None
        self.offset_y = None

        self.bands = []
        self.curve = None

    def update_band(self, bandupdate):
        self.bands[bandupdate.band_index] = bandupdate
        self.da.queue_draw()

    def on_mouse_down(self, widget, event):
        # grr cairo coordinates
        event.y = self.area_height - event.y - self.area_top
        event.x = event.x - self.area_left

        if self.selected is None:
            for label in self.regions:
                region = self.regions[label]
                w = self._coord_w(region[2])
                h = self._coord_h(region[3])

                x = self._coord_x(region[0]) - (w / 2)
                y = self._coord_y(region[1]) - (h / 2)
                if x < event.x < (x + w):
                    if y < event.y < (y + h):
                        self.selected = label
                        self.da.queue_draw()
                        return
            return

        region = self.regions[self.selected]
        w = self._coord_w(region[2])
        h = self._coord_h(region[3])
        x = self._coord_x(region[0]) - (w / 2)
        y = self._coord_y(region[1]) - (h / 2)

        mtop = 0
        mbottom = 0
        mleft = 0
        mright = 0
        if self.selected in self.masks:
            mask = self.masks[self.selected]
            mtop = mask[0]
            mbottom = mask[1]
            mleft = mask[2]
            mright = mask[3]

        if (x - 5) < event.x < (x + 5):
            if (y - 5) < event.y < (y + 5):
                self.handle = 'bl'
                self.offset_x = x + w
                self.offset_y = y + h
                return

        if (x - 5) < event.x < (x + 5):
            if (y - 5 + h) < event.y < (y + 5 + h):
                self.handle = 'tl'
                self.offset_x = x + w
                self.offset_y = y
                return

        if (x - 5 + w) < event.x < (x + 5 + w):
            if (y - 5) < event.y < (y + 5):
                self.handle = 'br'
                self.offset_x = x
                self.offset_y = y + h
                return

        if (x - 5 + w) < event.x < (x + 5 + w):
            if (y - 5 + h) < event.y < (y + 5 + h):
                self.handle = 'tr'
                self.offset_x = x
                self.offset_y = y
                return

        if (x - 5 + (mleft * w)) < event.x < (x + 5 + (mleft * w)):
            if (y - 15 + (h / 2)) < event.y < (y + 15 + (h / 2)):
                self.handle = 'ml'
                self.offset_x = x
                return

        if (x - 5 + w - (mright * w)) < event.x < (x + 5 + w - (mright * w)):
            if (y - 15 + (h / 2)) < event.y < (y + 15 + (h / 2)):
                self.handle = 'mr'
                self.offset_x = x + w
                return

        if (x - 15 + (w / 2)) < event.x < (x + 15 + (w / 2)):
            if (y - 5 + h - (mtop * h)) < event.y < (y + 5 + h - (mtop * h)):
                self.handle = 'mt'
                self.offset_y = y + h
                return

        if (x - 15 + (w / 2)) < event.x < (x + 15 + (w / 2)):
            if (y - 5 + (mbottom * h)) < event.y < (y + 5 + (mbottom * h)):
                self.handle = 'mb'
                self.offset_y = y
                return

        if x < event.x < (x + w):
            if y < event.y < (y + h):
                self.handle = 'pos'
                self.offset_x = (x + (w / 2)) - event.x
                self.offset_y = (y + (h / 2)) - event.y
                return

        self.selected = None
        self.da.queue_draw()

    def on_mouse_up(self, widget, *args):
        self.handle = None
        self.da.queue_draw()

    def on_mouse_move(self, widget, event):
        pass

    def on_region_update(self, label, pos_x=None, pos_y=None, size_x=None, size_y=None):
        if label.startswith("Upstream key"):
            keyer = int(label[13:]) - 1
            x, y = self._pos_to_atem(pos_x, pos_y)
            w = None
            h = None
            if size_x is not None and size_y is not None:
                w, h = self._size_to_atem(size_x, size_y)
                w = int(w * 100)
                h = int(h * 100)
            cmd = KeyPropertiesDveCommand(index=self.index, keyer=keyer, pos_x=int(x * 1000), pos_y=int(y * 1000),
                                          size_x=w, size_y=h)
            self.connection.mixer.send_commands([cmd])

    def _coord_x(self, input_coord):
        input_coord = (input_coord + 16) / 32.0
        return (self.area_width * input_coord)

    def _coord_y(self, input_coord):
        input_coord = (input_coord + 9) / 18.0
        return (self.area_height * input_coord)

    def _coord_w(self, input_coord):
        input_coord = input_coord / 16.0
        return (self.area_width * input_coord)

    def _coord_h(self, input_coord):
        input_coord = input_coord / 9
        return (self.area_height * input_coord)

    def _biquadmodule(self, biquad):
        ln10_10 = (())

    def _biquad_lowshelf(self, band):
        pass

    def calculate_filter(self):
        width = self.get_allocation().width
        max_freq = 20000 * 2
        resolution = min(width, 512)
        df = (max_freq / 2) / resolution
        curve = [0] * resolution

        self.bands = [
            ['highpass', 80, 60, 20],
            ['highpass', 2000, 60, 20],
        ]
        biquads = []
        for band in self.bands:
            if band[0] == 'highpass':
                biquads.append(HighPass(band[1]))
            elif band[0] == 'band':
                biquads.append(Peaking(band[1], band[2], band[3]))

        for n in range(1, resolution):
            f0 = n * df
            for band in biquads:
                f = math.pow(10, f0 / 20000) / (math.pow(10, 1))
                curve[n] += band.calculate(f)

        self.curve = curve

    def on_draw(self, widget, cr):
        width, height = widget.get_allocated_width(), widget.get_allocated_height()
        context = self.get_style_context()

        if self.curve is None:
            self.calculate_filter()

        context.save()
        context.add_class('eq-window')
        Gtk.render_background(context, cr, 0, 0, width, height)

        context.restore()
        context.save()
        context.add_class('eq-window-lines')

        Gtk.render_line(context, cr, 0, height / 2, width, height / 2)
        context.restore()
        context.save()
        context.add_class('eq-window-curve')

        ppp = width / (len(self.curve) - 1)
        last_val = self.curve[0]
        for i in range(1, len(self.curve)):
            point = self.curve[i]
            scale = 0.1
            Gtk.render_line(context, cr, (i - 1) * ppp, (last_val * scale) + (height // 2), i * ppp,
                            (point * scale) + (height // 2))
            last_val = point
        context.restore()

M gtk_switcher/ui/style.css => gtk_switcher/ui/style.css +8 -0
@@ 177,4 177,12 @@ entry.mini {
    padding: 0;
    margin: 0;
    background: rgba(0, 0, 0, 0.5);
}

.eq-window-lines {
    color: #555;
}

.eq-window-curve {
    color: #15539e;
}
\ No newline at end of file