#!/usr/bin/python3
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GLib, Pango
import cairo, math

GST_OK = False
try:
    gi.require_version('Gst', '1.0')
    from gi.repository import Gst
    Gst.init(None)
    GST_OK = True
except Exception:
    pass

def play_click(accent=False):
    if not GST_OK: return
    freq = 1800 if accent else 1200
    try:
        pipe = Gst.parse_launch(f'audiotestsrc freq={freq} num-buffers=2 ! audioconvert ! autoaudiosink')
        pipe.set_state(Gst.State.PLAYING)
        GLib.timeout_add(150, lambda: pipe.set_state(Gst.State.NULL) or False)
    except Exception: pass

class App(Gtk.Window):
    def __init__(self):
        super().__init__(title='Metronome')
        self.set_default_size(380, 380)
        self.playing = False; self.beat = 0; self.flash = False
        self.timer_id = None
        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
        vbox.set_margin_top(16); vbox.set_margin_bottom(16)
        vbox.set_margin_start(16); vbox.set_margin_end(16)
        self.add(vbox)
        # BPM
        hb = Gtk.Box(spacing=8)
        hb.add(Gtk.Label(label='BPM:'))
        self.bpm_spin = Gtk.SpinButton.new_with_range(40, 240, 1)
        self.bpm_spin.set_value(120)
        self.bpm_spin.connect('value-changed', self.bpm_changed)
        hb.pack_start(self.bpm_spin, False, False, 0)
        self.bpm_lbl = Gtk.Label()
        self.bpm_lbl.override_font(Pango.FontDescription('Bold 16'))
        self.bpm_lbl.set_text('120 BPM')
        hb.pack_start(self.bpm_lbl, True, True, 0)
        vbox.pack_start(hb, False, False, 0)
        # time signature
        hb2 = Gtk.Box(spacing=8)
        hb2.add(Gtk.Label(label='Time Sig:'))
        self.sig_sel = Gtk.ComboBoxText()
        for s in ['2/4','3/4','4/4','6/8']:
            self.sig_sel.append_text(s)
        self.sig_sel.set_active(2)
        hb2.pack_start(self.sig_sel, False, False, 0)
        vbox.pack_start(hb2, False, False, 0)
        # visual
        self.da = Gtk.DrawingArea(); self.da.set_size_request(200, 200)
        self.da.set_halign(Gtk.Align.CENTER)
        self.da.connect('draw', self.draw_beat)
        vbox.pack_start(self.da, True, True, 0)
        self.beat_lbl = Gtk.Label()
        self.beat_lbl.override_font(Pango.FontDescription('Monospace Bold 24'))
        self.beat_lbl.set_text('—')
        vbox.pack_start(self.beat_lbl, False, False, 0)
        # controls
        self.play_btn = Gtk.Button(label='▶ Start')
        self.play_btn.connect('clicked', self.toggle)
        vbox.pack_start(self.play_btn, False, False, 0)
    def get_beats(self):
        return int(self.sig_sel.get_active_text().split('/')[0])
    def bpm_changed(self, *_):
        bpm = int(self.bpm_spin.get_value())
        self.bpm_lbl.set_text(f'{bpm} BPM')
        if self.playing: self.restart_timer()
    def toggle(self, *_):
        self.playing = not self.playing
        if self.playing:
            self.play_btn.set_label('⏹ Stop')
            self.beat = 0; self.restart_timer()
        else:
            self.play_btn.set_label('▶ Start')
            if self.timer_id: GLib.source_remove(self.timer_id)
            self.flash = False; self.da.queue_draw()
            self.beat_lbl.set_text('—')
    def restart_timer(self):
        if self.timer_id: GLib.source_remove(self.timer_id)
        bpm = int(self.bpm_spin.get_value())
        interval = int(60000 / bpm)
        self.timer_id = GLib.timeout_add(interval, self.tick)
    def tick(self):
        if not self.playing: return False
        beats = self.get_beats()
        accent = self.beat % beats == 0
        play_click(accent)
        self.flash = True; self.beat_lbl.set_text(f'{(self.beat%beats)+1}/{beats}')
        self.da.queue_draw()
        self.beat += 1
        GLib.timeout_add(100, self.unflash)
        return True
    def unflash(self):
        self.flash = False; self.da.queue_draw()
        return False
    def draw_beat(self, w, cr):
        alloc = w.get_allocation()
        cx, cy = alloc.width/2, alloc.height/2
        r = min(cx,cy) - 10
        cr.set_source_rgb(0.9,0.9,0.9); cr.paint()
        if self.flash:
            cr.set_source_rgb(0.2, 0.8, 0.2)
        else:
            cr.set_source_rgb(0.6, 0.6, 0.6)
        cr.arc(cx, cy, r, 0, math.pi*2); cr.fill()
        cr.set_source_rgb(0.3,0.3,0.3); cr.set_line_width(3)
        cr.arc(cx, cy, r, 0, math.pi*2); cr.stroke()

win = App()
win.connect('destroy', Gtk.main_quit)
win.show_all()
Gtk.main()
