How could I create a music file using godot? - audio
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.
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.
Related
A-frame: play a random sound on click
I'm making scene with a radio-like object, and I'd like to make it play a random audio file from my assets when is clicked. This question help me a lot to understand how to add the audio files Play sound on click in A-Frame but I can't figure out the random aspect. Thank you very much.
You can have an array of sound elements // use ID's - grab by document.getElementById(audioIds[num]) var audioIds = ['one', 'two', 'three'] // use a class - grab by audioEls[num] var audioEls = document.getElementsByClassName('sharedClassName') and use a simple random to select an item // get a random number between 0 and the number of the elements let lastIndex = audioEls.length - 1 // arrays begin at 0. var audioEl = audioEls[Math.round(Math.random() * lastIndex)] Then on click stop any playing sound, and grab a new one: this.el.addEventListener('click', () => { if (!playing) { audioEl = audio[Math.round(Math.random() * (audio.length - 1))] audioEl.play(); } else { audioEl.pause(); audioEl.currentTime = 0; } playing = !playing; }); You can check it out in my fiddle.
Insert 3D text to the front face of a cube GameObject
I want to add a text to a cube, which has a 3D text as a child. I am trying to write/show the text to the front face (or very close) of the cube. I am using the below javascript to write on the 3D text: #pragma strict var countdown : int = 200; function Update() { GetComponent(TextMesh).text = countdown.ToString(); } EDIT: My difficulty is to make the text appear in the front side/face of the cube, like it is written on it. My last failed try was to use the below lines: var tm = gameObject.AddComponent(TextMesh); tm.text = countdown.ToString(); Any ideas?
If the TextMesh object is a child of the cube, then what you'd want to use is: transform.Find("TextMeshObjectName").GetComponent(TextMesh).text = countdown.ToString(); where TextMeshObjectName is the name of your TextMesh object. I'm also assuming the script is running from within the cube. There are of course other ways to find children as well, so if you like, take a look at this link here
Stopping the sound at mouse click in Actionscript 3
So I have this code, I want the sound to stop when I click on the start button but when I click on the start button the sound doesn't stop but it's volume is decreased, it's very odd. Can someone fix this ? var mySound:Sound = new Sound(); mySound.load(new URLRequest("Sounds/Sound.mp3")); var channel:SoundChannel = mySound.play(); channel = mySound.play(); StartButton.addEventListener(MouseEvent.CLICK, StartGame); function StartGame(e:MouseEvent):void { channel.stop(); gotoAndPlay(2); }
Well, you already initialized channel in the line var channel:SoundChannel = mySound.play(); Then, you (probably accidentally) gave it a value anew: channel = mySound.play(); You'll find if you take that last line out, the code will work just fine.
XNA SoundEffects not playing
Followed a few tutorials and I'm having no luck, I'm trying to add a simple .WAV sound effect into XNA using free samples, I have named the sound as: SoundEffect hit1; SoundEffect hit2; And then loaded the content with: hit1= content.load<SoundEffect>("hit1") But when it comes to adding the play to a button press and I go to test it there's no sound at all even no errors or nothing the game loads and is playable but any sound effects are not working. ** // Sounds SoundEffect hit1; SoundEffect hit2; This is my variable names: // Sounds hit1 = Content.Load<SoundEffect>("hit1"); hit2 = Content.Load<SoundEffect>("hit2"); This is how I'm loading them in the loadcontent method: //If Keyboard Key W is Pressed or Buttong Y if (keys1.IsKeyDown(Keys.W) && oldKeys1.IsKeyUp(Keys.W) || (oldpad1.Buttons.Y == ButtonState.Released) && (pad1.Buttons.Y == ButtonState.Pressed)) { secondsPassed = 0; // IF The Target Image is a Gnome if (targets[0] == sprites[0]) { //They whacked a gnome GnomeHits = GnomeHits + 1; runNum = 0; secondsPassed = 0; hit1.Play(); } else { //They whacked a troll scoreNum = scoreNum + 1; runNum = runNum + 1; GnomeHits = 0; secondsPassed = 0; hit2.Play(); } SetUpSpriteLoop(); And this is one of the control buttons I'm trying to assign sound too when I hit F5 and run the game when I hit the key or button no sound at all.
I had a similar problem with Monogame (built from XNA's ashes) on a Windows 10 machine. I fixed it by reinstalling DirectX, and everything just worked.
My first recommendation would be to make sure you are updating the keyboard presses. I only say that as I can't see you doing that in your code. If you do not do this then the key states will not update and therefore you will never enter the if statement. Once you have ensured you are entering the if statement, I would play around with the volume of the sound effect to make sure it is actually audible. I may be a bit off base there however if I am I suggest following this tutorial rbwhitaker is a great resource for XNA.
TWebbrowser very slow to load real time markers from local google map HTML
I am actually displaying every 2 seconds many real time GPS points (20) by showing it from an index.html (using google maps to display and treat points) in my TWebBrowser, and it slows a lot my app. And as i got another module under the same application allowing me to enter data in my database, it freezes usually my app. I know Threads are made for that, but i am not sure that it will solve my problem. Any ideas ??? Thanks Gwenael PS : did the fact that i am loading my javascript code from an external file (and not the source code loadead in my delphi application), can slow my app ?
If you get your javascript from an external file it will get cached, so no, that probably won't slow you down much, except for the first time maybe. Probable causes for slowness, and suggestions to speed things up: TWebBrowser wraps Internet Explorer, which is not exactly famous for its raw speed when it comes to this type of task; If you want fast JavaScript handling, consider DelphiChromiumEmbedded If you draw a marker every two seconds, you'll have to draw 1800 markers for a one-hour drive. If you want to show multiple trips, it's just going to be a heavy task to draw all the icons with alpha-transparency and all. I usually draw a marker (arrow with driving direction) every 2 minutes, or if more than 200m has been travelled since the last marker. That way you don't have to draw a whole cloud of markers when a car is standing still. You can use douglas-peucker algorithm to simplify the line. As a parameter you'll give a maximum error that you allow in the line, and it'll remove as many points as possible without exceeding that error. So, when you have a straight line, it'll remove all points between the edges. Also, you can consider clustering points at certain zoomlevels. If you would use OpenLayers instead, it would be easier, but with the help of the Google Maps Util Library you can do the same with Google Maps (Example). If you zoom out, it's a bit useless to draw 2000 overlapping icons on an area of 10x10 pixels. If you show me your code, I can give you some more direct advice on how to speed things up.
Here is my Delphi code : i := 0; With DMMain.MDMain do begin QLastPositionGPS.Close ; QLastPositionGPS.Open ; QLastPositionGPS.First ; for i:=0 to QLastPositionGPS.RecordCount-1 do begin GPSLatitude := StringReplace(QLastPositionGPS.FieldByName('latitude').AsString, ',', '.', [rfreplaceall]) ; GPSLongitude := StringReplace(QLastPositionGPS.FieldByName('longitude').AsString, ',', '.', [rfreplaceall]) ; HeureDernierGPS := QLastPositionGPS.FieldByName('maj').AsString ; MDMain.QGPSactifs.Close ; MDMain.QGPSactifs.ParamByName('id_artisan').AsInteger := MDMain.QLastPositionGPS.FieldByName('id_artisan').AsInteger ; MDMain.QGPSactifs.Open ; if MDMain.QGPSactifs.FieldByName('etat').AsBoolean = True then begin CdrCarto.Chromium1.Browser.MainFrame.ExecuteJavaScript('AjouterMarqueurCirculant('+ GPSLatitude + ', ' + GPSLongitude + ', ' + MDMain.QLastPositionGPS.FieldByName('id_artisan').AsString + ')', 'about:blank', 0) ; end else if OptionDisplayGPSActif then if (MDMain.QGPSactifs.FieldByName('etat').AsBoolean = False) and (MDMain.QGPSactifs.FieldByName('etat_serveur').AsBoolean = True) then begin CdrCarto.Chromium1.Browser.MainFrame.ExecuteJavaScript('AjouterMarqueurGPS('+ GPSLatitude + ', ' + GPSLongitude + ', ' + MDMain.QLastPositionGPS.FieldByName('id_artisan').AsString + ')', 'about:blank', 0); end; QLastPositionGPS.Next ; MDMain.QGPSactifs.Close ; end; QLastPositionGPS.Close ; end; end; and my Javascript code : function AjouterMarqueurCirculant(Lat, Long, notaxi) { var marker = new MarkerWithLabel({ position: new google.maps.LatLng(Lat, Long), draggable: true, map: map, labelContent: "Taxi "+notaxi, labelAnchor: new google.maps.Point(22, 0), labelClass: "labelsactif", // the CSS class for the label labelStyle: {opacity: 0.75}, labelVisible: true, icon:"icones/taxi_circulant_ok.png" }); var iw = new google.maps.InfoWindow({ content: "Nom Prenom" }); google.maps.event.addListener(marker, "click", function (e) { iw.open(map, marker); }); markersCirculant.push(marker); bounds.extend(new google.maps.LatLng(Lat, Long)); }