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