diff --git a/scenes/MultiMorseBanner.tscn b/scenes/MultiMorseBanner.tscn index a9a5267..255334d 100644 --- a/scenes/MultiMorseBanner.tscn +++ b/scenes/MultiMorseBanner.tscn @@ -1,6 +1,9 @@ -[gd_scene load_steps=2 format=3 uid="uid://ug3u6jf36dst"] +[gd_scene load_steps=3 format=3 uid="uid://ug3u6jf36dst"] -[ext_resource type="Script" uid="uid://j1oei8suq5sj" path="res://scenes/morse_banner.gd" id="1_a1ve8"] +[ext_resource type="Script" uid="uid://b1k6j1jti114u" path="res://scenes/multi_morse_banner.gd" id="1_a1ve8"] + +[sub_resource type="AudioStreamGenerator" id="AudioStreamGenerator_a1ve8"] +mix_rate = 22050.0 [node name="MorseBanner" type="Control"] custom_minimum_size = Vector2(200, 100) @@ -11,3 +14,8 @@ anchor_bottom = 1.0 grow_horizontal = 2 grow_vertical = 2 script = ExtResource("1_a1ve8") + +[node name="Player" type="AudioStreamPlayer" parent="."] +stream = SubResource("AudioStreamGenerator_a1ve8") +volume_db = -100.0 +stream_paused = true diff --git a/scenes/MultiplayerConnect.tscn b/scenes/MultiplayerConnect.tscn index 341033f..308c4c2 100644 --- a/scenes/MultiplayerConnect.tscn +++ b/scenes/MultiplayerConnect.tscn @@ -78,6 +78,10 @@ text = "Freq:" unique_name_in_owner = true layout_mode = 2 +[node name="PlayerContainer" type="VBoxContainer" parent="MorseView/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 + [node name="MorseButton" type="Button" parent="MorseView/VBoxContainer"] layout_mode = 2 size_flags_vertical = 3 @@ -87,3 +91,5 @@ text = "MORSE" [connection signal="pressed" from="ConnectView/VBoxContainer/HBoxContainer/CreateButton" to="." method="_on_create_button_pressed"] [connection signal="pressed" from="ConnectView/VBoxContainer/RefreshButton" to="." method="_on_refresh_button_pressed"] [connection signal="item_clicked" from="ConnectView/VBoxContainer/FreqList" to="." method="_on_freq_list_join"] +[connection signal="button_down" from="MorseView/VBoxContainer/MorseButton" to="." method="_on_morse_button_button_down"] +[connection signal="button_up" from="MorseView/VBoxContainer/MorseButton" to="." method="_on_morse_button_button_up"] diff --git a/scenes/multi_morse_banner.gd b/scenes/multi_morse_banner.gd index 974bf3f..04c4811 100644 --- a/scenes/multi_morse_banner.gd +++ b/scenes/multi_morse_banner.gd @@ -1,14 +1,59 @@ @tool +class_name MultiMorseBanner extends Control +static var sample_hz := 22050.0 +static var vol_on := -30 +var phase := 0.0 + +var num: int +var tone_hz: int var color_on := Color(0, 128, 0) var color_off := Color(0, 0, 0) var last_delta := 0.0 @export_range(0.1, 120.0) var display_sec := 5.0 @export var stretch_display := false +var playback; + +func _ready() -> void: + $Player.stream.mix_rate = sample_hz + $Player.volume_db = -100 + $Player.play() + playback = $Player.get_stream_playback() + fill_buffer() var morse_step_perc := 0.45 +const MMB_SCENE := preload("res://scenes/MultiMorseBanner.tscn") +class LocalMorseState: + var states: Array[int] = [] + var curr_state := false + var last_change: int = 0 + var start_time := 0 + + func reset() -> void: + last_change = Time.get_ticks_msec() + start_time = last_change + states = [] + + func set_state(state: bool) -> void: + if state == curr_state: + return + curr_state = state + + var now := Time.get_ticks_msec() + states.push_back(now - last_change) + last_change = now + +var morse_state := LocalMorseState.new() + +static func new_banner(num: int, color: Color, tone: int): + var mmb = MMB_SCENE.instantiate() + mmb.num = num + mmb.color_on = color + mmb.tone_hz = tone + + return mmb func _draw_morse_rect(x: float, width: float, state: bool): var rect := Rect2(max(x, 0.0), morse_step_perc * size.y, width, (0.5 - morse_step_perc) * size.y) @@ -22,18 +67,18 @@ func _draw(): if Engine.is_editor_hint(): return - var morse_on := MorseState.curr_state - var first_time := Time.get_ticks_msec() - MorseState.last_change + var morse_on := morse_state.curr_state + var first_time := Time.get_ticks_msec() - morse_state.last_change var curr_x := float(size.x) var px_per_s := 0.0 if not stretch_display: px_per_s = size.x / display_sec else: - px_per_s = size.x / (Time.get_ticks_msec() - MorseState.start_time) * 1000.0 + px_per_s = size.x / (Time.get_ticks_msec() - morse_state.start_time) * 1000.0 - for n in [-1] + range(MorseState.states.size() - 1, -1, -1): - var duration := first_time if n == -1 else MorseState.states[n] + for n in [-1] + range(morse_state.states.size() - 1, -1, -1): + var duration := first_time if n == -1 else morse_state.states[n] var rect_width: float = min(duration / 1000.0 * px_per_s, curr_x) curr_x -= rect_width if morse_on: @@ -46,3 +91,21 @@ func _draw(): func _process(_delta): last_delta += _delta queue_redraw() + fill_buffer() + +func set_morse_state(state: bool) -> void: + morse_state.set_state(state) + # morse_state = state + if state: + $Player.volume_db = vol_on + else: + $Player.volume_db = -100 + +func fill_buffer(): + var increment = tone_hz / sample_hz + var frames_available = playback.get_frames_available() + + for i in range(frames_available): + playback.push_frame(Vector2.ONE * sin(phase * TAU)) + phase = fmod(phase + increment, 1.0) + diff --git a/scenes/multiplayer_connect.gd b/scenes/multiplayer_connect.gd index da121a1..ef425b6 100644 --- a/scenes/multiplayer_connect.gd +++ b/scenes/multiplayer_connect.gd @@ -4,10 +4,32 @@ var server := "localhost" var port := "3784" var ws := WebSocketPeer.new() -var mb_scene := preload("res://scenes/MultiMorseBanner.tscn") var available_freqs = [] +var mmb_self: MultiMorseBanner +var mmb_others: Dictionary[String, MultiMorseBanner] + +class PlayerData: + var num: int + var color: Color + var tone: int + + func _init(num, color, tone): + self.num = num + self.color = color + self.tone = tone + +var player_data := [ + PlayerData.new(0, Color( 0, 255, 0), 880), + PlayerData.new(1, Color(255, 0, 0), 880), + PlayerData.new(2, Color( 0, 0, 255), 880), + PlayerData.new(3, Color(255, 0, 255), 880), +] func _ready() -> void: + # FIXME: connection handling / reconnect + # FIXME: status / error messages + # FIXME: randomize default join frquency + # FIXME: automatic refresh print("ws://%s:%s" % [server, port]) ws.connect_to_url("ws://%s:%s" % [server, port]) @@ -31,6 +53,9 @@ func _handle_packet() -> void: print("parsed ", parsed) match parsed["type"]: + "hello": + # fetch frequency list on first join + _on_refresh_button_pressed() "join": _join_freq(parsed["freq"]) @@ -42,7 +67,14 @@ func _handle_packet() -> void: var idx: int = %FreqList.add_item(text) %FreqList.set_item_metadata(idx, freq) -func _join_freq(freq: String): +func _join_freq(freq: String, player_id: String, other_players: Array[String]): + for child in %PlayerContainer.get_children(): + %PlayerContainer.remove_child(child) + + mmb_self = make_player(0) + for n, player_id in enumeerate(other_players): + make_player(n + 1) + %FreqLabel.text = "%s MHz" % freq %ConnectView.hide() %MorseView.show() @@ -66,4 +98,26 @@ func _on_freq_list_join(index: int, at_position: Vector2, mouse_button_index: in print("Yop ", index, " metadata ", freq) var join_cmd := {"cmd": "join", "freq": freq, "type": "cw-generator"} ws.send_text(JSON.stringify(join_cmd) + "\n") + +func make_player(no: int): + var pd: PlayerData + if no < player_data.size(): + pd = player_data[no] + else: + pd = PlayerData.new(no, Color(randi_range(0, 255), randi_range(0, 255), randi_range(0, 255)), randi_range(440, 440 * 3)) + var mmb = MultiMorseBanner.new_banner(pd.num, pd.color, pd.tone) + %PlayerContainer.add_child(mmb) + return mmb + + +func _on_morse_button_button_down() -> void: + if not mmb_self: + return + mmb_self.set_morse_state(true) + + +func _on_morse_button_button_up() -> void: + if not mmb_self: + return + mmb_self.set_morse_state(false) diff --git a/signalsrv/signalsrv.py b/signalsrv/signalsrv.py index 0e2bbec..86edb8b 100644 --- a/signalsrv/signalsrv.py +++ b/signalsrv/signalsrv.py @@ -92,7 +92,7 @@ class Client: self.curr_freq = freq self.freqs[freq] = [self] - await self._send(type="join", freq=self.curr_freq, others=[]) + await self._send(type="join", freq=self.curr_freq, self_id=self.id, others=[]) async def _join_room(self, data): if self.curr_freq: @@ -108,9 +108,12 @@ class Client: await self._send_error(f"Frequency {freq} not available") return + self.curr_freq = freq self.freqs[freq].append(self) # FIXME: do we need locking here? - await self._send(type="join", freq=self.curr_freq, players=[c.id for c in self._others(freq)]) + print("FREQ", self.curr_freq, freq, self.freqs) + await self._send(type="join", freq=self.curr_freq, self_id=self.id, players=[c.id for c in self._others(freq) + if not c.id == self.id]) for other in self._others(freq): await self._send(type="player-joined", player=self.id)