OpenTTD Source 20241224-master-gf74b0cf984
sound.cpp
Go to the documentation of this file.
1/*
2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
6 */
7
10#include "stdafx.h"
11#include "landscape.h"
12#include "sound_type.h"
13#include "soundloader_func.h"
14#include "mixer.h"
15#include "newgrf_sound.h"
17#include "window_func.h"
18#include "window_gui.h"
19#include "vehicle_base.h"
20
21/* The type of set we're replacing */
22#define SET_TYPE "sounds"
23#include "base_media_func.h"
24
25#include "safeguards.h"
26
27static std::array<SoundEntry, ORIGINAL_SAMPLE_COUNT> _original_sounds;
28
29static void OpenBankFile(const std::string &filename)
30{
35 static std::unique_ptr<RandomAccessFile> original_sound_file;
36
37 _original_sounds.fill({});
38
39 /* If there is no sound file (nosound set), don't load anything */
40 if (filename.empty()) return;
41
42 original_sound_file = std::make_unique<RandomAccessFile>(filename, BASESET_DIR);
43 size_t pos = original_sound_file->GetPos();
44 uint count = original_sound_file->ReadDword();
45
46 /* The new format has the highest bit always set */
47 auto source = HasBit(count, 31) ? SoundSource::BasesetNewFormat : SoundSource::BasesetOldFormat;
48 ClrBit(count, 31);
49 count /= 8;
50
51 /* Simple check for the correct number of original sounds. */
52 if (count != ORIGINAL_SAMPLE_COUNT) {
53 /* Corrupt sample data? Just leave the allocated memory as those tell
54 * there is no sound to play (size = 0 due to calloc). Not allocating
55 * the memory disables valid NewGRFs that replace sounds. */
56 Debug(misc, 6, "Incorrect number of sounds in '{}', ignoring.", filename);
57 return;
58 }
59
60 original_sound_file->SeekTo(pos, SEEK_SET);
61
62 /* Read sound file positions. */
63 for (auto &sound : _original_sounds) {
64 sound.file = original_sound_file.get();
65 sound.file_offset = GB(original_sound_file->ReadDword(), 0, 31) + pos;
66 sound.file_size = original_sound_file->ReadDword();
67 sound.source = source;
68 }
69}
70
71static bool SetBankSource(MixerChannel *mc, SoundEntry *sound, SoundID sound_id)
72{
73 assert(sound != nullptr);
74
75 if (sound->file != nullptr) {
76 if (!LoadSound(*sound, sound_id)) {
77 /* Mark as invalid. */
78 sound->file = nullptr;
79 return false;
80 }
81 sound->file = nullptr;
82 }
83
84 /* Check for valid sound. */
85 if (sound->data->empty()) return false;
86
87 MxSetChannelRawSrc(mc, sound->data, sound->rate, sound->bits_per_sample == 16);
88
89 return true;
90}
91
92void InitializeSound()
93{
94 Debug(misc, 1, "Loading sound effects...");
95 OpenBankFile(BaseSounds::GetUsedSet()->files->filename);
96}
97
98
99/* Low level sound player */
100static void StartSound(SoundID sound_id, float pan, uint volume)
101{
102 if (volume == 0) return;
103
104 SoundEntry *sound = GetSound(sound_id);
105 if (sound == nullptr) return;
106
107 if (sound->rate == 0) {
108 /* If the sound's sample rate is not set then the sound needs to be loaded, but if the sound's file pointer
109 * is empty then an attempt was already made to load the sound but it failed. We don't want to try again. */
110 if (sound->file == nullptr) return;
111 }
112
113 MixerChannel *mc = MxAllocateChannel();
114 if (mc == nullptr) return;
115
116 if (!SetBankSource(mc, sound, sound_id)) return;
117
118 /* Apply the sound effect's own volume. */
119 volume = sound->volume * volume;
120
121 MxSetChannelVolume(mc, volume, pan);
122 MxActivateChannel(mc);
123}
124
125
126static const uint8_t _vol_factor_by_zoom[] = {255, 255, 255, 190, 134, 87};
127static_assert(lengthof(_vol_factor_by_zoom) == ZOOM_LVL_END);
128
129static const uint8_t _sound_base_vol[] = {
130 128, 90, 128, 128, 128, 128, 128, 128,
131 128, 90, 90, 128, 128, 128, 128, 128,
132 128, 128, 128, 80, 128, 128, 128, 128,
133 128, 128, 128, 128, 128, 128, 128, 128,
134 128, 128, 90, 90, 90, 128, 90, 128,
135 128, 90, 128, 128, 128, 90, 128, 128,
136 128, 128, 128, 128, 90, 128, 128, 128,
137 128, 90, 128, 128, 128, 128, 128, 128,
138 128, 128, 90, 90, 90, 128, 128, 128,
139 90,
140};
141
142static const uint8_t _sound_idx[] = {
143 2, 3, 4, 5, 6, 7, 8, 9,
144 10, 11, 12, 13, 14, 15, 16, 17,
145 18, 19, 20, 21, 22, 23, 24, 25,
146 26, 27, 28, 29, 30, 31, 32, 33,
147 34, 35, 36, 37, 38, 39, 40, 0,
148 1, 41, 42, 43, 44, 45, 46, 47,
149 48, 49, 50, 51, 52, 53, 54, 55,
150 56, 57, 58, 59, 60, 61, 62, 63,
151 64, 65, 66, 67, 68, 69, 70, 71,
152 72,
153};
154
155void SndCopyToPool()
156{
158 for (uint i = 0; i < ORIGINAL_SAMPLE_COUNT; i++) {
159 sound[i] = _original_sounds[_sound_idx[i]];
160 sound[i].volume = _sound_base_vol[i];
161 sound[i].priority = 0;
162 }
163}
164
169void ChangeSoundSet(int index)
170{
171 if (BaseSounds::GetIndexOfUsedSet() == index) return;
172
173 auto set = BaseSounds::GetSet(index);
174 BaseSounds::ini_set = set->name;
176
178 InitializeSound();
179
180 /* Replace baseset sounds in the pool with the updated original sounds. This is safe to do as
181 * any sound still playing holds its own shared_ptr to the sample data. */
182 for (uint i = 0; i < ORIGINAL_SAMPLE_COUNT; i++) {
183 SoundEntry *sound = GetSound(i);
184 /* GRF Container 0 means the sound comes from the baseset, and isn't overridden by NewGRF. */
185 if (sound == nullptr || sound->grf_container_ver != 0) continue;
186
187 *sound = _original_sounds[_sound_idx[i]];
188 sound->volume = _sound_base_vol[i];
189 sound->priority = 0;
190 }
191
193}
194
204static void SndPlayScreenCoordFx(SoundID sound, int left, int right, int top, int bottom)
205{
206 /* Iterate from back, so that main viewport is checked first */
207 for (const Window *w : Window::IterateFromBack()) {
208 const Viewport *vp = w->viewport;
209
210 if (vp != nullptr &&
211 left < vp->virtual_left + vp->virtual_width && right > vp->virtual_left &&
212 top < vp->virtual_top + vp->virtual_height && bottom > vp->virtual_top) {
213 int screen_x = (left + right) / 2 - vp->virtual_left;
214 int width = (vp->virtual_width == 0 ? 1 : vp->virtual_width);
215 float panning = (float)screen_x / width;
216
217 StartSound(
218 sound,
219 panning,
220 _vol_factor_by_zoom[vp->zoom]
221 );
222 return;
223 }
224 }
225}
226
227void SndPlayTileFx(SoundID sound, TileIndex tile)
228{
229 /* emits sound from center of the tile */
230 int x = std::min(Map::MaxX() - 1, TileX(tile)) * TILE_SIZE + TILE_SIZE / 2;
231 int y = std::min(Map::MaxY() - 1, TileY(tile)) * TILE_SIZE - TILE_SIZE / 2;
232 int z = (y < 0 ? 0 : GetSlopePixelZ(x, y));
233 Point pt = RemapCoords(x, y, z);
234 y += 2 * TILE_SIZE;
235 Point pt2 = RemapCoords(x, y, GetSlopePixelZ(x, y));
236 SndPlayScreenCoordFx(sound, pt.x, pt2.x, pt.y, pt2.y);
237}
238
239void SndPlayVehicleFx(SoundID sound, const Vehicle *v)
240{
242 v->coord.left, v->coord.right,
243 v->coord.top, v->coord.bottom
244 );
245}
246
247void SndPlayFx(SoundID sound)
248{
249 StartSound(sound, 0.5, UINT8_MAX);
250}
251
253
254
255static const char * const _sound_file_names[] = { "samples" };
256
257
258template <class T, size_t Tnum_files, bool Tsearch_in_tars>
260
261template <class Tbase_set>
262/* static */ const char *BaseMedia<Tbase_set>::GetExtension()
263{
264 return ".obs"; // OpenTTD Base Sounds
265}
266
267template <class Tbase_set>
269{
270 if (BaseMedia<Tbase_set>::used_set != nullptr) return true;
271
272 const Tbase_set *best = nullptr;
273 for (const Tbase_set *c = BaseMedia<Tbase_set>::available_sets; c != nullptr; c = c->next) {
274 /* Skip unusable sets */
275 if (c->GetNumMissing() != 0) continue;
276
277 if (best == nullptr ||
278 (best->fallback && !c->fallback) ||
279 best->valid_files < c->valid_files ||
280 (best->valid_files == c->valid_files &&
281 (best->shortname == c->shortname && best->version < c->version))) {
282 best = c;
283 }
284 }
285
287 return BaseMedia<Tbase_set>::used_set != nullptr;
288}
289
Generic function implementations for base data (graphics, sounds).
#define INSTANTIATE_BASE_MEDIA_METHODS(repl_type, set_type)
Force instantiation of methods so we don't get linker errors.
debug_inline constexpr bool HasBit(const T x, const uint8_t y)
Checks if a bit in a value is set.
debug_inline static constexpr uint GB(const T x, const uint8_t s, const uint8_t n)
Fetch n bits from x, started at bit s.
constexpr T ClrBit(T &x, const uint8_t y)
Clears a bit in a variable.
Base for all base media (graphics, sounds)
static bool DetermineBestSet()
Determine the graphics pack that has to be used.
Definition gfxinit.cpp:466
static const SoundsSet * GetUsedSet()
Return the used set.
static const SoundsSet * GetSet(int index)
Get the name of the graphics set at the specified index.
static int GetIndexOfUsedSet()
Get the index of the currently active graphics set.
static const char * GetExtension()
Get the extension that is used to identify this set.
Definition gfxinit.cpp:490
static bool SetSet(const SoundsSet *set)
Set the set to be used.
static std::string ini_set
The set as saved in the config file.
#define Debug(category, level, format_string,...)
Ouptut a line of debugging information.
Definition debug.h:37
@ BASESET_DIR
Subdirectory for all base data (base sets, intro game)
int GetSlopePixelZ(int x, int y, bool ground_vehicle)
Return world Z coordinate of a given point of a tile.
Functions related to OTTD's landscape.
Point RemapCoords(int x, int y, int z)
Map 3D world or tile coordinate to equivalent 2D coordinate as used in the viewports and smallmap.
Definition landscape.h:79
static debug_inline uint TileY(TileIndex tile)
Get the Y component of a tile.
Definition map_func.h:425
static debug_inline uint TileX(TileIndex tile)
Get the X component of a tile.
Definition map_func.h:415
void MxCloseAllChannels()
Close all mixer channels.
Definition mixer.cpp:119
void MxSetChannelVolume(MixerChannel *mc, uint volume, float pan)
Set volume and pan parameters for a sound.
Definition mixer.cpp:213
Functions to mix sound samples.
SoundEntry * AllocateSound(uint num)
Allocate sound slots.
Functions related to NewGRF provided sounds.
Class related to random access to files.
A number of safeguards to prevent using unsafe methods.
static void SndPlayScreenCoordFx(SoundID sound, int left, int right, int top, int bottom)
Decide 'where' (between left and right speaker) to play the sound effect.
Definition sound.cpp:204
static const char *const _sound_file_names[]
Names corresponding to the sound set's files.
Definition sound.cpp:255
static void OpenBankFile(const std::string &filename)
Definition sound.cpp:29
void ChangeSoundSet(int index)
Change the configured sound set and reset sounds.
Definition sound.cpp:169
Types related to sounds.
static const uint ORIGINAL_SAMPLE_COUNT
The number of sounds in the original sample.cat.
Definition sound_type.h:124
Functions related to sound loaders.
Definition of base types and functions in a cross-platform compatible way.
#define lengthof(array)
Return the length of an fixed size array.
Definition stdafx.h:280
Information about a single base set.
static uint MaxY()
Gets the maximum Y coordinate within the map, including MP_VOID.
Definition map_func.h:306
static debug_inline uint MaxX()
Gets the maximum X coordinate within the map, including MP_VOID.
Definition map_func.h:297
Coordinates of a point in 2D.
uint8_t grf_container_ver
NewGRF container version if the sound is from a NewGRF.
Definition sound_type.h:30
All data of a sounds set.
Vehicle data structure.
Rect coord
NOSAVE: Graphical bounding box of the vehicle, i.e. what to redraw on moves.
Data structure for viewport, display of a part of the world.
ZoomLevel zoom
The zoom level of the viewport.
int virtual_top
Virtual top coordinate.
int virtual_left
Virtual left coordinate.
int virtual_width
width << zoom
int virtual_height
height << zoom
Data structure for an opened window.
Definition window_gui.h:273
AllWindows< false > IterateFromBack
Iterate all windows in Z order from back to front.
Definition window_gui.h:928
static const uint TILE_SIZE
Tile size in world coordinates.
Definition tile_type.h:15
Base class for all vehicles.
void InvalidateWindowData(WindowClass cls, WindowNumber number, int data, bool gui_scope)
Mark window data of the window of a given class and specific window number as invalid (in need of re-...
Definition window.cpp:3219
Window functions not directly related to making/drawing windows.
Functions, definitions and such used only by the GUI.
@ WN_GAME_OPTIONS_GAME_OPTIONS
Game options.
Definition window_type.h:26
@ WC_GAME_OPTIONS
Game options window; Window numbers:
@ ZOOM_LVL_END
End for iteration.
Definition zoom_type.h:25