mirror of
https://github.com/coop-deluxe/sm64coopdx.git
synced 2026-07-06 14:36:46 +00:00
[voice chat] rename symbols for consistency with other APIs
This commit is contained in:
parent
52a1c122cb
commit
a1f95525b5
10 changed files with 233 additions and 165 deletions
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
Loading…
Add table
Reference in a new issue