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