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 | |
15 | CSoundLoading::CSoundLoading(CGameClient *pGameClient, bool Render) : |
16 | m_pGameClient(pGameClient), |
17 | m_Render(Render) |
18 | { |
19 | Abortable(Abortable: true); |
20 | } |
21 | |
22 | void 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 | |
46 | int 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 | |
68 | void 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 | |
101 | void CSounds::OnReset() |
102 | { |
103 | if(Client()->State() >= IClient::STATE_ONLINE) |
104 | { |
105 | Sound()->StopAll(); |
106 | ClearQueue(); |
107 | } |
108 | } |
109 | |
110 | void CSounds::OnStateChange(int NewState, int OldState) |
111 | { |
112 | if(NewState == IClient::STATE_ONLINE || NewState == IClient::STATE_DEMOPLAYBACK) |
113 | OnReset(); |
114 | } |
115 | |
116 | void 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 | |
174 | void CSounds::ClearQueue() |
175 | { |
176 | mem_zero(block: m_aQueue, size: sizeof(m_aQueue)); |
177 | m_QueuePos = 0; |
178 | m_QueueWaitTime = time(); |
179 | } |
180 | |
181 | void 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 | |
194 | void 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 | |
203 | void 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 | |
221 | void 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 | |
239 | void 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 | |
250 | bool 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 | |
262 | ISound::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 | |
273 | ISound::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 | |