PowerShell Audio Sequencer
I got forwarded an addictive interactive sequencer yesterday (http://lab.andre-michelle.com/tonematrix) and was immediately hooked. I asked an internal mailing list if there was any kind of hardware that lets you do this kind of thing on the couch, and got the response – “you mean MIDI?” That’s close, but it is closer to a very simplified sequencer.
I play classical guitar… even being a fan of electronic music, I had never seen a sequencer used, or tried to make anything in one. I’m sure some researcher out there would love to have me for a “out of touch with reality” anthropology study.
Then I wondered, “Why should GUI folks have all the fun?”
88 lines later, a PowerShell Sequencer / Tracker was born: http://www.leeholmes.com/projects/PsTracker/PsTracker.zip.
Even as a jaded scripter, I’m constantly amazed how compact PowerShell is. Given an example input:
# Replace any dash with something else to make a sound in that spot.
# Format: <NOTE><OCTAVE> <PATTERN>
# If you restrict yourself to a pentatonic scale (i.e. CDEGAC), anything sounds good.
# Instruments: # ([Toub.Sound.Midi.GeneralMidiInstruments] | gm -static -mem Property | % { $_.Name } ) -join " "
# .Instrument OverdrivenGuitar
C5 ---------OO-OO--
A5 -------OO-OO---O
G4 --------------O-
E4 ----------------
D4 X---X---X---X---
C4 ----------------
A4 ----------------
G3 ----X-----------
E3 ----------------
D3 ----------------
C3 ----------------
A3 -------OO-OO----
G2 ----------------
E2 ----------------
D2 ----------------
C2 ----------------
# .Instrument SquareLead
C6 -X-X-XX-X--XXX
# .Instrument Ocarina
C7 ---------------X-X-XX-X--XXX
# .Instrument Kalimba
C8 -----------------X---X---X---X--
This is all it takes to process it:
#requires -Version 2
param($path, $bpm)
$scriptPath = & { Split-Path $myInvocation.ScriptName }
$trackEntries = @{}
function Update-Track
{
$trackEntries.Clear()
$instrument = $null
foreach($line in Get-Content $path)
{
if($line -match ".*Instrument (.+)([\s]*)$")
{
$instrument = $matches[1]
if(-not $trackEntries[$instrument]) { $trackEntries[$instrument] = @{} }
}
elseif($line -notmatch "#|(^[\s]*$)")
{
$note,$measures = -split $line
for($measure = 0; $measure -lt $measures.Length; $measure++)
{
if($measures[$measure] -ne "-")
{
$trackEntries[$instrument][$measure] = @($trackEntries[$instrument][$measure] + $note)
}
$trackEntries[$instrument]["Length"] = [Math]::Max($trackEntries[$instrument]["Length"], $measure)
}
}
}
}
$fsw = New-Object System.IO.FileSystemWatcher (Split-Path (Resolve-Path $path).ProviderPath),$path
Register-ObjectEvent $fsw Changed -SourceIdentifier TrackUpdated
Update-Track
Add-Type -Path (Join-Path $scriptPath "Toub.Sound.Midi.dll")
[Toub.Sound.Midi.MidiPlayer]::OpenMidi()
try
{
$sleep = 250
if($bpm) { $sleep = 1000 * 120 / (8 * $bpm) }
$currentMeasures = @{}
while($true)
{
$activeNotes = @()
foreach($instrument in $trackEntries.Keys)
{
if(-not $currentMeasures[$instrument]) { $currentMeasures[$instrument] = 0 }
$mappedInstrument = [Toub.Sound.Midi.GeneralMidiInstruments]::$instrument
[Toub.Sound.Midi.MidiPlayer]::Play(
(New-Object Toub.Sound.Midi.ProgramChange 0,0,$mappedInstrument) )
foreach($note in $trackEntries[$instrument][$currentMeasures[$instrument]])
{
[Toub.Sound.Midi.MidiPlayer]::Play( (New-Object Toub.Sound.Midi.NoteOn 0,0,$note,127) )
$activeNotes += New-Object Toub.Sound.Midi.NoteOff 0,0,$note,127
}
$currentMeasures[$instrument] =
($currentMeasures[$instrument] + 1) % (1 + $trackEntries[$instrument]["Length"])
}
Start-Sleep -m $sleep
$activeNotes | % { [Toub.Sound.Midi.MidiPlayer]::Play($_) }
if(Get-Event *TrackUpdated*)
{
Remove-Event TrackUpdated
Update-Track
}
}
}
finally
{
[Toub.Sound.Midi.MidiPlayer]::CloseMidi()
Unregister-Event TrackUpdated
Remove-Event *TrackUpdated*
}
For example:
.\Start-Tracker track.txt 60
If your system has a MIDI instrument for “Cowbells,” make sure to add more of them! This script builds on Stephen Toub’s MIDI library, which I can’t seem to find a reference to any longer.
As an aside, that research junket eventually led me to playing with a more feature-rich (free) sequencer called Linux Multimedia Studio. Keeping with the basis of starting with a pentatonic scale, this took only about an hour or two: http://www.leeholmes.com/projects/PsTracker/strive.mp3.