• Stars
    star
    156
  • Rank 238,355 (Top 5 %)
  • Language
    Python
  • Created over 9 years ago
  • Updated almost 8 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Generate MIDI files from time series data. You can control can control what octaves and octave ranges you want.

It's MIDITime!

Do you have time time series data you want to play as music? Of course you do!

MIDITime converts any kind of time series data into pitch, velocity and duration values based on musical options that you set up, then outputs a .mid file.

MIDI files aren't technically audio -- they're instructions on how software instruments should be played. You can either play .mid files directly in some music applications, or import them into a wide variety of music editors (like ProTools, Ableton, MaxMSP) and add a ton of bells and whistles to get broadcast-ready audio.

We used MIDITime to produce the data sonification in this episode of Reveal. The musical track -- without the talking -- is here.

Installing

pip install miditime

Usage

Very basic:

from miditime.miditime import MIDITime

# Instantiate the class with a tempo (120bpm is the default) and an output file destination.
mymidi = MIDITime(120, 'myfile.mid')

# Create a list of notes. Each note is a list: [time, pitch, velocity, duration]
midinotes = [
    [0, 60, 127, 3],  #At 0 beats (the start), Middle C with velocity 127, for 3 beats
    [10, 61, 127, 4]  #At 10 beats (12 seconds from start), C#5 with velocity 127, for 4 beats
]

# Add a track with those notes
mymidi.add_track(midinotes)

# Output the .mid file
mymidi.save_midi()

A little more fun, a lot more control:

Instantiate the class with a tempo (120bpm is the default), an output file destination, the number of seconds you want to represent a year in the final song (default is 5 sec/year), the base octave (C5 is middle C, so the default is 5, and how many octaves you want your output to range over (default is 1).

from miditime.miditime import MIDITime
mymidi = MIDITime(120, 'myfile.mid', 5, 5, 1)

Bring in some data (this is some earthquakes). I'm assuming your data is already in date order, from oldest to newest.

my_data = [
    {'event_date': <datetime object>, 'magnitude': 3.4},
    {'event_date': <datetime object>, 'magnitude': 3.2},
    {'event_date': <datetime object>, 'magnitude': 3.6},
    {'event_date': <datetime object>, 'magnitude': 3.0},
    {'event_date': <datetime object>, 'magnitude': 5.6},
    {'event_date': <datetime object>, 'magnitude': 4.0}
]

Convert your date/time data into an integer, like days since the epoch (Jan. 1, 1970). You can use the days_since_epoch() helper method, or not:

my_data_epoched = [{'days_since_epoch': mymidi.days_since_epoch(d['event_date']), 'magnitude': d['magnitude']} for d in my_data]

Convert your integer date/time to something reasonable for a song. For example, at 120 beats per minute, you'll need to scale the data down a lot to avoid a very long song if your data spans years. This uses the seconds_per_year attribute you set at the top, so if your date is converted to something other than days you may need to do your own conversion. But if your dataset spans years and your dates are in days (with fractions is fine), use the beat() helper method.

my_data_timed = [{'beat': mymidi.beat(d['days_since_epoch']), 'magnitude': d['magnitude']} for d in my_data_epoched]

Get the earliest date in your series so you can set that to 0 in the MIDI:

start_time = my_data_timed[0]['beat']

Set up some functions to scale your other variable (magnitude in our case) to match your desired mode/key and octave range. There are helper methods to assist this scaling, very similar to a charting library like D3. You can choose a linear or logarithmic scale.

def mag_to_pitch_tuned(magnitude):
    # 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 = mymidi.linear_scale_pct(3, 5.7, magnitude)

    # Another option: Linear scale, reverse order
    # scale_pct = mymidi.linear_scale_pct(3, 5.7, magnitude, True)

    # Another option: Logarithmic scale, reverse order
    # scale_pct = mymidi.log_scale_pct(3, 5.7, magnitude, True)

    # Pick a range of notes. This allows you to play in a key.
    c_major = ['C', 'D', 'E', 'F', 'G', 'A', 'B']

    #Find the note that matches your data point
    note = mymidi.scale_to_note(scale_pct, c_major)

    #Translate that note to a MIDI pitch
    midi_pitch = mymidi.note_to_midi_pitch(note)

    return midi_pitch

Now build your note list

note_list = []

for d in my_data_timed:
    note_list.append([
        d['beat'] - start_time,
        mag_to_pitch_tuned(d['magnitude']),
        100,  # velocity
        1  # duration, in beats
    ])

And finish

# Add a track with those notes
mymidi.add_track(note_list)

# Output the .mid file
mymidi.save_midi()

Play your music:

There are many programs to work with MIDI, but timidity (installable with apt) is a simple command-line one if you just want to hear what you hath wrought.

timidity mymidifilename.mid

License

This software is released under an MIT license. It would be awful nice if you credited Reveal and Michael Corey somehow if you use this to make something awesome.

Credits

Many thanks to Julia Smith for helping me to understand musical keys/modes better.

MIDITime is a wrapper around the actual midi-making hotness of midiutil, produced by Mark Conway Wirt. I have included midiutil in this package per his recommendation.

More Repositories

1

foiamachine

Automate your FOIAs. The real, production version.
CSS
44
star
2

Silicon-Valley-Diversity-Data

Download EEO-1 reports from Silicon Valley tech companies. Compiled by Reveal from The Center for Investigative Reporting
33
star
3

border_fence_map

GIS data for the U.S.-Mexico border fence (perhaps a wall in the future)
26
star
4

impact-tracker

PHP
19
star
5

vm

A series of shell scripts to bootstrap a data journalism virtual machine image for VirtualBox
Shell
18
star
6

svg_mapper

Tools to create SVG maps with an automatic VML backup for legacy IE browsers using GeoDjango and Raphaël
Python
17
star
7

django-project-template

A collection of development tasks and optimizations aimed at anyone doing news application development on tight deadlines in Django.
Python
17
star
8

generator-newsapp

yeoman generator for newsapps.
JavaScript
16
star
9

va-data-dashboard

dashboard for veterans data
CSS
13
star
10

data_sonification_piano_bar

A SRCCON 2016 experiment.
Python
8
star
11

space-journalism-handbook

The public website for the Space Journalism Handbook. Want to help?
HTML
6
star
12

rainmaker

A platform for collecting and displaying information about top state-level campaign donors.
JavaScript
5
star
13

legiscan-training

Jupyter Notebook
5
star
14

data-journalism-ignite

Slides and supporting material for Chase's data journalism ignite talk from NewsFoo 2012.
JavaScript
5
star
15

zoom_stitcher

Python functions for downloading, resizing, stitching together, and clipping GDAL-compatible geo-referenced raster graphics.
Python
4
star
16

tile_factory

Scripts to manage (upload and delete) tiles from MBTiles format to s3
Python
4
star
17

birddog

FOIA tracker app for News Hack Day
JavaScript
4
star
18

politicsverbatim

JavaScript
3
star
19

altair-theme-reveal

Jupyter Notebook
1
star
20

pesticides_data

Publicly released data from our http://apps.cironline.org/pesticides/ project
CSS
1
star
21

calvin

6 degrees of Kevin Bacon are not enough degrees to contain Calvin. Calvin is absolute. Calvin transcends state.
Python
1
star
22

az_stops

A quick tutorial on how to conduct an analysis of traffic stops to detect patterns of racial bias.
Jupyter Notebook
1
star
23

JupyterPandasTraining

Jupyter Notebook
1
star
24

sba-ppp-states-analysis

1
star