-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Demo of MicrophoneFeed with shader showing the captured waveform in stereo #1264
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
Draft
goatchurchprime
wants to merge
26
commits into
godotengine:master
Choose a base branch
from
goatchurchprime:gtch/micplotfeed
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.
Draft
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 06cd936
Update README.md
goatchurchprime f3cf5b1
Update README.md
goatchurchprime cb6a44c
attempt to handle case when MicrophoneServer does not exist
goatchurchprime 0b74281
microphone input option now
goatchurchprime 17b2e1b
iinput audio permissions for the android case
goatchurchprime 3f49ac5
text formatting
goatchurchprime 46c6b1e
capability to set the output device too
goatchurchprime de15299
Merge branch 'master' into gtch/micplotfeed
goatchurchprime 2524436
upgrade to 4.6
goatchurchprime 8fde065
add in microphone stream bus that you can turn on and off in remote d…
goatchurchprime f9c8654
change some formatting and spaces
goatchurchprime 411d423
move to mic_feed directory
goatchurchprime 4af9068
more formatting and correct PR referenced
goatchurchprime 938303d
Merge branch 'godotengine:master' into gtch/micplotfeed
goatchurchprime 8b85a86
remove spaces from readme
goatchurchprime f0f9820
Update audio/mic_feed/MicRecord.gdshader
goatchurchprime b00704d
Update audio/mic_feed/MicRecord.gd
goatchurchprime 7d52df2
Update audio/mic_feed/MicRecord.gd
goatchurchprime ec7320b
Update audio/mic_feed/MicRecord.gd
goatchurchprime d418779
Update audio/mic_feed/MicRecord.gd
goatchurchprime 7bc9b76
Update audio/mic_feed/MicRecord.gd
goatchurchprime b359dbb
Update audio/mic_feed/MicRecord.gd
goatchurchprime e44ff0d
Update audio/mic_feed/MicRecord.gd
goatchurchprime 5405b64
Apply suggestions from code review
goatchurchprime b671074
fix missing underscores
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
Some comments aren't visible on the classic Files Changed page.
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,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() | ||
goatchurchprime marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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: | ||
goatchurchprime marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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: | ||
goatchurchprime marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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: | ||
goatchurchprime marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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: | ||
goatchurchprime marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| if toggled_on: | ||
| $AudioGenerator.stream.mix_rate = input_mix_rate | ||
| $AudioGenerator.playing = toggled_on | ||
|
|
||
|
|
||
| func _process(delta: float) -> void: | ||
goatchurchprime marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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: | ||
goatchurchprime marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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: | ||
goatchurchprime marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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: | ||
goatchurchprime marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| if toggled_on: | ||
| $AudioMusic.play() | ||
| $PlayMusic.text = "Stop Music" | ||
| else: | ||
| $AudioMusic.stop() | ||
| $PlayMusic.text = "Play Music" | ||
|
|
||
|
|
||
| func _on_save_button_pressed() -> void: | ||
goatchurchprime marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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: | ||
goatchurchprime marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| OS.shell_open(ProjectSettings.globalize_path("user://")) | ||
|
|
||
|
|
||
| # 400Hz frequency can be used (from another device) to probe a stereo microphone | ||
goatchurchprime marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| # 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 | ||
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.