@@ -3,18 +3,22 @@ extends Control
33## Example scene demonstrating the 24-bit high-fidelity recording workflow.
44## This script handles UI updates, recording states, and file management.
55
6+ # --- Constants ---
7+ const ANDROID_RECORDINGS_SUBDIR = "recordings"
8+
69# --- UI References ---
710@onready var start_and_stop_button : Button = % StartAndStopButton
811@onready var play_stop_button : Button = % PlayStopButton
912@onready var save_button : Button = % SaveButton
1013@onready var format_option_button : OptionButton = % OptionButton
1114@onready var volume_progress_bar : ProgressBar = % ProgressBar
15+
1216@onready var input_hz_label : Label = % InputHz
1317@onready var output_hz_label : Label = % OutputHz
18+
1419@onready var input_devices_option : OptionButton = % InputDevicesOption
1520@onready var output_devices_option : OptionButton = % OutputDevicesOption
1621
17-
1822# --- Audio References ---
1923@onready var recorder : DirectAudioInputRecorder = $ DirectAudioInputRecorder
2024@onready var player_24bit : AudioStreamPlayerWav24B = $ AudioStreamPlayerWav24B
@@ -31,13 +35,19 @@ var _last_recorded_resource: AudioStreamWAV
3135
3236func _ready () -> void :
3337
34- if OS .get_name () == "Android" :
35- OS .request_permissions ()
36-
38+ _initialize_platform_permissions ()
3739 _setup_ui_initial_state ()
3840 _populate_format_options ()
3941 _load_devices ()
4042
43+ func _process (delta : float ) -> void :
44+ _update_volume_meter_logic (delta )
45+
46+ func _initialize_platform_permissions () -> void :
47+ if OS .get_name () == "Android" :
48+ # Requesting multiple permissions often needed for audio/storage
49+ OS .request_permissions ()
50+
4151func _setup_ui_initial_state () -> void :
4252 input_hz_label .text = str (AudioServer .get_input_mix_rate ())
4353 output_hz_label .text = str (AudioServer .get_mix_rate ())
@@ -64,25 +74,16 @@ func _populate_format_options() -> void:
6474
6575 format_option_button .selected = recorder .format
6676
67- # --- Main Loop ---
77+ # --- Logic & Processing ---
6878
69- func _process (delta : float ) -> void :
70- # Update the peak meter in real-time
71- var peak_db = recorder .get_peak_volume_db ().x
72- _update_volume_meter (peak_db , delta )
73-
74- # --- UI Logic ---
75-
76- func _update_volume_meter (peak_db : float , delta : float ) -> void :
77- var energy = db_to_linear (peak_db )
78- var current_val = volume_progress_bar .value
79+ func _update_volume_meter_logic (delta : float ) -> void :
80+ var peak_db : float = recorder .get_peak_volume_db ().x
81+ var energy := db_to_linear (peak_db )
82+ var current_val := volume_progress_bar .value
7983
80- # Instant rise, smooth fall (classic peak meter behavior)
81- # if energy > current_val:
82- # volume_progress_bar.value = energy
83- # else:
84+ # Smooth fall-off for the meter visualization
8485 volume_progress_bar .value = lerp (current_val , energy , meter_smooth_speed * delta )
85-
86+
8687# --- Signal Callbacks: Recorder ---
8788
8889func _on_start_and_stop_button_pressed () -> void :
@@ -94,19 +95,23 @@ func _on_start_and_stop_button_pressed() -> void:
9495func _on_recorder_on_recording_start () -> void :
9596 start_and_stop_button .text = "Stop Recording"
9697 start_and_stop_button .modulate = Color .RED
97- play_stop_button .disabled = true
98- save_button .disabled = true
98+ _toggle_playback_controls (true )
9999
100100func _on_recorder_on_recording_end () -> void :
101101 start_and_stop_button .text = "Record"
102102 start_and_stop_button .modulate = Color .WHITE
103103
104- # Generate the 24-bit resource immediately after recording
104+ # Cache recorded resources
105105 _last_recorded_resource24b = recorder .get_recording_as_wav24b ()
106106 _last_recorded_resource = recorder .get_recording ()
107107
108- play_stop_button .disabled = false
109- save_button .disabled = false
108+ _toggle_playback_controls (false )
109+
110+ func _toggle_playback_controls (is_recording : bool ) -> void :
111+ play_stop_button .disabled = is_recording
112+ save_button .disabled = is_recording
113+
114+ # --- Signal Callbacks: Playback ---
110115
111116# --- Signal Callbacks: Playback ---
112117
@@ -117,7 +122,7 @@ func _on_play_stop_button_pressed() -> void:
117122 _stop_playback ()
118123
119124func _start_playback () -> void :
120- if _last_recorded_resource :
125+ if _last_recorded_resource24b :
121126 player_24bit .play_24bit (_last_recorded_resource24b )
122127 play_stop_button .text = "Stop Playback"
123128 _is_playing = true
@@ -130,57 +135,62 @@ func _stop_playback() -> void:
130135func _on_player_24bit_finished () -> void :
131136 _stop_playback ()
132137
133- # --- Signal Callbacks: File Management ---
138+ # --- File Management & Android Workaround ---
134139
135140func _on_save_button_pressed () -> void :
136141 if not _last_recorded_resource :
142+ push_warning ("No recording available to save." )
137143 return
138144
139- var file_name = "recording_%s .wav" % _generate_uuid ().substr (0 , 8 )
145+ var file_name := "rec_%s .wav" % _generate_short_id ()
146+ var temp_path := "user://" + file_name
147+ var save_error : Error
140148
141- var file_path = "user://" + file_name
149+ # 1. High-speed internal save (Direct IO)
150+ if recorder .format == 2 : # 24-bit format index
151+ save_error = _last_recorded_resource24b .save_to_wav (temp_path )
152+ else :
153+ save_error = _last_recorded_resource .save_to_wav (temp_path )
142154
155+ if save_error != OK :
156+ push_error ("Failed to save temporary file. Error: " , save_error )
157+ return
158+
159+ # 2. Android Scoped Storage Workaround
143160 if OS .get_name () == "Android" :
144- file_path = OS .get_system_dir (OS .SYSTEM_DIR_DOCUMENTS ) + "/recordings/" + file_name
161+ _export_to_android_documents (temp_path , file_name )
162+ else :
163+ print ("File saved to user data: " , ProjectSettings .globalize_path (temp_path ))
164+
165+ # Moves a file from internal storage to the public Documents folder on Android.
166+ func _export_to_android_documents (source_path : String , file_name : String ) -> void :
167+ var docs_dir := OS .get_system_dir (OS .SYSTEM_DIR_DOCUMENTS ).path_join (ANDROID_RECORDINGS_SUBDIR )
145168
146- var error
169+ # Ensure directory exists
170+ if not DirAccess .dir_exists_absolute (docs_dir ):
171+ DirAccess .make_dir_recursive_absolute (docs_dir )
147172
148- if (recorder .format == 2 ):
149- error = _last_recorded_resource24b .save_to_wav (file_path )
150- else :
151- error = _last_recorded_resource .save_to_wav (file_path )
173+ var destination_path := docs_dir .path_join (file_name )
174+ var dir := DirAccess .open ("user://" )
152175
153- if error == OK :
154- print ("Success: %s audio saved at: " % recorder .available_formats ()[recorder .format ], ProjectSettings .globalize_path (file_path ))
176+ if dir and dir .copy (source_path , destination_path ) == OK :
177+ print ("Successfully exported to Documents: " , destination_path )
178+ dir .remove (source_path ) # Clean up temp file
155179 else :
156- push_error ("Failed to save audio. Error code : " , error )
180+ push_error ("Android Export Failed. Path : " , destination_path )
157181
158182func _on_option_button_item_selected (index : int ) -> void :
159183 recorder .format = index
160184
161185# --- Helpers ---
162186
163- ## Generates a unique identifier for file naming.
164- func _generate_uuid () -> String :
165- var crypto = Crypto .new ()
166- var b = crypto .generate_random_bytes (16 )
167-
168- # UUID v4 specific bit manipulation
169- b [6 ] = (b [6 ] & 0x0f ) | 0x40
170- b [8 ] = (b [8 ] & 0x3f ) | 0x80
171-
172- var hex = b .hex_encode ()
173- return "%s -%s -%s -%s -%s " % [
174- hex .substr (0 , 8 ), hex .substr (8 , 4 ),
175- hex .substr (12 , 4 ), hex .substr (16 , 4 ),
176- hex .substr (20 , 12 )
177- ]
187+ ## Generates a short random ID using crypto-safe bytes.
188+ func _generate_short_id () -> String :
189+ var crypto := Crypto .new ()
190+ return crypto .generate_random_bytes (4 ).hex_encode ()
178191
179192func _on_input_devices_option_item_selected (index : int ) -> void :
180- var s = input_devices_option .get_item_text (index )
181- AudioServer .input_device = s
182-
193+ AudioServer .input_device = input_devices_option .get_item_text (index )
183194
184195func _on_output_devices_option_item_selected (index : int ) -> void :
185- var e = output_devices_option .get_item_text (index )
186- AudioServer .output_device = e
196+ AudioServer .output_device = output_devices_option .get_item_text (index )
0 commit comments