Weekend Scripter: Use Hash Table with PowerShell to Create a Simple Piano

Doctor Scripto

Summary: Use a hash table to play musical notes in the console.

Honorary Scripting Guy, Sean Kearney, here—filling in for our good friend, Ed Wilson.

Today I decided to have some fun. It’s the weekend, I was bored, and I had too much time on my hands. (I have friends who suggest this happens a lot!)

I knew that in Windows PowerShell, I can make a beep noise by executing this:

[console]::beep(500,100)

If you check online, you will see that two values can be played with. The first is the frequency and the second is the duration. So if you want to drive the family dog (or possibly most of the people at the breakfast table) bonkers, you could do something like this

[console]::beep(7000,65000)

Of course we like the family dog, so we’re not going to actually do this. But it shows that we can alter frequencies and durations of the sound. So this is where my brain got bored. I wanted to do something that I hadn’t done in a very, very, very long time.

I wanted to turn my computer into a piano. It’s something I had done a long time ago on my old Commodore Vic 20. Granted, the console beep has none of the features I had on my Vic 20, or even in the early Commodore 64 SID chip. But I can make noise.

So I mucked about and figured out the notes for my piano by ear and by singing “Do Re Mi Fa So La Ti Do” about a hundred times. No, I didn’t sit down with a proper tuning fork, so for the most part, my notes are about as on-key as an angry parrot singing.

Here is my scale (my very off-key scale, I might add!) created in Windows PowerShell:

[console]::beep(290,400)

[console]::beep(330,400)

[console]::beep(360,400)

[console]::beep(390,400)

[console]::beep(440,400)

[console]::beep(480,400)

[console]::beep(530,400)

[console]::beep(580,400)

Cool. I’ve got some notes. Now for challenge number two…

I need a way to listen to the keyboard. Fortunately, this is actually pretty easy. By using the $Host variable and the following technique, we can listen to the keyboard:

$Key=$HOST.UI.RawUI.ReadKey(“NoEcho,IncludeKeyDown”)

This will allow us to wait until any key on the keyboard is pressed. Not the “ANYKEY”—they’ve stopped shipping that in newer models of computers.

We’ll have to run an additional command to make sure a pile of keys don’t build up in the keyboard queue:

$HOST.UI.RawUI.Flushinputbuffer()

Try this in your Windows PowerShell console: Tap the letter H, and you’ll notice some interesting results in the value of $Key:

Image of command output

You’ll see it traps the ASCII value of the key entered, in addition to the actual string content. So we know that we have an easy way to listen to the keyboard and retrieve information from it.

Let’s pick a set of characters for the “Piano” keyboard. We’ll take the regular notes (not sharp or flat) first, starting as the letter Q:

QWERTYUIOP

And then for the other notes, we’ll use appropriate numeric keys:

23 567 90

So our keyboard will look like this:

 2 3 5 6 7 9 0

QWERTYUIOP

But how will we match the keys to the notes? A big nasty pile of IF statements?

No, this is a perfect opportunity to set up a hash table. We’ll match the keys to each note value. I’ve also taken the liberty to add in some additional notes, such as the missing sharps, and a few extra notes beyond the base scale:

[array]$piano=$NULL

$piano+=@{“Q”=290}

$piano+=@{“2″=310}

$piano+=@{“W”=330}

$piano+=@{“3″=345}

$piano+=@{“E”=360}

$piano+=@{“R”=390}

$piano+=@{“5″=415}

$piano+=@{“T”=440}

$piano+=@{“6″=460}

$piano+=@{“Y”=480}

$piano+=@{“7″=505}

$piano+=@{“U”=530}

$piano+=@{“I”=580}

$piano+=@{“9″=605}

$piano+=@{“O”=630}

$piano+=@{“0″=670}

$piano+=@{“P”=710}

Lovely. We have a list of keys matched to numbers. If only there was an easy way to search an array for information…

But of course, in Windows PowerShell there is! Simply plug the value you’re looking for into the array. If it matches any entries, it will return them. For example, to find the entry that contains “T”, enter:

Image of command output

Oooo! So a simple little line like this should play a note after hitting a key:

$Key=$HOST.UI.RawUI.ReadKey(“NoEcho,IncludeKeyDown”)

$HOST.UI.RawUI.Flushinputbuffer()

$Note=$Piano.($Key.Character)

[Console]::Beep($Note,100)

So our loop would look something like this if we wanted to play it forever and ever and ever:

DO {

$Key=$HOST.UI.RawUI.ReadKey(“NoEcho,IncludeKeyDown”)

$HOST.UI.RawUI.Flushinputbuffer()

$Note=$Piano.($Key.Character)

[Console]::Beep($Note,100)

} Until ($False)

Come back tomorrow, and we’ll build on these ideas in an even more interesting toy, including a little error trapping and visualization! 

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 

0 comments

Discussion is closed.

Feedback usabilla icon