Skip to content

Instantly share code, notes, and snippets.

@jiaaro
Last active April 11, 2023 04:11
Show Gist options
  • Star 13 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save jiaaro/339df443b005e12d6c2a to your computer and use it in GitHub Desktop.
Save jiaaro/339df443b005e12d6c2a to your computer and use it in GitHub Desktop.
Generate a wave file from a MIDI file with Pydub

Simple example of rendering a midi file with Pydub

Basically, this takes a MIDI input file (I just googled and grabbed one of Maroon 5's "Animal" – I know, I know) and generates a WAV file.

NOTE: This is the slowest midi rendering program I have ever seen!

Dependencies:

In this example I an rendering a MIDI file of Maroon 5's "Animals" from http://www.free-midi.org

ok, ok, ok - Here's the output:

Here is the output my program generated (converted to mp3):

output.mp3

Obvious issues:

  • Tempo is hard-coded
  • All instruments are rendered using a Sine wave, including drums. Would be better to use different signal generators depending which instrument is being rendered (Sine is fine for flute, square is closer to saxaphone, etc)
  • This is REALLLY SLOWWW. I haven't looked at performance at all, but it took almost an hour (2014, 2.8 GHz i7, Macbook Pro) to render the wav file with the code above. LOL

One way to improve performance is to use a Mixer class like this one instead of just overlaying every note onto a silent "output" audio segment.

from collections import defaultdict
from mido import MidiFile
from pydub import AudioSegment
from pydub.generators import Sine
def note_to_freq(note, concert_A=440.0):
'''
from wikipedia: http://en.wikipedia.org/wiki/MIDI_Tuning_Standard#Frequency_values
'''
return (2.0 ** ((note - 69) / 12.0)) * concert_A
mid = MidiFile("./maroon_5-animals.mid")
output = AudioSegment.silent(mid.length * 1000.0)
tempo = 100 # bpm
def ticks_to_ms(ticks):
tick_ms = (60000.0 / tempo) / mid.ticks_per_beat
return ticks * tick_ms
for track in mid.tracks:
# position of rendering in ms
current_pos = 0.0
current_notes = defaultdict(dict)
# current_notes = {
# channel: {
# note: (start_time, message)
# }
# }
for msg in track:
current_pos += ticks_to_ms(msg.time)
if msg.type == 'note_on':
current_notes[msg.channel][msg.note] = (current_pos, msg)
if msg.type == 'note_off':
start_pos, start_msg = current_notes[msg.channel].pop(msg.note)
duration = current_pos - start_pos
signal_generator = Sine(note_to_freq(msg.note))
rendered = signal_generator.to_audio_segment(duration=duration-50, volume=-20).fade_out(100).fade_in(30)
output = output.overlay(rendered, start_pos)
output.export("animal.wav", format="wav")
@usworked
Copy link

usworked commented Dec 5, 2019

I added on the top of the script:

from collections import defaultdict

and it worked. Thanks!

@ttuanho
Copy link

ttuanho commented May 14, 2020

Thank you so much!

@Ashindustry007
Copy link

The piano notes in my midi are no more audible after the conversion. How to retain it...??

@matcha1024
Copy link

Me Too.
How to retain it?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment