class Coal2Midi(object): ''' Adapted from Jordan Wirfs-Brock's awesome coal production sonification. Post here: http://insideenergy.org/2016/05/03/listen-to-u-s-coal-production-fall-off-a-cliff/ Code and data here: https://github.com/InsideEnergy/Data-for-stories/tree/master/20160503-coal-production-sonification ''' epoch = datetime(1970, 1, 1) # TODO: Allow this to override the midtime epoch mymidi = None tempo = 120 min_attack = 30 max_attack = 255 min_duration = 1 max_duration = 5 seconds_per_year = 26 c_major = ['C', 'D', 'E', 'F', 'G', 'A', 'B'] c_minor = ['C', 'D', 'Eb', 'F', 'G', 'Ab', 'Bb'] a_minor = ['A', 'B', 'C', 'D', 'E', 'F', 'F#', 'G', 'G#'] c_blues_minor = ['C', 'Eb', 'F', 'F#', 'G', 'Bb'] d_minor = ['D', 'E', 'F', 'G', 'A', 'Bb', 'C'] c_gregorian = ['C', 'D', 'Eb', 'F', 'G', 'Ab', 'A', 'Bb'] current_key = c_major base_octave = 4 octave_range = 3 def __init__(self): self.csv_to_miditime() def read_csv(self, filepath): csv_file = open(filepath, 'rU') return csv.DictReader(csv_file, delimiter=',', quotechar='"') def remove_weeks(self, csv_obj): return [r for r in csv_obj if int(r['Week']) not in [53]] def round_to_quarter_beat(self, input): return round(input * 4) / 4 def round_to_half_beat(self, input): return round(input * 2) / 2 def make_notes(self, data_timed, data_key): note_list = [] start_time = data_timed[0]['beat'] for d in data_timed: note_list.append([ # self.round_to_half_beat(d['beat'] - start_time), round(d['beat'] - start_time), self.data_to_pitch_tuned(d[data_key]), 100, #mag_to_attack(d['magnitude']), # attack 1 # duration, in beats ]) return note_list def data_to_pitch_tuned(self, datapoint): # Where does this data point sit in the domain of your data? (I.E. the min magnitude is 3, the max in 5.6). In this case the optional 'True' means the scale is reversed, so the highest value will return the lowest percentage. scale_pct = self.mymidi.linear_scale_pct(0, self.maximum, datapoint) # Another option: Linear scale, reverse order # scale_pct = mymidi.linear_scale_pct(0, self.maximum, datapoint, True) # Another option: Logarithmic scale, reverse order # scale_pct = mymidi.log_scale_pct(0, self.maximum, datapoint, True) # Pick a range of notes. This allows you to play in a key. mode = self.current_key #Find the note that matches your data point note = self.mymidi.scale_to_note(scale_pct, mode) #Translate that note to a MIDI pitch midi_pitch = self.mymidi.note_to_midi_pitch(note) return midi_pitch def mag_to_attack(self, datapoint): # Where does this data point sit in the domain of your data? (I.E. the min magnitude is 3, the max in 5.6). In this case the optional 'True' means the scale is reversed, so the highest value will return the lowest percentage. scale_pct = self.mymidi.linear_scale_pct(0, self.maximum, datapoint) #max_attack = 10 adj_attack = (1 - scale_pct) * max_attack + 70 #adj_attack = 100 return adj_attack def csv_to_miditime(self): self.mymidi = MIDITime(self.tempo, 'coaltest.mid', self.seconds_per_year, self.base_octave, self.octave_range) raw_data = self.read_csv('data/coal_prod_1984_2016_weeks_summed.csv') filtered_data = self.remove_weeks(raw_data) self.minimum = self.mymidi.get_data_range(filtered_data, 'CoalProd')[0] / 1000000.0 self.maximum = self.mymidi.get_data_range(filtered_data, 'CoalProd')[1] / 1000000.0 timed_data = [] # Get the first day in the dataset, so we can use it's day of the week to anchor our other weekly data. first_day = self.mymidi.map_week_to_day(filtered_data[0]['Year'], filtered_data[0]['Week']) for r in filtered_data: # Convert the week to a date in that week week_start_date = self.mymidi.map_week_to_day(r['Year'], r['Week'], first_day.weekday()) # To get your date into an integer format, convert that date into the number of days since Jan. 1, 1970 days_since_epoch = self.mymidi.days_since_epoch(week_start_date) # Convert that integer date into a beat beat = self.mymidi.beat(days_since_epoch) timed_data.append({ 'days_since_epoch': days_since_epoch, 'beat': beat, 'CoalProdMillions': float(r['CoalProd']) / 1000000.0 }) note_list = self.make_notes(timed_data, 'CoalProdMillions') # Add a track with those notes self.mymidi.add_track(note_list) # Output the .mid file self.mymidi.save_midi()
class Coal2Midi(object): ''' Adapted from Jordan Wirfs-Brock's awesome coal production sonification. Post here: http://insideenergy.org/2016/05/03/listen-to-u-s-coal-production-fall-off-a-cliff/ Code and data here: https://github.com/InsideEnergy/Data-for-stories/tree/master/20160503-coal-production-sonification ''' epoch = datetime(1970, 1, 1) # TODO: Allow this to override the midtime epoch mymidi = None tempo = 120 min_attack = 30 max_attack = 255 min_duration = 1 max_duration = 5 seconds_per_year = 26 c_major = ['C', 'D', 'E', 'F', 'G', 'A', 'B'] c_minor = ['C', 'D', 'Eb', 'F', 'G', 'Ab', 'Bb'] a_minor = ['A', 'B', 'C', 'D', 'E', 'F', 'F#', 'G', 'G#'] c_blues_minor = ['C', 'Eb', 'F', 'F#', 'G', 'Bb'] d_minor = ['D', 'E', 'F', 'G', 'A', 'Bb', 'C'] c_gregorian = ['C', 'D', 'Eb', 'F', 'G', 'Ab', 'A', 'Bb'] current_key = c_major base_octave = 4 octave_range = 3 def __init__(self): self.csv_to_miditime() def read_csv(self, filepath): csv_file = open(filepath, 'rU') return csv.DictReader(csv_file, delimiter=',', quotechar='"') def remove_weeks(self, csv_obj): return [r for r in csv_obj if int(r['Week']) not in [53]] def round_to_quarter_beat(self, input): return round(input * 4) / 4 def round_to_half_beat(self, input): return round(input * 2) / 2 def make_notes(self, data_timed, data_key): note_list = [] start_time = data_timed[0]['beat'] for d in data_timed: note_list.append([ # self.round_to_half_beat(d['beat'] - start_time), round(d['beat'] - start_time), self.data_to_pitch_tuned(d[data_key]), 100, #mag_to_attack(d['magnitude']), # attack 1 # duration, in beats ]) return note_list def data_to_pitch_tuned(self, datapoint): # Where does this data point sit in the domain of your data? (I.E. the min magnitude is 3, the max in 5.6). In this case the optional 'True' means the scale is reversed, so the highest value will return the lowest percentage. scale_pct = self.mymidi.linear_scale_pct(0, self.maximum, datapoint) # Another option: Linear scale, reverse order # scale_pct = mymidi.linear_scale_pct(0, self.maximum, datapoint, True) # Another option: Logarithmic scale, reverse order # scale_pct = mymidi.log_scale_pct(0, self.maximum, datapoint, True) # Pick a range of notes. This allows you to play in a key. mode = self.current_key #Find the note that matches your data point note = self.mymidi.scale_to_note(scale_pct, mode) #Translate that note to a MIDI pitch midi_pitch = self.mymidi.note_to_midi_pitch(note) return midi_pitch def mag_to_attack(self, datapoint): # Where does this data point sit in the domain of your data? (I.E. the min magnitude is 3, the max in 5.6). In this case the optional 'True' means the scale is reversed, so the highest value will return the lowest percentage. scale_pct = self.mymidi.linear_scale_pct(0, self.maximum, datapoint) #max_attack = 10 adj_attack = (1 - scale_pct) * max_attack + 70 #adj_attack = 100 return adj_attack def csv_to_miditime(self): self.mymidi = MIDITime(self.tempo, 'coaltest.mid', self.seconds_per_year, self.base_octave, self.octave_range) raw_data = self.read_csv('data/coal_prod_1984_2016_weeks_summed.csv') filtered_data = self.remove_weeks(raw_data) self.minimum = self.mymidi.get_data_range(filtered_data, 'CoalProd')[0] / 1000000.0 self.maximum = self.mymidi.get_data_range(filtered_data, 'CoalProd')[1] / 1000000.0 timed_data = [] # Get the first day in the dataset, so we can use it's day of the week to anchor our other weekly data. first_day = self.mymidi.map_week_to_day(filtered_data[0]['Year'], filtered_data[0]['Week']) for r in filtered_data: # Convert the week to a date in that week week_start_date = self.mymidi.map_week_to_day( r['Year'], r['Week'], first_day.weekday()) # To get your date into an integer format, convert that date into the number of days since Jan. 1, 1970 days_since_epoch = self.mymidi.days_since_epoch(week_start_date) # Convert that integer date into a beat beat = self.mymidi.beat(days_since_epoch) timed_data.append({ 'days_since_epoch': days_since_epoch, 'beat': beat, 'CoalProdMillions': float(r['CoalProd']) / 1000000.0 }) note_list = self.make_notes(timed_data, 'CoalProdMillions') # Add a track with those notes self.mymidi.add_track(note_list) # Output the .mid file self.mymidi.save_midi()
class bomb2midi(object): ''' Submitted by Jennifer LaFleur. ''' epoch = datetime( 1945, 1, 1) # Not actually necessary, but optional to specify your own mymidi = None min_value = 0 max_value = 5.7 tempo = 120 min_attack = 30 max_attack = 255 min_duration = 1 max_duration = 5 seconds_per_year = 3 c_major = ['C', 'D', 'E', 'F', 'G', 'A', 'B'] c_minor = ['C', 'D', 'Eb', 'F', 'G', 'Ab', 'Bb'] a_minor = ['A', 'B', 'C', 'D', 'E', 'F', 'F#', 'G', 'G#'] c_blues_minor = ['C', 'Eb', 'F', 'F#', 'G', 'Bb'] d_minor = ['D', 'E', 'F', 'G', 'A', 'Bb', 'C'] c_gregorian = ['C', 'D', 'Eb', 'F', 'G', 'Ab', 'A', 'Bb'] current_key = c_major base_octave = 2 octave_range = 5 def __init__(self): self.csv_to_miditime() def read_csv(self, filepath): csv_file = open(filepath, 'rU') return csv.DictReader(csv_file, delimiter=',', quotechar='"') def remove_weeks(self, csv_obj): return [r for r in csv_obj if r['Date'] not in ['']] def round_to_quarter_beat(self, input): return round(input * 4) / 4 def make_notes(self, data_timed, data_key): note_list = [] start_time = data_timed[0]['beat'] for d in data_timed: note_list.append([ self.round_to_quarter_beat(d['beat'] - start_time), self.data_to_pitch_tuned(d[data_key]), 100, #mag_to_attack(d['magnitude']), # attack 1 # duration, in beats ]) return note_list def csv_to_miditime(self): raw_data = list(self.read_csv('data/bombs.csv')) filtered_data = self.remove_weeks(raw_data) self.mymidi = MIDITime(self.tempo, 'bombtest_log.mid', self.seconds_per_year, self.base_octave, self.octave_range, self.epoch) self.minimum = self.mymidi.get_data_range(filtered_data, 'Yieldnum')[0] self.maximum = self.mymidi.get_data_range(filtered_data, 'Yieldnum')[1] timed_data = [] for r in filtered_data: python_date = datetime.strptime(r["Date"], "%m/%d/%Y") days_since_epoch = self.mymidi.days_since_epoch(python_date) beat = self.mymidi.beat(days_since_epoch) timed_data.append({ 'days_since_epoch': days_since_epoch, 'beat': beat, 'BombYieldMillions': float(r['Yieldnum']) }) note_list = self.make_notes(timed_data, 'BombYieldMillions') # Add a track with those notes self.mymidi.add_track(note_list) # Output the .mid file self.mymidi.save_midi() def data_to_pitch_tuned(self, datapoint): # Where does this data point sit in the domain of your data? (I.E. the min magnitude is 3, the max in 5.6). In this case the optional 'True' means the scale is reversed, so the highest value will return the lowest percentage. #scale_pct = self.mymidi.linear_scale_pct(0, self.maximum, datapoint) # Another option: Linear scale, reverse order # scale_pct = self.mymidi.linear_scale_pct(0, self.maximum, datapoint, True) # print 10**self.maximum # Another option: Logarithmic scale, reverse order scale_pct = self.mymidi.log_scale_pct(0, self.maximum, datapoint, True, 'log') # Pick a range of notes. This allows you to play in a key. mode = self.current_key #Find the note that matches your data point note = self.mymidi.scale_to_note(scale_pct, mode) #Translate that note to a MIDI pitch midi_pitch = self.mymidi.note_to_midi_pitch(note) print scale_pct, note return midi_pitch def mag_to_attack(self, datapoint): # Where does this data point sit in the domain of your data? (I.E. the min magnitude is 3, the max in 5.6). In this case the optional 'True' means the scale is reversed, so the highest value will return the lowest percentage. scale_pct = self.mymidi.linear_scale_pct(0, self.maximum, datapoint) #max_attack = 10 adj_attack = (1 - scale_pct) * max_attack + 70 #adj_attack = 100 return adj_attack
class Electricity2Midi(object): ''' Data from http://www.eia.gov/totalenergy/data/monthly/#electricity ''' epoch = datetime(1973, 1, 1) # TODO: Allow this to override the midtime epoch mymidi = None tempo = 120 min_attack = 30 max_attack = 255 min_duration = 1 max_duration = 5 seconds_per_year = 3 c_major = ['C', 'D', 'E', 'F', 'G', 'A', 'B'] c_minor = ['C', 'D', 'Eb', 'F', 'G', 'Ab', 'Bb'] a_minor = ['A', 'B', 'C', 'D', 'E', 'F', 'F#', 'G', 'G#'] c_blues_minor = ['C', 'Eb', 'F', 'F#', 'G', 'Bb'] d_minor = ['D', 'E', 'F', 'G', 'A', 'Bb', 'C'] c_gregorian = ['C', 'D', 'Eb', 'F', 'G', 'Ab', 'A', 'Bb'] current_key = c_major base_octave = 4 octave_range = 3 def __init__(self): self.csv_to_miditime() def read_csv(self, filepath): csv_file = open(filepath, 'rU') return csv.DictReader(csv_file, delimiter=',', quotechar='"') def round_to_quarter_beat(self, input): return round(input * 4) / 4 def round_to_half_beat(self, input): return round(input * 2) / 2 def make_notes(self, data_timed, data_key, channel=0): note_list = [] # start_time = data_timed[0]['beat'] for d in data_timed: note_list.append([ [ # self.round_to_half_beat(d['beat'] - start_time), d['beat'], self.data_to_pitch_tuned(d[data_key]), 100, #mag_to_attack(d['magnitude']), # attack 0.5 # duration, in beats ], channel ]) return note_list def data_to_pitch_tuned(self, datapoint): # Where does this data point sit in the domain of your data? (I.E. the min magnitude is 3, the max in 5.6). In this case the optional 'True' means the scale is reversed, so the highest value will return the lowest percentage. scale_pct = self.mymidi.linear_scale_pct(0, self.maximum, datapoint) # Another option: Linear scale, reverse order # scale_pct = mymidi.linear_scale_pct(0, self.maximum, datapoint, True) # Another option: Logarithmic scale, reverse order # scale_pct = mymidi.log_scale_pct(0, self.maximum, datapoint, True) # Pick a range of notes. This allows you to play in a key. mode = self.current_key #Find the note that matches your data point note = self.mymidi.scale_to_note(scale_pct, mode) #Translate that note to a MIDI pitch midi_pitch = self.mymidi.note_to_midi_pitch(note) return midi_pitch def mag_to_attack(self, datapoint): # Where does this data point sit in the domain of your data? (I.E. the min magnitude is 3, the max in 5.6). In this case the optional 'True' means the scale is reversed, so the highest value will return the lowest percentage. scale_pct = self.mymidi.linear_scale_pct(0, self.maximum, datapoint) #max_attack = 10 adj_attack = (1 - scale_pct) * max_attack + 70 #adj_attack = 100 return adj_attack def energy_source_to_channel(self, data, attribute_name, channel): timed_data = [] for r in data: if r[attribute_name]: # Ignore nulls print r[attribute_name] # Convert the month to a date in that week month_start_date = datetime.strptime('%s 1' % (r['Month'], ), '%Y %B %d') print month_start_date # week_start_date = self.mymidi.map_week_to_day(r['Year'], r['Week'], first_day.weekday()) # To get your date into an integer format, convert that date into the number of days since Jan. 1, 1970 days_since_epoch = self.mymidi.days_since_epoch( month_start_date) # Convert that integer date into a beat beat = round(self.mymidi.beat(days_since_epoch) * 2) / 2 # Round to half beat # beat = round(self.mymidi.beat(days_since_epoch)) # Round to beat timed_data.append({ 'days_since_epoch': days_since_epoch, 'beat': beat, 'datapoint': float(r[attribute_name]) }) note_list = self.make_notes(timed_data, 'datapoint', channel) return note_list def remove_nulls(self, data_list): output = [] for d in data_list: row = {} for key, value in d.iteritems(): if value == 'Not Available': row[key] = None else: row[key] = value output.append(row) return output def csv_to_miditime(self): self.mymidi = MIDITime(self.tempo, 'electricity_monthly.mid', self.seconds_per_year, self.base_octave, self.octave_range, self.epoch) raw_data = list(self.read_csv('data/electricity_sources_monthly.csv')) filtered_data = self.remove_nulls(raw_data) # Find the range of all your data nat_gas_min = self.mymidi.get_data_range( filtered_data, 'Electricity Net Generation From Natural Gas, All Sectors')[0] nat_gas_max = self.mymidi.get_data_range( filtered_data, 'Electricity Net Generation From Natural Gas, All Sectors')[1] coal_min = self.mymidi.get_data_range( filtered_data, 'Electricity Net Generation From Coal, All Sectors')[0] coal_max = self.mymidi.get_data_range( filtered_data, 'Electricity Net Generation From Coal, All Sectors')[1] nuclear_min = self.mymidi.get_data_range( filtered_data, 'Electricity Net Generation From Nuclear Electric Power, All Sectors' )[0] nuclear_max = self.mymidi.get_data_range( filtered_data, 'Electricity Net Generation From Nuclear Electric Power, All Sectors' )[1] solar_min = self.mymidi.get_data_range( filtered_data, 'Electricity Net Generation From Solar/PV, All Sectors')[0] solar_max = self.mymidi.get_data_range( filtered_data, 'Electricity Net Generation From Solar/PV, All Sectors')[1] wind_min = self.mymidi.get_data_range( filtered_data, "Electricity Net Generation From Wind, All Sectors")[0] wind_max = self.mymidi.get_data_range( filtered_data, "Electricity Net Generation From Wind, All Sectors")[1] self.minimum = min( [nat_gas_min, coal_min, nuclear_min, solar_min, wind_min]) self.maximum = max( [nat_gas_max, coal_max, nuclear_max, solar_max, wind_max]) coal_notes = self.energy_source_to_channel( filtered_data, 'Electricity Net Generation From Coal, All Sectors', 0) natural_gas_notes = self.energy_source_to_channel( filtered_data, 'Electricity Net Generation From Natural Gas, All Sectors', 1) nuclear_notes = self.energy_source_to_channel( filtered_data, 'Electricity Net Generation From Nuclear Electric Power, All Sectors', 2) solar_notes = self.energy_source_to_channel( filtered_data, 'Electricity Net Generation From Solar/PV, All Sectors', 3) wind_notes = self.energy_source_to_channel( filtered_data, 'Electricity Net Generation From Wind, All Sectors', 4) # Add a track with those notes self.mymidi.add_track(natural_gas_notes + coal_notes + nuclear_notes + solar_notes + wind_notes) # Output the .mid file self.mymidi.save_midi()
class Pebble(object): ''' Lots of stuff cribbed from here: https://www.angio.net/personal/climb/speed ''' g = 9.8 mass_grams = 141 # 5 oz, or a baseball epoch = datetime( 2004, 1, 1) # Not actually necessary, but optional to specify your own mymidi = None tempo = 120 min_velocity = 30 max_velocity = 127 min_impact_duration = 1 max_impact_duration = 4 seconds_per_year = 1 c_major = ['C', 'D', 'E', 'F', 'G', 'A', 'B'] c_minor = ['C', 'D', 'Eb', 'F', 'G', 'Ab', 'Bb'] a_minor = ['A', 'B', 'C', 'D', 'E', 'F', 'F#', 'G', 'G#'] c_blues_minor = ['C', 'Eb', 'F', 'F#', 'G', 'Bb'] d_minor = ['D', 'E', 'F', 'G', 'A', 'Bb', 'C'] c_gregorian = ['C', 'D', 'Eb', 'F', 'G', 'Ab', 'A', 'Bb'] current_key = c_major base_octave = 2 octave_range = 4 def __init__(self): self.csv_to_miditime() def get_yearly_averages(self, rows, date_var, date_format, distance_var, unit): years = {} for r in rows: # filter out nulls if r[distance_var]: if r[distance_var] != '': # extract year year = datetime.strptime(r[date_var], date_format).year # make a decade decade = int('%s0' % (str(year)[:-1], )) # convert to meters (if feet): if unit == 'feet': distance_meters = self.feet_to_meters( float(r[distance_var])) else: distance_meters = float(r[distance_var]) if decade not in years: years[decade] = [distance_meters] else: years[decade].append(distance_meters) # now get averages output = [] for year, values in years.iteritems(): yearly_avg = { 'year': year, 'median_distance_meters': median(values) } output.append(yearly_avg) print yearly_avg # sort them return sorted(output, key=lambda k: k['year']) def feet_to_meters(self, feet): return float(feet) * 0.3048 def time_to_impact(self, height_meters): return math.sqrt(2 * float(height_meters) / self.g) def seconds_to_beats(self, seconds): # Just for manually setting seconds return seconds * (self.tempo / 60) def read_csv(self, filepath): csv_file = open(filepath, 'rU') return csv.DictReader(csv_file, delimiter=',', quotechar='"') # # def round_to_quarter_beat(self, input): # return round(input * 4) / 4 def velocity_on_impact(self, height_meters): # sqrt( 2 * g * height ) return math.sqrt(2 * self.g * float(height_meters)) def energy_on_impact( self, mass, velocity ): # Energy at splat time: 1/2 * mass * velocity2 = mass * g * height return (mass * velocity) / 2 def energy_to_velocity(self, datapoint): # Where does this data point sit in the domain of your data? (I.E. the min magnitude is 3, the max in 5.6). In this case the optional 'True' means the scale is reversed, so the highest value will return the lowest percentage. #scale_pct = self.mymidi.linear_scale_pct(0, self.maximum, datapoint) # Another option: Linear scale, reverse order scale_pct = self.mymidi.linear_scale_pct(0, self.maximum_energy, datapoint) # print 10**self.maximum # Another option: Logarithmic scale, reverse order # scale_pct = self.mymidi.log_scale_pct(0, self.maximum, datapoint, True, 'log') velocity_range = self.max_velocity - self.min_velocity velocity = self.min_velocity + (scale_pct * velocity_range) return velocity def data_to_pitch_tuned(self, datapoint): # Where does this data point sit in the domain of your data? (I.E. the min magnitude is 3, the max in 5.6). In this case the optional 'True' means the scale is reversed, so the highest value will return the lowest percentage. #scale_pct = self.mymidi.linear_scale_pct(0, self.maximum, datapoint) # Another option: Linear scale, reverse order scale_pct = self.mymidi.linear_scale_pct(0, self.maximum_energy, datapoint, True) # print 10**self.maximum # Another option: Logarithmic scale, reverse order # scale_pct = self.mymidi.log_scale_pct(0, self.maximum, datapoint, True, 'log') # Pick a range of notes. This allows you to play in a key. mode = self.current_key #Find the note that matches your data point note = self.mymidi.scale_to_note(scale_pct, mode) #Translate that note to a MIDI pitch midi_pitch = self.mymidi.note_to_midi_pitch(note) return midi_pitch def energy_to_duration(self, datapoint): # For impact duration, not fall scale_pct = self.mymidi.linear_scale_pct(self.minimum_energy, self.maximum_energy, datapoint) duration_range = self.max_impact_duration - self.min_impact_duration duration = self.min_impact_duration + (scale_pct * duration_range) return duration def make_falling_notes(self, data_timed, data_key, channel): note_list = [] start_time = data_timed[0]['beat'] for d in data_timed: note_list.append([ [ d['beat'] - start_time, self.mymidi.note_to_midi_pitch( "C4"), # pitch (set manually for drop) 100, # velocity self.seconds_to_beats( d['duration_secs']) # duration, in beats ], channel ]) return note_list def make_splashing_notes(self, data_timed, data_key, channel): note_list = [] start_time = data_timed[0]['beat'] for d in data_timed: velocity = self.velocity_on_impact(d['distance_meters']) energy = self.energy_on_impact(self.mass_grams, velocity) note_list.append([ [ d['beat'] - start_time + self.seconds_to_beats( d[data_key]), # falling start plus duration of fall self.data_to_pitch_tuned(energy), # pitch self.energy_to_velocity(energy), # velocity self.energy_to_duration(energy) # duration, in beats ], channel ]) return note_list def csv_to_miditime(self): # raw_data = list(self.read_csv('data/groundwater_test.csv')) raw_data = list(self.read_csv('data/15S18E30L001M_clean.csv')) # yearly_data = self.get_yearly_averages(raw_data, 'Date', "%m/%d/%Y", 'wl(m)', 'meters') yearly_data = self.get_yearly_averages(raw_data, 'Measurement_Date', "%m-%d-%Y", 'GSWS', 'feet') self.mymidi = MIDITime(self.tempo, 'media_out/pebble_longterm.mid', self.seconds_per_year, self.base_octave, self.octave_range, self.epoch) self.minimum_depth = self.mymidi.get_data_range( yearly_data, 'median_distance_meters')[0] self.maximum_depth = self.mymidi.get_data_range( yearly_data, 'median_distance_meters')[1] self.minimum_energy = self.energy_on_impact( self.mass_grams, self.velocity_on_impact( self.mymidi.get_data_range(yearly_data, 'median_distance_meters')[0])) self.maximum_energy = self.energy_on_impact( self.mass_grams, self.velocity_on_impact( self.mymidi.get_data_range(yearly_data, 'median_distance_meters')[1])) timed_data = [] for r in yearly_data: # python_date = datetime.strptime(r["Date"], "%Y-%m-%d") python_date = datetime.strptime('1/1/%s' % r["year"], "%m/%d/%Y") distance_meters = r['median_distance_meters'] days_since_epoch = self.mymidi.days_since_epoch(python_date) beat = self.mymidi.beat(days_since_epoch) timed_data.append({ 'days_since_epoch': days_since_epoch, 'beat': beat, 'distance_meters': distance_meters, 'duration_secs': self.time_to_impact(distance_meters) }) falling_note_list = self.make_falling_notes(timed_data, 'duration_secs', 0) splashing_note_list = self.make_splashing_notes( timed_data, 'duration_secs', 1) # Add a track with those notes self.mymidi.add_track(falling_note_list) self.mymidi.add_track(splashing_note_list) # Output the .mid file self.mymidi.save_midi()
class bomb2midi(object): ''' Submitted by Jennifer LaFleur. ''' epoch = datetime(1945, 1, 1) # Not actually necessary, but optional to specify your own mymidi = None min_value = 0 max_value = 5.7 tempo = 120 min_attack = 30 max_attack = 255 min_duration = 1 max_duration = 5 seconds_per_year = 3 c_major = ['C', 'D', 'E', 'F', 'G', 'A', 'B'] c_minor = ['C', 'D', 'Eb', 'F', 'G', 'Ab', 'Bb'] a_minor = ['A', 'B', 'C', 'D', 'E', 'F', 'F#', 'G', 'G#'] c_blues_minor = ['C', 'Eb', 'F', 'F#', 'G', 'Bb'] d_minor = ['D', 'E', 'F', 'G', 'A', 'Bb', 'C'] c_gregorian = ['C', 'D', 'Eb', 'F', 'G', 'Ab', 'A', 'Bb'] current_key = c_major base_octave = 2 octave_range = 5 def __init__(self): self.csv_to_miditime() def read_csv(self, filepath): csv_file = open(filepath, 'rU') return csv.DictReader(csv_file, delimiter=',', quotechar='"') def remove_weeks(self, csv_obj): return [r for r in csv_obj if r['Date'] not in ['']] def round_to_quarter_beat(self, input): return round(input * 4) / 4 def make_notes(self, data_timed, data_key): note_list = [] start_time = data_timed[0]['beat'] for d in data_timed: note_list.append([ self.round_to_quarter_beat(d['beat'] - start_time), self.data_to_pitch_tuned(d[data_key]), 100, #mag_to_attack(d['magnitude']), # attack 1 # duration, in beats ]) return note_list def csv_to_miditime(self): raw_data = list(self.read_csv('data/bombs.csv')) filtered_data = self.remove_weeks(raw_data) self.mymidi = MIDITime(self.tempo, 'bombtest_log.mid', self.seconds_per_year, self.base_octave, self.octave_range, self.epoch) self.minimum = self.mymidi.get_data_range(filtered_data, 'Yieldnum')[0] self.maximum = self.mymidi.get_data_range(filtered_data, 'Yieldnum')[1] timed_data = [] for r in filtered_data: python_date = datetime.strptime(r["Date"], "%m/%d/%Y") days_since_epoch = self.mymidi.days_since_epoch(python_date) beat = self.mymidi.beat(days_since_epoch) timed_data.append({ 'days_since_epoch': days_since_epoch, 'beat': beat, 'BombYieldMillions': float(r['Yieldnum']) }) note_list = self.make_notes(timed_data, 'BombYieldMillions') # Add a track with those notes self.mymidi.add_track(note_list) # Output the .mid file self.mymidi.save_midi() def data_to_pitch_tuned(self, datapoint): # Where does this data point sit in the domain of your data? (I.E. the min magnitude is 3, the max in 5.6). In this case the optional 'True' means the scale is reversed, so the highest value will return the lowest percentage. #scale_pct = self.mymidi.linear_scale_pct(0, self.maximum, datapoint) # Another option: Linear scale, reverse order # scale_pct = self.mymidi.linear_scale_pct(0, self.maximum, datapoint, True) # print 10**self.maximum # Another option: Logarithmic scale, reverse order scale_pct = self.mymidi.log_scale_pct(0, self.maximum, datapoint, True, 'log') # Pick a range of notes. This allows you to play in a key. mode = self.current_key #Find the note that matches your data point note = self.mymidi.scale_to_note(scale_pct, mode) #Translate that note to a MIDI pitch midi_pitch = self.mymidi.note_to_midi_pitch(note) print scale_pct, note return midi_pitch def mag_to_attack(self, datapoint): # Where does this data point sit in the domain of your data? (I.E. the min magnitude is 3, the max in 5.6). In this case the optional 'True' means the scale is reversed, so the highest value will return the lowest percentage. scale_pct = self.mymidi.linear_scale_pct(0, self.maximum, datapoint) #max_attack = 10 adj_attack = (1 - scale_pct) * max_attack + 70 #adj_attack = 100 return adj_attack