I'm trying to create a music game using godot, it's similar to playing a piano, the player presses keys, each key has a sound assigned, and the music plays. What I want is to have a kind of recorder inside the game to be able to save the melody created and export it to some file like mp3 or wav. I'm using gdScript and I've been researching about this but wasn't able to find anything.
1 Answers
This issue has two parts, first of all, we need to actually reproduce the audio, and second we need to record it and save it a file. And we have two options for playing the audio... Let us begin there.
Playing
Playing audio samples
As you probably know you can use AudioStreamPlayer
(2D
/3D
) to play audio. If you have samples an instrument, you can set up multiple of these, one for each note. And then play them individually by calling play
.
Alright, but what if you don't have a sample for each note? Just one.
Well, you can use pitch_scale
on your AudioStreamPlayer
(2D
/3D
), but that also shortens the note. Instead…
Look at the bottom of the Godot editor, there is a panel called Audio. There you can configure audio buses. Add a new bus, add the PitchShift
effect to it. If you select the effect, you can configure it on the Inspector panel. Now, in your AudioStreamPlayer
(2D
/3D
), you can select the bus that has the effect, and there you go.
Except, you would have to setup a lot of these. So, let us do that from code instead.
First, adding an AudioStreamPlayer
(2D
/3D
), is adding a node:
var player := AudioStreamPlayer.new()
add_child(player)
We want to set the sample, so we also need to load the sample:
var sample := preload("res://sample.ogg")
var player := AudioStreamPlayer.new()
player.stream = sample # <--
add_child(player)
By the way, double check in the import settings of your sample (With the file selected in the FileSystem panel, go to the Import panel) if it has loop
enabled or not.
To set the audio bus, you can do this:
var sample := preload("res://sample.ogg")
var player := AudioStreamPlayer.new()
player.stream = sample
player.bus = "Bus Name" # <--
add_child(player)
And that brings me to adding an audio bus:
AudioServer.add_bus()
Wait, hmm… Ok, let us put the bus explicitly at the end, so we know the index:
var bus_index := AudioServer.get_bus_count()
AudioServer.add_bus(bus_index)
And give it a name:
var bus_index := AudioServer.get_bus_count()
AudioServer.add_bus(bus_index)
AudioServer.set_bus_name(bus_index, "Bus Name") # <--
And let us route it to Master:
var bus_index := AudioServer.get_bus_count()
AudioServer.add_bus(bus_index)
AudioServer.set_bus_name(bus_index, "Bus Name")
AudioServer.set_bus_send(bus_index, "Master") # <--
Yes, it is set to Master by default. But you might need to change that.
And now we can add the effect:
var pitch_shift_effect := AudioEffectPitchShift.new()
pitch_shift_effect.pitch_scale = 0.1
AudioServer.add_bus_effect(bus_index, pitch_shift_effect, 0)
That 0
at the end is the index of the effect. Since this is the only effect in a newly created audio bus, it goes in the index 0
.
And, well, you can make a loop and add the players with their corresponding audio buses with their pitch shift effects.
Generating audio
Alright, perhaps you don't have a sample at all. Instead you want to generate waves. We can do that too.
This time we are going to have a AudioStreamPlayer
(2D
/3D
), and give it an AudioStreamGenerator
(you find it in the drop down menu from the stream
property in the Inspecto panel). Edit the resource to set the mix rate you want to work with, by default it is 44100
. But we are going to be generating this audio as close as real time as possible, so a lower rate might be necesary.
Now, in a script, we are going to get the playback object form the AudioStreamPlayer
(2D
/3D
):
onready var _playback := $Player.get_stream_playback()
Or if you are placing the script in the player, because why not:
onready var _playback := get_stream_playback()
We can push audio frames to the playback object. With playback.push_frame
, it takes a Vector2
, where each component is one of the stereo channels (x
= left, y
= right).
We are going to call get_frames_available
to figure out how many we need to push, and we are going to be pushing them every graphics frame (i.e. in _process
).
The following script will generate an sine wave with frequency 440
(A):
extends AudioStreamPlayer
onready var _playback := get_stream_playback()
onready var _sample_hz:float = stream.mix_rate
var _pulse_hz := 440.0
var _phase := 0.0
func _ready():
_fill_buffer()
func _process(_delta):
_fill_buffer()
func _fill_buffer():
var increment := _pulse_hz / _sample_hz
for frame_index in int(_playback.get_frames_available()):
_playback.push_frame(Vector2.ONE * sin(_phase * TAU))
_phase = fmod(_phase + increment, 1.0)
You can set the AudioStreamPlayer
to autoplay.
This code is adapted from the official Audio Generator demo.
We, of course, may want to play multiple of these notes at the same time.
So, I created a Note
class that looks like this (just add a new script in the FileSystem panel):
class_name Note extends Object
var increment:float
var pulse_hz:float
var phase:float
func _init(hz:float, sample_hz):
pulse_hz = hz
phase = 0.0
increment = pulse_hz / sample_hz
func frame() -> float:
var result := sin(phase * TAU)
phase = fmod(phase + increment, 1.0)
return result
And now we can play them like this:
extends AudioStreamPlayer
onready var _playback := get_stream_playback()
onready var _sample_hz:float = stream.mix_rate
onready var notes := [
Note.new(440, _sample_hz),
Note.new(554.37, _sample_hz),
Note.new(622.25, _sample_hz)
]
func _ready():
_fill_buffer()
func _process(_delta):
_fill_buffer()
func _fill_buffer():
var note_count := notes.size()
for frame_index in int(_playback.get_frames_available()):
var frame := 0.0
for note in notes:
frame += note.frame()
_playback.push_frame(Vector2.ONE * frame / note_count)
Recording
To record we are going to add a "Record" (AudioEffectRecord
) effect to an audio bus. Let us say you added it to Master (the first bus), and it is the first effect there, we can get if from code like this:
var record_bus_index := 0 # Master
var record_effect_index := 0 # First Effect
var record_effect = AudioServer.get_bus_effect(record_bus_index, record_effect_index) as AudioEffectRecord
Then we need to start recording, like this:
record_effect.set_recording_active(true)
And when we are done recording we can get what was recorded (an AudioStreamSample
) and stop recording:
var recording := record_effect.get_recording()
record_effect.set_recording_active(false)
And finally we can save it to a WAV file:
recording.save_to_wav("user://recording.wav")
See also the official Audio Mic Record demo project.
No, there is no in engine solution to save an MP3.
Linky links
- The official Audio Generator demo.
- The official Audio Mic Record demo project.
- The addon godot-simple-sampler.
- The addon godot-midi-player.

- 31,890
- 5
- 57
- 86