In Bard's Lesson the main mechanic is to play the piccolo, a windwood instrument, and in the game's world this piccolo has three pitches that together build up a C Major chord. But if you check the game's source there's only a C5 sample being used in the game. So how do I managed the pitches?
Godot Audio Server
Turns out that Godot Engine 3.0 has a really powerful AudioServer, the class in charge of handling audio related stuff. In the current release, Juan Linietsky built a new audio system from scratch, which turned out to be the most feature complete audio system among the game engines I know (and the range isn't by far small).
From within the AudioServer a Godot user can build up his/her own mixing table.
As you can see there is the option to add many Audio Buses, an AudioBus is a system that communicates audio data within the AudioServer, in such a way you can send audio outputs to the input of other buses, which will end being played in the Master Bus, the default and irremovable AudioBus.
Using Audio Buses
There's something very important to know in forehand before you start working with AudioBuses in Godot Engine. The order matters, the arrange of the buses in the mixing table is very important because
They communicate from right to left only
This means that you can't pass data from the Master bus to any other bus for instance, and in the case shown you can't pass data from the SFX bus to the Birbs bus, and so on. But an AudioBus can receive audio data from every bus beyond its index.
For instance the Master bus can receive data directly from any of the other buses, the Birbs bus can receive data from the Player, UI and BGM buses and so on.
But of course you can tweak with their index and move them from left to right and vice versa.
In this case I'm moving the Player bus to the end of the chain, this will enable the BGM bus to receive data from the Player bus, but not the other way around.
And to do this you just need to select from the bottom drop down menu, which shows the available buses, the one you want to send your bus' data to.
You might by asking yourself:
Why would I want to pass audio data using buses, what is this for?
Audio Bus Layout
Well there's a very good reason why this is VERY, VERY AWESOME. As you can presume, you can arrange the buses in many different layouts, which we call AudioBusLayout.
But the most amazing thing about this is that, since an AudioBusLayout is a .tres file, which means it is a resource, you can save as many AudioBusLayouts you want and even use them between projects! HOW...AWESOME...IS...THAT? You already figured out what's coming next right?
Now comes the best part, nothing of this said until now makes sense if you can't chain AudioEffects. Yep! In Godot we can make use of builtin AudioEffects, which range from simple Panner or Amplify to very complex and useful Filters or Equalizations and even... a Pitch Shifter.
From now on I'll presume you already blew up your mind thinking:
OMG, so I can make an AudioBusLayout for a cave environment with some Reverb and Echo effects and I can load it in runtime when the player enters a cave, and when it leaves it I can reset it to a field environment AudioBusLayout with some nice filters, and then when the player faces a boss, I can load a special battle AudioBusLayout which amplifies the sound effects and I can use these layouts in many projects just having to tweak a little bit, because they are .tres files?
Yes...I know how you're feeling. But let's go back to the part where I show you how you can at least tweak with the effects using GDScript.
Shifting the Pitch
Well, as you can presume from the screen shots and this long introduction to the Godot's Audio System, to shift the pitch of the C5 sample used for the piccolo sound effect I just need to add a PitchShifter AudioEffect to the Player and the Birds buses.
Now comes the fancy part. By using an AudioStreamPlayer2D (but this approach works with similar Nodes) we will use a block of logic to access its Bus, then we will iterate through it to look for an AudioEffectPitchShift, when we find it, we will access it and set its pitch_scale to be the respective value of an Array of pitches we will define later on, and then we will play the AudioStreamPlayer2D.
This whole function will also use an argument(p) to know which index of the PITCHES Array shall be used, which in the case of Bard's Lesson, is the index of an InputEvent in the input action for interactions.
Note that since I'm using this script in the parent Node of the AudioStreamPlayer2D, in this case the flute node, I'm accessing it instead of calling the methods get_bus() and play() directly, if you are using this function as an extension of your AudioStreamPlayer, you can use these methods without referring to another Node.
Pitches of a Note Scale
In music theory every note is a frequency, i.e. a time based repetition. Every note has its own frequency but in order to create harmony between notes we use some specific intervals of frequencies which we call Scale.
Root Note + Third Note + Perfect Fifth Note
The interval between the Root Note and the other notes that form this harmony can be expressed as:
1:1 + 5:4 + 3:2. Simplifying as 1.0, 1.25 and 1.5
And now we already have our pitches to shift in our algorithm! So we will use an Array to store these float numbers and voila!
And since Juan implemented a very, very good algorithm to shift the pitch of an AudioSample that doesn't just compress its frequency (messing with the actual duration of the sample) we can securely shift the pitch knowing our sample will always have the expected duration. But in my case I did something more in order to it to have an undefined sustain...but this is a topic for another tutorial!
That's it, keep developing and until the next time!