## Overview

In this lesson, students will create a "Soundboard" App. This application will allow users to create a "song" by pressing the number keys to add a "note." Through this app, students will utilize almost all of coding concepts they have learned throughout this Unit, as well as the new techniques of nested loops and relative indexing.

## Goals

Students will be able to:

• Combine the coding concepts learned throughout this Unit
• Learn what "Nested Loops" and "Relative Indexing" are and how to use them

## Purpose

This lesson is designed with the primary purpose of bringing together all the concepts that students have learned throughout this Unit. In particular, the application makes great use of repeat while loops, Arrays, and KeyboardEvents. The app incorporates the techniques that students have already learned, but also builds upon them by introducing slightly more complicated uses. These new techniques, nested loops and relative indexing, are based on the basic concepts that students already know, but simply push them a little further.

## Getting Started

Since this application uses many concepts from throughout the Unit, it is a good idea to quickly review them. Don't take too long covering any one of these topics though, but simply remind students of the general usage or format. Below are the most crucial topics that you should consider for review.

• Repeat While Loops
• Arrays
• Using KeyboardEvents for user interaction

## Activity

In this Activity, students will bring together everything they've learned throughout the Unit to create a new application. To start, they will make a simple "Soundboard" app which lets the user input up to 10 notes, play the stored song, and clear the stored song so they can input a new one. This simple version requires students to use KeyboardEvents, conditional if-statements, Arrays, and repeat while (or repeat until) loops. Next, students refactor the simple version of the app to allow unlimited length songs, as well as an action to calculate the median frequency stored in the song. These new features will introduce the concepts of nested loops and relative indexing to the students. Nested loops are simply loops that are within another loop, and thus these inner loops run through a full set of iterations each time the outer loop runs. Relative indexing is a way to access items stored in an Array by a position relative to another variable, such as using "array:Get(counter + 10)" or "array:Get(counter / 2)" instead of simply "array:Get(counter)."

### Student Instructions

For today's lesson, we'll be creating a "Soundboard" app, which will let users create a "song" by adding "notes" one by one using the number keys. Below is a template for this program, which contains the blank actions of "PressedKey," "PlaySong," and "ClearSong." We have already completed the "Main" and "CreateGame" actions for you. We will add more actions later, but for now we'll focus on getting a simple version up and running. As with Lesson 14, we recommend that you write your code in a text editor so you can save your work and follow each step without having to scroll up and down repeatedly. Then you can simply copy your code into the online IDEs when it comes time to test the program.

``````use Libraries.Compute.Math
use Libraries.Containers.Array
use Libraries.Sound.Audio
use Libraries.Sound.AudioSamples
use Libraries.Game.Game
use Libraries.Interface.Events.KeyboardEvent
use Libraries.Interface.Events.KeyboardListener
use Libraries.Curriculum.AudioGame.Song
use Libraries.Curriculum.AudioGame.AudioGame

class Main is AudioGame, KeyboardListener
Song song

action Main
StartGame()
end

action CreateGame()
end

action PressedKey(KeyboardEvent event)

end

action PlaySong

end

action ClearSong

end
end``````

As shown from the template above, we have also provided you a class variable: "Song song." This variable is of a special type created specifically for this lesson, which creates, stores, and plays up to a ten-second long song. We'll explain how this type is used as we use it, so don't worry about not knowing the ins and outs of how it functions.

#### The "PressedKey" action

For this action, we need to write code that creates a sound of different frequency based on the key that was pressed, as well as keys for playing and clearing the song. To start, we'll set up the frequency input.

#### Do This

• Create a number variable to store the frequency, and initialize it to 0
• Create a series of if and elseif statements comparing event:keyCode to event:NUM_1 through event:NUM_0
• Inside each if/elseif, set the frequency variable equal to 100 for event:NUM_1, 200 for event:NUM_2, 300 for event:NUM_3, and so on (for NUM_0, set frequency equal to 1000)

