Quantcast
Channel: EarSketch » User-defined Functions
Viewing all articles
Browse latest Browse all 3

Variable Scoping and Return Values

$
0
0

In the previous section, we encountered a script that created a short composition consisting of an A section followed by a B section, and then the return of the A section. Here is that code:

'''
Music with A and B sections
'''
from earsketch import *

# initialize Reaper
init()
setTempo(120)

# A section
def sectionA(leadGuitar, secondGuitar, drums, bass, startMeasure, endMeasure):
    # create an A section
    fitMedia(leadGuitar, 1, startMeasure, endMeasure) # lead
    fitMedia(drums, 2, startMeasure, endMeasure) # drums
    # bass beat from startMeasure (inclusive) to endMeasure (exclusive)
    for measure in range(startMeasure,endMeasure):
        makeBeat(bass, 3, measure, "0---00--000-0000")
    # second guitar every other measure from startMeasure (inclusive) to endMeasure+1 (exclusive)
    for measure in range(startMeasure, endMeasure, 2):
        fitMedia(secondGuitar, 4, measure, measure+1)
    setEffect(4, DISTORTION, DISTO_GAIN, 10) # distortion on track 4

# B section
def sectionB(guitar, drums, cymbalCrash, startMeasure, endMeasure):
    fitMedia(drums, 1, startMeasure, endMeasure)
    fitMedia(guitar, 2, startMeasure, endMeasure)
    fitMedia(cymbalCrash, 3, startMeasure, startMeasure+1)

# set up an ABA musical form through function calls
sectionA(Y01_GUITAR_1, Y01_WAH_GUITAR_1, Y01_DRUMS_1, Y01_BASS_1, 1, 5)
sectionB(Y01_WAH_GUITAR_1, Y01_OPEN_HI_HATS_1, Y01_CRASH_1, 5, 7)
sectionA(Y01_GUITAR_1, Y01_WAH_GUITAR_1, Y01_DRUMS_1, Y01_BASS_1, 7, 11)

# finish
finish()

We’ll make an important improvement to this code:

  1. In longer compositions, we will want to continue the ABA section layout with the same number of measures for each section. But right now, the sectionA() and sectionB() functions take the start measure and end measure as inputs, making it tedious for someone calling the function to simply repeat the ABA fragments. We’ll want to continue the A section for 4 measures, the B section for 2 measures, and then repeat the A section for 4 measures. To make this easier, we’ll modify the functions to return the end measure number, given the start measure number. Then we can use that return value to start the next section where the previous function left off.

To make this change, we’ll modify the sectionA() function to add music for 4 measures. Similarly, we’ll modify the sectionB() function to add music for 2 measures. Then we’ll update our top level code (outside the defined functions) to call our updated functions appropriately. Here is the new code:

'''
Music with A and B sections
'''
from earsketch import *

# initialize Reaper
init()
setTempo(120)

# A section
def sectionA(leadGuitar, secondGuitar, drums, bass, startMeasure):
    # create an A section
    endMeasure = startMeasure + 4
    fitMedia(leadGuitar, 1, startMeasure, endMeasure) # lead
    fitMedia(drums, 2, startMeasure, endMeasure) # drums
    # bass beat from startMeasure (inclusive) to endMeasure (exclusive)
    for measure in range(startMeasure,endMeasure):
        makeBeat(bass, 3, measure, "0---00--000-0000")
    # second guitar every other measure from startMeasure (inclusive) to endMeasure+1 (exclusive)
    for measure in range(startMeasure, endMeasure, 2):
        fitMedia(secondGuitar, 4, measure, measure+1)
    setEffect(4, DISTORTION, DISTO_GAIN, 10) # distortion on track 4
    return endMeasure

# B section
def sectionB(guitar, drums, cymbalCrash, startMeasure):
    endMeasure = startMeasure + 2
    fitMedia(drums, 1, startMeasure, endMeasure)
    fitMedia(guitar, 2, startMeasure, endMeasure)
    fitMedia(cymbalCrash, 3, startMeasure, startMeasure+1)
    return endMeasure

# set up an ABA musical form through function calls
endMeasure = sectionA(Y01_GUITAR_1, Y01_WAH_GUITAR_1, Y01_DRUMS_1, Y01_BASS_1, 1)
endMeasure = sectionB(Y01_WAH_GUITAR_1, Y01_OPEN_HI_HATS_1, Y01_CRASH_1, endMeasure)
endMeasure = sectionA(Y01_GUITAR_1, Y01_WAH_GUITAR_1, Y01_DRUMS_1, Y01_BASS_1, endMeasure)

# finish
finish()

First, notice the most important thing. This code produces exactly the same music as the previous code. Second, there are a few lines which are important to discuss, as they may be confusing because they involve two new programming concepts: 1) variable scoping and return values.

