Weekend Scripter: Use Windows PowerShell to Play Recorded Music

Weekend Scripter: Use Windows PowerShell to Play Recorded Music

  • Comments 5
  • Likes

Summary: Use a hash table and an array to run prearranged actions.

Honorary Scripting Guy, Sean Kearney here. I’m filling in for our good friend, Ed again today. I think he’s busy playing some tunes with the Windows PowerShell piano we created last week.

So continuing from last weekend’s silliness, I had the song “Popcorn” in my head. I was thinking, “Is there any way we could have some prerecorded music for Windows PowerShell?”

Well, this is definitely something we can do, but we need to figure out a few details to play music. Looking at a typically sheet of music, we have some details to be aware of:

Note  A note (A,B,C,D) could be sharp, flat, or neither (let’s say, neutral).

Octave  Our note would be an octave, and there can be multiple C notes at different frequencies.

Time  Timing could be quarter, half, or whole (even eighth or sixteenth, but we’ll start simple). The note could also be “dotted” (plays 50% longer than its variant).

Beats  Each measure plays a certain number of beats at a time (4/4 is what we’ll work with).

So before Ludwig Von Beethoven comes out to correct my musical errors, we’re going to define a special array. We’re going to change our original music array from last week slightly.

First, instead of a key on the keyboard, we’re going to create a new pattern for musical notes. We’re going to identify octave, note, and sharp or flat with this simple designation:

# First Column Identifies Octave (A-C) (where C is the highest) - Octave 0 = REST

# Second Column Identifies Note

# Third Column Designates Note as Sharp "S" or Not "N"

For a C note in the second octave, it will look like this:

BCN

A D# in the third octave looks like this 

CDS

And of course, a rest note will have a special designation of RRR. So our list of notes will translate to this:

[array]$notes=$NULL

$notes+=@{"RRR"=37}

$notes+=@{"BCN"=290}

$notes+=@{"BCS"=310}

$notes+=@{"BDN"=330}

$notes+=@{"BDS"=345}

$notes+=@{"BEN"=360}

$notes+=@{"BFN"=390}

$notes+=@{"BFS"=415}

$notes+=@{"BGN"=440}

$notes+=@{"BGS"=460}

$notes+=@{"CAN"=480}

$notes+=@{"CAS"=505}

$notes+=@{"CBN"=530}

$notes+=@{"CCN"=580}

$notes+=@{"CCS"=605}

$notes+=@{"CDN"=630}

$notes+=@{"CDS"=670}

$notes+=@{"CEN"=710}

$notes+=@{"CFN"=710}

$notes+=@{"CFN"=710}

$notes+=@{"CGN"=710}

$notes+=@{"CGN"=710} 

After that, we need to define some particular details for notes and duration. For time, we’ll base everything on 4/4 and over two blocks of measures:

Eighth note       8

Quarter note      4

Half note           2

Whole note       1

To plug in a dotted note, we’ll add an additional column, 1 or 0 for dotted or non-dotted. 

So to play an F#, second octave, whole note, non-dotted, we would use this:

BFS10

Or to play a G note, third octave, half note, dotted, we would use this: 

CGN21

So for our data array, the first three entries will contain the note, and the last pair is how long we’ll play it. Let’s plug-in some data directly:

# Whole Note Dotted

$Length=1

$Dotted=1

 

$PlayTime=4*(1/$Length)*(.5*[int]($Dotted="1"))

Cool. So let’s put this together as a small array to play “Popcorn”:

$MusicString=$NULL

$MusicString+="CEN80CDN80CEN80CCN80CAN80CCN80BGN80RRR80"

$MusicString+="CEN80CDN80CEN80CCN80CAN80CCN80BGN80RRR80"

$MusicString+="CEN80CFS80CGN80CFS80CGN80CEN80CFS80CEN80"

$MusicString+="CFS80CDN80CEN80CCN80CAN80CCN80CEN80"

We’ll create a loop to parse the data, and pass the results through to the array to determine the note and duration:

$Start=0

$End=$musicstring.Length

 

do {

$Data=$MusicString.Substring($Start,5)

 

$Note=$data.substring(0,3)

$Length=[int]$data.substring(3,1)

$Dotted=$data.substring(4,1)

 

$Tone=$Notes.$Note

 

$PlayTime=4*(1/$length)*(.5*[int]($Dotted="1"))

With this data in hand, let’s play the music:

If ($Note -ne "RRR")
{[console]::beep($tone,300*($Playtime))}
Else

{START-SLEEP -milliseconds (600*$Playtime)}

$Start=$Start+5

 

} Until ($Start -ge $End) 

TaDa! You’re a musician with Windows PowerShell!

If you’d like to download and listen to this silly little script, it’s sitting in the TechNet Gallery: Windows PowerShell Popcorn Tune.

You can adapt the music array to meet your own needs, including speeding it up and optimizing it (maybe even tune my horridly guessed notes).

Tune in tomorrow when we use Windows PowerShell to cook up a little popcorn for our script.

I invite you to follow the Scripting Guys on Twitter and Facebook. If you have any questions, send email to scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Sean Kearney,
Honorary Scripting Guy and Windows PowerShell MVP
 

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • Fun stuff.  :)  The dotted-note logic doesn't look right, though.  I think rather than this:

    $PlayTime=4*(1/$length)*(.5*[int]($Dotted="1"))

    You probably wanted this:

    $PlayTime = 4 * (1 / $length) + (.5 * [int]$Dotted)

  • @Sean - I see math is not one of your strong points;)

    $dotted=1 + ($data.substring(4,1) * .5)

    This is how to prevent a zero in a product list.  This will always be either 1 or 1.5.  This is one way to get a sustain or dotted note option when the options are binary (1/0,true/false)

    It looks like you were either trying to do that or possibly posted the wrong version.  I think doing it at the extraction is more explicit.

  • @david

    My intent was to pull a Binary True and convert to integer but you've found a better method! Cool!

    @jrv

    I love it!  This was I think my first idea (and I pulled this from a working script) but you're absolutely correct, there was DEFINITELY a better way :)

    Sean

  • @jrv and @david

    BTW, it appears I am the victim of a keyboard sneeze or SOMETHING odd :P

    I checked my original script, it had the correct formula (+ instead of * in the Math function)

    Chalk one up for Mr. Murphy ;)

    Sean

  • Actually, now that I look closer, none of us has it quite right yet.  JRV was closest, but was multiplying a string by a double, with the string on the left, and not casting it to a number first.  Either reversing the order (0.5 * $data.substring(4,1)) or casting $data.substring(4,1) to [int] would fix that.

    My original post was adding a flat 0.5 to the length, not actually multiplying the length by 1.5 (which seemed to be working, because my tests happened to be for values that came out as 1 and 1.5).

    Sean, from what you say you intended, it looks like the main problem was using "=" instead of "-eq" (though the math still needs a slight tweak, see below).

    Here's what I should have posted the first time:

    $PlayTime = 4 * (1 / $length) * (1 + .5 * [int]$Dotted)