#!/usr/bin/python3
import gi, math
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

NOTES = ['C','C#','D','D#','E','F','F#','G','G#','A','A#','B']
A4 = 440.0
def freq_to_note(freq):
    if freq <= 0: return '—'
    semitones = round(12 * math.log2(freq / A4))
    note = NOTES[(semitones + 9) % 12]
    octave = (semitones + 9) // 12 + 4
    return f'{note}{octave}'

WAVES = {'sine':'sine','square':'square','sawtooth':'sawtooth'}

class App(Gtk.Window):
    def __init__(self):
        super().__init__(title='Tone Generator')
        self.set_default_size(440, 280)
        self.pipeline = None
        self.playing = False
        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)
        grid = Gtk.Grid(column_spacing=10, row_spacing=10)
        grid.attach(Gtk.Label(label='Frequency (Hz):', xalign=0), 0, 0, 1, 1)
        self.freq_scale = Gtk.Scale.new_with_range(Gtk.Orientation.HORIZONTAL, 20, 20000, 1)
        self.freq_scale.set_value(440)
        self.freq_scale.connect('value-changed', self.update_display)
        grid.attach(self.freq_scale, 1, 0, 2, 1)
        grid.attach(Gtk.Label(label='Volume:', xalign=0), 0, 1, 1, 1)
        self.vol_scale = Gtk.Scale.new_with_range(Gtk.Orientation.HORIZONTAL, 0, 1, 0.01)
        self.vol_scale.set_value(0.5)
        grid.attach(self.vol_scale, 1, 1, 2, 1)
        grid.attach(Gtk.Label(label='Waveform:', xalign=0), 0, 2, 1, 1)
        self.wave_sel = Gtk.ComboBoxText()
        for w in WAVES: self.wave_sel.append_text(w.capitalize())
        self.wave_sel.set_active(0)
        grid.attach(self.wave_sel, 1, 2, 1, 1)
        vbox.pack_start(grid, False, False, 0)
        self.info_lbl = Gtk.Label()
        self.info_lbl.set_markup('<span size="large"><b>440 Hz — A4</b></span>')
        vbox.pack_start(self.info_lbl, True, True, 0)
        hb = Gtk.Box(spacing=10); hb.set_halign(Gtk.Align.CENTER)
        self.play_btn = Gtk.Button(label='▶ Play Tone')
        self.play_btn.connect('clicked', self.toggle)
        hb.pack_start(self.play_btn, False, False, 0)
        if not GST_OK:
            hb.add(Gtk.Label(label='(GStreamer not available)'))
        vbox.pack_start(hb, False, False, 0)
    def update_display(self, *_):
        freq = self.freq_scale.get_value()
        note = freq_to_note(freq)
        self.info_lbl.set_markup(f'<span size="large"><b>{freq:.0f} Hz — {note}</b></span>')
        if self.playing: self.update_pipeline()
    def update_pipeline(self):
        if self.pipeline:
            freq = self.freq_scale.get_value()
            vol = self.vol_scale.get_value()
            src = self.pipeline.get_by_name('src')
            vsrc = self.pipeline.get_by_name('vol')
            if src: src.set_property('freq', freq)
            if vsrc: vsrc.set_property('volume', vol)
    def toggle(self, *_):
        if not GST_OK:
            self.info_lbl.set_text('GStreamer not available')
            return
        if not self.playing:
            freq = self.freq_scale.get_value()
            vol = self.vol_scale.get_value()
            wave_idx = self.wave_sel.get_active()
            wave_map = {0:0, 1:1, 2:2}  # sine, square, sawtooth
            self.pipeline = Gst.parse_launch(
                f'audiotestsrc name=src freq={freq} wave={wave_map[wave_idx]} ! volume name=vol volume={vol} ! autoaudiosink')
            self.pipeline.set_state(Gst.State.PLAYING)
            self.playing = True
            self.play_btn.set_label('⏹ Stop')
        else:
            if self.pipeline:
                self.pipeline.set_state(Gst.State.NULL)
                self.pipeline = None
            self.playing = False
            self.play_btn.set_label('▶ Play Tone')

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