Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
2995f45
Add a preview of the microphone waveform in stereo from Input.get_mic…
goatchurchprime Mar 2, 2025
06cd936
Update README.md
goatchurchprime May 14, 2025
f3cf5b1
Update README.md
goatchurchprime May 14, 2025
cb6a44c
attempt to handle case when MicrophoneServer does not exist
goatchurchprime Jul 19, 2025
0b74281
microphone input option now
goatchurchprime Jul 25, 2025
17b2e1b
iinput audio permissions for the android case
goatchurchprime Jul 31, 2025
3f49ac5
text formatting
goatchurchprime Jul 31, 2025
46c6b1e
capability to set the output device too
goatchurchprime Aug 1, 2025
de15299
Merge branch 'master' into gtch/micplotfeed
goatchurchprime Aug 3, 2025
2524436
upgrade to 4.6
goatchurchprime Sep 29, 2025
8fde065
add in microphone stream bus that you can turn on and off in remote d…
goatchurchprime Sep 29, 2025
f9c8654
change some formatting and spaces
goatchurchprime Oct 11, 2025
411d423
move to mic_feed directory
goatchurchprime Oct 11, 2025
4af9068
more formatting and correct PR referenced
goatchurchprime Oct 11, 2025
938303d
Merge branch 'godotengine:master' into gtch/micplotfeed
goatchurchprime Oct 11, 2025
8b85a86
remove spaces from readme
goatchurchprime Oct 11, 2025
f0f9820
Update audio/mic_feed/MicRecord.gdshader
goatchurchprime Oct 13, 2025
b00704d
Update audio/mic_feed/MicRecord.gd
goatchurchprime Oct 13, 2025
7d52df2
Update audio/mic_feed/MicRecord.gd
goatchurchprime Oct 13, 2025
ec7320b
Update audio/mic_feed/MicRecord.gd
goatchurchprime Oct 13, 2025
d418779
Update audio/mic_feed/MicRecord.gd
goatchurchprime Oct 13, 2025
7bc9b76
Update audio/mic_feed/MicRecord.gd
goatchurchprime Oct 13, 2025
b359dbb
Update audio/mic_feed/MicRecord.gd
goatchurchprime Oct 13, 2025
e44ff0d
Update audio/mic_feed/MicRecord.gd
goatchurchprime Oct 13, 2025
5405b64
Apply suggestions from code review
goatchurchprime Oct 13, 2025
b671074
fix missing underscores
goatchurchprime Oct 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added audio/mic_feed/Intro.ogg
Binary file not shown.
19 changes: 19 additions & 0 deletions audio/mic_feed/Intro.ogg.import
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[remap]

importer="oggvorbisstr"
type="AudioStreamOggVorbis"
uid="uid://c2re52petqrvx"
path="res://.godot/imported/Intro.ogg-dfe75727d0e47692e220adf97ddb7ad9.oggvorbisstr"

[deps]

source_file="res://Intro.ogg"
dest_files=["res://.godot/imported/Intro.ogg-dfe75727d0e47692e220adf97ddb7ad9.oggvorbisstr"]

[params]

loop=true
loop_offset=0
bpm=0
beat_count=0
bar_beats=4
189 changes: 189 additions & 0 deletions audio/mic_feed/MicRecord.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
extends Control

var wav_recording: AudioStreamWAV
var input_mix_rate: int = 44100
var audio_chunk_size_ms: int = 20
var audio_sample_size: int = 882

var total_samples: int = 0
var sample_duration: float = 0.0
var recording_buffer: Variant = null

var audio_sample_image: Image
var audio_sample_texture: ImageTexture
var generator_timestamp: float = 0.0
var generator_freq: float = 0.0

var microphone_feed = null


func _ready() -> void:
for d in AudioServer.get_input_device_list():
$OptionInput.add_item(d)
assert($OptionInput.get_item_text($OptionInput.selected) == "Default")

for d in AudioServer.get_output_device_list():
$OptionOutput.add_item(d)
assert($OptionOutput.get_item_text($OptionOutput.selected) == "Default")

input_mix_rate = int(AudioServer.get_input_mix_rate())
print("Input mix rate: ", input_mix_rate)
print("Output mix rate: ", AudioServer.get_mix_rate())
print("Project mix rate: ", ProjectSettings.get(&"audio/driver/mix_rate"))

