| 1 | /* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ |
| 2 | /* If you are missing that file, acquire a complete release at teeworlds.com. */ |
| 3 | |
| 4 | #include "sounds.h" |
| 5 | |
| 6 | #include <engine/engine.h> |
| 7 | #include <engine/shared/config.h> |
| 8 | #include <engine/sound.h> |
| 9 | |
| 10 | #include <generated/client_data.h> |
| 11 | |
| 12 | #include <game/client/components/camera.h> |
| 13 | #include <game/client/components/menus.h> |
| 14 | #include <game/client/gameclient.h> |
| 15 | #include <game/localization.h> |
| 16 | |
| 17 | CSoundLoading::CSoundLoading(CGameClient *pGameClient, bool Render) : |
| 18 | m_pGameClient(pGameClient), |
| 19 | m_Render(Render) |
| 20 | { |
| 21 | Abortable(Abortable: true); |
| 22 | } |
| 23 | |
| 24 | void CSoundLoading::Run() |
| 25 | { |
| 26 | for(int s = 0; s < g_pData->m_NumSounds; s++) |
| 27 | { |
| 28 | const char *pLoadingCaption = Localize(pStr: "Loading DDNet Client" ); |
| 29 | const char *pLoadingContent = Localize(pStr: "Loading sound files" ); |
| 30 | |
| 31 | for(int i = 0; i < g_pData->m_aSounds[s].m_NumSounds; i++) |
| 32 | { |
| 33 | if(State() == IJob::STATE_ABORTED) |
| 34 | return; |
| 35 | |
| 36 | int Id = m_pGameClient->Sound()->LoadWV(pFilename: g_pData->m_aSounds[s].m_aSounds[i].m_pFilename); |
| 37 | g_pData->m_aSounds[s].m_aSounds[i].m_Id = Id; |
| 38 | // try to render a frame |
| 39 | if(m_Render) |
| 40 | m_pGameClient->m_Menus.RenderLoading(pCaption: pLoadingCaption, pContent: pLoadingContent, IncreaseCounter: 0); |
| 41 | } |
| 42 | |
| 43 | if(m_Render) |
| 44 | m_pGameClient->m_Menus.RenderLoading(pCaption: pLoadingCaption, pContent: pLoadingContent, IncreaseCounter: 1); |
| 45 | } |
| 46 | } |
| 47 | |
| 48 | void CSounds::UpdateChannels() |
| 49 | { |
| 50 | const float NewGuiSoundVolume = g_Config.m_SndChatVolume / 100.0f; |
| 51 | if(NewGuiSoundVolume != m_GuiSoundVolume) |
| 52 | { |
| 53 | m_GuiSoundVolume = NewGuiSoundVolume; |
| 54 | Sound()->SetChannel(ChannelId: CSounds::CHN_GUI, Volume: m_GuiSoundVolume, Panning: 0.0f); |
| 55 | } |
| 56 | |
| 57 | const float NewGameSoundVolume = g_Config.m_SndGameVolume / 100.0f; |
| 58 | if(NewGameSoundVolume != m_GameSoundVolume) |
| 59 | { |
| 60 | m_GameSoundVolume = NewGameSoundVolume; |
| 61 | Sound()->SetChannel(ChannelId: CSounds::CHN_WORLD, Volume: 0.9f * m_GameSoundVolume, Panning: 1.0f); |
| 62 | Sound()->SetChannel(ChannelId: CSounds::CHN_GLOBAL, Volume: m_GameSoundVolume, Panning: 0.0f); |
| 63 | } |
| 64 | |
| 65 | const float NewMapSoundVolume = g_Config.m_SndMapVolume / 100.0f; |
| 66 | if(NewMapSoundVolume != m_MapSoundVolume) |
| 67 | { |
| 68 | m_MapSoundVolume = NewMapSoundVolume; |
| 69 | Sound()->SetChannel(ChannelId: CSounds::CHN_MAPSOUND, Volume: m_MapSoundVolume, Panning: 1.0f); |
| 70 | } |
| 71 | |
| 72 | const float NewBackgroundMusicVolume = g_Config.m_SndBackgroundMusicVolume / 100.0f; |
| 73 | if(NewBackgroundMusicVolume != m_BackgroundMusicVolume) |
| 74 | { |
| 75 | m_BackgroundMusicVolume = NewBackgroundMusicVolume; |
| 76 | Sound()->SetChannel(ChannelId: CSounds::CHN_MUSIC, Volume: m_BackgroundMusicVolume, Panning: 1.0f); |
| 77 | } |
| 78 | } |
| 79 | |
| 80 | int CSounds::GetSampleId(int SetId) |
| 81 | { |
| 82 | if(!g_Config.m_SndEnable || !Sound()->IsSoundEnabled() || m_WaitForSoundJob || SetId < 0 || SetId >= g_pData->m_NumSounds) |
| 83 | return -1; |
| 84 | |
| 85 | CDataSoundset *pSet = &g_pData->m_aSounds[SetId]; |
| 86 | if(!pSet->m_NumSounds) |
| 87 | return -1; |
| 88 | |
| 89 | if(pSet->m_NumSounds == 1) |
| 90 | return pSet->m_aSounds[0].m_Id; |
| 91 | |
| 92 | // return random one |
| 93 | int Id; |
| 94 | do |
| 95 | { |
| 96 | Id = rand() % pSet->m_NumSounds; |
| 97 | } while(Id == pSet->m_Last); |
| 98 | pSet->m_Last = Id; |
| 99 | return pSet->m_aSounds[Id].m_Id; |
| 100 | } |
| 101 | |
| 102 | void CSounds::OnInit() |
| 103 | { |
| 104 | UpdateChannels(); |
| 105 | ClearQueue(); |
| 106 | |
| 107 | // load sounds |
| 108 | if(g_Config.m_ClThreadsoundloading) |
| 109 | { |
| 110 | m_pSoundJob = std::make_shared<CSoundLoading>(args: GameClient(), args: false); |
| 111 | GameClient()->Engine()->AddJob(pJob: m_pSoundJob); |
| 112 | m_WaitForSoundJob = true; |
| 113 | GameClient()->m_Menus.RenderLoading(pCaption: Localize(pStr: "Loading DDNet Client" ), pContent: Localize(pStr: "Loading sound files" ), IncreaseCounter: 0); |
| 114 | } |
| 115 | else |
| 116 | { |
| 117 | CSoundLoading(GameClient(), true).Run(); |
| 118 | m_WaitForSoundJob = false; |
| 119 | } |
| 120 | } |
| 121 | |
| 122 | void CSounds::OnReset() |
| 123 | { |
| 124 | if(Client()->State() >= IClient::STATE_ONLINE) |
| 125 | { |
| 126 | Sound()->StopAll(); |
| 127 | ClearQueue(); |
| 128 | } |
| 129 | } |
| 130 | |
| 131 | void CSounds::OnStateChange(int NewState, int OldState) |
| 132 | { |
| 133 | if(NewState == IClient::STATE_ONLINE || NewState == IClient::STATE_DEMOPLAYBACK) |
| 134 | OnReset(); |
| 135 | } |
| 136 | |
| 137 | void CSounds::OnRender() |
| 138 | { |
| 139 | // check for sound initialisation |
| 140 | if(m_WaitForSoundJob) |
| 141 | { |
| 142 | if(m_pSoundJob->State() == IJob::STATE_DONE) |
| 143 | m_WaitForSoundJob = false; |
| 144 | else |
| 145 | return; |
| 146 | } |
| 147 | |
| 148 | Sound()->SetListenerPosition(GameClient()->m_Camera.m_Center); |
| 149 | UpdateChannels(); |
| 150 | |
| 151 | // play sound from queue |
| 152 | if(m_QueuePos > 0) |
| 153 | { |
| 154 | int64_t Now = time(); |
| 155 | if(m_QueueWaitTime <= Now) |
| 156 | { |
| 157 | Play(Channel: m_aQueue[0].m_Channel, SetId: m_aQueue[0].m_SetId, Volume: 1.0f); |
| 158 | m_QueueWaitTime = Now + time_freq() * 3 / 10; // wait 300ms before playing the next one |
| 159 | if(--m_QueuePos > 0) |
| 160 | mem_move(dest: m_aQueue, source: m_aQueue + 1, size: m_QueuePos * sizeof(CQueueEntry)); |
| 161 | } |
| 162 | } |
| 163 | } |
| 164 | |
| 165 | void CSounds::ClearQueue() |
| 166 | { |
| 167 | mem_zero(block: m_aQueue, size: sizeof(m_aQueue)); |
| 168 | m_QueuePos = 0; |
| 169 | m_QueueWaitTime = time(); |
| 170 | } |
| 171 | |
| 172 | void CSounds::Enqueue(int Channel, int SetId) |
| 173 | { |
| 174 | if(GameClient()->m_SuppressEvents) |
| 175 | return; |
| 176 | if(m_QueuePos >= QUEUE_SIZE) |
| 177 | return; |
| 178 | if(Channel != CHN_MUSIC && g_Config.m_ClEditor) |
| 179 | return; |
| 180 | |
| 181 | m_aQueue[m_QueuePos].m_Channel = Channel; |
| 182 | m_aQueue[m_QueuePos++].m_SetId = SetId; |
| 183 | } |
| 184 | |
| 185 | void CSounds::PlayAndRecord(int Channel, int SetId, float Volume, vec2 Position) |
| 186 | { |
| 187 | // TODO: Volume and position are currently not recorded for sounds played with this function |
| 188 | // TODO: This also causes desync sounds during demo playback of demos recorded on high ping servers: |
| 189 | // https://github.com/ddnet/ddnet/issues/1282 |
| 190 | CNetMsg_Sv_SoundGlobal Msg; |
| 191 | Msg.m_SoundId = SetId; |
| 192 | Client()->SendPackMsgActive(pMsg: &Msg, Flags: MSGFLAG_NOSEND | MSGFLAG_RECORD); |
| 193 | |
| 194 | PlayAt(Channel, SetId, Volume, Position); |
| 195 | } |
| 196 | |
| 197 | void CSounds::Play(int Channel, int SetId, float Volume) |
| 198 | { |
| 199 | PlaySample(Channel, SampleId: GetSampleId(SetId), Flags: 0, Volume); |
| 200 | } |
| 201 | |
| 202 | void CSounds::PlayAt(int Channel, int SetId, float Volume, vec2 Position) |
| 203 | { |
| 204 | PlaySampleAt(Channel, SampleId: GetSampleId(SetId), Flags: 0, Volume, Position); |
| 205 | } |
| 206 | |
| 207 | void CSounds::Stop(int SetId) |
| 208 | { |
| 209 | if(m_WaitForSoundJob || SetId < 0 || SetId >= g_pData->m_NumSounds) |
| 210 | return; |
| 211 | |
| 212 | const CDataSoundset *pSet = &g_pData->m_aSounds[SetId]; |
| 213 | for(int i = 0; i < pSet->m_NumSounds; i++) |
| 214 | if(pSet->m_aSounds[i].m_Id != -1) |
| 215 | Sound()->Stop(SampleId: pSet->m_aSounds[i].m_Id); |
| 216 | } |
| 217 | |
| 218 | bool CSounds::IsPlaying(int SetId) |
| 219 | { |
| 220 | if(m_WaitForSoundJob || SetId < 0 || SetId >= g_pData->m_NumSounds) |
| 221 | return false; |
| 222 | |
| 223 | const CDataSoundset *pSet = &g_pData->m_aSounds[SetId]; |
| 224 | for(int i = 0; i < pSet->m_NumSounds; i++) |
| 225 | if(pSet->m_aSounds[i].m_Id != -1 && Sound()->IsPlaying(SampleId: pSet->m_aSounds[i].m_Id)) |
| 226 | return true; |
| 227 | return false; |
| 228 | } |
| 229 | |
| 230 | ISound::CVoiceHandle CSounds::PlaySample(int Channel, int SampleId, int Flags, float Volume) |
| 231 | { |
| 232 | if(GameClient()->m_SuppressEvents || (Channel == CHN_MUSIC && !g_Config.m_SndMusic) || SampleId == -1) |
| 233 | return ISound::CVoiceHandle(); |
| 234 | |
| 235 | if(Channel == CHN_MUSIC) |
| 236 | Flags |= ISound::FLAG_LOOP; |
| 237 | |
| 238 | return Sound()->Play(ChannelId: Channel, SampleId, Flags, Volume); |
| 239 | } |
| 240 | |
| 241 | ISound::CVoiceHandle CSounds::PlaySampleAt(int Channel, int SampleId, int Flags, float Volume, vec2 Position) |
| 242 | { |
| 243 | if(GameClient()->m_SuppressEvents || (Channel == CHN_MUSIC && !g_Config.m_SndMusic) || SampleId == -1) |
| 244 | return ISound::CVoiceHandle(); |
| 245 | |
| 246 | if(Channel == CHN_MUSIC) |
| 247 | Flags |= ISound::FLAG_LOOP; |
| 248 | |
| 249 | return Sound()->PlayAt(ChannelId: Channel, SampleId, Flags, Volume, Position); |
| 250 | } |
| 251 | |