| Exercise 11.1 - Guitar Strings |
1 |
2 |
3 |
|
This tutorial details several extended features in Manhattan, including a built-in physical guitar model, parallel playback threads, and anonymous (lambda) functions. There concepts are explored in the context of the 2003 hit, Hey Ya, by hip-hop duo OutKast.
In the first part, we look at an alternative to sample synthesis: physical modelling (or "virtual acoustic" synthesis). Rather than using recordings of real instruments, this approach seeks to faithfully simulate the energies and vibrations within the acoustic instrument, using a virtual model of its physics. A faithful physical model should not only sound, but also behave like the original, permitting all the same expressive possibilities and playing styles - unlike samples, which only reproduce what was recorded.
Manhattan supports up to two physically-modelled guitars (GX and GY), each of which can be configured to simulate a variety of different acoustic and electric guitar types, including classical, flamenco, nylon and steel hollow bodies (as well as banjos, mandolines, etc.) and numerous solid-body electric guitars with additional pedals and effects processing. We will use one to play the piece's chord sequence, strummed on acoustic (rhythm) guitar.
Start by strumming the virtual guitar:
- As in previous examples, the pattern is based on a single repeating bar (rows 4 to 19) that changes over time, using chord sequences and note patterns defined in arrays.
- However, we will start by simply strumming the guitar using the pattern in Strum . This array contains a rhythmic series of chords, mirroring the six open strings of a standard guitar: E,A,D,G,B,E - using note delays to replicate up and down strums across the strings. In the physical model, each channel is a single string, plucked by all the notes on the channel.
- While we could copy the array into the repeating bar to hear it, we can also tell Manhattan to play the array indepedently, using the play() or loop() functions, which 'fork' playback to play a specified range of cells independently (respectively once or looped). Manhattan supports parallel processing of up to 64 concurrent playback threads.
Enter the following code in the first cell of Channel 2, to stop the default playback of the whole pattern, and loop just the array by itself:
stop()
loop(@Strum)
- Playing the pattern, you'll hear the guitar being strummed, playing all strings without any fingering, as if picking the instrument up for the first time. While the performance lacks nuance, the timbre should sound authentic.
- We can vary the parameters of both strings and plucks to explore different guitar timbres and types. To do this, edit the parameters below GUITAR , which are committed to the model whenever @GUITAR.run() is called. Look in this cell to see how the model is controlled. The model has many other settings, which are detailed in the Code Reference (in the Help menu). Here, the following options are exposed:
Preset - loads a preset configuration (00 to 12),
to switch between types of guitar.
Resonance - string resonance (feedback)
Material - string material, e.g. nylon or steel,
 (relative dampening of the string).
