Last active
February 9, 2024 05:34
-
-
Save mmodrow/5750e0d17f574924ea44939a3ffc94f5 to your computer and use it in GitHub Desktop.
MIDI->RTTTL converter for BLHeli_32 and BlueJay Quadcopter ESCs
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
[CmdletBinding()] | |
param ( | |
[string] | |
$midiFilePath, | |
[string] | |
$label = "Label", | |
[int[]] | |
$midiChannels = @(2), | |
[int] | |
$ticksPerQuarterNote = 192, | |
[int] | |
$transpose = 0 | |
) | |
if ($midiFilePath -eq "") { | |
$midiFilePath = Get-ChildItem $PWD *.mid | Out-GridView -PassThru | Select-Object -ExpandProperty FullName | |
} | |
# get tool at: https://www.fourmilab.ch/webtools/midicsv/#Download | |
if (Test-Path $midiFilePath) { | |
if (!(Test-Path "./Midicsv.exe")) { | |
Write-Warning "Midicsv.exe missing. Get it at https://www.fourmilab.ch/webtools/midicsv/#Download and put it next to this script!" | |
Read-Host -Prompt "Press any key to quit." | Out-Null | |
exit 1 | |
} | |
$midiCsvFilePath = ($midiFilePath + ".csv") | |
& ./Midicsv.exe $midiFilePath $midiCsvFilePath | |
} else { | |
Write-Warning "Midi file `"" + $midiFilePath + "`" not found." | |
Write-Host "If you need to create/edit a midi file to use, check out http://www.midieditor.org/index.php?category=download" | |
Write-Host "For best results, quantize the midi file to 32nd notes or bigger." | |
Read-Host -Prompt "Press any key to quit." | Out-Null | |
exit 1 | |
} | |
# write Custom CSV Header for correct labelling of fields: | |
$customCsvHeaderLine = "MidiChannel, Ticks, Header, TrackId, NoteNumber, Velocity, DurationInTicks, ContributingNoteValues, RtttlNoteName, RtttlNoteNotation" | |
$midiCsvTxt = Get-Content $midiCsvFilePath | |
$midiCsvTxt[0] = $customCsvHeaderLine | |
$midi = $midiCsvTxt | convertfrom-csv | |
$msPerQuarterNote = ($midi | Where-Object { $_.Header -eq "Tempo" } | Select-Object -ExpandProperty TrackId -First 1) / 1000 | |
$bpm = [int](60 / ($msPerQuarterNote / 1000)) | |
function Get-NoteName { | |
param ( | |
[int] | |
$noteNumber, | |
[int] | |
$transpose | |
) | |
$noteNames = @("c", "c#", "d", "d#", "e", "f", "f#", "g", "g#", "a", "a#", "b") | |
$zeroedNoteNumber = $noteNumber - 12 | |
$noteName = $noteNames[$zeroedNoteNumber % 12] | |
[int] $octave = $zeroedNoteNumber / 12 - 1 + $transpose | |
$rttlNoteName = $noteName + $octave | |
return $rttlNoteName | |
} | |
function Get-ContributingNoteValues { | |
param ( | |
[int] | |
$ticks, | |
[int] | |
$ticksPerQuarterNote | |
) | |
$noteValues = [string[]]@( | |
"1", | |
"2.", | |
"2", | |
"4.", | |
"4", | |
"8.", | |
"8", | |
"16.", | |
"16", | |
"32.", | |
"32" | |
) | |
$noteValueTicks = [float[]]@( | |
(1 / 1), | |
(1 / 2 * 1.5), | |
(1 / 2), | |
(1 / 4 * 1.5), | |
(1 / 4), | |
(1 / 8 * 1.5), | |
(1 / 8), | |
(1 / 16 * 1.5), | |
(1 / 16), | |
(1 / 32 * 1.5), | |
(1 / 32) | |
) | |
$ticksPerFullNote = $ticksPerQuarterNote * 4 | |
$contributingNoteValues = [System.Collections.ArrayList]@() | |
while ($ticks -gt 0) { | |
for ($i = 0; $i -lt $noteValues.count; $i++) { | |
if ($ticks -ge ($ticksPerFullNote * $noteValueTicks[$i])) { | |
$contributingNoteValues.Add([string]$noteValues[$i]) | Out-Null | |
$ticks -= $ticksPerFullNote * $noteValueTicks[$i] | |
break | |
} | |
} | |
if ($ticks -lt ($ticksPerFullNote * $noteValueTicks[-1])) { | |
if ($ticks -gt 0) { | |
Write-Warning ("Tick unresolved. Remainder:" + $ticks) | |
} | |
break | |
} | |
} | |
return @($contributingNoteValues) | |
} | |
function Get-RtttlNoteNotation { | |
param ( | |
[string] | |
$noteName, | |
[string[]] | |
$contributingNoteValues | |
) | |
return ($contributingNoteValues | ForEach-Object { return ($_ + $noteName) -replace "(\d+)(\.)(\w\d*)", '$1$3$2' }) -join "," | |
} | |
[System.Collections.ArrayList]$finalNotes = [System.Collections.ArrayList]@( | |
[System.Collections.ArrayList]@(), | |
[System.Collections.ArrayList]@(), | |
[System.Collections.ArrayList]@(), | |
[System.Collections.ArrayList]@() | |
) | |
$notes = $midi | Where-Object { $midiChannels.Contains([int]$_.MidiChannel) -and ($_.Header -eq "Note_on_c" -or $_.Header -eq "Note_off_c") } | |
foreach ($note in $notes) { | |
$note.Ticks = [int]$note.Ticks | |
$note.Velocity = [int]$note.Velocity | |
# note on events | |
if ($note.Velocity -ne 0) { | |
$track = -1 | |
for ($i = 0; $i -lt $finalNotes.Count -and $track -eq -1; $i++) { | |
$queriedNote = $finalNotes[$i][-1] | |
if ((-not $finalNotes[$i].Count -or $queriedNote.durationInTicks) -and $queriedNote.NoteNumber -eq $note.NoteNumber) { | |
$track = $i | |
} | |
} | |
for ($i = 0; $i -lt $finalNotes.Count -and $track -eq -1; $i++) { | |
$queriedNote = $finalNotes[$i][-1] | |
if (-not $finalNotes[$i].Count -or $queriedNote.durationInTicks) { | |
$track = $i | |
} | |
} | |
if ($track -gt -1) { | |
$note.RtttlNoteName = Get-NoteName $note.NoteNumber $transpose | |
$previousNoteEndInTicks = 0 | |
if ($finalNotes[$track].Count) { | |
$previousNote = $finalNotes[$track][-1] | |
if ($previousNote.NoteNumber -eq $note.NoteNumber) { | |
$previousNote.DurationInTicks -= $ticksPerQuarterNote * 4 * 1 / 32 # subtract 32nd note | |
$previousNote.ContributingNoteValues = (Get-ContributingNoteValues $previousNote.DurationInTicks $ticksPerQuarterNote) | |
$previousNote.RtttlNoteNotation = (Get-RtttlNoteNotation $previousNote.RtttlNoteName $previousNote.ContributingNoteValues) | |
} | |
$previousNoteEndInTicks = ($previousNote.Ticks + $previousNote.DurationInTicks) | |
} | |
$pauseLengthInTicks = $note.Ticks - $previousNoteEndInTicks | |
if ($pauseLengthInTicks) { | |
$pauseValues = @(Get-ContributingNoteValues $pauseLengthInTicks $ticksPerQuarterNote) | |
$rtttlPause = (Get-RtttlNoteNotation "p" $pauseValues) | |
$finalNotes[$track].Add([PSCustomObject]@{ | |
Ticks = $previousNoteEndInTicks | |
RtttlNoteNotation = $rtttlPause | |
}) | Out-Null | |
} | |
$finalNotes[$track].Add($note) | Out-Null | |
} | |
} | |
# note off events | |
else { | |
$track = -1 | |
for ($i = 0; $i -lt $finalNotes.Count -and $track -eq -1; $i++) { | |
$queriedNote = $finalNotes[$i][-1] | |
if ($queriedNote.NoteNumber -eq $note.NoteNumber -and -not $queriedNote.durationInTicks) { | |
$track = $i | |
} | |
} | |
if ($track -gt -1) { | |
$releasedNote = $finalNotes[$track][-1] | |
$releasedNote.DurationInTicks = $note.Ticks - $releasedNote.Ticks + 1 | |
$releasedNote.ContributingNoteValues = @(Get-ContributingNoteValues ($releasedNote.DurationInTicks) $ticksPerQuarterNote) | |
$releasedNote.RtttlNoteNotation = (Get-RtttlNoteNotation $releasedNote.RtttlNoteName $releasedNote.ContributingNoteValues) | |
} | |
} | |
} | |
$rttl = $finalNotes | ForEach-Object { | |
$label + ":" + "b=" + $bpm + ",o=4,d=32:" + (($_.RtttlNoteNotation) -join ",") | |
} | |
Write-Host "Copy the following melodies to your Quad via https://esc-configurator.com/" | |
Write-Host ($rttl -join "`n`n") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment