2006-03-20 17:12:09 +00:00
|
|
|
/*
|
2014-06-01 17:55:23 +00:00
|
|
|
* Portions of this file are copyright Rebirth contributors and licensed as
|
|
|
|
* described in COPYING.txt.
|
|
|
|
* Portions of this file are copyright Parallax Software and licensed
|
|
|
|
* according to the Parallax license below.
|
|
|
|
* See COPYING.txt for license details.
|
|
|
|
|
2006-03-20 17:12:09 +00:00
|
|
|
THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX
|
|
|
|
SOFTWARE CORPORATION ("PARALLAX"). PARALLAX, IN DISTRIBUTING THE CODE TO
|
|
|
|
END-USERS, AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN, GRANTS A
|
|
|
|
ROYALTY-FREE, PERPETUAL LICENSE TO SUCH END-USERS FOR USE BY SUCH END-USERS
|
|
|
|
IN USING, DISPLAYING, AND CREATING DERIVATIVE WORKS THEREOF, SO LONG AS
|
|
|
|
SUCH USE, DISPLAY OR CREATION IS FOR NON-COMMERCIAL, ROYALTY OR REVENUE
|
|
|
|
FREE PURPOSES. IN NO EVENT SHALL THE END-USER USE THE COMPUTER CODE
|
|
|
|
CONTAINED HEREIN FOR REVENUE-BEARING PURPOSES. THE END-USER UNDERSTANDS
|
|
|
|
AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE.
|
|
|
|
COPYRIGHT 1993-1998 PARALLAX SOFTWARE CORPORATION. ALL RIGHTS RESERVED.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
*
|
|
|
|
* Include file for sound hardware.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
2015-04-02 02:36:55 +00:00
|
|
|
#pragma once
|
2006-03-20 17:12:09 +00:00
|
|
|
|
2018-07-28 23:22:58 +00:00
|
|
|
#include <SDL_version.h>
|
2017-06-25 20:46:03 +00:00
|
|
|
#include <type_traits>
|
2006-03-20 17:12:09 +00:00
|
|
|
#include "pstypes.h"
|
|
|
|
|
2012-11-11 22:12:51 +00:00
|
|
|
#ifdef __cplusplus
|
2015-04-02 02:36:55 +00:00
|
|
|
#include "dxxsconf.h"
|
2016-03-19 19:08:10 +00:00
|
|
|
#include "dsx-ns.h"
|
2015-10-10 03:44:14 +00:00
|
|
|
#include "fwd-object.h"
|
2015-10-10 03:44:14 +00:00
|
|
|
#include "fwd-segment.h"
|
2022-12-10 18:09:54 +00:00
|
|
|
#include "fwd-piggy.h"
|
2020-05-02 21:18:42 +00:00
|
|
|
#include <utility>
|
2012-11-11 22:12:51 +00:00
|
|
|
|
2016-01-09 16:38:15 +00:00
|
|
|
#ifdef dsx
|
2016-07-15 03:43:02 +00:00
|
|
|
namespace dcx {
|
2015-12-13 18:00:48 +00:00
|
|
|
|
2022-12-17 13:16:28 +00:00
|
|
|
enum class sound_pan : int
|
|
|
|
{
|
|
|
|
};
|
|
|
|
|
|
|
|
enum class sound_channel : uint8_t
|
|
|
|
{
|
|
|
|
None = UINT8_MAX,
|
|
|
|
};
|
|
|
|
|
2014-12-14 05:22:59 +00:00
|
|
|
struct sound_object;
|
2020-05-31 23:04:25 +00:00
|
|
|
extern int digi_volume;
|
2016-07-15 03:43:02 +00:00
|
|
|
|
2020-12-19 16:13:26 +00:00
|
|
|
enum class sound_stack : uint8_t
|
|
|
|
{
|
|
|
|
allow_stacking,
|
|
|
|
cancel_previous,
|
|
|
|
};
|
|
|
|
|
2023-01-07 22:17:31 +00:00
|
|
|
enum class sound_sample_rate : uint16_t
|
|
|
|
{
|
|
|
|
_11k = 11025,
|
|
|
|
_22k = 22050,
|
|
|
|
_44k = 44100,
|
|
|
|
};
|
|
|
|
|
2022-12-10 18:09:54 +00:00
|
|
|
struct digi_sound_deleter : std::default_delete<uint8_t[]>
|
|
|
|
{
|
|
|
|
game_sound_offset offset = {};
|
|
|
|
constexpr digi_sound_deleter() = default;
|
|
|
|
constexpr digi_sound_deleter(const game_sound_offset offset) :
|
|
|
|
offset(offset)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
constexpr bool must_free_buffer() const
|
|
|
|
{
|
|
|
|
return static_cast<std::underlying_type<game_sound_offset>::type>(offset) <= 0;
|
|
|
|
}
|
|
|
|
void operator()(uint8_t *const p) const
|
|
|
|
{
|
|
|
|
if (must_free_buffer())
|
|
|
|
this->std::default_delete<uint8_t[]>::operator()(p);
|
|
|
|
/* Else, this pointer was not owned by the unique_ptr, and should not
|
|
|
|
* be freed. This happens for some sounds that are stored in a single
|
|
|
|
* large allocation containing all the sounds, rather than individual
|
|
|
|
* per-sound allocations.
|
|
|
|
*/
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2013-12-22 22:03:07 +00:00
|
|
|
struct digi_sound
|
|
|
|
{
|
2022-12-10 18:09:54 +00:00
|
|
|
struct allocated_data : private std::unique_ptr<uint8_t[], digi_sound_deleter>
|
|
|
|
{
|
|
|
|
using base_type = std::unique_ptr<uint8_t[], digi_sound_deleter>;
|
|
|
|
using base_type::get;
|
|
|
|
using base_type::get_deleter;
|
|
|
|
using base_type::operator bool;
|
|
|
|
constexpr allocated_data() :
|
|
|
|
base_type()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
allocated_data(const base_type::pointer p, const game_sound_offset o) :
|
|
|
|
base_type(p, o)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
/* This is only used in the Descent 1 build. */
|
|
|
|
explicit allocated_data(std::unique_ptr<uint8_t[]> p, const game_sound_offset o) :
|
|
|
|
allocated_data(p.release(), o)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
/* Define reset() instead of inheriting via `using base_type::reset`,
|
|
|
|
* because only the zero-argument form of reset() should be exposed.
|
|
|
|
* The one-argument form should be hidden.
|
|
|
|
*/
|
|
|
|
void reset()
|
|
|
|
{
|
|
|
|
this->base_type::reset();
|
|
|
|
}
|
|
|
|
allocated_data &operator=(allocated_data &&rhs)
|
|
|
|
{
|
|
|
|
auto &d = rhs.get_deleter();
|
|
|
|
if (d.must_free_buffer())
|
|
|
|
/* If the other object exclusively owns the data it controls,
|
|
|
|
* then use the standard move semantics of unique_ptr.
|
|
|
|
*/
|
|
|
|
return static_cast<allocated_data &>(this->base_type::operator=(std::move(rhs)));
|
|
|
|
/* Otherwise, the source object does not free its data, so assume
|
|
|
|
* that it is safe, and sometimes necessary, to copy from the
|
|
|
|
* source instead of moving from it.
|
|
|
|
*/
|
|
|
|
if (this == &rhs)
|
|
|
|
return *this;
|
|
|
|
/* Free the old data, if needed. */
|
|
|
|
this->base_type::reset();
|
|
|
|
/* Mark this deleter as not requiring a free. */
|
|
|
|
get_deleter() = d;
|
|
|
|
/* Change this pointer to share ownership with the other object. */
|
|
|
|
this->base_type::reset(rhs.get());
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
std::size_t length;
|
|
|
|
int freq;
|
|
|
|
allocated_data data;
|
|
|
|
std::span<const uint8_t> span() const
|
|
|
|
{
|
|
|
|
return {data.get(), length};
|
|
|
|
}
|
2013-12-22 22:03:07 +00:00
|
|
|
};
|
2006-03-20 17:12:09 +00:00
|
|
|
|
2022-12-17 13:16:28 +00:00
|
|
|
extern sound_channel SoundQ_channel;
|
|
|
|
|
2022-12-10 18:09:54 +00:00
|
|
|
}
|
2022-12-17 13:16:28 +00:00
|
|
|
|
2022-12-10 18:09:54 +00:00
|
|
|
namespace dsx {
|
|
|
|
|
2006-03-20 17:12:09 +00:00
|
|
|
extern int digi_init();
|
|
|
|
extern void digi_close();
|
|
|
|
|
|
|
|
// Volume is max at F1_0.
|
|
|
|
extern void digi_play_sample( int sndnum, fix max_volume );
|
|
|
|
extern void digi_play_sample_once( int sndnum, fix max_volume );
|
2015-11-26 02:56:55 +00:00
|
|
|
#if defined(DXX_BUILD_DESCENT_I) || defined(DXX_BUILD_DESCENT_II)
|
2018-05-13 03:14:34 +00:00
|
|
|
void digi_link_sound_to_object(unsigned soundnum, vcobjptridx_t objnum, uint8_t forever, fix max_volume, sound_stack once);
|
2022-02-19 14:52:17 +00:00
|
|
|
void digi_kill_sound_linked_to_segment(vmsegidx_t segnum, sidenum_t sidenum, int soundnum);
|
2022-06-05 17:44:52 +00:00
|
|
|
void digi_link_sound_to_pos(unsigned soundnum, vcsegptridx_t segnum, sidenum_t sidenum, const vms_vector &pos, int forever, fix max_volume);
|
2006-03-20 17:12:09 +00:00
|
|
|
// Same as above, but you pass the max distance sound can be heard. The old way uses f1_0*256 for max_distance.
|
2018-05-13 03:14:34 +00:00
|
|
|
void digi_link_sound_to_object2(unsigned soundnum, vcobjptridx_t objnum, uint8_t forever, fix max_volume, sound_stack once, vm_distance max_distance);
|
|
|
|
void digi_link_sound_to_object3(unsigned soundnum, vcobjptridx_t objnum, uint8_t forever, fix max_volume, sound_stack once, vm_distance max_distance, int loop_start, int loop_end);
|
2015-11-26 02:56:55 +00:00
|
|
|
void digi_kill_sound_linked_to_object(vcobjptridx_t);
|
|
|
|
#endif
|
2006-03-20 17:12:09 +00:00
|
|
|
|
2021-06-28 03:37:51 +00:00
|
|
|
void digi_play_sample_3d(int soundno, sound_pan angle, int volume); // Volume from 0-0x7fff
|
2006-03-20 17:12:09 +00:00
|
|
|
|
|
|
|
extern void digi_init_sounds();
|
|
|
|
extern void digi_sync_sounds();
|
|
|
|
|
|
|
|
extern void digi_set_digi_volume( int dvolume );
|
|
|
|
|
|
|
|
extern void digi_pause_digi_sounds();
|
|
|
|
extern void digi_resume_digi_sounds();
|
|
|
|
|
|
|
|
extern int digi_xlat_sound(int soundno);
|
|
|
|
|
2022-12-17 13:16:28 +00:00
|
|
|
void digi_stop_sound(sound_channel channel);
|
2006-03-20 17:12:09 +00:00
|
|
|
|
|
|
|
// Volume 0-F1_0
|
2016-07-16 16:52:04 +00:00
|
|
|
constexpr sound_object *sound_object_none = nullptr;
|
2022-12-17 13:16:28 +00:00
|
|
|
sound_channel digi_start_sound(short soundnum, fix volume, sound_pan pan, int looping, int loop_start, int loop_end, sound_object *);
|
2006-03-20 17:12:09 +00:00
|
|
|
|
|
|
|
// Stops all sounds that are playing
|
|
|
|
void digi_stop_all_channels();
|
|
|
|
|
2010-06-14 08:13:16 +00:00
|
|
|
void digi_stop_digi_sounds();
|
|
|
|
|
2022-12-17 13:16:28 +00:00
|
|
|
void digi_end_sound(sound_channel channel);
|
|
|
|
void digi_set_channel_pan(sound_channel channel, sound_pan pan);
|
|
|
|
void digi_set_channel_volume(sound_channel channel, int volume);
|
|
|
|
int digi_is_channel_playing(sound_channel channel);
|
2006-03-20 17:12:09 +00:00
|
|
|
|
|
|
|
extern void digi_play_sample_looping( int soundno, fix max_volume,int loop_start, int loop_end );
|
|
|
|
extern void digi_change_looping_volume( fix volume );
|
|
|
|
extern void digi_stop_looping_sound();
|
|
|
|
|
|
|
|
// Plays a queued voice sound.
|
|
|
|
extern void digi_start_sound_queued( short soundnum, fix volume );
|
|
|
|
|
2007-09-18 13:37:39 +00:00
|
|
|
// Following declarations are for the runtime switching system
|
|
|
|
|
2010-06-14 08:13:16 +00:00
|
|
|
#define MUSIC_TYPE_NONE 0
|
|
|
|
#define MUSIC_TYPE_BUILTIN 1
|
2019-10-24 17:39:08 +00:00
|
|
|
#if DXX_USE_SDL_REDBOOK_AUDIO
|
2010-06-14 08:13:16 +00:00
|
|
|
#define MUSIC_TYPE_REDBOOK 2
|
2018-07-28 23:22:58 +00:00
|
|
|
#endif
|
2010-06-14 08:13:16 +00:00
|
|
|
#define MUSIC_TYPE_CUSTOM 3
|
|
|
|
|
2007-09-21 19:06:05 +00:00
|
|
|
#define SOUND_MAX_VOLUME F1_0 / 2
|
2007-09-18 13:37:39 +00:00
|
|
|
|
Backport D2's Dont_start_sound_objects to D1
Descent 2 has a hack, present as far back as I can trace, that
suppresses starting sounds during level load. The original reason was
not recorded, but this hack has the useful side effect that it avoids
using uninitialized data when set_sound_sources tries to use a Viewer
that has not been reset for the objects of the new level.
Descent 1 lacks this hack, so an invalid Viewer is used, which may
trigger a valptridx trap if the undefined data has an invalid segment
number, and could cause memory corruption in builds which do not
validate the segment index. The valptridx trap:
```
terminate called after throwing an instance of 'valptridx<dcx::segment>::index_range_exception'
what(): similar/main/digiobj.cpp:389: invalid index used in array subscript: base=(nil) size=9000 index=65021
```
The backtrace leading to the trap:
```
d1x::digi_link_sound_common (viewer=..., so=..., pos=..., forever=<optimized out>, max_volume=<optimized out>, max_distance=..., soundnum=42, segnum=...) at similar/main/digiobj.cpp:389
0x00005555555a4e2d in d1x::digi_link_sound_to_pos2 (vcobjptr=..., max_distance=..., max_volume=32768, forever=1, pos=..., sidenum=4, segnum=..., org_soundnum=121) at similar/main/digiobj.cpp:483
d1x::digi_link_sound_to_pos (soundnum=soundnum@entry=121, segnum=..., sidenum=sidenum@entry=4, pos=..., forever=forever@entry=1, max_volume=32768) at similar/main/digiobj.cpp:490
0x00005555555c140d in d1x::set_sound_sources (vcsegptridx=..., vcvertptr=...) at similar/main/gameseq.cpp:817
d1x::LoadLevel (level_num=<optimized out>, page_in_textures=1) at similar/main/gameseq.cpp:1022
0x00005555555c2654 in d1x::StartNewLevelSub (level_num=-1, page_in_textures=<optimized out>) at similar/main/gameseq.cpp:1865
```
Backport this hack into Descent 1. Ultimately, the hack should go away
and data should be loaded in an order that does not access undefined
memory.
Reported-by: Spacecpp <https://github.com/dxx-rebirth/dxx-rebirth/issues/463>
2019-10-26 23:13:14 +00:00
|
|
|
extern int Dont_start_sound_objects;
|
2015-11-26 02:56:56 +00:00
|
|
|
void digi_select_system();
|
2010-06-14 08:13:16 +00:00
|
|
|
|
|
|
|
#ifdef _WIN32
|
|
|
|
// Windows native-MIDI stuff.
|
|
|
|
void digi_win32_set_midi_volume( int mvolume );
|
2013-06-08 22:24:17 +00:00
|
|
|
int digi_win32_play_midi_song(const char * filename, int loop );
|
2010-11-28 15:49:40 +00:00
|
|
|
void digi_win32_pause_midi_song();
|
|
|
|
void digi_win32_resume_midi_song();
|
2011-07-15 08:43:03 +00:00
|
|
|
void digi_win32_stop_midi_song();
|
2010-06-14 08:13:16 +00:00
|
|
|
#endif
|
2014-12-14 05:22:59 +00:00
|
|
|
void digi_end_soundobj(sound_object &);
|
2012-11-11 00:14:30 +00:00
|
|
|
void SoundQ_end();
|
2016-07-15 03:43:02 +00:00
|
|
|
#ifndef NDEBUG
|
2022-12-17 13:16:28 +00:00
|
|
|
void verify_sound_channel_free(sound_channel channel);
|
2016-07-15 03:43:02 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2020-12-19 16:13:26 +00:00
|
|
|
namespace dsx {
|
2007-09-18 13:37:39 +00:00
|
|
|
|
2015-04-02 02:36:55 +00:00
|
|
|
class RAIIdigi_sound
|
|
|
|
{
|
2022-12-17 13:16:28 +00:00
|
|
|
static constexpr std::integral_constant<sound_channel, sound_channel::None> invalid_channel{};
|
|
|
|
sound_channel channel = invalid_channel;
|
|
|
|
static void stop(const sound_channel channel)
|
2015-04-02 02:36:55 +00:00
|
|
|
{
|
|
|
|
if (channel != invalid_channel)
|
|
|
|
digi_stop_sound(channel);
|
|
|
|
}
|
|
|
|
public:
|
|
|
|
~RAIIdigi_sound()
|
|
|
|
{
|
|
|
|
stop(channel);
|
|
|
|
}
|
2022-12-17 13:16:28 +00:00
|
|
|
void reset(const sound_channel c = invalid_channel)
|
2015-04-02 02:36:55 +00:00
|
|
|
{
|
2020-05-02 21:18:42 +00:00
|
|
|
stop(std::exchange(channel, c));
|
2015-04-02 02:36:55 +00:00
|
|
|
}
|
|
|
|
operator int() const = delete;
|
|
|
|
explicit operator bool() const
|
|
|
|
{
|
|
|
|
return channel != invalid_channel;
|
|
|
|
}
|
|
|
|
};
|
2015-12-13 18:00:48 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
#endif
|