diff --git a/StarWars60.wav b/StarWars60.wav new file mode 100644 index 0000000..6e6f3a3 Binary files /dev/null and b/StarWars60.wav differ diff --git a/StarWars60.wav.mid b/StarWars60.wav.mid new file mode 100644 index 0000000..aae84d6 Binary files /dev/null and b/StarWars60.wav.mid differ diff --git a/__pycache__/averageAmplitude.cpython-38.pyc b/__pycache__/averageAmplitude.cpython-38.pyc new file mode 100644 index 0000000..4857ff2 Binary files /dev/null and b/__pycache__/averageAmplitude.cpython-38.pyc differ diff --git a/averageAmplitude.py b/averageAmplitude.py new file mode 100644 index 0000000..f2e47a3 --- /dev/null +++ b/averageAmplitude.py @@ -0,0 +1,13 @@ +import numpy as np +from scipy.signal import argrelextrema + +def averageAmplitude(numPyArray): +# for local maxima + listOfMaxs = numPyArray[argrelextrema(numPyArray, np.greater)[0]] + print(listOfMaxs) + average = sum(listOfMaxs) / len(listOfMaxs) + print (average) + return average + + + diff --git a/frequency.py b/frequency.py new file mode 100644 index 0000000..1a5d072 --- /dev/null +++ b/frequency.py @@ -0,0 +1,38 @@ +import numpy as np +import scipy.io.wavfile +import sys +from aubio import source, pitch + +win_s = 4096 +hop_s = 512 + +out = scipy.io.wavfile.read("StarWars60.wav", mmap=False) +a, b = out + +your_file = "StarWars60.wav" +samplerate = a + + +s = source(your_file, samplerate, hop_s) +samplerate = s.samplerate + +tolerance = 0.8 + +pitch_o = pitch("yin", win_s, hop_s, samplerate) +pitch_o.set_unit("midi") +pitch_o.set_tolerance(tolerance) + +pitches = [] +confidences = [] + +total_frames = 0 +while True: + samples, read = s() + pitch = pitch_o(samples)[0] + pitches += [pitch] + confidence = pitch_o.get_confidence() + confidences += [confidence] + total_frames += read + if read < hop_s: break + +print("Average frequency = " + str(np.array(pitches).mean()) + " hz") \ No newline at end of file diff --git a/keyFinder.py b/keyFinder.py new file mode 100644 index 0000000..4003f70 --- /dev/null +++ b/keyFinder.py @@ -0,0 +1,7 @@ +import music21 +import wav2midi2wav.audio_to_midi_melodia as a2m + +a2m.audio_to_midi_melodia('StarWars60.wav', 'StarWars60.mid', 120, smooth=0.25, minduration=0.1, savejams=False) +score = music21.converter.parse('StarWars60.mid') +key = score.analyze('key') +print(key.tonic.name, key.mode) \ No newline at end of file diff --git a/makeWaves.py b/makeWaves.py new file mode 100644 index 0000000..60e8d46 --- /dev/null +++ b/makeWaves.py @@ -0,0 +1,20 @@ +import averageAmplitude +import frequency +import math + +def makeWaves(pixels, squareDimension, amplitude, frequency): + dividedRowNum = squareDimension / 3 + for j in (0, squareDimension): + i = int(amplitude / 10 * math.sin(frequency * j) + dividedRowNum) + transparencyValue = pixels[i,j][3] + if (transparencyValue in range(129,255)): + transparencyValue = 129 + elif (transparencyValue in range(0,128)): + transparencyValue = 128 + + i = int(amplitude / 10 * math.sin(frequency * j) + dividedRowNum * 2) + transparencyValue = pixels[i,j][3] + if (transparencyValue in range(129,255)): + transparencyValue = 129 + elif (transparencyValue in range(0,128)): + transparencyValue = 128 \ No newline at end of file diff --git a/reverse2.wav b/reverse2.wav new file mode 100644 index 0000000..55b1459 Binary files /dev/null and b/reverse2.wav differ diff --git a/wav2midi2wav/README.md b/wav2midi2wav/README.md new file mode 100644 index 0000000..80b0c95 --- /dev/null +++ b/wav2midi2wav/README.md @@ -0,0 +1,59 @@ +### Wav to MIDI to Wav +Extracts the melody notes from an audio file and exports them to MIDI and JAMS files. + +The script extracts the melody from an audio file using the [Melodia](http://mtg.upf.edu/technologies/melodia) algorithm, and then segments the continuous pitch sequence into a series of quantized notes, and exports to MIDI using the provided BPM. + +### Usage +```bash +python main.py [--folder FOLDER_NAME] [--bpm BPM] [--smooth SMOOTH] [--minduration MINDURATION] [--jams] +``` +For example: +```bash +python main.py --folder assets/COGNIMUSE/ --bpm 146 --smooth 0.25 --minduration 0.1 --jams +``` + +### Notes +* *wav* to *mp3*: +``` +ffmpeg -i assets/test/10.wav assets/test/10.mp3 +``` +* Find out *bpm* (146 in my case): +``` +bpm-tag assets/test/10.mp3 +``` +* *wav* to *mid*: +``` +python audio_to_midi_melodia.py assets/test/10.wav assets/test/10.mid 146 --smooth 0.25 --minduration 0.1 --jams +``` +* *mid* to *wav* [converter](https://www.zamzar.com/convert/midi-to-wav/) +```bash +timidity 10.mid -Ow -o 10_recovered.wav +``` + +### Dependencies +* Install dependencies: +```bash +pip install vamp jams numpy scipy +``` +* Librosa +```bash +git clone https://github.com/librosa/librosa +cd dependencies/librosa +python setup.py build +python setup.py install +``` +* [MidiUtil](https://code.google.com/p/midiutil/) +```bash +cd dependencies/MIDIUtil-0.89 +python setup.py install +``` +* Download [Melodia plugin](http://mtg.upf.edu/technologies/melodia) and copy all files to */usr/local/lib/vamp* +* Audio tools +```bash +pip install pydub +apt-get install ffmpeg bpm-tools +apt-get install timidity timidity-interfaces-extra +``` + +### Credit +[audio_to_midi_melodia](https://github.com/justinsalamon/audio_to_midi_melodia) diff --git a/wav2midi2wav/__init__.py b/wav2midi2wav/__init__.py new file mode 100644 index 0000000..f98a91b --- /dev/null +++ b/wav2midi2wav/__init__.py @@ -0,0 +1,2 @@ +# CREATED: 11/11/15 11:22 AM by Justin Salamon +__version__ = '0.0.0' \ No newline at end of file diff --git a/wav2midi2wav/__pycache__/__init__.cpython-38.pyc b/wav2midi2wav/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..e7766d0 Binary files /dev/null and b/wav2midi2wav/__pycache__/__init__.cpython-38.pyc differ diff --git a/wav2midi2wav/__pycache__/audio_to_midi_melodia.cpython-38.pyc b/wav2midi2wav/__pycache__/audio_to_midi_melodia.cpython-38.pyc new file mode 100644 index 0000000..94704ae Binary files /dev/null and b/wav2midi2wav/__pycache__/audio_to_midi_melodia.cpython-38.pyc differ diff --git a/wav2midi2wav/assets/test/10.mid b/wav2midi2wav/assets/test/10.mid new file mode 100644 index 0000000..686991a Binary files /dev/null and b/wav2midi2wav/assets/test/10.mid differ diff --git a/wav2midi2wav/assets/test/10.wav b/wav2midi2wav/assets/test/10.wav new file mode 100644 index 0000000..4835085 Binary files /dev/null and b/wav2midi2wav/assets/test/10.wav differ diff --git a/wav2midi2wav/assets/test/10_recovered.wav b/wav2midi2wav/assets/test/10_recovered.wav new file mode 100644 index 0000000..c9cc110 Binary files /dev/null and b/wav2midi2wav/assets/test/10_recovered.wav differ diff --git a/wav2midi2wav/assets/test/14.mid b/wav2midi2wav/assets/test/14.mid new file mode 100644 index 0000000..1794f19 Binary files /dev/null and b/wav2midi2wav/assets/test/14.mid differ diff --git a/wav2midi2wav/assets/test/14.wav b/wav2midi2wav/assets/test/14.wav new file mode 100644 index 0000000..f1d89f8 Binary files /dev/null and b/wav2midi2wav/assets/test/14.wav differ diff --git a/wav2midi2wav/assets/test/14_recovered.wav b/wav2midi2wav/assets/test/14_recovered.wav new file mode 100644 index 0000000..78f50a5 Binary files /dev/null and b/wav2midi2wav/assets/test/14_recovered.wav differ diff --git a/wav2midi2wav/assets/test/23.mid b/wav2midi2wav/assets/test/23.mid new file mode 100644 index 0000000..e66ba7f Binary files /dev/null and b/wav2midi2wav/assets/test/23.mid differ diff --git a/wav2midi2wav/assets/test/23.wav b/wav2midi2wav/assets/test/23.wav new file mode 100644 index 0000000..3a53f2a Binary files /dev/null and b/wav2midi2wav/assets/test/23.wav differ diff --git a/wav2midi2wav/assets/test/23_recovered.wav b/wav2midi2wav/assets/test/23_recovered.wav new file mode 100644 index 0000000..f8ea895 Binary files /dev/null and b/wav2midi2wav/assets/test/23_recovered.wav differ diff --git a/wav2midi2wav/audio_to_midi_melodia.py b/wav2midi2wav/audio_to_midi_melodia.py new file mode 100644 index 0000000..4ca5523 --- /dev/null +++ b/wav2midi2wav/audio_to_midi_melodia.py @@ -0,0 +1,217 @@ +# CREATED: 11/9/15 3:57 PM by Justin Salamon + +import librosa +import vamp +import argparse +import os +import numpy as np +from midiutil.MidiFile import MIDIFile +from scipy.signal import medfilt +import jams +import wav2midi2wav + +''' +Extract the melody from an audio file and convert it to MIDI. + +The script extracts the melody from an audio file using the Melodia algorithm, +and then segments the continuous pitch sequence into a series of quantized +notes, and exports to MIDI using the provided BPM. If the --jams option is +specified the script will also save the output as a JAMS file. Note that the +JAMS file uses the original note onset/offset times estimated by the algorithm +and ignores the provided BPM value. + +Note: Melodia can work pretty well and is the result of several years of +research. The note segmentation/quantization code was hacked in about 30 +minutes. Proceed at your own risk... :) + +usage: audio_to_midi_melodia.py [-h] [--smooth SMOOTH] + [--minduration MINDURATION] [--jams] + infile outfile bpm + + +Examples: +python audio_to_midi_melodia.py --smooth 0.25 --minduration 0.1 --jams + ~/song.wav ~/song.mid 60 +''' + + +def save_jams(jamsfile, notes, track_duration, orig_filename): + + # Construct a new JAMS object and annotation records + jam = jams.JAMS() + + # Store the track duration + jam.file_metadata.duration = track_duration + jam.file_metadata.title = orig_filename + + midi_an = jams.Annotation(namespace='pitch_midi', + duration=track_duration) + midi_an.annotation_metadata = \ + jams.AnnotationMetadata( + data_source='audio_to_midi_melodia.py v%s' % __init__.__version__, + annotation_tools='audio_to_midi_melodia.py (https://github.com/' + 'justinsalamon/audio_to_midi_melodia)') + + # Add midi notes to the annotation record. + for n in notes: + midi_an.append(time=n[0], duration=n[1], value=n[2], confidence=0) + + # Store the new annotation in the jam + jam.annotations.append(midi_an) + + # Save to disk + jam.save(jamsfile) + + +def save_midi(outfile, notes, tempo): + + track = 0 + time = 0 + midifile = MIDIFile(1) + + # Add track name and tempo. + midifile.addTrackName(track, time, "MIDI TRACK") + midifile.addTempo(track, time, tempo) + + channel = 0 + volume = 100 + + for note in notes: + onset = note[0] * (tempo/60.) + duration = note[1] * (tempo/60.) + # duration = 1 + pitch = note[2] + midifile.addNote(track, channel, pitch, onset, duration, volume) + + # And write it to disk. + binfile = open(outfile, 'wb') + midifile.writeFile(binfile) + binfile.close() + + +def midi_to_notes(midi, fs, hop, smooth, minduration): + + # smooth midi pitch sequence first + if (smooth > 0): + filter_duration = smooth # in seconds + filter_size = int(filter_duration * fs / float(hop)) + if filter_size % 2 == 0: + filter_size += 1 + midi_filt = medfilt(midi, filter_size) + else: + midi_filt = midi + # print(len(midi),len(midi_filt)) + + notes = [] + p_prev = None + duration = 0 + onset = 0 + for n, p in enumerate(midi_filt): + if p == p_prev: + duration += 1 + else: + # treat 0 as silence + if p_prev > 0: + # add note + duration_sec = duration * hop / float(fs) + # only add notes that are long enough + if duration_sec >= minduration: + onset_sec = onset * hop / float(fs) + notes.append((onset_sec, duration_sec, p_prev)) + + # start new note + onset = n + duration = 1 + p_prev = p + + # add last note + if p_prev > 0: + # add note + duration_sec = duration * hop / float(fs) + onset_sec = onset * hop / float(fs) + notes.append((onset_sec, duration_sec, p_prev)) + + return notes + + +def hz2midi(hz): + + # convert from Hz to midi note + hz_nonneg = hz.copy() + idx = hz_nonneg <= 0 + hz_nonneg[idx] = 1 + midi = 69 + 12*np.log2(hz_nonneg/440.) + midi[idx] = 0 + + # round + midi = np.round(midi) + + return midi + + +def audio_to_midi_melodia(infile, outfile, bpm, smooth=0.25, minduration=0.1, + savejams=False): + + # define analysis parameters + fs = 44100 + hop = 128 + + # load audio using librosa + print("Loading audio...") + data, sr = librosa.load(infile, sr=fs, mono=True) + + # extract melody using melodia vamp plugin + print("Extracting melody f0 with MELODIA...") + melody = vamp.collect(data, sr, "mtg-melodia:melodia", + parameters={"voicing": 0.2}) + + # hop = melody['vector'][0] + pitch = melody['vector'][1] + + # impute missing 0's to compensate for starting timestamp + pitch = np.insert(pitch, 0, [0]*8) + + # debug + # np.asarray(pitch).dump('f0.npy') + # print(len(pitch)) + + # convert f0 to midi notes + print("Converting Hz to MIDI notes...") + midi_pitch = hz2midi(pitch) + + # segment sequence into individual midi notes + notes = midi_to_notes(midi_pitch, fs, hop, smooth, minduration) + + # save note sequence to a midi file + print("Saving MIDI to disk...") + save_midi(outfile, notes, bpm) + + if savejams: + print("Saving JAMS to disk...") + jamsfile = outfile.replace(".mid", ".jams") + track_duration = len(data) / float(fs) + save_jams(jamsfile, notes, track_duration, os.path.basename(infile)) + + print("Conversion complete.") + + +if __name__ == "__main__": + + parser = argparse.ArgumentParser() + parser.add_argument("infile", default="assets/10.wav", help="Path to input audio file.") + parser.add_argument("outfile", default="assets/10.mid", help="Path for saving output MIDI file.") + parser.add_argument("bpm", type=int, default=146, help="Tempo of the track in BPM.") + parser.add_argument("--smooth", type=float, default=0.25, + help="Smooth the pitch sequence with a median filter " + "of the provided duration (in seconds).") + parser.add_argument("--minduration", type=float, default=0.1, + help="Minimum allowed duration for note (in seconds). " + "Shorter notes will be removed.") + parser.add_argument("--jams", action="store_const", const=True, + default=False, help="Also save output in JAMS format.") + + args = parser.parse_args() + + audio_to_midi_melodia(args.infile, args.outfile, args.bpm, + smooth=args.smooth, minduration=args.minduration, + savejams=args.jams) diff --git a/wav2midi2wav/main_wav2mid2wav.py b/wav2midi2wav/main_wav2mid2wav.py new file mode 100644 index 0000000..cc0d960 --- /dev/null +++ b/wav2midi2wav/main_wav2mid2wav.py @@ -0,0 +1,121 @@ + +import os +import glob +import argparse +from timeit import default_timer as timer +from pydub import AudioSegment + + +if __name__ == '__main__': + + parser = argparse.ArgumentParser() + # parser.add_argument("--folder", default="assets/splices_audio_BMI/", help="Path to input audio file.") + parser.add_argument("--folder", default="assets/COGNIMUSE/", help="Path to input audio file.") + parser.add_argument("--bpm", type=int, default=146, help="Tempo of the track in BPM.") + parser.add_argument("--smooth", type=float, default=0.25, + help="Smooth the pitch sequence with a median filter " + "of the provided duration (in seconds).") + parser.add_argument("--minduration", type=float, default=0.1, + help="Minimum allowed duration for note (in seconds). " + "Shorter notes will be removed.") + parser.add_argument("--jams", action="store_const", const=True, + default=False, help="Also save output in JAMS format.") + + args = parser.parse_args() + + init_time = timer() + + FOLDER = args.folder + "*" + + IFS = ".wav" # delimiter + IFS_CONVERT = ".mp3" + BPM = args.bpm + size_audio_files = 3 # in seconds + + subdirs = glob.glob(FOLDER) + # subdirs = [args.folder+'audio'] + #for sub in subdirs: + # print(sub) + # files = glob.glob(sub + "/*{}".format(IFS)) + # f = files[0] + # sound = AudioSegment.from_file(f) + # size_audio_files = len(sound) + + subdirs_final = [] + for sub in subdirs: + print(sub) + sub_folder = sub.split('/')[-1] + #print(sub_folder) + files = glob.glob(sub+"/*{}".format(IFS)) + for f in files: + f_base = f.split(IFS)[0] + filename = sub_folder + '_' + f_base.split('/')[-1] + print(f_base) + f_out = f.replace(IFS, IFS_CONVERT) + + # wav to mid + mid_dir = '{}_mid/'.format(sub) + if not os.path.exists(mid_dir): + os.mkdir(mid_dir) + + command_str = 'python audio_to_midi_melodia.py {f_base}{ifs} {mid_dir}{filename}.mid {bpm} --smooth ' \ + '{smooth} --minduration {mindur}'.format(f_base=f_base, bpm=BPM, ifs=IFS, + smooth=args.smooth, mindur=args.minduration, + mid_dir=mid_dir, filename=filename) + if args.jams: + command_str += ' --jams' + os.system(command_str) + + # mid to wav + rec_dir = '{}_rec/'.format(sub) + if not os.path.exists(rec_dir): + os.mkdir(rec_dir) + os.system('timidity {mid_dir}{filename}.mid -Ow -o {rec_dir}{filename}_rec.wav'. + format(f_base=f_base, filename=filename, rec_dir=rec_dir, mid_dir=mid_dir)) + + # change sample rate to 16000, 1 channel, 16 bits + sr_dir = '{}_16000_c1_16bits_music'.format(sub) + subdirs_final.append(sr_dir) + if not os.path.exists(sr_dir): + os.mkdir(sr_dir) + os.system('sox {rec_dir}{filename}_rec.wav -c1 -b16 -r16000 {sr_dir}/{filename}.wav'. + format(filename=filename, rec_dir=rec_dir, sr_dir=sr_dir)) + + subdirs = subdirs_final # [args.folder+'audio_16000_c1_16bits_music'] + ## Normalize data length (all audio samples must have the same duration) + #num_chunks = float("inf") + #for sub in subdirs: + # files = glob.glob("{}/*{}".format(sub, IFS)) + # for f in files: + # sound = AudioSegment.from_file(f) + # sound_chunks = len(sound) + # print("f: {}, {}, {}, {}".format(sound.rms, sound_chunks, sound.frame_rate, sound.frame_count())) + # if num_chunks > sound_chunks: + # num_chunks = sound_chunks + #print("num_chunks: {}".format(num_chunks)) + + splice_seconds = size_audio_files * 1000 # ms + for sub in subdirs: + files = glob.glob("{}/*{}".format(sub, IFS)) + sr_dir = '{}_eq/'.format(sub) + if not os.path.exists(sr_dir): + os.mkdir(sr_dir) + for f in files: + f_base = f.split(IFS)[0] + filename = f_base.split('/')[-1] + + sound = AudioSegment.from_file(f) + sound_chunks = len(sound) + + first_x_seconds = sound[:splice_seconds] + first_x_seconds.export("{}{}.wav".format(sr_dir, filename), format="wav") + + #chunk_size = int(len(sound) / num_chunks # ms)) + + #loudness_over_time = [] + #for i in range(0, len(sound), chunk_size): + # chunk = sound[i:i + chunk_size] + #loudness_over_time.append(chunk.rms) + + end_time = timer() + print("Program took {} minutes".format((end_time-init_time)/60)) # COGNIMUSE: 47 minutes diff --git a/wav2midi2wav/version.py b/wav2midi2wav/version.py new file mode 100644 index 0000000..4950d9b --- /dev/null +++ b/wav2midi2wav/version.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +"""Version info""" + +short_version = '0.0' +version = '0.0.2' diff --git a/wavToPng.py b/wavToPng.py index 6cc7796..70e821d 100644 --- a/wavToPng.py +++ b/wavToPng.py @@ -1,9 +1,11 @@ import scipy.io.wavfile import math from PIL import Image +import averageAmplitude import numpy as np import matplotlib.pyplot as plt def sigmoid(x): # condense variables down to 0