OpenTTD Source 20250528-master-g3aca5d62a8
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#include "base_media_func.h"
21#include "base_media_sounds.h"
22
23#include "safeguards.h"
24
25static std::array<SoundEntry, ORIGINAL_SAMPLE_COUNT> _original_sounds;
26
27static void OpenBankFile(const std::string &filename)
28{
33 static std::unique_ptr<RandomAccessFile> original_sound_file;
34
35 _original_sounds.fill({});
36
37 /* If there is no sound file (nosound set), don't load anything */
38 if (filename.empty()) return;
39
40 original_sound_file = std::make_unique<RandomAccessFile>(filename, BASESET_DIR);
41 size_t pos = original_sound_file->GetPos();
42 uint count = original_sound_file->ReadDword();
43
44 /* The new format has the highest bit always set */
45 auto source = HasBit(count, 31) ? SoundSource::BasesetNewFormat : SoundSource::BasesetOldFormat;
46 ClrBit(count, 31);
47 count /= 8;
48
49 /* Simple check for the correct number of original sounds. */
50 if (count != ORIGINAL_SAMPLE_COUNT) {
51 /* Corrupt sample data? Just leave the allocated memory as those tell
52 * there is no sound to play (size = 0 due to calloc). Not allocating
53 * the memory disables valid NewGRFs that replace sounds. */
54 Debug(misc, 6, "Incorrect number of sounds in '{}', ignoring.", filename);
55 return;
56 }
57
58 original_sound_file->SeekTo(pos, SEEK_SET);
59
60 /* Read sound file positions. */
61 for (auto &sound : _original_sounds) {
62 sound.file = original_sound_file.get();
63 sound.file_offset = GB(original_sound_file->ReadDword(), 0, 31) + pos;
64 sound.file_size = original_sound_file->ReadDword();
65 sound.source = source;
66 }
67}
68
69static bool SetBankSource(MixerChannel *mc, SoundEntry *sound, SoundID sound_id)
70{
71 assert(sound != nullptr);
72
73 if (sound->file != nullptr) {
74 if (!LoadSound(*sound, sound_id)) {
75 /* Mark as invalid. */
76 sound->file = nullptr;
77 return false;
78 }
79 sound->file = nullptr;
80 }
81
82 /* Check for valid sound. */
83 if (sound->data->empty()) return false;
84
85 MxSetChannelRawSrc(mc, sound->data, sound->rate, sound->bits_per_sample == 16);
86
87 return true;
88}
89
90void InitializeSound()
91{
92 Debug(misc, 1, "Loading sound effects...");
93 OpenBankFile(BaseSounds::GetUsedSet()->files[0].filename);
94}
95
96
97/* Low level sound player */
98static void StartSound(SoundID sound_id, float pan, uint volume)
99{
100 if (volume == 0) return;
101
102 SoundEntry *sound = GetSound(sound_id);
103 if (sound == nullptr) return;
104
105 if (sound->rate == 0) {
106 /* If the sound's sample rate is not set then the sound needs to be loaded, but if the sound's file pointer
107 * is empty then an attempt was already made to load the sound but it failed. We don't want to try again. */
108 if (sound->file == nullptr) return;
109 }
110
111 MixerChannel *mc = MxAllocateChannel();
112 if (mc == nullptr) return;
113
114 if (!SetBankSource(mc, sound, sound_id)) return;
115
116 /* Apply the sound effect's own volume. */
117 volume = sound->volume * volume;
118
119 MxSetChannelVolume(mc, volume, pan);
120 MxActivateChannel(mc);
121}
122
123
124static const uint8_t _vol_factor_by_zoom[] = {255, 255, 255, 190, 134, 87};
125static_assert(lengthof(_vol_factor_by_zoom) == to_underlying(ZoomLevel::End));
126
127static const uint8_t _sound_base_vol[] = {
128 128, 90, 128, 128, 128, 128, 128, 128,
129 128, 90, 90, 128, 128, 128, 128, 128,
130 128, 128, 128, 80, 128, 128, 128, 128,
131 128, 128, 128, 128, 128, 128, 128, 128,
132 128, 128, 90, 90, 90, 128, 90, 128,
133 128, 90, 128, 128, 128, 90, 128, 128,
134 128, 128, 128, 128, 90, 128, 128, 128,
135 128, 90, 128, 128, 128, 128, 128, 128,
136 128, 128, 90, 90, 90, 128, 128, 128,
137 90,
138};
139
140static const uint8_t _sound_idx[] = {
141 2, 3, 4, 5, 6, 7, 8, 9,
142 10, 11, 12, 13, 14, 15, 16, 17,
143 18, 19, 20, 21, 22, 23, 24, 25,
144 26, 27, 28, 29, 30, 31, 32, 33,
145 34, 35, 36, 37, 38, 39, 40, 0,
146 1, 41, 42, 43, 44, 45, 46, 47,
147 48, 49, 50, 51, 52, 53, 54, 55,
148 56, 57, 58, 59, 60, 61, 62, 63,
149 64, 65, 66, 67, 68, 69, 70, 71,
150 72,
151};
152
153void SndCopyToPool()
154{
156 for (uint i = 0; i < ORIGINAL_SAMPLE_COUNT; i++) {
157 sound[i] = _original_sounds[_sound_idx[i]];
158 sound[i].volume = _sound_base_vol[i];
159 sound[i].priority = 0;
160 }
161}
162
167void ChangeSoundSet(int index)
168{
169 if (BaseSounds::GetIndexOfUsedSet() == index) return;
170
171 auto set = BaseSounds::GetSet(index);
172 BaseSounds::ini_set = set->name;
174
176 InitializeSound();
177
178 /* Replace baseset sounds in the pool with the updated original sounds. This is safe to do as
179 * any sound still playing holds its own shared_ptr to the sample data. */
180 for (uint i = 0; i < ORIGINAL_SAMPLE_COUNT; i++) {
181 SoundEntry *sound = GetSound(i);
182 /* GRF Container 0 means the sound comes from the baseset, and isn't overridden by NewGRF. */
183 if (sound == nullptr || sound->grf_container_ver != 0) continue;
184
185 *sound = _original_sounds[_sound_idx[i]];
186 sound->volume = _sound_base_vol[i];
187 sound->priority = 0;
188 }
189
191}
192
202static void SndPlayScreenCoordFx(SoundID sound, int left, int right, int top, int bottom)
203{
204 /* Iterate from back, so that main viewport is checked first */
205 for (const Window *w : Window::IterateFromBack()) {
206 if (w->viewport == nullptr) continue;
207
208 const Viewport &vp = *w->viewport;
209 if (left < vp.virtual_left + vp.virtual_width && right > vp.virtual_left &&
210 top < vp.virtual_top + vp.virtual_height && bottom > vp.virtual_top) {
211 int screen_x = (left + right) / 2 - vp.virtual_left;
212 int width = (vp.virtual_width == 0 ? 1 : vp.virtual_width);
213 float panning = (float)screen_x / width;
214
215 StartSound(
216 sound,
217 panning,
218 _vol_factor_by_zoom[to_underlying(vp.zoom)]
219 );
220 return;
221 }
222 }
223}
224
225void SndPlayTileFx(SoundID sound, TileIndex tile)
226{
227 /* emits sound from center of the tile */
228 int x = std::min(Map::MaxX() - 1, TileX(tile)) * TILE_SIZE + TILE_SIZE / 2;
229 int y = std::min(Map::MaxY() - 1, TileY(tile)) * TILE_SIZE - TILE_SIZE / 2;
230 int z = (y < 0 ? 0 : GetSlopePixelZ(x, y));
231 Point pt = RemapCoords(x, y, z);
232 y += 2 * TILE_SIZE;
233 Point pt2 = RemapCoords(x, y, GetSlopePixelZ(x, y));
234 SndPlayScreenCoordFx(sound, pt.x, pt2.x, pt.y, pt2.y);
235}
236
237void SndPlayVehicleFx(SoundID sound, const Vehicle *v)
238{
240 v->coord.left, v->coord.right,
241 v->coord.top, v->coord.bottom
242 );
243}
244
245void SndPlayFx(SoundID sound)
246{
247 StartSound(sound, 0.5, UINT8_MAX);
248}
249
251static const std::string_view _sound_file_names[] = { "samples" };
252
253template <>
254/* static */ std::span<const std::string_view> BaseSet<SoundsSet>::GetFilenames()
255{
256 return _sound_file_names;
257}
258
259template <>
260/* static */ std::string_view BaseMedia<SoundsSet>::GetExtension()
261{
262 return ".obs"; // OpenTTD Base Sounds
263}
264
265template <>
267{
268 if (BaseMedia<SoundsSet>::used_set != nullptr) return true;
269
270 const SoundsSet *best = nullptr;
271 for (const SoundsSet *c = BaseMedia<SoundsSet>::available_sets; c != nullptr; c = c->next) {
272 /* Skip unusable sets */
273 if (c->GetNumMissing() != 0) continue;
274
275 if (best == nullptr ||
276 (best->fallback && !c->fallback) ||
277 best->valid_files < c->valid_files ||
278 (best->valid_files == c->valid_files &&
279 (best->shortname == c->shortname && best->version < c->version))) {
280 best = c;
281 }
282 }
283
285 return BaseMedia<SoundsSet>::used_set != nullptr;
286}
287
288template class BaseMedia<SoundsSet>;
Generic function implementations for base data (graphics, sounds).
Generic functions for replacing base sounds data.
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 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 bool DetermineBestSet()
Determine the graphics pack that has to be used.
static bool SetSet(const SoundsSet *set)
Set the set to be used.
static std::string_view GetExtension()
Get the extension that is used to identify this set.
static std::string ini_set
The set as saved in the config file.
#define Debug(category, level, format_string,...)
Output a line of debugging information.
Definition debug.h:37
constexpr std::underlying_type_t< enum_type > to_underlying(enum_type e)
Implementation of std::to_underlying (from C++23)
Definition enum_type.hpp:17
@ BASESET_DIR
Subdirectory for all base data (base sets, intro game)
Definition fileio_type.h:95
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:424
static debug_inline uint TileX(TileIndex tile)
Get the X component of a tile.
Definition map_func.h:414
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:202
static const std::string_view _sound_file_names[]
Names corresponding to the sound set's files.
Definition sound.cpp:251
static void OpenBankFile(const std::string &filename)
Definition sound.cpp:27
void ChangeSoundSet(int index)
Change the configured sound set and reset sounds.
Definition sound.cpp:167
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:271
uint valid_files
Number of the files that could be found and are valid.
static std::span< const std::string_view > GetFilenames()
Get the internal names of the files in this set.
bool fallback
This set is a fallback set, i.e. it should be used only as last resort.
std::vector< uint32_t > version
The version of this base set.
uint32_t shortname
Four letter short variant of the name.
static uint MaxY()
Gets the maximum Y coordinate within the map, including MP_VOID.
Definition map_func.h:305
static debug_inline uint MaxX()
Gets the maximum X coordinate within the map, including MP_VOID.
Definition map_func.h:296
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
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:933
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:3265
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:28
@ WC_GAME_OPTIONS
Game options window; Window numbers:
@ End
End for iteration.