Next, we need to add a note through the "sound" class variable. To do so, we can use the "GenerateSound" action, which accepts a frequency. However, we also need to make sure that we only call GenerateSound if the user actually pressed a number key. Since frequency starts at 0 before the if/elseif statements, we can check if frequency is still 0 after the if/elseif statements to determine if one of the number keys was pressed. Finally, we need to make sure that the "sound" variable is not already full, since each ConvenientSound object can only hold 10 sounds at once. We can check if it's full through the "IsFull" action, which returns true if it's full, and false if it is not.

#### Do This

• Add an if-statement for when the frequency variable is not equal to 0
• Add a nested if-statement inside the previous one to check that "song" is not full with "not song:IsFull()"
• Within the nested if-statement, call the GenerateSound action with "song:GenerateSound(frequency)"

## Test Your Code - "PressedKey" action

#### The "PlaySong" action

While testing your code so far, you were limited to simply playing the sound you were adding only when first creating it. At this stage, we can hardly call it a song. As such, we'll next work on the "PlaySong" action, which will play through all notes stored within the "song" class variable.

Before we tackle the PlaySong action, we need to explain a few more things about the "Song" class. Each Song object has an Array of AudioSamples, which can be obtained from the value-returning action of "GetAudioArray." These AudioSamples can be loaded into an Audio object and then played. Once we obtain the Array of AudioSamples, we can simply use a repeat while loop to play each AudioSample.

#### Do This

• Create an Audio object
• Create a counter for the repeat while loop and initialize it to 0
• Create an Array of AudioSamples and initialize it with "song:GetAudioArray()"
• Make a repeat while loop using your counter and the "GetSize()" action of your Array of AudioSamples
• Finally, inside of the loop, use the following three lines of code to load, play, and reset the Audio object:

``````Audio audio
QueueAudio(audio)``````

Now that we have finished the PlaySong action, we need to set up a way for users to call it. To do so, we will return to the "PressedKey" action, where we'll simply add another elseif to the first conditional to call the PlaySong action when the user presses the spacebar.

#### Do This

• Add an elseif comparing event:keyCode to event:SPACE
• Call the PlaySong action inside of the new elseif

Note that you don't need to alter the second conditional where we add a note to the "song" variable. This is because we don't change the value of the frequency variable, so it remains at 0 if the spacebar is pressed, and the second conditional is not taken.

## Test Your Code - "PressedKey" action

#### The "ClearSong" action

At this point, users should be able to input notes into a song, and then press the spacebar to play the stored song. However, what if the user makes a mistake, inputting the wrong note? It's annoying to have to restart the program to start over, so next we'll write the "ClearSong" action. Fortunately, this action is very easy; in fact, it's only one line. To clear the song, we simply need to call the "Clear" action of the "song" class variable. You may think it's silly to have an action for this one line of code, but we will edit it later when we make our application more complicated.

#### Do This

• Call the Clear action using "song:Clear()"

Don't forget, we also need to set up a way for the user to call the ClearSong action while running the program. To do so, we will once again return to the "PressedKey" action and add another elseif to the first conditional to call the ClearSong action when the user presses the escape key.

#### Do This

• Add an elseif comparing event:keyCode to event:ESCAPE
• Call the ClearSong action inside of the new elseif

## Test Your Code - "ClearSong" action

#### Unlimited Length Songs

Although we now have a version up and running, you may have noticed that it's rather limited. In particular, we can only have up to 10 notes at any one time. So, instead of having just one Song object, let's make an Array of them. This will require a bit of refactoring to make our current code work again.

To start, directly under the line of "class Main is AudioGame, KeyboardListener," replace the single Song object with an Array of Song objects.

Next, we need to change the conditional where we call the "GenerateSound" action in the "PressedKey" action. In particular, we need to set up when to add a new Song object to the Array.

#### Do This

• Create an integer outside of the conditional and set it equal to the Song Array's size
• Remove the nested conditional that used to check if the "song" class variable is full
• Introduce a new nested if-statement, which checks if the size variable is 0 or if the Song Array's last element is full
• Hint: remember that the last element in the Song Array is indexed by "size + 1" and that you can use the "IsFull()" action to check if the Song is full
• Inside the nested if-statement, create a new Song object, add it to the Array of Songs, and increment the size variable by one
• Lastly, inside the first if-statement but after the nested if-statement, call the "GenerateSound" action on the Array of Song's last element

