-
-
Notifications
You must be signed in to change notification settings - Fork 2k
AudioServer.get_input_frames() demo #1285
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
goatchurchprime
wants to merge
12
commits into
godotengine:master
Choose a base branch
from
goatchurchprime:gtch/mic_input
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
f6aacf9
add mic_input
goatchurchprime 5102826
v4.6
goatchurchprime c76baee
having trouble with the UI
goatchurchprime a0a3b8a
Speeding up stuff in here
goatchurchprime 24c7413
change readme
goatchurchprime 667b0d7
Update audio/mic_input/MicRecord.gd
goatchurchprime 76ddf70
Update audio/mic_input/MicRecord.gd
goatchurchprime 410b6d5
Update audio/mic_input/MicRecord.gd
goatchurchprime f515d60
Apply suggestions from code review
goatchurchprime c02c2b8
small changes
goatchurchprime f13c926
Update screenshot in README.md
goatchurchprime e5599a3
remove spaces
goatchurchprime File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Binary file not shown.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,255 @@ | ||
| extends Control | ||
|
|
||
| @onready var input_mix_rate: int = int(AudioServer.get_input_mix_rate()) | ||
| var audio_chunk_time: float = 0.02 | ||
| @onready var audio_sample_size: int = int(input_mix_rate * audio_chunk_time + 0.5) | ||
|
|
||
| var recording_start_time: float = 0.0 | ||
| var recording_buffer: Variant = null | ||
| var recording_time: float = 0.0 | ||
| var recording_index: int = 0 | ||
| const max_recording_time: float = 10.0 | ||
|
|
||
| var microphone_active: bool = false | ||
|
|
||
| var audio_sample_image: Image | ||
| var audio_sample_texture: ImageTexture | ||
| var generator_timestamp: float = 0.0 | ||
| var generator_freq: float = 0.0 | ||
|
|
||
| var guessed_generator_feedback_buffer_frames: int = 1 | ||
| @onready var pitch_shift_effect: AudioEffectPitchShift = AudioServer.get_bus_effect(1, 0) | ||
|
|
||
| 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") | ||
|
|
||
| print("Output mix rate: ", AudioServer.get_mix_rate()) | ||
| print("Project mix rate: ", ProjectSettings.get(&"audio/driver/mix_rate")) | ||
|
|
||
| if not AudioServer.has_method("get_input_frames"): | ||
| %Status.text = "**** Error: requires https://github.com/godotengine/godot/pull/113288 to work" | ||
| print(%Status.text) | ||
| set_process(false) | ||
| $MicrophoneOn.disabled = true | ||
| return | ||
|
|
||
| await get_tree().create_timer(0.5).timeout | ||
| %MicrophoneOn.button_pressed = true | ||
|
|
||
|
|
||
| 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, source_button: Variant) -> 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") | ||
| # Must enable Record Audio permission in on Android | ||
| @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) | ||
|
|
||
| var err: int = AudioServer.set_input_device_active(true) | ||
| if err != OK: | ||
| print("Input device error: ", err) | ||
| source_button.button_pressed = false | ||
| return | ||
|
|
||
| on_microphone_input_start() | ||
| print("Input buffer length frames: ", AudioServer.get_input_buffer_length_frames()) | ||
| print("Input buffer length seconds: ", AudioServer.get_input_buffer_length_frames() * 1.0 / input_mix_rate) | ||
| microphone_active = true | ||
|
|
||
| else: | ||
| AudioServer.set_input_device_active(false) | ||
| microphone_active = false | ||
|
|
||
| %OptionInput.disabled = microphone_active | ||
|
|
||
| func on_microphone_input_start() -> void: | ||
| input_mix_rate = int(AudioServer.get_input_mix_rate()) | ||
| audio_sample_size = int(input_mix_rate * audio_chunk_time + 0.5) | ||
| print("Input mix rate: ", input_mix_rate) | ||
| print("Sample size: ", audio_sample_size) | ||
| %InputMixRate.text = "Mix rate: %d" % input_mix_rate | ||
|
|
||
| $AudioGeneratorFeedback.stream.mix_rate = input_mix_rate | ||
| guessed_generator_feedback_buffer_frames = nearest_po2(int(input_mix_rate * $AudioGeneratorFeedback.stream.buffer_length)) | ||
| print("guessed_generator_feedback_buffer_frames ", guessed_generator_feedback_buffer_frames) | ||
|
|
||
| var blank_image: 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_mic_to_generator_toggled(toggled_on: bool) -> void: | ||
| $AudioGeneratorFeedback.playing = toggled_on | ||
|
|
||
| func _process_tone_generator() -> void: | ||
| var gplayback: AudioStreamGeneratorPlayback = $AudioGeneratorTone.get_stream_playback() | ||
| var gdt: float = 1.0 / $AudioGeneratorTone.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 _process(_delta: float) -> void: | ||
| while AudioServer.get_input_frames_available() >= audio_sample_size: | ||
| var audio_samples: PackedVector2Array = AudioServer.get_input_frames(audio_sample_size) | ||
| if not audio_samples: | ||
| break | ||
|
|
||
| if %RecordingLoop.button_pressed and recording_buffer: | ||
| audio_samples = recording_buffer[recording_index] | ||
| recording_index += 1 | ||
| if recording_index == len(recording_buffer): | ||
| recording_index = 0 | ||
|
|
||
| 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) | ||
|
|
||
| if %RecordButton.button_pressed: | ||
| recording_time = Time.get_ticks_msec() * 0.001 - recording_start_time | ||
| var nframes: int = len(recording_buffer) * audio_sample_size | ||
| recording_buffer.append(audio_samples) | ||
| %RecInfo.text = "Frames: %d Time: %.3f Frames/sec: %.0f" % [nframes, recording_time, nframes / recording_time] | ||
| if recording_time > 10.0: | ||
| %RecordButton.button_pressed = false | ||
|
|
||
| if %MicToGenerator.button_pressed: | ||
| $AudioGeneratorFeedback.get_stream_playback().push_buffer(audio_samples) | ||
|
|
||
| if %MicToGenerator.button_pressed: | ||
| if microphone_active: | ||
| adjust_feedback_speed() | ||
|
|
||
| if generator_freq != 0.0 and $AudioGeneratorTone.playing: | ||
| _process_tone_generator() | ||
|
|
||
| func adjust_feedback_speed() -> void: | ||
| var target_time_lag: float = %PlaybackLag.value - AudioServer.get_time_to_next_mix() | ||
| var buffer_time_lag: float = (guessed_generator_feedback_buffer_frames - $AudioGeneratorFeedback.get_stream_playback().get_frames_available()) * 1.0 /$AudioGeneratorFeedback.stream.mix_rate | ||
| %RealLagLabel.text = "Real lag: %.2f" % (buffer_time_lag + AudioServer.get_time_to_next_mix()) | ||
| var buffer_time_mismatch: float = buffer_time_lag - target_time_lag | ||
| if $AudioGeneratorFeedback.stream_paused: | ||
| if buffer_time_mismatch > 0.0: | ||
| print("Unpausing stream at target mismatch: ", buffer_time_mismatch) | ||
| $AudioGeneratorFeedback.stream_paused = false | ||
| $AudioGeneratorFeedback.pitch_scale = 1.0 | ||
| pitch_shift_effect.pitch_scale = 1.0 | ||
| elif buffer_time_mismatch < -0.1: | ||
| print("Pausing stream at target mismatch: ", buffer_time_mismatch) | ||
| $AudioGeneratorFeedback.stream_paused = true | ||
| elif $AudioGeneratorFeedback.pitch_scale != 1.0: | ||
| if buffer_time_mismatch < 0.0: | ||
| print("Set pitch=1 at target mismatch: ", buffer_time_mismatch) | ||
| $AudioGeneratorFeedback.pitch_scale = 1.0 | ||
| pitch_shift_effect.pitch_scale = 1.0 | ||
| elif buffer_time_mismatch > 0.1: | ||
| if $AudioGeneratorFeedback.pitch_scale == 1.0: | ||
| $AudioGeneratorFeedback.pitch_scale = 1.5 | ||
| print("Set pitch=", $AudioGeneratorFeedback.pitch_scale, " at target mismatch: ", buffer_time_mismatch) | ||
| pitch_shift_effect.pitch_scale = 0.667 | ||
|
|
||
| func _on_record_button_toggled(toggled_on: bool) -> void: | ||
| if toggled_on: | ||
| recording_buffer = [] | ||
| recording_start_time = Time.get_ticks_msec() * 0.001 | ||
| %RecordingLoop.disabled = true | ||
| %RecordButton.text = "Stop" | ||
| %Status.text = "Status: Recording..." | ||
| else: | ||
| recording_index = 0 | ||
| $AudioWav.stream = null | ||
| %RecordButton.text = "Record" | ||
| %Status.text = "" | ||
| %SaveButton.disabled = false | ||
| %PlayRecording.disabled = false | ||
| %RecordingLoop.disabled = false | ||
|
|
||
| func buffer_to_wav(buffer: Variant) -> AudioStreamWAV: | ||
| var recording_data: PackedByteArray | ||
| var data_size: int = 4 * audio_sample_size * len(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(buffer)): | ||
| for j in range(audio_sample_size): | ||
| var k: int = 44 + 4 * (i * audio_sample_size + j) | ||
| recording_data.encode_s16(k, clampi(buffer[i][j].x * 32768, -32768, 32767)) | ||
| recording_data.encode_s16(k + 2, clampi(buffer[i][j].y * 32768, -32768, 32767)) | ||
| print("Recording data size bytes: ", len(recording_data)) | ||
| return AudioStreamWAV.load_from_buffer(recording_data) | ||
|
|
||
| func _on_play_recording_pressed() -> void: | ||
| assert(recording_buffer != null) | ||
| if $AudioWav.stream == null: | ||
| $AudioWav.stream = buffer_to_wav(recording_buffer) | ||
| $AudioWav.stream.mix_rate = input_mix_rate | ||
| $AudioWav.seek(0.0) | ||
| $AudioWav.play() | ||
| %PlayRecording.text = "Playing Wav" | ||
|
|
||
| func _on_audio_wav_finished() -> void: | ||
| %PlayRecording.text = "Play Recording" | ||
|
|
||
| func _on_play_music_toggled(toggled_on: bool, source_button: Variant) -> void: | ||
| if toggled_on: | ||
| $AudioMusic.play() | ||
| source_button.text = "Stop Music" | ||
| else: | ||
| $AudioMusic.stop() | ||
| source_button.text = "Play Music" | ||
|
|
||
| func _on_save_button_pressed() -> void: | ||
| var save_path: String = %WavFilename.text | ||
| assert(recording_buffer != null) | ||
| if $AudioWav.stream == null: | ||
| $AudioWav.stream = buffer_to_wav(recording_buffer) | ||
| $AudioWav.stream.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://")) | ||
|
|
||
| # A chunk size of 20ms is will be 8 wavelengths at 400Hz per chunk, | ||
| # where the wavelength will be 343/400=0.8575m long. | ||
| # Use this to plot the response of a stereo microphone. | ||
| func _on_option_tone_item_selected(index: int) -> void: | ||
| if index != 0: | ||
| $AudioGeneratorTone.playing = true | ||
| generator_freq = int(%OptionTone.get_item_text(index)) | ||
| else: | ||
| $AudioGeneratorTone.playing = false | ||
| generator_freq = 0.0 | ||
|
|
||
| func _on_recording_loop_toggled(toggled_on: bool) -> void: | ||
| %RecordButton.disabled = toggled_on | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| uid://dbbfvbf6ronrp |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| uid://cl4x5tyii4r6q |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.