Initial version

This commit is contained in:
Gergely Polonkai 2016-05-20 11:10:07 +02:00
commit ff4dbf3095
3 changed files with 254 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/venv/

230
git-sound.py Normal file
View File

@ -0,0 +1,230 @@
# -*- coding: utf-8
import argparse
import sys
import os
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
# The original scale was: [60, 62, 64, 65, 67, 69, 71]
notes = [68, 69, 71, 72, 74, 76, 77]
notecount = len(notes)
def gen_history(log, commit):
counter = 0
to_process = [commit]
while len(to_process) > 0:
counter += 1
if counter % 500 == 0:
print("Done with {} commits".format(counter))
commit = to_process.pop()
if not commit in log:
log.append(commit)
to_process += commit.parents
def gen_volume(deletions, insertions, deviation=10):
return max(
deviation,
min(255 - deviation,
127 - deletions + insertions))
def sha_to_note(sha):
note_num = reduce(lambda res, digit: res + int(digit, 16),
list(str(sha)), 0) % notecount
return notes[note_num]
def get_file_sha(commit, file_name):
elements = file_name.split(os.sep)
t = commit.tree
while True:
try:
t = t[elements.pop(0)]
except KeyError:
# The file has been deleted
return '0000000000000000000000000000000000000000'
if isinstance(t, Blob):
break
return t.hexsha
def gen_note(commit):
stat = commit.stats
note = sha_to_note(commit.hexsha)
file_notes = []
for file_name, file_stat in stat.files.items():
file_notes.append({
'note': sha_to_note(get_file_sha(commit, file_name)) - 12,
'volume': gen_volume(file_stat['deletions'],
file_stat['insertions'],
deviation=10),
})
return {
'commit_note': sha_to_note(commit.hexsha) - 24,
'commit_volume': gen_volume(stat.total['deletions'],
stat.total['insertions'],
deviation=20),
'file_notes': file_notes,
}
class MemMIDI(MIDIFile):
def __init__(self, tracks=None):
if tracks is None:
tracks = [("Sample Track", 120)]
self.track_count = len(tracks)
MIDIFile.__init__(self, self.track_count)
self.mem_file = StringIO()
if tracks is None:
self.addTrackName(0, 0, "Sample Track")
self.addTempo(0, 0, 120)
else:
for idx, (name, tempo) in enumerate(tracks):
self.addTrackName(idx, 0, name)
self.addTempo(idx, 0, tempo)
def writeMem(self):
self.writeFile(self.mem_file)
def exportFile(self, filename):
with open(filename, 'w') as f:
self.mem_file.seek(0)
shutil.copyfileobj(self.mem_file, f)
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Voice of a Repo')
parser.add_argument('repository', type=str)
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")
args = parser.parse_args()
try:
repo = Repo(args.repository)
except InvalidGitRepositoryError:
print("{} is not a valid Git repository".format(
os.path.abspath(args.repository)))
sys.exit(1)
try:
branch = repo.heads[args.branch]
except IndexError:
print("Branch '{}' does not exist in this repo".format(args.branch))
sys.exit(1)
orig_log = []
if args.verbose:
print("Generating Git log…")
gen_history(orig_log, branch.commit)
if args.verbose:
print("Sorting commits…")
orig_log.sort(key=lambda commit: commit.authored_date)
if args.verbose:
print("Generating MIDI data…")
log = map(gen_note, orig_log)
if args.verbose:
print("Creating MIDI…")
MyMIDI = MemMIDI()
track = 0
time = 0
log_channel = 0
decor_channel = 1
# Duration of one note
duration = 0.3
MyMIDI.addProgramChange(track, log_channel, 0, 104)
MyMIDI.addProgramChange(track, decor_channel, 0, 115)
# WRITE THE SEQUENCE
for section in log:
section_len = len(section['file_notes']) * duration
# Add a long note
MyMIDI.addNote(track, log_channel,
section['commit_note'], time,
section_len, section['commit_volume'])
for i, file_note in enumerate(section['file_notes']):
MyMIDI.addNote(track, decor_channel,
file_note['note'], time + i * duration,
duration, file_note['volume'])
time += section_len
MyMIDI.writeMem()
if args.file:
if args.verbose:
print("Saving file to {}".format(args.file))
MyMIDI.exportFile(args.file)
if args.play:
if args.verbose:
print("Playing!")
# Import pygame stuff here
import pygame
import pygame.mixer
# PLAYBACK
pygame.init()
pygame.mixer.init()
MyMIDI.mem_file.seek(0)
pygame.mixer.music.load(MyMIDI.mem_file)
pygame.mixer.music.play()
while pygame.mixer.music.get_busy():
sleep(1)

23
notes.txt Normal file
View File

@ -0,0 +1,23 @@
start: 60
steps: [0, 2, 2, 1, 2, 2, 2, 1]
Next octave: +12
Notes used in PI music:
83 o
81 ------------------------------------------o------
79 #o
77 --------------------------------o----------------
76 o
74 --------------------- o -------------------------
72 o
71 ----------- o -----------------------------------
69 o
67 - #o --------------------------------------------
65
64 -------------------------------------------------
62
60 -------------------------------------------------
0 1 2 3 4 5 6 7 8 9