The first change is to the function argument lists, shown on lines 11 and 26. We removed the endMeasure parameter from both the sectionA() and sectionB() functions. Because section A is always four measures in length, we define a new variable on line 13 to hold the end measure value, defined as four measures after the startMeasure input parameter. Similarly, line 27 defines the same variable in the sectionB() function. But how can the same variable endMeasure be used in both the sectionA() and sectionB() functions? Actually, how can it also be defined and used at lines 34-36, outside of the functions? That this code works demonstrates a simple but necessary programming feature called variable scoping, which refers to the “range” or “view” of a variable definition. Put another way, if a variable is defined in code, it can be used anywhere that the variable is “in scope”. In the code shown above, there are three separate “variable scopes” in use: 1) the top level of the script (lines 34-36), 2) the sectionA() function variable scope, and 3) the sectionB() function variable scope. This is because the top level of an EarSketch script always has its own scope (provided by the EarSketch environment). Whenever you call a function (but not when you define it!), a new variable scope is created for the function call. In fact, calling a function is the only way to create a new variable scope in Python. The important effect of variable scoping is that a variable can be used in any scope in which it is defined, or in any scope created from that scope. This is quite technical, but is critically important. On lines 34-36, we call the sectionA() and sectionB() functions, which creates a new scope each time a function is called. Let’s walk through lines 34-36 step by step to understand what’s happening.

endMeasure = sectionA(Y01_GUITAR_1, Y01_WAH_GUITAR_1, Y01_DRUMS_1, Y01_BASS_1, 1)

Line 34 defines a variable called endMeasure. The variable is assigned a value that is returned from the sectionA() function (we will explain this shortly). This endMeasure variable is at the top level scope of your EarSketch script. Here is a useful diagram that illustrates this top level scope:

Top level EarSketch script variable scope

Top level EarSketch script variable scope

When the sectionA() function is called at line 34, a new scope is created, with its own endMeasure variable (defined on line 13). This is visualized with the following illustration:

Script and sectionA function scopes

Script and sectionA function scopes

This diagram shows that there are two variable scopes when functionA() is called: 1) the top level script variable scope and 2) the variable scope created when sectionA() function is called. The endMeasure variable defined inside the sectionA() function is a variable declared inside the new scope, and thus it hides the endMeasure variable defined at the top level of the script. Thus, the endMeasure variable used at lines 14, 15, 17, 20, and 23 is the endMeasure variable declared at line 13 inside the sectionA() function, and not the endMeasure variable declared at line 34, which is exactly what we want.

Next, we call functionB() and assign the return value to the endMeasure variable:

endMeasure = sectionB(Y01_WAH_GUITAR_1, Y01_OPEN_HI_HATS_1, Y01_CRASH_1, endMeasure)

The endMeasure variable declared in the sectionB() function at line 27 works exactly the same way as when we called sectionA() at line 34, creating another scope just for that function, which is illustrated as follows:

All variable scopes

All variable scopes

Notice that the sectionA() function scope and the sectionB() function scopes are separate, but both are within the top level script scope.

To summarize, there are two important takeaways about the concept of variable scoping:

  1. A new variable scope is created when a function is called. This ensures that variables declared inside the function (including function parameters) have the expected values when the programmer writes a function and when that function is called.
  2. A declared variable can be used at the scope at which it is declared, or inside that scope. Thus, when a variable is declared, it can be used inside a function called from that scope. However, if a variable with the same name exists in two scopes (as is the case in the code above), the variable at the innermost scope (“nearest” scope) is always used. Again, this is illustrated in the diagrams above.

Return Values

The second important topic is function return values. This is a much simpler concept than variable scoping. Quite simply, a value can be returned from a function and assigned to a variable, as is done at line 34 in the code above:

endMeasure = sectionA(Y01_GUITAR_1, Y01_WAH_GUITAR_1, Y01_DRUMS_1, Y01_BASS_1, 1)

At this line of code, the sectionA() function is called. The last line of that function, line 23, contains a return statement, which returns a value from the function to the code that called the function:

    return endMeasure

The endMeasure variable defined inside of the sectionA() function scope (at line 13) is returned from the function, and assigned to the endMeasure variable in the top level scope (at line 34 shown above). Similarly, the sectionB() function also returns the endMeasure variable defined in the function. Thus, the top level endMeasure variable is always up to date with the current end measure, and can be passed in to subsequent function calls, and updated using the return values of the functions, shown at lines 35-36:

endMeasure = sectionB(Y01_WAH_GUITAR_1, Y01_OPEN_HI_HATS_1, Y01_CRASH_1, endMeasure)
endMeasure = sectionA(Y01_GUITAR_1, Y01_WAH_GUITAR_1, Y01_DRUMS_1, Y01_BASS_1, endMeasure)

Notice that the endMeasure variable is passed in to the sectionB() function, and also updated with the return value from the function. The same thing happens at line 36.

Exercise

What do you think will happen if the endMeasure variable in the sectionA() function is renamed to finalMeasure, and similarly changed where it is used in the sectionA() function? Will the output of the script change? Try it and see. Explain the results.


Viewing all articles
Browse latest Browse all 3

Latest Images

Trending Articles





Latest Images