from gi.repository import Gtk, GObject, Gdk
from subprocess import Popen
from collections import deque
from signal import SIGINT
from textwrap import dedent
class Note():
"""Defines note names and frequencies
notes is a table containing notes described by their frequence and their name
in both french and english notation
INDEX_FR_NAME, INDEX_EN_NAME and INDEX_FREQ are indexes to its columns
"""
INDEX_FR_NAME, INDEX_EN_NAME, INDEX_FREQ = range(3)
notes = [["Do1", "C1", "32.7"], ["Do♯1","C♯1", "34.65"],
["Ré1", "D1", "36.71"], ["Ré♯1","D♯1", "38.89"],
["Mi1", "E1", "41.2"], ["Fa1", "F1", "43.65"],
["Fa♯1","F♯1", "46.25"], ["Sol1", "G1", "49"],
["Sol♯1","G♯1", "51.91"], ["La1", "A1", "55"],
["La♯1","A♯1", "58.27"], ["Si1", "B1", "61.74"],
["Do2", "C2", "65.41"], ["Do♯2","C♯2", "69.3"],
["Ré2", "D2", "73.42"], ["Ré♯2","D♯2", "77.78"],
["Mi2", "E2", "82.41"], ["Fa2", "F2", "87.31"],
["Fa♯2","F♯2", "92.5"], ["Sol2", "G2", "98"],
["Sol♯2","G♯2", "103.83"], ["La2", "A2", "110"],
["La♯2","A♯2", "116.54"], ["Si2", "B2", "123.47"],
["Do3", "C3", "130.81"], ["Do♯3","C♯3", "138.59"],
["Ré3", "D3", "146.83"], ["Ré♯3","D♯3", "155.56"],
["Mi3", "E3", "164.81"], ["Fa3", "F3", "174.61"],
["Fa♯3","F♯3", "185"], ["Sol3", "G3", "196"],
["Sol♯3","G♯3", "207.65"], ["La3", "A3", "220"],
["La♯3","A♯3", "233.08"], ["Si3", "B3", "246.94"],
["Do4", "C4", "261.63"], ["Do♯4","C♯4", "277.18"],
["Ré4", "D4", "293.66"], ["Ré♯4","D♯4", "311.13"],
["Mi4", "E4", "329.63"], ["Fa4", "F4", "349.23"],
["Fa♯4","F♯4", "369.99"], ["Sol4", "G4", "392"],
["Sol♯4","G♯4", "415.3"], ["La4", "A4", "440"],
["La♯4","A♯4", "466.16"], ["Si4", "B4", "493.88"],
["Do5", "C5", "523.25"], ["Do♯5","C♯5", "554.37"],
["Ré5", "D5", "587.33"], ["Ré♯5","D♯5", "622.25"],
["Mi5", "E5", "659.26"], ["Fa5", "F5", "698.46"],
["Fa♯5","F5♯", "739.99"], ["Sol5", "G5", "783.99"],
["Sol♯5","G5♯", "830.61"], ["La5", "A5", "880"],
["La♯5","A♯5", "932.33"], ["Si5", "B5", "987.77"],
["Do6", "C6", "1046.5"], ["Do♯6","C♯6", "1108.73"],
["Ré6", "D6", "1174.66"], ["Ré♯6","D♯6", "1244.51"],
["Mi6", "E6", "1318.51"], ["Fa6", "F6", "1396.91"],
["Fa♯6","F♯6", "1479.98"], ["Sol6", "G6", "1567.98"],
["Sol♯6","G♯6", "1661.22"], ["La6", "A6", "1760"],
["La♯6","A♯6", "1864.66"], ["Si6", "B6", "1975.53"],
["Do7", "C7", "2093"], ["Do♯7","C♯7", "2217.46"],
["Ré7", "D7", "2349.32"], ["Ré♯7","D♯7", "2489.02"],
["Mi7", "E7", "2637.02"], ["Fa7", "F7", "2793.83"],
["Fa♯7","F♯7", "2959.96"], ["Sol7", "G7", "3135.96"],
["Sol♯7","G♯7", "3322.44"], ["La7", "A7", "3520"],
["La♯7","A♯7", "3729.31"], ["Si7", "B7", "3951.07"],
["Do8", "C8", "4186.01"]]
class Note_selection_dialog(Gtk.Dialog):
"""Custom frequency selector widget
Allows the selection of a frequency from the notes table of class Note
public method : get_index()
"""
def __init__(self, title, index, notestyle_index):
"""Create and display a frequency selector.
title is the window title to display,
index is the index of the frequency to select as default,
notestyle_index is the language column index in Note.notes table.
"""
index = len(Note.notes) - 1 - index
store = (Gtk.ListStore(str, str, str))
for freq in Note.notes:
store.prepend(freq)
GObject.GObject.__init__(self)
self.set_modal(True)
self.set_title(title)
self.set_size_request(150,600)
self.set_border_width(10)
self.connect("key-press-event", self._key_pressed)
TODO
self.set_position(Gtk.WindowPosition.MOUSE)
self._tree = Gtk.TreeView(store)
self._tree.connect("button-press-event", self._double_click)
self._tree.set_cursor(Gtk.TreePath(index), None, False)
self._tree.scroll_to_cell(index, use_align=True, row_align=0.5)
self._renderer = Gtk.CellRendererText()
self._column = Gtk.TreeViewColumn("Note")
self._column.pack_start(self._renderer, True)
self._column.add_attribute(self._renderer, "text", notestyle_index)
self._tree.append_column(self._column)
renderer = Gtk.CellRendererText()
column = Gtk.TreeViewColumn("Frequency", renderer, text=2)
self._tree.append_column(column)
scroll = Gtk.ScrolledWindow()
scroll.add(self._tree)
self.vbox.pack_start(scroll, True, True, 0)
self._tree.show()
scroll.show()
self.show()
def _key_pressed(self, widget, event):
"""Process keypress event."""
key = Gdk.keyval_name(event.keyval)
if 'Escape' == key:
self.emit('response', Gtk.ResponseType.CANCEL)
elif 'Return' == key:
self._note_selected()
elif 'Page_Up' == key:
TODO
model, treeiter = self._tree.get_selection().get_selected()
index = model.get_path(treeiter).get_indices()[0] - 12
if index < 0:
index = 0
self._tree.set_cursor(Gtk.TreePath(index), None, False)
self._tree.scroll_to_cell(index, use_align=True, row_align=0.5)
return True
elif 'Page_Down' == key:
model, treeiter = self._tree.get_selection().get_selected()
index = model.get_path(treeiter).get_indices()[0] + 12
if index > len(Note.notes) - 1:
index = len(Note.notes) - 1
self._tree.set_cursor(Gtk.TreePath(index), None, False)
self._tree.scroll_to_cell(index, use_align=True, row_align=0.5)
return True
def _double_click(self, widget, event):
"""Process double-click event."""
if event.button == 1 and event.type == Gdk.EventType._2BUTTON_PRESS :
self._note_selected()
def _note_selected(self):
"""Get row index of selection and emit response signal."""
select = self._tree.get_selection()
model, treeiter = select.get_selected()
if treeiter is not None:
self._index = len(Note.notes) - 1 \
- model.get_path(treeiter).get_indices()[0]
self.emit('response', Gtk.ResponseType.OK)
def get_index(self):
"""Return index of selected frequency's row in Note.notes table."""
return self._index
class Key(Gtk.Box):
"""A key that can be laid out on a keyboard
Each key consists of a button to play a sound, a button to select the
frequency, and two buttons to tune up or down with a half tone step
public methods : adjust_freq(), get_freq(), get_index(), set_label(),
set_key_enabled()
"""
def __init__(self, index, tuner):
"""Create a Key
index is the index of the note in Note.notes,
tuner is the Tuner keyboard in which the Key is laid out.
"""
self._index = index
self._tuner = tuner
GObject.GObject.__init__(self,
spacing=5,
orientation=Gtk.Orientation.VERTICAL)
self._play_button = Gtk.Button(stock=Gtk.STOCK_MEDIA_PLAY)
TODO
self._play_button.get_children()[0].get_children()[0].get_children()[1].set_text('')
self.pack_start(self._play_button, True, True, 0)
self._play_button.show()
self._pick_button = Gtk.Button()
self.pack_start(self._pick_button, True, True, 0)
self._pick_button.show()
HBox_arrow = Gtk.Box()
arrow = Gtk.Arrow(arrow_type=Gtk.ArrowType.UP,
shadow_type=Gtk.ShadowType.OUT)
HBox_arrow.pack_start(arrow, True, True, 0)
arrow.show()
label = Gtk.Label(label="8")
HBox_arrow.pack_start(label, True, True, 0)
label.show()
self._button_oct_up = Gtk.Button()
self._button_oct_up.add(HBox_arrow)
self.pack_start(self._button_oct_up, False, True, 0)
HBox_arrow.show()
self._button_oct_up.show()
HBox_arrow = Gtk.Box()
arrow = Gtk.Arrow(arrow_type=Gtk.ArrowType.UP,
shadow_type=Gtk.ShadowType.OUT)
HBox_arrow.pack_start(arrow, True, True, 0)
arrow.show()
label = Gtk.Label(label=u"\u266F")
HBox_arrow.pack_start(label, True, True, 0)
label.show()
self._button_up = Gtk.Button()
self._button_up.add(HBox_arrow)
self.pack_start(self._button_up, False, True, 0)
HBox_arrow.show()
self._button_up.show()
HBox_arrow = Gtk.Box()
arrow = Gtk.Arrow(arrow_type=Gtk.ArrowType.DOWN,
shadow_type=Gtk.ShadowType.OUT)
HBox_arrow.pack_start(arrow, True, True, 0)
arrow.show()
label = Gtk.Label(label=u"\u266D")
HBox_arrow.pack_start(label, True, True, 0)
label.show()
self._button_down = Gtk.Button()
self._button_down.add(HBox_arrow)
self.pack_start(self._button_down, False, True, 0)
HBox_arrow.show()
self._button_down.show()
HBox_arrow = Gtk.Box()
arrow = Gtk.Arrow(arrow_type=Gtk.ArrowType.DOWN,
shadow_type=Gtk.ShadowType.OUT)
HBox_arrow.pack_start(arrow, True, True, 0)
arrow.show()
label = Gtk.Label(label="8")
HBox_arrow.pack_start(label, True, True, 0)
label.show()
self._button_oct_down = Gtk.Button()
self._button_oct_down.add(HBox_arrow)
self.pack_start(self._button_oct_down, False, True, 0)
HBox_arrow.show()
self._button_oct_down.show()
self._handler = self._play_button.connect("clicked", self._play)
self._button_up.connect("clicked", self.adjust_freq, 1)
self._button_down.connect("clicked", self.adjust_freq, -1)
self._button_oct_up.connect("clicked", self.adjust_freq, 12)
self._button_oct_down.connect("clicked", self.adjust_freq, -12)
self._pick_button.connect("clicked", self._select_note)
self.set_label()
self.show()
def adjust_freq(self, widget=None, shift=0):
"""Adjust frequency by shifting frequency index.
shift is a signed number of semitones to shift from.
If highest or lowest freq reached, stop there and return False.
"""
while (shift > 0):
if self._index < len(Note.notes) - 1:
self._index = self._index + 1
self.set_label()
if self._index is len(Note.notes) - 1 :
self._button_up.set_sensitive(False)
self._button_oct_up.set_sensitive(False)
self._button_down.set_sensitive(True)
self._button_oct_down.set_sensitive(True)
self._tuner.keys_modified()
shift -= 1
else:
return False
while (shift < 0):
if self._index > 0:
self._index = self._index - 1
self.set_label()
if self._index is 0:
self._button_down.set_sensitive(False)
self._button_oct_down.set_sensitive(False)
self._button_up.set_sensitive(True)
self._button_oct_up.set_sensitive(True)
self._tuner.keys_modified()
shift += 1
else:
return False
def _select_note(self, *args):
"""Select a note using the note selector widget."""
note_selector = Note_selection_dialog("Select note",
self._index,
self._tuner.get_notestyle())
response = note_selector.run()
if response == Gtk.ResponseType.OK:
self._index = note_selector.get_index()
self.set_label()
if self._index is 0:
self._button_up.set_sensitive(True)
self._button_oct_up.set_sensitive(True)
self._button_down.set_sensitive(False)
self._button_oct_down.set_sensitive(False)
elif self._index is (len(Note.notes) - 1 ):
self._button_up.set_sensitive(False)
self._button_oct_up.set_sensitive(False)
self._button_down.set_sensitive(True)
self._button_oct_down.set_sensitive(True)
else:
self._button_up.set_sensitive(True)
self._button_oct_up.set_sensitive(True)
self._button_down.set_sensitive(True)
self._button_oct_down.set_sensitive(True)
self._tuner.keys_modified()
note_selector.destroy()
def get_freq(self):
"""Return current frequency value."""
return Note.notes[self._index][Note.INDEX_FREQ]
def get_index(self):
"""Return current frequency index."""
return self._index
def set_label(self):
"""Print frequency name on the frequency selector button."""
notestyle = self._tuner.get_notestyle()
self._pick_button.set_label(Note.notes[self._index][notestyle])
def set_key_enabled(self, enable):
"""Enable or disable play button.
enable is a boolean. True means enable.
"""
self._play_button.set_sensitive(enable)
def _play(self, *args):
"""Order Tuner to play the note at self._index in Note.notes."""
self._tuner.add_note_to_queue(
None, Note.notes[self._index][Note.INDEX_FREQ])
class Tuner:
"""Keyboard emitting notes to tune a music intrument such as a guitar.
The layout of the keyboard can be modified : keys added or removed,
key step tuning, global step tuning. French and english notestyle are
supported. The length of each note is adjustable.
Two backends are currently supported : beep and sox. The first uses the
internal speaker, the latter the soundcard output.
The key's notes are picked in Note.notes table.
Public methods : add_note_to_queue(), get_notestyle(), keys_modified()
"""
_BEEP, _SOX_SINE, _SOX_PLUCK = range(3)
def __init__(self, default_keys=[]):
"""Initialize and display a keyboard
default_keys is an optional array of frequency indexes
"""
self._default_keys = default_keys
self._buttons = []
self._beep_process = 0
self._beep_queue = deque([])
self._note_playing = False
self._keys_modified = False
self._beep_length = 1
self._notestyle = Note.INDEX_FR_NAME
self._backend = self._SOX_PLUCK
self._window = Gtk.Window()
self._window.connect("destroy", self._close_request)
self._window.set_title("Tuner")
self._window.set_border_width(10)
VBox = Gtk.Box(homogeneous=False, spacing=10,
orientation=Gtk.Orientation.VERTICAL)
HBox_controls = Gtk.Box()
self._HBox_keys = Gtk.Box(homogeneous=True)
HBox_settings = Gtk.Box(homogeneous=False, spacing=10)
self._reset_button = Gtk.Button(stock=Gtk.STOCK_CANCEL)
self._reset_button.set_sensitive(False)
HBox_controls.pack_start(self._reset_button, False, False, 0)
self._reset_button.connect("clicked", self._reset_keys)
self._reset_button.show()
self._button_rem = Gtk.Button(stock=Gtk.STOCK_REMOVE)
HBox_controls.pack_start(self._button_rem, False, False, 0)
self._button_rem.show()
self._button_add = Gtk.Button(stock=Gtk.STOCK_ADD)
HBox_controls.pack_start(self._button_add, False, False, 0)
self._button_add.show()
self._button_add.connect("clicked", self._add_key)
self._button_rem.connect("clicked", self._rem_key)
self._stop_playback_button = Gtk.Button(stock=Gtk.STOCK_MEDIA_STOP)
self._stop_playback_button.set_sensitive(False)
HBox_controls.pack_end(self._stop_playback_button, False, False, 0)
self._stop_playback_button.connect("clicked",
self._stop_playback_request)
self._stop_playback_button.show()
self._play_all_button = Gtk.Button(stock=Gtk.STOCK_MEDIA_PLAY)
HBox_controls.pack_end(self._play_all_button, False, False, 0)
self._play_all_button.connect("clicked", self._play_all)
self._play_all_button.show()
VBox_inc_dec = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5)
HBox_arrow = Gtk.Box()
arrow = Gtk.Arrow(arrow_type=Gtk.ArrowType.DOWN,
shadow_type=Gtk.ShadowType.OUT)
HBox_arrow.pack_start(arrow, True, True, 0)
arrow.show()
label = Gtk.Label(label="8")
HBox_arrow.pack_start(label, True, True, 0)
label.show()
self._button_oct_down = Gtk.Button()
self._button_oct_down.add(HBox_arrow)
VBox_inc_dec.pack_end(self._button_oct_down, False, True, 0)
HBox_arrow.show()
self._button_oct_down.show()
HBox_arrow = Gtk.Box()
arrow = Gtk.Arrow(arrow_type=Gtk.ArrowType.DOWN,
shadow_type=Gtk.ShadowType.OUT)
HBox_arrow.pack_start(arrow, True, True, 0)
arrow.show()
label = Gtk.Label(label=u"\u266D")
HBox_arrow.pack_start(label, True, True, 0)
label.show()
self._button_down = Gtk.Button()
self._button_down.add(HBox_arrow)
VBox_inc_dec.pack_end(self._button_down, False, True, 0)
HBox_arrow.show()
self._button_down.show()
HBox_arrow = Gtk.Box()
arrow = Gtk.Arrow(arrow_type=Gtk.ArrowType.UP,
shadow_type=Gtk.ShadowType.OUT)
HBox_arrow.pack_start(arrow, True, True, 0)
arrow.show()
label = Gtk.Label(label=u"\u266F")
HBox_arrow.pack_start(label, True, True, 0)
label.show()
self._button_up = Gtk.Button()
self._button_up.add(HBox_arrow)
VBox_inc_dec.pack_end(self._button_up, False, True, 0)
HBox_arrow.show()
self._button_up.show()
HBox_arrow = Gtk.Box()
arrow = Gtk.Arrow(arrow_type=Gtk.ArrowType.UP,
shadow_type=Gtk.ShadowType.OUT)
HBox_arrow.pack_start(arrow, True, True, 0)
arrow.show()
label = Gtk.Label(label="8")
HBox_arrow.pack_start(label, True, True, 0)
label.show()
self._button_oct_up = Gtk.Button()
self._button_oct_up.add(HBox_arrow)
VBox_inc_dec.pack_end(self._button_oct_up, False, True, 0)
HBox_arrow.show()
self._button_oct_up.show()
VBox_inc_dec.show()
self._HBox_keys.pack_start(VBox_inc_dec, True, True, 0)
self._button_up.connect("clicked", self._adjust_freq, 1)
self._button_down.connect("clicked", self._adjust_freq, -1)
self._button_oct_up.connect("clicked", self._adjust_freq, 12)
self._button_oct_down.connect("clicked", self._adjust_freq, -12)
self._reset_keys()
VBox_notestyle = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
label = Gtk.Label(label="Notestyle")
label.set_alignment(0,1)
VBox_notestyle.pack_start(label, False, True, 0)
label.show()
button = Gtk.RadioButton.new_with_label_from_widget(None, "French")
button.connect("clicked", self._set_notestyle, Note.INDEX_FR_NAME)
VBox_notestyle.pack_start(button, False, True, 0)
button.show()
button = Gtk.RadioButton.new_with_label_from_widget(button, "English")
button.connect("clicked", self._set_notestyle, Note.INDEX_EN_NAME)
VBox_notestyle.pack_start(button, False, True, 0)
button.show()
HBox_settings.pack_start(VBox_notestyle, False, True, 0)
VBox_notestyle.show()
separator = Gtk.VSeparator()
HBox_settings.pack_start(separator, False, True, 0)
separator.show()
VBox_beep_length= Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
label = Gtk.Label(label="Length (s)")
label.set_alignment(0,1)
VBox_beep_length.pack_start(label, False, True, 0)
label.show()
beep_length_adj = Gtk.Adjustment(value=self._beep_length, \
lower=1, \
upper=10, \
step_increment=1, \
page_increment=1, \
page_size=0)
beep_length_spin = Gtk.SpinButton(adjustment=beep_length_adj,
climb_rate=0,
digits=1)
beep_length_adj.connect("value_changed",
self._set_beep_length,
beep_length_spin)
VBox_beep_length.pack_start(beep_length_spin, False, True, 0)
beep_length_spin.show()
HBox_settings.pack_start(VBox_beep_length, False, True, 0)
VBox_beep_length.show()
separator = Gtk.VSeparator()
HBox_settings.pack_start(separator, False, True, 0)
separator.show()
VBox_backend = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
label = Gtk.Label(label="Output")
label.set_alignment(0,1)
VBox_backend.pack_start(label, False, True, 0)
label.show()
button = Gtk.RadioButton.new_with_label_from_widget(None, "Sox (pluck)")
button.connect("clicked", self._set_backend, self._SOX_PLUCK)
VBox_backend.pack_start(button, False, True, 0)
button.show()
button = Gtk.RadioButton.new_with_label_from_widget(button, "Sox (sine)")
button.connect("clicked", self._set_backend, self._SOX_SINE)
VBox_backend.pack_start(button, False, True, 0)
button.show()
button = Gtk.RadioButton.new_with_label_from_widget(button, "Beep")
button.connect("clicked", self._set_backend, self._BEEP)
VBox_backend.pack_start(button, False, True, 0)
button.show()
HBox_settings.pack_start(VBox_backend, False, True, 0)
VBox_backend.show()
VBox.pack_start(HBox_controls, True, True, 0)
VBox.pack_start(self._HBox_keys, True, True, 0)
VBox.pack_start(HBox_settings, True, True, 0)
HBox_controls.show()
self._HBox_keys.show()
HBox_settings.show()
VBox.show()
self._window.add(VBox)
self._window.show()
GObject.idle_add(self._play_note_from_queue)
def _play_note(self, freq):
""" Play a note.
freq is the frequency of the note to be played.
"""
self._set_buttons_enabled(False)
try:
if self._backend is self._BEEP:
self._beep_process = \
Popen(['beep',
'-f %s' % freq,
'-l %s' % (1000 * self._beep_length)])
elif self._backend is self._SOX_SINE:
self._beep_process = \
Popen(['play', '-n',
'synth', '1',
'sine', '%s' % freq,
'repeat', '%s' % (self._beep_length - 1)])
elif self._backend is self._SOX_PLUCK:
self._beep_process = \
Popen(['play', '-n',
'synth', '1',
'pluck', '%s' % freq,
'repeat', '%s' % (self._beep_length - 1)])
except OSError:
self._missing_package_error(self._backend)
self._set_buttons_enabled(True)
else:
self._note_playing = True
GObject.idle_add(self._poll_beep_in_progress)
def _stop_playback_request(self, *args):
"""Ask for playback stop in main loop to avoid race conditions."""
GObject.idle_add(self._stop_playback)
def _stop_playback(self):
"""Clear queue and interrupt playback. Called from main loop."""
self._beep_queue.clear()
if self._note_playing :
try:
self._beep_process.send_signal(SIGINT)
except OSError:
print ("Could not SIGINT process")
def _missing_package_error(self, package):
"""Display dialog if a backend package is missing.
package is an enum standing for the incriminated backend.
"""
self._stop_playback_request()
if package is self._BEEP:
error_message = dedent("""\
Oops! It seems beep is not installed.
(http://johnath.com/beep/)
A package should be available for your distribution.
""")
elif (package is self._SOX_SINE) or (package is self._SOX_PLUCK):
error_message = dedent("""\
Oops! It seems sox is not installed.
(http://sox.sourceforge.net/)
A package should be available for your distribution.
""")
print (error_message)
dialog = Gtk.Dialog(title="Missing program", \
parent=None, \
flags=Gtk.DialogFlags.MODAL, \
buttons=(Gtk.STOCK_DIALOG_ERROR, \
Gtk.ResponseType.CLOSE))
label = Gtk.Label(label=error_message)
dialog.vbox.pack_start(label, True, True, 0)
label.show()
response = dialog.run()
if (Gtk.ResponseType.CLOSE == response or Gtk.ResponseType.DELETE_EVENT == response):
dialog.destroy()
def add_note_to_queue(self, widget, freq):
"""Add frequency to playback queue array.
freq is the frequency of the note to add to the queue.
"""
self._beep_queue.append(freq)
def _play_note_from_queue(self):
"""Play first note from the queue
This function is called from main loop.
"""
if not self._note_playing :
if self._beep_queue:
self._play_note(self._beep_queue.popleft())
return True
def _play_all(self, *args):
"""Add all notes on keyboard to queue."""
for key in self._buttons:
self.add_note_to_queue(None, key.get_freq())
def _add_key(self, widget, index=None, reset=False):
"""Add a key on the rightmost part of the keyboard.
If no freq specified, use same as rightmost key. If keyboard empty,
start with default 110 Hz.
index is the index of the freq the key should be created with.
reset is set if the function is called by the reset() function, in
which case the "keys modified" flag should not be set, to keep the
reset button disabled.
"""
if index is None:
if len(self._buttons):
index = self._buttons[-1].get_index()
else:
index = 21
key = Key(index, self)
self._HBox_keys.pack_start(key, True, True, 0)
self._buttons.append(key)
self._button_rem.set_sensitive(True)
if not reset:
self.keys_modified()
def _rem_key(self, widget=None, reset=False):
"""Remove rightmost key from the keyboard.
reset is set if the function is called by the reset() function, in
which case the "keys modified" flag should not be set, to keep the
reset button disabled.
"""
if len(self._buttons):
self._buttons.pop().destroy()
if not len(self._buttons):
self._button_rem.set_sensitive(False)
if not reset:
self.keys_modified()
def get_notestyle(self):
"""Return current notestyle."""
return self._notestyle
def _set_notestyle(self, widget, notestyle):
"""Set notestyle variable.
notestyle is an enum standing for the notestyle's index in Note.notes.
"""
self._notestyle = notestyle
for key in self._buttons:
key.set_label()
def _set_backend(self, widget, backend):
"""Set backend to use for playback.
backend is an enum standing for the backend to use.
"""
self._backend = backend
def _set_beep_length (self, widget, beep_length_spin):
"""Set the length of the playback for each note.
beep_length_spin is the spin button containing the length value,
expressed in seconds.
"""
self._beep_length = beep_length_spin.get_value()
def _adjust_freq(self, widget=None, shift=0):
"""Adjust frequency of all keys.
shift is a signed number of semitones to shift from.
"""
for key in self._buttons:
key.adjust_freq(shift=shift)
self.keys_modified()
def _set_buttons_enabled(self, enable):
"""Enable or disable buttons
This function is called to disable play buttons and enable the stop
button during playback.
"""
for key in self._buttons:
key.set_key_enabled(enable)
self._play_all_button.set_sensitive(enable)
self._stop_playback_button.set_sensitive(not enable)
if self._keys_modified :
self._reset_button.set_sensitive(enable)
def _reset_keys (self, *args):
"""Reset keyboard to default configuration."""
for i in range(len(self._buttons)):
self._rem_key(reset=True)
for i in range (len(self._default_keys)):
self._add_key(None, self._default_keys[i], reset=True)
self._keys_modified = False
self._reset_button.set_sensitive(False)
def keys_modified (self):
"""Set _keys_modified flag and enable reset button."""
self._keys_modified = True
self._reset_button.set_sensitive(True)
def _poll_beep_in_progress (self):
"""Unset _note_playing flag when playback is over.
This function is called from main loop.
"""
if self._beep_process.poll() is None:
return True
if not self._beep_queue:
self._set_buttons_enabled(True)
self._note_playing = False
return False
def _close_request (self, *args):
"""Ask for application close in main loop to avoid race conditions."""
GObject.idle_add(self._close)
def _close (self):
"""Interrupt playback before closing application.
This function is called from main loop.
"""
if self._beep_process is not 0 :
if self._note_playing :
try:
self._beep_process.send_signal(SIGINT)
except OSError:
print ("Could not SIGINT process")
else:
self._beep_process.wait()
Gtk.main_quit()
if __name__ == "__main__":
"""Create Tuner with default guitar tuning (E, A, D, G, B, e)."""
Tuner([16, 21, 26, 31, 35, 40])
Gtk.main()