Skip to content

Instantly share code, notes, and snippets.

@mmodrow
Last active February 9, 2024 05:34
Show Gist options
  • Save mmodrow/5750e0d17f574924ea44939a3ffc94f5 to your computer and use it in GitHub Desktop.
Save mmodrow/5750e0d17f574924ea44939a3ffc94f5 to your computer and use it in GitHub Desktop.
MIDI->RTTTL converter for BLHeli_32 and BlueJay Quadcopter ESCs
[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