[voice chat] rename symbols for consistency with other APIs

This commit is contained in:
Dominicentek 2026-06-02 20:58:20 +02:00
parent 52a1c122cb
commit a1f95525b5
10 changed files with 233 additions and 165 deletions

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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) {

View file

@ -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 };

View file

@ -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);

View file

@ -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;
}

View file

@ -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);
}
}
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -3,8 +3,7 @@
#include "types.h"
extern bool voicechat_loopback;
extern float voicechat_mic_level;
#include <opus/opus.h>
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