Last active
September 28, 2021 14:59
-
-
Save duhaime/b8947b22feb0cf2d7ab8c10daeb3bf9f to your computer and use it in GitHub Desktop.
Revisions
-
duhaime revised this gist
Sep 28, 2021 . 1 changed file with 99 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,99 @@ from fractions import Fraction import music21 import glob # remove leading / trailing whitespace music21.defaults.ticksAtStart = 0 notes = set([12*j + i for i in [0,2,4,5,7,9,11] for j in [0,1,2,3,4,5,6,7,8,9,10] ]) def constrain_pitch(val): # keep the pitch in C major, no incidentals while val not in notes: val = int(val) + 1 if val > max(notes): val - 2 return val def get_score(path, **kwargs): # return a score object if True: s = music21.converter.parse(path, forceSource=False, quantizePost=False, quarterLengthDivisors=(4,3), # smaller numbers mean fewer different note durations ).stripTies(inPlace=True) else: m = music21.midi.MidiFile() m.open(path) m.read() m.close() if kwargs.get('remove_percussion', True): tracks = [t for t in m.tracks if not any([e.channel == 10 for e in t.events])] else: tracks = m.tracks s = music21.stream.Score() music21.midi.translate.midiTracksToStreams(tracks, inputM21=s, forceSource=False, quantizePost=False, ticksPerQuarter=m.ticksPerQuarterNote, quarterLengthDivisors=(4,3), ) return s def midi_to_string(path, **kwargs): d = { '32nd': 1/32, '16th': 1/16, 'eighth': 1/8, 'quarter': 1/4, 'half': 1/2, 'whole': 1, } score = get_score(path, **kwargs) # transpose to c major / a minor key = score.analyze('key') assert key.mode in ['major', 'minor'] interval = 60 - key.tonic.midi if key.mode == 'minor': interval -= 3 score = score.transpose(interval) # convert midi to string s = '' last_offset = 0 for n in score.flat.notes: # get the note/chord duration duration = n.duration.components[0].type # n.duration.type returns complex for tied notes if not duration or duration == 'zero': continue duration = float(Fraction(d.get(duration, duration))) # get the offset since the previous note/chord delta = float(Fraction(n.offset - last_offset)) if delta: s += 'w{} '.format(round(delta, 4)) # add the note/chord to the string for i in [n] if isinstance(n, music21.note.Note) else n.notes: try: pitch = i.pitch.midi if kwargs.get('constrain'): pitch = constrain_pitch(pitch) s += 'n{}_{} '.format(pitch, round(duration, 4)) except: print(' * could not parse note', i) last_offset = n.offset return s def string_to_midi(s): # convert string back to midi time = 0 stream = music21.stream.Stream() for i in s.split(): if i.startswith('n'): note, duration = i.lstrip('n').split('_') n = music21.note.Note(int(note)) n.duration.quarterLength = float(duration) * 4 stream.insertIntoNoteOrChord(time, n) elif i.startswith('w'): duration = float(Fraction(i.lstrip('w'))) time += duration else: print('did not expect', i) return stream -
duhaime revised this gist
Sep 24, 2021 . 2 changed files with 161 additions and 58 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,161 @@ { "cells": [ { "cell_type": "code", "execution_count": null, "id": "7592aa63", "metadata": {}, "outputs": [], "source": [ "%load_ext autoreload\n", "%autoreload 2\n", "\n", "from fractions import Fraction\n", "import music21\n", "import glob\n", "\n", "def midi_to_string(path):\n", " d = {\n", " '32nd': 1/32,\n", " '16th': 1/16,\n", " 'eighth': 1/8,\n", " 'quarter': 1/4,\n", " 'half': 1/2,\n", " 'whole': 1,\n", " }\n", " score = music21.converter.parse(path, forceSource=False, quantizePost=False)\n", " # validate time signature is 4/4\n", " time_signature = score.parts[0].timeSignature\n", " if time_signature: assert time_signature.ratioString == '4/4'\n", " # validate mode is major / minor\n", " key = score.analyze('key')\n", " assert key.mode == 'minor' or key.mode == 'major'\n", " # transpose to c major / a minor\n", " interval = 60 - key.tonic.midi\n", " if key.mode == 'minor': interval -= 3\n", " score = score.transpose(interval)\n", " # convert midi to string\n", " s = ''\n", " last_offset = 0\n", " for n in score.flat.notes:\n", " if not isinstance(n, music21.chord.Chord):\n", " note = n.pitch.midi\n", " duration = n.duration.components[0].type # n.duration.type returns complex for tied notes\n", " duration = float(Fraction(d.get(duration, duration)))\n", " offset = n.offset\n", " delta = float(Fraction(offset - last_offset))\n", " if delta: s += 'w{} '.format(round(delta, 4))\n", " s += 'n{}_{} '.format(note, round(duration, 4))\n", " last_offset = offset\n", " return s\n", "\n", "def string_to_midi(s):\n", " # convert string back to midi\n", " time = 0\n", " stream = music21.stream.Stream()\n", " for i in s.split():\n", " if i.startswith('n'):\n", " note, duration = i.lstrip('n').split('_')\n", " n = music21.note.Note(int(note))\n", " n.duration.quarterLength = float(duration) * 4\n", " stream.insertIntoNoteOrChord(time, n)\n", " elif i.startswith('w'):\n", " duration = float(Fraction(i.lstrip('w')))\n", " time += duration\n", " else:\n", " print('did not expect', i)\n", " return stream" ] }, { "cell_type": "code", "execution_count": null, "id": "6e8add3b", "metadata": {}, "outputs": [], "source": [ "s = midi_to_string('data/exodus/ambrosia.midi')" ] }, { "cell_type": "code", "execution_count": null, "id": "4a55cde4", "metadata": {}, "outputs": [], "source": [ "midi = string_to_midi(s)\n", "midi.write('midi', fp='converted.midi')" ] }, { "cell_type": "code", "execution_count": null, "id": "8c66d809", "metadata": {}, "outputs": [], "source": [ "from collections import defaultdict\n", "from nltk import ngrams\n", "import random\n", "\n", "def markov(s, sequence_length=4, output_length=250):\n", " # train markov model\n", " d = defaultdict(list)\n", " tokens = list(ngrams(s.split(), sequence_length))\n", " for idx, i in enumerate(tokens[:-1]):\n", " d[i].append(tokens[idx+1])\n", " # sample from markov model\n", " generated = [random.choice(tokens)]\n", " while len(generated) < output_length:\n", " generated.append(random.choice(d[generated[-1]]))\n", " # format the result into a string\n", " return ' '.join([' '.join(i) for i in generated])\n", "\n", "generated = markov(s)\n", "midi = string_to_midi(generated)" ] }, { "cell_type": "code", "execution_count": null, "id": "11f869f4", "metadata": {}, "outputs": [], "source": [ "midi.write('midi', fp='generated.midi')" ] }, { "cell_type": "code", "execution_count": null, "id": "646db079", "metadata": {}, "outputs": [], "source": [ "s" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.11" } }, "nbformat": 4, "nbformat_minor": 5 } This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,58 +0,0 @@ -
duhaime revised this gist
Sep 23, 2021 . 1 changed file with 1 addition and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,3 +1,4 @@ from fractions import Fraction import music21 path = 'data/exodus/ambrosia.midi' @@ -30,8 +31,6 @@ last_offset = offset # convert string back to midi d = { '32nd': 1/32, '16th': 1/16, -
duhaime created this gist
Sep 23, 2021 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,59 @@ import music21 path = 'data/exodus/ambrosia.midi' score = music21.converter.parse(path, forceSource=True, quantizePost=False) # validate time signature is 4/4 time_signature = score.parts[0].timeSignature if time_signature: assert time_signature.ratioString == '4/4' # validate mode is major / minor key = score.analyze('key') assert key.mode == 'minor' or key.mode == 'major' # transpose to c major / a minor interval = 60 - key.tonic.midi if key.mode == 'minor': interval -= 3 score = score.transpose(interval) # convert midi to string s = '' last_offset = 0 for n in score.flat.notes: if not isinstance(n, music21.chord.Chord): note = n.pitch.midi duration = n.duration.type # n.duration.quarterLength offset = n.offset delta = offset - last_offset if delta: s += 'wait_{} '.format(delta) s += 'note_{}_{} '.format(note, duration) last_offset = offset # convert string back to midi from fractions import Fraction d = { '32nd': 1/32, '16th': 1/16, 'eighth': 1/8, 'quarter': 1/4, 'half': 1/2, 'whole': 1, } time = 0 stream = music21.stream.Stream() for i in s.split(): if i.startswith('note'): note, duration = i.lstrip('note_').split('_') n = music21.note.Note(int(note)) n.duration.type = duration stream.insertIntoNoteOrChord(time, n) elif i.startswith('wait'): duration = float(Fraction(i.lstrip('wait_'))) time += duration else: print('did not expect', i) stream.write('midi', fp='yeet.midi') # play the result stream.show('midi')