Skip to content

Commit bc556ed

Browse files
Add a preview of the microphone waveform in stereo from Input.get_microphone_buffer
1 parent fdb2f50 commit bc556ed

17 files changed

+463
-2
lines changed

audio/mic_input/Intro.ogg

684 KB
Binary file not shown.

audio/mic_input/Intro.ogg.import

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
[remap]
2+
3+
importer="oggvorbisstr"
4+
type="AudioStreamOggVorbis"
5+
uid="uid://c2re52petqrvx"
6+
path="res://.godot/imported/Intro.ogg-dfe75727d0e47692e220adf97ddb7ad9.oggvorbisstr"
7+
8+
[deps]
9+
10+
source_file="res://Intro.ogg"
11+
dest_files=["res://.godot/imported/Intro.ogg-dfe75727d0e47692e220adf97ddb7ad9.oggvorbisstr"]
12+
13+
[params]
14+
15+
loop=true
16+
loop_offset=0
17+
bpm=0
18+
beat_count=0
19+
bar_beats=4

audio/mic_input/MicRecord.gd

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
extends Control
2+
3+
var wav_recording: AudioStreamWAV
4+
var input_mix_rate : int = 44100
5+
var audio_chunk_size_ms : int = 20
6+
var audio_sample_size : int = 882
7+
8+
var total_samples : int = 0
9+
var sample_duration : float = 0.0
10+
var recording_buffer : Variant = null
11+
12+
var audio_sample_image : Image
13+
var audio_sample_texture : ImageTexture
14+
var generator_timestamp : float = 0.0
15+
var generator_freq : float = 0.0
16+
17+
func _ready() -> void:
18+
if not Input.has_method("start_microphone"):
19+
$Status.text = "Error: requires PR#105244 to work"
20+
input_mix_rate = int(AudioServer.get_input_mix_rate())
21+
print("Input mix rate: ", input_mix_rate)
22+
print("Output mix rate: ", AudioServer.get_mix_rate())
23+
print("Project mix rate: ", ProjectSettings.get("audio/driver/mix_rate"))
24+
$InputMixRate.text = "Mix rate: %d" % input_mix_rate
25+
audio_sample_size = int(audio_chunk_size_ms*input_mix_rate/1000.0)
26+
var blank_image : PackedVector2Array = PackedVector2Array()
27+
blank_image.resize(audio_sample_size)
28+
audio_sample_image = Image.create_from_data(audio_sample_size, 1, false, Image.FORMAT_RGF, blank_image.to_byte_array())
29+
audio_sample_texture = ImageTexture.create_from_image(audio_sample_image)
30+
$MicTexture.material.set_shader_parameter("audiosample", audio_sample_texture)
31+
32+
func _on_microphone_on_toggled(toggled_on : bool) -> void:
33+
if toggled_on:
34+
Input.start_microphone()
35+
total_samples = 0
36+
sample_duration = 0.0
37+
else:
38+
Input.stop_microphone()
39+
40+
func _on_mic_to_generator_toggled(toggled_on : bool) -> void:
41+
if toggled_on:
42+
$AudioGenerator.stream.mix_rate = input_mix_rate
43+
$AudioGenerator.playing = toggled_on
44+
45+
func _process(delta : float) -> void:
46+
sample_duration += delta
47+
while Input.get_microphone_frames_available() >= audio_sample_size:
48+
var audio_samples : PackedVector2Array = Input.get_microphone_buffer(audio_sample_size)
49+
if audio_samples:
50+
audio_sample_image.set_data(audio_sample_size, 1, false, Image.FORMAT_RGF, audio_samples.to_byte_array())
51+
audio_sample_texture.update(audio_sample_image)
52+
total_samples += 1
53+
$SampleCount.text = "%.0f samples/sec" % (total_samples*audio_sample_size/sample_duration)
54+
if recording_buffer != null:
55+
recording_buffer.append(audio_samples)
56+
if $MicToGenerator.button_pressed:
57+
$AudioGenerator.get_stream_playback().push_buffer(audio_samples)
58+
if generator_freq != 0.0:
59+
var gplayback : AudioStreamGeneratorPlayback = $AudioGenerator.get_stream_playback()
60+
var gdt : float = 1.0/$AudioGenerator.stream.mix_rate
61+
for i in range(gplayback.get_frames_available()):
62+
var a : float = 0.5*sin(generator_timestamp*generator_freq*TAU)
63+
gplayback.push_frame(Vector2(a, a))
64+
generator_timestamp += gdt
65+
66+
func _on_record_button_toggled(toggled_on : bool) -> void:
67+
total_samples = 0
68+
sample_duration = 0.0
69+
if toggled_on:
70+
$PlayButton.disabled = true
71+
$SaveButton.disabled = true
72+
recording_buffer = [ ]
73+
$RecordButton.text = "Stop"
74+
$Status.text = "Status: Recording..."
75+
76+
else:
77+
$PlayButton.disabled = false
78+
$SaveButton.disabled = false
79+
var recording_data : PackedByteArray = PackedByteArray()
80+
var data_size : int = 4*audio_sample_size*len(recording_buffer)
81+
recording_data.resize(44 + data_size)
82+
recording_data.encode_u32(0, 0x46464952) # RIFF
83+
recording_data.encode_u32(4, len(recording_data) - 8)
84+
recording_data.encode_u32(8, 0x45564157) # WAVE
85+
recording_data.encode_u32(12, 0x20746D66) # 'fmt '
86+
recording_data.encode_u32(16, 16)
87+
recording_data.encode_u16(20, 1)
88+
recording_data.encode_u16(22, 2)
89+
recording_data.encode_u32(24, input_mix_rate)
90+
recording_data.encode_u32(28, input_mix_rate*4) # *16*2/8
91+
recording_data.encode_u16(32, 4) # 16*2/8
92+
recording_data.encode_u16(34, 16)
93+
recording_data.encode_u32(36, 0x61746164) # 'data'
94+
recording_data.encode_u32(40, data_size)
95+
for i in range(len(recording_buffer)):
96+
for j in range(audio_sample_size):
97+
var k : int = 44 + 4*(i*audio_sample_size + j)
98+
recording_data.encode_s16(k, clampi(recording_buffer[i][j].x*32768, -32768, 32767))
99+
recording_data.encode_s16(k+2, clampi(recording_buffer[i][j].y*32768, -32768, 32767))
100+
wav_recording = AudioStreamWAV.load_from_buffer(recording_data)
101+
102+
$RecordButton.text = "Record"
103+
$Status.text = ""
104+
recording_buffer = null
105+
106+
func _on_play_button_pressed() -> void:
107+
print_rich("\n[b]Playing recording:[/b] %s" % wav_recording)
108+
$AudioWav.stream = wav_recording
109+
$AudioWav.play()
110+
111+
func _on_play_music_toggled(toggled_on : bool) -> void:
112+
if toggled_on:
113+
$AudioMusic.play()
114+
$PlayMusic.text = "Stop Music"
115+
else:
116+
$AudioMusic.stop()
117+
$PlayMusic.text = "Play Music"
118+
119+
func _on_save_button_pressed() -> void:
120+
var save_path: String = $SaveButton/Filename.text
121+
wav_recording.save_to_wav(save_path)
122+
$Status.text = "Status: Saved WAV file to: %s\n(%s)" % [save_path, ProjectSettings.globalize_path(save_path)]
123+
124+
func _on_open_user_folder_button_pressed() -> void:
125+
OS.shell_open(ProjectSettings.globalize_path("user://"))
126+
127+
# 400Hz frequency can be used (from another device) to probe a stereo microphone
128+
# response due to where there should be 8 wavelengths in the space of 20ms (2.5ms per wave).
129+
# The wavelength is then 343/400=0.8575m long.
130+
func _on_option_tone_item_selected(index : int) -> void:
131+
if index != 0:
132+
$AudioGenerator.playing = true
133+
if not $MicToGenerator.button_pressed and not $PlayMusic.button_pressed:
134+
generator_freq = int($OptionTone.get_item_text(index))
135+
else:
136+
generator_freq = 0.0

