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

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

DRUMS = ['Kick','Snare','Hi-Hat','Open HH','Tom 1','Tom 2','Clap','Rimshot']
FREQS = [60, 200, 8000, 6000, 300, 250, 1000, 800]
STEPS = 16

def play_tone(freq, dur=0.08):
    if not GST_OK: return
    try:
        pipe = Gst.parse_launch(
            f'audiotestsrc freq={freq} num-buffers=4 ! audioconvert ! autoaudiosink')
        pipe.set_state(Gst.State.PLAYING)
        def stop():
            pipe.set_state(Gst.State.NULL)
            return False
        GLib.timeout_add(int(dur*1000+200), stop)
    except Exception:
        pass

class App(Gtk.Window):
    def __init__(self):
        super().__init__(title='Drum Machine')
        self.set_default_size(780, 360)
        self.bpm = 120
        self.playing = False
        self.step = 0
        self.pattern = [[False]*STEPS for _ in range(len(DRUMS))]
        self.timer_id = None
        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
        vbox.set_margin_top(8); vbox.set_margin_bottom(8)
        vbox.set_margin_start(8); vbox.set_margin_end(8)
        self.add(vbox)
        # controls
        cbox = Gtk.Box(spacing=10)
        self.play_btn = Gtk.Button(label='▶ Play')
        self.play_btn.connect('clicked', self.toggle_play)
        cbox.pack_start(self.play_btn, False, False, 0)
        cbox.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', lambda s: setattr(self, 'bpm', int(s.get_value())))
        cbox.pack_start(self.bpm_spin, False, False, 0)
        clear_btn = Gtk.Button(label='Clear')
        clear_btn.connect('clicked', self.clear)
        cbox.pack_start(clear_btn, False, False, 0)
        vbox.pack_start(cbox, False, False, 0)
        # grid
        grid = Gtk.Grid(column_spacing=2, row_spacing=2)
        self.step_btns = []
        for d, drum in enumerate(DRUMS):
            lbl = Gtk.Label(label=drum, xalign=1, width_chars=8)
            grid.attach(lbl, 0, d, 1, 1)
            row_btns = []
            for s in range(STEPS):
                btn = Gtk.ToggleButton()
                btn.set_size_request(36, 32)
                if s % 4 == 0:
                    btn.get_style_context().add_class('suggested-action')
                btn.connect('toggled', self.on_toggle, d, s)
                grid.attach(btn, s+1, d, 1, 1)
                row_btns.append(btn)
            self.step_btns.append(row_btns)
        vbox.pack_start(grid, True, True, 0)
        # step indicator
        self.step_lbl = Gtk.Label(label='Step: -')
        vbox.pack_start(self.step_lbl, False, False, 0)
    def on_toggle(self, btn, d, s):
        self.pattern[d][s] = btn.get_active()
    def toggle_play(self, *_):
        self.playing = not self.playing
        if self.playing:
            self.play_btn.set_label('⏹ Stop')
            self.step = 0
            interval = int(60000 / self.bpm / 4)
            self.timer_id = GLib.timeout_add(interval, self.tick)
        else:
            self.play_btn.set_label('▶ Play')
            if self.timer_id: GLib.source_remove(self.timer_id)
            self.step_lbl.set_text('Step: -')
    def tick(self):
        if not self.playing: return False
        for d in range(len(DRUMS)):
            if self.pattern[d][self.step]:
                play_tone(FREQS[d])
        self.step_lbl.set_text(f'Step: {self.step+1}/{STEPS}')
        self.step = (self.step + 1) % STEPS
        interval = int(60000 / self.bpm / 4)
        self.timer_id = GLib.timeout_add(interval, self.tick)
        return False
    def clear(self, *_):
        self.pattern = [[False]*STEPS for _ in range(len(DRUMS))]
        for row in self.step_btns:
            for btn in row: btn.set_active(False)

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