It's hard to believe that using technology to record and play back music only dates back to 1878, when Edison patented the phonograph. We've come so far since then-with music synthesizers, CDs, sampling and remixing, phones that play music, and even long-distance jamming over the Internet. In this chapter, you'll take part in this tradition by building a Xylophone app that records and plays music.
With the app shown in Figure 9-1 (originally created by Liz Looney of the App Inventor team), you can:
Figure 9-1. The Xylophone app UI
This tutorial covers the following concepts:
Connect to the App Inventor website and start a new project. Name it "Xylophone", and also set the screen’s title to "Xylophone". Open the Blocks Editor and connect to your phone or emulator.
This app has 13 different components (8 of which compose the keyboard), listed in Table 9-1. Since there are so many, it would get pretty boring to create all of them before starting to write our program, so we'll break down the app into its functional parts and build them sequentially by going back and forth between the Designer and the Blocks Editor, as we did with the Ladybug Chase app in Chapter 5.
Table 9-1. All of the components for the Xylophone app
Our user interface will include an eight-note keyboard for a pentatonic (seven-note) major scale ranging from Low C to High C. We will create this musical keyboard in this section.
Start by creating the first two xylophone keys, which we will implement as buttons.
(Later, we will repeat step 2 for six more note buttons.)
The view in the Component Designer should look something like Figure 9-2.
Figure 9-2. Placing buttons to create a keyboard
The display on your phone should look similar, although there will not be any empty space between the two colored buttons.
We can't have a xylophone without sounds, so create a Sound component, leaving its name as Sound1. Change the MinimumInterval property from its default value of 500 milliseconds to 0. This allows us to play the sound as often as we want, instead of having to wait half a second (500 milliseconds) between plays. Don’t set its Source property, which we will set in the Blocks Editor.
Upload the sound files 1.wav and 2.wav by clicking the sound link. Unlike in previous chapters, where it was OK to change the names of media files, it is important to use these exact names for reasons that will soon become clear. You can upload the remaining six sound files when directed to later.
The behavior we need to program is for a sound file to play when the corresponding button is clicked. Specifically, if Button1 is clicked, we'd like to play 1.wav; if Button2 is clicked, we'd like to play 2.wav; and so on. We can set this up in the Blocks Editor as shown in Figure 9-3 by doing the following:
Figure 9-3. Playing a sound when a button is clicked
We could do the same for Button2, as shown in Figure 9-4 (just changing the text value), but the code would be awfully repetitive.
Figure 9-4. Adding more sounds
Repeated code is a good sign that you should create a procedure, which you've already done in Chapter 3’s MoleMash game and Chapter 5’s Ladybug Chase game. Specifically, we'll create a procedure that takes a number as an argument, sets Sound1's Source to the appropriate file, and plays the sound. This is another example of refactoring-improving a program’s implementation without changing its behavior, a concept introduced in the MoleMash tutorial. We can use the Text drawer's join block to combine the number (e.g., 1) and the text ".wav" to create the proper filename (e.g., "1.wav"). Here are the steps for creating the procedure we need:
Now, when Button1 is clicked, the procedure PlayNote will be called, with its number argument having the value 1. It should set Sound1.Source to "1.wav" and play the sound.
Create a similar Button2.Click block with a call to PlayNote with an argument of 2.(You can copy the existing PlayNote block and move it into the body of Button2.Click, making sure to change the argument.) Your program should look like Figure 9-5.
Figure 9-5. Creating a procedure to play a note
If you tried out the preceding calls to PlayNote, you may have been disappointed by not hearing the sound you expected or by experiencing an unexpected delay. That's because Android needs to load sounds at runtime, which takes time, before they can be played. This issue didn't come up before, because filenames placed in a Sound component’s Source property in the Designer are automatically loaded when the program starts. Since we don’t set Sound1.Source until after the program has started, that initialization process does not take place. We have to explicitly load the sounds when the program starts up, as shown in Figure 9-6.Figure 9-6. Loading sounds when the app launches
Test your app. Now if you restart the app by clicking on "Connect to Device..." in the Blocks Editor, the notes should play without delay. (If you don’t hear anything, make sure that the media volume on your phone is not set to mute.)
Now that we have the first two buttons and notes implemented and working, add the remaining six notes by going back to the Designer and uploading the sound files 3.wav, 4.wav, 5.wav, 6.wav, 7.wav, and 8.wav. Then create six new buttons, following the same steps as you did before but setting their Text and BackgroundColor properties as follows:
Back in the Blocks Editor, create Click blocks for each of the new buttons with appropriate calls to PlayNote. Similarly, add each new sound file to Screen.Initialize, as shown in Figure 9-8.
With your program getting so large, you might find it helpful to click the white minus signs near the bottom of the "container" blocks, such as PlayNote, to minimize them and conserve screen space.
Test your app. You should now have all the buttons, and each one will play a different note when you click it.
Playing notes by pressing buttons is fun, but being able to record and play back songs is even better. To implement playback, we will need to maintain a record of played notes. In addition to remembering the pitches (sound files) that were played, we must also record the amount of time between notes, or we won't be able to distinguish between two notes played in quick succession and two played with a 10-second silence between them.
Our app will maintain two lists, each of which will have one entry for each note that has been played:
Note. Before continuing, you may wish to review lists, which we covered in the Presidents Quiz.
We can get the timing information from a Clock component, which we will also use to properly time the notes for playback.
In the Designer, you will need to add a Clock component and Play and Reset buttons, which we will put in a HorizontalArrangement:
The Designer view should look like Figure 9-9.Figure 9-9. Adding components for recording and playing back sounds
We now need to add the correct behavior in the Blocks Editor. We will need to maintain lists of notes and times and add to the lists whenever the user presses a button.
This defines a new variable named "notes" to be an empty list. Repeat the steps for another variable, which you should name "times". These new blocks should look like Figure 9-10.Figure 9-10. Setting the variables to record notes
Whenever a note is played, we need to save both the name of the sound file (to the list notes) and the instant in time at which it was played (to the list times). To record the instant in time, we will use the Clock1.Now block, which returns the current instant in time (e.g., March 12, 2011, 8:33:14 AM), to the nearest millisecond. These values, obtained through the Sound1.Source and Clock1.Now blocks, should be added to the lists notes and times, respectively, as shown in Figure 9-11.Figure 9-11. Adding the sounds played to the list
For example, if you play "Row, Row, Row Your Boat" [C C C D E], your lists would end up having five entries, which might be:
When the user presses the Reset button, we want the two lists to go back to their original, empty states. Since the user won’t see any change, it's nice to add a small Sound1.Vibrate block so he knows that the key click was registered. Figure 9-12 shows the blocks for this behavior.Figure 9-12. Providing feedback when the user resets the app
As a thought experiment, let's first look at how to implement note playback without worrying about timing. We could (but won't) do this by creating these blocks as shown in Figure 9-13:
This may be the first time you've seen a procedure make a call to itself. While at first glance this might seem bogus, it is in fact an important and powerful computer science concept called recursion.
To get a better idea of how recursion works, let's step through what happens if a user plays three notes (1.wav, 3.wav, and 6.wav) and then presses the Play button. First, PlayButton.Click starts running. Since the length of the list notes is 3, which is greater than 0, count gets set to 1, and PlayBackNote is called:
Note. Although recursion is powerful, it can also be dangerous. As a thought experiment, ask yourself what would have happened if the programmer forgot to insert the blocks in PlayBackNote that incremented count.
While the recursion is correct, there is a different problem with the preceding example: almost no time passes between one call to Sound1.Play and the next, so each note gets interrupted by the next note, except for the last one. No note (except for the last) is allowed to complete before Sound1’s source is changed and Sound1.Play is called again. To get the correct behavior, we need to implement a delay between calls to PlayBackNote.
We will implement the delay by setting the timer on the clock to the amount of time between the current note and the next note. For example, if the next note is played 3,000 milliseconds (3 seconds) after the current note, we will set Clock1.TimerInterval to 3,000, after which PlayBackNote should be called again. Make the changes shown in Figure 9-14 to the body of the if block in PlayBackNote, and create and fill in the Clock1.Timer event handler, which says what should happen when the timer goes off.Figure 9-14. Adding delays between the notes
Let's assume the following contents for the two lists:
Here are some alternative scenarios to explore:
Here are some of the ideas we’ve covered in this tutorial: