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
17CSoundLoading::CSoundLoading(CGameClient *pGameClient, bool Render) :
18 m_pGameClient(pGameClient),
19 m_Render(Render)
20{
21 Abortable(Abortable: true);
22}
23
24void 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
48void 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
80int 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
102void 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
122void CSounds::OnReset()
123{
124 if(Client()->State() >= IClient::STATE_ONLINE)
125 {
126 Sound()->StopAll();
127 ClearQueue();
128 }
129}
130
131void CSounds::OnStateChange(int NewState, int OldState)
132{
133 if(NewState == IClient::STATE_ONLINE || NewState == IClient::STATE_DEMOPLAYBACK)
134 OnReset();
135}
136
137void 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
165void CSounds::ClearQueue()
166{
167 mem_zero(block: m_aQueue, size: sizeof(m_aQueue));
168 m_QueuePos = 0;
169 m_QueueWaitTime = time();
170}
171
172void 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
185void 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
197void CSounds::Play(int Channel, int SetId, float Volume)
198{
199 PlaySample(Channel, SampleId: GetSampleId(SetId), Flags: 0, Volume);
200}
201
202void CSounds::PlayAt(int Channel, int SetId, float Volume, vec2 Position)
203{
204 PlaySampleAt(Channel, SampleId: GetSampleId(SetId), Flags: 0, Volume, Position);
205}
206
207void 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
218bool 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
230ISound::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
241ISound::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