git-sound/git-sound.py

854 lines
24 KiB
Python
Raw Normal View History

2016-06-09 15:55:09 +00:00
#! /usr/bin/env python
2016-05-20 09:10:07 +00:00
# -*- coding: utf-8
2016-06-09 19:31:46 +00:00
"""
Generate sound for Git repositories
"""
from __future__ import print_function
2016-05-20 09:10:07 +00:00
import argparse
import sys
import os
2016-06-09 19:31:46 +00:00
try:
import pygame
import pygame.mixer
PYGAME_AVAILABLE = True
except ImportError:
PYGAME_AVAILABLE = False
try:
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
from gi.repository import GLib
GTK_AVAILABLE = True
except ImportError:
GTK_AVAILABLE = False
2016-05-20 09:10:07 +00:00
import shutil
from midiutil.MidiFile import MIDIFile
from StringIO import StringIO
from time import sleep
from git import Repo
from git.objects.blob import Blob
from git.exc import InvalidGitRepositoryError
2016-06-09 19:31:46 +00:00
SCALES = {
2016-06-09 10:12:49 +00:00
'c-major': ('C Major', [60, 62, 64, 65, 67, 69, 71]),
'a-harmonic-minor': ('A Harmonic Minor', [68, 69, 71, 72, 74, 76, 77]),
'chromatic': ('Chromatic', [60, 61, 62, 63, 64, 65, 66, 67, 68, 69]),
'pentatonic': ('Pentatonic', [54, 64, 72, 81, 96, 108]),
2016-06-09 15:53:44 +00:00
'd-major': ('D Major', [62, 64, 65, 67, 69, 71, 72]),
}
2016-05-20 09:10:07 +00:00
2016-06-09 19:31:46 +00:00
PROGRAMS = {
'sitar-tablah': {
2016-06-09 10:12:49 +00:00
'name': 'Sitar and Tablah',
'commit': {
'program': 104,
'octave': -2,
},
'file': {
'program': 115,
'octave': -1,
},
},
'bells': {
2016-06-09 10:12:49 +00:00
'name': 'Bells',
'commit': {
'program': 14,
'octave': 0,
},
'file': {
'program': 9,
'octave': 0,
},
},
2016-05-26 15:29:47 +00:00
'metal': {
2016-06-09 10:12:49 +00:00
'name': 'Metal',
2016-05-26 15:29:47 +00:00
'commit': {
2016-06-09 08:58:36 +00:00
'program': 29,
2016-05-26 15:29:47 +00:00
'octave': -1,
},
'file': {
'program': 33,
'octave': -3,
},
},
2016-05-26 17:32:03 +00:00
'pure-violin': {
2016-06-09 10:12:49 +00:00
'name': 'Violin',
2016-05-26 17:32:03 +00:00
'commit': {
'program': 40,
'octave': 0,
},
'file': {
'program': None,
'octave': 0,
},
},
2016-06-09 09:01:27 +00:00
'space': {
2016-06-09 10:12:49 +00:00
'name': 'Space',
2016-06-09 09:01:27 +00:00
'commit': {
'program': 94,
'octave': 1,
},
'file': {
'program': 80,
'octave': 1,
'volume': -30,
},
},
2016-06-09 15:34:38 +00:00
'sea-copter': {
'name': 'Helicopter on the shore',
'commit': {
'program': 125,
'octave': 0,
},
'file': {
'program': 122,
'octave': 0,
},
},
}
2016-05-20 09:10:07 +00:00
def get_file_sha(commit, file_name):
2016-06-09 19:31:46 +00:00
"""
Get the SHA1 ID of a file by its name, in the given commit.
"""
2016-05-20 09:10:07 +00:00
2016-06-09 19:31:46 +00:00
elements = file_name.split(os.sep)
tree = commit.tree
2016-05-20 09:10:07 +00:00
while True:
try:
2016-06-09 19:31:46 +00:00
tree = tree[elements.pop(0)]
2016-05-26 17:53:49 +00:00
except (KeyError, IndexError):
2016-05-24 10:12:15 +00:00
# The file has been deleted, return the hash of an empty file
return 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391'
2016-05-20 09:10:07 +00:00
2016-06-09 19:31:46 +00:00
if isinstance(tree, Blob):
2016-05-20 09:10:07 +00:00
break
2016-06-09 19:31:46 +00:00
return tree.hexsha
2016-05-20 09:10:07 +00:00
2016-06-09 15:54:09 +00:00
class GitSoundWindow(object):
2016-06-09 19:31:46 +00:00
"""
GIU class for git-sound.
"""
2016-06-09 15:54:09 +00:00
2016-06-09 19:31:46 +00:00
def __init__(self):
self.builder = Gtk.Builder()
2016-06-09 15:54:09 +00:00
self.builder.add_from_file('git-sound.ui')
2016-06-09 16:22:45 +00:00
self.win = self.builder.get_object('main-window')
2016-06-09 15:54:09 +00:00
self.play_button = self.builder.get_object('play-button')
self.stop_button = self.builder.get_object('stop-button')
2016-06-09 19:31:46 +00:00
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')
2016-06-09 19:40:53 +00:00
self.notelen_spin = self.builder.get_object('notelen-spin')
2016-06-09 15:54:09 +00:00
self.gitmidi = None
2016-06-09 19:31:46 +00:00
program_store = self.builder.get_object('program-list')
for program_id, program in 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 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()
2016-06-09 15:54:09 +00:00
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:
2016-06-09 19:31:46 +00:00
dialog = Gtk.MessageDialog(
self.chooser_button.get_toplevel(),
Gtk.DialogFlags.MODAL,
Gtk.MessageType.ERROR,
Gtk.ButtonsType.OK,
2016-06-09 15:54:09 +00:00
"{} 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):
2016-06-09 19:31:46 +00:00
"""
Change the status bar text.
"""
2016-06-09 15:54:09 +00:00
self.statusbar.push(self.statusbar.get_context_id("git-sound"), text)
2016-06-09 19:31:46 +00:00
def settings_changed(self):
"""
Callback to use if anything MIDI-related is changed
(repository, branch, scale or program).
"""
2016-06-09 15:54:09 +00:00
self.gitmidi = None
self.set_buttons_sensitivity()
self.stop_midi()
def set_buttons_sensitivity(self, disable_all=False):
2016-06-09 19:31:46 +00:00
"""
Set buttons sensitivity based on different conditions.
It checks if a repository and a branch is selected or if MIDI
data is already generated.
"""
2016-06-09 15:54:09 +00:00
generate_button = self.builder.get_object('generate-button')
save_button = self.builder.get_object('save-button')
if disable_all:
generate_button.set_sensitive(False)
self.play_button.set_sensitive(False)
self.stop_button.set_sensitive(False)
save_button.set_sensitive(False)
return
if self.gitmidi is not None:
generate_button.set_sensitive(False)
self.play_button.set_sensitive(True)
self.stop_button.set_sensitive(False)
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:
generate_button.set_sensitive(True)
self.play_button.set_sensitive(False)
self.stop_button.set_sensitive(False)
save_button.set_sensitive(False)
2016-06-09 19:31:46 +00:00
def generate_repo(self):
"""
Generate repository data for MIDI data.
"""
repo_path = self.chooser_button.get_file().get_path()
2016-06-09 15:54:09 +00:00
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())
2016-06-09 19:40:53 +00:00
notelen = self.notelen_spin.get_value()
2016-06-09 15:54:09 +00:00
self.set_status("Generating data")
self.progressbar.set_fraction(0.0)
self.progressbar.pulse()
self.gitmidi = GitMIDI(repository=repo_path,
branch=branch_selected,
verbose=False,
2016-06-09 19:31:46 +00:00
scale=SCALES[scale_selected][1],
program=PROGRAMS[program_selected],
2016-06-09 15:54:09 +00:00
volume_range=vol_deviation,
2016-06-09 19:40:53 +00:00
skip=skip,
note_duration=notelen)
2016-06-09 15:54:09 +00:00
self.gitmidi.gen_repo_data(callback=self.genrepo_cb)
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=None, current=None):
2016-06-09 19:31:46 +00:00
"""
Generate repository data. This is called when the user presses
the Generate button.
"""
2016-06-09 15:54:09 +00:00
if max_count is None or current is None:
self.progressbar.pulse()
else:
self.progressbar.set_fraction(current / max_count)
# Make sure the progress bar gets updated
2016-06-09 19:31:46 +00:00
Gtk.main_iteration_do(False)
2016-06-09 15:54:09 +00:00
def update_play_pos(self):
2016-06-09 19:31:46 +00:00
"""
Update playback position label.
"""
2016-06-09 16:22:45 +00:00
if self.gitmidi is None:
return
2016-06-09 15:54:09 +00:00
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):
2016-06-09 19:31:46 +00:00
"""
Start MIDI playback.
"""
2016-06-09 15:54:09 +00:00
self.set_status(u"Playing…")
self.gitmidi.play(track=True)
2016-06-09 19:31:46 +00:00
GLib.timeout_add_seconds(1, self.update_play_pos)
2016-06-09 15:54:09 +00:00
self.play_button.set_sensitive(False)
self.stop_button.set_sensitive(True)
def stop_midi(self):
2016-06-09 19:31:46 +00:00
"""
Stop MIDI playback.
"""
2016-06-09 15:54:09 +00:00
if self.gitmidi is not None:
self.gitmidi.stop()
2016-06-09 16:22:45 +00:00
def __save(self, dialog, response_id):
2016-06-09 19:31:46 +00:00
"""
Do the actual MIDI saving after the user chose a file.
"""
if response_id == Gtk.ResponseType.OK:
2016-06-09 16:22:45 +00:00
save_file = dialog.get_file().get_path()
dialog.destroy()
self.gitmidi.export_file(save_file)
def save_midi(self):
2016-06-09 19:31:46 +00:00
"""
Save MIDI data.
"""
dialog = Gtk.FileChooserDialog(
2016-06-09 16:22:45 +00:00
u"Save As…",
self.win,
2016-06-09 19:31:46 +00:00
Gtk.FileChooserAction.SAVE,
("Save", Gtk.ResponseType.OK))
2016-06-09 16:22:45 +00:00
dialog.set_do_overwrite_confirmation(True)
dialog.connect('response', self.__save)
dialog.run()
2016-06-09 15:54:09 +00:00
def start(self):
2016-06-09 19:31:46 +00:00
"""
Start the GUI.
"""
2016-06-09 15:54:09 +00:00
2016-06-09 16:22:45 +00:00
self.win.show_all()
2016-06-09 19:31:46 +00:00
Gtk.main()
2016-06-09 15:54:09 +00:00
sys.exit(0)
class GitMIDI(MIDIFile):
2016-06-09 19:31:46 +00:00
"""
Class to hold repository data, and MIDI data based on that repository.
"""
LOG_CHANNEL = 0
FILE_CHANNEL = 1
def __setup_midi(self, track_title=None):
2016-06-09 19:31:46 +00:00
"""
Initialise the MIDI file.
"""
if self.__verbose:
print("Preparing MIDI track…")
if track_title is None:
# TODO: Change this to something that connects to the repo
self.addTrackName(0, 0, "Sample Track")
2016-06-09 19:31:46 +00:00
self.addTempo(0, 0, self.__tempo)
if self.__need_commits:
self.addProgramChange(0, self.LOG_CHANNEL,
0, self.__program['commit']['program'])
if self.__need_files:
self.addProgramChange(0, self.FILE_CHANNEL,
0, self.__program['file']['program'])
def __setup_repo(self):
2016-06-09 19:31:46 +00:00
"""
Setup repository and get the specified branch.
"""
if self.__verbose:
print("Analyzing repository…")
repo = Repo(self.__repo_dir)
2016-06-09 19:31:46 +00:00
self.__branch_head = repo.heads[self.__branch].commit
def __init__(self,
2016-06-09 19:31:46 +00:00
repository=None,
branch=None,
verbose=False,
scale=None,
program=None,
volume_range=107,
skip=0,
2016-06-09 19:31:46 +00:00
note_duration=0.3,
max_beat_len=None,
tempo=120):
MIDIFile.__init__(self, 1)
2016-05-20 09:10:07 +00:00
self.__verbose = verbose
self.__written = False
2016-06-09 19:31:46 +00:00
self.__repo_dir = repository or '.'
self.__repo = None
2016-06-09 19:31:46 +00:00
self.__branch = branch or 'master'
self.__branch_head = None
2016-05-24 10:12:15 +00:00
self.__repo_data = None
2016-06-09 19:31:46 +00:00
self.__git_log = []
self.__mem_file = StringIO()
self.__scale = scale
self.__program = program
self.__volume_deviation = min(abs(63 - volume_range), 63)
self.__pygame_inited = False
self.__playing = False
self.__skip = skip
self.__note_duration = note_duration
2016-06-09 19:31:46 +00:00
self.__max_beat_len = max_beat_len or 0.3
self.__tempo = tempo
self.__need_commits = self.__program['commit']['program'] is not None
self.__need_files = self.__program['file']['program'] is not None
self.__setup_midi()
self.__setup_repo()
2016-05-20 09:10:07 +00:00
def gen_volume(self, deletions, insertions, modifier):
2016-05-26 17:54:01 +00:00
"""
Generate a volume based on the number of modified lines
(insertions - deletions).
deviation specifies the minimum and maximum volume (minimum is
2016-05-26 18:40:57 +00:00
the value of deviation, maximum is 127 - deviation).
2016-05-26 17:54:01 +00:00
"""
return max(
self.__volume_deviation,
min(127 - self.__volume_deviation,
63 - deletions + insertions + modifier))
def sha_to_note(self, sha):
2016-06-09 19:31:46 +00:00
"""
Calculate note based on an SHA1 hash
"""
note_num = reduce(lambda res, digit: res + int(digit, 16),
list(str(sha)), 0) % len(self.__scale)
return self.__scale[note_num]
def gen_beat(self, commit, callback=None):
2016-06-09 19:31:46 +00:00
"""
Generate data for a beat based on a commit and its files.
"""
stat = commit.stats
file_notes = []
for file_name, file_stat in stat.files.items():
if callback is not None:
callback(max_count=None, current=None)
volume_mod = self.__program['file'].get('volume', 0)
2016-06-09 19:31:46 +00:00
file_note = self.sha_to_note(get_file_sha(commit, file_name)) + \
self.__program['file']['octave'] * 12
file_volume = self.gen_volume(file_stat['deletions'],
file_stat['insertions'],
2016-06-09 19:31:46 +00:00
volume_mod)
file_notes.append({
'note': file_note,
'volume': file_volume,
})
if callback is not None:
callback(max_count=None, current=None)
volume_mod = self.__program['commit'].get('volume', 0)
return {
'commit_note': self.sha_to_note(commit.hexsha) +
2016-06-09 19:31:46 +00:00
self.__program['commit']['octave'] * 12,
'commit_volume': self.gen_volume(stat.total['deletions'],
stat.total['insertions'],
volume_mod),
'file_notes': file_notes,
}
def gen_repo_data(self, force=False, callback=None):
2016-05-24 10:12:15 +00:00
"""
Populate __repo_data with the Git history data. If force is
False and the repo_data is already calculated, we do not do
anything.
"""
if self.__repo_data and not force:
return
if self.__verbose:
print("Reading repository log…")
self.__repo_data = []
2016-05-24 10:12:15 +00:00
counter = 0
2016-06-09 19:31:46 +00:00
to_process = [self.__branch_head]
2016-05-24 10:12:15 +00:00
while len(to_process) > 0:
counter += 1
# TODO: Make this 500 configurable
if counter % 500 == 0 and self.__verbose:
2016-05-24 10:12:15 +00:00
print("Done with {} commits".format(counter))
2016-06-09 19:31:46 +00:00
current_commit = to_process.pop()
2016-05-24 10:12:15 +00:00
if callback is not None:
callback(max_count=None, current=None)
2016-06-09 19:31:46 +00:00
if current_commit not in self.__repo_data:
self.__repo_data.append(current_commit)
to_process += current_commit.parents
2016-05-24 10:12:15 +00:00
if self.__verbose:
print("{} commits found".format(counter))
print("Sorting commits…")
self.__repo_data.sort(key=lambda commit: commit.authored_date)
if self.__verbose:
print("Generating MIDI data…")
2016-05-24 10:12:15 +00:00
2016-06-09 19:31:46 +00:00
self.__git_log = [self.gen_beat(commit, callback=callback)
for commit in self.__repo_data[self.__skip:]]
2016-05-24 10:12:15 +00:00
@property
def repo_data(self):
2016-06-09 19:31:46 +00:00
"""
Get repository data for MIDI generation.
"""
2016-05-24 10:12:15 +00:00
if self.__repo_data is None:
self.gen_repo_data(force=True)
return self.__repo_data
def write_mem(self):
2016-06-09 19:31:46 +00:00
"""
Write MIDI data to the memory file.
"""
self.writeFile(self.__mem_file)
self.__written = True
def export_file(self, filename):
2016-06-09 19:31:46 +00:00
"""
Export MIDI data to a file.
"""
if not self.__written:
self.write_mem()
2016-05-20 09:10:07 +00:00
2016-06-09 19:31:46 +00:00
with open(filename, 'w') as midi_file:
self.__mem_file.seek(0)
shutil.copyfileobj(self.__mem_file, midi_file)
2016-05-20 09:10:07 +00:00
def generate_midi(self, callback=None):
2016-06-09 19:31:46 +00:00
"""
Generate MIDI data.
"""
if self.__verbose:
print("Creating MIDI…")
track = 0
time = 0
log_channel = 0
decor_channel = 1
2016-06-09 19:31:46 +00:00
log_length = len(self.__git_log)
current = 0
# WRITE THE SEQUENCE
2016-06-09 19:31:46 +00:00
for section in self.__git_log:
current += 1
section_len = len(section['file_notes']) * self.__note_duration
if callback is not None:
callback(max_count=log_length, current=current)
# Add a long note
if self.__need_commits:
self.addNote(track, log_channel,
section['commit_note'], time,
section_len, section['commit_volume'])
if self.__need_files:
for i, file_note in enumerate(section['file_notes']):
self.addNote(track, decor_channel,
file_note['note'],
time + i * self.__note_duration,
self.__note_duration, file_note['volume'])
time += section_len
def __init_pygame(self):
2016-06-09 19:31:46 +00:00
"""
Initialise pygame.
"""
2016-06-09 19:31:46 +00:00
if not PYGAME_AVAILABLE or self.__pygame_inited:
return
2016-06-09 19:31:46 +00:00
# Initialise pygame
pygame.init()
pygame.mixer.init()
def play(self, track=False):
2016-06-09 19:31:46 +00:00
"""
Start MIDI playback. If pygame is not available, dont do anything.
"""
if not PYGAME_AVAILABLE:
return "pygame is not available, cannot start playback"
if self.__verbose:
print("Playing!")
self.__init_pygame()
2016-06-09 19:31:46 +00:00
self.__mem_file.seek(0)
pygame.mixer.music.load(self.__mem_file)
pygame.mixer.music.play()
self.__playing = True
if not track:
2016-06-09 19:31:46 +00:00
while pygame.mixer.music.get_busy():
sleep(1)
self.__playing = False
2016-06-09 15:53:56 +00:00
def stop(self):
2016-06-09 19:31:46 +00:00
"""
Stop MIDI playback.
"""
if not PYGAME_AVAILABLE:
return
pygame.mixer.music.stop()
2016-06-09 15:53:56 +00:00
def get_play_pos(self):
2016-06-09 19:31:46 +00:00
"""
Get current playback position from the mixer
"""
if not self.__playing:
return None
2016-06-09 19:31:46 +00:00
if pygame.mixer.music.get_busy():
return pygame.mixer.music.get_pos()
else:
self.__playing = False
return None
2016-06-09 19:31:46 +00:00
def main():
"""
Main function, used if we are not imported.
"""
parser = argparse.ArgumentParser(description='Voice of a Repo',
epilog='Use the special value list for ' +
'scale and program to list the ' +
'available program combinations')
2016-05-20 09:10:07 +00:00
parser.add_argument('repository', type=str, nargs='?', default='.')
2016-05-20 09:10:07 +00:00
parser.add_argument('--branch',
type=str,
default='master',
help="The branch to generate sound for [master]")
parser.add_argument('--file',
type=str,
default=None,
help="Save the generated MIDI sequence to this file")
parser.add_argument('--play',
action='store_true',
default=False,
help="Play the generated file (requires pygame with " +
"MIDI support)")
parser.add_argument('--verbose',
action='store_true',
default=False,
help="Print messages during execution")
parser.add_argument('--scale',
type=str,
default=None,
help="Scale to use in the generated track")
parser.add_argument('--program',
type=str,
default=None,
help="Program setting to use in the generated track")
parser.add_argument('--volume-range',
type=int,
default=100,
help="The volume range to use.")
parser.add_argument('--skip',
type=int,
default=0,
metavar='N',
help="Skip the first N commits " +
"(comes in handy if the repo started " +
"with some huge commits)")
2016-05-20 09:10:07 +00:00
args = parser.parse_args()
2016-06-09 19:31:46 +00:00
if args.scale is None and args.program is None and GTK_AVAILABLE:
GitSoundWindow().start()
sys.exit(0)
2016-06-09 15:54:09 +00:00
2016-06-09 09:10:29 +00:00
if args.scale is None and args.program != 'list':
print("Please specify a scale!")
2016-06-09 09:10:29 +00:00
sys.exit(1)
2016-06-09 09:10:29 +00:00
if args.program is None and args.scale != 'list':
print("Please specify a program!")
2016-06-09 09:10:29 +00:00
sys.exit(1)
2016-06-09 09:10:29 +00:00
if args.scale == 'list':
2016-06-09 19:31:46 +00:00
for scale in SCALES.keys():
2016-06-09 09:10:29 +00:00
print(scale)
2016-06-09 09:10:29 +00:00
sys.exit(0)
2016-06-09 09:10:29 +00:00
if args.program == 'list':
2016-06-09 19:31:46 +00:00
for program in PROGRAMS.keys():
2016-06-09 09:10:29 +00:00
print(program)
2016-06-09 09:10:29 +00:00
sys.exit(0)
2016-06-09 19:31:46 +00:00
if args.scale not in SCALES:
2016-06-09 09:10:29 +00:00
print("{} is an unknown scale.".format(args.scale))
print("Use 'list' to list the available scales.")
2016-06-09 09:10:29 +00:00
sys.exit(1)
2016-06-09 19:31:46 +00:00
if args.program not in PROGRAMS:
2016-06-09 09:10:29 +00:00
print("{} is an unknown program.".format(args.program))
print("Use 'list' to list the available programs.")
2016-06-09 09:10:29 +00:00
sys.exit(1)
2016-06-09 09:10:29 +00:00
try:
repo_midi = GitMIDI(repository=args.repository,
branch=args.branch,
verbose=args.verbose,
2016-06-09 19:31:46 +00:00
scale=SCALES[args.scale][1],
program=PROGRAMS[args.program],
volume_range=args.volume_range,
skip=args.skip)
2016-06-09 09:10:29 +00:00
except InvalidGitRepositoryError:
2016-06-09 19:31:46 +00:00
print("{} is not a valid Git repository"
2016-06-09 09:10:29 +00:00
.format(os.path.abspath(args.repository)))
2016-05-20 09:10:07 +00:00
2016-06-09 09:10:29 +00:00
sys.exit(1)
2016-05-20 09:10:07 +00:00
2016-06-09 09:10:29 +00:00
except IndexError:
print("Branch '{}' does not exist in this repo".format(args.branch))
2016-05-20 09:10:07 +00:00
2016-06-09 09:10:29 +00:00
sys.exit(1)
2016-05-20 09:10:07 +00:00
2016-06-09 09:10:29 +00:00
repo_midi.gen_repo_data()
repo_midi.generate_midi()
repo_midi.write_mem()
2016-05-20 09:10:07 +00:00
2016-06-09 09:10:29 +00:00
if args.file:
if args.verbose:
print("Saving file to {}".format(args.file))
2016-05-20 09:10:07 +00:00
2016-06-09 09:10:29 +00:00
repo_midi.export_file(args.file)
2016-05-20 09:10:07 +00:00
2016-06-09 09:10:29 +00:00
if args.play:
repo_midi.play()
2016-06-09 19:31:46 +00:00
if __name__ == '__main__':
main()