From a1f95525b5f5e10f09871e95d2aa5ea8e9cedb3e Mon Sep 17 00:00:00 2001 From: Dominicentek Date: Tue, 2 Jun 2026 20:58:20 +0200 Subject: [PATCH] [voice chat] rename symbols for consistency with other APIs --- src/game/hud.c | 12 +- src/pc/audio/audio_sdl.c | 2 +- src/pc/djui/djui_interactable.c | 18 +- src/pc/djui/djui_panel_voice_chat.c | 33 +-- src/pc/nametags.c | 10 +- src/pc/network/network_player.c | 4 + .../network/packets/packet_network_players.c | 4 + src/pc/network/packets/packet_voicechat.c | 10 +- src/pc/voice_chat.c | 223 ++++++++++-------- src/pc/voice_chat.h | 82 ++++++- 10 files changed, 233 insertions(+), 165 deletions(-) diff --git a/src/game/hud.c b/src/game/hud.c index 0459f3f27..392f30c23 100644 --- a/src/game/hud.c +++ b/src/game/hud.c @@ -598,16 +598,16 @@ void render_hud_voicechat(void) { s32 y = 35; const Texture* tex = NULL; - if (voicechat_error[0] != VOICECHAT_ERR_NONE) tex = texture_microphone_warning; - else if (*voicechat_player_muted(0) & VOICECHAT_MUTE_DEAFENED) tex = texture_headphones; - else if (*voicechat_player_muted(0) & VOICECHAT_MUTE_GLOBAL) tex = texture_microphone_red_muted; + if (gVoicePlayer->error != VOICECHAT_ERR_NONE) tex = texture_microphone_warning; + else if (gVoicePlayer->clientMutedState & VOICECHAT_MUTE_DEAFENED) tex = texture_headphones; + else if (gVoicePlayer->clientMutedState & VOICECHAT_MUTE_GLOBAL) tex = texture_microphone_red_muted; else if (configVoiceChatActivationMode == VOICECHAT_ACTMODE_PUSH_TO_TALK) { - if (*voicechat_player_muted(0) != VOICECHAT_UNMUTED) tex = texture_microphone_muted; + if (gVoicePlayer->clientMutedState != VOICECHAT_UNMUTED) tex = texture_microphone_muted; else tex = texture_microphone; } else if (configVoiceChatActivationMode == VOICECHAT_ACTMODE_THRESHOLD) { - if (*voicechat_player_muted(0) != VOICECHAT_UNMUTED) tex = texture_microphone_muted; - else if (voicechat_player_is_talking(0)) tex = texture_microphone; + if (gVoicePlayer->clientMutedState != VOICECHAT_UNMUTED) tex = texture_microphone_muted; + else if (gVoicePlayer->talking) tex = texture_microphone; else tex = NULL; } diff --git a/src/pc/audio/audio_sdl.c b/src/pc/audio/audio_sdl.c index 067b55156..d32c075d2 100644 --- a/src/pc/audio/audio_sdl.c +++ b/src/pc/audio/audio_sdl.c @@ -45,7 +45,7 @@ static bool audio_sdl_init(void) { .callback = audio_sdl_capture_callback }, NULL, 0); if (capture_dev == 0) { - voicechat_error[0] = VOICECHAT_ERR_NO_MICROPHONE; + gVoicePlayer->error = VOICECHAT_ERR_NO_MICROPHONE; fprintf(stderr, "SDL_OpenAudio capture error: %s\n", SDL_GetError()); return false; } diff --git a/src/pc/djui/djui_interactable.c b/src/pc/djui/djui_interactable.c index bf294d986..25f0c7277 100644 --- a/src/pc/djui/djui_interactable.c +++ b/src/pc/djui/djui_interactable.c @@ -238,19 +238,10 @@ bool djui_interactable_on_key_down(int scancode) { if (scancode == (int)configKeyPushToTalk[i]) pressPushToTalk = true; } - u32* mute_state = voicechat_player_muted(0); if (pressChat) djui_chat_box_toggle(); - if (pressMute && configVoiceChatActivationMode == VOICECHAT_ACTMODE_THRESHOLD) { - if (*mute_state & VOICECHAT_MUTE_LOCAL) *mute_state &= ~VOICECHAT_MUTE_LOCAL; - else *mute_state |= VOICECHAT_MUTE_LOCAL; - } - if (pressDeafen) { - if (*mute_state & VOICECHAT_MUTE_DEAFENED) *mute_state &= ~VOICECHAT_MUTE_DEAFENED; - else *mute_state |= VOICECHAT_MUTE_DEAFENED; - network_send_voicechat_muted(gNetworkPlayers[0].globalIndex, VOICECHAT_MUTE_DEAFENED, *mute_state & VOICECHAT_MUTE_DEAFENED); - } - if (pressPushToTalk && configVoiceChatActivationMode == VOICECHAT_ACTMODE_PUSH_TO_TALK) - *mute_state &= ~VOICECHAT_MUTE_LOCAL; + if (pressMute && configVoiceChatActivationMode == VOICECHAT_ACTMODE_THRESHOLD) voicechat_toggle_mute(); + if (pressDeafen) voicechat_toggle_deafen(); + if (pressPushToTalk && configVoiceChatActivationMode == VOICECHAT_ACTMODE_PUSH_TO_TALK) voicechat_set_mute(false); return pressChat || pressMute || pressDeafen || pressPushToTalk; } @@ -310,8 +301,7 @@ void djui_interactable_on_key_up(int scancode) { if (scancode == (int)configKeyPushToTalk[i]) disablePushToTalk = true; } - if (disablePushToTalk && configVoiceChatActivationMode == VOICECHAT_ACTMODE_PUSH_TO_TALK) - *voicechat_player_muted(0) |= VOICECHAT_MUTE_LOCAL; + if (disablePushToTalk && configVoiceChatActivationMode == VOICECHAT_ACTMODE_PUSH_TO_TALK) voicechat_set_mute(true); } if (sPendingConsoleToggleScancode != -1 && scancode == sPendingConsoleToggleScancode) { diff --git a/src/pc/djui/djui_panel_voice_chat.c b/src/pc/djui/djui_panel_voice_chat.c index 965a08511..d27217748 100644 --- a/src/pc/djui/djui_panel_voice_chat.c +++ b/src/pc/djui/djui_panel_voice_chat.c @@ -17,9 +17,7 @@ static void djui_panel_sound_value_change(UNUSED struct DjuiBase* caller) { static void djui_panel_mode_value_change(UNUSED struct DjuiBase* caller) { djui_base_set_enabled(activation_threshold_slider, configVoiceChatActivationMode == VOICECHAT_ACTMODE_THRESHOLD); - u32* mute_state = voicechat_player_muted(0); - if (configVoiceChatActivationMode == VOICECHAT_ACTMODE_PUSH_TO_TALK) *mute_state |= VOICECHAT_MUTE_LOCAL; - else *mute_state &= ~VOICECHAT_MUTE_LOCAL; + voicechat_set_mute(configVoiceChatActivationMode == VOICECHAT_ACTMODE_PUSH_TO_TALK); } static void djui_panel_threshold_value_change(UNUSED struct DjuiBase* caller) { @@ -27,7 +25,7 @@ static void djui_panel_threshold_value_change(UNUSED struct DjuiBase* caller) { } static void djui_panel_voicechat_back(struct DjuiBase* caller) { - voicechat_loopback = false; + gVoiceChatLoopback = false; djui_panel_menu_back(caller); } @@ -46,8 +44,8 @@ void djui_panel_voice_chat_create(struct DjuiBase* caller) { activation_threshold_slider = &djui_slider_create(body, DLANG(VOICECHAT, ACTIVATION_THRESHOLD), &configVoiceChatActivationThreshold, 0, 100, djui_panel_threshold_value_change)->base; djui_progress_bar_create(body, &threshold, 0, 1, false); - djui_progress_bar_create(body, &voicechat_mic_level, 0, 1, false); - djui_checkbox_create(body, DLANG(VOICECHAT, LOOPBACK), &voicechat_loopback, NULL); + djui_progress_bar_create(body, &gVoiceChatMicLevel, 0, 1, false); + djui_checkbox_create(body, DLANG(VOICECHAT, LOOPBACK), &gVoiceChatLoopback, NULL); djui_panel_mode_value_change(NULL); djui_panel_threshold_value_change(NULL); @@ -79,18 +77,17 @@ static bool is_dark_theme() { } static void get_microphone_icon(struct TextureInfo* tex, s32 player, bool red) { - u32* mute_state = voicechat_player_muted(player); if (red) { - if (*mute_state & VOICECHAT_MUTE_GLOBAL) dynos_texture_get("texture_microphone_red_icon_muted", tex); + if (gVoicePlayers[player].clientMutedState & VOICECHAT_MUTE_GLOBAL) dynos_texture_get("texture_microphone_red_icon_muted", tex); else dynos_texture_get("texture_microphone_red_icon", tex); } else { if (is_dark_theme()) { - if (*mute_state & VOICECHAT_MUTE_LOCAL) dynos_texture_get("texture_microphone_icon_muted", tex); + if (gVoicePlayers[player].clientMutedState & VOICECHAT_MUTE_LOCAL) dynos_texture_get("texture_microphone_icon_muted", tex); else dynos_texture_get("texture_microphone_icon", tex); } else { - if (*mute_state & VOICECHAT_MUTE_LOCAL) dynos_texture_get("texture_microphone_black_icon_muted", tex); + if (gVoicePlayers[player].clientMutedState & VOICECHAT_MUTE_LOCAL) dynos_texture_get("texture_microphone_black_icon_muted", tex); else dynos_texture_get("texture_microphone_black_icon", tex); } } @@ -103,17 +100,9 @@ static void toggle_mute(struct DjuiBase* caller) { } if (!mic) return; - u32* mute_state = voicechat_player_muted(mic->player_id); - if (mic->global) { - if (*mute_state & VOICECHAT_MUTE_GLOBAL) *mute_state &= ~VOICECHAT_MUTE_GLOBAL; - else *mute_state |= VOICECHAT_MUTE_GLOBAL; - network_send_voicechat_muted(gNetworkPlayers[mic->player_id].globalIndex, VOICECHAT_MUTE_GLOBAL, *mute_state & VOICECHAT_MUTE_GLOBAL); - } - else { - if (*mute_state & VOICECHAT_MUTE_LOCAL) *mute_state &= ~VOICECHAT_MUTE_LOCAL; - else *mute_state |= VOICECHAT_MUTE_LOCAL; - network_send_voicechat_muted(gNetworkPlayers[mic->player_id].globalIndex, VOICECHAT_MUTE_LOCAL, *mute_state & VOICECHAT_MUTE_LOCAL); - } + if (mic->global) voicechat_toggle_global_mute(mic->player_id); + else voicechat_toggle_mute_other(mic->player_id); + get_microphone_icon(&mic->button->icon->textureInfo, mic->player_id, mic->global); } @@ -149,7 +138,7 @@ static void djui_panel_voice_chat_add_players(struct DjuiBase* body) { struct DjuiButton* gmute = djui_image_button_create(&inner_layout->base, TEXTURE_INFO(gmute_tex), DJUI_BUTTON_STYLE_NORMAL, toggle_mute); struct DjuiButton* lmute = djui_image_button_create(&inner_layout->base, TEXTURE_INFO(lmute_tex), DJUI_BUTTON_STYLE_NORMAL, toggle_mute); - struct DjuiSlider* vol = djui_slider_create(&inner_layout->base, gNetworkPlayers[i].name, voicechat_player_volume(i), 0, 100, NULL); + struct DjuiSlider* vol = djui_slider_create(&inner_layout->base, gNetworkPlayers[i].name, &gVoicePlayers[i].volume, 0, 200, NULL); sMicButtons[button_counter++] = (struct MicButton){ .base = &lmute->base, .button = lmute, .player_id = i, .global = false }; sMicButtons[button_counter++] = (struct MicButton){ .base = &gmute->base, .button = gmute, .player_id = i, .global = true }; diff --git a/src/pc/nametags.c b/src/pc/nametags.c index 2ee043657..2ebb1587e 100644 --- a/src/pc/nametags.c +++ b/src/pc/nametags.c @@ -175,11 +175,11 @@ void nametags_render(void) { // render mic icon const char* mic = NULL; - if (voicechat_error[playerIndex] != VOICECHAT_ERR_NONE) mic = "texture_microphone_warning"; - else if (voicechat_others_muted[playerIndex] & VOICECHAT_MUTE_DEAFENED) mic = "texture_headphones"; - else if (*voicechat_player_muted(playerIndex) & VOICECHAT_MUTE_GLOBAL) mic = "texture_microphone_red_muted"; - else if (*voicechat_player_muted(playerIndex) & VOICECHAT_MUTE_LOCAL) mic = "texture_microphone_muted"; - else if (voicechat_player_is_talking(playerIndex)) mic = "texture_microphone"; + if (gVoicePlayers[playerIndex].error != VOICECHAT_ERR_NONE) mic = "texture_microphone_warning"; + else if (gVoicePlayers[playerIndex].playerMutedState & VOICECHAT_MUTE_DEAFENED) mic = "texture_headphones"; + else if (gVoicePlayers[playerIndex].clientMutedState & VOICECHAT_MUTE_GLOBAL) mic = "texture_microphone_red_muted"; + else if (gVoicePlayers[playerIndex].clientMutedState & VOICECHAT_MUTE_LOCAL) mic = "texture_microphone_muted"; + else if (gVoicePlayers[playerIndex].talking) mic = "texture_microphone"; if (mic) { struct TextureInfo texture; dynos_texture_get(mic, &texture); diff --git a/src/pc/network/network_player.c b/src/pc/network/network_player.c index 5e1661bbb..5e64b554f 100644 --- a/src/pc/network/network_player.c +++ b/src/pc/network/network_player.c @@ -17,6 +17,7 @@ #endif #include "game/mario.h" #include "pc/djui/djui_unicode.h" +#include "pc/voice_chat.h" struct NetworkPlayer gNetworkPlayers[MAX_PLAYERS] = { 0 }; struct NetworkPlayer *gNetworkPlayerLocal = NULL; @@ -364,6 +365,9 @@ u8 network_player_connected(enum NetworkPlayerType type, u8 globalIndex, u8 mode } #endif + if (gServerSettings.voiceChat != VOICECHAT_TYPE_DISABLED) + voicechat_init_player(localIndex); + return localIndex; } diff --git a/src/pc/network/packets/packet_network_players.c b/src/pc/network/packets/packet_network_players.c index c9e57e298..e9371b1d5 100644 --- a/src/pc/network/packets/packet_network_players.c +++ b/src/pc/network/packets/packet_network_players.c @@ -6,6 +6,7 @@ #include "pc/debuglog.h" #include "pc/configfile.h" #include "pc/network/moderator_list.h" +#include "pc/voice_chat.h" static void network_send_to_network_players(u8 sendToLocalIndex) { SOFT_ASSERT(gNetworkType == NT_SERVER); @@ -134,6 +135,9 @@ void network_receive_network_players(struct Packet *p) { np->palette = palette; network_player_update_model(localIndex); } + + if (gServerSettings.voiceChat != VOICECHAT_TYPE_DISABLED) + voicechat_init_player(localIndex); } } } \ No newline at end of file diff --git a/src/pc/network/packets/packet_voicechat.c b/src/pc/network/packets/packet_voicechat.c index 831f689fa..a09e9c75c 100644 --- a/src/pc/network/packets/packet_voicechat.c +++ b/src/pc/network/packets/packet_voicechat.c @@ -16,7 +16,7 @@ void network_send_voicechat_frame(void) { for (int i = 1; i < MAX_PLAYERS; i++) { if (!gNetworkPlayers[i].connected) continue; - if (voicechat_others_muted[i]) continue; + if (gVoicePlayers[i].playerMutedState) continue; network_send_to(i, &p); } } @@ -63,10 +63,10 @@ void network_receive_voicechat_muted(struct Packet* p) { if (!sender->moderator && sender->globalIndex != 0) mask &= ~VOICECHAT_MUTE_GLOBAL; if (mask & VOICECHAT_MUTE_GLOBAL) { - if (muted) *voicechat_player_muted(receiver->localIndex) |= VOICECHAT_MUTE_GLOBAL; - else *voicechat_player_muted(receiver->localIndex) &= ~VOICECHAT_MUTE_GLOBAL; + if (muted) gVoicePlayers[receiver->localIndex].clientMutedState |= VOICECHAT_MUTE_GLOBAL; + else gVoicePlayers[receiver->localIndex].clientMutedState &= ~VOICECHAT_MUTE_GLOBAL; } - if (muted) voicechat_others_muted[sender->localIndex] |= (mask & ~VOICECHAT_MUTE_GLOBAL); - else voicechat_others_muted[sender->localIndex] &= ~(mask & ~VOICECHAT_MUTE_GLOBAL); + if (muted) gVoicePlayers[sender->localIndex].playerMutedState |= (mask & ~VOICECHAT_MUTE_GLOBAL); + else gVoicePlayers[sender->localIndex].playerMutedState &= ~(mask & ~VOICECHAT_MUTE_GLOBAL); } \ No newline at end of file diff --git a/src/pc/voice_chat.c b/src/pc/voice_chat.c index 05c3366a3..661ee096b 100644 --- a/src/pc/voice_chat.c +++ b/src/pc/voice_chat.c @@ -21,33 +21,14 @@ #define HEARING_RADIUS 8192 #define FULL_VOL_RADIUS 1024 -typedef struct { - bool dynamic; - u32 size, capacity; - u32 tail, head; - u8* bytes; -} Buffer; +bool gVoiceChatLoopback = false; +float gVoiceChatMicLevel = 0; +s32 gVoiceChatDefaultChannel = 0; -static struct { - bool talking; - u32 muted_state; - u32 volume; - Buffer audio; - union { - OpusEncoder* encoder; - OpusDecoder* decoder; - }; -} players[MAX_PLAYERS], *client = &players[0]; +struct VoicePlayer gVoicePlayers[MAX_PLAYERS]; +struct VoicePlayer* gVoicePlayer = &gVoicePlayers[0]; -static bool inited = false; - -bool voicechat_loopback = false; -float voicechat_mic_level = 0; - -enum VoiceChatMuteState voicechat_others_muted[MAX_PLAYERS]; -enum VoiceChatError voicechat_error[MAX_PLAYERS]; - -static Buffer loopback_buffer = { .capacity = FRAME_SIZE * MAX_FRAMES * sizeof(s16) }; +static struct VoiceBuffer sLoopbackBuffer = { .capacity = FRAME_SIZE * MAX_FRAMES * sizeof(s16) }; static const char* get_opus_error(int err) { switch (err) { @@ -61,7 +42,7 @@ static const char* get_opus_error(int err) { } } -static u32 buffer_read(Buffer* buffer, u32 bytes, void* out) { +static u32 buffer_read(struct VoiceBuffer* buffer, u32 bytes, void* out) { if (bytes > buffer->size) bytes = buffer->size; for (u32 i = 0; i < bytes; i++) { if (out) ((u8*)out)[i] = buffer->bytes[buffer->tail]; @@ -71,7 +52,7 @@ static u32 buffer_read(Buffer* buffer, u32 bytes, void* out) { return bytes; } -static void buffer_grow(Buffer* buffer, u32 bytes) { +static void buffer_grow(struct VoiceBuffer* buffer, u32 bytes) { if (buffer->dynamic && buffer->size + bytes > buffer->capacity) { buffer->capacity = buffer->size + bytes; if (buffer->capacity % 1024) buffer->capacity += 1024 - (buffer->capacity % 1024); @@ -89,7 +70,7 @@ static void buffer_grow(Buffer* buffer, u32 bytes) { if (!buffer->bytes) buffer->bytes = malloc(buffer->capacity); } -static void buffer_write(Buffer* buffer, u32 bytes, void* data) { +static void buffer_write(struct VoiceBuffer* buffer, u32 bytes, void* data) { buffer_grow(buffer, bytes); for (u32 i = 0; i < bytes; i++) { buffer->bytes[buffer->head] = data ? ((u8*)data)[i] : 0; @@ -99,11 +80,11 @@ static void buffer_write(Buffer* buffer, u32 bytes, void* data) { if (buffer->size > buffer->capacity) buffer->size = buffer->capacity; } -static void buffer_drain(Buffer* buffer) { +static void buffer_drain(struct VoiceBuffer* buffer) { buffer->head = buffer->tail = buffer->size = 0; } -static u32 voicechat_num_frames_in_buffer(Buffer* buffer) { +static u32 voicechat_num_frames_in_buffer(struct VoiceBuffer* buffer) { return buffer->size / FRAME_SIZE / sizeof(s16); } @@ -138,7 +119,7 @@ static bool is_below_threshold() { if (configVoiceChatActivationMode == VOICECHAT_ACTMODE_PUSH_TO_TALK) return false; static int decay = 0; - if (voicechat_mic_level < configVoiceChatActivationThreshold / 100.f) { + if (gVoiceChatMicLevel < configVoiceChatActivationThreshold / 100.f) { if (decay > 0) { decay--; return false; @@ -163,116 +144,113 @@ static void voicechat_callback(const u8* input, u32 bytes) { sum += abs(samples[i]); } avg = sum / num_samples; - voicechat_mic_level = 1 - powf(1 - avg / 32767.f, 10); + gVoiceChatMicLevel = 1 - powf(1 - avg / 32767.f, 10); - if (voicechat_loopback) - buffer_write(&loopback_buffer, bytes, samples); + if (gVoiceChatLoopback) + buffer_write(&sLoopbackBuffer, bytes, samples); - if (!inited || gNetworkType == NT_NONE || - configVoiceChatActivationMode == VOICECHAT_ACTMODE_DISABLED || - !gServerSettings.voiceChat || client->muted_state != VOICECHAT_UNMUTED || + if (gNetworkType == NT_NONE || configVoiceChatActivationMode == VOICECHAT_ACTMODE_DISABLED || + !gServerSettings.voiceChat || gVoicePlayer->clientMutedState != VOICECHAT_UNMUTED || is_below_threshold() ) { // drain the pcm buffer - client->talking = false; - buffer_drain(&client->audio); + gVoicePlayer->talking = false; + buffer_drain(&gVoicePlayer->internal.buffer); return; } - client->talking = true; + gVoicePlayer->talking = true; - buffer_write(&client->audio, bytes, samples); + buffer_write(&gVoicePlayer->internal.buffer, bytes, samples); - if (voicechat_num_frames_in_buffer(&client->audio) >= MIN_FRAMES_REQUIRED) + if (voicechat_num_frames_in_buffer(&gVoicePlayer->internal.buffer) >= MIN_FRAMES_REQUIRED) network_send_voicechat_frame(); } void voicechat_init() { gAudioApi->record_callback(voicechat_callback); +} - for (int i = 0; i < MAX_PLAYERS; i++) { - int err; - if (i == 0) { - players[i].encoder = opus_encoder_create(INTERNAL_SAMPLE_RATE, 1, OPUS_APPLICATION_VOIP, &err); - if (err >= 0) opus_encoder_ctl(players[i].encoder, OPUS_SET_BITRATE(BITRATE)); - } - else { - players[i].decoder = opus_decoder_create(INTERNAL_SAMPLE_RATE, 1, &err); - } - - if (err < 0) { - fprintf(stderr, "[VOICECHAT] Failed to initialize player %d: %s\n", i, get_opus_error(err)); - voicechat_error[i] = VOICECHAT_ERR_FAILED_TO_INITIALIZE; - } - else voicechat_error[i] = VOICECHAT_ERR_NONE; - - players[i].audio.capacity = FRAME_SIZE * MAX_FRAMES * sizeof(s16); - players[i].audio.dynamic = false; - players[i].volume = 100; +static void voicechat_shutdown_player(s32 id) { + if (id == 0) { + if (gVoicePlayers[id].internal.encoder) opus_encoder_destroy(gVoicePlayers[id].internal.encoder); + gVoicePlayers[id].internal.encoder = NULL; + } + else { + if (gVoicePlayers[id].internal.decoder) opus_decoder_destroy(gVoicePlayers[id].internal.decoder); + gVoicePlayers[id].internal.decoder = NULL; } - if (configVoiceChatActivationMode == VOICECHAT_ACTMODE_PUSH_TO_TALK) - client->muted_state |= VOICECHAT_MUTE_LOCAL; - - inited = true; + buffer_drain(&gVoicePlayers[id].internal.buffer); + free(gVoicePlayers[id].internal.buffer.bytes); + gVoicePlayers[id].internal.buffer.bytes = NULL; } void voicechat_shutdown() { - if (!inited) return; - - if (client->encoder) opus_encoder_destroy(client->encoder); - for (int i = 1; i < MAX_PLAYERS; i++) { - if (players[i].decoder) opus_decoder_destroy(players[i].decoder); + for (int i = 0; i < MAX_PLAYERS; i++) { + voicechat_shutdown_player(i); } - - inited = false; } -bool voicechat_inited() { - return inited; -} +void voicechat_init_player(s32 id) { + // clear any previous state + voicechat_shutdown_player(id); -u32* voicechat_player_muted(s32 id) { - return &players[id].muted_state; -} + int err; + if (id == 0) { + gVoicePlayers[id].internal.encoder = opus_encoder_create(INTERNAL_SAMPLE_RATE, 1, OPUS_APPLICATION_VOIP, &err); + if (err >= 0) opus_encoder_ctl(gVoicePlayers[id].internal.encoder, OPUS_SET_BITRATE(BITRATE)); + } + else { + gVoicePlayers[id].internal.decoder = opus_decoder_create(INTERNAL_SAMPLE_RATE, 1, &err); + } -u32* voicechat_player_volume(s32 id) { - return &players[id].volume; -} + if (err < 0) { + fprintf(stderr, "[VOICE CHAT] Failed to initialize player %d: %s\n", id, get_opus_error(err)); + gVoicePlayers[id].error = VOICECHAT_ERR_FAILED_TO_INITIALIZE; + } + else gVoicePlayers[id].error = VOICECHAT_ERR_NONE; -bool voicechat_player_is_talking(s32 id) { - return players[id].talking; + gVoicePlayers[id].internal.buffer.capacity = FRAME_SIZE * MAX_FRAMES * sizeof(s16); + gVoicePlayers[id].internal.buffer.dynamic = false; + gVoicePlayers[id].volume = 100; + gVoicePlayers[id].talking = false; + gVoicePlayers[id].clientMutedState = 0; + gVoicePlayers[id].playerMutedState = 0; + + if (id == 0 && configVoiceChatActivationMode == VOICECHAT_ACTMODE_PUSH_TO_TALK) + gVoicePlayers[id].clientMutedState |= VOICECHAT_MUTE_LOCAL; } u32 voicechat_encode_audio(u8* packet, u32 max_size) { s16 pcm[FRAME_SIZE]; - u32 bytes_read = buffer_read(&client->audio, FRAME_SIZE * sizeof(s16), pcm); + u32 bytes_read = buffer_read(&gVoicePlayer->internal.buffer, FRAME_SIZE * sizeof(s16), pcm); memset((u8*)pcm + bytes_read, 0, sizeof(pcm) - bytes_read); - if (!client->encoder) return 0; + if (!gVoicePlayer->internal.encoder) return 0; - s32 out = opus_encode(client->encoder, pcm, FRAME_SIZE, packet, max_size); + s32 out = opus_encode(gVoicePlayer->internal.encoder, pcm, FRAME_SIZE, packet, max_size); if (out < 0) { fprintf(stderr, "[VOICE CHAT] Failed to encode opus packet: %s\n", get_opus_error(out)); - voicechat_error[0] = VOICECHAT_ERR_FAILED_TO_ENCODE; + gVoicePlayers[0].error = VOICECHAT_ERR_FAILED_TO_ENCODE; return 0; } - voicechat_error[0] = VOICECHAT_ERR_NONE; + gVoicePlayers[0].error = VOICECHAT_ERR_NONE; return out; } void voicechat_decode_audio(s32 id, u8* packet, u32 packet_size) { - if (!voicechat_is_ingame(id) || !players[id].decoder) return; + if (!voicechat_is_ingame(id) || !gVoicePlayers[id].internal.decoder) return; s16 pcm[FRAME_SIZE * sizeof(s16)]; - s32 num_frames = opus_decode(players[id].decoder, packet, packet_size, pcm, FRAME_SIZE, 0); + s32 num_frames = opus_decode(gVoicePlayers[id].internal.decoder, packet, packet_size, pcm, FRAME_SIZE, 0); if (num_frames < 0) { fprintf(stderr, "[VOICE CHAT] Failed to decode opus packet: %s\n", get_opus_error(num_frames)); - voicechat_error[id] = VOICECHAT_ERR_FAILED_TO_DECODE; + gVoicePlayers[id].error = VOICECHAT_ERR_FAILED_TO_DECODE; return; } - voicechat_error[id] = VOICECHAT_ERR_NONE; - buffer_write(&players[id].audio, num_frames * sizeof(s16), pcm); + gVoicePlayers[id].error = VOICECHAT_ERR_NONE; + buffer_write(&gVoicePlayers[id].internal.buffer, num_frames * sizeof(s16), pcm); } void voicechat_mix(s16* out_pcm, u32 num_out_samples) { @@ -281,19 +259,19 @@ void voicechat_mix(s16* out_pcm, u32 num_out_samples) { // skip over player 0 because thats the client for (s32 i = 1; i < MAX_PLAYERS; i++) { - players[i].talking = false; + gVoicePlayers[i].talking = false; - if (!voicechat_is_ingame(i) || players[i].muted_state != VOICECHAT_UNMUTED || client->muted_state & VOICECHAT_MUTE_DEAFENED) { - buffer_drain(&players[i].audio); + if (!voicechat_is_ingame(i) || gVoicePlayers[i].clientMutedState != VOICECHAT_UNMUTED || gVoicePlayer->clientMutedState & VOICECHAT_MUTE_DEAFENED) { + buffer_drain(&gVoicePlayers[i].internal.buffer); continue; } - if (voicechat_num_frames_in_buffer(&players[i].audio) < MIN_FRAMES_REQUIRED) continue; + if (voicechat_num_frames_in_buffer(&gVoicePlayers[i].internal.buffer) < MIN_FRAMES_REQUIRED) continue; - players[i].talking = true; + gVoicePlayers[i].talking = true; s16 player_pcm[num_samples]; - u32 n = buffer_read(&players[i].audio, sizeof(player_pcm), player_pcm) / sizeof(s16); + u32 n = buffer_read(&gVoicePlayers[i].internal.buffer, sizeof(player_pcm), player_pcm) / sizeof(s16); memset(player_pcm + n, 0, sizeof(player_pcm) - n * sizeof(s16)); f32 vol_left = 1, vol_right = 1; @@ -325,14 +303,14 @@ void voicechat_mix(s16* out_pcm, u32 num_out_samples) { for (s32 s = 0; s < num_samples * 2; s++) { float pan_factor = s % 2 == 0 ? vol_left : vol_right; - s32 mixed_sample = mixed[s] + (s16)(player_pcm[(int)(s / 2)] * pan_factor * (players[i].volume / 100.f) * (configVoiceChatVolume / 127.f)); + s32 mixed_sample = mixed[s] + (s16)(player_pcm[(int)(s / 2)] * pan_factor * (gVoicePlayers[i].volume / 100.f) * (configVoiceChatVolume / 127.f)); mixed[s] = mixed_sample > 32767 ? 32767 : mixed_sample < -32767 ? -32767 : mixed_sample; } } - if (voicechat_loopback) { + if (gVoiceChatLoopback) { s16 pcm[num_samples]; - u32 n = buffer_read(&loopback_buffer, sizeof(pcm), pcm) / sizeof(s16); + u32 n = buffer_read(&sLoopbackBuffer, sizeof(pcm), pcm) / sizeof(s16); memset(pcm + n, 0, sizeof(pcm) - n * sizeof(s16)); for (s32 s = 0; s < num_samples * 2; s++) { @@ -343,3 +321,44 @@ void voicechat_mix(s16* out_pcm, u32 num_out_samples) { mix_and_resample_stereo_pcm(out_pcm, mixed, num_out_samples, num_samples); } + +void voicechat_toggle_mute() { + voicechat_set_mute(!(gVoicePlayer->clientMutedState & VOICECHAT_MUTE_LOCAL)); +} + +void voicechat_toggle_global_mute(s32 id) { + voicechat_set_global_mute(id, !(gVoicePlayers[id].clientMutedState & VOICECHAT_MUTE_GLOBAL)); +} + +void voicechat_toggle_mute_other(s32 id) { + voicechat_set_mute_other(id, !(gVoicePlayers[id].clientMutedState & VOICECHAT_MUTE_LOCAL)); +} + +void voicechat_toggle_deafen() { + voicechat_set_deafen(!(gVoicePlayer->clientMutedState & VOICECHAT_MUTE_DEAFENED)); +} + +void voicechat_set_mute(bool muted) { + if (muted) gVoicePlayer->clientMutedState |= VOICECHAT_MUTE_LOCAL; + else gVoicePlayer->clientMutedState &= ~VOICECHAT_MUTE_LOCAL; +} + +void voicechat_set_global_mute(s32 id, bool muted) { + if (!gNetworkPlayers[0].moderator && gNetworkPlayers[0].globalIndex != 0) return; + + if (muted) gVoicePlayers[id].clientMutedState |= VOICECHAT_MUTE_GLOBAL; + else gVoicePlayers[id].clientMutedState &= ~VOICECHAT_MUTE_GLOBAL; + network_send_voicechat_muted(gNetworkPlayers[id].globalIndex, VOICECHAT_MUTE_GLOBAL, muted); +} + +void voicechat_set_mute_other(s32 id, bool muted) { + if (muted) gVoicePlayers[id].clientMutedState |= VOICECHAT_MUTE_LOCAL; + else gVoicePlayers[id].clientMutedState &= ~VOICECHAT_MUTE_LOCAL; + network_send_voicechat_muted(gNetworkPlayers[id].globalIndex, VOICECHAT_MUTE_LOCAL, muted); +} + +void voicechat_set_deafen(bool muted) { + if (muted) gVoicePlayer->clientMutedState |= VOICECHAT_MUTE_DEAFENED; + else gVoicePlayer->clientMutedState &= ~VOICECHAT_MUTE_DEAFENED; + network_send_voicechat_muted(gNetworkPlayers[0].globalIndex, VOICECHAT_MUTE_DEAFENED, muted); +} diff --git a/src/pc/voice_chat.h b/src/pc/voice_chat.h index 25748e74b..f7ac66e52 100644 --- a/src/pc/voice_chat.h +++ b/src/pc/voice_chat.h @@ -3,8 +3,7 @@ #include "types.h" -extern bool voicechat_loopback; -extern float voicechat_mic_level; +#include enum VoiceChatType { VOICECHAT_TYPE_DISABLED, @@ -18,32 +17,95 @@ enum VoiceChatActivationMode { VOICECHAT_ACTMODE_THRESHOLD }; -extern enum VoiceChatError { +enum VoiceChatError { VOICECHAT_ERR_NONE, VOICECHAT_ERR_NO_MICROPHONE, VOICECHAT_ERR_FAILED_TO_INITIALIZE, VOICECHAT_ERR_FAILED_TO_ENCODE, VOICECHAT_ERR_FAILED_TO_DECODE, -} voicechat_error[MAX_PLAYERS]; +}; -extern enum VoiceChatMuteState { +enum VoiceChatMuteState { VOICECHAT_UNMUTED = 0, VOICECHAT_MUTE_LOCAL = (1 << 0), VOICECHAT_MUTE_GLOBAL = (1 << 1), VOICECHAT_MUTE_DEAFENED = (1 << 2), -} voicechat_others_muted[MAX_PLAYERS]; +}; + +struct VoiceBuffer { + bool dynamic; + u32 size, capacity; + u32 tail, head; + u8* bytes; +}; + +struct VoicePlayerInternal { + struct VoiceBuffer buffer; + union { + OpusEncoder* encoder; + OpusDecoder* decoder; + }; +}; + +struct VoicePlayer { + bool talking; + u32 volume; + enum VoiceChatError error; + enum VoiceChatMuteState clientMutedState; // did I mute them? + enum VoiceChatMuteState playerMutedState; // did they mute themselves? + struct VoicePlayerInternal internal; +}; + +extern struct VoicePlayer gVoicePlayers[MAX_PLAYERS]; +extern struct VoicePlayer* gVoicePlayer; + +extern bool gVoiceChatLoopback; +extern float gVoiceChatMicLevel; +extern s32 gVoiceChatDefaultChannel; void voicechat_init(); -bool voicechat_inited(); void voicechat_shutdown(); -u32* voicechat_player_muted(s32 id); -u32* voicechat_player_volume(s32 id); -bool voicechat_player_is_talking(s32 id); +void voicechat_init_player(s32 id); u32 voicechat_encode_audio(u8* packet, u32 max_size); void voicechat_decode_audio(s32 id, u8* packet, u32 packet_size); void voicechat_mix(s16* out_pcm, u32 num_samples); +/* |description|Toggles mute state on the current player|descriptionEnd| */ +void voicechat_toggle_mute(); +/* |description|Toggles global mute state on another player|descriptionEnd| */ +void voicechat_toggle_global_mute(s32 id); +/* |description|Toggles mute state on another player for the current client|descriptionEnd| */ +void voicechat_toggle_mute_other(s32 id); +/* |description|Toggles deafen state on the current player|descriptionEnd| */ +void voicechat_toggle_deafen(); +/* |description|Sets mute state on the current player|descriptionEnd| */ +void voicechat_set_mute(bool muted); +/* |description|Sets global mute state on another player|descriptionEnd| */ +void voicechat_set_global_mute(s32 id, bool muted); +/* |description|Sets mute state on another player for the current client|descriptionEnd| */ +void voicechat_set_mute_other(s32 id, bool muted); +/* |description|Sets deafen state on the current player|descriptionEnd| */ +void voicechat_set_deafen(bool muted); + +/* |description|Creates a new voice channel and returns its ID|descriptionEnd| */ +s32 voicechat_create_channel(); +/* |description| +Removes a voice channel. +All players in the to-be-removed channel will get moved to the default channel +|descriptionEnd| */ +void voicechat_remove_channel(s32 channel); + +/* |description|Returns the channel ID that the player is in|descriptionEnd| */ +s32 voicechat_get_channel(s32 local_id); +/* |description|Moves a player to another channel|descriptionEnd| */ +void voicechat_switch_channel(s32 local_id, s32 channel); + +/* |description|Sets if players in `channel` should be able to hear players in `other_channel`|descriptionEnd| */ +void voicechat_hear(s32 channel, s32 other_channel, bool can_hear); +/* |description|Checks if players in `channel` can hear players in `other_channel`|descriptionEnd| */ +void voicechat_can_hear(s32 channel, s32 other_channel); + #endif \ No newline at end of file