dxx-rebirth/common/main/digi.h

270 lines
7.6 KiB
C
Raw Normal View History

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.
*
*/
#pragma once
2006-03-20 17:12:09 +00:00
Enable building with SDL2 This commit enables Rebirth to build with SDL2, but the result is not perfect. - SDL2 removed some sticky key support. Rebirth may behave differently now in this area. - SDL2 removed some key-repeat related support. Rebirth may behave differently now in this area. - SDL2 gained the ability to make a window fullscreen by sizing it to the desktop instead of by changing the desktop resolution. Rebirth uses this, and it mostly works. - Resizing while in the automap does not notify the automap code, so the view is wrong until the player switches out of automap mode and back in. - SDL2 changed how to enumerate available resolutions. Since fitting the window to the desktop is generally more useful than fitting the desktop to the window, I chose to drop support for enumerating resolutions instead of porting to the new API. Users can now enter an arbitrary window dimension and Rebirth will make an attempt to use it. - It might be useful to cap the window dimension at the desktop dimension, but that is not done yet. - Entering fullscreen mode through the Controls->Graphics submenu failed to notify the relevant subsystems, causing the rendered content not to rescale. For now, compile out the option to toggle full screen through that menu. Toggling through Alt+Enter works properly. Despite these quirks, this is a substantial improvement over the prior commit, where SDL2 cannot be used at all. The remaining issues can be resolved in future work. References: <https://github.com/dxx-rebirth/dxx-rebirth/issues/82>
2018-07-28 23:22:58 +00:00
#include <SDL_version.h>
#include <type_traits>
2006-03-20 17:12:09 +00:00
#include "pstypes.h"
#ifdef __cplusplus
#include "dxxsconf.h"
#include "dsx-ns.h"
#include "fwd-object.h"
#include "fwd-segment.h"
#include "fwd-piggy.h"
#include <utility>
2016-01-09 16:38:15 +00:00
#ifdef dsx
2016-07-15 03:43:02 +00:00
namespace dcx {
2022-12-17 13:16:28 +00:00
enum class sound_pan : int
{
};
enum class sound_channel : uint8_t
{
None = UINT8_MAX,
};
struct sound_object;
extern int digi_volume;
2016-07-15 03:43:02 +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,
};
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.
*/
}
};
struct digi_sound
{
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};
}
};
2006-03-20 17:12:09 +00:00
2022-12-17 13:16:28 +00:00
extern sound_channel SoundQ_channel;
}
2022-12-17 13:16:28 +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 );
#if defined(DXX_BUILD_DESCENT_I) || defined(DXX_BUILD_DESCENT_II)
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);
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.
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);
void digi_kill_sound_linked_to_object(vcobjptridx_t);
#endif
2006-03-20 17:12:09 +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
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();
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 );
// Following declarations are for the runtime switching system
#define MUSIC_TYPE_NONE 0
#define MUSIC_TYPE_BUILTIN 1
#if DXX_USE_SDL_REDBOOK_AUDIO
#define MUSIC_TYPE_REDBOOK 2
Enable building with SDL2 This commit enables Rebirth to build with SDL2, but the result is not perfect. - SDL2 removed some sticky key support. Rebirth may behave differently now in this area. - SDL2 removed some key-repeat related support. Rebirth may behave differently now in this area. - SDL2 gained the ability to make a window fullscreen by sizing it to the desktop instead of by changing the desktop resolution. Rebirth uses this, and it mostly works. - Resizing while in the automap does not notify the automap code, so the view is wrong until the player switches out of automap mode and back in. - SDL2 changed how to enumerate available resolutions. Since fitting the window to the desktop is generally more useful than fitting the desktop to the window, I chose to drop support for enumerating resolutions instead of porting to the new API. Users can now enter an arbitrary window dimension and Rebirth will make an attempt to use it. - It might be useful to cap the window dimension at the desktop dimension, but that is not done yet. - Entering fullscreen mode through the Controls->Graphics submenu failed to notify the relevant subsystems, causing the rendered content not to rescale. For now, compile out the option to toggle full screen through that menu. Toggling through Alt+Enter works properly. Despite these quirks, this is a substantial improvement over the prior commit, where SDL2 cannot be used at all. The remaining issues can be resolved in future work. References: <https://github.com/dxx-rebirth/dxx-rebirth/issues/82>
2018-07-28 23:22:58 +00:00
#endif
#define MUSIC_TYPE_CUSTOM 3
#define SOUND_MAX_VOLUME F1_0 / 2
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();
#ifdef _WIN32
// Windows native-MIDI stuff.
void digi_win32_set_midi_volume( int mvolume );
int digi_win32_play_midi_song(const char * filename, int loop );
void digi_win32_pause_midi_song();
void digi_win32_resume_midi_song();
void digi_win32_stop_midi_song();
#endif
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
}
namespace dsx {
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)
{
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)
{
stop(std::exchange(channel, c));
}
operator int() const = delete;
explicit operator bool() const
{
return channel != invalid_channel;
}
};
}
#endif
#endif