Hardness - how hard the string is plucked.
Position - position of the pluck on the string,
sounding thinner near the extremes.
- Play around with the settings to explore different guitars. Notice how lowering the Resonance setting can also be used to mirror the effect of dampening a guitar string with a hand.
- When ready, remove the stop() and loop() code and click here to reset the guitar settings, then proceed to part 2 to create the chord sequence.
| Exercise 11.2 - Chords and Chorus |
1 |
2 |
3 |
|
The chord sequence for Hey Ya is provided in the Chords array, below the Strum pattern. Unlike previous examples, it runs left to right (rather than a vertical list). Each chord contains the pitches for the six strings, in rows 0 to 5 of the array.
Strum the chord sequence:
-
Using the arp() command, insert code into cell [2:4] to use the Strum pattern with pitches taken from the Chords array - based on the current value of the Chord counter to select column and appropriate range of rows (using 'to'). Try to work this out for yourself, based on what you have learnt previously, but if you get stuck, click here to see the code.
arp(@Strum, @Chords[@Chord:0 to 5])
- Play the pattern and you should hear the full chord sequence, strummed on the guitar. Note the insertion of a 2/4 bar in the middle of the 4/4 sequence; one of the unique features of the Hey Ya piece.
- Remove the "01" in the SoloGuitar variable by setting it to zero, and play the pattern again to enable the other instruments in the mix.
Add the chorus motif using the play() function:
- After the introductory verse, the chorus plays - first with added bass, then again with a synth pad (in place of the sung, "Hey Ya"). The original is also notable for a memorable lead motif, currently absent. This motif is provided, split into short fragments, in the Chorus1 and Chorus2 arrays.
Notice how these use a different tempo from the main piece (@4D - or 77 bpm), running at half the speed, so that the same duration of music only takes half the space in the pattern.
- To play these melody fragments, we will trigger them at the appropriate times, using play():
- The first to sound is Chorus2 , halfway through the first chord. Using the Chord number to track where we are, we can trigger playback in Row 12 (marked by the 2 label):
@Chord == 1 ? play(@Chorus2)
- An extended version appears again in the third chord at the same point in the bar. We'll not only play Chorus2 , but also the notes beyond it, specifying the range of rows to play manually. Add a second line of code to the cell to do this:
@Chord == 3 ? play([36 to 43])
- Finally, this phrase is leads into Chorus1 in the next chord. At the cell marked 1 , add this line of code to the cell:
@Chord == 4 ? play(@Chorus1)
- Audition the full piece from the start. All being well, the melody should be audible when the chorus comes around in Sections 2 and 3.
This illustrates a simple use of parallel playback - the melody fragments are play independently of the main sequence, and even maintain their own separate tempo. In this example, it's allowed us to very concisely represent the chorus melody of the piece (for example, compareed to the arrays for the bass or verse, which last a similar duration in the piece). However, much grander uses of the parallel playback are possible: from building up a whole piece by triggering or looping phrases on-demand to creatively exploring musical time through polyrhythm, polymeter, and polytempi.
- When ready, move to step 3.
| Exercise 11.3 - Lambda Functions |
1 |
2 |
3 |
|
Sometimes we want to run a specific code expression over a range of cells, but don't want to litter all the cells with copies of the code, nor multiple run() statements or macro effects. Manhattan supports an advanced programming concept that allows you to create an anonymous function, called a lambda function - and run it over a range of cells.
To demonstrate the syntax, we'll use a simple example that runs a formula to slightly randomise all the volume entries in our strummed guitar part, adding a touch of human 'error' to the performance.
Randomise volumes using a lambda function:
- Using a lambda is a bit like assigning a value, except that we assign a code expression to run in the cell. Such code could change values, but functions like any code written in the cell itself, so can do anything.
- To apply a lambda function, we specify the range of cells, and the code to run, using the f() function. Add the following below the arp() function to [2:4]:
([2:4] to [7:19]) = f(".volume = .volume + rnd(-10,10)")
This line of code identifies a range between two points, takes any current volume and adds a random number between -10 and 10. This 'humanises' the somewhat rigid volume patterns specified in the Strum array, but acknowledging that a guitarists fingers never hit all the strings exactly the same every time.
- Play the pattern. The effect is subtle; you might try setting SoloGuitar to 1 to hear the guitar by itself.
- You can also run lambdas for specific properties, such as in the following code to humanise the timings of the strum:
([2:4] to [7:19]).param = f(".param ? .param + rnd(0,1)")
- When using lambdas, several variables are provided to enable formulas to be sensitive to where they are in the range and pattern:
c - current channel within the pattern, starting 1
r - current row within the pattern, starting 1
x - current channel within the range, starting 0
y - current row within the range, starting 0
i - current cell index, top-to-bottom, left-to-right
To see how these work, place the following in [2:0]:
([2:0] to [7:3]) = f(".volume = x")
Lambda functions are simple and extremely powerful, capable of quickly applying changes over broad areas of the music. They can be used to search, replace, increment, or randomise values, to calculate number sequences or iterate over cells, or even affect playback actions by triggering multiple play() or loop() commands - anything a single cell or function can do, but on a larger scale.