Piano in Max2
I thought it would be fun to create a musical keyboard using SWiSH Max2. There are many mouse activated keyboards (mostly javascript) available on the web. The problem with the mouse click idea, is the keyboard becomes monophonic. This project overcomes this limitation by monitoring keypresses on the computer keyboard.
The example below uses a key listener object to monitor key presses. This allows a polyphonic keyboard to be implemented. Use the slider to adjust the volume. Once you have clicked on the keyboard, you should be able to play notes by pressing the characters a w s e d f t g y h u j and k. The mouse can also be used to click on individual notes.
This project highlights a number of useful concepts:
- Use of the key listener object
- Use of movie clips to control the playing and stopping of sound clips
- Use of the sound object to control volume and pan (left – right distribution)
- Use of the Library (sorry, the library is not available in miniMax2)
- Use of the loaderbar_silver progress component
- Use of the slider component
Each of these items are discussed in detail below. The project .swi can be downloaded from here: flashkb.zip
General layout
As the exported swf file contains the sound files for all of the ‘notes’, it is about 250kb in size. Although this is not a huge file, it does take a little while to load if the user is using a slow internet connection. For this reason, I have added a pre-loader to the first scene, “Scene_loader“.
The second scene, “Scene_kb“, contains the keyboard. This scene is played when the movie is loaded.
The gradient background bluegrad has been added to the Library so that it can be shared by both scenes. Another advantage of using the Library in this instance, is that if I want to apply new colors, I can simply edit the library item and the colors are updated for both scenes.
The sound clips that I used can be downloaded here – mp3notes.zip. These files were made by recording an actual keyboard playing each note, then editing the sounds and exporting them to separate MP3 files. The sound editing was done with, audacity which I highly recommend for editing sound clips.
The sound clips are named c0, d0, e0, f0, g0, a1, b1, c1. These represent the notes c, d, e, f, g, a, b, c. The number 0, 1 represents the octave. i.e. “c1″ is the octave above “c0″. The sharp notes are named cs0, ds0, fs0, gs0, as1. Although as1 is actually b flat, but I thought I would stay consistent. This note naming convention is used throughout the project.
Scene_loader – the preloader
This scene consists of some static text, the loaderbar_silver progress component (i.e. Outline > Scene_loader > loader) and an instance of the blue gradient for the purposes of giving an interesting background. Note Scene_loader and Scene_kb have “Stop playing at end” checked in their Properties panel.
The ‘loader’ progressbar parameters were set as follows:
Event Function: Events (not used in this instance)
Hide When Loaded: true (but not needed in this instance)
When Loaded: Play Next Scene (note this is not the default setting)
Display Mode: Percent
Text: Loading:
The colors were also altered to suit the background color.
With these settings, when the movie is fully loaded, it progresses to the next scene. This is all handled by the loaderbar_silver component.
Scene_kb – the main scene
The scene contains two movie clips keys and notes as well as an instance of the blue grad gradient background. The ‘KeyListener’ object as well as other functions are setup in the script of this scene.
‘notes’ movie clip
This movie clip contains sub movie clips (one per note) which play each individual note. The sub movie clips are named c0, cs0, d0, ds0, e0, f0, fs0, g0, gs0, a1, as1, b1 and c1. These names correspond to the note names previously mentioned. The sub movie clips are identical apart from the sound that they control and their pan (left / right) position.
The script for the note “c0″ is shown below. This script is identical in the other sound movie clips except for the sound clip that is manipulated and the pan position.
onSelfEvent (load) {
var volstep = 10; // 10% per frame
}
onFrame (1) {
var trackAudio:Sound = new Sound(this);
var vol = 100;
trackAudio.setPan(-100);
stop();
}
onFrame (2) {
setLabel("makeSound");
_parent.note(_name,1); // show note pressed
vol = 100;
trackAudio.setVolume(vol);
stopSound("c0.mp3");
playSound("c0.mp3");
stop();
}
onFrame(5) {
setLabel("silent");
_parent.note(_name,0); // show note off
vol -= volstep;
trackAudio.setVolume(vol);
}
onFrame(6) {
setLabel("silentLoop");
vol -= volstep;
trackAudio.setVolume(vol);
}
onFrame(7) {
if (vol >= volstep)
gotoAndPlay("silentLoop");
}
onFrame(8) {
stopSound("c0.mp3");
}
Here is a brief description of the script:
The onSelfEvent (load) event is used to define volstep. This is the rate, % per frame, that the sound level is reduced when the key press ends. Stopping the sound immediatly when the key is released sounds very unnatural.
onFrame (1) is used to define the sound object that is associated with this note. The sound object is used to allow the volume to be controlled when the key is released. It also allows the pan (left / right) positon of the sound to be defined. I have used a different pan for each note so that the lower notes appear on the left, the higher notes appear on the right.
onFrame(2) is used to start the sound playing. Note that it is labeled “makeSound“. It is good practice to label significant frames in your movie. This not only helps document you code, it also provides defined locations where external movie clips can control the movie clip via the gotoAndPlay() command. Further, if you should insert frames in your movie, the external references will not require modification as the labels will stay with the significant frames, whatever their absolute frame number.
This frame also calls the note() function in the parent movie clip to show that this note is currently being played. It also sets the volume to 100% and starts playing this note via the playSound() action. To prevent multiple sounds of the same note from playing, the playSound() action is preceeded by a stopSound() action applied to the same sound clip.
onFrame (5) labeled “silent” is the start of the frame sequence that is played when the key is released. The function _parent.note() is called to show that this note is no longer being played. The remaining frames are used to reduce the volume of the sound at a faster rate than its natural decay.
Frames 6 and 7 are used to reduce the volume on each succesive frame by volstep percent. When the volume is to be reduced to a final value of zero, frame 8 executes a stopSound() action. The movie clip then returns to frame 1 where it waits for future commands.
Returning to the notes movie clip this also contains the ‘Slider’ that is used to control the master volume. To control the volume, the notes movie clip also has a sound object associated with it.
onSelfEvent (load) {
var trackAudio:Sound = new Sound(this);
}
The slider is simply the component slider, Components | Sliders | Slider with the following non-standard parameter settings:
Event Notification > Notify When? : On Move
Event Notification > Event Function : Events
The colors were also modified to suit the background.
The events function, when called by the slider, has the current position supplied in the value parameter. This is simply passed to the trackAudio.setVolume() method.
function Events(n,v) {
trackAudio.setVolume(v);
}
The note function simply calls a function of the same name in the parent object (main movie).
function note(name,state) {
return _parent.note(name,state); // just chain to parent function
}
Scene_kb Script
This script defines the KeyListener object and the functions playkey and note. These script sections are described below.
KeyListener
This is defined during the onSelfEvent (load) of Scene_kb
onSelfEvent (load) {
var lastkey = new Array(); // array to handle key repeats
var thiskey;
for (var i=0; i<128; i++)
lastkey[i] = 0; // set default last key condition.
var KeyListener:Object = new Object();
KeyListener.onKeyDown = function() {
// trace("KeyDown");
thiskey = Key.getAscii();
if (lastkey[thiskey])
return; // key repeat
lastkey[thiskey] = 1;
playkey(thiskey, 1);
}
KeyListener.onKeyUp = function() {
// trace("KeyUp");
thiskey = Key.getAscii();
lastkey[thiskey] = 0; // reset key press status
playkey(thiskey, 0);
}
Key.addListener(KeyListener);
trace("added listener");
}
One problem facing the key listener is the automatic key repeat that is normally applied. To work around the key repeat, when a keypress is noted for the first time, it’s status is changed to pressed. When the key is released, its status is changed to not pressed. If a keypress event is received that is due to a key repeat it can be ignored as the corresponding status will already show pressed.
This key repeat logic is implemented using the array lastkey[ ] which is indexed by the key code. Initially all members of the array are set to 0 (not pressed). If a key is pressed the corresponding array element is set to 1. If a key is released, the corresponding array element is set to 0.
Apart from the complexity introduced by needing to ignore key repeat, the key listener object is configured to call playkey(key,1) when a key is pressed or playkey(key,0) when the key is released.
function playkey(keycode, makesound)
This function is used to play or stop the note that is associated with the keycode.
function playkey(keycode,makesound) {
// set label depending on the value of makesound
// 1 means make sound, 0 means make silent.
l = (makesound)?"makeSound":"silent";
// map the keycode to the corresponding note.
switch(keycode) {
case ord('a'): notes.c0.gotoAndPlay(l); break;
case ord('w'): notes.cs0.gotoAndPlay(l); break;
case ord('s'): notes.d0.gotoAndPlay(l); break;
case ord('e'): notes.ds0.gotoAndPlay(l); break;
case ord('d'): notes.e0.gotoAndPlay(l); break;
case ord('f'): notes.f0.gotoAndPlay(l); break;
case ord('t'): notes.fs0.gotoAndPlay(l); break;
case ord('g'): notes.g0.gotoAndPlay(l); break;
case ord('y'): notes.gs0.gotoAndPlay(l); break;
case ord('h'): notes.a1.gotoAndPlay(l); break;
case ord('u'): notes.as1.gotoAndPlay(l); break;
case ord('j'): notes.b1.gotoAndPlay(l); break;
case ord('k'): notes.c1.gotoAndPlay(l); break;
}
}
If makesound is 1 then the corresponding note should be played. If makesound is 0 then the corresponding note should be stopped.
The line l = (makesound)?”makeSound”:”silent”; sets the label to be either “makeSound” or “silent” depending on if the sound is to be started or stopped.
The switch statement then maps the key code to the approrpriate note.
function note(name,state)
name is the name of the note being played. eg “c0″, “d0″ etc.
state is 1 if the note is sounding, 0 if the note is stopped.
This function is called by the function note() in the notes movie clip. It traces the currently selected note / state to the debug panel. In addition, it causes the corresponding key within the keys movie clip to show a pressed (or released) state.
function note(name,state) {
trace(name add ":" add state);
keys[name].press._visible = state;
}
‘keys’ movie clip
This movie clip contains the black and white keys that comprise the keyboard. The keys are instances of two basic key types kb (key black) and kw (key white) that are stored in the library. See the Content panel | Movie Clips to examine the kb and kw objects in more detail. The use of the library simplifies maintenance as a changes made to the kb and kw objects will automatically apply to all of the linked instances. The keys are named according to the corresponding notes, c0, d0 etc. This is important as the note() function described above, relies on this assumption.
The keys movie clip uses the following onSelfEvent (load) function to initialise the keys with the corresponding characters from the keyboard.
onSelfEvent (load) {
c0.keyid.text = 'a';
d0.keyid.text = 's';
e0.keyid.text = 'd';
f0.keyid.text = 'f';
g0.keyid.text = 'g';
a1.keyid.text = 'h';
b1.keyid.text = 'j';
c1.keyid.text = 'k';
cs0.keyid.text = 'w';
ds0.keyid.text = 'e';
fs0.keyid.text = 't';
gs0.keyid.text = 'y';
as1.keyid.text = 'u';
}
Notes
Although the keyboard is polyphonic, I have seen that with some key press combinations only a limited number of notes are played.
eg. press k, then j then h. On my keyboard the 3rd note does not play although the sequence k, j, a, s, g works.
I have added debugging to the key listener object and it is simply not seeing the event.I think this could be due to the keyboard hardware. I assume that the keys are arranged in a x/y matrix and it is possible that some key combinations will be masked if some other key combination is pressed.
That’s it. I hope this has been a useful overview of a fun little project.


(8 votes, average: 4.00 out of 5)