OpenGothic
Open source reimplementation of Gothic I and II
Loading...
Searching...
No Matches
gamemusic.cpp
Go to the documentation of this file.
1#include "gamemusic.h"
2#include "gothic.h"
3
4#include <Tempest/Sound>
5#include <Tempest/Log>
6
8#include "dmusic/mixer.h"
9#include "resources.h"
10#include "dmusic.h"
11
12using namespace Tempest;
13
14static constexpr uint16_t SAMPLE_RATE = 44100;
15
16struct GameMusic::MusicProvider : Tempest::SoundProducer {
17 using Tempest::SoundProducer::SoundProducer;
18
19 public:
20 virtual void stopTheme() {
21 std::lock_guard<std::recursive_mutex> guard(pendingSync);
22 pendingMusic.reset();
23 }
24
25 void setEnabled(bool b) {
26 if(enable == b)
27 return;
28
29 std::lock_guard<std::recursive_mutex> guard(pendingSync);
30 if(b) {
31 hasPending = true;
32 reloadTheme = true;
33 enable.store(true);
34 } else {
35 enable.store(false);
36 stopTheme();
37 }
38 }
39
40 bool isEnabled() const {
41 return enable.load();
42 }
43
44 void playTheme(const zenkit::IMusicTheme &theme, GameMusic::Tags tags) {
45 std::lock_guard<std::recursive_mutex> guard(pendingSync);
46 reloadTheme = !pendingMusic || pendingMusic->file != theme.file;
47 pendingMusic = theme;
48 pendingTags = tags;
49 hasPending = true;
50 }
51
52 protected:
53 bool updateTheme(zenkit::IMusicTheme& theme, Tags& tags) {
54 bool reloadTheme = false;
55
56 std::lock_guard<std::recursive_mutex> guard(pendingSync);
57 if(hasPending && pendingMusic && enable.load()) {
58 hasPending = false;
59 reloadTheme = this->reloadTheme;
60 theme = this->pendingMusic.value();
61 tags = this->pendingTags;
62 }
63
64 this->reloadTheme = false;
65 return reloadTheme;
66 }
67
68 static zenkit::MusicTransitionEffect computeTransitionEffect(Tags nextTags, Tags currTags, zenkit::MusicTransitionEffect transtype) {
69 const int cur = currTags & (Tags::Std | Tags::Fgt | Tags::Thr);
70 const int next = nextTags & (Tags::Std | Tags::Fgt | Tags::Thr);
71
72 //zenkit::MusicTransitionEffect embellishment = transtype;
73 zenkit::MusicTransitionEffect embellishment = zenkit::MusicTransitionEffect::NONE;
74 if(next == Tags::Std) {
75 if(cur != Tags::Std)
76 embellishment = zenkit::MusicTransitionEffect::BREAK;
77 }
78 else if(next == Tags::Fgt) {
79 if(cur == Tags::Thr)
80 embellishment = zenkit::MusicTransitionEffect::FILL;
81 }
82 else if(next == Tags::Thr) {
83 if(cur == Tags::Fgt)
84 embellishment = zenkit::MusicTransitionEffect::NONE;
85 }
86
87 return embellishment;
88 }
89
90 private:
91 std::atomic_bool enable{true};
92
93 std::recursive_mutex pendingSync;
94 bool hasPending = false;
95 bool reloadTheme = false;
96 Tags pendingTags = Tags::Day;
97 std::optional<zenkit::IMusicTheme> pendingMusic;
98
99 protected:
101 };
102
104 using GameMusic::MusicProvider::MusicProvider;
105
106 void renderSound(int16_t *out, size_t n) override {
107 if(!isEnabled()) {
108 std::memset(out, 0, n * sizeof(int32_t) * 2);
109 return;
110 }
111 updateTheme();
112 mix.mix(out, n);
113 }
114
115 void updateTheme() {
116 zenkit::IMusicTheme theme;
117 Tags tags;
119 return;
120
121 if(theme.file.empty()) {
122 stopTheme();
123 return;
124 }
125
126 try {
127 if(/*reloadTheme*/true) {
129
130 Dx8::Music m;
131 m.addPattern(p);
132
133 auto effect = computeTransitionEffect(tags, currentTags, theme.transtype);
135 mix.setMusic(m, em);
137 }
138 mix.setMusicVolume(theme.vol);
139 }
140 catch (std::runtime_error &) {
141 Log::e("unable to load sound: \"", theme.file, "\"");
142 stopTheme();
143 }
144 catch (std::bad_alloc &) {
145 Log::e("out of memory for sound: \"", theme.file, "\"");
146 stopTheme();
147 }
148 }
149
150 void stopTheme() override {
152 mix.setMusic(Dx8::Music());
153 }
154
155 static Dx8::DMUS_EMBELLISHT_TYPES computeEmbellishment(zenkit::MusicTransitionEffect ef) {
156 switch (ef) {
157 case zenkit::MusicTransitionEffect::UNKNOWN:
158 case zenkit::MusicTransitionEffect::NONE:
160 case zenkit::MusicTransitionEffect::GROOVE:
162 case zenkit::MusicTransitionEffect::FILL:
164 case zenkit::MusicTransitionEffect::BREAK:
166 case zenkit::MusicTransitionEffect::INTRO:
168 case zenkit::MusicTransitionEffect::END:
169 case zenkit::MusicTransitionEffect::END_AND_INTO:
171 }
173 }
174
175 private:
176 Dx8::Mixer mix;
177 };
178
179static std::pair<DmTiming, DmEmbellishmentType> computeEmbellishmentAndTiming(const zenkit::MusicTransitionEffect effect, const zenkit::MusicTransitionType type) {
180 DmEmbellishmentType embellishment = DmEmbellishment_NONE;
181 switch (effect) {
182 case zenkit::MusicTransitionEffect::UNKNOWN:
183 case zenkit::MusicTransitionEffect::NONE:
184 embellishment = DmEmbellishment_NONE;
185 break;
186 case zenkit::MusicTransitionEffect::GROOVE:
187 embellishment = DmEmbellishment_GROOVE;
188 break;
189 case zenkit::MusicTransitionEffect::FILL:
190 embellishment = DmEmbellishment_FILL;
191 break;
192 case zenkit::MusicTransitionEffect::BREAK:
193 embellishment = DmEmbellishment_BREAK;
194 break;
195 case zenkit::MusicTransitionEffect::INTRO:
196 embellishment = DmEmbellishment_INTRO;
197 break;
198 case zenkit::MusicTransitionEffect::END:
199 embellishment = DmEmbellishment_END;
200 break;
201 case zenkit::MusicTransitionEffect::END_AND_INTO:
202 embellishment = DmEmbellishment_END_AND_INTRO;
203 break;
204 }
205
206 DmTiming timing = DmTiming_MEASURE;
207 switch (type) {
208 case zenkit::MusicTransitionType::UNKNOWN:
209 case zenkit::MusicTransitionType::MEASURE:
210 timing = DmTiming_MEASURE;
211 break;
212 case zenkit::MusicTransitionType::IMMEDIATE:
213 timing = DmTiming_INSTANT;
214 break;
215 case zenkit::MusicTransitionType::BEAT:
216 timing = DmTiming_BEAT;
217 break;
218 }
219
220 return std::make_pair(timing, embellishment);
221 }
222
224 GothicKitMusicProvider(uint16_t rate, uint16_t channels) : GameMusic::MusicProvider(rate, channels) {
225 /*
226 Dm_setRandomNumberGenerator([](void*) -> uint32_t {
227 return 0;
228 }, nullptr);
229 */
230
231 DmResult rv = DmPerformance_create(&performance, rate);
232 if(rv != DmResult_SUCCESS) {
233 Log::e("Unable to create DmPerformance object. Out of memory?");
234 }
235 }
236
238 DmPerformance_release(performance);
239 }
240
241 void renderSound(int16_t *out, size_t n) override {
242 if(!isEnabled()) {
243 std::memset(out, 0, n * sizeof(int32_t) * 2);
244 return;
245 }
246 updateTheme();
247 DmPerformance_renderPcm(performance, out, n * 2, DmRenderOptions(DmRender_SHORT | DmRender_STEREO));
248 }
249
250 void updateTheme() {
251 zenkit::IMusicTheme theme;
252 Tags tags;
254 return;
255
256 if(theme.file.empty()) {
257 stopTheme();
258 return;
259 }
260
261 auto effect = computeTransitionEffect(tags, currentTags, theme.transtype);
262 auto [timing, embellishment] = computeEmbellishmentAndTiming(effect, theme.transsubtype);
263
264 DmSegment* sgt = Resources::loadMusicSegment(theme.file.c_str());
265 DmResult rv = DmPerformance_playTransition(performance, sgt, embellishment, timing);
266 if(rv != DmResult_SUCCESS) {
267 Log::e("Failed to play theme: ", theme.file);
268 stopTheme();
269 }
270
271 DmPerformance_setVolume(performance, theme.vol);
272 DmSegment_release(sgt);
274 }
275
276 void stopTheme() override {
278 DmPerformance_playTransition(performance, nullptr, DmEmbellishment_NONE, DmTiming_INSTANT);
279 }
280
281 DmEmbellishmentType computeEmbellishment(Tags nextTags, Tags currTags, zenkit::MusicTransitionEffect transtype) const {
282 const int cur = currTags & (Tags::Std | Tags::Fgt | Tags::Thr);
283 const int next = nextTags & (Tags::Std | Tags::Fgt | Tags::Thr);
284
285 DmEmbellishmentType embellishment = DmEmbellishment_NONE;
286 switch (transtype) {
287 case zenkit::MusicTransitionEffect::UNKNOWN:
288 case zenkit::MusicTransitionEffect::NONE:
289 embellishment = DmEmbellishment_NONE;
290 break;
291 case zenkit::MusicTransitionEffect::GROOVE:
292 embellishment = DmEmbellishment_GROOVE;
293 break;
294 case zenkit::MusicTransitionEffect::FILL:
295 embellishment = DmEmbellishment_FILL;
296 break;
297 case zenkit::MusicTransitionEffect::BREAK:
298 embellishment = DmEmbellishment_BREAK;
299 break;
300 case zenkit::MusicTransitionEffect::INTRO:
301 embellishment = DmEmbellishment_INTRO;
302 break;
303 case zenkit::MusicTransitionEffect::END:
304 embellishment = DmEmbellishment_END;
305 break;
306 case zenkit::MusicTransitionEffect::END_AND_INTO:
307 embellishment = DmEmbellishment_END_AND_INTRO;
308 break;
309 }
310
311 if(next == Tags::Std) {
312 if(cur != Tags::Std)
313 embellishment = DmEmbellishmentType::DmEmbellishment_BREAK;
314 }
315 else if(next == Tags::Fgt) {
316 if(cur == Tags::Thr)
317 embellishment = DmEmbellishmentType::DmEmbellishment_FILL;
318 }
319 else if(next == Tags::Thr) {
320 if(cur == Tags::Fgt)
321 embellishment = DmEmbellishmentType::DmEmbellishment_NONE;
322 }
323
324 return embellishment;
325 }
326
327 private:
328 DmPerformance *performance = nullptr;
329 };
330
331static constexpr int PROVIDER_UNINITIALIZED = -1;
332static constexpr int PROVIDER_OPENGOTHIC = 0;
333// static constexpr int PROVIDER_GOTHICKIT = 1;
334
335GameMusic *GameMusic::instance = nullptr;
336
338 instance = this;
339 provider = PROVIDER_UNINITIALIZED;
340
341 Gothic::inst().onSettingsChanged.bind(this, &GameMusic::setupSettings);
342 setupSettings();
343 }
344
346 sound = SoundEffect();
347 instance = nullptr;
348 Gothic::inst().onSettingsChanged.ubind(this, &GameMusic::setupSettings);
349 }
350
352 return *instance;
353 }
354
356 return Tags(daytime | mode);
357 }
358
360 impl->setEnabled(e);
361 }
362
364 return impl->isEnabled();
365 }
366
368 const char *clsTheme = "";
369 switch (m) {
371 clsTheme = "SYS_Loading";
372 break;
373 }
374 if(auto theme = Gothic::musicDef()[clsTheme])
376 }
377
378void GameMusic::setMusic(const zenkit::IMusicTheme &theme, Tags tags) {
379 currentMusic.theme = theme;
380 currentMusic.tags = tags;
381 impl->playTheme(currentMusic.theme, currentMusic.tags);
382 }
383
385 setEnabled(false);
386 }
387
388void GameMusic::setupSettings() {
389 const int musicEnabled = Gothic::settingsGetI("SOUND", "musicEnabled");
390 const float musicVolume = Gothic::settingsGetF("SOUND", "musicVolume");
391 const int providerIndex = Gothic::settingsGetI("INTERNAL", "soundProviderIndex");
392
393 if(providerIndex != provider) {
394 Log::i("Switching music provider to ", providerIndex == PROVIDER_OPENGOTHIC ? "'OpenGothic'" : "'GothicKit'");
395 sound = SoundEffect();
396
397 std::unique_ptr<MusicProvider> p;
398 if(providerIndex == PROVIDER_OPENGOTHIC) {
399 p = std::make_unique<OpenGothicMusicProvider>(SAMPLE_RATE, 2);
400 } else {
401 p = std::make_unique<GothicKitMusicProvider>(SAMPLE_RATE, 2);
402 }
403
404 provider = providerIndex;
405 impl = p.get();
406 impl->playTheme(currentMusic.theme, currentMusic.tags);
407
408 sound = device.load(std::move(p));
409 sound.play();
410 }
411
412 setEnabled(musicEnabled != 0);
413 sound.setVolume(musicVolume);
414 }
void setMusic(const Music &m, DMUS_EMBELLISHT_TYPES embellishment=DMUS_EMBELLISHT_NORMAL)
Definition mixer.cpp:33
void setMusicVolume(float v)
Definition mixer.cpp:41
void mix(int16_t *out, size_t samples)
Definition mixer.cpp:253
void addPattern(const PatternList &list)
Definition music.cpp:10
void setEnabled(bool e)
static Tags mkTags(Tags daytime, Tags mode)
static GameMusic & inst()
zenkit::IMusicTheme theme
Definition gamemusic.h:47
void setMusic(Music m)
bool isEnabled() const
void stopMusic()
Tags tags
Definition gamemusic.h:48
Tempest::Signal< void()> onSettingsChanged
Definition gothic.h:182
static Gothic & inst()
Definition gothic.cpp:249
static int settingsGetI(std::string_view sec, std::string_view name)
Definition gothic.cpp:807
static const MusicDefinitions & musicDef()
Definition gothic.cpp:659
static float settingsGetF(std::string_view sec, std::string_view name)
Definition gothic.cpp:841
static Dx8::PatternList loadDxMusic(std::string_view name)
static DmSegment * loadMusicSegment(char const *name)
static constexpr int PROVIDER_UNINITIALIZED
static constexpr uint16_t SAMPLE_RATE
Definition gamemusic.cpp:14
static std::pair< DmTiming, DmEmbellishmentType > computeEmbellishmentAndTiming(const zenkit::MusicTransitionEffect effect, const zenkit::MusicTransitionType type)
static constexpr int PROVIDER_OPENGOTHIC
DMUS_EMBELLISHT_TYPES
Definition structs.h:93
@ DMUS_EMBELLISHT_INTRO
Definition structs.h:97
@ DMUS_EMBELLISHT_END
Definition structs.h:98
@ DMUS_EMBELLISHT_FILL
Definition structs.h:95
@ DMUS_EMBELLISHT_BREAK
Definition structs.h:96
@ DMUS_EMBELLISHT_NORMAL
Definition structs.h:94
DmEmbellishmentType computeEmbellishment(Tags nextTags, Tags currTags, zenkit::MusicTransitionEffect transtype) const
void renderSound(int16_t *out, size_t n) override
GothicKitMusicProvider(uint16_t rate, uint16_t channels)
void playTheme(const zenkit::IMusicTheme &theme, GameMusic::Tags tags)
Definition gamemusic.cpp:44
virtual void stopTheme()
Definition gamemusic.cpp:20
static zenkit::MusicTransitionEffect computeTransitionEffect(Tags nextTags, Tags currTags, zenkit::MusicTransitionEffect transtype)
Definition gamemusic.cpp:68
bool updateTheme(zenkit::IMusicTheme &theme, Tags &tags)
Definition gamemusic.cpp:53
static Dx8::DMUS_EMBELLISHT_TYPES computeEmbellishment(zenkit::MusicTransitionEffect ef)
void renderSound(int16_t *out, size_t n) override