Now that we have fixed the PressedKey action, we need to refactor the PlaySong action. To do so, we will need to make use of a new technique called "nested loops." Just like nested conditionals, nested loops are a loop contained within a loop. However, nested loops are a bit more complicated, mostly because of the counter. Since this is a new technique, we've written the action out for you below and will explain how it works (don't worry, you'll get a chance to try it out for yourself soon). Much of this action should look familiar to you, as the nested loop is almost the same as the loop in the original PlaySong action we wrote.

``````integer outerCounter = 0

repeat while outerCounter < arrayOfSongs:GetSize()
integer innerCounter = 0
Song currentSong = arrayOfSongs:Get(outerCounter)
Array arrayOfAudioSamples = currentSong:GetAudioArray()

repeat while innerCounter < arrayOfAudioSamples:GetSize()
Audio audio
QueueAudio(audio)

innerCounter = innerCounter + 1
end

outerCounter = outerCounter + 1
end``````

The first thing to notice is that we have two counters: one for the outside loop, and one for the inside loop. Notice how the declaration for the "innerCounter" is inside of the outside loop. This means that, for every iteration of the outside loop, the "innerCounter" is set to 0, the inner loop runs from 0 to the size of the "arrayOfAudioSamples," and then increments the "outerCounter" by one before running the next iteration of the outer loop. Thus, nested loops tend to run through many more iterations than they may seem to, but it is easily calculated through multiplication. For example, consider that we have 3 Song objects in the "arrayOfSongs" and that each of those Song objects is full (so the "arrayOfAudioSamples" will have 10 elements for all three). On the first iteration of the outside loop with the first Song, the inner loop runs through 10 iterations, one for each element in the AudioSamples Array. The second iteration of the outer loop will have the inner loop go through another 10 iterations, and then the third iteration of the outer loop will have the inner loop go through yet another 10 iterations of the inner loop, for 30 iterations total.

Finally, the last bit of refactoring we need to do is in the "ClearSong" action. We need to rewrite this action entirely, but that's not saying much considering it was only one line. Now that we have an Array of Songs, we can actually clear it without using the "Clear" action. Instead we can simply remove all the items from the Array by repeatedly calling the "RemoveFromFront" action.

#### Do This

• Create an integer to be a counter and set it equal to 0
• Create another integer and set it equal to the arrayOfSongs' size
• Create a repeat while loop for while the counter variable is less than the size variable
• Call the "RemoveFromFront()" action on the Song Array

Now your program should be completely refactored, and can now our songs can have as many notes as you want. Test your code below and ensure it is working properly. As a side note, be careful about how many individual notess you add to your song. While we have the functionality to have as many notes as we want, it could take a long time for the song to play, and you are locked out from doing anything until the song is finished when you try to play it.

## Test Your Code - Unlimited Length Songs

#### Finding the Median Frequency

The final functionality we will add to our app is an action to find the Median frequency. This action will put the frequency of each note in the Array of Song into an Array of numbers, sort it, and then calculate the middle value, or median. To start, we'll focus on putting the frequencies into a single Array.

#### Do This

• Create an Array of numbers to hold all frequencies
• Create two repeat while loops, one nested in the other
• Hint: the structure of these loops is very similar to those in the PlaySong action, except using an Array of numbers instead of an Array of AudioSamples
• Make the outer loop go while the outerCounter is less than the Array of Song's size
• Inside the outer loop but before the inner loop, create an innerCounter and set it to 0, create a temporary Song object and set it equal to the Song indexed by the outerCounter, and create a temporary Array of numbers to hold the (up to) 10 frequencies in the temporary Song object, using the "GetFrequencyArray()" action
• Make the inner loop go while the innerCounter is less than the size of the temporary Array of numbers
• Inside the inner loop, get the frequency indexed by the innerCounter in the temporary Array of numbers, and then add that frequency to the Array of frequency we made in the first step above

