Code

- Initial Idea - 
It's apparently very tough to get Mathematica to play sounds without displaying graphics.  In fact, it seems impossible.  But you can get around it by having Mathematica display the graphics in a temporary window, as described at http://forums.wolfram.com/mathgroup/archive/2002/Feb/msg00276.html
Pretty sweet.  I'll have to get the timing right, though - I have to figure out a way to get it to stop playing immediately when I click on the next button.

>From here I have to get the keyboard to actually look like a keyboard, and then I'm moving in two main directions.  I have to find a way to associate keystrokes with the different buttons so that someone can play piano on their computer keyboard, and I want to create buttons that shifts the key so that a player is not restricted to C major.  A way to do this second part would be to set up the note definitions as I have them now, but to add an n variable to the power of two.  i.e., D would be Sin(440 ((2^((n - 7)/12))) x), and the n would be defined by global buttons, so that n - 7 becomes generally defined as the sub-dominant.  So if n = - 7, we modulate to the key of D and n - 7 is G.  So I could have a series of 12 buttons above the keyboard that each set a different key (sharps and flats would be as easy as natural tones), which would certainly be advantageous in some instances.  In fact, as long as I'm binding keys, the function keys would be perfect for such buttons.  A player could be noodling along in C major (F3 if A is F1), decide he wants to switch to the dominant, and hit F10 without losing a beat.  The other option would be to set up some algorithm so that the buttons, rather than represent specific keys, represent keys relative to the current key.  So if our man is playing in C and wants to switch to the dominant, he needs only to hit the dominant button (or whatever corresponding keystroke).  If he wants to get back to C, he just hits the sub-dominant button.  If he wanted, he could keep going all the way around the circle of fifths.  All of this, of course, hinges on my ability to find a way to bind keystrokes.

Of course, there seems little point in modulating keys if you can't use the minor mode, so I'll have to find a way to make a button for that as well.  The thing to do most likely would be to find the relative major and then bump it down three half steps.  So if I want A minor, the program will set the key to C major and then adjust the key bindings so that the leftmost key is set to A instead of C.  The player could even play the harmonic minor if the keyboard on the screen and the keyboard on the desk are bound right - in A minor, the real keys (the typing keys, represented in lower case) a s d f would correspond to A B C D and j k l ; would correspond to E F G A.  For accidentals, the upper row of letters could be used.  w e r t would correspond to A# B# C# D# and i o p [ would correspond to E# F# G# A#.  So for a harmonic minor scale the player would type a s d f j o p ;

This will certainly take some getting used to, but hopefully can be figured out with a little practice.

