commit 51c7cca0bfcbb15bb6ed07b7143f7c3bc241360b Author: pietru Date: Mon Nov 11 15:17:55 2024 +0100 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..80d224e --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +records/ + +# Godot 4+ specific ignores +.godot/ + +# Godot-specific ignores +.import/ +export.cfg +export_presets.cfg + +# Imported translations (automatically generated from CSV files) +*.translation + +# Mono-specific ignores +.mono/ +data_*/ +mono_crash.*.json \ No newline at end of file diff --git a/MicRecord.gd b/MicRecord.gd new file mode 100644 index 0000000..8c9c603 --- /dev/null +++ b/MicRecord.gd @@ -0,0 +1,239 @@ +extends Control + +var effect: AudioEffectRecord +var recording: AudioStreamWAV + +var stereo := true +var mix_rate := 44100 # This is the default mix rate on recordings. +var format := AudioStreamWAV.FORMAT_16_BITS # This is the default format on recordings. + +# 2147483647 +var noise_max_lvl := 2000000#10000000 + +func _ready() -> void: + var idx := AudioServer.get_bus_index("Record") + effect = AudioServer.get_bus_effect(idx, 2) + %Controls.visible=false + + +#region Record stuff +func _on_load_words_pressed() -> void: + if not FileAccess.file_exists(%SavePath.text.path_join("list.json")): + return + var txt = FileAccess.get_file_as_string(%SavePath.text.path_join("list.json")) + words=JSON.parse_string(txt) + for t in words: + %WordList.add_item(t) + +func save_words(): + var fl = FileAccess.open(%SavePath.text.path_join("list.json"),FileAccess.WRITE) + var txt = JSON.stringify(words,"\t") + fl.store_string(txt) + fl.close() + +func _process(delta: float) -> void: + if Input.is_action_just_pressed("ui_right"): + if last_item_index!=null: + run_swap_code(last_item_index+1) + if Input.is_action_just_pressed("ui_left"): + if last_item_index!=null: + run_swap_code(last_item_index-1) + +func run_swap_code(new_index): + if effect.is_recording_active(): + start_new_record() + await get_tree().process_frame + _on_save_button_pressed() + if words.size()<=new_index: + new_index=0 + if 0>new_index: + new_index=words.size()-1 + %WordList.select(new_index) + %WordList.ensure_current_is_visible() + _on_item_list_item_selected(new_index) + +var words :Array= [] +func _on_add_word_btn_pressed() -> void: + if (%SoundName.text=="" or words.has(%SoundName.text)): + return + %WordList.add_item(%SoundName.text) + words.append(%SoundName.text) + %SoundName.text="" + save_words() + +var last_item_index :int = -1 +var last_item_text := "" +var last_item_path := "" +func _on_item_list_item_selected(index: int) -> void: + %Controls.visible=true + last_item_text=%WordList.get_item_text(index) + %WordName.text=last_item_text + last_item_index=index + last_item_path=%SavePath.text + last_item_path=last_item_path.path_join(last_item_text+".wav") + if FileAccess.file_exists(last_item_path): + recording=load(last_item_path) + +func _on_record_button_pressed() -> void: + if effect.is_recording_active(): + recording = effect.get_recording() + %PlayButton.disabled = false + %SaveButton.disabled = false + effect.set_recording_active(false) + recording.set_mix_rate(mix_rate) + recording.set_format(format) + recording.set_stereo(stereo) + remove_noise() + %RecordButton.text = "Record" + %Status.text = "" + else: + %PlayButton.disabled = true + %SaveButton.disabled = true + effect.set_recording_active(true) + %RecordButton.text = "Stop" + %Status.text = "Status: Recording..." + +func start_new_record(): + recording = effect.get_recording() + %PlayButton.disabled = false + %SaveButton.disabled = false + if recording!=null: + effect.set_recording_active(false) + recording.set_mix_rate(mix_rate) + recording.set_format(format) + recording.set_stereo(stereo) + remove_noise() + await get_tree().process_frame + effect.set_recording_active(true) + +func remove_noise(): + return + #var data := recording.data.to_int32_array() + #var new_data := PackedInt32Array() + # + #var offset = 0 + #var uc + #for i in data: + #if i < noise_max_lvl: + #i=0 + #new_data.append(i) + #recording.data=new_data.to_byte_array() + + +func _on_play_button_pressed() -> void: + print_rich("\n[b]Playing recording:[/b] %s" % recording) + print_rich("[b]Format:[/b] %s" % ("8-bit uncompressed" if recording.format == 0 else "16-bit uncompressed" if recording.format == 1 else "IMA ADPCM compressed")) + print_rich("[b]Mix rate:[/b] %s Hz" % recording.mix_rate) + print_rich("[b]Stereo:[/b] %s" % ("Yes" if recording.stereo else "No")) + var data := recording.get_data() + print_rich("[b]Size:[/b] %s bytes" % data.size()) + $AudioStreamPlayer.stream = recording + $AudioStreamPlayer.play() + + +func _on_save_button_pressed() -> void: + if recording==null: + return + if not DirAccess.dir_exists_absolute(%SavePath.text): + DirAccess.make_dir_absolute(%SavePath.text) + if FileAccess.file_exists(last_item_path): + DirAccess.remove_absolute(last_item_path) + recording.save_to_wav(last_item_path) + %Status.text = "Status: Saved WAV file to: %s" % [last_item_text+".wav"] + + +func _on_delete_button_pressed() -> void: + if (last_item_index==null): + return + if DirAccess.dir_exists_absolute(%SavePath.text) and FileAccess.file_exists(last_item_path): + DirAccess.remove_absolute(last_item_path) + words.erase(last_item_text) + last_item_index=-1 + last_item_text="" + last_item_path="" + %Controls.visible=false + save_words() +#endregion + + +#region PlanText stuff +func _on_load_text_btn_pressed() -> void: + if not FileAccess.file_exists(%WordsTXTPath.text): + return + var txt = FileAccess.get_file_as_string(%WordsTXTPath.text) + %TextWordsEdit.text=txt + +func _on_add_words_btn_pressed() -> void: + var txt :String= %TextWordsEdit.text + txt=txt.replace("\n"," ").replace("\t"," ") + var wrds = txt.split(" ",false) + for wrd in wrds: + if !words.has(wrd): + %WordList.add_item(wrd) + words.append(wrd) + +var play = false +func _on_play_text_btn_pressed() -> void: + play=!play + + if play: + var txt :String= %TextWordsEdit.text + txt=txt.replace("\n"," ").replace("\t"," ") + var wrds = txt.split(" ",false) + for wrd in wrds: + var path = %SavePath.text.path_join(wrd+".wav") + if FileAccess.file_exists(path): + var stream = load(path) + + $AudioStreamPlayer2.stream = stream + $AudioStreamPlayer2.play() + await $AudioStreamPlayer2.finished + play=false + else: + $AudioStreamPlayer2.stop() +#endregion + + + + + +#region Aditional stuff +func _on_mix_rate_option_button_item_selected(index: int) -> void: + match index: + 0: + mix_rate = 11025 + 1: + mix_rate = 16000 + 2: + mix_rate = 22050 + 3: + mix_rate = 32000 + 4: + mix_rate = 44100 + 5: + mix_rate = 48000 + if recording != null: + recording.set_mix_rate(mix_rate) + + +func _on_format_option_button_item_selected(index: int) -> void: + match index: + 0: + format = AudioStreamWAV.FORMAT_8_BITS + 1: + format = AudioStreamWAV.FORMAT_16_BITS + 2: + format = AudioStreamWAV.FORMAT_IMA_ADPCM + if recording != null: + recording.set_format(format) + + +func _on_stereo_check_button_toggled(button_pressed: bool) -> void: + stereo = button_pressed + if recording != null: + recording.set_stereo(stereo) + + +func _on_open_user_folder_button_pressed() -> void: + OS.shell_open(ProjectSettings.globalize_path(%SavePath.text)) +#endregion diff --git a/MicRecord.tscn b/MicRecord.tscn new file mode 100644 index 0000000..476a93d --- /dev/null +++ b/MicRecord.tscn @@ -0,0 +1,301 @@ +[gd_scene load_steps=4 format=3 uid="uid://dvjlkpjvjxn0h"] + +[ext_resource type="Script" path="res://MicRecord.gd" id="1"] + +[sub_resource type="AudioStreamMicrophone" id="1"] + +[sub_resource type="LabelSettings" id="LabelSettings_7vfcs"] +font_size = 64 + +[node name="Scene" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1") + +[node name="AudioStreamRecord" type="AudioStreamPlayer" parent="."] +stream = SubResource("1") +autoplay = true +bus = &"Record" + +[node name="AudioStreamPlayer" type="AudioStreamPlayer" parent="."] + +[node name="AudioStreamPlayer2" type="AudioStreamPlayer" parent="."] + +[node name="TabContainer" type="TabContainer" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +current_tab = 1 + +[node name="Record" type="VBoxContainer" parent="TabContainer"] +visible = false +layout_mode = 2 +metadata/_tab_index = 0 + +[node name="HBoxContainer" type="HBoxContainer" parent="TabContainer/Record"] +layout_mode = 2 + +[node name="Status" type="Label" parent="TabContainer/Record/HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +horizontal_alignment = 1 + +[node name="VSeparator" type="VSeparator" parent="TabContainer/Record/HBoxContainer"] +self_modulate = Color(1, 1, 1, 0) +custom_minimum_size = Vector2(20, 0) +layout_mode = 2 + +[node name="FormatLabel" type="Label" parent="TabContainer/Record/HBoxContainer"] +layout_mode = 2 +text = "Format:" + +[node name="FormatOptionButton" type="OptionButton" parent="TabContainer/Record/HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +selected = 1 +item_count = 3 +popup/item_0/text = "8-bit Uncompressed" +popup/item_1/text = "16-bit Uncompressed" +popup/item_1/id = 1 +popup/item_2/text = "IMA ADPCM Compression" +popup/item_2/id = 2 + +[node name="VSeparator2" type="VSeparator" parent="TabContainer/Record/HBoxContainer"] +self_modulate = Color(1, 1, 1, 0) +custom_minimum_size = Vector2(20, 0) +layout_mode = 2 + +[node name="MixRateLabel" type="Label" parent="TabContainer/Record/HBoxContainer"] +layout_mode = 2 +text = "Mix rate:" + +[node name="MixRateOptionButton" type="OptionButton" parent="TabContainer/Record/HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +selected = 4 +item_count = 6 +popup/item_0/text = "11025 Hz" +popup/item_1/text = "16000 Hz" +popup/item_1/id = 1 +popup/item_2/text = "22050 Hz" +popup/item_2/id = 2 +popup/item_3/text = "32000 Hz" +popup/item_3/id = 3 +popup/item_4/text = "44100 Hz" +popup/item_4/id = 4 +popup/item_5/text = "48000 Hz" +popup/item_5/id = 5 + +[node name="VSeparator3" type="VSeparator" parent="TabContainer/Record/HBoxContainer"] +self_modulate = Color(1, 1, 1, 0) +custom_minimum_size = Vector2(20, 0) +layout_mode = 2 + +[node name="StereoLabel" type="Label" parent="TabContainer/Record/HBoxContainer"] +layout_mode = 2 +text = "Stereo:" + +[node name="StereoCheckButton" type="CheckButton" parent="TabContainer/Record/HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +button_pressed = true + +[node name="HBoxContainer2" type="HBoxContainer" parent="TabContainer/Record"] +layout_mode = 2 + +[node name="Label" type="Label" parent="TabContainer/Record/HBoxContainer2"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "World: " +horizontal_alignment = 2 +vertical_alignment = 1 + +[node name="SoundName" type="LineEdit" parent="TabContainer/Record/HBoxContainer2"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +caret_blink = true + +[node name="AddWordBTN" type="Button" parent="TabContainer/Record/HBoxContainer2"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Add Word" + +[node name="VSeparator" type="VSeparator" parent="TabContainer/Record/HBoxContainer2"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="VSeparator2" type="VSeparator" parent="TabContainer/Record/HBoxContainer2"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="LoadWords" type="Button" parent="TabContainer/Record/HBoxContainer2"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Load Words" + +[node name="Label2" type="Label" parent="TabContainer/Record/HBoxContainer2"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Save Folder Path: " +horizontal_alignment = 2 +vertical_alignment = 1 + +[node name="SavePath" type="LineEdit" parent="TabContainer/Record/HBoxContainer2"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +text = "res://records" +caret_blink = true + +[node name="HBoxContainer3" type="HBoxContainer" parent="TabContainer/Record"] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="WordList" type="ItemList" parent="TabContainer/Record/HBoxContainer3"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 0.5 + +[node name="Controls" type="VBoxContainer" parent="TabContainer/Record/HBoxContainer3"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 + +[node name="WordName" type="Label" parent="TabContainer/Record/HBoxContainer3/Controls"] +unique_name_in_owner = true +layout_mode = 2 +text = "<==" +label_settings = SubResource("LabelSettings_7vfcs") +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="HSeparator" type="HSeparator" parent="TabContainer/Record/HBoxContainer3/Controls"] +self_modulate = Color(1, 1, 1, 0) +custom_minimum_size = Vector2(0, 40) +layout_mode = 2 + +[node name="HBoxContainer" type="HBoxContainer" parent="TabContainer/Record/HBoxContainer3/Controls"] +layout_mode = 2 +alignment = 1 + +[node name="RecordButton" type="Button" parent="TabContainer/Record/HBoxContainer3/Controls/HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +text = "Record" + +[node name="PlayButton" type="Button" parent="TabContainer/Record/HBoxContainer3/Controls/HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +disabled = true +text = "Play" + +[node name="SaveButton" type="Button" parent="TabContainer/Record/HBoxContainer3/Controls/HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +disabled = true +text = "Save" + +[node name="OpenUserFolderButton" type="Button" parent="TabContainer/Record/HBoxContainer3/Controls/HBoxContainer"] +layout_mode = 2 +text = "Open User Folder" + +[node name="HSeparator2" type="HSeparator" parent="TabContainer/Record/HBoxContainer3/Controls"] +self_modulate = Color(1, 1, 1, 0) +custom_minimum_size = Vector2(0, 40) +layout_mode = 2 + +[node name="HBoxContainer2" type="HBoxContainer" parent="TabContainer/Record/HBoxContainer3/Controls"] +layout_mode = 2 +alignment = 2 + +[node name="DeleteButton" type="Button" parent="TabContainer/Record/HBoxContainer3/Controls/HBoxContainer2"] +layout_mode = 2 +text = "Delete" + +[node name="PlanText" type="VBoxContainer" parent="TabContainer"] +layout_mode = 2 +metadata/_tab_index = 1 + +[node name="HBoxContainer2" type="HBoxContainer" parent="TabContainer/PlanText"] +layout_mode = 2 + +[node name="VSeparator" type="VSeparator" parent="TabContainer/PlanText/HBoxContainer2"] +self_modulate = Color(1, 1, 1, 0) +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="PlayTextBTN" type="Button" parent="TabContainer/PlanText/HBoxContainer2"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Play Text" + +[node name="VSeparator2" type="VSeparator" parent="TabContainer/PlanText/HBoxContainer2"] +self_modulate = Color(1, 1, 1, 0) +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 0.7 + +[node name="AddWordsBTN" type="Button" parent="TabContainer/PlanText/HBoxContainer2"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Add Words" + +[node name="LoadTextBTN" type="Button" parent="TabContainer/PlanText/HBoxContainer2"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Load Text" + +[node name="Label2" type="Label" parent="TabContainer/PlanText/HBoxContainer2"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Text File Path: " +horizontal_alignment = 2 +vertical_alignment = 1 + +[node name="WordsTXTPath" type="LineEdit" parent="TabContainer/PlanText/HBoxContainer2"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 1.5 +text = "res://records/phrases.txt" +caret_blink = true + +[node name="VSeparator3" type="VSeparator" parent="TabContainer/PlanText/HBoxContainer2"] +self_modulate = Color(1, 1, 1, 0) +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="HBoxContainer3" type="HBoxContainer" parent="TabContainer/PlanText"] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="TextWordsEdit" type="TextEdit" parent="TabContainer/PlanText/HBoxContainer3"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +placeholder_text = "Text to read load worlds" + +[connection signal="item_selected" from="TabContainer/Record/HBoxContainer/FormatOptionButton" to="." method="_on_format_option_button_item_selected"] +[connection signal="item_selected" from="TabContainer/Record/HBoxContainer/MixRateOptionButton" to="." method="_on_mix_rate_option_button_item_selected"] +[connection signal="toggled" from="TabContainer/Record/HBoxContainer/StereoCheckButton" to="." method="_on_stereo_check_button_toggled"] +[connection signal="pressed" from="TabContainer/Record/HBoxContainer2/AddWordBTN" to="." method="_on_add_word_btn_pressed"] +[connection signal="pressed" from="TabContainer/Record/HBoxContainer2/LoadWords" to="." method="_on_load_words_pressed"] +[connection signal="item_selected" from="TabContainer/Record/HBoxContainer3/WordList" to="." method="_on_item_list_item_selected"] +[connection signal="pressed" from="TabContainer/Record/HBoxContainer3/Controls/HBoxContainer/RecordButton" to="." method="_on_record_button_pressed"] +[connection signal="pressed" from="TabContainer/Record/HBoxContainer3/Controls/HBoxContainer/PlayButton" to="." method="_on_play_button_pressed"] +[connection signal="pressed" from="TabContainer/Record/HBoxContainer3/Controls/HBoxContainer/SaveButton" to="." method="_on_save_button_pressed"] +[connection signal="pressed" from="TabContainer/Record/HBoxContainer3/Controls/HBoxContainer/OpenUserFolderButton" to="." method="_on_open_user_folder_button_pressed"] +[connection signal="pressed" from="TabContainer/Record/HBoxContainer3/Controls/HBoxContainer2/DeleteButton" to="." method="_on_delete_button_pressed"] +[connection signal="pressed" from="TabContainer/PlanText/HBoxContainer2/PlayTextBTN" to="." method="_on_play_text_btn_pressed"] +[connection signal="pressed" from="TabContainer/PlanText/HBoxContainer2/AddWordsBTN" to="." method="_on_add_words_btn_pressed"] +[connection signal="pressed" from="TabContainer/PlanText/HBoxContainer2/LoadTextBTN" to="." method="_on_load_text_btn_pressed"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/default_bus_layout.tres b/default_bus_layout.tres new file mode 100644 index 0000000..a208971 --- /dev/null +++ b/default_bus_layout.tres @@ -0,0 +1,26 @@ +[gd_resource type="AudioBusLayout" load_steps=4 format=3 uid="uid://tuxl6tvrr2dv"] + +[sub_resource type="AudioEffectFilter" id="AudioEffectFilter_081mb"] +resource_name = "Filter" +gain = 4.0 + +[sub_resource type="AudioEffectPitchShift" id="AudioEffectPitchShift_r6ahm"] +resource_name = "PitchShift" +pitch_scale = 0.85 + +[sub_resource type="AudioEffectRecord" id="AudioEffectRecord_fo272"] +resource_name = "Record" + +[resource] +bus/1/name = &"Record" +bus/1/solo = false +bus/1/mute = true +bus/1/bypass_fx = false +bus/1/volume_db = 0.0 +bus/1/send = &"Master" +bus/1/effect/0/effect = SubResource("AudioEffectFilter_081mb") +bus/1/effect/0/enabled = true +bus/1/effect/1/effect = SubResource("AudioEffectPitchShift_r6ahm") +bus/1/effect/1/enabled = true +bus/1/effect/2/effect = SubResource("AudioEffectRecord_fo272") +bus/1/effect/2/enabled = true diff --git a/icon.webp b/icon.webp new file mode 100644 index 0000000..166cc56 Binary files /dev/null and b/icon.webp differ diff --git a/icon.webp.import b/icon.webp.import new file mode 100644 index 0000000..cc29159 --- /dev/null +++ b/icon.webp.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://brwp8bimc75uu" +path="res://.godot/imported/icon.webp-e94f9a68b0f625a567a797079e4d325f.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://icon.webp" +dest_files=["res://.godot/imported/icon.webp-e94f9a68b0f625a567a797079e4d325f.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/project.godot b/project.godot new file mode 100644 index 0000000..f389c7e --- /dev/null +++ b/project.godot @@ -0,0 +1,35 @@ +; Engine configuration file. +; It's best edited using the editor UI and not directly, +; since the parameters that go here are not all obvious. +; +; Format: +; [section] ; section goes between [] +; param=value ; assign values to parameters + +config_version=5 + +[application] + +config/name="Announcer Recorder Helper" +config/description="Based on record audio demo. +Helps record words for black mesa like announcer system." +config/tags=PackedStringArray("audio", "gui", "tool") +run/main_scene="res://MicRecord.tscn" +config/features=PackedStringArray("4.3") +run/low_processor_mode=true +config/icon="res://icon.webp" + +[audio] + +driver/enable_input=true +enable_audio_input=true + +[debug] + +gdscript/warnings/untyped_declaration=1 + +[display] + +window/stretch/mode="canvas_items" +window/stretch/aspect="expand" +window/vsync/vsync_mode=0 diff --git a/screenshots/.gdignore b/screenshots/.gdignore new file mode 100644 index 0000000..e69de29 diff --git a/screenshots/mic_record.png b/screenshots/mic_record.png new file mode 100644 index 0000000..57ded2f Binary files /dev/null and b/screenshots/mic_record.png differ