296 lines
9.3 KiB
Python
296 lines
9.3 KiB
Python
# -*- coding: utf-8
|
|
"""
|
|
GUI for git-sound
|
|
"""
|
|
|
|
import sys
|
|
import gi
|
|
gi.require_version('Gtk', '3.0')
|
|
|
|
from gi.repository import Gtk
|
|
from gi.repository import GLib
|
|
|
|
from git.exc import InvalidGitRepositoryError
|
|
from git import Repo
|
|
|
|
from .gitmidi import GitMIDI
|
|
|
|
|
|
class GitSoundWindow(object):
|
|
"""
|
|
GIU class for git-sound.
|
|
"""
|
|
|
|
def __init__(self, programs, scales):
|
|
self.__programs = programs
|
|
self.__scales = scales
|
|
|
|
self.builder = Gtk.Builder()
|
|
self.builder.add_from_file('git-sound.ui')
|
|
|
|
self.win = self.builder.get_object('main-window')
|
|
self.play_button = self.builder.get_object('play-button')
|
|
self.stop_button = self.builder.get_object('stop-button')
|
|
self.vol_spin = self.builder.get_object('vol-spin')
|
|
self.program_combo = self.builder.get_object('program-combo')
|
|
self.progressbar = self.builder.get_object('generate-progress')
|
|
self.branch_combo = self.builder.get_object('branch-combo')
|
|
self.statusbar = self.builder.get_object('statusbar')
|
|
self.pos_label = self.builder.get_object('position-label')
|
|
self.skip_spin = self.builder.get_object('skip-spin')
|
|
self.scale_combo = self.builder.get_object('scale-combo')
|
|
self.chooser_button = self.builder.get_object('repo-chooser')
|
|
self.notelen_spin = self.builder.get_object('notelen-spin')
|
|
self.beatlen_spin = self.builder.get_object('beatlen-spin')
|
|
self.generate_button = self.builder.get_object('generate-button')
|
|
self.save_button = self.builder.get_object('save-button')
|
|
|
|
self.gitmidi = None
|
|
|
|
program_store = self.builder.get_object('program-list')
|
|
|
|
for program_id, program in self.__programs.items():
|
|
program_store.append([program['name'], program_id])
|
|
|
|
renderer = Gtk.CellRendererText()
|
|
self.program_combo.pack_start(renderer, True)
|
|
self.program_combo.add_attribute(renderer, "text", 0)
|
|
|
|
scale_store = self.builder.get_object('scale-list')
|
|
|
|
for scale_id, scale in self.__scales.items():
|
|
scale_store.append([scale[0], scale_id])
|
|
|
|
renderer = Gtk.CellRendererText()
|
|
self.scale_combo.pack_start(renderer, True)
|
|
self.scale_combo.add_attribute(renderer, "text", 0)
|
|
|
|
self.builder.connect_signals({
|
|
'read_branches': lambda button: self.read_branches(),
|
|
'settings_changed': lambda button: self.settings_changed(),
|
|
'generate_repo': lambda button: self.generate_repo(),
|
|
'play_midi': lambda button: self.play_midi(),
|
|
'stop_midi': lambda button: self.stop_midi(),
|
|
'save_midi': lambda button: self.save_midi(),
|
|
})
|
|
|
|
self.win.connect("delete-event", Gtk.main_quit)
|
|
|
|
def read_branches(self):
|
|
"""
|
|
Callback for the repository chooser. Upon change, this reads
|
|
all the branches from the selected repository.
|
|
"""
|
|
|
|
# Make sure the Play, Stop and Save buttons are disabled
|
|
self.gitmidi = None
|
|
repo_path = self.chooser_button.get_file().get_path()
|
|
self.branch_combo.remove_all()
|
|
self.branch_combo.set_button_sensitivity(False)
|
|
self.set_buttons_sensitivity(disable_all=True)
|
|
|
|
try:
|
|
repo = Repo(repo_path)
|
|
except InvalidGitRepositoryError:
|
|
dialog = Gtk.MessageDialog(
|
|
self.chooser_button.get_toplevel(),
|
|
Gtk.DialogFlags.MODAL,
|
|
Gtk.MessageType.ERROR,
|
|
Gtk.ButtonsType.OK,
|
|
"{} is not a valid Git repository".format(
|
|
repo_path))
|
|
|
|
dialog.connect('response',
|
|
lambda dialog, response_id: dialog.destroy())
|
|
dialog.run()
|
|
|
|
return
|
|
|
|
self.set_status('Opened repository: {}'.format(repo_path))
|
|
self.branch_combo.set_button_sensitivity(True)
|
|
|
|
for head in repo.heads:
|
|
self.branch_combo.append_text(head.name)
|
|
|
|
def set_status(self, text):
|
|
"""
|
|
Change the status bar text.
|
|
"""
|
|
|
|
self.statusbar.push(self.statusbar.get_context_id("git-sound"), text)
|
|
|
|
def settings_changed(self):
|
|
"""
|
|
Callback to use if anything MIDI-related is changed
|
|
(repository, branch, scale or program).
|
|
"""
|
|
|
|
self.stop_midi()
|
|
self.gitmidi = None
|
|
self.set_buttons_sensitivity()
|
|
|
|
def set_buttons_sensitivity(self, disable_all=False):
|
|
"""
|
|
Set buttons sensitivity based on different conditions.
|
|
|
|
It checks if a repository and a branch is selected or if MIDI
|
|
data is already generated.
|
|
"""
|
|
|
|
self.stop_button.set_sensitive(False)
|
|
|
|
if disable_all:
|
|
self.generate_button.set_sensitive(False)
|
|
self.play_button.set_sensitive(False)
|
|
self.save_button.set_sensitive(False)
|
|
|
|
return
|
|
|
|
if self.gitmidi is not None:
|
|
self.generate_button.set_sensitive(False)
|
|
self.play_button.set_sensitive(True)
|
|
self.save_button.set_sensitive(True)
|
|
|
|
return
|
|
|
|
branch_selected = self.branch_combo.get_active_text() is not None
|
|
program_selected = self.program_combo.get_active_id() is not None
|
|
scale_selected = self.scale_combo.get_active_id() is not None
|
|
|
|
if branch_selected and program_selected and scale_selected:
|
|
self.generate_button.set_sensitive(True)
|
|
self.play_button.set_sensitive(False)
|
|
self.save_button.set_sensitive(False)
|
|
|
|
def generate_repo(self):
|
|
"""
|
|
Generate repository data for MIDI data.
|
|
"""
|
|
|
|
repo_path = self.chooser_button.get_file().get_path()
|
|
branch_selected = self.branch_combo.get_active_text()
|
|
program_selected = self.program_combo.get_active_id()
|
|
scale_selected = self.scale_combo.get_active_id()
|
|
skip = int(self.skip_spin.get_value())
|
|
vol_deviation = int(self.vol_spin.get_value())
|
|
notelen = self.notelen_spin.get_value()
|
|
beatlen = int(self.beatlen_spin.get_value()) or None
|
|
|
|
self.progressbar.set_fraction(0.0)
|
|
self.progressbar.pulse()
|
|
self.set_status("Reading commits")
|
|
self.gitmidi = GitMIDI(repository=repo_path,
|
|
branch=branch_selected,
|
|
verbose=False,
|
|
scale=self.__scales[scale_selected][1],
|
|
program=self.__programs[program_selected],
|
|
volume_range=vol_deviation,
|
|
skip=skip,
|
|
note_duration=notelen,
|
|
max_beat_len=beatlen)
|
|
|
|
self.set_status("Generating beats")
|
|
self.gitmidi.gen_repo_data(callback=self.genrepo_cb)
|
|
self.set_status("Generating MIDI")
|
|
self.gitmidi.generate_midi(callback=self.genrepo_cb)
|
|
self.gitmidi.write_mem()
|
|
|
|
self.set_buttons_sensitivity(disable_all=False)
|
|
|
|
def genrepo_cb(self, max_count, current):
|
|
"""
|
|
Generate repository data. This is called when the user presses
|
|
the Generate button.
|
|
"""
|
|
|
|
if max_count is None or current is None:
|
|
self.progressbar.pulse()
|
|
else:
|
|
fraction = float(current) / float(max_count)
|
|
self.progressbar.set_fraction(fraction)
|
|
|
|
# Make sure the progress bar gets updated
|
|
Gtk.main_iteration_do(False)
|
|
Gtk.main_iteration_do(False)
|
|
|
|
def update_play_pos(self):
|
|
"""
|
|
Update playback position label.
|
|
"""
|
|
|
|
if self.gitmidi is None:
|
|
return
|
|
|
|
position = self.gitmidi.get_play_pos()
|
|
|
|
if position is None:
|
|
self.set_status("Stopped")
|
|
self.pos_label.set_text("0:00")
|
|
self.play_button.set_sensitive(True)
|
|
self.stop_button.set_sensitive(False)
|
|
|
|
return False
|
|
|
|
position = int(position / 1000)
|
|
|
|
minutes = int(position / 60)
|
|
seconds = position - (minutes * 60)
|
|
|
|
self.pos_label.set_text("{}:{:02}".format(minutes, seconds))
|
|
|
|
return True
|
|
|
|
def play_midi(self):
|
|
"""
|
|
Start MIDI playback.
|
|
"""
|
|
|
|
self.set_status(u"Playing…")
|
|
self.gitmidi.play(track=True)
|
|
GLib.timeout_add_seconds(1, self.update_play_pos)
|
|
self.play_button.set_sensitive(False)
|
|
self.stop_button.set_sensitive(True)
|
|
|
|
def stop_midi(self):
|
|
"""
|
|
Stop MIDI playback.
|
|
"""
|
|
|
|
if self.gitmidi is not None:
|
|
self.gitmidi.stop()
|
|
|
|
def __save(self, dialog, response_id):
|
|
"""
|
|
Do the actual MIDI saving after the user chose a file.
|
|
"""
|
|
|
|
if response_id == Gtk.ResponseType.OK:
|
|
save_file = dialog.get_file().get_path()
|
|
dialog.destroy()
|
|
self.gitmidi.export_file(save_file)
|
|
|
|
def save_midi(self):
|
|
"""
|
|
Save MIDI data.
|
|
"""
|
|
|
|
dialog = Gtk.FileChooserDialog(
|
|
u"Save As…",
|
|
self.win,
|
|
Gtk.FileChooserAction.SAVE,
|
|
("Save", Gtk.ResponseType.OK))
|
|
dialog.set_do_overwrite_confirmation(True)
|
|
|
|
dialog.connect('response', self.__save)
|
|
dialog.run()
|
|
|
|
def start(self):
|
|
"""
|
|
Start the GUI.
|
|
"""
|
|
|
|
self.win.show_all()
|
|
Gtk.main()
|
|
|
|
sys.exit(0)
|