The other thing I need to figure out is harmony.  This doesn't seem too tough, though - whereas now I have a = Play(Sin(x)), I could set a = Sin{x} and have some sort of global Play function that adds up all the keys being played - if a = Sin(2π 440 x) and e = Sin(2π 440 ((2^(7/12)))x), if a and e are being played then the function is Play(Sin(2π 440 x) + Sin(2π 440 ((2^(7/12)))x), or a perfect fifth.

 - Final Version - 
 It became apparent after repeated attempts (and Jim's insistence) that Mathematica could not bind keystrokes to evaluate different functions.  Or if it could, it would take a lot more manipulation and advanced programming than I was capable of.  Thus most of my initial idea becomes impossible or irrelevant.  I include it with this final version of my project, however, because I do think it was a really good idea, and something I may want to possibly work on in the future.
 
But, as I said back when I intially wrote that idea, much of it hinged on my ability to find a way to bind keystrokes.  So, what exactly did I have to change from my initial idea?  Well, firstly and most obviously I couldn't have the user play the computer keyboard as they would a piano.  This ended up making the onscreen keyboard pretty useless - it gets pretty old pretty quick just clicking on a picture - so it seemed kinda pointless to add functionality directly to that keyboard (my ideas regarding binding the function keys to specific keys obviously ended up being unrealized).
 
 If I had it all to do again, or could keep working on it, I would incorporate more functionality into the keyboard rather than forcing text strings upon the user - there could be a button to change the keys from individual notes to the appropriate triads in the key - for instance, if the player sets the program to chord mode and clicks the C key it would play a C Major triad - C E G - while if he clicked the D key it would play a D minor triad - D F A - as is appropriate in the key of C.  This wouldn't allow the player to use chords containing tones not in the correct key, obviously, and while this would substantially limit the compositional breadth of this program it would still be fun.
 
 Speaking of compositional breadth, there's not a heck of a lot of possibility considering that there is currently no way of setting rhythm without hacking into the code, and even then not much can be done.  On a fast computer, the keyboard is responsive enough that it will play a note as soon as it's clicked, switching from another note if need be.  What I had initially aimed for, but soon found to be next to impossible, was a functionality such that the note would play as long as the button was clicked.  I realized before too long that buttons are unfortunately set for the purpose of evaluating functions, and in such a framework it doesn't make much sense to think of returning values after the button is pushed and continuously until the player lifts his finger.  What I tried instead was to set long note values of 8 seconds so that when the player clicks a note, it will keep playing until he clicks another note.  This was unsatisfactory, though, as 8 seconds of a note takes longer to render than 2 seconds, and the slowdown is enough to make the keyboard lag even on my computer.  So rhythm is still a problem, though not as much of one as it used to be.
 
My biggest intial problem was being able to play the keyboard without having the window scroll down to accomodate all the new graphics.  While the method of displaying graphics in a temporary window was intriguing, it didn't serve my purposes; if I set it up as the user from the message board suggested, it would open and close a new window every time somebody clicked a note.  Not only was this distracting, but it took a lot of time, and made the keyboard lag, which pretty much decimated any hope of achieving some sort of rhythm.  The code also highlighted an odd problem I encountered - when I clicked a note on the keyboard, Mathematica would give an error message "Could not process unknown packet 'Power.'"  This happened with or without the temporary window.  Yet when I evaluated the function seperately from the keyboard program - basically I copied the Play function from my definitions and evaluated it - there was no error message.  For the life of me, I could not figure out the source of this error message.  But for some reason it went away when I set the temporary window so that it didn't close automatically.  So I ended up with one version with a bunch of annoying windows popping up like fireflies that lagged and had an error message (with an accompanying beep that was most unsuitable for an audio program) and another version that had less lag and no beep but created window after unclosing window that slowly swallowed the user's screen.  Despite the annoyingness of the error message, it wasn't as much of an obstacle as the screen full of windows that had to be closed one by one.  So I redoubled my efforts to root out the error message.  To no avail.  In a fit of frustration, it occured to me that I might be able to turn off all error messages, or at least silence them.  Lo and behold, the options to turn of error messages and warning beeps were right there in Options[$FrontEnd,MessageOptions].  Once I turned off the error messages, it occured to me in one of those "wow, I'm stupid" moments that I didn't need to display the graphics in a new window at all - if the user clicked a couple notes and then collapsed the cell with the graphics, subsequently clicked notes would play but would be hidden in the collapsed cell.  This may not be perfect, as it requires some Mathematica know-how on the part of the user, but it's by far the best solution.  Though this leap didn't solve my rhythmic problems, it did take a big step in the right direction - much of the unnecessary lag has been eliminated.

Since I couldn't bind keystrokes, and you can't click two notes at once with a mouse, I decided I'd have to create a system for harmony that differed from that in my initial idea.  I settled on just defining a bunch of different chords.  In order to do that, I felt it would be easier to first define all notes with their accompanying trig functions.  All of this defining ended up taking a long time, but it worked out in the end.  As a result, a melody or chord progression can be composed independent of the keyboard and played like an animation.  While this lacks the graphical flair and initial ease of use of the keyboard, it has much more possiblity, and is much easier to use once the user figures out how.  I suppose the way it's set up now, the user could fool around on the keyboard until he finds a melody he likes, and then save it as a list.  It's actually quite capable, especially if you don't need the note lengths to differ.

SetOptions[$FrontEnd, 
      MessageOptions -> {WarningAction -> Null}]

SetOptions[$FrontEnd, 
      MessageOptions -> {ErrorAction -> Null}]

SetOptions[$FrontEnd, 
      MessageOptions -> {ConsoleMessageAction -> Null}]

SetOptions[$FrontEnd, 
      MessageOptions -> {KernelMessageAction -> Null}]

The Code for Displaying the Wave in a New Window

This is commented out now, as I no longer need it.  When I was using it, there were two versions - one in which the windows closed automatically, and another in which they had to be closed manually.

(* Cutting this out is one way to get around the timing problem .   Unfortunately it creates a million windows that have to be closed.
hiddenSoundDisplay[sndObject_Sound] := \(Module[{nb}, \ 
              nb\  = \ NotebookCreate[WindowSize\  -> \ {0.1, \ 0.1}]; \ 
              NotebookWrite[nb, \ 
                Cell[GraphicsData["PostScript", \ 
                    DisplayString[sndObject]]]]; \ 
              SelectionMove[nb, \ All, \ Notebook]; \ 
              FrontEndTokenExecute["SoundPlay"]; \ 
Pause[2]
NotebookClose[nb, \ 
                  Interactive\  -> \ False]]\) 
*)

(*$SoundDisplayFunction  =  hiddenSoundDisplay *)

Self-Contained Play Function Definitions

These are for the keyboard.  Clicking a note on the keyboard executes the play function for that note.  If there is too much lag, the user can edit SampleDepth and SampleRate to find more effective settings.

playHighC := 
      Module[{}, 
        Play[Sin[2 Pi 440 ((2^(3/12))) x], {x, 0, 2}, 
          SampleDepth -> 16, SampleRate -> 44100]]

playB := 
      Module[{}, 
        Play[Sin[2 Pi 440 ((2^(2/12))) x], {x, 0, 2}, 
          SampleDepth -> 16, SampleRate -> 44100]]

playG := 
      Module[{}, 
        Play[Sin[2 Pi 440 ((2^(-2/12))) x], {x, 0, 2}, 
          SampleDepth -> 16, SampleRate -> 44100]]

playF := 
      Module[{}, 
        Play[Sin[2 Pi 440 ((2^(-4/12))) x], {x, 0, 2}, 
          SampleDepth -> 16, SampleRate -> 44100]]

playE := 
      Module[{}, 
        Play[Sin[2 Pi 440 ((2^(-5/12))) x], {x, 0, 2}, 
          SampleDepth -> 16, SampleRate -> 44100]]

playC := 
      Module[{}, 
        Play[Sin[2 Pi 440 ((2^(-9/12))) x], {x, 0, 2}, 
          SampleDepth -> 16, SampleRate -> 44100]]

playA := 
      Module[{}, 
        Play[Sin[2 Pi 440 x], {x, 0, 2}, SampleDepth -> 16, 
          SampleRate -> 44100]]

playD := 
      Module[{}, 
        Play[Sin[2 Pi 440 ((2^(-7/12))) x], {x, 0, 2}, 
          SampleDepth -> 16, SampleRate -> 44100]]

playCsharp := 
      Module[{}, 
        Play[Sin[2 Pi 440 ((2^(-8/12))) x], {x, 0, 2}, 
          SampleDepth -> 16, SampleRate -> 44100]]

playDsharp := 
      Module[{}, 
        Play[Sin[2 Pi 440 ((2^(-6/12))) x], {x, 0, 2}, 
          SampleDepth -> 16, SampleRate -> 44100]]

playFsharp := 
      Module[{}, 
        Play[Sin[2 Pi 440 ((2^(-3/12))) x], {x, 0, 2}, 
          SampleDepth -> 16, SampleRate -> 44100]]

playGsharp := 
      Module[{}, 
        Play[Sin[2 Pi 440 ((2^(-1/12))) x], {x, 0, 2}, 
          SampleDepth -> 16, SampleRate -> 44100]]

playAsharp := 
      Module[{}, 
        Play[Sin[2 Pi 440 ((2^(1/12))) x], {x, 0, 2}, 
          SampleDepth -> 16, SampleRate -> 44100]]

Note-As-Trig Definitions

These definitions allow for melodies to be easily saved and edited.  They also make it easy for the user to define custom chords.

HighC = Sin[2 Pi 440 ((2^(3/12))) x];

b = Sin[2 Pi 440 ((2^(2/12))) x];

A = Sin[2 Pi 440 ((2^(1/12))) x];

a = Sin[2 Pi 440 x];

G = Sin[2 Pi 440 ((2^(-1/12))) x];

g = Sin[2 Pi 440 ((2^(-2/12))) x];

F = Sin[2 Pi 440 ((2^(-3/12))) x];

f = Sin[2 Pi 440 ((2^(-4/12))) x];

e = Sin[2 Pi 440 ((2^(-5/12))) x];

Ds = Sin[2 Pi 440 ((2^(-6/12))) x];

d = Sin[2 Pi 440 ((2^(-7/12))) x];

Cs = Sin[2 Pi 440 ((2^(-8/12))) x];

c = Sin[2 Pi 440 ((2^(-9/12))) x];

Chord Definitions

These are all the basic chords - major, minor, diminished, and augmented for each note.  The user can easily define new chords as needed (as easily as typing GM7=(g+d+b+f))

CM = (c + e + g + HighC);

Cm = (c + Ds + g + HighC);

Cd = (c + Ds + F + HighC);

CA = (c + e + G + HighC);

CsM = (Cs + f + G);

Csm = (Cs + e + G);

Csd = (Cs + e + g);

CsA = (Cs + f + a);

DM = (d + F + a);

Dm = (d + f + a);

Dd = (d + f + G);

DA = (d + F + A);

DsM = (Ds + g + A);

Dsm = (Ds + F + A);

Dsd = (Ds + F + a);

DsA = (Ds + g + b);

EM = (e + G + b);

Em = (e + g + b);

Ed = (e + g + A);

EA = (e + G + HighC);

FM = (f + a + HighC);

Fm = (f + G + HighC);

Fd = (f + G + b);

FA = (f + a + Cs);

FsM = (Cs + F + A);

Fsm = (Cs + F + a);

Fsd = (F + a + HighC);

FsA = (Cs + F + b);

GM = (g + b + d);

Gm = (g + A + d);

Gd = (g + A + Cs);

GA = (g + b + Ds);

GsM = (G + HighC + Ds);

Gsm = (G + b + Ds);

Gsd = (G + b + d);

GsA = (G + HighC + e);

AM = (a + Cs + e);

Am = (a + HighC + e);

Ad = (a + HighC + Ds);

AA = (a + Cs + f);

AsM = (A + d + f);

Asm = (A + Cs + f);

Asd = (A + Cs + e);

AsA = (A + Ds + F);

BM = (b + Ds + F);

Bm = (b + d + F);

Bd = (b + d + f);

BA = (b + Ds + g);

Keyboard Button Grid

This is the keyboard layout.  Two rows of buttons, one for sharps/flats (the black keys) and the other for naturals (the white keys).

\(init = 
      GridBox[{{
\(,\)\(
\)\(ButtonBox[
StyleBox["C#", FontFamily -> "Helvetica", 
                    FontColor -> GrayLevel[1]], 
                  ButtonFunction :> 
                    FrontEndExecute[{playCsharp}], 
                  ButtonEvaluator -> Automatic, 
                  Background -> GrayLevel[0], Active -> True] // 
                DisplayForm\)\(,\)\(
\)\(,\)\(
\)\(ButtonBox[
StyleBox["D#", 
                    FontFamily -> "Helvetica", 
                    FontColor -> GrayLevel[1]], 
                  ButtonFunction :> 
                    FrontEndExecute[{playDsharp}], 
                  ButtonEvaluator -> Automatic, 
                  Background -> GrayLevel[0], Active -> True] // 
                DisplayForm\)\(,\)\(
\)\(,\)\(
\)\(,\)\(
\)\(ButtonBox[
StyleBox["F#", FontFamily -> "Helvetica", 
                    FontColor -> GrayLevel[1]], 
                  ButtonFunction :> 
                    FrontEndExecute[{playFsharp}], 
                  ButtonEvaluator -> Automatic, 
                  Background -> GrayLevel[0], Active -> True] // 
                DisplayForm\)\(,\)\(
\)\(,\)\(
\)\(ButtonBox[
StyleBox["G#", 
                    FontFamily -> "Helvetica", 
                    FontColor -> GrayLevel[1]], 
                  ButtonFunction :> 
                    FrontEndExecute[{playGsharp}], 
                  ButtonEvaluator -> Automatic, 
                  Background -> GrayLevel[0], Active -> True] // 
                DisplayForm\)\(,\)\(
\)\(,\)\(
\)\(ButtonBox[
StyleBox["A#", 
                    FontFamily -> "Helvetica", 
                    FontColor -> GrayLevel[1]], 
                  ButtonFunction :> 
                    FrontEndExecute[{playAsharp}], 
                  ButtonEvaluator -> Automatic, 
                  Background -> GrayLevel[0], Active -> True] // 
                DisplayForm\)\(,\)\(
\)\(,\)
}, 
{
ButtonBox[
StyleBox["C", FontFamily -> "Helvetica"], 
                  ButtonFunction :> FrontEndExecute[{playC}], 
                  ButtonEvaluator -> Automatic, 
                  Background -> GrayLevel[1], Active -> True] // 
                DisplayForm, 
, 
ButtonBox[
StyleBox["D", 
                    FontFamily -> "Helvetica"], 
                  ButtonFunction :> FrontEndExecute[{playD}], 
                  ButtonEvaluator -> Automatic, 
                  Background -> GrayLevel[1], Active -> True] // 
                DisplayForm, 
, 
ButtonBox[
StyleBox["E", 
                    FontFamily -> "Helvetica"], 
                  ButtonFunction :> FrontEndExecute[{playE}], 
                  ButtonEvaluator -> Automatic, 
                  Background -> GrayLevel[1], Active -> True] // 
                DisplayForm, 
ButtonBox[
StyleBox["F", 
                    FontFamily -> "Helvetica"], 
                  ButtonFunction :> FrontEndExecute[{playF}], 
                  ButtonEvaluator -> Automatic, 
                  Background -> GrayLevel[1], Active -> True] // 
                DisplayForm, 
, 
ButtonBox[
StyleBox["G", 
                    FontFamily -> "Helvetica"], 
                  ButtonFunction :> FrontEndExecute[{playG}], 
                  ButtonEvaluator -> Automatic, 
                  Background -> GrayLevel[1], Active -> True] // 
                DisplayForm, 
, 
ButtonBox[
StyleBox["A", 
                    FontFamily -> "Helvetica"], 
                  ButtonFunction :> FrontEndExecute[{playA}], 
                  ButtonEvaluator -> Automatic, 
                  Background -> GrayLevel[1], Active -> True] // 
                DisplayForm, 
, 
ButtonBox[
StyleBox["B", 
                    FontFamily -> "Helvetica"], 
                  ButtonFunction :> FrontEndExecute[{playB}], 
                  ButtonEvaluator -> Automatic, 
                  Background -> GrayLevel[1], Active -> True] // 
                
                DisplayForm, 
ButtonBox[
StyleBox["C", 
                    FontFamily -> "Helvetica"], 
                  ButtonFunction :> FrontEndExecute[{playHighC}], 
                  ButtonEvaluator -> Automatic, 
                  Background -> GrayLevel[1], Active -> True] // 
                DisplayForm
}}, ColumnLines -> True, 
          RowMinHeight -> 4, ColumnSpacings ->  .1] // 
        DisplayForm\)

Sound Workshop

Keyboard

Use the keyboard to have fun.  If you find a melody you like, you can save it as a list as described below.

init

Chord Progression

Type progression={chord,chord,chord,etc} to create a chord progression.  A sample has been provided.  If you want to save this one and create another, use another name to define your new progression, i.e. progression1.

progression = {CM, CA, Am, EM, Am, FM, Dm, EM, Am, FM, Dm, GM, 
        CM}

This will play your chord progression.  If you're using a progression of a name other than progression (i.e. progression1) put that where progression is currently entered.  {x,0,1} sets how long each chord lasts (currently 1 second.  {x,0,2} would be 2 seconds, and {x,0,10} would be ten seconds).  SampleRate-> sets the sample rate.  The lower the sample rate the more quickly the chords will appear, the higher the sample rate, the more bright and clear the chords will sound.  To avoid distortion from aliasing, do not set this below 1050.  {i,1,13} references the chords in your list.  Set the second number (here 13) to the number of chords in your progression.

Do[
Play[progression[[i]], {x, 0, 1}, 
        SampleRate -> 1700], 
{i, 1, 13}]

Note - If you want to create new chords, this can be done simply in the code.  Try it out!

Melody

This works much the same way as progression, but with monophonic results.  Simply type the notes in your melody seperated by commas.  If you want to have a note in a different octave, you can edit the Sin function.  Here I wanted the final A in my melody to be an octave lower, so I cut the coefficient of x in half by halving 880 to 440.  If I wanted an octave up I would have doubled 880 to 1760 and if I wanted two octaves down I would have quartered 880 to 220.

melody = {a, G, g, d, Ds, d, c, a}

melody = {Sin[880 π x], Sin[440 (2^(11/12)) π x], 
        Sin[440 (2^(5/6)) π x], Sin[440 (2^(5/12)) π x], 
        Sin[440 (Sqrt[2]) π x], Sin[440 (2^(5/12)) π x], 
        Sin[440 (2^(1/4)) π x], Sin[440 π x], d, Cs, c, g, G, 
        g, f, d}

melody = {Sin[880 π x], Sin[440 (2^(11/12)) π x], 
        Sin[440 (2^(5/6)) π x], Sin[440 (2^(5/12)) π x], 
        Sin[440 (Sqrt[2]) π x], Sin[440 (2^(5/12)) π x], 
        Sin[440 (2^(1/4)) π x], Sin[440 π x], 
        Sin[880 (2^(5/12)) π x], Sin[880 (2^(1/3)) π x], 
        Sin[880 (2^(1/4)) π x], Sin[440 (2^(5/6)) π x], 
        Sin[440 (2^(11/12)) π x], Sin[440 (2^(5/6)) π x], 
        Sin[440 (2^(2/3)) π x], Sin[440 (2^(5/12)) π x], Ds, 
        f, g, d, Cs, d, Ds, e, a, A, a, G, a, G, g, d}

Again, this works exactly like progression.  If you've named your melody something other than melody, use that instead.

Do[Play[melody[[i]], {x, 0,  .5}, SampleRate -> 44100], {i, 1, 
        32}]