├── .gitignore ├── add_blips ├── add_blips.py └── sounds │ ├── blip_high.wav │ ├── blip_low.wav │ └── blip_med.wav ├── afromb └── afromb.py ├── capsule ├── README.txt ├── capsule.py ├── capsule_support.py └── utils.py ├── cowbell ├── cowbell.py └── sounds │ ├── cowbell0.wav │ ├── cowbell1.wav │ ├── cowbell2.wav │ ├── cowbell3.wav │ ├── cowbell4.wav │ ├── trill.wav │ ├── walken0.wav │ ├── walken1.wav │ ├── walken10.wav │ ├── walken11.wav │ ├── walken12.wav │ ├── walken13.wav │ ├── walken14.wav │ ├── walken15.wav │ ├── walken2.wav │ ├── walken3.wav │ ├── walken4.wav │ ├── walken5.wav │ ├── walken6.wav │ ├── walken7.wav │ ├── walken8.wav │ └── walken9.wav ├── drums ├── breaks │ ├── AmenBrother.mp3 │ ├── GiveituporTurnitLoose.mp3 │ └── assemblyline.mp3 └── drums.py ├── earworm ├── README.txt ├── earworm.py ├── earworm_support.py └── utils.py ├── filter └── filter.py ├── jingler ├── jingler.py └── sounds │ ├── signalBell0.wav │ ├── signalBell1.wav │ ├── signalBell2.wav │ ├── sleighBell0.wav │ ├── sleighBell1.wav │ └── snowSanta0.wav ├── limp └── lopside.py ├── midi └── enToMIDI.py ├── multi ├── multitest.py └── multivideo.py ├── music ├── Hiiragi_Fukuda-Open_Fields_Blues.mp3 ├── Karl_Blau-Gnos_Levohs.mp3 ├── Raleigh_Moncrief-Guppies.mp3 ├── Tracky_Birthday-Newish_Disco.mp3 └── attribution.txt ├── one └── one.py ├── quanta └── quanta.py ├── readme.md ├── reverse └── reverse.py ├── rhythmstring └── rhythmstring.py ├── save └── save.py ├── selection └── tonic.py ├── sorting ├── sorting.py ├── sorting_pitch.py └── sorting_timbre.py ├── step ├── step-by-pitch.py ├── step-by-section.py └── step.py ├── stretch ├── beatshift.py ├── cycle_dirac.py ├── cycle_soundtouch.py └── simple_stretch.py ├── summary └── summary.py ├── swinger └── swinger.py ├── videolizer ├── videolizer.py └── videos │ ├── soultrain1.11.0.mp4 │ ├── soultrain2.18.0.mp4 │ └── soultrain3.1.120.mp4 ├── videx ├── vafroma.py ├── vafroma2.py ├── vafroma3.py ├── vafromb.py ├── vdissoc.py ├── vone.py └── vreverse.py └── waltzify └── waltzify.py /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /add_blips/add_blips.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf=8 3 | # 4 | # by Douglas Repetto, 10 June 2009 5 | # plus code from various other examples and fixes by remix devs 6 | 7 | """ 8 | add_blips.py 9 | 10 | Add a blip to any combination of each tatum/beat/bar in a song. 11 | 12 | """ 13 | import sys 14 | import os.path 15 | import numpy 16 | 17 | import echonest.remix.audio as audio 18 | 19 | usage=""" 20 | Usage: 21 | python add_blips.py [tatums] [beats] [bars] 22 | 23 | where 24 | tatums == add blips to tatums 25 | beats == add blips to beats (default) 26 | bars == add blips to bars 27 | 28 | Example: 29 | python add_blips.py bootsy.mp3 bootsy_blips.mp3 beats bars 30 | 31 | """ 32 | 33 | blip_filenames = ('sounds/blip_low.wav', 'sounds/blip_med.wav', 'sounds/blip_high.wav') 34 | 35 | #the blip.wav files are stored in the sounds/ directory relative to the 36 | #script. if the script is run from another directory those sounds won't 37 | #be found. this fixes that problem. 38 | prefix = os.path.dirname(os.path.abspath(sys.argv[0])) 39 | blip_filenames = map(lambda x: os.path.join(prefix, x), blip_filenames) 40 | 41 | def main(input_filename, output_filename, tatums, beats, bars): 42 | 43 | audiofile = audio.LocalAudioFile(input_filename) 44 | num_channels = audiofile.numChannels 45 | sample_rate = audiofile.sampleRate 46 | 47 | # mono files have a shape of (len,) 48 | out_shape = list(audiofile.data.shape) 49 | out_shape[0] = len(audiofile) 50 | out = audio.AudioData(shape=out_shape, sampleRate=sample_rate,numChannels=num_channels) 51 | 52 | # same hack to change shape: we want blip_files[0] as a short, silent blip 53 | null_shape = list(audiofile.data.shape) 54 | null_shape[0] = 2 55 | null_audio = audio.AudioData(shape=null_shape) 56 | null_audio.endindex = len(null_audio) 57 | 58 | low_blip = audio.AudioData(blip_filenames[0]) 59 | med_blip = audio.AudioData(blip_filenames[1]) 60 | high_blip = audio.AudioData(blip_filenames[2]) 61 | 62 | all_tatums = audiofile.analysis.tatums 63 | all_beats = audiofile.analysis.beats 64 | all_bars = audiofile.analysis.bars 65 | 66 | if not all_tatums: 67 | print "Didn't find any tatums in this analysis!" 68 | print "No output." 69 | sys.exit(-1) 70 | 71 | print "going to add blips..." 72 | 73 | for tatum in all_tatums: 74 | mix_list = [audiofile[tatum], null_audio, null_audio, null_audio] 75 | if tatums: 76 | print "match! tatum start time:" + str(tatum.start) 77 | mix_list[1] = low_blip 78 | 79 | if beats: 80 | for beat in all_beats: 81 | if beat.start == tatum.start: 82 | print "match! beat start time: " + str(beat.start) 83 | mix_list[2] = med_blip 84 | break 85 | 86 | if bars: 87 | for bar in all_bars: 88 | if bar.start == tatum.start: 89 | print "match! bar start time: " + str(bar.start) 90 | mix_list[3] = high_blip 91 | break 92 | out_data = audio.megamix(mix_list) 93 | out.append(out_data) 94 | del(out_data) 95 | print "blips added, going to encode", output_filename, "..." 96 | out.encode(output_filename) 97 | print "Finito, Benito!" 98 | 99 | 100 | if __name__=='__main__': 101 | tatums = False 102 | beats = False 103 | bars = False 104 | try: 105 | input_filename = sys.argv[1] 106 | output_filename = sys.argv[2] 107 | if len(sys.argv) == 3: 108 | bars = 1 109 | print "blipping bars by default." 110 | for arg in sys.argv[3:len(sys.argv)]: 111 | if arg == "tatums": 112 | tatums = True 113 | print "blipping tatums." 114 | if arg == "beats": 115 | beats = True 116 | print "blipping beats." 117 | if arg == "bars": 118 | bars = True 119 | print "blipping bars." 120 | except: 121 | print usage 122 | sys.exit(-1) 123 | main(input_filename, output_filename, tatums, beats, bars) 124 | -------------------------------------------------------------------------------- /add_blips/sounds/blip_high.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/echonest/remix-examples/05e190069e0b25baef9a4259267acfa34f2ed913/add_blips/sounds/blip_high.wav -------------------------------------------------------------------------------- /add_blips/sounds/blip_low.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/echonest/remix-examples/05e190069e0b25baef9a4259267acfa34f2ed913/add_blips/sounds/blip_low.wav -------------------------------------------------------------------------------- /add_blips/sounds/blip_med.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/echonest/remix-examples/05e190069e0b25baef9a4259267acfa34f2ed913/add_blips/sounds/blip_med.wav -------------------------------------------------------------------------------- /afromb/afromb.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf=8 3 | 4 | """ 5 | afromb.py 6 | 7 | Re-synthesize song A using the segments of song B. 8 | 9 | By Ben Lacker, 2009-02-24. 10 | """ 11 | import numpy 12 | import sys 13 | import time 14 | import echonest.remix.audio as audio 15 | 16 | usage=""" 17 | Usage: 18 | python afromb.py [env] 19 | 20 | Example: 21 | python afromb.py BillieJean.mp3 CryMeARiver.mp3 BillieJeanFromCryMeARiver.mp3 0.9 env 22 | 23 | The 'env' flag applies the volume envelopes of the segments of A to those 24 | from B. 25 | 26 | Mix is a number 0-1 that determines the relative mix of the resynthesized 27 | song and the original input A. i.e. a mix value of 0.9 yields an output that 28 | is mostly the resynthesized version. 29 | 30 | """ 31 | 32 | class AfromB(object): 33 | def __init__(self, input_filename_a, input_filename_b, output_filename): 34 | self.input_a = audio.LocalAudioFile(input_filename_a) 35 | self.input_b = audio.LocalAudioFile(input_filename_b) 36 | self.segs_a = self.input_a.analysis.segments 37 | self.segs_b = self.input_b.analysis.segments 38 | self.output_filename = output_filename 39 | 40 | def calculate_distances(self, a): 41 | distance_matrix = numpy.zeros((len(self.segs_b), 4), 42 | dtype=numpy.float32) 43 | pitch_distances = [] 44 | timbre_distances = [] 45 | loudmax_distances = [] 46 | for b in self.segs_b: 47 | pitch_diff = numpy.subtract(b.pitches,a.pitches) 48 | pitch_distances.append(numpy.sum(numpy.square(pitch_diff))) 49 | timbre_diff = numpy.subtract(b.timbre,a.timbre) 50 | timbre_distances.append(numpy.sum(numpy.square(timbre_diff))) 51 | loudmax_diff = b.loudness_begin - a.loudness_begin 52 | loudmax_distances.append(numpy.square(loudmax_diff)) 53 | distance_matrix[:,0] = pitch_distances 54 | distance_matrix[:,1] = timbre_distances 55 | distance_matrix[:,2] = loudmax_distances 56 | distance_matrix[:,3] = range(len(self.segs_b)) 57 | distance_matrix = self.normalize_distance_matrix(distance_matrix) 58 | return distance_matrix 59 | 60 | def normalize_distance_matrix(self, mat, mode='minmed'): 61 | """ Normalize a distance matrix on a per column basis. 62 | """ 63 | if mode == 'minstd': 64 | mini = numpy.min(mat,0) 65 | m = numpy.subtract(mat, mini) 66 | std = numpy.std(mat,0) 67 | m = numpy.divide(m, std) 68 | m = numpy.divide(m, mat.shape[1]) 69 | elif mode == 'minmed': 70 | mini = numpy.min(mat,0) 71 | m = numpy.subtract(mat, mini) 72 | med = numpy.median(m) 73 | m = numpy.divide(m, med) 74 | m = numpy.divide(m, mat.shape[1]) 75 | elif mode == 'std': 76 | std = numpy.std(mat,0) 77 | m = numpy.divide(mat, std) 78 | m = numpy.divide(m, mat.shape[1]) 79 | return m 80 | 81 | def run(self, mix=0.5, envelope=False): 82 | dur = len(self.input_a.data) + 100000 # another two seconds 83 | # determine shape of new array 84 | if len(self.input_a.data.shape) > 1: 85 | new_shape = (dur, self.input_a.data.shape[1]) 86 | new_channels = self.input_a.data.shape[1] 87 | else: 88 | new_shape = (dur,) 89 | new_channels = 1 90 | out = audio.AudioData(shape=new_shape, 91 | sampleRate=self.input_b.sampleRate, 92 | numChannels=new_channels) 93 | for a in self.segs_a: 94 | seg_index = a.absolute_context()[0] 95 | # find best match from segs in B 96 | distance_matrix = self.calculate_distances(a) 97 | distances = [numpy.sqrt(x[0]+x[1]+x[2]) for x in distance_matrix] 98 | match = self.segs_b[distances.index(min(distances))] 99 | segment_data = self.input_b[match] 100 | reference_data = self.input_a[a] 101 | if segment_data.endindex < reference_data.endindex: 102 | if new_channels > 1: 103 | silence_shape = (reference_data.endindex,new_channels) 104 | else: 105 | silence_shape = (reference_data.endindex,) 106 | new_segment = audio.AudioData(shape=silence_shape, 107 | sampleRate=out.sampleRate, 108 | numChannels=segment_data.numChannels) 109 | new_segment.append(segment_data) 110 | new_segment.endindex = len(new_segment) 111 | segment_data = new_segment 112 | elif segment_data.endindex > reference_data.endindex: 113 | index = slice(0, int(reference_data.endindex), 1) 114 | segment_data = audio.AudioData(None,segment_data.data[index], 115 | sampleRate=segment_data.sampleRate) 116 | if envelope: 117 | # db -> voltage ratio http://www.mogami.com/e/cad/db.html 118 | linear_max_volume = pow(10.0,a.loudness_max/20.0) 119 | linear_start_volume = pow(10.0,a.loudness_begin/20.0) 120 | if(seg_index == len(self.segs_a)-1): # if this is the last segment 121 | linear_next_start_volume = 0 122 | else: 123 | linear_next_start_volume = pow(10.0,self.segs_a[seg_index+1].loudness_begin/20.0) 124 | pass 125 | when_max_volume = a.time_loudness_max 126 | # Count # of ticks I wait doing volume ramp so I can fix up rounding errors later. 127 | ss = 0 128 | # Set volume of this segment. Start at the start volume, ramp up to the max volume , then ramp back down to the next start volume. 129 | cur_vol = float(linear_start_volume) 130 | # Do the ramp up to max from start 131 | samps_to_max_loudness_from_here = int(segment_data.sampleRate * when_max_volume) 132 | if(samps_to_max_loudness_from_here > 0): 133 | how_much_volume_to_increase_per_samp = float(linear_max_volume - linear_start_volume)/float(samps_to_max_loudness_from_here) 134 | for samps in xrange(samps_to_max_loudness_from_here): 135 | try: 136 | segment_data.data[ss] *= cur_vol 137 | except IndexError: 138 | pass 139 | cur_vol = cur_vol + how_much_volume_to_increase_per_samp 140 | ss = ss + 1 141 | # Now ramp down from max to start of next seg 142 | samps_to_next_segment_from_here = int(segment_data.sampleRate * (a.duration-when_max_volume)) 143 | if(samps_to_next_segment_from_here > 0): 144 | how_much_volume_to_decrease_per_samp = float(linear_max_volume - linear_next_start_volume)/float(samps_to_next_segment_from_here) 145 | for samps in xrange(samps_to_next_segment_from_here): 146 | cur_vol = cur_vol - how_much_volume_to_decrease_per_samp 147 | try: 148 | segment_data.data[ss] *= cur_vol 149 | except IndexError: 150 | pass 151 | ss = ss + 1 152 | mixed_data = audio.mix(segment_data,reference_data,mix=mix) 153 | out.append(mixed_data) 154 | out.encode(self.output_filename) 155 | 156 | def main(): 157 | try: 158 | input_filename_a = sys.argv[1] 159 | input_filename_b = sys.argv[2] 160 | output_filename = sys.argv[3] 161 | mix = sys.argv[4] 162 | if len(sys.argv) == 6: 163 | env = True 164 | else: 165 | env = False 166 | except: 167 | print usage 168 | sys.exit(-1) 169 | AfromB(input_filename_a, input_filename_b, output_filename).run(mix=mix, 170 | envelope=env) 171 | 172 | if __name__=='__main__': 173 | tic = time.time() 174 | main() 175 | toc = time.time() 176 | print "Elapsed time: %.3f sec" % float(toc-tic) 177 | -------------------------------------------------------------------------------- /capsule/README.txt: -------------------------------------------------------------------------------- 1 | == capsule.py == 2 | 3 | capsule.py beat matches parts of several songs into a single mp3. Given a transition duration, and an inter-transition duration, the program searches for the best order, the closest transition patterns, and apply beat alignment, time-stretching and crossfading into a single audio stream output. The result is a music collage with smooth transitions acting as the glue. Note that beat alignment is independent of downbeats during pattern search: it is optimal in the sense of timbral pattern matching, which may be perceptually tricky in the case of different styles. Therefore downbeats (first beat of the bar) don't necessarily align. Transitions can start anywhere in the track, and for any duration: it is up to the developer to apply additional constraints on top of the core engine of capsule to create less predictable and more interesting results. 4 | 5 | A version of Capsule powers http://2012.jamodyssey.com/ 6 | 7 | 8 | == Usage == 9 | 10 | Usage: capsule.py [options] 11 | 12 | Options: 13 | -h, --help show this help message and exit 14 | -t TRANSITION, --transition=TRANSITION 15 | transition (in seconds) default=8 16 | -i INTER, --inter=INTER 17 | section that's not transitioning (in seconds) 18 | default=8 19 | -o, --order automatically order tracks 20 | -e, --equalize automatically adjust volumes 21 | -v, --verbose show results on screen 22 | -p PDB, --pdb=PDB dummy; here for not crashing when using nose 23 | 24 | 25 | == Examples == 26 | 27 | After installing your Echo Nest API Key, try the following in your terminal: 28 | $ python capsule.py ../music/Raleigh_Moncrief-Guppies.mp3 ../music/Hiiragi_Fukuda-Open_Fields_Blues.mp3 29 | It'll combine into capsule.mp3, 8 seconds (default) of Guppies with 8 seconds of Open_Fields_Blues via 8 seconds (default) of beat-matched transition. 30 | 31 | To change the transition and inter transition paramaters use the options -t and -i, e.g., 32 | $ python capsule.py -t 4 -i 20 ../music/Raleigh_Moncrief-Guppies.mp3 ../music/Hiiragi_Fukuda-Open_Fields_Blues.mp3 33 | makes a quicker 4-second transition with longer 20-second inter-transition excerpts of the tracks. 34 | Note that every track that doesn't fit the parameter constraints will simply be rejected from the mix. 35 | 36 | Option -o allows you to automatically order the tracks, currently by tempo: 37 | $ python capsule.py -o ../music/Raleigh_Moncrief-Guppies.mp3 ../music/Hiiragi_Fukuda-Open_Fields_Blues.mp3 38 | plays EverythingIsOnTheOne.mp3 first. 39 | 40 | Option -v allows you to see details about what is going on during computation. 41 | $ python capsule.py -o -v ../music/Raleigh_Moncrief-Guppies.mp3 ../music/Hiiragi_Fukuda-Open_Fields_Blues.mp3 42 | displays the following time of action, action name, time parameters, duration, and title. 43 | 44 | 00:00 Fade in 100.421 -> 100.671 (0.250) Guppies.mp3 45 | 00:00 Playback 100.671 -> 108.671 (8.000) Guppies.mp3 46 | 00:08 Crossmatch 108.671 -> 232.107 (7.502) Guppies.mp3 -> Open_Fields_Blues.mp3 47 | 00:15 Playback 232.107 -> 240.297 (8.190) Open_Fields_Blues.mp3 48 | 00:23 Fade out 240.297 -> 246.297 (6.000) Open_Fields_Blues.mp3 49 | 50 | Note that every capsule starts with a 250 ms quick fade in and ends with a 6-second fade out. 51 | 52 | With option -e you can equalize the relative volume between tracks. 53 | $ python capsule.py -o -e -v ../music/Raleigh_Moncrief-Guppies.mp3../music/Hiiragi_Fukuda-Open_Fields_Blues.mp3 54 | pushes the gain of the first track by 33% and the second track by 10% 55 | 56 | Vol = 133% Raleigh_Moncrief-Guppies.mp3 57 | Vol = 110% Hiiragi_Fukuda-Open_Fields_Blues.mp3 58 | -------------------------------------------------------------------------------- /capsule/capsule.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf=8 3 | 4 | """ 5 | capsule.py 6 | 7 | accepts songs on the commandline, order them, beatmatch them, and output an audio file 8 | 9 | Created by Tristan Jehan and Jason Sundram. 10 | """ 11 | 12 | import os 13 | import sys 14 | from optparse import OptionParser 15 | 16 | from echonest.remix.action import render, make_stereo 17 | from echonest.remix.audio import LocalAudioFile 18 | from pyechonest import util 19 | 20 | from capsule_support import order_tracks, equalize_tracks, resample_features, timbre_whiten, initialize, make_transition, terminate, FADE_OUT, display_actions, is_valid 21 | 22 | 23 | def tuples(l, n=2): 24 | """ returns n-tuples from l. 25 | e.g. tuples(range(4), n=2) -> [(0, 1), (1, 2), (2, 3)] 26 | """ 27 | return zip(*[l[i:] for i in range(n)]) 28 | 29 | def do_work(audio_files, options): 30 | 31 | inter = float(options.inter) 32 | trans = float(options.transition) 33 | order = bool(options.order) 34 | equal = bool(options.equalize) 35 | verbose = bool(options.verbose) 36 | 37 | # Get pyechonest/remix objects 38 | analyze = lambda x : LocalAudioFile(x, verbose=verbose, sampleRate = 44100, numChannels = 2) 39 | tracks = map(analyze, audio_files) 40 | 41 | # decide on an initial order for those tracks 42 | if order == True: 43 | if verbose: print "Ordering tracks..." 44 | tracks = order_tracks(tracks) 45 | 46 | if equal == True: 47 | equalize_tracks(tracks) 48 | if verbose: 49 | print 50 | for track in tracks: 51 | print "Vol = %.0f%%\t%s" % (track.gain*100.0, track.filename) 52 | print 53 | 54 | valid = [] 55 | # compute resampled and normalized matrices 56 | for track in tracks: 57 | if verbose: print "Resampling features for", track.filename 58 | track.resampled = resample_features(track, rate='beats') 59 | track.resampled['matrix'] = timbre_whiten(track.resampled['matrix']) 60 | # remove tracks that are too small 61 | if is_valid(track, inter, trans): 62 | valid.append(track) 63 | # for compatibility, we make mono tracks stereo 64 | track = make_stereo(track) 65 | tracks = valid 66 | 67 | if len(tracks) < 1: return [] 68 | # Initial transition. Should contain 2 instructions: fadein, and playback. 69 | if verbose: print "Computing transitions..." 70 | start = initialize(tracks[0], inter, trans) 71 | 72 | # Middle transitions. Should each contain 2 instructions: crossmatch, playback. 73 | middle = [] 74 | [middle.extend(make_transition(t1, t2, inter, trans)) for (t1, t2) in tuples(tracks)] 75 | 76 | # Last chunk. Should contain 1 instruction: fadeout. 77 | end = terminate(tracks[-1], FADE_OUT) 78 | 79 | return start + middle + end 80 | 81 | def get_options(warn=False): 82 | usage = "usage: %s [options] " % sys.argv[0] 83 | parser = OptionParser(usage=usage) 84 | parser.add_option("-t", "--transition", default=8, help="transition (in seconds) default=8") 85 | parser.add_option("-i", "--inter", default=8, help="section that's not transitioning (in seconds) default=8") 86 | parser.add_option("-o", "--order", action="store_true", help="automatically order tracks") 87 | parser.add_option("-e", "--equalize", action="store_true", help="automatically adjust volumes") 88 | parser.add_option("-v", "--verbose", action="store_true", help="show results on screen") 89 | parser.add_option("-p", "--pdb", default=True, help="dummy; here for not crashing when using nose") 90 | 91 | (options, args) = parser.parse_args() 92 | if warn and len(args) < 2: 93 | parser.print_help() 94 | return (options, args) 95 | 96 | def main(): 97 | options, args = get_options(warn=True); 98 | actions = do_work(args, options) 99 | verbose = bool(options.verbose) 100 | 101 | if verbose: 102 | display_actions(actions) 103 | print "Output Duration = %.3f sec" % sum(act.duration for act in actions) 104 | 105 | print "Rendering..." 106 | # Send to renderer 107 | render(actions, 'capsule.mp3', verbose) 108 | return 1 109 | 110 | if __name__ == "__main__": 111 | main() 112 | # for profiling, do this: 113 | #import cProfile 114 | #cProfile.run('main()', 'capsule_prof') 115 | # then in ipython: 116 | #import pstats 117 | #p = pstats.Stats('capsule_prof') 118 | #p.sort_stats('cumulative').print_stats(30) 119 | -------------------------------------------------------------------------------- /capsule/capsule_support.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf=8 3 | 4 | """ 5 | capsule_support.py 6 | 7 | Created by Tristan Jehan and Jason Sundram. 8 | """ 9 | 10 | import numpy as np 11 | from copy import deepcopy 12 | from echonest.remix.action import Crossfade, Playback, Crossmatch, Fadein, Fadeout, humanize_time 13 | from utils import rows, flatten 14 | 15 | # constants for now 16 | X_FADE = 3 17 | FADE_IN = 0.25 18 | FADE_OUT = 6 19 | MIN_SEARCH = 4 20 | MIN_MARKERS = 2 21 | MIN_ALIGN_DURATION = 3 22 | LOUDNESS_THRESH = -8 23 | FUSION_INTERVAL = .06 # this is what we use in the analyzer 24 | AVG_PEAK_OFFSET = 0.025 # Estimated time between onset and peak of segment. 25 | 26 | # TODO: this should probably be in actions? 27 | def display_actions(actions): 28 | total = 0 29 | print 30 | for a in actions: 31 | print "%s\t %s" % (humanize_time(total), unicode(a)) 32 | total += a.duration 33 | print 34 | 35 | def evaluate_distance(mat1, mat2): 36 | return np.linalg.norm(mat1.flatten() - mat2.flatten()) 37 | 38 | def upsample_matrix(m): 39 | """ Upsample matrices by a factor of 2.""" 40 | r, c = m.shape 41 | out = np.zeros((2*r, c), dtype=np.float32) 42 | for i in xrange(r): 43 | out[i*2 , :] = m[i, :] 44 | out[i*2+1, :] = m[i, :] 45 | return out 46 | 47 | def upsample_list(l, rate=2): 48 | """ Upsample lists by a factor of 2.""" 49 | if rate != 2: return l[:] 50 | # Assume we're an AudioQuantumList. 51 | def split(x): 52 | a = deepcopy(x) 53 | a.duration = x.duration / 2 54 | b = deepcopy(a) 55 | b.start = x.start + a.duration 56 | return a, b 57 | 58 | return flatten(map(split, l)) 59 | 60 | def average_duration(l): 61 | return sum([i.duration for i in l]) / float(len(l)) 62 | 63 | def align(track1, track2, mat1, mat2): 64 | """ Constrained search between a settled section and a new section. 65 | Outputs location in mat2 and the number of rows used in the transition. 66 | """ 67 | # Get the average marker duration. 68 | marker1 = average_duration(getattr(track1.analysis, track1.resampled['rate'])[track1.resampled['index']:track1.resampled['index']+rows(mat1)]) 69 | marker2 = average_duration(getattr(track2.analysis, track2.resampled['rate'])[track2.resampled['index']:track2.resampled['index']+rows(mat2)]) 70 | 71 | def get_adjustment(tr1, tr2): 72 | """Update tatum rate if necessary""" 73 | dist = np.log2(tr1 / tr2) 74 | if dist < -0.5: return (1, 2) 75 | elif dist > 0.5: return (2, 1) 76 | else: return (1, 1) 77 | 78 | rate1, rate2 = get_adjustment(marker1, marker2) 79 | if rate1 == 2: mat1 = upsample_matrix(mat1) 80 | if rate2 == 2: mat2 = upsample_matrix(mat2) 81 | 82 | # Update sizes. 83 | rows2 = rows(mat2) 84 | rows1 = min( rows(mat1), max(rows2 - MIN_SEARCH, MIN_MARKERS)) # at least the best of MIN_SEARCH choices 85 | 86 | # Search for minimum. 87 | def dist(i): 88 | return evaluate_distance(mat1[0:rows1,:], mat2[i:i+rows1,:]) 89 | 90 | min_loc = min(xrange(rows2 - rows1), key=dist) 91 | min_val = dist(min_loc) 92 | 93 | # Let's make sure track2 ends its transition on a regular tatum. 94 | if rate2 == 2 and (min_loc + rows1) & 1: 95 | rows1 -= 1 96 | 97 | return min_loc, rows1, rate1, rate2 98 | 99 | def equalize_tracks(tracks): 100 | 101 | def db_2_volume(loudness): 102 | return (1.0 - LOUDNESS_THRESH * (LOUDNESS_THRESH - loudness) / 100.0) 103 | 104 | for track in tracks: 105 | loudness = track.analysis.loudness 106 | track.gain = db_2_volume(loudness) 107 | 108 | def order_tracks(tracks): 109 | """ Finds the smoothest ordering between tracks, based on tempo only.""" 110 | tempos = [track.analysis.tempo['value'] for track in tracks] 111 | median = np.median(tempos) 112 | def fold(t): 113 | q = np.log2(t / median) 114 | if q < -.5: return t * 2.0 115 | elif q > .5: return t / 2.0 116 | else: return t 117 | 118 | new_tempos = map(fold, tempos) 119 | order = np.argsort(new_tempos) 120 | return [tracks[i] for i in order] 121 | 122 | def is_valid(track, inter, transition): 123 | markers = getattr(track.analysis, track.resampled['rate']) 124 | if len(markers) < 1: 125 | dur = track.duration 126 | else: 127 | dur = markers[-1].start + markers[-1].duration - markers[0].start 128 | return inter + 2 * transition < dur 129 | 130 | def get_central(analysis, member='segments'): 131 | """ Returns a tuple: 132 | 1) copy of the members (e.g. segments) between end_of_fade_in and start_of_fade_out. 133 | 2) the index of the first retained member. 134 | """ 135 | def central(s): 136 | return analysis.end_of_fade_in <= s.start and (s.start + s.duration) < analysis.start_of_fade_out 137 | 138 | members = getattr(analysis, member) # this is nicer than data.__dict__[member] 139 | ret = filter(central, members[:]) 140 | index = members.index(ret[0]) if ret else 0 141 | 142 | return ret, index 143 | 144 | def get_mean_offset(segments, markers): 145 | if segments == markers: 146 | return 0 147 | 148 | index = 0 149 | offsets = [] 150 | try: 151 | for marker in markers: 152 | while segments[index].start < marker.start + FUSION_INTERVAL: 153 | offset = abs(marker.start - segments[index].start) 154 | if offset < FUSION_INTERVAL: 155 | offsets.append(offset) 156 | index += 1 157 | except IndexError, e: 158 | pass 159 | 160 | return np.average(offsets) if offsets else AVG_PEAK_OFFSET 161 | 162 | def resample_features(data, rate='tatums', feature='timbre'): 163 | """ 164 | Resample segment features to a given rate within fade boundaries. 165 | @param data: analysis object. 166 | @param rate: one of the following: segments, tatums, beats, bars. 167 | @param feature: either timbre or pitch. 168 | @return A dictionary including a numpy matrix of size len(rate) x 12, a rate, and an index 169 | """ 170 | ret = {'rate': rate, 'index': 0, 'cursor': 0, 'matrix': np.zeros((1, 12), dtype=np.float32)} 171 | segments, ind = get_central(data.analysis, 'segments') 172 | markers, ret['index'] = get_central(data.analysis, rate) 173 | 174 | if len(segments) < 2 or len(markers) < 2: 175 | return ret 176 | 177 | # Find the optimal attack offset 178 | meanOffset = get_mean_offset(segments, markers) 179 | tmp_markers = deepcopy(markers) 180 | 181 | # Apply the offset 182 | for m in tmp_markers: 183 | m.start -= meanOffset 184 | if m.start < 0: m.start = 0 185 | 186 | # Allocate output matrix, give it alias mat for convenience. 187 | mat = ret['matrix'] = np.zeros((len(tmp_markers)-1, 12), dtype=np.float32) 188 | 189 | # Find the index of the segment that corresponds to the first marker 190 | f = lambda x: tmp_markers[0].start < x.start + x.duration 191 | index = (i for i,x in enumerate(segments) if f(x)).next() 192 | 193 | # Do the resampling 194 | try: 195 | for (i, m) in enumerate(tmp_markers): 196 | while segments[index].start + segments[index].duration < m.start + m.duration: 197 | dur = segments[index].duration 198 | if segments[index].start < m.start: 199 | dur -= m.start - segments[index].start 200 | 201 | C = min(dur / m.duration, 1) 202 | 203 | mat[i, 0:12] += C * np.array(getattr(segments[index], feature)) 204 | index += 1 205 | 206 | C = min( (m.duration + m.start - segments[index].start) / m.duration, 1) 207 | mat[i, 0:12] += C * np.array(getattr(segments[index], feature)) 208 | except IndexError, e: 209 | pass # avoid breaking with index > len(segments) 210 | 211 | return ret 212 | 213 | def column_whiten(mat): 214 | """ Zero mean, unit variance on a column basis""" 215 | m = mat - np.mean(mat,0) 216 | return m / np.std(m,0) 217 | 218 | def timbre_whiten(mat): 219 | if rows(mat) < 2: return mat 220 | m = np.zeros((rows(mat), 12), dtype=np.float32) 221 | m[:,0] = mat[:,0] - np.mean(mat[:,0],0) 222 | m[:,0] = m[:,0] / np.std(m[:,0],0) 223 | m[:,1:] = mat[:,1:] - np.mean(mat[:,1:].flatten(),0) 224 | m[:,1:] = m[:,1:] / np.std(m[:,1:].flatten(),0) # use this! 225 | return m 226 | 227 | def move_cursor(track, duration, cursor, buf=MIN_MARKERS): 228 | dur = 0 229 | while dur < duration and cursor < rows(track.resampled['matrix']) - buf: 230 | markers = getattr(track.analysis, track.resampled['rate']) 231 | dur += markers[track.resampled['index'] + cursor].duration 232 | cursor += 1 233 | return dur, cursor 234 | 235 | def get_mat_out(track, transition): 236 | """ Find and output the matrix to use in the next alignment. 237 | Assumes that track.resampled exists. 238 | """ 239 | cursor = track.resampled['cursor'] 240 | mat = track.resampled['matrix'] 241 | # update cursor location to after the transition 242 | duration, cursor = move_cursor(track, transition, cursor) 243 | # output matrix with a proper number of rows, from beginning of transition 244 | return mat[track.resampled['cursor']:cursor,:] 245 | 246 | def get_mat_in(track, transition, inter): 247 | """ Find and output the search matrix to use in the next alignment. 248 | Assumes that track.resampled exists. 249 | """ 250 | # search from the start 251 | cursor = 0 252 | track.resampled['cursor'] = cursor 253 | mat = track.resampled['matrix'] 254 | 255 | # compute search zone by anticipating what's playing after the transition 256 | marker_end = getattr(track.analysis, track.resampled['rate'])[track.resampled['index'] + rows(mat)].start 257 | marker_start = getattr(track.analysis, track.resampled['rate'])[track.resampled['index']].start 258 | search_dur = (marker_end - marker_start) - inter - 2 * transition 259 | 260 | if search_dur < 0: 261 | return mat[:MIN_MARKERS,:] 262 | 263 | # find what the location is in rows 264 | duration, cursor = move_cursor(track, search_dur, cursor) 265 | 266 | return mat[:cursor,:] 267 | 268 | def make_crossfade(track1, track2, inter): 269 | 270 | markers1 = getattr(track1.analysis, track1.resampled['rate']) 271 | 272 | if len(markers1) < MIN_SEARCH: 273 | start1 = track1.resampled['cursor'] 274 | else: 275 | start1 = markers1[track1.resampled['index'] + track1.resampled['cursor']].start 276 | 277 | start2 = max((track2.analysis.duration - (inter + 2 * X_FADE)) / 2, 0) 278 | markers2 = getattr(track2.analysis, track2.resampled['rate']) 279 | 280 | if len(markers2) < MIN_SEARCH: 281 | track2.resampled['cursor'] = start2 + X_FADE + inter 282 | dur = min(track2.analysis.duration - 2 * X_FADE, inter) 283 | else: 284 | duration, track2.resampled['cursor'] = move_cursor(track2, start2+X_FADE+inter, 0) 285 | dur = markers2[track2.resampled['index'] + track2.resampled['cursor']].start - X_FADE - start2 286 | 287 | xf = Crossfade((track1, track2), (start1, start2), X_FADE) 288 | pb = Playback(track2, start2 + X_FADE, dur) 289 | 290 | return [xf, pb] 291 | 292 | def make_crossmatch(track1, track2, rate1, rate2, loc2, rows): 293 | markers1 = upsample_list(getattr(track1.analysis, track1.resampled['rate']), rate1) 294 | markers2 = upsample_list(getattr(track2.analysis, track2.resampled['rate']), rate2) 295 | 296 | def to_tuples(l, i, n): 297 | return [(t.start, t.duration) for t in l[i : i + n]] 298 | 299 | start1 = rate1 * (track1.resampled['index'] + track1.resampled['cursor']) 300 | start2 = loc2 + rate2 * track2.resampled['index'] # loc2 has already been multiplied by rate2 301 | 302 | return Crossmatch((track1, track2), (to_tuples(markers1, start1, rows), to_tuples(markers2, start2, rows))) 303 | 304 | def make_transition(track1, track2, inter, transition): 305 | # the minimal transition is 2 markers 306 | # the minimal inter is 0 sec 307 | markers1 = getattr(track1.analysis, track1.resampled['rate']) 308 | markers2 = getattr(track2.analysis, track2.resampled['rate']) 309 | 310 | if len(markers1) < MIN_SEARCH or len(markers2) < MIN_SEARCH: 311 | return make_crossfade(track1, track2, inter) 312 | 313 | # though the minimal transition is 2 markers, the alignment is on at least 3 seconds 314 | mat1 = get_mat_out(track1, max(transition, MIN_ALIGN_DURATION)) 315 | mat2 = get_mat_in(track2, max(transition, MIN_ALIGN_DURATION), inter) 316 | 317 | try: 318 | loc, n, rate1, rate2 = align(track1, track2, mat1, mat2) 319 | except: 320 | return make_crossfade(track1, track2, inter) 321 | 322 | if transition < MIN_ALIGN_DURATION: 323 | duration, cursor = move_cursor(track2, transition, loc) 324 | n = max(cursor-loc, MIN_MARKERS) 325 | 326 | xm = make_crossmatch(track1, track2, rate1, rate2, loc, n) 327 | # loc and n are both in terms of potentially upsampled data. 328 | # Divide by rate here to get end_crossmatch in terms of the original data. 329 | end_crossmatch = (loc + n) / rate2 330 | 331 | if markers2[-1].start < markers2[end_crossmatch].start + inter + transition: 332 | inter = max(markers2[-1].start - transition, 0) 333 | 334 | # move_cursor sets the cursor properly for subsequent operations, and gives us duration. 335 | dur, track2.resampled['cursor'] = move_cursor(track2, inter, end_crossmatch) 336 | pb = Playback(track2, sum(xm.l2[-1]), dur) 337 | 338 | return [xm, pb] 339 | 340 | def initialize(track, inter, transition): 341 | """find initial cursor location""" 342 | mat = track.resampled['matrix'] 343 | markers = getattr(track.analysis, track.resampled['rate']) 344 | 345 | try: 346 | # compute duration of matrix 347 | mat_dur = markers[track.resampled['index'] + rows(mat)].start - markers[track.resampled['index']].start 348 | start = (mat_dur - inter - transition - FADE_IN) / 2 349 | dur = start + FADE_IN + inter 350 | # move cursor to transition marker 351 | duration, track.resampled['cursor'] = move_cursor(track, dur, 0) 352 | # work backwards to find the exact locations of initial fade in and playback sections 353 | fi = Fadein(track, markers[track.resampled['index'] + track.resampled['cursor']].start - inter - FADE_IN, FADE_IN) 354 | pb = Playback(track, markers[track.resampled['index'] + track.resampled['cursor']].start - inter, inter) 355 | except: 356 | track.resampled['cursor'] = FADE_IN + inter 357 | fi = Fadein(track, 0, FADE_IN) 358 | pb = Playback(track, FADE_IN, inter) 359 | 360 | return [fi, pb] 361 | 362 | def terminate(track, fade): 363 | """ Deal with last fade out""" 364 | cursor = track.resampled['cursor'] 365 | markers = getattr(track.analysis, track.resampled['rate']) 366 | if MIN_SEARCH <= len(markers): 367 | cursor = markers[track.resampled['index'] + cursor].start 368 | return [Fadeout(track, cursor, min(fade, track.duration-cursor))] 369 | -------------------------------------------------------------------------------- /capsule/utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | """ 4 | utils.py 5 | 6 | Created by Jason Sundram, on 2010-04-05. 7 | Copyright (c) 2010 The Echo Nest. All rights reserved. 8 | """ 9 | 10 | def flatten(l): 11 | """ Converts a list of tuples to a flat list. 12 | e.g. flatten([(1,2), (3,4)]) => [1,2,3,4] 13 | """ 14 | return [item for pair in l for item in pair] 15 | 16 | def tuples(l, n=2): 17 | """ returns n-tuples from l. 18 | e.g. tuples(range(4), n=2) -> [(0, 1), (1, 2), (2, 3)] 19 | """ 20 | return zip(*[l[i:] for i in range(n)]) 21 | 22 | def rows(m): 23 | """returns the # of rows in a numpy matrix""" 24 | return m.shape[0] -------------------------------------------------------------------------------- /cowbell/cowbell.py: -------------------------------------------------------------------------------- 1 | # By Rob Ochshorn and Adam Baratz. 2 | # Slightly refactored by Joshua Lifton. 3 | import numpy 4 | import os 5 | import random 6 | import time 7 | 8 | import echonest.remix.audio as audio 9 | 10 | usage = """ 11 | Usage: 12 | python cowbell.py 13 | 14 | Example: 15 | python cowbell.py YouCanCallMeAl.mp3 YouCanCallMeCow.mp3 0.2 0.5 16 | 17 | Reference: 18 | http://www.youtube.com/watch?v=ZhSkRHXTKlw 19 | """ 20 | 21 | # constants 22 | COWBELL_THRESHOLD = 0.85 23 | COWBELL_OFFSET = -0.005 24 | 25 | # samples 26 | soundsPath = "sounds/" 27 | 28 | cowbellSounds = map(lambda x: audio.AudioData(os.path.join(soundsPath, "cowbell%s.wav" % x), sampleRate=44100, numChannels=2), range(5)) 29 | walkenSounds = map(lambda x: audio.AudioData(os.path.join(soundsPath, "walken%s.wav" % x), sampleRate=44100, numChannels=2), range(16)) 30 | trill = audio.AudioData(os.path.join(soundsPath, "trill.wav"), sampleRate=44100, numChannels=2) 31 | 32 | def linear(input, in1, in2, out1, out2): 33 | return ((input-in1) / (in2-in1)) * (out2-out1) + out1 34 | 35 | def exp(input, in1, in2, out1, out2, coeff): 36 | if (input <= in1): 37 | return out1 38 | if (input >= in2): 39 | return out2 40 | return pow( ((input-in1) / (in2-in1)) , coeff ) * (out2-out1) + out1 41 | 42 | class Cowbell: 43 | def __init__(self, input_file): 44 | self.audiofile = audio.LocalAudioFile(input_file) 45 | self.audiofile.data *= linear(self.audiofile.analysis.loudness, -2, -12, 0.5, 1.5) * 0.75 46 | 47 | def run(self, cowbell_intensity, walken_intensity, out): 48 | if cowbell_intensity != -1: 49 | self.cowbell_intensity = cowbell_intensity 50 | self.walken_intensity = walken_intensity 51 | t1 = time.time() 52 | sequence = self.sequence(cowbellSounds) 53 | print "Sequence and mixed in %g seconds" % (time.time() - t1) 54 | self.audiofile.encode(out) 55 | 56 | def sequence(self, chops): 57 | # add cowbells on the beats 58 | for beat in self.audiofile.analysis.beats: 59 | volume = linear(self.cowbell_intensity, 0, 1, 0.1, 0.3) 60 | # mix in cowbell on beat 61 | if self.cowbell_intensity == 1: 62 | self.mix(beat.start+COWBELL_OFFSET, seg=cowbellSounds[random.randint(0,1)], volume=volume) 63 | else: 64 | self.mix(beat.start+COWBELL_OFFSET, seg=cowbellSounds[random.randint(2,4)], volume=volume) 65 | # divide beat into quarters 66 | quarters = (numpy.arange(1,4) * beat.duration) / 4. + beat.start 67 | # mix in cowbell on quarters 68 | for quarter in quarters: 69 | volume = exp(random.random(), 0.5, 0.1, 0, self.cowbell_intensity, 0.8) * 0.3 70 | pan = linear(random.random(), 0, 1, -self.cowbell_intensity, self.cowbell_intensity) 71 | if self.cowbell_intensity < COWBELL_THRESHOLD: 72 | self.mix(quarter+COWBELL_OFFSET, seg=cowbellSounds[2], volume=volume) 73 | else: 74 | randomCowbell = linear(random.random(), 0, 1, COWBELL_THRESHOLD, 1) 75 | if randomCowbell < self.cowbell_intensity: 76 | self.mix(start=quarter+COWBELL_OFFSET, seg=cowbellSounds[random.randint(0,1)], volume=volume) 77 | else: 78 | self.mix(start=quarter+COWBELL_OFFSET, seg=cowbellSounds[random.randint(2,4)], volume=volume) 79 | # add trills / walken on section changes 80 | for section in self.audiofile.analysis.sections[1:]: 81 | if random.random() > self.walken_intensity: 82 | sample = trill 83 | volume = 0.3 84 | else: 85 | sample = walkenSounds[random.randint(0, len(walkenSounds)-1)] 86 | volume = 1.5 87 | self.mix(start=section.start+COWBELL_OFFSET, seg=sample, volume=volume) 88 | 89 | def mix(self, start=None, seg=None, volume=0.3, pan=0.): 90 | # this assumes that the audios have the same frequency/numchannels 91 | startsample = int(start * self.audiofile.sampleRate) 92 | seg = seg[0:] 93 | seg.data *= (volume-(pan*volume), volume+(pan*volume)) # pan + volume 94 | if self.audiofile.data.shape[0] - startsample > seg.data.shape[0]: 95 | self.audiofile.data[startsample:startsample+len(seg.data)] += seg.data[0:] 96 | 97 | 98 | def main(inputFilename, outputFilename, cowbellIntensity, walkenIntensity ) : 99 | c = Cowbell(inputFilename) 100 | print 'cowbelling...' 101 | c.run(cowbellIntensity, walkenIntensity, outputFilename) 102 | 103 | if __name__ == '__main__': 104 | import sys 105 | try : 106 | inputFilename = sys.argv[1] 107 | outputFilename = sys.argv[2] 108 | cowbellIntensity = float(sys.argv[3]) 109 | walkenIntensity = float(sys.argv[4]) 110 | except : 111 | print usage 112 | sys.exit(-1) 113 | main(inputFilename, outputFilename, cowbellIntensity, walkenIntensity) 114 | -------------------------------------------------------------------------------- /cowbell/sounds/cowbell0.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/echonest/remix-examples/05e190069e0b25baef9a4259267acfa34f2ed913/cowbell/sounds/cowbell0.wav -------------------------------------------------------------------------------- /cowbell/sounds/cowbell1.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/echonest/remix-examples/05e190069e0b25baef9a4259267acfa34f2ed913/cowbell/sounds/cowbell1.wav -------------------------------------------------------------------------------- /cowbell/sounds/cowbell2.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/echonest/remix-examples/05e190069e0b25baef9a4259267acfa34f2ed913/cowbell/sounds/cowbell2.wav -------------------------------------------------------------------------------- /cowbell/sounds/cowbell3.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/echonest/remix-examples/05e190069e0b25baef9a4259267acfa34f2ed913/cowbell/sounds/cowbell3.wav -------------------------------------------------------------------------------- /cowbell/sounds/cowbell4.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/echonest/remix-examples/05e190069e0b25baef9a4259267acfa34f2ed913/cowbell/sounds/cowbell4.wav -------------------------------------------------------------------------------- /cowbell/sounds/trill.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/echonest/remix-examples/05e190069e0b25baef9a4259267acfa34f2ed913/cowbell/sounds/trill.wav -------------------------------------------------------------------------------- /cowbell/sounds/walken0.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/echonest/remix-examples/05e190069e0b25baef9a4259267acfa34f2ed913/cowbell/sounds/walken0.wav -------------------------------------------------------------------------------- /cowbell/sounds/walken1.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/echonest/remix-examples/05e190069e0b25baef9a4259267acfa34f2ed913/cowbell/sounds/walken1.wav -------------------------------------------------------------------------------- /cowbell/sounds/walken10.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/echonest/remix-examples/05e190069e0b25baef9a4259267acfa34f2ed913/cowbell/sounds/walken10.wav -------------------------------------------------------------------------------- /cowbell/sounds/walken11.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/echonest/remix-examples/05e190069e0b25baef9a4259267acfa34f2ed913/cowbell/sounds/walken11.wav -------------------------------------------------------------------------------- /cowbell/sounds/walken12.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/echonest/remix-examples/05e190069e0b25baef9a4259267acfa34f2ed913/cowbell/sounds/walken12.wav -------------------------------------------------------------------------------- /cowbell/sounds/walken13.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/echonest/remix-examples/05e190069e0b25baef9a4259267acfa34f2ed913/cowbell/sounds/walken13.wav -------------------------------------------------------------------------------- /cowbell/sounds/walken14.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/echonest/remix-examples/05e190069e0b25baef9a4259267acfa34f2ed913/cowbell/sounds/walken14.wav -------------------------------------------------------------------------------- /cowbell/sounds/walken15.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/echonest/remix-examples/05e190069e0b25baef9a4259267acfa34f2ed913/cowbell/sounds/walken15.wav -------------------------------------------------------------------------------- /cowbell/sounds/walken2.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/echonest/remix-examples/05e190069e0b25baef9a4259267acfa34f2ed913/cowbell/sounds/walken2.wav -------------------------------------------------------------------------------- /cowbell/sounds/walken3.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/echonest/remix-examples/05e190069e0b25baef9a4259267acfa34f2ed913/cowbell/sounds/walken3.wav -------------------------------------------------------------------------------- /cowbell/sounds/walken4.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/echonest/remix-examples/05e190069e0b25baef9a4259267acfa34f2ed913/cowbell/sounds/walken4.wav -------------------------------------------------------------------------------- /cowbell/sounds/walken5.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/echonest/remix-examples/05e190069e0b25baef9a4259267acfa34f2ed913/cowbell/sounds/walken5.wav -------------------------------------------------------------------------------- /cowbell/sounds/walken6.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/echonest/remix-examples/05e190069e0b25baef9a4259267acfa34f2ed913/cowbell/sounds/walken6.wav -------------------------------------------------------------------------------- /cowbell/sounds/walken7.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/echonest/remix-examples/05e190069e0b25baef9a4259267acfa34f2ed913/cowbell/sounds/walken7.wav -------------------------------------------------------------------------------- /cowbell/sounds/walken8.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/echonest/remix-examples/05e190069e0b25baef9a4259267acfa34f2ed913/cowbell/sounds/walken8.wav -------------------------------------------------------------------------------- /cowbell/sounds/walken9.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/echonest/remix-examples/05e190069e0b25baef9a4259267acfa34f2ed913/cowbell/sounds/walken9.wav -------------------------------------------------------------------------------- /drums/breaks/AmenBrother.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/echonest/remix-examples/05e190069e0b25baef9a4259267acfa34f2ed913/drums/breaks/AmenBrother.mp3 -------------------------------------------------------------------------------- /drums/breaks/GiveituporTurnitLoose.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/echonest/remix-examples/05e190069e0b25baef9a4259267acfa34f2ed913/drums/breaks/GiveituporTurnitLoose.mp3 -------------------------------------------------------------------------------- /drums/breaks/assemblyline.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/echonest/remix-examples/05e190069e0b25baef9a4259267acfa34f2ed913/drums/breaks/assemblyline.mp3 -------------------------------------------------------------------------------- /drums/drums.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf=8 3 | """ 4 | drums.py 5 | 6 | Add drums to a song. 7 | 8 | At the moment, only works with songs in 4, and endings are rough. 9 | 10 | By Ben Lacker, 2009-02-24. 11 | """ 12 | import numpy 13 | import sys 14 | import time 15 | 16 | import echonest.remix.audio as audio 17 | 18 | usage=""" 19 | Usage: 20 | python drums.py [] 21 | 22 | Example: 23 | python drums.py HereComesTheSun.mp3 breaks/AmenBrother.mp3 HereComeTheDrums.mp3 64 4 0.6 24 | 25 | Drum instenity defaults to 0.5 26 | """ 27 | 28 | def mono_to_stereo(audio_data): 29 | data = audio_data.data.flatten().tolist() 30 | new_data = numpy.array((data,data)) 31 | audio_data.data = new_data.swapaxes(0,1) 32 | audio_data.numChannels = 2 33 | return audio_data 34 | 35 | def split_break(breakfile,n): 36 | drum_data = [] 37 | start = 0 38 | for i in range(n): 39 | start = int((len(breakfile) * (i))/n) 40 | end = int((len(breakfile) * (i+1))/n) 41 | ndarray = breakfile.data[start:end] 42 | new_data = audio.AudioData(ndarray=ndarray, 43 | sampleRate=breakfile.sampleRate, 44 | numChannels=breakfile.numChannels) 45 | drum_data.append(new_data) 46 | return drum_data 47 | 48 | 49 | def main(input_filename, output_filename, break_filename, break_parts, 50 | measures, mix): 51 | audiofile = audio.LocalAudioFile(input_filename) 52 | sample_rate = audiofile.sampleRate 53 | breakfile = audio.LocalAudioFile(break_filename) 54 | if breakfile.numChannels == 1: 55 | breakfile = mono_to_stereo(breakfile) 56 | num_channels = audiofile.numChannels 57 | drum_data = split_break(breakfile,break_parts) 58 | hits_per_beat = int(break_parts/(4 * measures)) 59 | bars = audiofile.analysis.bars 60 | out_shape = (len(audiofile)+100000,num_channels) 61 | out = audio.AudioData(shape=out_shape, sampleRate=sample_rate, 62 | numChannels=num_channels) 63 | if not bars: 64 | print "Didn't find any bars in this analysis!" 65 | print "No output." 66 | sys.exit(-1) 67 | for bar in bars[:-1]: 68 | beats = bar.children() 69 | for i in range(len(beats)): 70 | try: 71 | break_index = ((bar.local_context()[0] %\ 72 | measures) * 4) + (i % 4) 73 | except ValueError: 74 | break_index = i % 4 75 | tats = range((break_index) * hits_per_beat, 76 | (break_index + 1) * hits_per_beat) 77 | drum_samps = sum([len(drum_data[x]) for x in tats]) 78 | beat_samps = len(audiofile[beats[i]]) 79 | beat_shape = (beat_samps,num_channels) 80 | tat_shape = (float(beat_samps/hits_per_beat),num_channels) 81 | beat_data= audio.AudioData(shape=beat_shape, 82 | sampleRate=sample_rate, 83 | numChannels=num_channels) 84 | for j in tats: 85 | tat_data= audio.AudioData(shape=tat_shape, 86 | sampleRate=sample_rate, 87 | numChannels=num_channels) 88 | if drum_samps > beat_samps/hits_per_beat: 89 | # truncate drum hits to fit beat length 90 | tat_data.data = drum_data[j].data[:len(tat_data)] 91 | elif drum_samps < beat_samps/hits_per_beat: 92 | # space out drum hits to fit beat length 93 | #temp_data = add_fade_out(drum_data[j]) 94 | tat_data.append(drum_data[j]) 95 | tat_data.endindex = len(tat_data) 96 | beat_data.append(tat_data) 97 | del(tat_data) 98 | # account for rounding errors 99 | beat_data.endindex = len(beat_data) 100 | mixed_beat = audio.mix(beat_data, audiofile[beats[i]], mix=mix) 101 | del(beat_data) 102 | out.append(mixed_beat) 103 | finale = bars[-1].start + bars[-1].duration 104 | last = audio.AudioQuantum(audiofile.analysis.bars[-1].start, 105 | audiofile.analysis.duration - 106 | audiofile.analysis.bars[-1].start) 107 | last_data = audio.getpieces(audiofile,[last]) 108 | out.append(last_data) 109 | out.encode(output_filename) 110 | 111 | if __name__=='__main__': 112 | try: 113 | input_filename = sys.argv[1] 114 | break_filename = sys.argv[2] 115 | output_filename = sys.argv[3] 116 | break_parts = int(sys.argv[4]) 117 | measures = int(sys.argv[5]) 118 | if len(sys.argv) == 7: 119 | mix = float(sys.argv[6]) 120 | else: 121 | mix = 0.5 122 | except: 123 | print usage 124 | sys.exit(-1) 125 | main(input_filename, output_filename, break_filename, break_parts, 126 | measures, mix) 127 | -------------------------------------------------------------------------------- /earworm/README.txt: -------------------------------------------------------------------------------- 1 | == earworm.py == 2 | 3 | earworm.py allows to seamlessly extend and shrink the length of a song without affecting its tempo, by jumping strategically backward or forward into it. It is based on a graph representation of beat-synchronous pattern self-similarities, i.e., "close-enough" structural repetitions. The algorithm is tuned to: 4 | 5 | - minimize the numbers of "loops" 6 | - never loop twice in the same exact location 7 | - end up as close as possible to the requested duration without any time-stretching or cropping 8 | - take into account timbre and pitch similarities 9 | - smooth transparent jumps and transitions 10 | - efficient graph representation useful for structure visualization, and other song manipulations 11 | - also find and construct loops of arbitrary lengths for infinite looping 12 | 13 | It is up to the developer to modify the algorithm and choose alternative default parameters for other use cases. 14 | 15 | 16 | == Usage == 17 | 18 | Usage: earworm.py [options] 19 | 20 | Options: 21 | -h, --help show this help message and exit 22 | -d DURATION, --duration=DURATION 23 | target duration (argument in seconds) default=600 24 | -m MINIMUM, --minimum=MINIMUM 25 | minimal loop size (in beats) default=8 26 | -i, --infinite generate an infinite loop (wav file) 27 | -l, --length length must be accurate 28 | -k, --pickle output graph as a pickle object 29 | -g, --graph output graph as a gml text file 30 | -p, --plot output graph as png image 31 | -f, --force force (re)computing the graph 32 | -S, --shortest output the shortest loop (wav file) 33 | -L, --longest output the longest loop (wav file) 34 | -v, --verbose show results on screen 35 | 36 | 37 | == Examples == 38 | 39 | After installing your Echo Nest API Key, you can try the script in the terminal, e.g.: 40 | $ python earworm.py ../music/BillieJean.mp3 41 | generates a 600 second (default) version of Billie Jean called BillieJean_600.mp3 42 | 43 | Option -d allows you to request the length: 44 | $ python earworm.py -d 1200 ../music/BillieJean.mp3 45 | generates a close to 20 min long version of Billie Jean: 20 min and 883 milliseconds to be exact. 46 | 47 | By adding the option -l: 48 | $ python earworm.py -d 1200 -l ../music/BillieJean.mp3 49 | you get the exact 20 min long version of Billie Jean! What happens is that a fade out is started early in order to finish at 20 min worth of audio samples precisely. 50 | 51 | The option -m takes an integer parameter: the minimum number of beats, e.g.: 52 | $ python earworm.py -d 1200 -l -m 16 ../music/BillieJean.mp3 53 | to eliminate jumps that are shorter than 16 beats in length. That allows to avoid immediate typically more noticeable repetitions. Default is 8 beats. 54 | 55 | Note that if you request a short duration, e.g. 30 seconds: 56 | $ python earworm.py -d 30 ../music/BillieJean.mp3 57 | the representation may not allow for such a short path from beginning to end, and will output the shortest path found, given the constraints. 58 | 59 | Use the option -v to visualize where jumps are created: 60 | $ python earworm.py -v ../music/BillieJean.mp3 61 | outputs the following pointers, showing time of action, action name, time parameters, duration, and title: 62 | 63 | 00:00 Playback 0.000 -> 99.824 (99.824) Billie Jean 64 | 01:39 Jump 99.824 -> 92.117 (0.515) Billie Jean 65 | 01:40 Playback 92.117 -> 139.847 (47.730) Billie Jean 66 | 02:28 Jump 139.847 -> 33.586 (0.512) Billie Jean 67 | 02:28 Playback 33.586 -> 251.584 (217.998) Billie Jean 68 | 06:06 Jump 251.584 -> 243.896 (0.510) Billie Jean 69 | 06:07 Playback 243.896 -> 271.556 (27.660) Billie Jean 70 | 06:34 Jump 271.556 -> 87.510 (0.516) Billie Jean 71 | 06:35 Playback 87.510 -> 294.452 (206.941) Billie Jean 72 | 73 | along with a long list of links from a beat marker, to other beat offsets: 74 | 75 | 0 [4, 120] 76 | 1 [4, 88] 77 | 2 [4] 78 | 3 [4] 79 | 4 [88, 4] 80 | 5 [88] 81 | ... 82 | 527 [-8, -168, -72, -360] 83 | 528 [-8, -168, -360] 84 | 529 [-8, -360, -168] 85 | 530 [-8] 86 | 531 [-8, -80] 87 | 532 [-8, -80] 88 | 89 | In this case, beat 0 is structurally similar to 4 beats and 120 beats forward. Beat 532 is similar to 8 beats and 80 beats earlier. 90 | 91 | You can cache the graph representation that may take time to compute, with option -k: 92 | $ python earworm.py -k ../music/BillieJean.mp3 93 | That'll save a graph pickle file called BillieJean.mp3.graph.gpkl which will be loaded the next time you call this song. 94 | 95 | The infinite option -i outputs a loopable wav file (for sample precision) rather than an mp3: 96 | $ python earworm.py -i ../music/BillieJean.mp3 97 | generates a close to 600 second long "loop" called BillieJean_600_loop.wav that may be heard from a player with proper "loop mode" capabilities for infinite seamless playing. For that reason, the file doesn't start at the very beginning or finish at the very end of the original file. 98 | 99 | For convenience, the shortest or longest single loops can be generated respectively via -S and -L 100 | $ python earworm.py -S ../music/BillieJean.mp3 101 | makes an 8 second loopable file called BillieJean_8_shortest.mp3, and 102 | $ python earworm.py -L ../music/BillieJean.mp3 103 | makes an 184 second loopable file BillieJean_184_longest.mp3 104 | 105 | It is possible to combine -m and -S to find the shortest loop with at least -m beats, e.g., 106 | $ python earworm.py -S -m 32 ../music/BillieJean.mp3 107 | makes a single loop of 16 seconds (32 beats of about 500 ms) BillieJean_16_shortest.mp3 108 | 109 | -------------------------------------------------------------------------------- /earworm/earworm.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf=8 3 | 4 | """ 5 | earworm.py 6 | (name suggested by Jonathan Feinberg on 03/10/10) 7 | 8 | Accepts a song and duration on the commandline, and makes a new audio file of that duration. 9 | Creates an optimal loop if specified for looping. 10 | 11 | Created by Tristan Jehan and Jason Sundram. 12 | """ 13 | 14 | from copy import deepcopy 15 | from optparse import OptionParser 16 | import numpy as np 17 | from numpy.matlib import repmat, repeat 18 | from numpy import sqrt 19 | import operator 20 | import os 21 | import sys 22 | 23 | try: 24 | import networkx as nx 25 | except ImportError: 26 | print """earworm.py requires networkx. 27 | 28 | If setuptools is installed on your system, simply: 29 | easy_install networkx 30 | 31 | Otherwise, you can get it here: http://pypi.python.org/pypi/networkx 32 | 33 | Get the source, unzip it, cd to the directory it is in and run: 34 | python setup.py install 35 | """ 36 | sys.exit(1) 37 | 38 | from echonest.remix.action import Playback, Jump, Fadeout, render, display_actions 39 | from echonest.remix.audio import LocalAudioFile 40 | # from echonest.remix.cloud_support import AnalyzedAudioFile 41 | 42 | from earworm_support import evaluate_distance, timbre_whiten, resample_features 43 | from utils import rows, tuples, flatten 44 | 45 | 46 | DEF_DUR = 600 47 | MAX_SIZE = 800 48 | MIN_RANGE = 16 49 | MIN_JUMP = 16 50 | MIN_ALIGN = 16 51 | MAX_EDGES = 8 52 | FADE_OUT = 3 53 | RATE = 'beats' 54 | 55 | def read_graph(name="graph.gpkl"): 56 | if os.path.splitext(name)[1] == ".gml": 57 | return nx.read_gml(name) 58 | else: 59 | return nx.read_gpickle(name) 60 | 61 | def save_graph(graph, name="graph.gpkl"): 62 | if os.path.splitext(name)[1] == ".gml": 63 | nx.write_gml(graph, name) 64 | else: 65 | nx.write_gpickle(graph, name) 66 | 67 | def print_screen(paths): 68 | for i, p in enumerate(paths): 69 | print i, [l[0] - i for l in p] 70 | 71 | def save_plot(graph, name="graph.png"): 72 | """save plot with index numbers rather than timing""" 73 | edges = graph.edges(data=True) 74 | nodes = [edge[2]['source'] for edge in edges] 75 | order = np.argsort(nodes) 76 | edges = [edges[i] for i in order.tolist()] 77 | new_edges = [] 78 | for edge in edges: 79 | v = edge[2]['target'] - edge[2]['source']-1 80 | new_edges.append((edge[2]['source'], edge[2]['target'])) 81 | DG = nx.DiGraph() 82 | DG.add_edges_from(new_edges) 83 | A = nx.to_agraph(DG) 84 | A.layout() 85 | A.draw(name) 86 | 87 | def make_graph(paths, markers): 88 | DG = nx.DiGraph() 89 | # add nodes 90 | for i in xrange(len(paths)): 91 | DG.add_node(markers[i].start) 92 | # add edges 93 | edges = [] 94 | for i in xrange(len(paths)): 95 | if i != len(paths)-1: 96 | edges.append((markers[i].start, markers[i+1].start, {'distance':0, 'duration': markers[i].duration, 'source':i, 'target':i+1})) # source and target for plots only 97 | edges.extend([(markers[i].start, markers[l[0]+1].start, {'distance':l[1], 'duration': markers[i].duration, 'source':i, 'target':l[0]+1}) for l in paths[i]]) 98 | DG.add_edges_from(edges) 99 | return DG 100 | 101 | def make_similarity_matrix(matrix, size=MIN_ALIGN): 102 | singles = matrix.tolist() 103 | points = [flatten(t) for t in tuples(singles, size)] 104 | numPoints = len(points) 105 | distMat = sqrt(np.sum((repmat(points, numPoints, 1) - repeat(points, numPoints, axis=0))**2, axis=1, dtype=np.float32)) 106 | return distMat.reshape((numPoints, numPoints)) 107 | 108 | def get_paths(matrix, size=MIN_RANGE): 109 | mat = make_similarity_matrix(matrix, size=MIN_ALIGN) 110 | paths = [] 111 | for i in xrange(rows(mat)): 112 | paths.append(get_loop_points(mat[i,:], size)) 113 | return paths 114 | 115 | def get_paths_slow(matrix, size=MIN_RANGE): 116 | paths = [] 117 | for i in xrange(rows(matrix)-MIN_ALIGN+1): 118 | vector = np.zeros((rows(matrix)-MIN_ALIGN+1,), dtype=np.float32) 119 | for j in xrange(rows(matrix)-MIN_ALIGN+1): 120 | vector[j] = evaluate_distance(matrix[i:i+MIN_ALIGN,:], matrix[j:j+MIN_ALIGN,:]) 121 | paths.append(get_loop_points(vector, size)) 122 | return paths 123 | 124 | # can this be made faster? 125 | def get_loop_points(vector, size=MIN_RANGE, max_edges=MAX_EDGES): 126 | res = set() 127 | m = np.mean(vector) 128 | s = np.std(vector) 129 | for i in xrange(vector.size-size): 130 | sub = vector[i:i+size] 131 | j = np.argmin(sub) 132 | if sub[j] < m-s and j != 0 and j != size-1 and sub[j] < sub[j-1] and sub[j] < sub[j+1] and sub[j] != 0: 133 | res.add((i+j, sub[j])) 134 | i = i+j # we skip a few steps 135 | # let's remove clusters of minima 136 | res = sorted(res, key=operator.itemgetter(0)) 137 | out = set() 138 | i = 0 139 | while i < len(res): 140 | tmp = [res[i]] 141 | j = 1 142 | while i+j < len(res): 143 | if res[i+j][0]-res[i+j-1][0] < MIN_RANGE: 144 | tmp.append(res[i+j]) 145 | j = j+1 146 | else: 147 | break 148 | tmp = sorted(tmp, key=operator.itemgetter(1)) 149 | out.add(tmp[0]) 150 | i = i+j 151 | out = sorted(out, key=operator.itemgetter(1)) 152 | return out[:max_edges] 153 | 154 | def path_intersect(timbre_paths, pitch_paths): 155 | assert(len(timbre_paths) == len(pitch_paths)) 156 | paths = [] 157 | for i in xrange(len(timbre_paths)): 158 | t_list = timbre_paths[i] 159 | p_list = pitch_paths[i] 160 | t = [l[0] for l in t_list] 161 | p = [l[0] for l in p_list] 162 | r = filter(lambda x:x in t,p) 163 | res = [(v, t_list[t.index(v)][1] + p_list[p.index(v)][1]) for v in r] 164 | paths.append(res) 165 | return paths 166 | 167 | def get_jumps(graph, mode='backward'): 168 | loops = [] 169 | edges = graph.edges(data=True) 170 | for edge in edges: 171 | if mode == 'infinite' and edge[1] < edge[0] or edge[2]['distance'] == 0: 172 | loops.append(edge) 173 | if mode == 'backward' and edge[1] < edge[0]: 174 | loops.append(edge) 175 | if mode == 'forward' and edge[0] < edge[1] and 1 < edge[2]['target']-edge[2]['source']: 176 | loops.append(edge) 177 | if mode == 'infinite': 178 | order = np.argsort([l[0] for l in loops]).tolist() 179 | if mode == 'backward': 180 | order = np.argsort([l[0]-l[1]+l[2]['duration'] for l in loops]).tolist() 181 | order.reverse() # we want long loops first 182 | if mode == 'forward': 183 | order = np.argsort([l[1]-l[0]-l[2]['duration'] for l in loops]).tolist() 184 | order.reverse() # we want long loops first 185 | loops = [loops[i] for i in order] 186 | return loops 187 | 188 | def trim_graph(graph): 189 | 190 | # trim first_node if necessary 191 | first_node = min(graph.nodes()) 192 | deg = graph.degree(first_node) 193 | while deg <= 1: 194 | graph.remove_node(first_node) 195 | first_node = min(graph.nodes()) 196 | deg = graph.degree(first_node) 197 | 198 | # trim last node if necessary 199 | last_node = max(graph.nodes()) 200 | deg = graph.degree(last_node) 201 | while deg <= 1: 202 | graph.remove_node(last_node) 203 | last_node = max(graph.nodes()) 204 | deg = graph.degree(last_node) 205 | 206 | return graph, first_node, last_node 207 | 208 | def collect(edges, path): 209 | # kind slow but fine 210 | res = [] 211 | for p in path: 212 | for e in edges: 213 | if (p[0], p[1]) == (e[0], e[1]): 214 | if e[2]['target']-e[2]['source'] == 1: 215 | res.append(p) 216 | else: 217 | res.append(e) 218 | return res 219 | 220 | def infinite(graph, track, target): 221 | DG = nx.DiGraph() 222 | loops = get_jumps(graph, mode='backward') 223 | DG.add_edges_from(loops) 224 | DG, first_node, last_node = trim_graph(DG) 225 | 226 | def dist(node1, node2): return node2-node1 227 | 228 | # search for shortest path from last to first node 229 | alt = True 230 | path = [] 231 | while path == []: 232 | edges = DG.edges(data=True) 233 | try: 234 | path = tuples(nx.astar_path(DG, last_node, first_node, dist)) 235 | except: 236 | if alt == True: 237 | DG.remove_node(first_node) 238 | alt = False 239 | else: 240 | DG.remove_node(last_node) 241 | alt = True 242 | DG, first_node, last_node = trim_graph(DG) 243 | 244 | assert(path != []) # FIXME -- maybe find a few loops and deal with them 245 | 246 | res = collect(edges, path) 247 | res_dur = 0 248 | for r in res: 249 | if r[1] < r[0]: res_dur += r[2]['duration'] 250 | else: res_dur += r[1]-r[0] 251 | 252 | # trim graph to DG size 253 | f_n = min(graph.nodes()) 254 | while f_n < first_node: 255 | graph.remove_node(f_n) 256 | f_n = min(graph.nodes()) 257 | l_n = max(graph.nodes()) 258 | while last_node < l_n: 259 | graph.remove_node(l_n) 260 | l_n = max(graph.nodes()) 261 | 262 | # find optimal path 263 | path = compute_path(graph, max(target-res_dur, 0)) 264 | path = path + res 265 | # build actions 266 | actions = make_jumps(path, track) 267 | actions.pop(-1) 268 | jp = Jump(track, actions[-1].source, actions[-1].target, actions[-1].duration) 269 | actions.pop(-1) 270 | actions.append(jp) 271 | return actions 272 | 273 | def remove_short_loops(graph, mlp): 274 | edges = graph.edges(data=True) 275 | for e in edges: 276 | dist = e[2]['target'] - e[2]['source'] 277 | if dist == 1: continue 278 | if mlp < dist: continue 279 | if dist <= -mlp+1: continue 280 | graph.remove_edge(e[0], e[1]) 281 | 282 | def one_loop(graph, track, mode='shortest'): 283 | jumps = get_jumps(graph, mode='backward') 284 | if len(jumps) == 0: return [] 285 | loop = None 286 | if mode == 'longest': 287 | loop = jumps[0] 288 | else: 289 | jumps.reverse() 290 | for jump in jumps: 291 | if jump[1] < jump[0]: 292 | loop = jump 293 | break 294 | if loop == None: return [] 295 | # Let's capture a bit of the attack 296 | OFFSET = 0.025 # 25 ms 297 | pb = Playback(track, loop[1]-OFFSET, loop[0]-loop[1]) 298 | jp = Jump(track, loop[0]-OFFSET, loop[1]-OFFSET, loop[2]['duration']) 299 | return [pb, jp] 300 | 301 | def compute_path(graph, target): 302 | 303 | first_node = min(graph.nodes()) 304 | last_node = max(graph.nodes()) 305 | 306 | # find the shortest direct path from first node to last node 307 | if target == 0: 308 | def dist(node1, node2): return node2-node1 # not sure why, but it works 309 | # we find actual jumps 310 | edges = graph.edges(data=True) 311 | path = tuples(nx.astar_path(graph, first_node, last_node, dist)) 312 | res = collect(edges, path) 313 | return res 314 | 315 | duration = last_node - first_node 316 | if target < duration: 317 | # build a list of sorted jumps by length. 318 | remaining = duration-target 319 | # build a list of sorted loops by length. 320 | loops = get_jumps(graph, mode='forward') 321 | 322 | def valid_jump(jump, jumps, duration): 323 | for j in jumps: 324 | if j[0] < jump[0] and jump[0] < j[1]: 325 | return False 326 | if j[0] < jump[1] and jump[1] < j[1]: 327 | return False 328 | if duration - (jump[1]-jump[0]+jump[2]['duration']) < 0: 329 | return False 330 | if duration - (jump[1]-jump[0]+jump[2]['duration']) < 0: 331 | return False 332 | return True 333 | 334 | res = [] 335 | while 0 < remaining: 336 | if len(loops) == 0: break 337 | for l in loops: 338 | if valid_jump(l, res, remaining) == True: 339 | res.append(l) 340 | remaining -= (l[1]-l[0]+l[2]['duration']) 341 | loops.remove(l) 342 | break 343 | if l == loops[-1]: 344 | loops.remove(l) 345 | break 346 | res = sorted(res, key=operator.itemgetter(0)) 347 | 348 | elif duration < target: 349 | remaining = target-duration 350 | loops = get_jumps(graph, mode='backward') 351 | tmp_loops = deepcopy(loops) 352 | res = [] 353 | # this resolution value is about the smallest denominator 354 | resolution = loops[-1][1]-loops[-1][0]-loops[-1][2]['duration'] 355 | while remaining > 0: 356 | if len(tmp_loops) == 0: 357 | tmp_loops = deepcopy(loops) 358 | d = -9999999999999999 359 | i = 0 360 | while d < resolution and i+1 <= len(tmp_loops): 361 | l = tmp_loops[i] 362 | d = remaining - (l[0]-l[1]+l[2]['duration']) 363 | i += 1 364 | res.append(l) 365 | remaining -= (l[0]-l[1]+l[2]['duration']) 366 | tmp_loops.remove(l) 367 | order = np.argsort([l[0] for l in res]).tolist() 368 | res = [res[i] for i in order] 369 | 370 | else: 371 | return [(first_node, last_node)] 372 | 373 | def dist(node1, node2): return 0 # not sure why, but it works 374 | start = tuples(nx.astar_path(graph, first_node, res[0][0], dist)) 375 | end = tuples(nx.astar_path(graph, res[-1][1], last_node, dist)) 376 | 377 | return start + res + end 378 | 379 | def make_jumps(path, track): 380 | actions = [] 381 | source = path[0][0] 382 | #pb = Playback(track, 0, 10) 383 | for p in path: 384 | try: 385 | if p[2]['target']-p[2]['source'] == 1: 386 | raise 387 | target = p[0] 388 | if 0 < target-source: 389 | actions.append(Playback(track, source, target-source)) 390 | actions.append(Jump(track, p[0], p[1], p[2]['duration'])) 391 | source = p[1] 392 | except: 393 | target = p[1] 394 | if 0 < target-source: 395 | actions.append(Playback(track, source, target-source)) 396 | return actions 397 | 398 | def terminate(dur_intro, middle, dur_outro, duration, lgh): 399 | # merge intro 400 | if isinstance(middle[0], Playback): 401 | middle[0].start = 0 402 | middle[0].duration += dur_intro 403 | start = [] 404 | else: 405 | start = [Playback(middle[0].track, 0, dur_intro)] 406 | # merge outro 407 | if isinstance(middle[-1], Playback): 408 | middle[-1].duration += dur_outro 409 | end = [] 410 | else: 411 | end = [Playback(middle[-1].track, middle[-1].start + middle[-1].duration, dur_outro)] 412 | # combine 413 | actions = start + middle + end 414 | if lgh == False: 415 | return actions 416 | excess = sum(inst.duration for inst in actions)-duration 417 | if excess == 0: 418 | return actions 419 | # trim the end with fadeout 420 | if actions[-1].duration <= FADE_OUT+excess: 421 | start = actions[-1].start 422 | dur = FADE_OUT 423 | actions.remove(actions[-1]) 424 | else: 425 | actions[-1].duration -= FADE_OUT+excess 426 | start = actions[-1].start+actions[-1].duration 427 | dur = FADE_OUT 428 | actions.append(Fadeout(middle[0].track, start, dur)) 429 | return actions 430 | 431 | def do_work(track, options): 432 | 433 | dur = float(options.duration) 434 | mlp = int(options.minimum) 435 | lgh = bool(options.length) 436 | inf = bool(options.infinite) 437 | pkl = bool(options.pickle) 438 | gml = bool(options.graph) 439 | plt = bool(options.plot) 440 | fce = bool(options.force) 441 | sho = bool(options.shortest) 442 | lon = bool(options.longest) 443 | vbs = bool(options.verbose) 444 | 445 | mp3 = track.filename 446 | try: 447 | if fce == True: 448 | raise 449 | graph = read_graph(mp3+".graph.gpkl") 450 | except: 451 | # compute resampled and normalized matrix 452 | timbre = resample_features(track, rate=RATE, feature='timbre') 453 | timbre['matrix'] = timbre_whiten(timbre['matrix']) 454 | pitch = resample_features(track, rate=RATE, feature='pitches') 455 | 456 | # pick a tradeoff between speed and memory size 457 | if rows(timbre['matrix']) < MAX_SIZE: 458 | # faster but memory hungry. For euclidean distances only. 459 | t_paths = get_paths(timbre['matrix']) 460 | p_paths = get_paths(pitch['matrix']) 461 | else: 462 | # slower but memory efficient. Any distance possible. 463 | t_paths = get_paths_slow(timbre['matrix']) 464 | p_paths = get_paths_slow(pitch['matrix']) 465 | 466 | # intersection of top timbre and pitch results 467 | paths = path_intersect(t_paths, p_paths) 468 | # TEMPORARY -- check that the data looks good 469 | if vbs == True: 470 | print_screen(paths) 471 | # make graph 472 | markers = getattr(track.analysis, timbre['rate'])[timbre['index']:timbre['index']+len(paths)] 473 | graph = make_graph(paths, markers) 474 | 475 | # remove smaller loops for quality results 476 | if 0 < mlp: 477 | remove_short_loops(graph, mlp) 478 | # plot graph 479 | if plt == True: 480 | save_plot(graph, mp3+".graph.png") 481 | # save graph 482 | if pkl == True: 483 | save_graph(graph, mp3+".graph.gpkl") 484 | if gml == True: 485 | save_graph(graph, mp3+".graph.gml") 486 | # single loops 487 | if sho == True: 488 | return one_loop(graph, track, mode='shortest') 489 | if lon == True: 490 | return one_loop(graph, track, mode='longest') 491 | # other infinite loops 492 | if inf == True: 493 | if vbs == True: 494 | print "\nInput Duration:", track.analysis.duration 495 | # get the optimal path for a given duration 496 | return infinite(graph, track, dur) 497 | 498 | dur_intro = min(graph.nodes()) 499 | dur_outro = track.analysis.duration - max(graph.nodes()) 500 | 501 | if vbs == True: 502 | print "Input Duration:", track.analysis.duration 503 | # get the optimal path for a given duration 504 | path = compute_path(graph, max(dur-dur_intro-dur_outro, 0)) 505 | # build actions 506 | middle = make_jumps(path, track) 507 | # complete list of actions 508 | actions = terminate(dur_intro, middle, dur_outro, dur, lgh) 509 | 510 | return actions 511 | 512 | def main(): 513 | usage = "usage: %s [options] " % sys.argv[0] 514 | parser = OptionParser(usage=usage) 515 | parser.add_option("-d", "--duration", default=DEF_DUR, help="target duration (argument in seconds) default=600") 516 | parser.add_option("-m", "--minimum", default=MIN_JUMP, help="minimal loop size (in beats) default=8") 517 | parser.add_option("-i", "--infinite", action="store_true", help="generate an infinite loop (outputs a wav file)") 518 | parser.add_option("-l", "--length", action="store_true", help="length must be accurate") 519 | parser.add_option("-k", "--pickle", action="store_true", help="output graph as a pickle object") 520 | parser.add_option("-g", "--graph", action="store_true", help="output graph as a gml text file") 521 | parser.add_option("-p", "--plot", action="store_true", help="output graph as png image") 522 | parser.add_option("-f", "--force", action="store_true", help="force (re)computing the graph") 523 | parser.add_option("-S", "--shortest", action="store_true", help="output the shortest loop") 524 | parser.add_option("-L", "--longest", action="store_true", help="output the longest loop") 525 | parser.add_option("-v", "--verbose", action="store_true", help="show results on screen") 526 | 527 | (options, args) = parser.parse_args() 528 | if len(args) < 1: 529 | parser.print_help() 530 | return -1 531 | 532 | verbose = options.verbose 533 | track = LocalAudioFile(args[0], verbose=verbose) 534 | 535 | # this is where the work takes place 536 | actions = do_work(track, options) 537 | 538 | if verbose: 539 | display_actions(actions) 540 | print "Output Duration = %.3f sec" % sum(act.duration for act in actions) 541 | 542 | # Send to renderer 543 | name = os.path.splitext(os.path.basename(args[0])) 544 | 545 | # Output wav for loops in order to remain sample accurate 546 | if bool(options.infinite) == True: 547 | name = name[0]+'_'+str(int(options.duration))+'_loop.wav' 548 | elif bool(options.shortest) == True: 549 | name = name[0]+'_'+str(int(sum(act.duration for act in actions)))+'_shortest.wav' 550 | elif bool(options.longest) == True: 551 | name = name[0]+'_'+str(int(sum(act.duration for act in actions)))+'_longest.wav' 552 | else: 553 | name = name[0]+'_'+str(int(options.duration))+'.mp3' 554 | 555 | if options.verbose: 556 | print "Rendering..." 557 | render(actions, name, verbose=verbose) 558 | return 1 559 | 560 | 561 | if __name__ == "__main__": 562 | main() 563 | -------------------------------------------------------------------------------- /earworm/earworm_support.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | """ 5 | earworm_support.py 6 | 7 | Created by Tristan Jehan and Jason Sundram. 8 | """ 9 | 10 | import numpy as np 11 | from copy import deepcopy 12 | from utils import rows 13 | 14 | FUSION_INTERVAL = .06 # This is what we use in the analyzer 15 | AVG_PEAK_OFFSET = 0.025 # Estimated time between onset and peak of segment. 16 | 17 | 18 | def evaluate_distance(mat1, mat2): 19 | return np.linalg.norm(mat1.flatten() - mat2.flatten()) 20 | 21 | def timbre_whiten(mat): 22 | if rows(mat) < 2: return mat 23 | m = np.zeros((rows(mat), 12), dtype=np.float32) 24 | m[:,0] = mat[:,0] - np.mean(mat[:,0],0) 25 | m[:,0] = m[:,0] / np.std(m[:,0],0) 26 | m[:,1:] = mat[:,1:] - np.mean(mat[:,1:].flatten(),0) 27 | m[:,1:] = m[:,1:] / np.std(m[:,1:].flatten(),0) # use this! 28 | return m 29 | 30 | 31 | def get_central(analysis, member='segments'): 32 | """ Returns a tuple: 33 | 1) copy of the members (e.g. segments) between end_of_fade_in and start_of_fade_out. 34 | 2) the index of the first retained member. 35 | """ 36 | def central(s): 37 | return analysis.end_of_fade_in <= s.start and (s.start + s.duration) < analysis.start_of_fade_out 38 | 39 | members = getattr(analysis, member) 40 | ret = filter(central, members[:]) 41 | index = members.index(ret[0]) if ret else 0 42 | 43 | return ret, index 44 | 45 | 46 | def get_mean_offset(segments, markers): 47 | if segments == markers: 48 | return 0 49 | 50 | index = 0 51 | offsets = [] 52 | try: 53 | for marker in markers: 54 | while segments[index].start < marker.start + FUSION_INTERVAL: 55 | offset = abs(marker.start - segments[index].start) 56 | if offset < FUSION_INTERVAL: 57 | offsets.append(offset) 58 | index += 1 59 | except IndexError, e: 60 | pass 61 | 62 | return np.average(offsets) if offsets else AVG_PEAK_OFFSET 63 | 64 | 65 | def resample_features(data, rate='tatums', feature='timbre'): 66 | """ 67 | Resample segment features to a given rate within fade boundaries. 68 | @param data: analysis object. 69 | @param rate: one of the following: segments, tatums, beats, bars. 70 | @param feature: either timbre or pitch. 71 | @return A dictionary including a numpy matrix of size len(rate) x 12, a rate, and an index 72 | """ 73 | ret = {'rate': rate, 'index': 0, 'cursor': 0, 'matrix': np.zeros((1, 12), dtype=np.float32)} 74 | segments, ind = get_central(data.analysis, 'segments') 75 | markers, ret['index'] = get_central(data.analysis, rate) 76 | 77 | if len(segments) < 2 or len(markers) < 2: 78 | return ret 79 | 80 | # Find the optimal attack offset 81 | meanOffset = get_mean_offset(segments, markers) 82 | # Make a copy for local use 83 | tmp_markers = deepcopy(markers) 84 | 85 | # Apply the offset 86 | for m in tmp_markers: 87 | m.start -= meanOffset 88 | if m.start < 0: m.start = 0 89 | 90 | # Allocate output matrix, give it alias mat for convenience. 91 | mat = ret['matrix'] = np.zeros((len(tmp_markers)-1, 12), dtype=np.float32) 92 | 93 | # Find the index of the segment that corresponds to the first marker 94 | f = lambda x: tmp_markers[0].start < x.start + x.duration 95 | index = (i for i,x in enumerate(segments) if f(x)).next() 96 | 97 | # Do the resampling 98 | try: 99 | for (i, m) in enumerate(tmp_markers): 100 | while segments[index].start + segments[index].duration < m.start + m.duration: 101 | dur = segments[index].duration 102 | if segments[index].start < m.start: 103 | dur -= m.start - segments[index].start 104 | 105 | C = min(dur / m.duration, 1) 106 | 107 | mat[i, 0:12] += C * np.array(getattr(segments[index], feature)) 108 | index += 1 109 | 110 | C = min( (m.duration + m.start - segments[index].start) / m.duration, 1) 111 | mat[i, 0:12] += C * np.array(getattr(segments[index], feature)) 112 | except IndexError, e: 113 | pass # avoid breaking with index > len(segments) 114 | 115 | return ret -------------------------------------------------------------------------------- /earworm/utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | """ 4 | utils.py 5 | 6 | Created by Jason Sundram, on 2010-04-05. 7 | """ 8 | 9 | def flatten(l): 10 | """ Converts a list of tuples to a flat list. 11 | e.g. flatten([(1,2), (3,4)]) => [1,2,3,4] 12 | """ 13 | return [item for pair in l for item in pair] 14 | 15 | def tuples(l, n=2): 16 | """ returns n-tuples from l. 17 | e.g. tuples(range(4), n=2) -> [(0, 1), (1, 2), (2, 3)] 18 | """ 19 | return zip(*[l[i:] for i in range(n)]) 20 | 21 | def rows(m): 22 | """returns the # of rows in a numpy matrix""" 23 | return m.shape[0] -------------------------------------------------------------------------------- /filter/filter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env/python 2 | #encoding: utf=8 3 | """ 4 | filter.py 5 | 6 | Filters lists of AudioQuanta (bars, beats, tatums, segments) 7 | by various proporties, and resynthesizes them 8 | 9 | 'pitch' takes an integer a finds chunks that have a pitch maximum in the given index 10 | 'pitches' takes a list of integers (be sure to quote them on the command line: "[0, 4, 7]") 11 | and finds chunks that have pitch maxima in those pitches - a simple chord-finder 12 | 'duration' takes a pair of integers (be sure to quote them on the command line: "[7, 14]") 13 | or floats and finds chunks that overlap / are within that range in time 14 | 'louder' and 'softer' take a float and finds chunks that are louder or softer than the number 15 | (in dBFS, so 0.0 is the loudest) 16 | 17 | By Thor Kell, 2012-11-14 18 | """ 19 | 20 | import echonest.remix.audio as audio 21 | 22 | usage = """ 23 | python filter.py 24 | 25 | """ 26 | def main(units, key, value, input_filename, output_filename): 27 | audiofile = audio.LocalAudioFile(input_filename) 28 | chunks = audiofile.analysis.__getattribute__(units) 29 | 30 | if key == 'pitch': 31 | value = int(value); 32 | if key == 'pitches': 33 | value = eval(value) 34 | if type(value) != list: 35 | print usage 36 | sys.exit(-1) 37 | if key == 'duration': 38 | value = eval(value) 39 | duration_start = value[0] 40 | duration_end = value[1] 41 | if key == 'louder' or key == 'softer': 42 | value = float(value) 43 | 44 | filtered_chunks = [] 45 | for chunk in chunks: 46 | if key == 'pitch': 47 | pitches = chunk.mean_pitches() 48 | if pitches.index(max(pitches)) == value: 49 | filtered_chunks.append(chunk) 50 | 51 | if key == 'pitches': 52 | max_indexes = [] 53 | pitches = chunk.mean_pitches() 54 | max_pitches = sorted(pitches, reverse=True) 55 | for pitch in max_pitches: 56 | max_indexes.append(pitches.index(pitch)) 57 | 58 | if set(value) == set(max_indexes[0:len(value)]): 59 | filtered_chunks.append(chunk) 60 | 61 | if key == 'duration': 62 | if chunk.start < duration_end and chunk.end > duration_start: 63 | filtered_chunks.append(chunk) 64 | elif chunk.start > duration_end: 65 | break 66 | 67 | if key == 'louder': 68 | if chunk.mean_loudness() > value: 69 | filtered_chunks.append(chunk) 70 | 71 | if key == 'softer': 72 | if chunk.mean_loudness() < value: 73 | filtered_chunks.append(chunk) 74 | 75 | out = audio.getpieces(audiofile, filtered_chunks) 76 | out.encode(output_filename) 77 | 78 | if __name__ == '__main__': 79 | import sys 80 | try: 81 | unit = sys.argv[1] 82 | key = sys.argv[2] 83 | value = sys.argv[3] 84 | input_filename = sys.argv[4] 85 | output_filename = sys.argv[5] 86 | 87 | except: 88 | print usage 89 | sys.exit(-1) 90 | main(unit, key, value, input_filename, output_filename) 91 | -------------------------------------------------------------------------------- /jingler/jingler.py: -------------------------------------------------------------------------------- 1 | # By Tristan Jehan, based on cowbell.py, itself based on previous jingler code. 2 | # This script takes an mp3 file and produces an mp3 file dubbed with synchronized sleighbells 3 | # and other christmassy sounds. 4 | import numpy 5 | import os 6 | import random 7 | import time 8 | 9 | import echonest.remix.audio as audio 10 | 11 | usage = """ 12 | Usage: 13 | python jingler.py 14 | Example: 15 | python jingler.py aha.mp3 aha_Jingled.mp3 16 | Reference: 17 | http://www.thejingler.com 18 | """ 19 | 20 | # constants 21 | SLEIGH_OFFSET = [-0.2, -0.075] # seconds 22 | SIGNAL_OFFSET = -1.0 # seconds 23 | SANTA_OFFSET = -6.25 # seconds 24 | SLEIGH_THRESHOLD = 0.420 # seconds 25 | MIN_BEAT_DURATION = 0.280 # seconds 26 | MAX_BEAT_DURATION = 0.560 # seconds 27 | LIMITER_THRESHOLD = 29491 # 90% of the dynamic range 28 | LIMITER_COEFF = 0.2 # compresion curve 29 | 30 | # samples 31 | soundsPath = "sounds/" 32 | 33 | snowSantaSounds = map(lambda x: audio.AudioData(os.path.join(soundsPath, "snowSanta%s.wav" % x), sampleRate=44100, numChannels=2), range(1)) 34 | sleighBellSounds = map(lambda x: audio.AudioData(os.path.join(soundsPath, "sleighBell%s.wav" % x), sampleRate=44100, numChannels=2), range(2)) 35 | signalBellSounds = map(lambda x: audio.AudioData(os.path.join(soundsPath, "signalBell%s.wav" % x), sampleRate=44100, numChannels=2), range(3)) 36 | 37 | # some math useful for normalization 38 | def linear(input, in1, in2, out1, out2): 39 | return ((input-in1) / (in2-in1)) * (out2-out1) + out1 40 | 41 | class Jingler: 42 | def __init__(self, input_file): 43 | self.audiofile = audio.LocalAudioFile(input_file) 44 | self.audiofile.data *= linear(self.audiofile.analysis.loudness, 45 | -2, -12, 0.5, 1.5) * 0.75 46 | # Check that there are beats in the song 47 | if len(self.audiofile.analysis.beats) < 5: 48 | print 'not enough beats in this song...' 49 | sys.exit(-2) 50 | self.duration = len(self.audiofile.data) / self.audiofile.sampleRate 51 | 52 | def run(self, out): 53 | # sequence and mix 54 | t1 = time.time() 55 | print "Sequencing and mixing..." 56 | self.sequence(sleighBellSounds) 57 | print "Normalizing audio output..." 58 | #self.limiter() # slow but can be used in place of self.normalize() 59 | self.normalize() # remove this line if you use self.limiter() instead 60 | # at this point self.audiofile.data is down to a int16 array 61 | print "Sequenced, mixed and normalized in %.3f secs" % (time.time() - t1) 62 | # save 63 | t1 = time.time() 64 | print "Encoding mp3..." 65 | self.audiofile.encode(out) 66 | print "Encoded in %.3f secs" % (time.time() - t1) 67 | 68 | def normalize(self): 69 | # simple normalization that prevents clipping. There can be a little bit of a volume drop. 70 | # to prevent volume drops, use self.limiter() instead, however it is slower. 71 | factor = 32767.0 / numpy.max(numpy.absolute(self.audiofile.data.flatten())) 72 | if factor < 1: 73 | # we return to 16 bit arrays 74 | self.audiofile.data = numpy.array(self.audiofile.data * factor, dtype=numpy.int16) 75 | 76 | def sequence(self, chops): 77 | # adjust sounds and durations 78 | if self.audiofile.analysis.beats[4].duration < MIN_BEAT_DURATION: 79 | stride = 2 80 | else: 81 | stride = 1 82 | if self.audiofile.analysis.beats[4].duration > MAX_BEAT_DURATION: 83 | multiplier = 0.5 84 | else: 85 | multiplier = 1 86 | if self.audiofile.analysis.beats[4].duration * stride * multiplier > SLEIGH_THRESHOLD: 87 | sleighBellIndex = 0 88 | else: 89 | sleighBellIndex = 1 90 | # add sleigh bells on the beats 91 | for i in range(0,len(self.audiofile.analysis.beats),stride): 92 | beat = self.audiofile.analysis.beats[i] 93 | # let's put a little bit of jitter in the volume 94 | vol = 0.6 + random.randint(-100, 100) / 1000.0 95 | sample = sleighBellSounds[sleighBellIndex] 96 | # let's stop jingling 5 seconds before the end anyhow 97 | if beat.start + SLEIGH_OFFSET[sleighBellIndex] + 5.0 < self.duration: 98 | self.mix(beat.start + SLEIGH_OFFSET[sleighBellIndex], seg=sample, volume=vol) 99 | if multiplier == 0.5: 100 | self.mix(beat.start + beat.duration/2.0 + SLEIGH_OFFSET[sleighBellIndex], seg=sample, volume=vol/2.0) 101 | # add signal bells on section changes 102 | for section in self.audiofile.analysis.sections[1:]: 103 | sample = signalBellSounds[random.randint(0,1)] 104 | self.mix(start=section.start+SIGNAL_OFFSET, seg=sample, volume=0.5) 105 | # add other signals in case there's some silence at the beginning 106 | if self.audiofile.analysis.end_of_fade_in > 0.5: 107 | sample = signalBellSounds[2] 108 | self.mix(start=max(0,self.audiofile.analysis.end_of_fade_in-1.0), seg=sample, volume=1.0) 109 | # add santa walking at the end of the track 110 | sample = snowSantaSounds[0] 111 | self.mix(start=self.duration+SANTA_OFFSET, seg=sample, volume=1.0) 112 | 113 | def mix(self, start=None, seg=None, volume=0.3, pan=0.): 114 | # this assumes that the audios have the same samplerate/numchannels 115 | startsample = int(start * self.audiofile.sampleRate) 116 | seg = seg[0:] 117 | seg.data *= (volume-(pan*volume), volume+(pan*volume)) # pan and volume 118 | if start > 0 and self.audiofile.data.shape[0] - startsample > seg.data.shape[0]: 119 | self.audiofile.data[startsample:startsample+len(seg.data)] += seg.data[0:] 120 | 121 | def curve_int16(self, x): 122 | return int(numpy.power((x-LIMITER_THRESHOLD)/3276, LIMITER_COEFF) * 3276 + LIMITER_THRESHOLD) 123 | 124 | def limit_int16(self, x): 125 | if x > LIMITER_THRESHOLD: 126 | tmp = self.curve_int16(x) 127 | if tmp > 32767: return 32767 128 | else: return tmp 129 | elif x < -LIMITER_THRESHOLD: 130 | value = -x 131 | tmp = self.curve_int16(value) 132 | if tmp > 32767: return -32767 133 | else: return -tmp 134 | return x 135 | 136 | def limiter(self): 137 | # simple attempt at compressing and limiting the mixed signal to avoid clipping 138 | # this is a bit slower than I would like (roughly 10 secs) but it works fine 139 | vec_limiter = numpy.vectorize(self.limit_int16, otypes=[numpy.int16]) 140 | self.audiofile.data = vec_limiter(self.audiofile.data) 141 | 142 | 143 | def main(inputFilename, outputFilename): 144 | j = Jingler(inputFilename) 145 | print 'jingling...' 146 | j.run(outputFilename) 147 | print 'Done.' 148 | 149 | if __name__ == '__main__': 150 | import sys 151 | try : 152 | inputFilename = sys.argv[1] 153 | outputFilename = sys.argv[2] 154 | except : 155 | print usage 156 | sys.exit(-1) 157 | main(inputFilename, outputFilename) 158 | -------------------------------------------------------------------------------- /jingler/sounds/signalBell0.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/echonest/remix-examples/05e190069e0b25baef9a4259267acfa34f2ed913/jingler/sounds/signalBell0.wav -------------------------------------------------------------------------------- /jingler/sounds/signalBell1.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/echonest/remix-examples/05e190069e0b25baef9a4259267acfa34f2ed913/jingler/sounds/signalBell1.wav -------------------------------------------------------------------------------- /jingler/sounds/signalBell2.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/echonest/remix-examples/05e190069e0b25baef9a4259267acfa34f2ed913/jingler/sounds/signalBell2.wav -------------------------------------------------------------------------------- /jingler/sounds/sleighBell0.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/echonest/remix-examples/05e190069e0b25baef9a4259267acfa34f2ed913/jingler/sounds/sleighBell0.wav -------------------------------------------------------------------------------- /jingler/sounds/sleighBell1.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/echonest/remix-examples/05e190069e0b25baef9a4259267acfa34f2ed913/jingler/sounds/sleighBell1.wav -------------------------------------------------------------------------------- /jingler/sounds/snowSanta0.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/echonest/remix-examples/05e190069e0b25baef9a4259267acfa34f2ed913/jingler/sounds/snowSanta0.wav -------------------------------------------------------------------------------- /limp/lopside.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf=8 3 | 4 | """ 5 | lopside.py 6 | 7 | Cut out the final beat or group of tatums in each bar. 8 | Demonstrates the beat hierarchy navigation in AudioQuantum 9 | 10 | Originally by Adam Lindsay, 2009-01-19. 11 | """ 12 | import echonest.remix.audio as audio 13 | import sys 14 | 15 | usage = """ 16 | Usage: 17 | python lopside.py [tatum|beat] 18 | Beat is selected by default. 19 | 20 | Example: 21 | python lopside.py beat aha.mp3 ahawaltz.mp3 22 | """ 23 | 24 | 25 | def main(units, inputFile, outputFile): 26 | audiofile = audio.LocalAudioFile(inputFile) 27 | collect = audio.AudioQuantumList() 28 | if not audiofile.analysis.bars: 29 | print "No bars found in this analysis!" 30 | print "No output." 31 | sys.exit(-1) 32 | for b in audiofile.analysis.bars[0:-1]: 33 | # all but the last beat 34 | collect.extend(b.children()[0:-1]) 35 | if units.startswith("tatum"): 36 | # all but the last half (round down) of the last beat 37 | half = - (len(b.children()[-1].children()) // 2) 38 | collect.extend(b.children()[-1].children()[0:half]) 39 | # endings were rough, so leave everything after the start 40 | # of the final bar intact: 41 | last = audio.AudioQuantum(audiofile.analysis.bars[-1].start, 42 | audiofile.analysis.duration - 43 | audiofile.analysis.bars[-1].start) 44 | collect.append(last) 45 | out = audio.getpieces(audiofile, collect) 46 | out.encode(outputFile) 47 | 48 | if __name__ == '__main__': 49 | try: 50 | units = sys.argv[-3] 51 | inputFilename = sys.argv[-2] 52 | outputFilename = sys.argv[-1] 53 | except: 54 | print usage 55 | sys.exit(-1) 56 | main(units, inputFilename, outputFilename) 57 | -------------------------------------------------------------------------------- /midi/enToMIDI.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | """ 4 | enToMIDI.py 5 | 6 | Created by Brian Whitman on 2008-11-25. 7 | Copyright (c) 2008 __MyCompanyName__. All rights reserved. 8 | """ 9 | 10 | import sys 11 | import os 12 | import echonest.remix.audio as audio 13 | from copy import copy 14 | from echonest.remix.support import midi 15 | from echonest.remix.support.midi.MidiOutFile import MidiOutFile 16 | from math import pow 17 | 18 | def main(): 19 | # Examples: 20 | # TRLYNOP11DE633DD31 church bells 21 | # TRWMWTX11DE6393849 a7 unt by lithops 22 | # TRMTWYL11DD5A1D829 valley hi by stereolab 23 | #a = audio.ExistingTrack("TRMTWYL11DD5A1D829").analysis 24 | a = audio.LocalAudioFile(sys.argv[1]).analysis 25 | midi = MidiOutFile('output.mid') 26 | midi.header() 27 | midi.start_of_track() 28 | midi.tempo(int(60000000.00 / 60.0)) # 60 BPM, one Q per second, 96 ticks per Q, 96 ticks per second.) 29 | BOOST = 30 # Boost volumes if you want 30 | 31 | # Do you want the channels to be split by timbre or no? 32 | splitChannels = True 33 | 34 | for seg_index in xrange(len(a.segments)): 35 | s = a.segments[seg_index] 36 | 37 | if(splitChannels): 38 | # Figure out a channel to assign this segment to. Let PCA do the work here... we'll just take the sign of coeffs 1->5 as a 4-bit # 39 | bits = [0,0,0,0] 40 | for i in xrange(4): 41 | # Can't use the first coeff because it's (always?) positive. 42 | if(s.timbre[i+1]>=0): bits[i] =1 43 | channel = bits[0]*8 + bits[1]*4 + bits[2]*2 + bits[3]*1 44 | else: 45 | channel = 0 46 | 47 | # Get the loudnesses in MIDI cc #7 vals for the start of the segment, the loudest part, and the start of the next segment. 48 | # db -> voltage ratio http://www.mogami.com/e/cad/db.html 49 | linearMaxVolume = int(pow(10.0,s.loudness_max/20.0)*127.0)+BOOST 50 | linearStartVolume = int(pow(10.0,s.loudness_begin/20.0)*127.0)+BOOST 51 | if(seg_index == len(a.segments)-1): # if this is the last segment 52 | linearNextStartVolume = 0 53 | else: 54 | linearNextStartVolume = int(pow(10.0,a.segments[seg_index+1].loudness_begin/20.0)*127.0)+BOOST 55 | whenMaxVolume = s.time_loudness_max 56 | 57 | # Count the # of ticks I wait in doing the volume ramp so I can fix up rounding errors later. 58 | tt = 0 59 | 60 | # take pitch vector and hit a note on for each pitch at its relative volume. That's 12 notes per segment. 61 | for note in xrange(12): 62 | volume = int(s.pitches[note]*127.0) 63 | midi.update_time(0) 64 | midi.note_on(channel=channel, note=0x3C+note, velocity=volume) 65 | midi.update_time(0) 66 | 67 | # Set volume of this segment. Start at the start volume, ramp up to the max volume , then ramp back down to the next start volume. 68 | curVol = float(linearStartVolume) 69 | 70 | # Do the ramp up to max from start 71 | ticksToMaxLoudnessFromHere = int(96.0 * whenMaxVolume) 72 | if(ticksToMaxLoudnessFromHere > 0): 73 | howMuchVolumeToIncreasePerTick = float(linearMaxVolume - linearStartVolume)/float(ticksToMaxLoudnessFromHere) 74 | for ticks in xrange(ticksToMaxLoudnessFromHere): 75 | midi.continuous_controller(channel,7,int(curVol)) 76 | curVol = curVol + howMuchVolumeToIncreasePerTick 77 | tt = tt + 1 78 | midi.update_time(1) 79 | 80 | # Now ramp down from max to start of next seg 81 | ticksToNextSegmentFromHere = int(96.0 * (s.duration-whenMaxVolume)) 82 | if(ticksToNextSegmentFromHere > 0): 83 | howMuchVolumeToDecreasePerTick = float(linearMaxVolume - linearNextStartVolume)/float(ticksToNextSegmentFromHere) 84 | for ticks in xrange(ticksToNextSegmentFromHere): 85 | curVol = curVol - howMuchVolumeToDecreasePerTick 86 | if curVol < 0: 87 | curVol = 0 88 | midi.continuous_controller(channel, 7 ,int(curVol)) 89 | tt = tt + 1 90 | midi.update_time(1) 91 | 92 | # Account for rounding error if any 93 | midi.update_time(int(96.0*s.duration)-tt) 94 | 95 | # Send the note off 96 | for note in xrange(12): 97 | midi.note_off(channel=channel, note=0x3C+note) 98 | midi.update_time(0) 99 | 100 | midi.update_time(0) 101 | midi.end_of_track() 102 | midi.eof() 103 | 104 | 105 | 106 | if __name__ == '__main__': 107 | main() 108 | 109 | -------------------------------------------------------------------------------- /multi/multitest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf=8 3 | 4 | """ 5 | multitest.py 6 | 7 | Take a whole directory of audio files and smash them together, by 8 | beat, with a fairly simple algorithm. Demonstrates the use of 9 | deferred audio file loading used in conjunction with render_serially(), 10 | which allow one to remix countless files without running out of memory 11 | too soon. 12 | 13 | Originally by Adam Lindsay, 2000-05-05. 14 | """ 15 | 16 | import os, sys 17 | import time 18 | from math import sqrt 19 | 20 | from echonest.remix import audio 21 | 22 | usage = """ 23 | Usage: 24 | python multitest.py 25 | 26 | Example: 27 | python multitest.py ../music mashedbeats.mp3 40 28 | """ 29 | 30 | def main(num_beats, directory, outfile): 31 | 32 | aud = [] 33 | ff = os.listdir(directory) 34 | for f in ff: 35 | # collect the files 36 | if f.rsplit('.', 1)[1].lower() in ['mp3', 'aif', 'aiff', 'aifc', 'wav']: 37 | aud.append(audio.LocalAudioFile(os.path.join(directory,f))) 38 | # mind the rate limit 39 | 40 | num_files = len(aud) 41 | x = audio.AudioQuantumList() 42 | 43 | print >> sys.stderr, "Assembling beats.", 44 | for w in range(num_beats): 45 | print >> sys.stderr, '.', 46 | ssong = aud[w%num_files].analysis 47 | s = ssong.beats[w%len(ssong.beats)] 48 | tsong = aud[(w-1)%num_files].analysis 49 | t = tsong.beats[w%len(tsong.beats)] 50 | 51 | x.append(audio.Simultaneous([s,t])) 52 | 53 | print >> sys.stderr, "\nStarting rendering pass..." 54 | 55 | then = time.time() 56 | # call render_sequentially() with no arguments, and then it calls itself with 57 | # contextual arguments for each source, for each AudioQuantum. It's a lot of 58 | # tree-walking, but each source file gets loaded once (and takes itself from) 59 | # memory when its rendering pass finishes. 60 | x.render().encode(outfile) 61 | 62 | print >> sys.stderr, "%f sec for rendering" % (time.time() - then,) 63 | 64 | if __name__ == '__main__': 65 | try: 66 | directory = sys.argv[-3] 67 | outfile = sys.argv[-2] 68 | num_beats = int(sys.argv[-1]) 69 | except: 70 | print usage 71 | sys.exit(-1) 72 | main(num_beats, directory, outfile) 73 | 74 | -------------------------------------------------------------------------------- /multi/multivideo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf=8 3 | """ 4 | multivideo.py 5 | 6 | Take a whole directory of video files and smash them together, by 7 | beat, with a fairly simple algorithm. 8 | 9 | By Ben Lacker, based on multitest.py by Adam Lindsay. 10 | """ 11 | import os, sys 12 | from math import sqrt 13 | 14 | from echonest.remix import audio, video 15 | 16 | usage = """ 17 | Usage: 18 | python multivideo.py 19 | 20 | Example: 21 | python multivideo.py ../music/SingleLadies.mp3 ../videos mashedbeats.mpg 22 | """ 23 | 24 | def main(infile, directory, outfile): 25 | afile = audio.LocalAudioFile(infile) 26 | av = [] 27 | ff = os.listdir(directory) 28 | for f in ff: 29 | # collect the files 30 | if f.rsplit('.', 1)[1].lower() in ['mp3', 'aif', 'aiff', 'aifc', 'wav', 'mpg', 'flv', 'mov', 'mp4']: 31 | av.append(video.loadav(os.path.join(directory,f))) 32 | num_files = len(av) 33 | # not sure the best way to handle these settings 34 | newv = video.EditableFrames(settings=av[0].video.settings) 35 | print >> sys.stderr, "Assembling beats.", 36 | for i, beat in enumerate(afile.analysis.beats): 37 | print >> sys.stderr, '.', 38 | vid = av[i%num_files] 39 | if beat.end > vid.audio.duration: 40 | # do something smart 41 | continue 42 | newv += vid.video[beat] 43 | outav = video.SynchronizedAV(audio=afile, video=newv) 44 | outav.save(outfile) 45 | 46 | if __name__ == '__main__': 47 | try: 48 | infile = sys.argv[-3] 49 | directory = sys.argv[-2] 50 | outfile = sys.argv[-1] 51 | except: 52 | print usage 53 | sys.exit(-1) 54 | main(infile, directory, outfile) 55 | -------------------------------------------------------------------------------- /music/Hiiragi_Fukuda-Open_Fields_Blues.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/echonest/remix-examples/05e190069e0b25baef9a4259267acfa34f2ed913/music/Hiiragi_Fukuda-Open_Fields_Blues.mp3 -------------------------------------------------------------------------------- /music/Karl_Blau-Gnos_Levohs.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/echonest/remix-examples/05e190069e0b25baef9a4259267acfa34f2ed913/music/Karl_Blau-Gnos_Levohs.mp3 -------------------------------------------------------------------------------- /music/Raleigh_Moncrief-Guppies.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/echonest/remix-examples/05e190069e0b25baef9a4259267acfa34f2ed913/music/Raleigh_Moncrief-Guppies.mp3 -------------------------------------------------------------------------------- /music/Tracky_Birthday-Newish_Disco.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/echonest/remix-examples/05e190069e0b25baef9a4259267acfa34f2ed913/music/Tracky_Birthday-Newish_Disco.mp3 -------------------------------------------------------------------------------- /music/attribution.txt: -------------------------------------------------------------------------------- 1 | Hiiragi Fukuda - "Open Fields Blues" 2 | http://freemusicarchive.org/music/hiiragi_fukuda/The_Stable_At_Dawn/01_01 3 | Licensed under a Attribution-NonCommercial-ShareAlike License. 4 | 5 | Karl Blau - "Gnos Levohs" 6 | http://freemusicarchive.org/music/Karl_Blau/Zebra/12_Gnos_Levohs 7 | Licensed under a Attribution-Noncommercial-Share Alike 3.0 United States License. 8 | 9 | Raleigh Moncrief - "Guppies" 10 | http://freemusicarchive.org/music/Raleigh_Moncrief/Vitamins_EP/Raleigh_Moncrief_-_Vitamins_EP_-_02_Guppies 11 | Licensed under a Attribution-NonCommercial License. 12 | 13 | Tracky_Birthday - "Newish_Disco" 14 | http://freemusicarchive.org/music/Tracky_Birthday/Animal_Audition/Newish_Disco 15 | Licensed under a Attribution-Noncommercial-Share Alike 3.0 United States License. 16 | -------------------------------------------------------------------------------- /one/one.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf=8 3 | """ 4 | one.py 5 | 6 | Digest only the first beat of every bar. 7 | 8 | By Ben Lacker, 2009-02-18. 9 | """ 10 | import echonest.remix.audio as audio 11 | 12 | usage = """ 13 | Usage: 14 | python one.py 15 | 16 | Example: 17 | python one.py EverythingIsOnTheOne.mp3 EverythingIsReallyOnTheOne.mp3 18 | """ 19 | 20 | def main(input_filename, output_filename): 21 | audiofile = audio.LocalAudioFile(input_filename) 22 | bars = audiofile.analysis.bars 23 | collect = audio.AudioQuantumList() 24 | for bar in bars: 25 | collect.append(bar.children()[0]) 26 | out = audio.getpieces(audiofile, collect) 27 | out.encode(output_filename) 28 | 29 | if __name__ == '__main__': 30 | import sys 31 | try: 32 | input_filename = sys.argv[1] 33 | output_filename = sys.argv[2] 34 | except: 35 | print usage 36 | sys.exit(-1) 37 | main(input_filename, output_filename) 38 | -------------------------------------------------------------------------------- /quanta/quanta.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf=8 3 | 4 | """ 5 | quanta.py 6 | 7 | Insert silences between a song's segments, tatums, beats, or sections. 8 | Demonstrates the Remix API's handling of audio quanta. 9 | 10 | By Ben Lacker 2009-3-4, updated by Thor Kell, 2012-9-26 11 | """ 12 | import sys 13 | 14 | import echonest.remix.audio as audio 15 | 16 | usage = """ 17 | Usage: 18 | python quanta.py [e] 19 | 20 | Example: 21 | python quanta.py beats SingleLadies.mp3 SingleLadiesBeats.mp3 22 | 23 | The 'e' flag, inserts a silence equal in duration to the preceding audio quantum. 24 | Otherwise, each silence is one second long. 25 | """ 26 | 27 | ACCEPTED_UNITS = ["segments", "tatums", "beats", "bars", "sections"] 28 | 29 | def main(input_filename, output_filename, units, equal_silence): 30 | audio_file = audio.LocalAudioFile(input_filename) 31 | chunks = audio_file.analysis.__getattribute__(units) 32 | num_channels = audio_file.numChannels 33 | sample_rate = audio_file.sampleRate 34 | if equal_silence: 35 | new_shape = ((audio_file.data.shape[0] * 2) + 100000, 36 | audio_file.data.shape[1]) 37 | else: 38 | new_shape = (audio_file.data.shape[0]+(len(chunks) * 44100) + 10000, 39 | audio_file.data.shape[1]) 40 | out = audio.AudioData(shape=new_shape, sampleRate=sample_rate, numChannels=num_channels) 41 | 42 | for chunk in chunks: 43 | chunk_data = audio_file[chunk] 44 | if equal_silence: 45 | silence_shape = chunk_data.data.shape 46 | else: 47 | silence_shape = (44100, audio_file.data.shape[1]) 48 | silence = audio.AudioData(shape=silence_shape, sampleRate=sample_rate, numChannels=num_channels) 49 | silence.endindex = silence.data.shape[0] 50 | 51 | out.append(chunk_data) 52 | out.append(silence) 53 | out.encode(output_filename) 54 | 55 | if __name__ == '__main__': 56 | try: 57 | units = sys.argv[1] 58 | input_filename = sys.argv[2] 59 | output_filename = sys.argv[3] 60 | if len(sys.argv) == 5: 61 | equal_silence = True 62 | else: 63 | equal_silence = False 64 | except: 65 | print usage 66 | sys.exit(-1) 67 | if not units in ACCEPTED_UNITS: 68 | print usage 69 | sys.exit(-1) 70 | main(input_filename, output_filename, units, equal_silence) 71 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Welcome to Echo Nest Remix - the Examples. 2 | 3 | Echo Nest Remix is **the Internet Synthesizer.** 4 | Make amazing things from music, automatically. Turn any music or video into Python, Flash, or Javascript code. 5 | 6 | This repo contains every known Remix example - the main repo, that you can install or fork the library from, is at 7 | 8 | 9 | -![alt text](http://i.imgur.com/WWLYo.gif "Frustrated cat wants examples!") 10 | -------------------------------------------------------------------------------- /reverse/reverse.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf=8 3 | 4 | """ 5 | reverse.py 6 | 7 | Reverse the beats or segments of a song. 8 | 9 | Originally created by Robert Ochshorn on 2008-06-11. Refactored by 10 | Joshua Lifton 2008-09-07. 11 | """ 12 | 13 | import echonest.remix.audio as audio 14 | 15 | usage = """ 16 | Usage: 17 | python reverse.py 18 | 19 | Example: 20 | python reverse.py beats YouCanCallMeAl.mp3 AlMeCallCanYou.mp3 21 | """ 22 | 23 | def main(toReverse, inputFilename, outputFilename): 24 | audioFile = audio.LocalAudioFile(inputFilename) 25 | if toReverse == 'beats' : 26 | chunks = audioFile.analysis.beats 27 | elif toReverse == 'segments' : 28 | chunks = audioFile.analysis.segments 29 | else : 30 | print usage 31 | return 32 | chunks.reverse() 33 | reversedAudio = audio.getpieces(audioFile, chunks) 34 | reversedAudio.encode(outputFilename) 35 | 36 | if __name__ == '__main__': 37 | import sys 38 | try : 39 | toReverse = sys.argv[1] 40 | inputFilename = sys.argv[2] 41 | outputFilename = sys.argv[3] 42 | except : 43 | print usage 44 | sys.exit(-1) 45 | if not toReverse in ["beats", "segments"]: 46 | print usage 47 | sys.exit(-1) 48 | main(toReverse, inputFilename, outputFilename) 49 | -------------------------------------------------------------------------------- /rhythmstring/rhythmstring.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | """ 4 | rhythmstring.py 5 | 6 | Computes and prints a rhythm vector for a file - compare with beat starts. 7 | 8 | The rhythm vector presents eight channels of onsets. 9 | Each channel represents a band of frequency from 0 to 5512.5 Hz. 10 | (Specifically, these are the 8 lowest bands in the MPEG-Audio 32 band filterbank.) 11 | 12 | Created by Tristan Jehan on 2013-08-12, refactored by Thor Kell on 2014-10 13 | Copyright (c) 2013 The Echo Nest. All rights reserved. 14 | """ 15 | 16 | import sys, os 17 | import numpy as np 18 | import zlib, base64 19 | import json 20 | import cPickle as pkl 21 | import echonest.remix.audio as audio 22 | 23 | usage = """ 24 | Usage: 25 | python rhythmstring.py 26 | 27 | Example: 28 | python rhythmstring.py IveGotRhythm.mp3 29 | """ 30 | 31 | # The rhythmstring format goes as follows: 32 | # Fs Hop Nch ... 33 | 34 | # where: 35 | # Fs: sampling rate 36 | # Hop: hop size 37 | # Nch: number of echoprint channels 38 | # Cm: channel index 39 | # Nos: number of onsets 40 | # Oi: intial onset frame 41 | # do_n: number of frames to the next onset 42 | 43 | 44 | def get_beat_starts(analysis): 45 | beat_starts = [] 46 | for b in analysis.beats: 47 | beat_starts.append(float(b.start)) 48 | return beat_starts 49 | 50 | def decode_string(s): 51 | if s == None or s == "": 52 | return None 53 | l = [int(i) for i in s.split(' ')] 54 | Fs = l.pop(0) 55 | Hop = l.pop(0) 56 | frame_duration = float(Hop)/float(Fs) 57 | Nch = l.pop(0) 58 | onsets = [] 59 | n = 0 60 | for i in range(Nch): 61 | N = l[n] 62 | n += 1 63 | for j in range(N): 64 | if j == 0: ons = [l[n]] 65 | else: ons.append(ons[j-1] + l[n]) 66 | n += 1 67 | onsets.append(ons) 68 | new_onsets = [] 69 | for ons in onsets: 70 | new_ons = [] 71 | for o in ons: 72 | new_ons.append(o*frame_duration) 73 | new_onsets.append(new_ons) 74 | return new_onsets 75 | 76 | def decompress_string(compressed_string): 77 | compressed_string = compressed_string.encode('utf8') 78 | if compressed_string == "": 79 | return None 80 | try: 81 | actual_string = zlib.decompress(base64.urlsafe_b64decode(compressed_string)) 82 | except (zlib.error, TypeError): 83 | print "Could not decode base64 zlib string %s" % (compressed_string) 84 | return None 85 | return actual_string 86 | 87 | def get_rhythm_data(analysis): 88 | beat_starts = get_beat_starts(analysis) 89 | rhythmstring = analysis.rhythmstring 90 | decompressed_string = decompress_string(rhythmstring) 91 | onsets = decode_string(decompressed_string) 92 | return beat_starts, onsets 93 | 94 | def get_rhythm(analysis): 95 | onsets = get_rhythm_data(analysis) 96 | return onsets 97 | 98 | def main(input_filename): 99 | audiofile = audio.LocalAudioFile(input_filename) 100 | onsets = get_rhythm(audiofile.analysis) 101 | print onsets 102 | 103 | if __name__ == '__main__': 104 | import sys 105 | try: 106 | input_filename = sys.argv[1] 107 | except: 108 | print usage 109 | sys.exit(-1) 110 | main(input_filename) 111 | 112 | -------------------------------------------------------------------------------- /save/save.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf=8 3 | """ 4 | save.py 5 | 6 | Save an Echo Nest analysis as a local file. You can save analysis by using .save() 7 | You can then load them by simply calling audio.LocalAudioFile('the_file.analysis.en') 8 | 9 | Note that save() saves to the same location as the initial file, and 10 | creates a corresponding .wav file. Moving the .wav file will break things! 11 | 12 | By Thor Kell, 10-2012 13 | """ 14 | 15 | usage = """ 16 | Usage: 17 | python save.py 18 | 19 | Example: 20 | python save.py SaveMe.mp3 21 | """ 22 | 23 | import echonest.remix.audio as audio 24 | 25 | def main(input_filename): 26 | audiofile = audio.LocalAudioFile(input_filename) 27 | audiofile.save() 28 | print "Saved anaylsis for %s" % input_filename 29 | 30 | 31 | if __name__ == '__main__': 32 | import sys 33 | try: 34 | input_filename = sys.argv[1] 35 | except: 36 | print usage 37 | sys.exit(-1) 38 | main(input_filename) 39 | 40 | -------------------------------------------------------------------------------- /selection/tonic.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf=8 3 | 4 | """ 5 | tonic.py 6 | 7 | Digest all beats, tatums, or bars that start in the key of the song. 8 | Demonstrates content-based selection filtering via AudioQuantumLists 9 | 10 | Originally by Adam Lindsay, 2008-09-15. 11 | Refactored by Thor Kell, 2012-11-01 12 | """ 13 | import echonest.remix.audio as audio 14 | 15 | usage = """ 16 | Usage: 17 | python tonic.py 18 | 19 | Example: 20 | python tonic.py beats HereComesTheSun.mp3 HereComesTheTonic.mp3 21 | """ 22 | 23 | ACCEPTED_UNITS = ["tatums", "beats", "bars"] 24 | 25 | def main(units, inputFile, outputFile): 26 | audiofile = audio.LocalAudioFile(inputFile) 27 | tonic = audiofile.analysis.key['value'] 28 | 29 | chunks = audiofile.analysis.__getattribute__(units) 30 | 31 | # Get the segments 32 | all_segments = audiofile.analysis.segments 33 | 34 | # Find tonic segments 35 | tonic_segments = audio.AudioQuantumList(kind="segment") 36 | for segment in all_segments: 37 | pitches = segment.pitches 38 | if pitches.index(max(pitches)) == tonic: 39 | tonic_segments.append(segment) 40 | 41 | # Find each chunk that matches each segment 42 | out_chunks = audio.AudioQuantumList(kind=units) 43 | for chunk in chunks: 44 | for segment in tonic_segments: 45 | if chunk.start >= segment.start and segment.end >= chunk.start: 46 | out_chunks.append(chunk) 47 | break 48 | 49 | out = audio.getpieces(audiofile, out_chunks) 50 | out.encode(outputFile) 51 | 52 | if __name__ == '__main__': 53 | import sys 54 | try: 55 | units = sys.argv[-3] 56 | inputFilename = sys.argv[-2] 57 | outputFilename = sys.argv[-1] 58 | except: 59 | print usage 60 | sys.exit(-1) 61 | if not units in ACCEPTED_UNITS: 62 | print usage 63 | sys.exit(-1) 64 | main(units, inputFilename, outputFilename) 65 | -------------------------------------------------------------------------------- /sorting/sorting.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env/python 2 | #encoding: utf=8 3 | """ 4 | sorting.py 5 | 6 | Sorts AudioQuanta (bars, beats, tatums, segments) by various qualities, and resynthesizes them. 7 | 8 | By Thor Kell, 2012-11-02 9 | """ 10 | import echonest.remix.audio as audio 11 | usage = """ 12 | python sorting.py [reverse] 13 | 14 | """ 15 | def main(units, key, input_filename, output_filename): 16 | audiofile = audio.LocalAudioFile(input_filename) 17 | chunks = audiofile.analysis.__getattribute__(units) 18 | 19 | # Define the sorting function 20 | if key == 'duration': 21 | def sorting_function(chunk): 22 | return chunk.duration 23 | 24 | if key == 'confidence': 25 | def sorting_function(chunk): 26 | if units != 'segments': 27 | return chunk.confidence 28 | else: 29 | # Segments have no confidence, so we grab confidence from the tatum 30 | return chunk.tatum.confidence 31 | 32 | if key == 'loudness': 33 | def sorting_function(chunk): 34 | return chunk.mean_loudness() 35 | 36 | sorted_chunks = sorted(chunks, key=sorting_function, reverse=reverse) 37 | 38 | out = audio.getpieces(audiofile, sorted_chunks) 39 | out.encode(output_filename) 40 | 41 | if __name__ == '__main__': 42 | import sys 43 | try: 44 | unit = sys.argv[1] 45 | key = sys.argv[2] 46 | input_filename = sys.argv[3] 47 | output_filename = sys.argv[4] 48 | if len(sys.argv) == 6 and sys.argv[5] == 'reverse': 49 | reverse = True 50 | else: 51 | reverse = False 52 | except: 53 | print usage 54 | sys.exit(-1) 55 | main(unit, key, input_filename, output_filename) 56 | -------------------------------------------------------------------------------- /sorting/sorting_pitch.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env/python 2 | #encoding: utf=8 3 | """ 4 | sorting_pitch.py 5 | 6 | Sorts AudioQuanta (bars, beats, tatums, segments) by the maximum pitch. 7 | Results can be modded by 0-11 to sort relative to C, C#, D, D#, etc. 8 | 9 | By Thor Kell, 2012-11-02 10 | """ 11 | 12 | import echonest.remix.audio as audio 13 | usage = """ 14 | python sorting_pitch.py <0-11> [reverse] 15 | 16 | """ 17 | def main(units, key, input_filename, output_filename): 18 | audiofile = audio.LocalAudioFile(input_filename) 19 | chunks = audiofile.analysis.__getattribute__(units) 20 | key = int(key) 21 | 22 | def sorting_function(chunk): 23 | pitches = chunk.mean_pitches() 24 | return pitches.index(max(pitches)) - key % 12 25 | 26 | sorted_chunks = sorted(chunks, key=sorting_function, reverse=reverse) 27 | 28 | out = audio.getpieces(audiofile, sorted_chunks) 29 | out.encode(output_filename) 30 | 31 | if __name__ == '__main__': 32 | import sys 33 | try: 34 | unit = sys.argv[1] 35 | key = sys.argv[2] 36 | input_filename = sys.argv[3] 37 | output_filename = sys.argv[4] 38 | if len(sys.argv) == 6 and sys.argv[5] == 'reverse': 39 | reverse = True 40 | else: 41 | reverse = False 42 | 43 | except: 44 | print usage 45 | sys.exit(-1) 46 | main(unit, key, input_filename, output_filename) 47 | -------------------------------------------------------------------------------- /sorting/sorting_timbre.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env/python 2 | #encoding: utf=8 3 | """ 4 | sorting_timbre.py 5 | 6 | Sorts AudioQuanta (bars, beats, tatums, segments) by timbral bin (0-11). 7 | By Thor Kell, 2012-11-14 8 | """ 9 | 10 | import echonest.remix.audio as audio 11 | usage = """ 12 | python sorting.py <0-11> [reverse] 13 | 14 | """ 15 | def main(units, timbre_bin, input_filename, output_filename): 16 | audiofile = audio.LocalAudioFile(input_filename) 17 | chunks = audiofile.analysis.__getattribute__(units) 18 | timbre_bin = int(timbre_bin) 19 | 20 | # For any chunk, return the timbre value of the given bin 21 | def sorting_function(chunk): 22 | timbre = chunk.mean_timbre() 23 | return timbre[timbre_bin] 24 | 25 | sorted_chunks = sorted(chunks, key=sorting_function, reverse=reverse) 26 | 27 | import pdb 28 | #pdb.set_trace() 29 | 30 | out = audio.getpieces(audiofile, sorted_chunks) 31 | out.encode(output_filename) 32 | 33 | if __name__ == '__main__': 34 | import sys 35 | try: 36 | unit = sys.argv[1] 37 | timbre_bin = sys.argv[2] 38 | input_filename = sys.argv[3] 39 | output_filename = sys.argv[4] 40 | if len(sys.argv) == 6 and sys.argv[5] == 'reverse': 41 | reverse = True 42 | else: 43 | reverse = False 44 | 45 | except: 46 | print usage 47 | sys.exit(-1) 48 | main(unit, timbre_bin, input_filename, output_filename) 49 | -------------------------------------------------------------------------------- /step/step-by-pitch.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf=8 3 | 4 | """ 5 | step.py 6 | 7 | For each bar, take one of the nearest (in timbre) beats 8 | to the last beat, chosen from all of the beats that fall 9 | on the one. Repeat for all the twos, etc. 10 | 11 | The variation parameter, (_v_) means there's a roughly 12 | one in _v_ chance that the actual next beat is chosen. The 13 | length is the length in bars you want it to go on. 14 | 15 | Originally by Adam Lindsay, 2009-03-10. 16 | Refactored by Thor Kell, 2012-12-12 17 | """ 18 | 19 | import random 20 | import numpy 21 | import echonest.remix.audio as audio 22 | 23 | usage = """ 24 | Usage: 25 | python step.py inputFilename outputFilename [variation [length]] 26 | 27 | variation is the number of near candidates chosen from. [default=4] 28 | length is the number of bars in the final product. [default=40] 29 | 30 | Example: 31 | python step.py Discipline.mp3 Undisciplined.mp3 4 100 32 | """ 33 | 34 | def main(infile, outfile, choices=4, bars=40): 35 | audiofile = audio.LocalAudioFile(infile) 36 | meter = audiofile.analysis.time_signature['value'] 37 | fade_in = audiofile.analysis.end_of_fade_in 38 | fade_out = audiofile.analysis.start_of_fade_out 39 | 40 | beats = [] 41 | for b in audiofile.analysis.beats: 42 | if b.start > fade_in or b.end < fade_out: 43 | beats.append(b) 44 | output = audio.AudioQuantumList() 45 | 46 | beat_array = [] 47 | for m in range(meter): 48 | metered_beats = [] 49 | for b in beats: 50 | if beats.index(b) % meter == m: 51 | metered_beats.append(b) 52 | beat_array.append(metered_beats) 53 | 54 | # Always start with the first beat 55 | output.append(beat_array[0][0]); 56 | for x in range(1, bars * meter): 57 | meter_index = x % meter 58 | next_candidates = beat_array[meter_index] 59 | 60 | def sorting_function(chunk, target_chunk=output[-1]): 61 | timbre = chunk.mean_pitches() 62 | target_timbre = target_chunk.mean_pitches() 63 | timbre_distance = numpy.linalg.norm(numpy.array(timbre) - numpy.array(target_timbre)) 64 | return timbre_distance 65 | 66 | next_candidates = sorted(next_candidates, key=sorting_function) 67 | next_index = random.randint(0, min(choices, len(next_candidates) -1 )) 68 | output.append(next_candidates[next_index]) 69 | 70 | out = audio.getpieces(audiofile, output) 71 | out.encode(outfile) 72 | 73 | 74 | if __name__ == '__main__': 75 | import sys 76 | try: 77 | inputFilename = sys.argv[1] 78 | outputFilename = sys.argv[2] 79 | if len(sys.argv) > 3: 80 | variation = int(sys.argv[3]) 81 | else: 82 | variation = 4 83 | if len(sys.argv) > 4: 84 | length = int(sys.argv[4]) 85 | else: 86 | length = 40 87 | except: 88 | print usage 89 | sys.exit(-1) 90 | main(inputFilename, outputFilename, variation, length) 91 | -------------------------------------------------------------------------------- /step/step-by-section.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf=8 3 | 4 | """ 5 | step.py 6 | 7 | For each bar, take one of the nearest (in timbre) beats 8 | to the last beat, chosen from all of the beats that fall 9 | on the one in this section. Repeat for all the twos, etc. 10 | 11 | This version divides things by section, retaining the 12 | structure and approximate length of the original. The 13 | variation parameter, (_v_) means there's a roughly 14 | one in _v_ chance that the actual next beat is chosen. A 15 | musical M-x dissociated-press. 16 | 17 | Originally by Adam Lindsay, 2009-03-10. 18 | Refactored by Thor Kell, 2012-12-12 19 | """ 20 | 21 | import random 22 | import numpy 23 | import echonest.remix.audio as audio 24 | 25 | usage = """ 26 | Usage: 27 | python step.py inputFilename outputFilename [variation] 28 | 29 | variation is the number of near candidates chosen from. [default=4] 30 | 31 | Example: 32 | python step.py Discipline.mp3 Undisciplined.mp3 4 33 | """ 34 | 35 | def main(infile, outfile, choices=4): 36 | audiofile = audio.LocalAudioFile(infile) 37 | meter = audiofile.analysis.time_signature['value'] 38 | sections = audiofile.analysis.sections 39 | output = audio.AudioQuantumList() 40 | 41 | for section in sections: 42 | beats = [] 43 | bars = section.children() 44 | for bar in bars: 45 | beats.extend(bar.children()) 46 | 47 | beat_array = [] 48 | for m in range(meter): 49 | metered_beats = [] 50 | for b in beats: 51 | if beats.index(b) % meter == m: 52 | metered_beats.append(b) 53 | beat_array.append(metered_beats) 54 | 55 | # Always start with the first beat 56 | output.append(beat_array[0][0]); 57 | for x in range(1, len(bars) * meter): 58 | meter_index = x % meter 59 | next_candidates = beat_array[meter_index] 60 | 61 | def sorting_function(chunk, target_chunk=output[-1]): 62 | timbre = chunk.mean_timbre() 63 | target_timbre = target_chunk.mean_timbre() 64 | timbre_distance = numpy.linalg.norm(numpy.array(timbre) - numpy.array(target_timbre)) 65 | return timbre_distance 66 | 67 | next_candidates = sorted(next_candidates, key=sorting_function) 68 | next_index = random.randint(0, min(choices, len(next_candidates) - 1)) 69 | output.append(next_candidates[next_index]) 70 | 71 | out = audio.getpieces(audiofile, output) 72 | out.encode(outfile) 73 | 74 | 75 | if __name__ == '__main__': 76 | import sys 77 | try: 78 | inputFilename = sys.argv[1] 79 | outputFilename = sys.argv[2] 80 | if len(sys.argv) > 3: 81 | variation = int(sys.argv[3]) 82 | else: 83 | variation = 4 84 | except: 85 | print usage 86 | sys.exit(-1) 87 | main(inputFilename, outputFilename, variation) 88 | -------------------------------------------------------------------------------- /step/step.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf=8 3 | 4 | """ 5 | step.py 6 | 7 | For each bar, take one of the nearest (in timbre) beats 8 | to the last beat, chosen from all of the beats that fall 9 | on the one. Repeat for all the twos, etc. 10 | 11 | The variation parameter, (_v_) means there's a roughly 12 | one in _v_ chance that the actual next beat is chosen. The 13 | length is the length in bars you want it to go on. 14 | 15 | Originally by Adam Lindsay, 2009-03-10. 16 | Refactored by Thor Kell, 2012-12-12 17 | """ 18 | 19 | import random 20 | import numpy 21 | import echonest.remix.audio as audio 22 | 23 | usage = """ 24 | Usage: 25 | python step.py inputFilename outputFilename [variation [length]] 26 | 27 | variation is the number of near candidates chosen from. [default=4] 28 | length is the number of bars in the final product. [default=40] 29 | 30 | Example: 31 | python step.py Discipline.mp3 Undisciplined.mp3 4 100 32 | """ 33 | 34 | def main(infile, outfile, choices=4, bars=40): 35 | audiofile = audio.LocalAudioFile(infile) 36 | meter = audiofile.analysis.time_signature['value'] 37 | fade_in = audiofile.analysis.end_of_fade_in 38 | fade_out = audiofile.analysis.start_of_fade_out 39 | 40 | beats = [] 41 | for b in audiofile.analysis.beats: 42 | if b.start > fade_in or b.end < fade_out: 43 | beats.append(b) 44 | output = audio.AudioQuantumList() 45 | 46 | beat_array = [] 47 | for m in range(meter): 48 | metered_beats = [] 49 | for b in beats: 50 | if beats.index(b) % meter == m: 51 | metered_beats.append(b) 52 | beat_array.append(metered_beats) 53 | 54 | # Always start with the first beat 55 | output.append(beat_array[0][0]); 56 | for x in range(1, bars * meter): 57 | meter_index = x % meter 58 | next_candidates = beat_array[meter_index] 59 | 60 | def sorting_function(chunk, target_chunk=output[-1]): 61 | timbre = chunk.mean_timbre() 62 | target_timbre = target_chunk.mean_timbre() 63 | timbre_distance = numpy.linalg.norm(numpy.array(timbre) - numpy.array(target_timbre)) 64 | return timbre_distance 65 | 66 | next_candidates = sorted(next_candidates, key=sorting_function) 67 | next_index = random.randint(0, min(choices, len(next_candidates) - 1)) 68 | output.append(next_candidates[next_index]) 69 | 70 | out = audio.getpieces(audiofile, output) 71 | out.encode(outfile) 72 | 73 | 74 | if __name__ == '__main__': 75 | import sys 76 | try: 77 | inputFilename = sys.argv[1] 78 | outputFilename = sys.argv[2] 79 | if len(sys.argv) > 3: 80 | variation = int(sys.argv[3]) 81 | else: 82 | variation = 4 83 | if len(sys.argv) > 4: 84 | length = int(sys.argv[4]) 85 | else: 86 | length = 40 87 | except: 88 | print usage 89 | sys.exit(-1) 90 | main(inputFilename, outputFilename, variation, length) 91 | -------------------------------------------------------------------------------- /stretch/beatshift.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | """ 4 | beatshift.py 5 | 6 | Pitchshift each beat based on its position in the bar. 7 | Beat one is unchanged, beat two is shifted down one half step, 8 | beat three is shifted down two half steps, etc. 9 | 10 | Created by Ben Lacker on 2009-06-24. 11 | Refactored by Thor Kell on 2013-03-06. 12 | """ 13 | import numpy 14 | import os 15 | import random 16 | import sys 17 | import time 18 | 19 | from echonest.remix import audio, modify 20 | 21 | usage = """ 22 | Usage: 23 | python beatshift.py 24 | Exampel: 25 | python beatshift.py CryMeARiver.mp3 CryMeAShifty.mp3 26 | """ 27 | 28 | def main(input_filename, output_filename): 29 | soundtouch = modify.Modify() 30 | audiofile = audio.LocalAudioFile(input_filename) 31 | beats = audiofile.analysis.beats 32 | out_shape = (len(audiofile.data),) 33 | out_data = audio.AudioData(shape=out_shape, numChannels=1, sampleRate=44100) 34 | 35 | for i, beat in enumerate(beats): 36 | data = audiofile[beat].data 37 | number = beat.local_context()[0] % 12 38 | new_beat = soundtouch.shiftPitchSemiTones(audiofile[beat], number*-1) 39 | out_data.append(new_beat) 40 | 41 | out_data.encode(output_filename) 42 | 43 | if __name__ == '__main__': 44 | import sys 45 | try: 46 | input_filename = sys.argv[1] 47 | output_filename = sys.argv[2] 48 | except: 49 | print usage 50 | sys.exit(-1) 51 | main(input_filename, output_filename) 52 | -------------------------------------------------------------------------------- /stretch/cycle_dirac.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | """ 4 | cycle.py 5 | 6 | Periodically time-compress and time-stretch the beats in each measure. 7 | Each measure starts fast and ends slow. 8 | 9 | Created by Thor Kell on 2013-05-06, based on code by Ben Lacker. 10 | """ 11 | import math 12 | import os 13 | import sys 14 | import dirac 15 | from echonest.remix import audio 16 | 17 | usage = """ 18 | Usage: 19 | python cycle.py 20 | Exampel: 21 | python cycle.py CryMeARiver.mp3 CryCycle.mp3 22 | """ 23 | 24 | def main(input_filename, output_filename): 25 | audiofile = audio.LocalAudioFile(input_filename) 26 | bars = audiofile.analysis.bars 27 | collect = [] 28 | 29 | for bar in bars: 30 | bar_ratio = (bars.index(bar) % 4) / 2.0 31 | beats = bar.children() 32 | for beat in beats: 33 | beat_index = beat.local_context()[0] 34 | ratio = beat_index / 2.0 + 0.5 35 | ratio = ratio + bar_ratio # dirac can't compress by less than 0.5! 36 | beat_audio = beat.render() 37 | scaled_beat = dirac.timeScale(beat_audio.data, ratio) 38 | ts = audio.AudioData(ndarray=scaled_beat, shape=scaled_beat.shape, 39 | sampleRate=audiofile.sampleRate, numChannels=scaled_beat.shape[1]) 40 | collect.append(ts) 41 | 42 | out = audio.assemble(collect, numChannels=2) 43 | out.encode(output_filename) 44 | 45 | if __name__ == '__main__': 46 | import sys 47 | try: 48 | input_filename = sys.argv[1] 49 | output_filename = sys.argv[2] 50 | except: 51 | print usage 52 | sys.exit(-1) 53 | main(input_filename, output_filename) 54 | 55 | -------------------------------------------------------------------------------- /stretch/cycle_soundtouch.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | """ 4 | cycle.py 5 | 6 | Periodically time-compress and time-stretch the beats in each measure. 7 | Each measure starts fast and ends slow. 8 | 9 | Currently there is a bug with soundtouch. 10 | Please see cycle_dirac, and use dirac for time stretching. 11 | 12 | Created by Ben Lacker on 2009-06-16. 13 | Refactored by Thor Kell on 2013-03-06. 14 | """ 15 | import math 16 | import os 17 | import sys 18 | 19 | from echonest.remix import audio, modify 20 | 21 | usage = """ 22 | Usage: 23 | python cycle.py 24 | Exampel: 25 | python cycle.py CryMeARiver.mp3 CryCycle.mp3 26 | """ 27 | 28 | def main(input_filename, output_filename): 29 | 30 | audiofile = audio.LocalAudioFile(input_filename) 31 | soundtouch = modify.Modify() 32 | beats = audiofile.analysis.beats 33 | collect = [] 34 | 35 | for beat in beats: 36 | context = beat.local_context() 37 | ratio = (math.cos(math.pi * 2 * context[0]/float(context[1])) / 2) + 1 38 | new = soundtouch.shiftTempo(audiofile[beat], ratio) 39 | collect.append(new) 40 | 41 | out = audio.assemble(collect) 42 | out.encode(output_filename) 43 | 44 | if __name__ == '__main__': 45 | import sys 46 | try: 47 | input_filename = sys.argv[1] 48 | output_filename = sys.argv[2] 49 | except: 50 | print usage 51 | sys.exit(-1) 52 | main(input_filename, output_filename) 53 | -------------------------------------------------------------------------------- /stretch/simple_stretch.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | """ 4 | simple_stretch.py 5 | 6 | Compress or exapand the entire track, beat by beat. 7 | 8 | Created by Thor Kell on 2013-11-18 9 | """ 10 | import math 11 | import os 12 | import sys 13 | import dirac 14 | from echonest.remix import audio 15 | 16 | usage = """ 17 | Usage: 18 | python simple_stretch.py 19 | Example: 20 | python simple_stretch.py CryMeARiver.mp3 StrechMeARiver.mp3 21 | Notes: 22 | Ratio must be greater than 0.5 23 | """ 24 | 25 | def main(input_filename, output_filename, ratio): 26 | audiofile = audio.LocalAudioFile(input_filename) 27 | beats = audiofile.analysis.beats 28 | collect = [] 29 | 30 | for beat in beats: 31 | beat_audio = beat.render() 32 | scaled_beat = dirac.timeScale(beat_audio.data, ratio) 33 | ts = audio.AudioData(ndarray=scaled_beat, shape=scaled_beat.shape, 34 | sampleRate=audiofile.sampleRate, numChannels=scaled_beat.shape[1]) 35 | collect.append(ts) 36 | 37 | out = audio.assemble(collect, numChannels=2) 38 | out.encode(output_filename) 39 | 40 | if __name__ == '__main__': 41 | import sys 42 | try: 43 | input_filename = sys.argv[1] 44 | output_filename = sys.argv[2] 45 | ratio = float(sys.argv[3]) 46 | if ratio < 0.5: 47 | print "Error: Ratio must be greater than 0.5!" 48 | sys.exit(-1) 49 | except: 50 | print usage 51 | sys.exit(-1) 52 | main(input_filename, output_filename, ratio) 53 | -------------------------------------------------------------------------------- /summary/summary.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf=8 3 | 4 | """ 5 | summary.py 6 | 7 | Digest only the first or only the second tatum of every beat. 8 | 9 | By Ben Lacker, 2009-02-18. 10 | """ 11 | import sys 12 | import echonest.remix.audio as audio 13 | 14 | usage = """ 15 | Usage: 16 | python summary.py [and] 17 | 18 | Example: 19 | python summary.py RichGirl.mp3 RichSummary.mp3 20 | """ 21 | 22 | 23 | def main(input_filename, output_filename, index): 24 | audio_file = audio.LocalAudioFile(input_filename) 25 | beats = audio_file.analysis.beats 26 | collect = audio.AudioQuantumList() 27 | for beat in beats: 28 | tata = beat.children() 29 | if len(tata)>1: 30 | tat = tata[index] 31 | else: 32 | tat = tata[0] 33 | collect.append(tat) 34 | out = audio.getpieces(audio_file, collect) 35 | out.encode(output_filename) 36 | 37 | 38 | if __name__ == '__main__': 39 | try: 40 | if sys.argv[1]=='and': 41 | index = 1 42 | else: 43 | index = 0 44 | input_filename = sys.argv[-2] 45 | output_filename = sys.argv[-1] 46 | except: 47 | print usage 48 | sys.exit(-1) 49 | main(input_filename, output_filename, index) 50 | -------------------------------------------------------------------------------- /swinger/swinger.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf=8 3 | 4 | """ 5 | swinger.py 6 | (name suggested by Jason Sundram) 7 | 8 | Make your music swing (or un-swing). 9 | Created by Tristan Jehan. 10 | """ 11 | 12 | from optparse import OptionParser 13 | import os, sys 14 | import dirac 15 | 16 | from echonest.remix.audio import LocalAudioFile, AudioData 17 | from echonest.remix.action import render, Playback, display_actions 18 | 19 | def do_work(track, options): 20 | 21 | verbose = bool(options.verbose) 22 | 23 | # swing factor 24 | swing = float(options.swing) 25 | if swing < -0.9: swing = -0.9 26 | if swing > +0.9: swing = +0.9 27 | 28 | if swing == 0: 29 | return Playback(track, 0, track.analysis.duration) 30 | 31 | beats = track.analysis.beats 32 | offset = int(beats[0].start * track.sampleRate) 33 | 34 | # compute rates 35 | rates = [] 36 | for beat in beats[:-1]: 37 | # put swing 38 | if 0 < swing: 39 | rate1 = 1+swing 40 | dur = beat.duration/2.0 41 | stretch = dur * rate1 42 | rate2 = (beat.duration-stretch)/dur 43 | # remove swing 44 | else: 45 | rate1 = 1 / (1+abs(swing)) 46 | dur = (beat.duration/2.0) / rate1 47 | stretch = dur * rate1 48 | rate2 = (beat.duration-stretch)/(beat.duration-dur) 49 | # build list of rates 50 | start1 = int(beat.start * track.sampleRate) 51 | start2 = int((beat.start+dur) * track.sampleRate) 52 | rates.append((start1-offset, rate1)) 53 | rates.append((start2-offset, rate2)) 54 | if verbose: 55 | args = (beats.index(beat), dur, beat.duration-dur, stretch, beat.duration-stretch) 56 | print "Beat %d — split [%.3f|%.3f] — stretch [%.3f|%.3f] seconds" % args 57 | 58 | # get audio 59 | vecin = track.data[offset:int(beats[-1].start * track.sampleRate),:] 60 | # time stretch 61 | if verbose: 62 | print "\nTime stretching..." 63 | vecout = dirac.timeScale(vecin, rates, track.sampleRate, 0) 64 | # build timestretch AudioData object 65 | ts = AudioData(ndarray=vecout, shape=vecout.shape, 66 | sampleRate=track.sampleRate, numChannels=vecout.shape[1], 67 | verbose=verbose) 68 | # initial and final playback 69 | pb1 = Playback(track, 0, beats[0].start) 70 | pb2 = Playback(track, beats[-1].start, track.analysis.duration-beats[-1].start) 71 | 72 | return [pb1, ts, pb2] 73 | 74 | def main(): 75 | usage = "usage: %s [options] " % sys.argv[0] 76 | parser = OptionParser(usage=usage) 77 | parser.add_option("-s", "--swing", default=0.33, help="swing factor default=0.33") 78 | parser.add_option("-v", "--verbose", action="store_true", help="show results on screen") 79 | 80 | (options, args) = parser.parse_args() 81 | if len(args) < 1: 82 | parser.print_help() 83 | return -1 84 | 85 | verbose = options.verbose 86 | track = None 87 | 88 | track = LocalAudioFile(args[0], verbose=verbose) 89 | if verbose: 90 | print "Computing swing . . ." 91 | # this is where the work takes place 92 | actions = do_work(track, options) 93 | 94 | if verbose: 95 | display_actions(actions) 96 | 97 | # Send to renderer 98 | name = os.path.splitext(os.path.basename(args[0])) 99 | sign = ('-','+')[float(options.swing) >= 0] 100 | name = name[0] + '_swing' + sign + str(int(abs(float(options.swing))*100)) +'.mp3' 101 | name = name.replace(' ','') 102 | name = os.path.join(os.getcwd(), name) # TODO: use sys.path[0] instead of getcwd()? 103 | 104 | if verbose: 105 | print "Rendering... %s" % name 106 | render(actions, name, verbose=verbose) 107 | if verbose: 108 | print "Success!" 109 | return 1 110 | 111 | 112 | if __name__ == "__main__": 113 | try: 114 | main() 115 | except Exception, e: 116 | print e 117 | -------------------------------------------------------------------------------- /videolizer/videolizer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf=8 3 | 4 | """ 5 | videolizer.py 6 | 7 | Sync up some video sequences to a song. 8 | Created by Tristan Jehan. 9 | """ 10 | import echonest.remix.audio as aud 11 | from sys import stdout 12 | import subprocess 13 | import random 14 | import shutil 15 | import glob 16 | import sys 17 | import os 18 | 19 | VIDEO_FOLDER = 'videos' 20 | IMAGE_FOLDER = 'images' 21 | AUDIO_FOLDER = 'audio' 22 | AUDIO_EXAMPLE = '../music/Tracky_Birthday-Newish_Disco.mp3' 23 | TMP_FOLDER = IMAGE_FOLDER+'/tmp' 24 | VIDEO_CSV = 'video.csv' 25 | EXTENSION = '.mp4' 26 | TMP_CSV = 'tmp.csv' 27 | NUM_BEATS_RANGE = (16,32) 28 | EPSILON = 10e-9 29 | BITRATE = 1000000 30 | FPS = 30 31 | 32 | def display_in_place(line): 33 | stdout.write('\r%s' % line) 34 | stdout.flush() 35 | 36 | def time_to_frame(time): 37 | return int(time * FPS) 38 | 39 | def frame_to_time(frame): 40 | return float(frame) / float(FPS) 41 | 42 | def process_videos(folder, csv_file): 43 | files = glob.glob(folder+'/*'+EXTENSION) 44 | print "Converting videos to images..." 45 | for f in files: 46 | print "> %s..." % f 47 | convert_video_to_images(f, IMAGE_FOLDER) 48 | print "Extracting audio from video..." 49 | for f in files: 50 | print "> %s..." % f 51 | convert_video_to_audio(f) 52 | print "Analyzing video beats..." 53 | if os.path.exists(TMP_CSV): 54 | os.remove(TMP_CSV) 55 | for f in files: 56 | print "> %s..." % f 57 | analyze_video_beats(f, TMP_CSV) 58 | os.rename(TMP_CSV, csv_file) 59 | 60 | def video_to_audio(video): 61 | if not os.path.exists(AUDIO_FOLDER): 62 | os.makedirs(AUDIO_FOLDER) 63 | ext = os.path.splitext(video)[1] 64 | audio = video.replace(ext,'.m4a').replace(VIDEO_FOLDER, AUDIO_FOLDER) 65 | return audio 66 | 67 | def convert_video_to_images(video, output): 68 | ext = os.path.splitext(video)[1] 69 | name = os.path.basename(video.replace(ext,'')).split('.')[0] 70 | folder = output+'/'+name 71 | if not os.path.exists(folder): 72 | os.makedirs(folder) 73 | command = 'en-ffmpeg -loglevel panic -i \"%s\" -r %d -f image2 \"%s/%%05d.png\"' % (video, FPS, folder) 74 | cmd = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) 75 | cmd.wait() 76 | 77 | def convert_video_to_audio(video): 78 | audio = video_to_audio(video) 79 | if not os.path.exists(audio): 80 | command = 'en-ffmpeg -loglevel panic -y -i \"%s\" -vn -acodec copy \"%s\"' % (video, audio) 81 | cmd = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) 82 | cmd.wait() 83 | 84 | def analyze_video_beats(video, csv_file): 85 | audio = video_to_audio(video) 86 | track = aud.LocalAudioFile(audio, verbose=True) 87 | beats = track.analysis.beats 88 | mini = 0 89 | maxi = sys.maxint 90 | l = video.split('.') 91 | if len(l) > 3: 92 | if l[-3].isdigit() and int(l[-3]) is not 0: 93 | mini = int(l[-3]) 94 | if l[-2].isdigit() and int(l[-2]) is not 0: 95 | maxi = int(l[-2]) 96 | line = video 97 | for beat in beats: 98 | if mini < beat.start and beat.start < maxi: 99 | line = line+',%.5f' % beat.start 100 | fid = open(csv_file, 'a') 101 | fid.write(line+'\n') 102 | fid.close() 103 | 104 | def crop_audio(audio_file, beats): 105 | ext = os.path.splitext(audio_file)[1] 106 | name = os.path.basename(audio_file) 107 | output = AUDIO_FOLDER+'/'+name.replace(ext,'.cropped'+ext) 108 | if not os.path.exists(output): 109 | print "Cropping audio to available beats..." 110 | duration = float(beats[-1].start) - float(beats[0].start) 111 | command = 'en-ffmpeg -loglevel panic -y -ss %f -t %f -i \"%s\" \"%s\"' % (float(beats[0].start), duration, audio_file, output) 112 | cmd = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) 113 | cmd.wait() 114 | return output 115 | 116 | def combine_audio_and_images(audio_file, images_folder, output): 117 | print "Making final video out of image sequences..." 118 | tmp_video = VIDEO_FOLDER+'/tmp'+EXTENSION 119 | command = 'en-ffmpeg -loglevel panic -f image2 -r %d -i \"%s/%%05d.png\" -b %d \"%s\"' % (FPS, images_folder, BITRATE, tmp_video) 120 | cmd = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) 121 | cmd.wait() 122 | print "Combining final video with cropped audio..." 123 | command = 'en-ffmpeg -loglevel panic -vcodec copy -y -i \"%s\" -i \"%s\" \"%s\"' % (audio_file, tmp_video, output) 124 | cmd = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) 125 | cmd.wait() 126 | os.remove(tmp_video) 127 | 128 | def select_sequences(number_beats, data): 129 | sequences = [] 130 | count_beats = 0 131 | keys = data.keys() 132 | while count_beats < number_beats: 133 | video = random.choice(keys) 134 | nbeats = random.randint(NUM_BEATS_RANGE[0], NUM_BEATS_RANGE[1]) 135 | vbeats = data[video] 136 | if nbeats >= len(vbeats): 137 | nbeats = len(vbeats) 138 | if count_beats + nbeats > number_beats: 139 | nbeats = number_beats - count_beats 140 | start = random.randint(0, len(vbeats)-nbeats-1) 141 | count_beats += nbeats 142 | sequences.append((video, start, nbeats)) 143 | return sequences 144 | 145 | def resample_sequences(source_beats, sequences, data, tmp_folder): 146 | print "Building new video as a sequence of images..." 147 | audio_beats = [float(b.start) for b in source_beats] 148 | jitter = 0 149 | counter_beats = 0 150 | counter_images = 0 151 | for seq in sequences: 152 | ext = os.path.splitext(seq[0])[1] 153 | name = os.path.basename(seq[0].replace(ext,'')).split('.')[0] 154 | src_folder = IMAGE_FOLDER+'/'+name 155 | video_beats = data[seq[0]] 156 | video_time = float(video_beats[seq[1]]) 157 | audio_time = float(audio_beats[counter_beats]) 158 | for beats in zip(audio_beats[counter_beats+1:counter_beats+seq[2]+1], video_beats[seq[1]+1:seq[1]+seq[2]+1]): 159 | audio_beat_dur = beats[0] - audio_time 160 | video_beat_dur = beats[1] - video_time 161 | rate = audio_beat_dur / video_beat_dur 162 | num_frames_float = video_beat_dur * rate * FPS 163 | num_frames = int(num_frames_float) 164 | jitter += num_frames_float - num_frames 165 | if jitter >= 1-EPSILON: 166 | num_frames += int(jitter) 167 | jitter -= int(jitter) 168 | for i in range(0, num_frames): 169 | counter_images += 1 170 | src = '%s/%05d.png' % (src_folder, time_to_frame(video_time + frame_to_time(i/rate))) 171 | dst = '%s/%05d.png' % (tmp_folder, counter_images) 172 | display_in_place('Copying %s to %s' % (src, dst)) 173 | shutil.copyfile(src, dst) 174 | video_time = beats[1] - frame_to_time(jitter) 175 | audio_time = beats[0] 176 | counter_beats += seq[2] 177 | print 178 | 179 | def process_csv(csv_file): 180 | data = {} 181 | fid = open(csv_file,'r') 182 | for line in fid: 183 | l = line.strip().split(',') 184 | data[l[0]] = [float(v) for v in l[1:]] 185 | fid.close() 186 | return data 187 | 188 | def videolize(file_in, file_ou): 189 | if not os.path.exists(VIDEO_CSV): 190 | process_videos(VIDEO_FOLDER, VIDEO_CSV) 191 | data = process_csv(VIDEO_CSV) 192 | track = aud.LocalAudioFile(file_in, verbose=True) 193 | beats = track.analysis.beats 194 | if os.path.exists(TMP_FOLDER): 195 | shutil.rmtree(TMP_FOLDER) 196 | os.makedirs(TMP_FOLDER) 197 | sequences = select_sequences(len(beats)-1, data) 198 | resample_sequences(beats, sequences, data, TMP_FOLDER) 199 | file_cropped = crop_audio(file_in, beats) 200 | combine_audio_and_images(file_cropped, TMP_FOLDER, file_ou) 201 | shutil.rmtree(TMP_FOLDER) 202 | 203 | def main(): 204 | usage = "usage:\tpython %s \ntry:\tpython %s %s\ 205 | \nnote:\tvideo filename format is name...mp4\n\ 206 | \twith and in seconds, where 0 means ignore" % (sys.argv[0], sys.argv[0], AUDIO_EXAMPLE) 207 | if len(sys.argv) < 2: 208 | print usage 209 | return -1 210 | file_in = sys.argv[1] 211 | ext = os.path.splitext(file_in)[1] 212 | name = os.path.basename(file_in.replace(ext,'')) 213 | file_ou = name+EXTENSION 214 | videolize(file_in, file_ou) 215 | 216 | if __name__ == "__main__": 217 | try: 218 | main() 219 | except Exception, e: 220 | print e 221 | -------------------------------------------------------------------------------- /videolizer/videos/soultrain1.11.0.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/echonest/remix-examples/05e190069e0b25baef9a4259267acfa34f2ed913/videolizer/videos/soultrain1.11.0.mp4 -------------------------------------------------------------------------------- /videolizer/videos/soultrain2.18.0.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/echonest/remix-examples/05e190069e0b25baef9a4259267acfa34f2ed913/videolizer/videos/soultrain2.18.0.mp4 -------------------------------------------------------------------------------- /videolizer/videos/soultrain3.1.120.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/echonest/remix-examples/05e190069e0b25baef9a4259267acfa34f2ed913/videolizer/videos/soultrain3.1.120.mp4 -------------------------------------------------------------------------------- /videx/vafroma.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf=8 3 | 4 | """ 5 | vafroma.py 6 | 7 | Re-synthesize video A using the segments of song A. 8 | 9 | By Ben Lacker, P. Lamere 10 | """ 11 | import numpy 12 | import sys 13 | import time 14 | import echonest.remix.audio as audio 15 | import echonest.remix.video as video 16 | import echonest.remix.modify as modify 17 | 18 | usage=""" 19 | Usage: 20 | python vafroma.py 21 | 22 | Example: 23 | python vafroma.py BillieJeanMusicVideo.mp4 24 | """ 25 | 26 | 27 | dur_weight = 1000 28 | #dur_weight = 100 29 | timbre_weight = .001 30 | pitch_weight = 10 31 | loudness_weight = 1 32 | 33 | class AfromA(object): 34 | def __init__(self, input_filename, output_filename): 35 | self.av = video.loadav(input_filename) 36 | self.segs = self.av.audio.analysis.segments 37 | self.output_filename = output_filename 38 | 39 | def get_distance_from(self, seg): 40 | distances = [] 41 | for a in self.segs: 42 | ddur = numpy.square(seg.duration - a.duration) 43 | dloud = numpy.square(seg.loudness_max - a.loudness_max) 44 | 45 | timbre_diff = numpy.subtract(seg.timbre, a.timbre) 46 | dtimbre = (numpy.sum(numpy.square(timbre_diff))) 47 | 48 | pitch_diff = numpy.subtract(seg.pitches, a.pitches) 49 | dpitch = (numpy.sum(numpy.square(pitch_diff))) 50 | 51 | #print dur_weight * ddur, timbre_weight * dtimbre, \ 52 | # pitch_weight * dpitch, loudness_weight * dloud 53 | distance = dur_weight * ddur \ 54 | + loudness_weight * dloud \ 55 | + timbre_weight * dtimbre \ 56 | + pitch_weight * dpitch; 57 | distances.append(distance) 58 | 59 | return distances 60 | 61 | 62 | def run(self): 63 | st = modify.Modify() 64 | collect = audio.AudioQuantumList() 65 | for a in self.segs: 66 | seg_index = a.absolute_context()[0] 67 | 68 | distances = self.get_distance_from(a) 69 | 70 | distances[seg_index] = sys.maxint 71 | 72 | match_index = distances.index(min(distances)) 73 | match = self.segs[match_index] 74 | print seg_index, match_index 75 | # make the length of the new seg match the length 76 | # of the old seg 77 | collect.append(match) 78 | out = video.getpieces(self.av, collect) 79 | out.save(self.output_filename) 80 | 81 | def main(): 82 | try: 83 | input_filename = sys.argv[1] 84 | if len(sys.argv) > 2: 85 | output_filename = sys.argv[2] 86 | else: 87 | output_filename = "aa_" + input_filename 88 | except: 89 | print usage 90 | sys.exit(-1) 91 | AfromA(input_filename, output_filename).run() 92 | 93 | 94 | if __name__=='__main__': 95 | tic = time.time() 96 | main() 97 | toc = time.time() 98 | print "Elapsed time: %.3f sec" % float(toc-tic) 99 | -------------------------------------------------------------------------------- /videx/vafroma2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf=8 3 | 4 | """ 5 | vafroma2.py 6 | 7 | Re-synthesize video A using the segments of song A. 8 | Same as vafroma.py, but avoids re-using segments. 9 | 10 | By Ben Lacker, P. Lamere 11 | """ 12 | import numpy 13 | import sys 14 | import time 15 | import echonest.remix.audio as audio 16 | import echonest.remix.video as video 17 | import echonest.remix.modify as modify 18 | 19 | usage=""" 20 | Usage: 21 | python vafroma2.py 22 | 23 | Example: 24 | python vafroma2.py BillieJeanMusicVideo.mp4 25 | """ 26 | 27 | 28 | dur_weight = 1000 29 | #dur_weight = 100 30 | timbre_weight = .001 31 | pitch_weight = 10 32 | loudness_weight = 1 33 | 34 | class AfromA(object): 35 | def __init__(self, input_filename, output_filename): 36 | self.av = video.loadav(input_filename) 37 | self.segs = self.av.audio.analysis.segments 38 | self.output_filename = output_filename 39 | 40 | def get_distance_from(self, seg): 41 | distances = [] 42 | for a in self.segs: 43 | ddur = numpy.square(seg.duration - a.duration) 44 | dloud = numpy.square(seg.loudness_max - a.loudness_max) 45 | 46 | timbre_diff = numpy.subtract(seg.timbre, a.timbre) 47 | dtimbre = (numpy.sum(numpy.square(timbre_diff))) 48 | 49 | pitch_diff = numpy.subtract(seg.pitches, a.pitches) 50 | dpitch = (numpy.sum(numpy.square(pitch_diff))) 51 | 52 | #print dur_weight * ddur, timbre_weight * dtimbre, \ 53 | # pitch_weight * dpitch, loudness_weight * dloud 54 | distance = dur_weight * ddur \ 55 | + loudness_weight * dloud \ 56 | + timbre_weight * dtimbre \ 57 | + pitch_weight * dpitch; 58 | distances.append(distance) 59 | 60 | return distances 61 | 62 | 63 | def run(self): 64 | st = modify.Modify() 65 | collect = audio.AudioQuantumList() 66 | used = [] 67 | for a in self.segs: 68 | seg_index = a.absolute_context()[0] 69 | 70 | distances = self.get_distance_from(a) 71 | 72 | distances[seg_index] = sys.maxint 73 | for u in used: 74 | distances[u] = sys.maxint 75 | 76 | match_index = distances.index(min(distances)) 77 | match = self.segs[match_index] 78 | print seg_index, match_index 79 | # make the length of the new seg match the length 80 | # of the old seg 81 | collect.append(match) 82 | used.append(match_index) 83 | out = video.getpieces(self.av, collect) 84 | out.save(self.output_filename) 85 | 86 | def main(): 87 | try: 88 | input_filename = sys.argv[1] 89 | if len(sys.argv) > 2: 90 | output_filename = sys.argv[2] 91 | else: 92 | output_filename = "aa2_" + input_filename 93 | except: 94 | print usage 95 | sys.exit(-1) 96 | AfromA(input_filename, output_filename).run() 97 | 98 | 99 | if __name__=='__main__': 100 | tic = time.time() 101 | main() 102 | toc = time.time() 103 | print "Elapsed time: %.3f sec" % float(toc-tic) 104 | -------------------------------------------------------------------------------- /videx/vafroma3.py: -------------------------------------------------------------------------------- 1 | 2 | #!/usr/bin/env python 3 | # encoding: utf=8 4 | 5 | """ 6 | vafroma3.py 7 | 8 | Re-synthesize video A using the segments of song A. 9 | Tries to use longer sequences of video by boosting the distance neighbors of similar segments. 10 | 11 | By Ben Lacker, P. Lamere 12 | """ 13 | import numpy 14 | import sys 15 | import time 16 | import echonest.remix.audio as audio 17 | import echonest.remix.video as video 18 | import echonest.remix.modify as modify 19 | 20 | usage=""" 21 | Usage: 22 | python vafroma3.py 23 | 24 | Example: 25 | python vafroma3.py BillieJeanMusicVideo.mp4 26 | """ 27 | 28 | 29 | dur_weight = 1000 30 | #dur_weight = 100 31 | timbre_weight = .001 32 | pitch_weight = 10 33 | loudness_weight = 1 34 | 35 | class AfromA(object): 36 | def __init__(self, input_filename, output_filename): 37 | self.av = video.loadav(input_filename) 38 | self.segs = self.av.audio.analysis.segments 39 | self.output_filename = output_filename 40 | 41 | def get_distance_from(self, seg): 42 | distances = [] 43 | for a in self.segs: 44 | ddur = numpy.square(seg.duration - a.duration) 45 | dloud = numpy.square(seg.loudness_max - a.loudness_max) 46 | 47 | timbre_diff = numpy.subtract(seg.timbre, a.timbre) 48 | dtimbre = (numpy.sum(numpy.square(timbre_diff))) 49 | 50 | pitch_diff = numpy.subtract(seg.pitches, a.pitches) 51 | dpitch = (numpy.sum(numpy.square(pitch_diff))) 52 | 53 | #print dur_weight * ddur, timbre_weight * dtimbre, \ 54 | # pitch_weight * dpitch, loudness_weight * dloud 55 | distance = dur_weight * ddur \ 56 | + loudness_weight * dloud \ 57 | + timbre_weight * dtimbre \ 58 | + pitch_weight * dpitch; 59 | distances.append(distance) 60 | 61 | return distances 62 | 63 | 64 | def run(self): 65 | st = modify.Modify() 66 | last_index = 0 67 | collect = audio.AudioQuantumList() 68 | match_index = -1 69 | for a in self.segs: 70 | seg_index = a.absolute_context()[0] 71 | 72 | distances = self.get_distance_from(a) 73 | 74 | distances[seg_index] = sys.maxint 75 | 76 | if match_index < len(distances) -1: 77 | distances[match_index + 1] *= .3 78 | 79 | match_index = distances.index(min(distances)) 80 | match = self.segs[match_index] 81 | print seg_index, match_index 82 | # make the length of the new seg match the length 83 | # of the old seg 84 | collect.append(match) 85 | out = video.getpieces(self.av, collect) 86 | out.save(self.output_filename) 87 | 88 | def main(): 89 | try: 90 | input_filename = sys.argv[1] 91 | if len(sys.argv) > 2: 92 | output_filename = sys.argv[2] 93 | else: 94 | output_filename = "aa3_" + input_filename 95 | except: 96 | print usage 97 | sys.exit(-1) 98 | AfromA(input_filename, output_filename).run() 99 | 100 | 101 | if __name__=='__main__': 102 | tic = time.time() 103 | main() 104 | toc = time.time() 105 | print "Elapsed time: %.3f sec" % float(toc-tic) 106 | -------------------------------------------------------------------------------- /videx/vafromb.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf=8 3 | 4 | """ 5 | vafromb.py 6 | 7 | Re-synthesize video A using the segments of video B. 8 | 9 | By Ben Lacker, 2009-02-24. 10 | """ 11 | import numpy 12 | import sys 13 | import time 14 | 15 | from echonest.remix import action, audio, video 16 | 17 | usage=""" 18 | Usage: 19 | python vafromb.py [env] 20 | 21 | Example: 22 | python vafromb.py BillieJean.mp4 CryMeARiver.mp4 BillieJeanFromCryMeARiver.mp4 0.9 env 23 | 24 | The 'env' flag applies the volume envelopes of the segments of A to those 25 | from B. 26 | 27 | Mix is a number 0-1 that determines the relative mix of the resynthesized 28 | song and the original input A. i.e. a mix value of 0.9 yields an output that 29 | is mostly the resynthesized version. 30 | 31 | """ 32 | 33 | class AfromB(object): 34 | def __init__(self, input_filename_a, input_filename_b, output_filename): 35 | "Synchronizes slavebundle on masterbundle, writes to outbundle" 36 | self.master = video.loadav(input_filename_a) 37 | # convert slave so it matches master's settings 38 | converted = video.convertmov(input_filename_b, settings=self.master.video.settings) 39 | self.slave = video.loadav(converted) 40 | self.out = output_filename 41 | 42 | self.input_a = self.master.audio 43 | self.input_b = self.slave.audio 44 | self.segs_a = self.input_a.analysis.segments 45 | self.segs_b = self.input_b.analysis.segments 46 | self.output_filename = output_filename 47 | 48 | def calculate_distances(self, a): 49 | distance_matrix = numpy.zeros((len(self.segs_b), 4), dtype=numpy.float32) 50 | pitch_distances = [] 51 | timbre_distances = [] 52 | loudmax_distances = [] 53 | for b in self.segs_b: 54 | pitch_diff = numpy.subtract(b.pitches,a.pitches) 55 | pitch_distances.append(numpy.sum(numpy.square(pitch_diff))) 56 | timbre_diff = numpy.subtract(b.timbre,a.timbre) 57 | timbre_distances.append(numpy.sum(numpy.square(timbre_diff))) 58 | loudmax_diff = b.loudness_begin - a.loudness_begin 59 | loudmax_distances.append(numpy.square(loudmax_diff)) 60 | distance_matrix[:,0] = pitch_distances 61 | distance_matrix[:,1] = timbre_distances 62 | distance_matrix[:,2] = loudmax_distances 63 | distance_matrix[:,3] = range(len(self.segs_b)) 64 | distance_matrix = self.normalize_distance_matrix(distance_matrix) 65 | return distance_matrix 66 | 67 | def normalize_distance_matrix(self, mat, mode='minmed'): 68 | """ Normalize a distance matrix on a per column basis. 69 | """ 70 | if mode == 'minstd': 71 | mini = numpy.min(mat,0) 72 | m = numpy.subtract(mat, mini) 73 | std = numpy.std(mat,0) 74 | m = numpy.divide(m, std) 75 | m = numpy.divide(m, mat.shape[1]) 76 | elif mode == 'minmed': 77 | mini = numpy.min(mat,0) 78 | m = numpy.subtract(mat, mini) 79 | med = numpy.median(m) 80 | m = numpy.divide(m, med) 81 | m = numpy.divide(m, mat.shape[1]) 82 | elif mode == 'std': 83 | std = numpy.std(mat,0) 84 | m = numpy.divide(mat, std) 85 | m = numpy.divide(m, mat.shape[1]) 86 | return m 87 | 88 | def run(self, mix=0.5, envelope=False): 89 | dur = len(self.input_a.data) + 100000 # another two seconds 90 | # determine shape of new array. 91 | # do everything in mono; I'm not fancy. 92 | new_shape = (dur,) 93 | new_channels = 1 94 | self.input_a = action.make_mono(self.input_a) 95 | self.input_b = action.make_mono(self.input_b) 96 | out = audio.AudioData(shape=new_shape, sampleRate=self.input_b.sampleRate, numChannels=new_channels) 97 | for a in self.segs_a: 98 | seg_index = a.absolute_context()[0] 99 | # find best match from segs in B 100 | distance_matrix = self.calculate_distances(a) 101 | distances = [numpy.sqrt(x[0]+x[1]+x[2]) for x in distance_matrix] 102 | match = self.segs_b[distances.index(min(distances))] 103 | segment_data = self.input_b[match] 104 | reference_data = self.input_a[a] 105 | if segment_data.endindex < reference_data.endindex: 106 | if new_channels > 1: 107 | silence_shape = (reference_data.endindex,new_channels) 108 | else: 109 | silence_shape = (reference_data.endindex,) 110 | new_segment = audio.AudioData(shape=silence_shape, 111 | sampleRate=out.sampleRate, 112 | numChannels=segment_data.numChannels) 113 | new_segment.append(segment_data) 114 | new_segment.endindex = len(new_segment) 115 | segment_data = new_segment 116 | elif segment_data.endindex > reference_data.endindex: 117 | index = slice(0, int(reference_data.endindex), 1) 118 | segment_data = audio.AudioData(None,segment_data.data[index], 119 | sampleRate=segment_data.sampleRate) 120 | 121 | chopvideo = self.slave.video[match] # get editableframes object 122 | masterchop = self.master.video[a] 123 | startframe = self.master.video.indexvoodo(a.start) # find start index 124 | endframe = self.master.video.indexvoodo(a.start + a.duration) 125 | for i in xrange(len(chopvideo.files)): 126 | if startframe+i < len(self.master.video.files): 127 | self.master.video.files[startframe+i] = chopvideo.files[i] 128 | last_frame = chopvideo.files[i] 129 | for i in xrange(len(chopvideo.files), len(masterchop.files)): 130 | if startframe+i < len(self.master.video.files): 131 | self.master.video.files[startframe+i] = last_frame 132 | 133 | if envelope: 134 | # db -> voltage ratio http://www.mogami.com/e/cad/db.html 135 | linear_max_volume = pow(10.0,a.loudness_max/20.0) 136 | linear_start_volume = pow(10.0,a.loudness_begin/20.0) 137 | if(seg_index == len(self.segs_a)-1): # if this is the last segment 138 | linear_next_start_volume = 0 139 | else: 140 | linear_next_start_volume = pow(10.0,self.segs_a[seg_index+1].loudness_begin/20.0) 141 | pass 142 | when_max_volume = a.time_loudness_max 143 | # Count # of ticks I wait doing volume ramp so I can fix up rounding errors later. 144 | ss = 0 145 | # Set volume of this segment. Start at the start volume, ramp up to the max volume , then ramp back down to the next start volume. 146 | cur_vol = float(linear_start_volume) 147 | # Do the ramp up to max from start 148 | samps_to_max_loudness_from_here = int(segment_data.sampleRate * when_max_volume) 149 | if(samps_to_max_loudness_from_here > 0): 150 | how_much_volume_to_increase_per_samp = float(linear_max_volume - linear_start_volume)/float(samps_to_max_loudness_from_here) 151 | for samps in xrange(samps_to_max_loudness_from_here): 152 | try: 153 | segment_data.data[ss] *= cur_vol 154 | except IndexError: 155 | pass 156 | cur_vol = cur_vol + how_much_volume_to_increase_per_samp 157 | ss = ss + 1 158 | # Now ramp down from max to start of next seg 159 | samps_to_next_segment_from_here = int(segment_data.sampleRate * (a.duration-when_max_volume)) 160 | if(samps_to_next_segment_from_here > 0): 161 | how_much_volume_to_decrease_per_samp = float(linear_max_volume - linear_next_start_volume)/float(samps_to_next_segment_from_here) 162 | for samps in xrange(samps_to_next_segment_from_here): 163 | cur_vol = cur_vol - how_much_volume_to_decrease_per_samp 164 | try: 165 | segment_data.data[ss] *= cur_vol 166 | except IndexError: 167 | pass 168 | ss = ss + 1 169 | mixed_data = audio.mix(segment_data,reference_data,mix=mix) 170 | out.append(mixed_data) 171 | self.master.audio = out 172 | self.master.save(self.output_filename) 173 | 174 | def main(): 175 | try: 176 | input_filename_a = sys.argv[1] 177 | input_filename_b = sys.argv[2] 178 | output_filename = sys.argv[3] 179 | mix = sys.argv[4] 180 | if len(sys.argv) == 6: 181 | env = True 182 | else: 183 | env = False 184 | except Exception: 185 | print usage 186 | sys.exit(-1) 187 | AfromB(input_filename_a, input_filename_b, output_filename).run(mix=mix, envelope=env) 188 | 189 | if __name__=='__main__': 190 | tic = time.time() 191 | main() 192 | toc = time.time() 193 | print "Elapsed time: %.3f sec" % float(toc-tic) 194 | -------------------------------------------------------------------------------- /videx/vdissoc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf=8 3 | 4 | """ 5 | vdissoc.py 6 | 7 | The video version of step-by-section.py. 8 | 9 | For each bar, take one of the nearest (in timbre) beats 10 | to the last beat, chosen from all of the beats that fall 11 | on the one in this section. Repeat for all the twos, etc. 12 | 13 | This version divides things by section, retaining the 14 | structure and approximate length of the original. The 15 | variation parameter, (_v_) means there's a roughly 16 | one in _v_ chance that the actual next beat is chosen. A 17 | musical M-x dissociated-press. 18 | 19 | Originally by Adam Lindsay, 2009-06-27. 20 | Refactored by Thor Kell, 2012-15-12 21 | """ 22 | import random 23 | import numpy 24 | from echonest.remix import video, audio 25 | 26 | usage = """ 27 | Usage: 28 | python vdissoc.py inputFilenameOrUrl outputFilename [variation] 29 | 30 | variation is the number of near candidates chosen from. [default=4] 31 | 32 | Example: 33 | python vdissoc.py 'http://www.youtube.com/watch?v=Es7mk19wMrk' Seventh.mp4 34 | """ 35 | def main(infile, outfile, choices=4): 36 | if infile.startswith("http://"): 37 | av = video.loadavfromyoutube(infile) 38 | else: 39 | av = video.loadav(infile) 40 | 41 | meter = av.audio.analysis.time_signature['value'] 42 | sections = av.audio.analysis.sections 43 | output = audio.AudioQuantumList() 44 | 45 | for section in sections: 46 | beats = [] 47 | bars = section.children() 48 | for bar in bars: 49 | beats.extend(bar.children()) 50 | 51 | if not bars or not beats: 52 | continue 53 | 54 | beat_array = [] 55 | for m in range(meter): 56 | metered_beats = [] 57 | for b in beats: 58 | if beats.index(b) % meter == m: 59 | metered_beats.append(b) 60 | beat_array.append(metered_beats) 61 | 62 | # Always start with the first beat 63 | output.append(beat_array[0][0]); 64 | for x in range(1, len(bars) * meter): 65 | meter_index = x % meter 66 | next_candidates = beat_array[meter_index] 67 | 68 | def sorting_function(chunk, target_chunk=output[-1]): 69 | timbre = chunk.mean_timbre() 70 | target_timbre = target_chunk.mean_timbre() 71 | timbre_distance = numpy.linalg.norm(numpy.array(timbre) - numpy.array(target_timbre)) 72 | return timbre_distance 73 | 74 | next_candidates = sorted(next_candidates, key=sorting_function) 75 | next_index = random.randint(0, min(choices, len(next_candidates) - 1)) 76 | output.append(next_candidates[next_index]) 77 | 78 | out = video.getpieces(av, output) 79 | out.save(outfile) 80 | 81 | if __name__ == '__main__': 82 | import sys 83 | try: 84 | inputFilename = sys.argv[1] 85 | outputFilename = sys.argv[2] 86 | if len(sys.argv) > 3: 87 | variation = int(sys.argv[3]) 88 | else: 89 | variation = 4 90 | except: 91 | print usage 92 | sys.exit(-1) 93 | main(inputFilename, outputFilename, variation) 94 | -------------------------------------------------------------------------------- /videx/vone.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | """ 4 | vone.py 5 | 6 | Created by Ben Lacker on 2009-06-19. 7 | Copyright (c) 2009 __MyCompanyName__. All rights reserved. 8 | """ 9 | 10 | import sys 11 | import os 12 | 13 | from echonest.remix import audio, video 14 | 15 | usage = """ 16 | Usage: 17 | python vone.py 18 | 19 | Example: 20 | python vone.py EverythingIsOnTheOne.mpg EverythingIsReallyOnTheOne.mpg 21 | """ 22 | 23 | 24 | def main(input_filename, output_filename): 25 | if input_filename.startswith("http://"): 26 | av = video.loadavfromyoutube(input_filename) 27 | else: 28 | av = video.loadav(input_filename) 29 | collect = audio.AudioQuantumList() 30 | for bar in av.audio.analysis.bars: 31 | collect.append(bar.children()[0]) 32 | out = video.getpieces(av, collect) 33 | out.save(output_filename) 34 | 35 | 36 | if __name__ == '__main__': 37 | import sys 38 | try: 39 | input_filename = sys.argv[1] 40 | output_filename = sys.argv[2] 41 | except: 42 | print usage 43 | sys.exit(-1) 44 | main(input_filename, output_filename) 45 | -------------------------------------------------------------------------------- /videx/vreverse.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | """ 4 | vreverse.py 5 | 6 | Created by Ben Lacker on 2009-06-19. 7 | Copyright (c) 2009 __MyCompanyName__. All rights reserved. 8 | """ 9 | 10 | import sys 11 | import os 12 | 13 | from echonest.remix import video 14 | 15 | usage = """ 16 | Usage: 17 | python vreverse.py 18 | 19 | Example: 20 | python vreverse.py beats YouCanCallMeAl.mpg AlMeCallCanYou.mpg 21 | """ 22 | 23 | 24 | def main(toReverse, inputFilename, outputFilename): 25 | if inputFilename.startswith("http://"): 26 | av = video.loadavfromyoutube(inputFilename) 27 | else: 28 | av = video.loadav(inputFilename) 29 | if toReverse == 'tatums': 30 | chunks = av.audio.analysis.tatums 31 | elif toReverse == 'beats': 32 | chunks = av.audio.analysis.beats 33 | chunks.reverse() 34 | out = video.getpieces(av, chunks) 35 | out.save(outputFilename) 36 | 37 | if __name__ == '__main__': 38 | try : 39 | toReverse = sys.argv[1] 40 | inputFilename = sys.argv[2] 41 | outputFilename = sys.argv[3] 42 | except : 43 | print usage 44 | sys.exit(-1) 45 | if not toReverse in ["beats", "tatums"]: 46 | print usage 47 | sys.exit(-1) 48 | main(toReverse, inputFilename, outputFilename) 49 | -------------------------------------------------------------------------------- /waltzify/waltzify.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf=8 3 | 4 | """ 5 | waltzify.py 6 | 7 | Turn 4/4 music into 3/4 8 | Modified approach suggested by Mary Farbood. 9 | Created by Tristan Jehan. 10 | """ 11 | 12 | import os, sys 13 | import dirac, math 14 | from optparse import OptionParser 15 | from echonest.remix.audio import LocalAudioFile, AudioData 16 | from echonest.remix.action import render, Playback, display_actions 17 | 18 | 19 | def select_tempo(index, num_beats, min_tempo, max_tempo, rate): 20 | v = math.atan(float(rate)*float(index)/float(num_beats))/1.57 21 | return min_tempo + v * float(max_tempo-min_tempo) 22 | 23 | 24 | def do_work(track, options): 25 | 26 | # manage options 27 | verbose = bool(options.verbose) 28 | low_tempo = float(options.low) 29 | high_tempo = float(options.high) 30 | rate_tempo = float(options.rate) 31 | rubato = float(options.rubato) 32 | tempo = float(options.tempo) 33 | 34 | # acceleration or not 35 | if rate_tempo == 0: 36 | if tempo == 0: 37 | low_tempo = track.analysis.tempo['value'] 38 | high_tempo = low_tempo 39 | else: 40 | low_tempo = tempo 41 | high_tempo = tempo 42 | 43 | rates = [] 44 | count = min(max(0,int(options.offset)),1) 45 | beats = track.analysis.beats 46 | offset = int(beats[0].start * track.sampleRate) 47 | 48 | # for every beat 49 | for beat in beats[:-1]: 50 | 51 | # get a tempo, particularly for accelerando 52 | target_tempo = select_tempo(beats.index(beat), len(beats), low_tempo, high_tempo, rate_tempo) 53 | 54 | # calculate rates 55 | if count == 0: 56 | dur = beat.duration/2.0 57 | rate1 = 60.0 / (target_tempo * dur) 58 | stretch = dur * rate1 59 | rate2 = rate1 + rubato 60 | elif count == 1: 61 | rate1 = 60.0 / (target_tempo * beat.duration) 62 | 63 | # add a change of rate at a given time 64 | start1 = int(beat.start * track.sampleRate) 65 | rates.append((start1-offset, rate1)) 66 | if count == 0: 67 | start2 = int((beat.start+dur) * track.sampleRate) 68 | rates.append((start2-offset, rate2)) 69 | 70 | # show on screen 71 | if verbose: 72 | if count == 0: 73 | args = (beats.index(beat), count, beat.duration, dur*rate1, dur*rate2, 60.0/(dur*rate1), 60.0/(dur*rate2)) 74 | print "Beat %d (%d) | stretch %.3f sec into [%.3f|%.3f] sec | tempo = [%d|%d] bpm" % args 75 | elif count == 1: 76 | args = (beats.index(beat), count, beat.duration, beat.duration*rate1, 60.0/(beat.duration*rate1)) 77 | print "Beat %d (%d) | stretch %.3f sec into %.3f sec | tempo = %d bpm" % args 78 | 79 | count = (count + 1) % 2 80 | 81 | # get audio 82 | vecin = track.data[offset:int(beats[-1].start * track.sampleRate),:] 83 | 84 | # time stretch 85 | if verbose: 86 | print "\nTime stretching..." 87 | vecout = dirac.timeScale(vecin, rates, track.sampleRate, 0) 88 | 89 | # build timestretch AudioData object 90 | ts = AudioData(ndarray=vecout, shape=vecout.shape, 91 | sampleRate=track.sampleRate, numChannels=vecout.shape[1], 92 | verbose=verbose) 93 | 94 | # initial and final playback 95 | pb1 = Playback(track, 0, beats[0].start) 96 | pb2 = Playback(track, beats[-1].start, track.analysis.duration-beats[-1].start) 97 | 98 | return [pb1, ts, pb2] 99 | 100 | 101 | def main(): 102 | usage = "usage: %s [options] " % sys.argv[0] 103 | parser = OptionParser(usage=usage) 104 | parser.add_option("-o", "--offset", default=0, help="offset where to start counting") 105 | parser.add_option("-l", "--low", default=100, help="low tempo") 106 | parser.add_option("-H", "--high", default=192, help="high tempo") 107 | parser.add_option("-r", "--rate", default=0, help="acceleration rate (try 30)") 108 | parser.add_option("-R", "--rubato", default=0, help="rubato on second beat (try 0.2)") 109 | parser.add_option("-t", "--tempo", default=0, help="target tempo (try 160)") 110 | parser.add_option("-v", "--verbose", action="store_true", help="show results on screen") 111 | 112 | (options, args) = parser.parse_args() 113 | if len(args) < 1: 114 | parser.print_help() 115 | return -1 116 | 117 | verbose = options.verbose 118 | 119 | # get Echo Nest analysis for this file 120 | track = LocalAudioFile(args[0], verbose=verbose) 121 | 122 | if verbose: 123 | print "Waltzifying..." 124 | 125 | # this is where the work takes place 126 | actions = do_work(track, options) 127 | 128 | if verbose: 129 | display_actions(actions) 130 | 131 | # new name 132 | name = os.path.splitext(os.path.basename(args[0])) 133 | name = str(name[0] + '_waltz_%d' % int(options.offset) +'.mp3') 134 | 135 | if verbose: 136 | print "Rendering... %s" % name 137 | 138 | # send to renderer 139 | render(actions, name, verbose=verbose) 140 | 141 | if verbose: 142 | print "Success!" 143 | 144 | return 1 145 | 146 | 147 | if __name__ == "__main__": 148 | try: 149 | main() 150 | except Exception, e: 151 | print e 152 | --------------------------------------------------------------------------------