if Engine.has_singleton("MicrophoneServer"):
microphone_feed = Engine.get_singleton("MicrophoneServer").get_feed(0)
if not microphone_feed:
$Status.text = "**** Error: requires PR#108773 to work"
print($Status.text)
set_process(false)
$MicrophoneOn.disabled = true

$InputMixRate.text = "Mix rate: %d" % input_mix_rate
audio_sample_size = int(audio_chunk_size_ms * input_mix_rate / 1000.0)

var blank_image: PackedVector2Array = PackedVector2Array()
blank_image.resize(audio_sample_size)
audio_sample_image = Image.create_from_data(audio_sample_size, 1, false, Image.FORMAT_RGF, blank_image.to_byte_array())
audio_sample_texture = ImageTexture.create_from_image(audio_sample_image)
$MicTexture.material.set_shader_parameter(&"audiosample", audio_sample_texture)


func _on_option_input_item_selected(index: int) -> void:
var input_device: String = $OptionInput.get_item_text(index)
print("Set input device: ", input_device)
AudioServer.set_input_device(input_device)


func _on_option_output_item_selected(index: int) -> void:
var output_device: String = $OptionOutput.get_item_text(index)
print("Set output device: ", output_device)
AudioServer.set_output_device(output_device)


func _on_microphone_on_toggled(toggled_on: bool) -> void:
if toggled_on:
if OS.get_name() == "Android" and not OS.request_permission("android.permission.RECORD_AUDIO"):
print("Waiting for user response after requesting audio permissions")
# yuou also need to enabled Record Audio in the android export settings
@warning_ignore("untyped_declaration")
var x = await get_tree().on_request_permissions_result
var permission: String = x[0]
var granted: bool = x[1]
assert(permission == "android.permission.RECORD_AUDIO")
print("Audio permission granted ", granted)

if not microphone_feed.is_active():
microphone_feed.set_active(true)
total_samples = 0
sample_duration = 0.0
print("Input buffer length frames: ", microphone_feed.get_buffer_length_frames())
print("Input buffer length seconds: ", microphone_feed.get_buffer_length_frames() * 1.0 / input_mix_rate)
else:
microphone_feed.set_active(false)


func _on_mic_to_generator_toggled(toggled_on: bool) -> void:
if toggled_on:
$AudioGenerator.stream.mix_rate = input_mix_rate
$AudioGenerator.playing = toggled_on


func _process(delta: float) -> void:
sample_duration += delta
while microphone_feed.get_frames_available() >= audio_sample_size:
var audio_samples: PackedVector2Array = microphone_feed.get_frames(audio_sample_size)
if audio_samples:
audio_sample_image.set_data(audio_sample_size, 1, false, Image.FORMAT_RGF, audio_samples.to_byte_array())
audio_sample_texture.update(audio_sample_image)
total_samples += 1
$SampleCount.text = "%.0f samples/sec" % (total_samples * audio_sample_size / sample_duration)
if recording_buffer != null:
recording_buffer.append(audio_samples)
if $MicToGenerator.button_pressed:
$AudioGenerator.get_stream_playback().push_buffer(audio_samples)
if generator_freq != 0.0:
var gplayback: AudioStreamGeneratorPlayback = $AudioGenerator.get_stream_playback()
var gdt: float = 1.0 / $AudioGenerator.stream.mix_rate
for i in range(gplayback.get_frames_available()):
var a: float = 0.5 * sin(generator_timestamp * generator_freq * TAU)
gplayback.push_frame(Vector2(a, a))
generator_timestamp += gdt


func _on_record_button_toggled(toggled_on: bool) -> void:
total_samples = 0
sample_duration = 0.0
if toggled_on:
$PlayButton.disabled = true
$SaveButton.disabled = true
recording_buffer = []
$RecordButton.text = "Stop"
$Status.text = "Status: Recording..."