After those two loops, we should have our Array of frequency filled with all of the frequencies in the song. However, to obtain the median, we need to sort the frequencies in order. Fortunately, we can easily do this by calling the "Sort" action, as shown in the following line of code.

``arrayOfAllFrequencies:Sort()``

Finally, we simply need to get the middle value. This is very easy using a concept called "relative indexing." When accessing items in an Array, we've been using variables like counters and the Array's size, but we can also perform computataions during the "Get" action. For example, suppose you had "array:Get(counter + 10)." In this case, we access the Array element located 10 indexes higher than the counter. You can do any other calculations as well, such as subtraction, multiplication, division, and modulo. While relative indexing is extremely helpful or even essential for certain problems, you need to be careful not to try and obtain an element that doesn't exist. Using the previous example, if our Array had 20 elements in it, then using "Get(counter + 10)" would cause an error for any counter greater than 10, since the last index would be 19. At the same time, a counter of -10 would actually work, as -10 plus 10 gives the valid index of 0.

For this problem, we want the median of our sorted Array of frequencies, so we want to use a variable equal to our Array's size, and then use relative indexing to get the middle value. There are two cases we need to consider: when there are an even number of elements in the Array, and when there are an odd number of elements in the Array. We can determine whether the size variable is even or odd using the modulo operator, which returns the remainder after dividing two numbers. In this case, we'll want to use "size mod 2," which will equal 0 when size is even and will equal 1 when size is odd. When size is even, we'll need to add together the middle two elements and average them, and when size is odd, we'll need to simply get the middle element.

#### Do This

• Create an integer and set it equal to the Array of frequency's size
• Create a number to store the median within, and initialize it to 0
• Create an if-elseif statement to check if the size variable is even or odd
• In the even conditional, create two numbers and assign the middle two values to them
• Hint: remember that the size variable is the number of elements, not the last index (consider the case of size = 4, where the indexes are 0, 1, 2, and 3, and the two middle indexes are 1 and 2)
• Set the median variable equal to the average of the two middle values of the Array ((frequency1 + frequency2) / 2)
• In the odd conditional, simply assign the middle value in the Array to the median variable
• After the conditionals, output the median frequency

Lastly, we need to return to the "PressedKey" action and map a key to call the Median action. We'll do this by simply adding another elseif to the conditional that compares event:keyCode to event:ENTER. However, within this elseif, we need to add in a nested if statement to see if the "arrayOfSongs" has at least one element in it. It wouldn't make sense to calculate a median on an empty list, after all, so we'll simply avoid making the action call in this case. You should also add an else statement to output an informative error message for the user that the song is empty, and thus has no median. Error messages like this are standard coding practice, since other people running your program probably don't know how it works and would otherwise be confused by unexpected results or program crashes.

And with that, our app is complete! Test it out thoroughly in the IDE below, and feel free to add any additional features, such as other statistics.

## Wrap Up

If students have had time to brainstorm or create additional features for the app, give them an opportunity to share. Brainstorm other ways that this stored data could be processed, and the types of effects that could be produced as a result. Some students may wish to extend this project on the Practice Create Performance Task they will complete in the next lesson.

## Standards Alignment

• Computer Science Principles: 1.2.1 (A, B, C, D)
• Computer Science Principles: 1.3.1 (C, D, E)
• Computer Science Principles: 2.2.1 (A, B, C)
• Computer Science Principles: 2.2.2 (A, B)
• Computer Science Principles: 4.1.1 (A, B, C, D)
• Computer Science Principles: 4.1.2 (A, B, C, G, I)
• Computer Science Principles: 5.1.1 (A, B, C, D, E)
• Computer Science Principles: 5.1.2 (A, B, C, J)
• Computer Science Principles: 5.3.1 (A, B, C, D, E, F, G, J, K, L)
• Computer Science Principles: 5.4.1 (A, B, C, D, E, F, G, H, L, M, N)
• Computer Science Principles: 5.5.1 (D, E, F, G, H, I, J)