From Data to Decibels: Sonifying Climate Change with Python and MIDI


Data visualization is a powerful tool for showing environmental changes, but data sonification—the process of converting datasets into sound—offers an entirely new way to experience information. By turning climate datasets like Ed Hawkins’ famous “Warming Stripes” into musical notes, we can literally hear the accelerating pace of global warming.

In this post, we will walk through how to take annual global temperature anomalies, map them to musical scales, and export them as a standard MIDI file using Python.


The Architecture of Sonification

To turn numbers into music, we need to map a continuous data point (temperature anomaly) to discrete musical parameters (pitch, velocity, or duration). Our pipeline will follow a straightforward 4-step workflow:

graph LR
    A["1. Ingest Data"] --> B["2. Normalize Values"]
    B --> C["3. Map to Scales"]
    C --> D["4. Synthesize MIDI"]
  1. Ingest: Load the historical temperature anomaly data using pandas.
  2. Normalize: Scale the raw temperature values to fit within a target range (0 to 1) using scikit-learn.
  3. Map: Translate those scaled values into specific MIDI notes bounded by a musical scale.
  4. Synthesize: Write those notes to a track using the mido library.

Step 1: Setting Up the Environment

We will use pandas for data manipulation, scikit-learn to handle the data normalization, and mido for generating the MIDI file. You can install the dependencies via pip:

pip install pandas scikit-learn mido

Step 2: Preparing the Climate Data

For this project, we will use global temperature anomaly data (the deviation from a long-term baseline temperature). A typical dataset looks like this:

YearAnomaly (°C)
1880-0.16
1950-0.17
20000.39
20231.17

Here is how we load and normalize this data using min-max scaling so that every temperature variation falls neatly between 0.0 (the coldest year) and 1.0 (the warmest year):

import pandas as pd
from sklearn.preprocessing import MinMaxScaler

def load_and_normalize_data(csv_path):
    # Load dataset (assuming columns: 'Year' and 'Anomaly')
    df = pd.read_csv(csv_path)
    
    # Initialize scaler to map temperatures between 0 and 1
    scaler = MinMaxScaler(feature_range=(0, 1))
    df['Normalized_Anomaly'] = scaler.fit_transform(df[['Anomaly']])
    
    return df

Step 3: Mapping Data to a Musical Scale

If you map data blindly to raw MIDI numbers (0–127), the result will sound chaotic and dissonant. To make it musical, we must constrain the output to a specific scale.

Because we are dealing with a somber topic like climate change, a Minor Pentatonic scale or a Natural Minor scale works beautifully. Let’s build a helper function that generates MIDI notes belonging to a specific scale across multiple octaves.

def generate_midi_scale(root_note, scale_intervals, octaves=4):
    """Generates a list of valid MIDI notes based on a root note and scale intervals."""
    scale = []
    for octave in range(octaves):
        for interval in scale_intervals:
            note = root_note + (octave * 12) + interval
            if note <= 127:
                scale.append(note)
    return sorted(scale)

# C-Minor Pentatonic intervals: Root, Minor 3rd, Perfect 4th, Perfect 5th, Minor 7th
C_MINOR_PENTATONIC = [0, 3, 5, 7, 10]
# Start at C3 (MIDI note 48)
musical_scale = generate_midi_scale(48, C_MINOR_PENTATONIC, octaves=4)

Now, we map the normalized temperature values (0.0 - 1.0) to the indices of our musical_scale array. Low values will trigger low notes, and high values will trigger high notes.

def map_value_to_scale(normalized_value, scale):
    """Maps a 0-1 value to the closest index in a musical scale array."""
    max_index = len(scale) - 1
    index = int(normalized_value * max_index)
    return scale[index]

Step 4: Generating the MIDI File

With our data mapped to musical notes, we use mido to build the MIDI file sequence. We will also dynamically calculate the velocity (volume) of each note: colder years will play softly, while warmer years will strike aggressively loud.

from mido import Message, MidiFile, MidiTrack

def create_climate_midi(df, scale, output_filename="climate_sonification.mid"):
    mid = MidiFile()
    track = MidiTrack()
    mid.tracks.append(track)
    
    # Add a program change to set the instrument (Acoustic Grand Piano)
    track.append(Message('program_change', program=0, time=0))
    
    # Playback settings
    note_duration = 240  # In MIDI ticks (240 ticks = 1/8 note at standard resolution)
    
    for index, row in df.iterrows():
        # Get note based on temperature
        note = map_value_to_scale(row['Normalized_Anomaly'], scale)
        
        # Dynamically scale velocity (volume) between 40 and 110 based on temperature
        velocity = int(40 + (row['Normalized_Anomaly'] * 70))
        
        # Note On event
        track.append(Message('note_on', note=note, velocity=velocity, time=0))
        
        # Note Off event (tells the sequencer to hold the note for note_duration)
        track.append(Message('note_off', note=note, velocity=0, time=note_duration))
        
    mid.save(output_filename)
    print(f"Successfully saved sonified data to {output_filename}")

# Orchestrating the execution (example wrapper)
# df = load_and_normalize_data("global_temps.csv")
# create_climate_midi(df, musical_scale)

Taking It Further: Creative Enhancements

Once you have your base MIDI file generated, you can import it into any Digital Audio Workstation (DAW) like Ableton Live, Logic Pro, or Reaper. To elevate the sonification, consider these approaches:

  • Dual-Track Counterpoint: Map global temperature anomalies to pitch on Track 1, and map carbon dioxide ($CO_2$) parts-per-million levels to a low synth bass line on Track 2.
  • Harmonic Modulation: Instead of just changing the pitch of single notes, use the data to control the complexity of chords—moving from simple triads during stable climate eras to tense, dissonant chords as anomalies peak.
  • Automation Mapping: Use the data points to generate MIDI CC (Continuous Controller) messages. You can map these to a synthesizer’s filter cutoff parameter, creating a sound that grows brighter and more distorted as the planet warms.