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