else:
$PlayButton.disabled = false
$SaveButton.disabled = false
var recording_data: PackedByteArray = PackedByteArray()
var data_size: int = 4 * audio_sample_size * len(recording_buffer)
recording_data.resize(44 + data_size)
recording_data.encode_u32(0, 0x46464952) # RIFF
recording_data.encode_u32(4, len(recording_data) - 8)
recording_data.encode_u32(8, 0x45564157) # WAVE
recording_data.encode_u32(12, 0x20746D66) # 'fmt '
recording_data.encode_u32(16, 16)
recording_data.encode_u16(20, 1)
recording_data.encode_u16(22, 2)
recording_data.encode_u32(24, input_mix_rate)
recording_data.encode_u32(28, input_mix_rate * 4) # *16*2/8
recording_data.encode_u16(32, 4) # 16*2/8
recording_data.encode_u16(34, 16)
recording_data.encode_u32(36, 0x61746164) # 'data'
recording_data.encode_u32(40, data_size)
for i in range(len(recording_buffer)):
for j in range(audio_sample_size):
var k: int = 44 + 4 * (i * audio_sample_size + j)
recording_data.encode_s16(k, clampi(recording_buffer[i][j].x * 32768, -32768, 32767))
recording_data.encode_s16(k + 2, clampi(recording_buffer[i][j].y * 32768, -32768, 32767))
wav_recording = AudioStreamWAV.load_from_buffer(recording_data)

$RecordButton.text = "Record"
$Status.text = ""
recording_buffer = null


func _on_play_button_pressed() -> void:
print_rich("\n[b]Playing recording:[/b] %s" % wav_recording)
$AudioWav.stream = wav_recording
$AudioWav.play()


func _on_play_music_toggled(toggled_on: bool) -> void:
if toggled_on:
$AudioMusic.play()
$PlayMusic.text = "Stop Music"
else:
$AudioMusic.stop()
$PlayMusic.text = "Play Music"


func _on_save_button_pressed() -> void:
var save_path: String = $SaveButton/Filename.text
wav_recording.save_to_wav(save_path)
$Status.text = "Status: Saved WAV file to: %s\n(%s)" % [save_path, ProjectSettings.globalize_path(save_path)]


func _on_open_user_folder_button_pressed() -> void:
OS.shell_open(ProjectSettings.globalize_path("user://"))


# 400Hz frequency can be used (from another device) to probe a stereo microphone
# response due to where there should be 8 wavelengths in the space of 20ms (2.5ms per wave).
# The wavelength is then 343/400=0.8575m long.
func _on_option_tone_item_selected(index: int) -> void:
if index != 0:
$AudioGenerator.playing = true
if not $MicToGenerator.button_pressed and not $PlayMusic.button_pressed:
generator_freq = int($OptionTone.get_item_text(index))
else:
generator_freq = 0.0
1 change: 1 addition & 0 deletions audio/mic_feed/MicRecord.gd.uid
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
uid://dbbfvbf6ronrp
26 changes: 26 additions & 0 deletions audio/mic_feed/MicRecord.gdshader
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
shader_type canvas_item;
render_mode blend_mix;

uniform sampler2D audiosample : repeat_enable;
const float cfac = 4.0;
const float mfac = 2.0;
const float mdisp = 0.166667;
const float mthick = 0.05;
const float mtiltfac = 0.125;

void fragment() {
vec4 b = texture(audiosample, UV + vec2(-(UV.y - 0.5) * mtiltfac, 0.0));
vec4 c = texture(audiosample, UV + vec2((UV.y - 0.5) * mtiltfac, 0.0));
float s = (b.r + c.g) / 2.0;
COLOR = vec4(0.1 + max(s, 0.0) * cfac, 0.1, 0.1 + max(-s, 0.0) * cfac, 1.0);

vec4 a = texture(audiosample, UV);
float dr = abs(UV.y * 2.0 - 1.0 - (a.r + mdisp) * mfac);
float dg = abs(UV.y * 2.0 - 1.0 - (a.g - mdisp) * mfac);

if (dg < mthick) {
COLOR = vec4(1.0, 1.0, 0.9, 1.0);
} else if (dr < mthick) {
COLOR = vec4(0.8, 0.8, 0.9, 1.0);
}
}
1 change: 1 addition & 0 deletions audio/mic_feed/MicRecord.gdshader.uid
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
uid://cl4x5tyii4r6q
Loading