audio/mic_input/MicRecord.gd.uid

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
uid://dbbfvbf6ronrp

audio/mic_input/MicRecord.gdshader

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
shader_type canvas_item;
2+
render_mode blend_mix;
3+
4+
uniform sampler2D audiosample : repeat_enable;
5+
const float cfac = 4.0;
6+
const float mfac = 2.0;
7+
const float mdisp = 0.166667;
8+
const float mthick = 0.05;
9+
const float mtiltfac = 0.125;
10+
11+
void fragment() {
12+
vec4 b = texture(audiosample, UV + vec2(-(UV.y-0.5)*mtiltfac, 0.0));
13+
vec4 c = texture(audiosample, UV + vec2((UV.y-0.5)*mtiltfac, 0.0));
14+
float s = (b.r + c.g)/2.0;
15+
COLOR = vec4(0.1+max(s,0.0)*cfac, 0.1, 0.1+max(-s,0.0)*cfac, 1.0);
16+
17+
vec4 a = texture(audiosample, UV);
18+
float dr = abs(UV.y*2.0 - 1.0 - (a.r + mdisp)*mfac);
19+
float dg = abs(UV.y*2.0 - 1.0 - (a.g - mdisp)*mfac);
20+
if (dg < mthick)
21+
COLOR = vec4(1.0,1.0,0.9,1.0);
22+
else if (dr < mthick)
23+
COLOR = vec4(0.8,0.8,0.9,1.0);
24+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
uid://cl4x5tyii4r6q

audio/mic_input/MicRecord.tscn

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
[gd_scene load_steps=6 format=3 uid="uid://dvjlkpjvjxn0h"]
2+
3+
[ext_resource type="Script" uid="uid://dbbfvbf6ronrp" path="res://MicRecord.gd" id="1"]
4+
[ext_resource type="AudioStream" uid="uid://c2re52petqrvx" path="res://Intro.ogg" id="2"]
5+
[ext_resource type="Shader" uid="uid://cl4x5tyii4r6q" path="res://MicRecord.gdshader" id="3_vkk4n"]
6+
7+
[sub_resource type="AudioStreamGenerator" id="AudioStreamGenerator_tfvr1"]
8+
9+
[sub_resource type="ShaderMaterial" id="ShaderMaterial_m4htd"]
10+
shader = ExtResource("3_vkk4n")
11+
12+
[node name="MicRecord" type="Control"]
13+
layout_mode = 3
14+
anchors_preset = 8
15+
anchor_left = 0.5
16+
anchor_top = 0.5
17+
anchor_right = 0.5
18+
anchor_bottom = 0.5
19+
offset_left = -320.0
20+
offset_top = -240.0
21+
offset_right = 254.0
22+
offset_bottom = 210.0
23+
grow_horizontal = 2
24+
grow_vertical = 2
25+
script = ExtResource("1")
26+
27+
[node name="AudioWav" type="AudioStreamPlayer" parent="."]
28+
autoplay = true
29+
30+
[node name="AudioMusic" type="AudioStreamPlayer" parent="."]
31+
stream = ExtResource("2")
32+
volume_db = -6.0
33+
34+
[node name="AudioGenerator" type="AudioStreamPlayer" parent="."]
35+
stream = SubResource("AudioStreamGenerator_tfvr1")
36+
bus = &"Generate"
37+
38+
[node name="Status" type="Label" parent="."]
39+
layout_mode = 1
40+
anchors_preset = 10
41+
anchor_right = 1.0
42+
offset_bottom = 26.0
43+
grow_horizontal = 2
44+
text = "Status: "
45+
horizontal_alignment = 1
46+
47+
[node name="MicrophoneOn" type="CheckBox" parent="."]
48+
layout_mode = 0
49+
offset_left = 5.0
50+
offset_top = 50.0
51+
offset_right = 154.0
52+
offset_bottom = 81.0
53+
tooltip_text = "Nothing works until you
54+
turn the microphone on."
55+
text = "Microphone On
56+
"
57+
58+
[node name="MicToGenerator" type="CheckBox" parent="."]
59+
layout_mode = 0
60+
offset_left = 156.0
61+
offset_top = 50.0
62+
offset_right = 315.0
63+
offset_bottom = 81.0
64+
tooltip_text = "Warning: this will cause feedback
65+
unless you use headphones"
66+
text = "Mic to Output
67+
"
68+
69+
[node name="InputMixRate" type="Label" parent="."]
70+
layout_mode = 0
71+
offset_left = 323.0
72+
offset_top = 56.0
73+
offset_right = 392.0
74+
offset_bottom = 79.0
75+
text = "Mix rate:"
76+
77+
[node name="SampleCount" type="Label" parent="."]
78+
layout_mode = 0
79+
offset_left = 460.0
80+
offset_top = 56.0
81+
offset_right = 542.0
82+
offset_bottom = 79.0
83+
text = "N-samples"
84+
85+
[node name="RecordButton" type="Button" parent="."]
86+
layout_mode = 0
87+
offset_left = 20.0
88+
offset_top = 105.0
89+
offset_right = 150.0
90+
offset_bottom = 145.0
91+
toggle_mode = true
92+
text = "Record"
93+
94+
[node name="PlayButton" type="Button" parent="."]
95+
layout_mode = 0
96+
offset_left = 167.0
97+
offset_top = 105.0
98+
offset_right = 297.0
99+
offset_bottom = 145.0
100+
disabled = true
101+
text = "Play"
102+
103+
[node name="PlayMusic" type="Button" parent="."]
104+
layout_mode = 0
105+
offset_left = 323.0
106+
offset_top = 105.0
107+
offset_right = 453.0
108+
offset_bottom = 145.0
109+
toggle_mode = true
110+
text = "Play Music"
111+
112+
[node name="MicTexture" type="ColorRect" parent="."]
113+
material = SubResource("ShaderMaterial_m4htd")
114+
layout_mode = 0
115+
offset_left = 20.0
116+
offset_top = 161.0
117+
offset_right = 548.0
118+
offset_bottom = 250.0
119+
120+
[node name="SaveButton" type="Button" parent="."]
121+
layout_mode = 0
122+
offset_left = 26.0
123+
offset_top = 295.0
124+
offset_right = 156.0
125+
offset_bottom = 335.0
126+
disabled = true
127+
text = "Save WAV To:"
128+
129+
[node name="Filename" type="LineEdit" parent="SaveButton"]
130+
layout_mode = 0
131+
offset_left = 150.0
132+
offset_right = 477.0
133+
offset_bottom = 40.0
134+
text = "user://record.wav"
135+
caret_blink = true
136+
137+
[node name="OpenUserFolderButton" type="Button" parent="."]
138+
layout_mode = 1
139+
offset_left = 28.0
140+
offset_top = 349.0
141+
offset_right = 191.0
142+
offset_bottom = 389.0
143+
text = "Open User Folder"
144+
145+
[node name="OptionTone" type="OptionButton" parent="."]
146+
layout_mode = 0
147+
offset_left = 474.0
148+
offset_top = 109.0
149+
offset_right = 568.0
150+
offset_bottom = 140.0
151+
selected = 0
152+
item_count = 5
153+
popup/item_0/text = "No tone"
154+
popup/item_0/id = 0
155+
popup/item_1/text = "200Hz"
156+
popup/item_1/id = 1
157+
popup/item_2/text = "400Hz"
158+
popup/item_2/id = 2
159+
popup/item_3/text = "1000Hz"
160+
popup/item_3/id = 3
161+
popup/item_4/text = "2000Hz"
162+
popup/item_4/id = 4
163+
164+
[connection signal="toggled" from="MicrophoneOn" to="." method="_on_microphone_on_toggled"]
165+
[connection signal="toggled" from="MicToGenerator" to="." method="_on_mic_to_generator_toggled"]
166+
[connection signal="toggled" from="RecordButton" to="." method="_on_record_button_toggled"]
167+
[connection signal="pressed" from="PlayButton" to="." method="_on_play_button_pressed"]
168+
[connection signal="toggled" from="PlayMusic" to="." method="_on_play_music_toggled"]
169+
[connection signal="pressed" from="SaveButton" to="." method="_on_save_button_pressed"]
170+
[connection signal="pressed" from="OpenUserFolderButton" to="." method="_on_open_user_folder_button_pressed"]
171+
[connection signal="item_selected" from="OptionTone" to="." method="_on_option_tone_item_selected"]

audio/mic_input/README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Audio Mic Record
2+
3+
This is an example showing how one can record audio from
4+
the microphone and later play it back or save it to a file.
5+
6+
With an addition on how to change the format, mix rate and
7+
stereo settings of the recording.
8+
9+
Language: GDScript
10+
11+
Renderer: Compatibility
12+
13+
Check out this demo on the asset library: https://godotengine.org/asset-library/asset/2760
14+
15+
## Screenshots
16+
17+
![Screenshot](screenshots/mic_record.png)
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[gd_resource type="AudioBusLayout" load_steps=2 format=3 uid="uid://tuxl6tvrr2dv"]
2+
3+
[sub_resource type="AudioEffectReverb" id="AudioEffectReverb_j3pel"]
4+
resource_name = "Reverb"
5+
6+
[resource]
7+
bus/1/name = &"Generate"
8+
bus/1/solo = false
9+
bus/1/mute = false
10+
bus/1/bypass_fx = false
11+
bus/1/volume_db = 0.0
12+
bus/1/send = &"Master"
13+
bus/1/effect/0/effect = SubResource("AudioEffectReverb_j3pel")
14+
bus/1/effect/0/enabled = false

audio/mic_input/icon.webp

714 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)