Created
January 26, 2021 16:27
-
-
Save PARC6502/277b79c960ed1d0e4334ce40acd124a5 to your computer and use it in GitHub Desktop.
A midi to Sonic Pi script that can handle tempo changes
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#! /usr/bin/env ruby | |
require 'midilib/sequence' | |
DEFAULT_MIDI_TEST_FILE = 'Tempo_Changes_test.mid' | |
# Read from MIDI file | |
seq = MIDI::Sequence.new() | |
# Either open the file provided by the first argument or the one defined above | |
File.open(ARGV[0] || DEFAULT_MIDI_TEST_FILE, 'rb') do |file| | |
# The block we pass in to Sequence.read is called at the end of every | |
# track read. It is optional, but is useful for progress reports. | |
seq.read(file) do |track, num_tracks, i| | |
#puts "read track #{track ? track.name : ''} (#{i} of #{num_tracks})" | |
end | |
end | |
PPQN = seq.ppqn # Pulses per quarter note, the number of ticks in one beat | |
def to_note (ticks) | |
# Converts the time in ticks to time in notes | |
return (ticks*1.0)/PPQN | |
end | |
def get_bpm (event) | |
# Gets a rounded bpm value from a Tempo event | |
return MIDI::Tempo.mpq_to_bpm(event.tempo).round | |
end | |
melodies = {} | |
sleeps = {} | |
bpm = seq.bpm.round.to_s | |
puts "Num tracks: #{seq.tracks.length}" | |
seq.each_with_index do |track, idx| | |
# Filter the track so it only has Note On events | |
puts track.name | |
track = track.select { |e| e.is_a?(MIDI::Tempo) || e.is_a?(MIDI::NoteOn) } | |
melody = [] | |
slps = [] | |
track.each_with_index do |e, i| | |
# Track tempo changes | |
if e.is_a?(MIDI::Tempo) | |
melody.push({bpm: get_bpm(e)}) | |
next | |
end | |
#Get the first sleep | |
if i == 0 | |
first_slp = to_note(e.time_from_start) | |
slps.push(first_slp) | |
end | |
# Calculate duration of note by comparing the time between on and off events. Convert from ticks to beats. | |
dur_delta = e.off.time_from_start - e.time_from_start | |
duration = to_note(dur_delta) | |
# Round duration to the closest 1/16 | |
#duration = (duration*16).round / 16.0 | |
note = e.note | |
melody.push({note: note, duration: duration}) | |
# There is one more note than there is sleep, so we have to break early on the last iteration | |
break if i == (track.length - 1) | |
slp_delta = track[i+1].time_from_start - e.time_from_start | |
slp = to_note(slp_delta) | |
slps.push(slp) | |
end | |
melodies[idx] = melody | |
sleeps[idx] = slps | |
end | |
#= Sonic Pi Output | |
# This section creates a file that's readable by sonic pi | |
#== Attack/sutain/release ratios | |
ATTACK = 0 | |
SUSTAIN = 0.8 | |
RELEASE = 0.2 | |
out = File.new('out.rb', 'w') | |
out.write("bpm = #{bpm}\n\n") | |
melodies.each do |key,value| | |
idx = key.to_s | |
out.write("melody#{idx} = #{value}\n") | |
out.write("sleeps#{idx} = #{sleeps[key]}\n\n") | |
out.write("in_thread do\n") | |
out.write(" sleep sleeps#{idx}[0]\n") | |
out.write(" melody#{idx}.each_with_index do |item,i|\n") | |
out.write(" unless item[:bpm].nil?\n") | |
out.write(" bpm = item[:bpm]\n") | |
out.write(" next\n") | |
out.write(" end \n") | |
out.write(" with_bpm bpm do\n") | |
out.write(" play item[:note]") | |
out.write(", attack: item[:duration]*#{ATTACK}") if ATTACK > 0 | |
out.write(", sustain: item[:duration]*#{SUSTAIN}") if SUSTAIN > 0 | |
out.write(", release: item[:duration]*#{RELEASE}") if RELEASE > 0 | |
out.write("\n") | |
out.write(" sleep sleeps#{idx}[i+1] if i+1 < sleeps#{idx}.length\n") | |
out.write(" end\n") #with_bpm | |
out.write(" end\n") #melody.each | |
out.write("end\n\n") #thread | |
end | |
out.close |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Script output on test file | |
bpm = 80 | |
melody0 = [{:bpm=>80}, {:note=>72, :duration=>0.9479166666666666}, {:note=>72, :duration=>0.9479166666666666}, {:note=>72, :duration=>0.9479166666666666}, {:note=>72, :duration=>0.9479166666666666}, {:bpm=>40}, {:note=>74, :duration=>0.9479166666666666}, {:note=>74, :duration=>0.9479166666666666}, {:note=>74, :duration=>0.9479166666666666}, {:note=>74, :duration=>0.9479166666666666}, {:bpm=>120}, {:note=>70, :duration=>0.9479166666666666}, {:note=>70, :duration=>0.9479166666666666}, {:note=>70, :duration=>0.9479166666666666}, {:note=>70, :duration=>0.9479166666666666}, {:bpm=>220}, {:note=>69, :duration=>0.9479166666666666}, {:note=>69, :duration=>0.9479166666666666}, {:note=>69, :duration=>0.9479166666666666}, {:note=>69, :duration=>0.9479166666666666}, {:note=>72, :duration=>0.9479166666666666}, {:note=>72, :duration=>0.9479166666666666}, {:note=>72, :duration=>0.9479166666666666}, {:note=>72, :duration=>0.9479166666666666}, {:note=>74, :duration=>0.9479166666666666}, {:note=>74, :duration=>0.9479166666666666}, {:note=>74, :duration=>0.9479166666666666}, {:note=>74, :duration=>0.9479166666666666}, {:note=>70, :duration=>0.9479166666666666}, {:note=>70, :duration=>0.9479166666666666}, {:note=>70, :duration=>0.9479166666666666}, {:note=>70, :duration=>0.9479166666666666}, {:bpm=>180}, {:note=>69, :duration=>0.9479166666666666}, {:bpm=>120}, {:note=>69, :duration=>0.9479166666666666}, {:bpm=>80}, {:note=>69, :duration=>0.9479166666666666}, {:bpm=>40}, {:note=>69, :duration=>0.9479166666666666}] | |
sleeps0 = [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0] | |
in_thread do | |
sleep sleeps0[0] | |
melody0.each_with_index do |item,i| | |
unless item[:bpm].nil? | |
bpm = item[:bpm] | |
next | |
end | |
with_bpm bpm do | |
play item[:note], sustain: item[:duration]*0.8, release: item[:duration]*0.2 | |
sleep sleeps0[i+1] if i+1 < sleeps0.length | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment