mirror of
				https://github.com/KartKrewDev/RingRacers.git
				synced 2025-10-30 08:01:28 +00:00 
			
		
		
		
	Merge branch 'new-audio-mixer' into 'master'
New Audio Mixer See merge request KartKrew/Kart!847
This commit is contained in:
		
						commit
						181a159f33
					
				
					 50 changed files with 9460 additions and 420 deletions
				
			
		|  | @ -130,9 +130,7 @@ if("${SRB2_CONFIG_SYSTEM_LIBRARIES}") | |||
| 	find_package(ZLIB REQUIRED) | ||||
| 	find_package(PNG REQUIRED) | ||||
| 	find_package(SDL2 REQUIRED) | ||||
| 	find_package(SDL2_mixer REQUIRED) | ||||
| 	find_package(CURL REQUIRED) | ||||
| 	find_package(OPENMPT REQUIRED) | ||||
| 	find_package(GME REQUIRED) | ||||
| endif() | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,33 +0,0 @@ | |||
| include(LibFindMacros) | ||||
| 
 | ||||
| libfind_pkg_check_modules(OPENMPT_PKGCONF OPENMPT) | ||||
| 
 | ||||
| find_path(OPENMPT_INCLUDE_DIR | ||||
| 	NAMES libopenmpt.h | ||||
| 	PATHS | ||||
| 		${OPENMPT_PKGCONF_INCLUDE_DIRS} | ||||
| 		"/usr/include/libopenmpt" | ||||
| 		"/usr/local/include/libopenmpt" | ||||
| ) | ||||
| 
 | ||||
| find_library(OPENMPT_LIBRARY | ||||
| 	NAMES openmpt | ||||
| 	PATHS | ||||
| 		${OPENMPT_PKGCONF_LIBRARY_DIRS} | ||||
| 		"/usr/lib" | ||||
| 		"/usr/local/lib" | ||||
| ) | ||||
| 
 | ||||
| set(OPENMPT_PROCESS_INCLUDES OPENMPT_INCLUDE_DIR) | ||||
| set(OPENMPT_PROCESS_LIBS OPENMPT_LIBRARY) | ||||
| libfind_process(OPENMPT) | ||||
| 
 | ||||
| if(OPENMPT_FOUND AND NOT TARGET openmpt) | ||||
| 	add_library(openmpt UNKNOWN IMPORTED) | ||||
| 	set_target_properties( | ||||
| 		openmpt | ||||
| 		PROPERTIES | ||||
| 		IMPORTED_LOCATION "${OPENMPT_LIBRARY}" | ||||
| 		INTERFACE_INCLUDE_DIRECTORIES "${OPENMPT_INCLUDE_DIR}" | ||||
| 	) | ||||
| endif() | ||||
|  | @ -1,44 +0,0 @@ | |||
| # Find SDL2 | ||||
| # Once done, this will define | ||||
| # | ||||
| #  SDL2_MIXER_FOUND - system has SDL2 | ||||
| #  SDL2_MIXER_INCLUDE_DIRS - SDL2 include directories | ||||
| #  SDL2_MIXER_LIBRARIES - link libraries | ||||
| 
 | ||||
| include(LibFindMacros) | ||||
| 
 | ||||
| libfind_pkg_check_modules(SDL2_MIXER_PKGCONF SDL2_mixer) | ||||
| 
 | ||||
| # includes | ||||
| find_path(SDL2_MIXER_INCLUDE_DIR | ||||
| 	NAMES SDL_mixer.h | ||||
| 	PATHS | ||||
| 		${SDL2_MIXER_PKGCONF_INCLUDE_DIRS} | ||||
| 		"/usr/include/SDL2" | ||||
| 		"/usr/local/include/SDL2" | ||||
| ) | ||||
| 
 | ||||
| # library | ||||
| find_library(SDL2_MIXER_LIBRARY | ||||
| 	NAMES SDL2_mixer | ||||
| 	PATHS | ||||
| 		${SDL2_MIXER_PKGCONF_LIBRARY_DIRS} | ||||
| 		"/usr/lib" | ||||
| 		"/usr/local/lib" | ||||
| ) | ||||
| 
 | ||||
| 
 | ||||
| # set include dir variables | ||||
| set(SDL2_MIXER_PROCESS_INCLUDES SDL2_MIXER_INCLUDE_DIR) | ||||
| set(SDL2_MIXER_PROCESS_LIBS SDL2_MIXER_LIBRARY) | ||||
| libfind_process(SDL2_MIXER) | ||||
| 
 | ||||
| if(SDL2_MIXER_FOUND AND NOT TARGET SDL2_mixer::SDL2_mixer) | ||||
| 	add_library(SDL2_mixer::SDL2_mixer UNKNOWN IMPORTED) | ||||
| 	set_target_properties( | ||||
| 		SDL2_mixer::SDL2_mixer | ||||
| 		PROPERTIES | ||||
| 		IMPORTED_LOCATION "${SDL2_MIXER_LIBRARY}" | ||||
| 		INTERFACE_INCLUDE_DIRECTORIES "${SDL2_MIXER_INCLUDE_DIR}" | ||||
| 	) | ||||
| endif() | ||||
|  | @ -214,9 +214,6 @@ if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}") | |||
| 	target_include_directories(SRB2SDL2 PRIVATE "${libgme_SOURCE_DIR}") | ||||
| endif() | ||||
| 
 | ||||
| target_link_libraries(SRB2SDL2 PRIVATE openmpt) | ||||
| target_compile_definitions(SRB2SDL2 PRIVATE -DHAVE_OPENMPT) | ||||
| 
 | ||||
| target_link_libraries(SRB2SDL2 PRIVATE ZLIB::ZLIB PNG::PNG CURL::libcurl) | ||||
| target_compile_definitions(SRB2SDL2 PRIVATE -DHAVE_ZLIB -DHAVE_PNG -DHAVE_CURL -D_LARGEFILE64_SOURCE) | ||||
| target_sources(SRB2SDL2 PRIVATE apng.c) | ||||
|  | @ -226,6 +223,8 @@ target_compile_definitions(SRB2SDL2 PRIVATE -DHAVE_DISCORDRPC -DUSE_STUN) | |||
| target_sources(SRB2SDL2 PRIVATE discord.c stun.c) | ||||
| 
 | ||||
| target_link_libraries(SRB2SDL2 PRIVATE tcbrindle::span) | ||||
| target_link_libraries(SRB2SDL2 PRIVATE stb_vorbis) | ||||
| target_link_libraries(SRB2SDL2 PRIVATE xmp-lite::xmp-lite) | ||||
| 
 | ||||
| set(SRB2_HAVE_THREADS ON) | ||||
| target_compile_definitions(SRB2SDL2 PRIVATE -DHAVE_THREADS) | ||||
|  | @ -538,6 +537,7 @@ if(SRB2_CONFIG_PROFILEMODE AND "${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") | |||
| 	target_link_options(SRB2SDL2 PRIVATE -pg) | ||||
| endif() | ||||
| 
 | ||||
| add_subdirectory(audio) | ||||
| add_subdirectory(io) | ||||
| add_subdirectory(sdl) | ||||
| add_subdirectory(objects) | ||||
|  |  | |||
							
								
								
									
										37
									
								
								src/audio/CMakeLists.txt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/audio/CMakeLists.txt
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,37 @@ | |||
| target_sources(SRB2SDL2 PRIVATE | ||||
| 	chunk_load.cpp | ||||
| 	chunk_load.hpp | ||||
| 	expand_mono.cpp | ||||
| 	expand_mono.hpp | ||||
| 	filter.cpp | ||||
| 	filter.hpp | ||||
| 	gain.cpp | ||||
| 	gain.hpp | ||||
| 	gme_player.cpp | ||||
| 	gme_player.hpp | ||||
| 	gme.cpp | ||||
| 	gme.hpp | ||||
| 	mixer.cpp | ||||
| 	mixer.hpp | ||||
| 	music_player.cpp | ||||
| 	music_player.hpp | ||||
| 	ogg_player.cpp | ||||
| 	ogg_player.hpp | ||||
| 	ogg.cpp | ||||
| 	ogg.hpp | ||||
| 	resample.cpp | ||||
| 	resample.hpp | ||||
| 	sample.hpp | ||||
| 	sound_chunk.hpp | ||||
| 	sound_effect_player.cpp | ||||
| 	sound_effect_player.hpp | ||||
| 	source.hpp | ||||
| 	wav_player.cpp | ||||
| 	wav_player.hpp | ||||
| 	wav.cpp | ||||
| 	wav.hpp | ||||
| 	xmp_player.cpp | ||||
| 	xmp_player.hpp | ||||
| 	xmp.cpp | ||||
| 	xmp.hpp | ||||
| ) | ||||
							
								
								
									
										206
									
								
								src/audio/chunk_load.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										206
									
								
								src/audio/chunk_load.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,206 @@ | |||
| // SONIC ROBO BLAST 2
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| // Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
 | ||||
| //
 | ||||
| // This program is free software distributed under the
 | ||||
| // terms of the GNU General Public License, version 2.
 | ||||
| // See the 'LICENSE' file for more details.
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| 
 | ||||
| #include "chunk_load.hpp" | ||||
| 
 | ||||
| #include <stb_vorbis.h> | ||||
| 
 | ||||
| #include "../cxxutil.hpp" | ||||
| #include "../io/streams.hpp" | ||||
| #include "gme.hpp" | ||||
| #include "gme_player.hpp" | ||||
| #include "ogg.hpp" | ||||
| #include "ogg_player.hpp" | ||||
| #include "resample.hpp" | ||||
| #include "sound_chunk.hpp" | ||||
| #include "sound_effect_player.hpp" | ||||
| #include "wav.hpp" | ||||
| #include "wav_player.hpp" | ||||
| 
 | ||||
| using std::nullopt; | ||||
| using std::optional; | ||||
| using std::size_t; | ||||
| 
 | ||||
| using namespace srb2::audio; | ||||
| using namespace srb2; | ||||
| 
 | ||||
| namespace { | ||||
| 
 | ||||
| // Utility for leveraging Resampler...
 | ||||
| class SoundChunkSource : public Source<1> { | ||||
| public: | ||||
| 	explicit SoundChunkSource(std::unique_ptr<SoundChunk>&& chunk) | ||||
| 		: chunk_(std::forward<std::unique_ptr<SoundChunk>>(chunk)) {} | ||||
| 
 | ||||
| 	virtual size_t generate(tcb::span<Sample<1>> buffer) override final { | ||||
| 		if (!chunk_) | ||||
| 			return 0; | ||||
| 
 | ||||
| 		size_t written = 0; | ||||
| 		for (; pos_ < chunk_->samples.size() && written < buffer.size(); pos_++) { | ||||
| 			buffer[written] = chunk_->samples[pos_]; | ||||
| 			written++; | ||||
| 		} | ||||
| 		return written; | ||||
| 	} | ||||
| 
 | ||||
| private: | ||||
| 	std::unique_ptr<SoundChunk> chunk_; | ||||
| 	size_t pos_ {0}; | ||||
| }; | ||||
| 
 | ||||
| template <class I> | ||||
| std::vector<Sample<1>> generate_to_vec(I& source, std::size_t estimate = 0) { | ||||
| 	std::vector<Sample<1>> generated; | ||||
| 
 | ||||
| 	size_t total = 0; | ||||
| 	size_t read = 0; | ||||
| 	generated.reserve(estimate); | ||||
| 	do { | ||||
| 		generated.resize(total + 4096); | ||||
| 		read = source.generate(tcb::span {generated.data() + total, 4096}); | ||||
| 		total += read; | ||||
| 	} while (read != 0); | ||||
| 	generated.resize(total); | ||||
| 	return generated; | ||||
| } | ||||
| 
 | ||||
| optional<SoundChunk> try_load_dmx(tcb::span<std::byte> data) { | ||||
| 	io::SpanStream stream {data}; | ||||
| 
 | ||||
| 	if (io::remaining(stream) < 8) | ||||
| 		return nullopt; | ||||
| 
 | ||||
| 	uint16_t version = io::read_uint16(stream); | ||||
| 	if (version != 3) | ||||
| 		return nullopt; | ||||
| 
 | ||||
| 	uint16_t rate = io::read_uint16(stream); | ||||
| 	uint32_t length = io::read_uint32(stream) - 32u; | ||||
| 
 | ||||
| 	if (io::remaining(stream) < (length + 32u)) | ||||
| 		return nullopt; | ||||
| 
 | ||||
| 	stream.seek(io::SeekFrom::kCurrent, 16); | ||||
| 
 | ||||
| 	std::vector<Sample<1>> samples; | ||||
| 	for (size_t i = 0; i < length; i++) { | ||||
| 		uint8_t doom_sample = io::read_uint8(stream); | ||||
| 		float float_sample = audio::sample_to_float(doom_sample); | ||||
| 		samples.push_back(Sample<1> {float_sample}); | ||||
| 	} | ||||
| 	size_t samples_len = samples.size(); | ||||
| 
 | ||||
| 	if (rate == 44100) { | ||||
| 		return SoundChunk {samples}; | ||||
| 	} | ||||
| 
 | ||||
| 	std::unique_ptr<SoundChunkSource> chunk_source = | ||||
| 		std::make_unique<SoundChunkSource>(std::make_unique<SoundChunk>(SoundChunk {std::move(samples)})); | ||||
| 	Resampler<1> resampler(std::move(chunk_source), rate / static_cast<float>(kSampleRate)); | ||||
| 
 | ||||
| 	std::vector<Sample<1>> resampled; | ||||
| 
 | ||||
| 	size_t total = 0; | ||||
| 	size_t read = 0; | ||||
| 	resampled.reserve(samples_len * (static_cast<float>(kSampleRate) / rate)); | ||||
| 	do { | ||||
| 		resampled.resize(total + 4096); | ||||
| 		read = resampler.generate(tcb::span {resampled.data() + total, 4096}); | ||||
| 		total += read; | ||||
| 	} while (read != 0); | ||||
| 	resampled.resize(total); | ||||
| 
 | ||||
| 	return SoundChunk {std::move(resampled)}; | ||||
| } | ||||
| 
 | ||||
| optional<SoundChunk> try_load_wav(tcb::span<std::byte> data) { | ||||
| 	io::SpanStream stream {data}; | ||||
| 
 | ||||
| 	audio::Wav wav; | ||||
| 	std::size_t sample_rate; | ||||
| 
 | ||||
| 	try { | ||||
| 		wav = audio::load_wav(stream); | ||||
| 	} catch (const std::exception& ex) { | ||||
| 		return nullopt; | ||||
| 	} | ||||
| 
 | ||||
| 	sample_rate = wav.sample_rate(); | ||||
| 
 | ||||
| 	audio::Resampler<1> resampler(std::make_unique<WavPlayer>(std::move(wav)), | ||||
| 								  sample_rate / static_cast<float>(kSampleRate)); | ||||
| 
 | ||||
| 	SoundChunk chunk {generate_to_vec(resampler)}; | ||||
| 	return chunk; | ||||
| } | ||||
| 
 | ||||
| optional<SoundChunk> try_load_ogg(tcb::span<std::byte> data) { | ||||
| 	std::shared_ptr<audio::OggPlayer<1>> player; | ||||
| 	try { | ||||
| 		io::SpanStream data_stream {data}; | ||||
| 		audio::Ogg ogg = audio::load_ogg(data_stream); | ||||
| 		player = std::make_shared<audio::OggPlayer<1>>(std::move(ogg)); | ||||
| 	} catch (...) { | ||||
| 		return nullopt; | ||||
| 	} | ||||
| 	player->looping(false); | ||||
| 	player->playing(true); | ||||
| 	player->reset(); | ||||
| 	std::size_t sample_rate = player->sample_rate(); | ||||
| 	audio::Resampler<1> resampler(player, sample_rate / 44100.); | ||||
| 	std::vector<Sample<1>> resampled {generate_to_vec(resampler)}; | ||||
| 
 | ||||
| 	SoundChunk chunk {std::move(resampled)}; | ||||
| 	return chunk; | ||||
| } | ||||
| 
 | ||||
| optional<SoundChunk> try_load_gme(tcb::span<std::byte> data) { | ||||
| 	std::shared_ptr<audio::GmePlayer<1>> player; | ||||
| 	try { | ||||
| 		if (data[0] == std::byte {0x1F} && data[1] == std::byte {0x8B}) { | ||||
| 			io::SpanStream stream {data}; | ||||
| 			audio::Gme gme = audio::load_gme(stream); | ||||
| 			player = std::make_shared<GmePlayer<1>>(std::move(gme)); | ||||
| 		} else { | ||||
| 			io::ZlibInputStream stream {io::SpanStream(data)}; | ||||
| 			audio::Gme gme = audio::load_gme(stream); | ||||
| 			player = std::make_shared<GmePlayer<1>>(std::move(gme)); | ||||
| 		} | ||||
| 	} catch (...) { | ||||
| 		return nullopt; | ||||
| 	} | ||||
| 	std::vector<Sample<1>> samples {generate_to_vec(*player)}; | ||||
| 	SoundChunk chunk {std::move(samples)}; | ||||
| 	return chunk; | ||||
| } | ||||
| 
 | ||||
| } // namespace
 | ||||
| 
 | ||||
| optional<SoundChunk> srb2::audio::try_load_chunk(tcb::span<std::byte> data) { | ||||
| 	optional<SoundChunk> ret; | ||||
| 
 | ||||
| 	ret = try_load_dmx(data); | ||||
| 	if (ret) | ||||
| 		return ret; | ||||
| 
 | ||||
| 	ret = try_load_wav(data); | ||||
| 	if (ret) | ||||
| 		return ret; | ||||
| 
 | ||||
| 	ret = try_load_ogg(data); | ||||
| 	if (ret) | ||||
| 		return ret; | ||||
| 
 | ||||
| 	ret = try_load_gme(data); | ||||
| 	if (ret) | ||||
| 		return ret; | ||||
| 
 | ||||
| 	return nullopt; | ||||
| } | ||||
							
								
								
									
										27
									
								
								src/audio/chunk_load.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/audio/chunk_load.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,27 @@ | |||
| // SONIC ROBO BLAST 2
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| // Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
 | ||||
| //
 | ||||
| // This program is free software distributed under the
 | ||||
| // terms of the GNU General Public License, version 2.
 | ||||
| // See the 'LICENSE' file for more details.
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| 
 | ||||
| #ifndef __SRB2_AUDIO_CHUNK_LOAD_HPP__ | ||||
| #define __SRB2_AUDIO_CHUNK_LOAD_HPP__ | ||||
| 
 | ||||
| #include <cstddef> | ||||
| #include <optional> | ||||
| 
 | ||||
| #include <tcb/span.hpp> | ||||
| 
 | ||||
| #include "sound_chunk.hpp" | ||||
| 
 | ||||
| namespace srb2::audio { | ||||
| 
 | ||||
| /// @brief Try to load a chunk from the given byte span.
 | ||||
| std::optional<SoundChunk> try_load_chunk(tcb::span<std::byte> data); | ||||
| 
 | ||||
| } // namespace srb2::audio
 | ||||
| 
 | ||||
| #endif // __SRB2_AUDIO_CHUNK_LOAD_HPP__
 | ||||
							
								
								
									
										26
									
								
								src/audio/expand_mono.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/audio/expand_mono.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,26 @@ | |||
| // SONIC ROBO BLAST 2
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| // Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
 | ||||
| //
 | ||||
| // This program is free software distributed under the
 | ||||
| // terms of the GNU General Public License, version 2.
 | ||||
| // See the 'LICENSE' file for more details.
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| 
 | ||||
| #include "expand_mono.hpp" | ||||
| 
 | ||||
| #include <algorithm> | ||||
| 
 | ||||
| using std::size_t; | ||||
| 
 | ||||
| using namespace srb2::audio; | ||||
| 
 | ||||
| ExpandMono::~ExpandMono() = default; | ||||
| 
 | ||||
| size_t ExpandMono::filter(tcb::span<Sample<1>> input_buffer, tcb::span<Sample<2>> buffer) { | ||||
| 	for (size_t i = 0; i < std::min(input_buffer.size(), buffer.size()); i++) { | ||||
| 		buffer[i].amplitudes[0] = input_buffer[i].amplitudes[0]; | ||||
| 		buffer[i].amplitudes[1] = input_buffer[i].amplitudes[0]; | ||||
| 	} | ||||
| 	return std::min(input_buffer.size(), buffer.size()); | ||||
| } | ||||
							
								
								
									
										27
									
								
								src/audio/expand_mono.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/audio/expand_mono.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,27 @@ | |||
| // SONIC ROBO BLAST 2
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| // Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
 | ||||
| //
 | ||||
| // This program is free software distributed under the
 | ||||
| // terms of the GNU General Public License, version 2.
 | ||||
| // See the 'LICENSE' file for more details.
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| 
 | ||||
| #ifndef __SRB2_AUDIO_EXPAND_MONO_HPP__ | ||||
| #define __SRB2_AUDIO_EXPAND_MONO_HPP__ | ||||
| 
 | ||||
| #include <tcb/span.hpp> | ||||
| 
 | ||||
| #include "filter.hpp" | ||||
| 
 | ||||
| namespace srb2::audio { | ||||
| 
 | ||||
| class ExpandMono : public Filter<1, 2> { | ||||
| public: | ||||
| 	virtual ~ExpandMono(); | ||||
| 	virtual std::size_t filter(tcb::span<Sample<1>> input_buffer, tcb::span<Sample<2>> buffer) override final; | ||||
| }; | ||||
| 
 | ||||
| } // namespace srb2::audio
 | ||||
| 
 | ||||
| #endif // __SRB2_AUDIO_EXPAND_MONO_HPP__
 | ||||
							
								
								
									
										40
									
								
								src/audio/filter.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								src/audio/filter.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,40 @@ | |||
| // SONIC ROBO BLAST 2
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| // Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
 | ||||
| //
 | ||||
| // This program is free software distributed under the
 | ||||
| // terms of the GNU General Public License, version 2.
 | ||||
| // See the 'LICENSE' file for more details.
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| 
 | ||||
| #include "filter.hpp" | ||||
| 
 | ||||
| using std::shared_ptr; | ||||
| using std::size_t; | ||||
| 
 | ||||
| using srb2::audio::Filter; | ||||
| using srb2::audio::Sample; | ||||
| using srb2::audio::Source; | ||||
| 
 | ||||
| template <size_t IC, size_t OC> | ||||
| size_t Filter<IC, OC>::generate(tcb::span<Sample<OC>> buffer) { | ||||
| 	input_buffer_.clear(); | ||||
| 	input_buffer_.resize(buffer.size()); | ||||
| 
 | ||||
| 	input_->generate(input_buffer_); | ||||
| 
 | ||||
| 	return filter(input_buffer_, buffer); | ||||
| } | ||||
| 
 | ||||
| template <size_t IC, size_t OC> | ||||
| void Filter<IC, OC>::bind(const shared_ptr<Source<IC>>& input) { | ||||
| 	input_ = input; | ||||
| } | ||||
| 
 | ||||
| template <size_t IC, size_t OC> | ||||
| Filter<IC, OC>::~Filter() = default; | ||||
| 
 | ||||
| template class srb2::audio::Filter<1, 1>; | ||||
| template class srb2::audio::Filter<1, 2>; | ||||
| template class srb2::audio::Filter<2, 1>; | ||||
| template class srb2::audio::Filter<2, 2>; | ||||
							
								
								
									
										46
									
								
								src/audio/filter.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/audio/filter.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,46 @@ | |||
| // SONIC ROBO BLAST 2
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| // Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
 | ||||
| //
 | ||||
| // This program is free software distributed under the
 | ||||
| // terms of the GNU General Public License, version 2.
 | ||||
| // See the 'LICENSE' file for more details.
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| 
 | ||||
| #ifndef __SRB2_AUDIO_FILTER_HPP__ | ||||
| #define __SRB2_AUDIO_FILTER_HPP__ | ||||
| 
 | ||||
| #include <cstddef> | ||||
| #include <memory> | ||||
| #include <vector> | ||||
| 
 | ||||
| #include <tcb/span.hpp> | ||||
| 
 | ||||
| #include "source.hpp" | ||||
| 
 | ||||
| namespace srb2::audio { | ||||
| 
 | ||||
| template <size_t IC, size_t OC> | ||||
| class Filter : public Source<OC> { | ||||
| public: | ||||
| 	virtual std::size_t generate(tcb::span<Sample<OC>> buffer) override; | ||||
| 
 | ||||
| 	void bind(const std::shared_ptr<Source<IC>>& input); | ||||
| 
 | ||||
| 	virtual std::size_t filter(tcb::span<Sample<IC>> input_buffer, tcb::span<Sample<OC>> buffer) = 0; | ||||
| 
 | ||||
| 	virtual ~Filter(); | ||||
| 
 | ||||
| private: | ||||
| 	std::shared_ptr<Source<IC>> input_; | ||||
| 	std::vector<Sample<IC>> input_buffer_; | ||||
| }; | ||||
| 
 | ||||
| extern template class Filter<1, 1>; | ||||
| extern template class Filter<1, 2>; | ||||
| extern template class Filter<2, 1>; | ||||
| extern template class Filter<2, 2>; | ||||
| 
 | ||||
| } // namespace srb2::audio
 | ||||
| 
 | ||||
| #endif // __SRB2_AUDIO_FILTER_HPP__
 | ||||
							
								
								
									
										43
									
								
								src/audio/gain.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/audio/gain.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,43 @@ | |||
| // SONIC ROBO BLAST 2
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| // Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
 | ||||
| //
 | ||||
| // This program is free software distributed under the
 | ||||
| // terms of the GNU General Public License, version 2.
 | ||||
| // See the 'LICENSE' file for more details.
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| 
 | ||||
| #include "gain.hpp" | ||||
| 
 | ||||
| #include <algorithm> | ||||
| 
 | ||||
| using std::size_t; | ||||
| 
 | ||||
| using srb2::audio::Filter; | ||||
| using srb2::audio::Gain; | ||||
| using srb2::audio::Sample; | ||||
| 
 | ||||
| constexpr const float kGainInterpolationAlpha = 0.8f; | ||||
| 
 | ||||
| template <size_t C> | ||||
| size_t Gain<C>::filter(tcb::span<Sample<C>> input_buffer, tcb::span<Sample<C>> buffer) { | ||||
| 	size_t written = std::min(buffer.size(), input_buffer.size()); | ||||
| 	for (size_t i = 0; i < written; i++) { | ||||
| 		buffer[i] = input_buffer[i]; | ||||
| 		buffer[i] *= gain_; | ||||
| 		gain_ += (new_gain_ - gain_) * kGainInterpolationAlpha; | ||||
| 	} | ||||
| 
 | ||||
| 	return written; | ||||
| } | ||||
| 
 | ||||
| template <size_t C> | ||||
| void Gain<C>::gain(float new_gain) { | ||||
| 	new_gain_ = std::clamp(new_gain, 0.0f, 1.0f); | ||||
| } | ||||
| 
 | ||||
| template <size_t C> | ||||
| Gain<C>::~Gain() = default; | ||||
| 
 | ||||
| template class srb2::audio::Gain<1>; | ||||
| template class srb2::audio::Gain<2>; | ||||
							
								
								
									
										33
									
								
								src/audio/gain.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/audio/gain.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,33 @@ | |||
| // SONIC ROBO BLAST 2
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| // Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
 | ||||
| //
 | ||||
| // This program is free software distributed under the
 | ||||
| // terms of the GNU General Public License, version 2.
 | ||||
| // See the 'LICENSE' file for more details.
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| 
 | ||||
| #ifndef __SRB2_AUDIO_GAIN_HPP__ | ||||
| #define __SRB2_AUDIO_GAIN_HPP__ | ||||
| 
 | ||||
| #include <tcb/span.hpp> | ||||
| 
 | ||||
| #include "filter.hpp" | ||||
| 
 | ||||
| namespace srb2::audio { | ||||
| 
 | ||||
| template <size_t C> | ||||
| class Gain : public Filter<C, C> { | ||||
| public: | ||||
| 	virtual std::size_t filter(tcb::span<Sample<C>> input_buffer, tcb::span<Sample<C>> buffer) override final; | ||||
| 	void gain(float new_gain); | ||||
| 
 | ||||
| 	virtual ~Gain(); | ||||
| 
 | ||||
| private: | ||||
| 	float new_gain_ {1.f}; | ||||
| 	float gain_ {1.f}; | ||||
| }; | ||||
| } // namespace srb2::audio
 | ||||
| 
 | ||||
| #endif // __SRB2_AUDIO_GAIN_HPP__
 | ||||
							
								
								
									
										141
									
								
								src/audio/gme.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								src/audio/gme.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,141 @@ | |||
| // SONIC ROBO BLAST 2
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| // Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
 | ||||
| //
 | ||||
| // This program is free software distributed under the
 | ||||
| // terms of the GNU General Public License, version 2.
 | ||||
| // See the 'LICENSE' file for more details.
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| 
 | ||||
| #include "gme.hpp" | ||||
| 
 | ||||
| #include <limits> | ||||
| #include <stdexcept> | ||||
| 
 | ||||
| #include "../cxxutil.hpp" | ||||
| 
 | ||||
| using namespace srb2; | ||||
| using namespace srb2::audio; | ||||
| 
 | ||||
| Gme::Gme() : memory_data_(), instance_(nullptr) { | ||||
| } | ||||
| 
 | ||||
| Gme::Gme(Gme&& rhs) noexcept : memory_data_(), instance_(nullptr) { | ||||
| 	std::swap(memory_data_, rhs.memory_data_); | ||||
| 	std::swap(instance_, rhs.instance_); | ||||
| } | ||||
| 
 | ||||
| Gme::Gme(std::vector<std::byte>&& data) : memory_data_(std::move(data)), instance_(nullptr) { | ||||
| 	_init_with_data(); | ||||
| } | ||||
| 
 | ||||
| Gme::Gme(tcb::span<std::byte> data) : memory_data_(data.begin(), data.end()), instance_(nullptr) { | ||||
| 	_init_with_data(); | ||||
| } | ||||
| 
 | ||||
| Gme& Gme::operator=(Gme&& rhs) noexcept { | ||||
| 	std::swap(memory_data_, rhs.memory_data_); | ||||
| 	std::swap(instance_, rhs.instance_); | ||||
| 
 | ||||
| 	return *this; | ||||
| } | ||||
| 
 | ||||
| Gme::~Gme() { | ||||
| 	if (instance_) { | ||||
| 		gme_delete(instance_); | ||||
| 		instance_ = nullptr; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| std::size_t Gme::get_samples(tcb::span<short> buffer) { | ||||
| 	SRB2_ASSERT(instance_ != nullptr); | ||||
| 
 | ||||
| 	gme_err_t err = gme_play(instance_, buffer.size(), buffer.data()); | ||||
| 	if (err) | ||||
| 		throw GmeException(err); | ||||
| 
 | ||||
| 	return buffer.size(); | ||||
| } | ||||
| 
 | ||||
| void Gme::seek(int sample) { | ||||
| 	SRB2_ASSERT(instance_ != nullptr); | ||||
| 
 | ||||
| 	gme_seek_samples(instance_, sample); | ||||
| } | ||||
| 
 | ||||
| std::optional<float> Gme::duration_seconds() const { | ||||
| 	SRB2_ASSERT(instance_ != nullptr); | ||||
| 
 | ||||
| 	gme_info_t* info = nullptr; | ||||
| 	gme_err_t res = gme_track_info(instance_, &info, 0); | ||||
| 	if (res) | ||||
| 		throw GmeException(res); | ||||
| 	auto info_finally = srb2::finally([&info] { gme_free_info(info); }); | ||||
| 
 | ||||
| 	if (info->length == -1) | ||||
| 		return std::nullopt; | ||||
| 
 | ||||
| 	// info lengths are in ms
 | ||||
| 	return static_cast<float>(info->length) / 1000.f; | ||||
| } | ||||
| 
 | ||||
| std::optional<float> Gme::loop_point_seconds() const { | ||||
| 	SRB2_ASSERT(instance_ != nullptr); | ||||
| 
 | ||||
| 	gme_info_t* info = nullptr; | ||||
| 	gme_err_t res = gme_track_info(instance_, &info, 0); | ||||
| 	if (res) | ||||
| 		throw GmeException(res); | ||||
| 	auto info_finally = srb2::finally([&info] { gme_free_info(info); }); | ||||
| 
 | ||||
| 	int loop_point_ms = info->intro_length; | ||||
| 	if (loop_point_ms == -1) | ||||
| 		return std::nullopt; | ||||
| 
 | ||||
| 	return loop_point_ms / 44100.f; | ||||
| } | ||||
| 
 | ||||
| float Gme::position_seconds() const { | ||||
| 	SRB2_ASSERT(instance_ != nullptr); | ||||
| 
 | ||||
| 	gme_info_t* info = nullptr; | ||||
| 	gme_err_t res = gme_track_info(instance_, &info, 0); | ||||
| 	if (res) | ||||
| 		throw GmeException(res); | ||||
| 	auto info_finally = srb2::finally([&info] { gme_free_info(info); }); | ||||
| 
 | ||||
| 	int position = gme_tell(instance_); | ||||
| 
 | ||||
| 	// adjust position, since GME's counter keeps going past loop
 | ||||
| 	if (info->length > 0) | ||||
| 		position %= info->length; | ||||
| 	else if (info->intro_length + info->loop_length > 0) | ||||
| 		position = position >= (info->intro_length + info->loop_length) ? (position % info->loop_length) : position; | ||||
| 	else | ||||
| 		position %= 150 * 1000; // 2.5 minutes
 | ||||
| 
 | ||||
| 	return position / 1000.f; | ||||
| } | ||||
| 
 | ||||
| void Gme::_init_with_data() { | ||||
| 	if (instance_) { | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	if (memory_data_.size() >= std::numeric_limits<long>::max()) | ||||
| 		throw std::invalid_argument("Buffer is too large for gme"); | ||||
| 	if (memory_data_.size() == 0) | ||||
| 		throw std::invalid_argument("Insufficient data from stream"); | ||||
| 
 | ||||
| 	gme_err_t result = | ||||
| 		gme_open_data(reinterpret_cast<const void*>(memory_data_.data()), memory_data_.size(), &instance_, 44100); | ||||
| 	if (result) | ||||
| 		throw GmeException(result); | ||||
| 
 | ||||
| 	// we no longer need the data, so there's no reason to keep the allocation
 | ||||
| 	memory_data_ = std::vector<std::byte>(); | ||||
| 
 | ||||
| 	result = gme_start_track(instance_, 0); | ||||
| 	if (result) | ||||
| 		throw GmeException(result); | ||||
| } | ||||
							
								
								
									
										74
									
								
								src/audio/gme.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								src/audio/gme.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,74 @@ | |||
| // SONIC ROBO BLAST 2
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| // Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
 | ||||
| //
 | ||||
| // This program is free software distributed under the
 | ||||
| // terms of the GNU General Public License, version 2.
 | ||||
| // See the 'LICENSE' file for more details.
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| 
 | ||||
| #ifndef __SRB2_AUDIO_GME_HPP__ | ||||
| #define __SRB2_AUDIO_GME_HPP__ | ||||
| 
 | ||||
| #include <cstddef> | ||||
| #include <memory> | ||||
| #include <optional> | ||||
| #include <string> | ||||
| #include <type_traits> | ||||
| #include <utility> | ||||
| #include <vector> | ||||
| 
 | ||||
| #include <gme/gme.h> | ||||
| #undef byte	 // BLARGG!! NO!!
 | ||||
| #undef check // STOP IT!!!!
 | ||||
| 
 | ||||
| #include "../io/streams.hpp" | ||||
| 
 | ||||
| namespace srb2::audio { | ||||
| 
 | ||||
| class GmeException : public std::exception { | ||||
| 	std::string msg_; | ||||
| 
 | ||||
| public: | ||||
| 	explicit GmeException(gme_err_t msg) : msg_(msg == nullptr ? "" : msg) {} | ||||
| 
 | ||||
| 	virtual const char* what() const noexcept override { return msg_.c_str(); } | ||||
| }; | ||||
| 
 | ||||
| class Gme { | ||||
| 	std::vector<std::byte> memory_data_; | ||||
| 	Music_Emu* instance_; | ||||
| 
 | ||||
| public: | ||||
| 	Gme(); | ||||
| 	Gme(const Gme&) = delete; | ||||
| 	Gme(Gme&& rhs) noexcept; | ||||
| 
 | ||||
| 	Gme& operator=(const Gme&) = delete; | ||||
| 	Gme& operator=(Gme&& rhs) noexcept; | ||||
| 
 | ||||
| 	explicit Gme(std::vector<std::byte>&& data); | ||||
| 	explicit Gme(tcb::span<std::byte> data); | ||||
| 
 | ||||
| 	std::size_t get_samples(tcb::span<short> buffer); | ||||
| 	void seek(int sample); | ||||
| 
 | ||||
| 	std::optional<float> duration_seconds() const; | ||||
| 	std::optional<float> loop_point_seconds() const; | ||||
| 	float position_seconds() const; | ||||
| 
 | ||||
| 	~Gme(); | ||||
| 
 | ||||
| private: | ||||
| 	void _init_with_data(); | ||||
| }; | ||||
| 
 | ||||
| template <typename I, typename std::enable_if_t<srb2::io::IsInputStreamV<I>, int> = 0> | ||||
| inline Gme load_gme(I& stream) { | ||||
| 	std::vector<std::byte> data = srb2::io::read_to_vec(stream); | ||||
| 	return Gme {std::move(data)}; | ||||
| } | ||||
| 
 | ||||
| } // namespace srb2::audio
 | ||||
| 
 | ||||
| #endif // __SRB2_AUDIO_GME_HPP__
 | ||||
							
								
								
									
										73
									
								
								src/audio/gme_player.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								src/audio/gme_player.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,73 @@ | |||
| // SONIC ROBO BLAST 2
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| // Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
 | ||||
| //
 | ||||
| // This program is free software distributed under the
 | ||||
| // terms of the GNU General Public License, version 2.
 | ||||
| // See the 'LICENSE' file for more details.
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| 
 | ||||
| #include "gme_player.hpp" | ||||
| 
 | ||||
| using namespace srb2; | ||||
| using namespace srb2::audio; | ||||
| 
 | ||||
| template <size_t C> | ||||
| GmePlayer<C>::GmePlayer(Gme&& gme) : gme_(std::forward<Gme>(gme)), buf_() { | ||||
| } | ||||
| 
 | ||||
| template <size_t C> | ||||
| GmePlayer<C>::GmePlayer(GmePlayer<C>&& rhs) noexcept = default; | ||||
| 
 | ||||
| template <size_t C> | ||||
| GmePlayer<C>& GmePlayer<C>::operator=(GmePlayer<C>&& rhs) noexcept = default; | ||||
| 
 | ||||
| template <size_t C> | ||||
| GmePlayer<C>::~GmePlayer() = default; | ||||
| 
 | ||||
| template <size_t C> | ||||
| std::size_t GmePlayer<C>::generate(tcb::span<Sample<C>> buffer) { | ||||
| 	buf_.clear(); | ||||
| 	buf_.resize(buffer.size() * 2); | ||||
| 
 | ||||
| 	std::size_t read = gme_.get_samples(tcb::make_span(buf_)); | ||||
| 	buf_.resize(read); | ||||
| 	std::size_t new_samples = std::min((read / 2), buffer.size()); | ||||
| 	for (std::size_t i = 0; i < new_samples; i++) { | ||||
| 		if constexpr (C == 1) { | ||||
| 			buffer[i].amplitudes[0] = (buf_[i * 2] / 32768.f + buf_[i * 2 + 1] / 32768.f) / 2.f; | ||||
| 		} else if constexpr (C == 2) { | ||||
| 			buffer[i].amplitudes[0] = buf_[i * 2] / 32768.f; | ||||
| 			buffer[i].amplitudes[1] = buf_[i * 2 + 1] / 32768.f; | ||||
| 		} | ||||
| 	} | ||||
| 	return new_samples; | ||||
| } | ||||
| 
 | ||||
| template <size_t C> | ||||
| void GmePlayer<C>::seek(float position_seconds) { | ||||
| 	gme_.seek(static_cast<std::size_t>(position_seconds * 44100.f)); | ||||
| } | ||||
| 
 | ||||
| template <size_t C> | ||||
| void GmePlayer<C>::reset() { | ||||
| 	gme_.seek(0); | ||||
| } | ||||
| 
 | ||||
| template <size_t C> | ||||
| std::optional<float> GmePlayer<C>::duration_seconds() const { | ||||
| 	return gme_.duration_seconds(); | ||||
| } | ||||
| 
 | ||||
| template <size_t C> | ||||
| std::optional<float> GmePlayer<C>::loop_point_seconds() const { | ||||
| 	return gme_.loop_point_seconds(); | ||||
| } | ||||
| 
 | ||||
| template <size_t C> | ||||
| float GmePlayer<C>::position_seconds() const { | ||||
| 	return gme_.position_seconds(); | ||||
| } | ||||
| 
 | ||||
| template class srb2::audio::GmePlayer<1>; | ||||
| template class srb2::audio::GmePlayer<2>; | ||||
							
								
								
									
										51
									
								
								src/audio/gme_player.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/audio/gme_player.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,51 @@ | |||
| // SONIC ROBO BLAST 2
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| // Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
 | ||||
| //
 | ||||
| // This program is free software distributed under the
 | ||||
| // terms of the GNU General Public License, version 2.
 | ||||
| // See the 'LICENSE' file for more details.
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| 
 | ||||
| #ifndef __SRB2_AUDIO_GME_PLAYER_HPP__ | ||||
| #define __SRB2_AUDIO_GME_PLAYER_HPP__ | ||||
| 
 | ||||
| #include <optional> | ||||
| 
 | ||||
| #include "gme.hpp" | ||||
| #include "source.hpp" | ||||
| 
 | ||||
| namespace srb2::audio { | ||||
| 
 | ||||
| template <size_t C> | ||||
| class GmePlayer : public Source<C> { | ||||
| 	Gme gme_; | ||||
| 	std::vector<short> buf_; | ||||
| 
 | ||||
| public: | ||||
| 	GmePlayer(Gme&& gme); | ||||
| 	GmePlayer(const GmePlayer<C>&) = delete; | ||||
| 	GmePlayer(GmePlayer<C>&& gme) noexcept; | ||||
| 
 | ||||
| 	~GmePlayer(); | ||||
| 
 | ||||
| 	GmePlayer& operator=(const GmePlayer<C>&) = delete; | ||||
| 	GmePlayer& operator=(GmePlayer<C>&& rhs) noexcept; | ||||
| 
 | ||||
| 	virtual std::size_t generate(tcb::span<Sample<C>> buffer) override; | ||||
| 
 | ||||
| 	void seek(float position_seconds); | ||||
| 
 | ||||
| 	std::optional<float> duration_seconds() const; | ||||
| 	std::optional<float> loop_point_seconds() const; | ||||
| 	float position_seconds() const; | ||||
| 
 | ||||
| 	void reset(); | ||||
| }; | ||||
| 
 | ||||
| extern template class GmePlayer<1>; | ||||
| extern template class GmePlayer<2>; | ||||
| 
 | ||||
| } // namespace srb2::audio
 | ||||
| 
 | ||||
| #endif // __SRB2_AUDIO_GME_PLAYER_HPP__
 | ||||
							
								
								
									
										62
									
								
								src/audio/mixer.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								src/audio/mixer.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,62 @@ | |||
| // SONIC ROBO BLAST 2
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| // Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
 | ||||
| //
 | ||||
| // This program is free software distributed under the
 | ||||
| // terms of the GNU General Public License, version 2.
 | ||||
| // See the 'LICENSE' file for more details.
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| 
 | ||||
| #include "mixer.hpp" | ||||
| 
 | ||||
| #include <algorithm> | ||||
| 
 | ||||
| using std::shared_ptr; | ||||
| using std::size_t; | ||||
| 
 | ||||
| using srb2::audio::Mixer; | ||||
| using srb2::audio::Sample; | ||||
| using srb2::audio::Source; | ||||
| 
 | ||||
| namespace { | ||||
| 
 | ||||
| template <size_t C> | ||||
| void default_init_sample_buffer(Sample<C>* buffer, size_t size) { | ||||
| 	std::for_each(buffer, buffer + size, [](auto& i) { i = Sample<C> {}; }); | ||||
| } | ||||
| 
 | ||||
| template <size_t C> | ||||
| void mix_sample_buffers(Sample<C>* dst, size_t size, Sample<C>* src, size_t src_size) { | ||||
| 	for (size_t i = 0; i < size && i < src_size; i++) { | ||||
| 		dst[i] += src[i]; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| } // namespace
 | ||||
| 
 | ||||
| template <size_t C> | ||||
| size_t Mixer<C>::generate(tcb::span<Sample<C>> buffer) { | ||||
| 	buffer_.resize(buffer.size()); | ||||
| 
 | ||||
| 	default_init_sample_buffer<C>(buffer.data(), buffer.size()); | ||||
| 
 | ||||
| 	for (auto& source : sources_) { | ||||
| 		size_t read = source->generate(buffer_); | ||||
| 
 | ||||
| 		mix_sample_buffers<C>(buffer.data(), buffer.size(), buffer_.data(), read); | ||||
| 	} | ||||
| 
 | ||||
| 	// because we initialized the out-buffer, we always generate size samples
 | ||||
| 	return buffer.size(); | ||||
| } | ||||
| 
 | ||||
| template <size_t C> | ||||
| void Mixer<C>::add_source(const shared_ptr<Source<C>>& source) { | ||||
| 	sources_.push_back(source); | ||||
| } | ||||
| 
 | ||||
| template <size_t C> | ||||
| Mixer<C>::~Mixer() = default; | ||||
| 
 | ||||
| template class srb2::audio::Mixer<1>; | ||||
| template class srb2::audio::Mixer<2>; | ||||
							
								
								
									
										41
									
								
								src/audio/mixer.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								src/audio/mixer.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,41 @@ | |||
| // SONIC ROBO BLAST 2
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| // Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
 | ||||
| //
 | ||||
| // This program is free software distributed under the
 | ||||
| // terms of the GNU General Public License, version 2.
 | ||||
| // See the 'LICENSE' file for more details.
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| 
 | ||||
| #ifndef __SRB2_AUDIO_MIXER_HPP__ | ||||
| #define __SRB2_AUDIO_MIXER_HPP__ | ||||
| 
 | ||||
| #include <memory> | ||||
| #include <vector> | ||||
| 
 | ||||
| #include <tcb/span.hpp> | ||||
| 
 | ||||
| #include "source.hpp" | ||||
| 
 | ||||
| namespace srb2::audio { | ||||
| 
 | ||||
| template <size_t C> | ||||
| class Mixer : public Source<C> { | ||||
| public: | ||||
| 	virtual std::size_t generate(tcb::span<Sample<C>> buffer) override final; | ||||
| 
 | ||||
| 	virtual ~Mixer(); | ||||
| 
 | ||||
| 	void add_source(const std::shared_ptr<Source<C>>& source); | ||||
| 
 | ||||
| private: | ||||
| 	std::vector<std::shared_ptr<Source<C>>> sources_; | ||||
| 	std::vector<Sample<C>> buffer_; | ||||
| }; | ||||
| 
 | ||||
| extern template class Mixer<1>; | ||||
| extern template class Mixer<2>; | ||||
| 
 | ||||
| } // namespace srb2::audio
 | ||||
| 
 | ||||
| #endif // __SRB2_AUDIO_MIXER_HPP__
 | ||||
							
								
								
									
										421
									
								
								src/audio/music_player.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										421
									
								
								src/audio/music_player.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,421 @@ | |||
| // SONIC ROBO BLAST 2
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| // Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
 | ||||
| //
 | ||||
| // This program is free software distributed under the
 | ||||
| // terms of the GNU General Public License, version 2.
 | ||||
| // See the 'LICENSE' file for more details.
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| 
 | ||||
| #include "music_player.hpp" | ||||
| 
 | ||||
| #include <algorithm> | ||||
| #include <cmath> | ||||
| #include <exception> | ||||
| #include <optional> | ||||
| #include <stdexcept> | ||||
| #include <string> | ||||
| #include <string_view> | ||||
| #include <variant> | ||||
| #include <vector> | ||||
| 
 | ||||
| #include <gme/gme.h> | ||||
| #include <stb_vorbis.h> | ||||
| #undef byte	 // BLARGG!! NO!!
 | ||||
| #undef check // STOP IT!!!!
 | ||||
| 
 | ||||
| #include "../cxxutil.hpp" | ||||
| #include "../io/streams.hpp" | ||||
| #include "gme_player.hpp" | ||||
| #include "ogg_player.hpp" | ||||
| #include "resample.hpp" | ||||
| #include "xmp_player.hpp" | ||||
| 
 | ||||
| using std::array; | ||||
| using std::byte; | ||||
| using std::make_unique; | ||||
| using std::size_t; | ||||
| using std::vector; | ||||
| 
 | ||||
| using srb2::audio::MusicPlayer; | ||||
| using srb2::audio::Resampler; | ||||
| using srb2::audio::Sample; | ||||
| using srb2::audio::Source; | ||||
| using namespace srb2; | ||||
| 
 | ||||
| class MusicPlayer::Impl { | ||||
| public: | ||||
| 	Impl() = default; | ||||
| 	Impl(tcb::span<std::byte> data) : Impl() { _load(data); } | ||||
| 
 | ||||
| 	size_t generate(tcb::span<Sample<2>> buffer) { | ||||
| 		if (!resampler_) | ||||
| 			return 0; | ||||
| 
 | ||||
| 		if (!playing_) | ||||
| 			return 0; | ||||
| 
 | ||||
| 		size_t total_written = 0; | ||||
| 
 | ||||
| 		while (total_written < buffer.size()) { | ||||
| 			const size_t generated = resampler_->generate(buffer.subspan(total_written)); | ||||
| 
 | ||||
| 			// To avoid a branch preventing optimizations, we're always going to apply
 | ||||
| 			// the fade gain, even if it would clamp anyway.
 | ||||
| 			for (std::size_t i = 0; i < generated; i++) { | ||||
| 				const float alpha = 1.0 - (gain_samples_target_ - std::min(gain_samples_ + i, gain_samples_target_)) / | ||||
| 											  static_cast<double>(gain_samples_target_); | ||||
| 				const float fade_gain = (gain_target_ - gain_) * std::clamp(alpha, 0.f, 1.f) + gain_; | ||||
| 				buffer[total_written + i] *= fade_gain; | ||||
| 			} | ||||
| 
 | ||||
| 			gain_samples_ = std::min(gain_samples_ + generated, gain_samples_target_); | ||||
| 
 | ||||
| 			if (gain_samples_ >= gain_samples_target_) { | ||||
| 				fading_ = false; | ||||
| 				gain_samples_ = gain_samples_target_; | ||||
| 				gain_ = gain_target_; | ||||
| 			} | ||||
| 
 | ||||
| 			total_written += generated; | ||||
| 
 | ||||
| 			if (generated == 0) { | ||||
| 				playing_ = false; | ||||
| 				break; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		return total_written; | ||||
| 	} | ||||
| 
 | ||||
| 	void _load(tcb::span<std::byte> data) { | ||||
| 		ogg_inst_ = nullptr; | ||||
| 		gme_inst_ = nullptr; | ||||
| 		xmp_inst_ = nullptr; | ||||
| 		resampler_ = std::nullopt; | ||||
| 
 | ||||
| 		try { | ||||
| 			io::SpanStream stream {data}; | ||||
| 			audio::Ogg ogg = audio::load_ogg(stream); | ||||
| 			ogg_inst_ = std::make_shared<audio::OggPlayer<2>>(std::move(ogg)); | ||||
| 			ogg_inst_->looping(looping_); | ||||
| 			resampler_ = Resampler<2>(ogg_inst_, ogg_inst_->sample_rate() / 44100.f); | ||||
| 		} catch (const std::exception& ex) { | ||||
| 			// it's probably not ogg
 | ||||
| 			ogg_inst_ = nullptr; | ||||
| 			resampler_ = std::nullopt; | ||||
| 		} | ||||
| 
 | ||||
| 		if (!resampler_) { | ||||
| 			try { | ||||
| 				if (data[0] == std::byte {0x1F} && data[1] == std::byte {0x8B}) { | ||||
| 					io::ZlibInputStream stream {io::SpanStream(data)}; | ||||
| 					audio::Gme gme = audio::load_gme(stream); | ||||
| 					gme_inst_ = std::make_shared<GmePlayer<2>>(std::move(gme)); | ||||
| 				} else { | ||||
| 					io::SpanStream stream {data}; | ||||
| 					audio::Gme gme = audio::load_gme(stream); | ||||
| 					gme_inst_ = std::make_shared<GmePlayer<2>>(std::move(gme)); | ||||
| 				} | ||||
| 
 | ||||
| 				resampler_ = Resampler<2>(gme_inst_, 1.f); | ||||
| 			} catch (const std::exception& ex) { | ||||
| 				// it's probably not gme
 | ||||
| 				gme_inst_ = nullptr; | ||||
| 				resampler_ = std::nullopt; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		if (!resampler_) { | ||||
| 			try { | ||||
| 				io::SpanStream stream {data}; | ||||
| 				audio::Xmp<2> xmp = audio::load_xmp<2>(stream); | ||||
| 				xmp_inst_ = std::make_shared<XmpPlayer<2>>(std::move(xmp)); | ||||
| 				xmp_inst_->looping(looping_); | ||||
| 
 | ||||
| 				resampler_ = Resampler<2>(xmp_inst_, 1.f); | ||||
| 			} catch (const std::exception& ex) { | ||||
| 				// it's probably not xmp
 | ||||
| 				xmp_inst_ = nullptr; | ||||
| 				resampler_ = std::nullopt; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		playing_ = false; | ||||
| 
 | ||||
| 		internal_gain(1.f); | ||||
| 	} | ||||
| 
 | ||||
| 	void play(bool looping) { | ||||
| 		if (ogg_inst_) { | ||||
| 			ogg_inst_->looping(looping); | ||||
| 			ogg_inst_->playing(true); | ||||
| 			playing_ = true; | ||||
| 			ogg_inst_->reset(); | ||||
| 		} else if (gme_inst_) { | ||||
| 			playing_ = true; | ||||
| 			gme_inst_->reset(); | ||||
| 		} else if (xmp_inst_) { | ||||
| 			xmp_inst_->looping(looping); | ||||
| 			playing_ = true; | ||||
| 			xmp_inst_->reset(); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	void unpause() { | ||||
| 		if (ogg_inst_) { | ||||
| 			ogg_inst_->playing(true); | ||||
| 			playing_ = true; | ||||
| 		} else if (gme_inst_) { | ||||
| 			playing_ = true; | ||||
| 		} else if (xmp_inst_) { | ||||
| 			playing_ = true; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	void pause() { | ||||
| 		if (ogg_inst_) { | ||||
| 			ogg_inst_->playing(false); | ||||
| 			playing_ = false; | ||||
| 		} else if (gme_inst_) { | ||||
| 			playing_ = false; | ||||
| 		} else if (xmp_inst_) { | ||||
| 			playing_ = false; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	void stop() { | ||||
| 		if (ogg_inst_) { | ||||
| 			ogg_inst_->reset(); | ||||
| 			ogg_inst_->playing(false); | ||||
| 			playing_ = false; | ||||
| 		} else if (gme_inst_) { | ||||
| 			gme_inst_->reset(); | ||||
| 			playing_ = false; | ||||
| 		} else if (xmp_inst_) { | ||||
| 			xmp_inst_->reset(); | ||||
| 			playing_ = false; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	void seek(float position_seconds) { | ||||
| 		if (ogg_inst_) { | ||||
| 			ogg_inst_->seek(position_seconds); | ||||
| 			return; | ||||
| 		} | ||||
| 		if (gme_inst_) { | ||||
| 			gme_inst_->seek(position_seconds); | ||||
| 			return; | ||||
| 		} | ||||
| 		if (xmp_inst_) { | ||||
| 			xmp_inst_->seek(position_seconds); | ||||
| 			return; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	bool playing() const { | ||||
| 		if (ogg_inst_) | ||||
| 			return ogg_inst_->playing(); | ||||
| 		else if (gme_inst_) | ||||
| 			return playing_; | ||||
| 		else if (xmp_inst_) | ||||
| 			return playing_; | ||||
| 
 | ||||
| 		return false; | ||||
| 	} | ||||
| 
 | ||||
| 	std::optional<audio::MusicType> music_type() const { | ||||
| 		if (ogg_inst_) | ||||
| 			return audio::MusicType::kOgg; | ||||
| 		else if (gme_inst_) | ||||
| 			return audio::MusicType::kGme; | ||||
| 		else if (xmp_inst_) | ||||
| 			return audio::MusicType::kMod; | ||||
| 
 | ||||
| 		return std::nullopt; | ||||
| 	} | ||||
| 
 | ||||
| 	std::optional<float> duration_seconds() const { | ||||
| 		if (ogg_inst_) | ||||
| 			return ogg_inst_->duration_seconds(); | ||||
| 		if (gme_inst_) | ||||
| 			return gme_inst_->duration_seconds(); | ||||
| 		if (xmp_inst_) | ||||
| 			return xmp_inst_->duration_seconds(); | ||||
| 
 | ||||
| 		return std::nullopt; | ||||
| 	} | ||||
| 
 | ||||
| 	std::optional<float> loop_point_seconds() const { | ||||
| 		if (ogg_inst_) | ||||
| 			return ogg_inst_->loop_point_seconds(); | ||||
| 		if (gme_inst_) | ||||
| 			return gme_inst_->loop_point_seconds(); | ||||
| 
 | ||||
| 		return std::nullopt; | ||||
| 	} | ||||
| 
 | ||||
| 	std::optional<float> position_seconds() const { | ||||
| 		if (ogg_inst_) | ||||
| 			return ogg_inst_->position_seconds(); | ||||
| 		if (gme_inst_) | ||||
| 			return gme_inst_->position_seconds(); | ||||
| 
 | ||||
| 		return std::nullopt; | ||||
| 	} | ||||
| 
 | ||||
| 	void fade_to(float gain, float seconds) { fade_from_to(gain_target_, gain, seconds); } | ||||
| 
 | ||||
| 	void fade_from_to(float from, float to, float seconds) { | ||||
| 		fading_ = true; | ||||
| 		gain_ = from; | ||||
| 		gain_target_ = to; | ||||
| 		// Gain samples target must always be at least 1 to avoid a div-by-zero.
 | ||||
| 		gain_samples_target_ = std::max(static_cast<uint64_t>(seconds * 44100.f), 1ULL); | ||||
| 		gain_samples_ = 0; | ||||
| 	} | ||||
| 
 | ||||
| 	bool fading() const { return fading_; } | ||||
| 
 | ||||
| 	void stop_fade() { internal_gain(gain_target_); } | ||||
| 
 | ||||
| 	void loop_point_seconds(float loop_point) { | ||||
| 		if (ogg_inst_) | ||||
| 			ogg_inst_->loop_point_seconds(loop_point); | ||||
| 	} | ||||
| 
 | ||||
| 	void internal_gain(float gain) { | ||||
| 		fading_ = false; | ||||
| 		gain_ = gain; | ||||
| 		gain_target_ = gain; | ||||
| 		gain_samples_target_ = 1; | ||||
| 		gain_samples_ = 0; | ||||
| 	} | ||||
| 
 | ||||
| private: | ||||
| 	std::shared_ptr<OggPlayer<2>> ogg_inst_; | ||||
| 	std::shared_ptr<GmePlayer<2>> gme_inst_; | ||||
| 	std::shared_ptr<XmpPlayer<2>> xmp_inst_; | ||||
| 	std::optional<Resampler<2>> resampler_; | ||||
| 	bool playing_ {false}; | ||||
| 	bool looping_ {false}; | ||||
| 
 | ||||
| 	// fade control
 | ||||
| 	float gain_target_ {1.f}; | ||||
| 	float gain_ {1.f}; | ||||
| 	bool fading_ {false}; | ||||
| 	uint64_t gain_samples_ {0}; | ||||
| 	uint64_t gain_samples_target_ {1}; | ||||
| }; | ||||
| 
 | ||||
| // The special member functions MUST be declared in this unit, where Impl is complete.
 | ||||
| MusicPlayer::MusicPlayer() : impl_(make_unique<MusicPlayer::Impl>()) { | ||||
| } | ||||
| MusicPlayer::MusicPlayer(tcb::span<std::byte> data) : impl_(make_unique<MusicPlayer::Impl>(data)) { | ||||
| } | ||||
| MusicPlayer::MusicPlayer(MusicPlayer&& rhs) noexcept = default; | ||||
| MusicPlayer& MusicPlayer::operator=(MusicPlayer&& rhs) noexcept = default; | ||||
| 
 | ||||
| MusicPlayer::~MusicPlayer() = default; | ||||
| 
 | ||||
| void MusicPlayer::play(bool looping) { | ||||
| 	SRB2_ASSERT(impl_ != nullptr); | ||||
| 
 | ||||
| 	return impl_->play(looping); | ||||
| } | ||||
| 
 | ||||
| void MusicPlayer::unpause() { | ||||
| 	SRB2_ASSERT(impl_ != nullptr); | ||||
| 
 | ||||
| 	return impl_->unpause(); | ||||
| } | ||||
| 
 | ||||
| void MusicPlayer::pause() { | ||||
| 	SRB2_ASSERT(impl_ != nullptr); | ||||
| 
 | ||||
| 	return impl_->pause(); | ||||
| } | ||||
| 
 | ||||
| void MusicPlayer::stop() { | ||||
| 	SRB2_ASSERT(impl_ != nullptr); | ||||
| 
 | ||||
| 	return impl_->stop(); | ||||
| } | ||||
| 
 | ||||
| void MusicPlayer::seek(float position_seconds) { | ||||
| 	SRB2_ASSERT(impl_ != nullptr); | ||||
| 
 | ||||
| 	return impl_->seek(position_seconds); | ||||
| } | ||||
| 
 | ||||
| bool MusicPlayer::playing() const { | ||||
| 	SRB2_ASSERT(impl_ != nullptr); | ||||
| 
 | ||||
| 	return impl_->playing(); | ||||
| } | ||||
| 
 | ||||
| size_t MusicPlayer::generate(tcb::span<Sample<2>> buffer) { | ||||
| 	SRB2_ASSERT(impl_ != nullptr); | ||||
| 
 | ||||
| 	return impl_->generate(buffer); | ||||
| } | ||||
| 
 | ||||
| std::optional<audio::MusicType> MusicPlayer::music_type() const { | ||||
| 	SRB2_ASSERT(impl_ != nullptr); | ||||
| 
 | ||||
| 	return impl_->music_type(); | ||||
| } | ||||
| 
 | ||||
| std::optional<float> MusicPlayer::duration_seconds() const { | ||||
| 	SRB2_ASSERT(impl_ != nullptr); | ||||
| 
 | ||||
| 	return impl_->duration_seconds(); | ||||
| } | ||||
| 
 | ||||
| std::optional<float> MusicPlayer::loop_point_seconds() const { | ||||
| 	SRB2_ASSERT(impl_ != nullptr); | ||||
| 
 | ||||
| 	return impl_->loop_point_seconds(); | ||||
| } | ||||
| 
 | ||||
| std::optional<float> MusicPlayer::position_seconds() const { | ||||
| 	SRB2_ASSERT(impl_ != nullptr); | ||||
| 
 | ||||
| 	return impl_->position_seconds(); | ||||
| } | ||||
| 
 | ||||
| void MusicPlayer::fade_to(float gain, float seconds) { | ||||
| 	SRB2_ASSERT(impl_ != nullptr); | ||||
| 
 | ||||
| 	impl_->fade_to(gain, seconds); | ||||
| } | ||||
| 
 | ||||
| void MusicPlayer::fade_from_to(float from, float to, float seconds) { | ||||
| 	SRB2_ASSERT(impl_ != nullptr); | ||||
| 
 | ||||
| 	impl_->fade_from_to(from, to, seconds); | ||||
| } | ||||
| 
 | ||||
| void MusicPlayer::internal_gain(float gain) { | ||||
| 	SRB2_ASSERT(impl_ != nullptr); | ||||
| 
 | ||||
| 	impl_->internal_gain(gain); | ||||
| } | ||||
| 
 | ||||
| bool MusicPlayer::fading() const { | ||||
| 	SRB2_ASSERT(impl_ != nullptr); | ||||
| 
 | ||||
| 	return impl_->fading(); | ||||
| } | ||||
| 
 | ||||
| void MusicPlayer::stop_fade() { | ||||
| 	SRB2_ASSERT(impl_ != nullptr); | ||||
| 
 | ||||
| 	impl_->stop_fade(); | ||||
| } | ||||
| 
 | ||||
| void MusicPlayer::loop_point_seconds(float loop_point) { | ||||
| 	SRB2_ASSERT(impl_ != nullptr); | ||||
| 
 | ||||
| 	impl_->loop_point_seconds(loop_point); | ||||
| } | ||||
							
								
								
									
										69
									
								
								src/audio/music_player.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								src/audio/music_player.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,69 @@ | |||
| // SONIC ROBO BLAST 2
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| // Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
 | ||||
| //
 | ||||
| // This program is free software distributed under the
 | ||||
| // terms of the GNU General Public License, version 2.
 | ||||
| // See the 'LICENSE' file for more details.
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| 
 | ||||
| #ifndef __SRB2_AUDIO_MUSIC_PLAYER_HPP__ | ||||
| #define __SRB2_AUDIO_MUSIC_PLAYER_HPP__ | ||||
| 
 | ||||
| #include <memory> | ||||
| #include <optional> | ||||
| 
 | ||||
| #include <tcb/span.hpp> | ||||
| 
 | ||||
| #include "source.hpp" | ||||
| 
 | ||||
| struct stb_vorbis; | ||||
| 
 | ||||
| namespace srb2::audio { | ||||
| 
 | ||||
| enum class MusicType { | ||||
| 	kOgg, | ||||
| 	kGme, | ||||
| 	kMod | ||||
| }; | ||||
| 
 | ||||
| class MusicPlayer : public Source<2> { | ||||
| public: | ||||
| 	MusicPlayer(); | ||||
| 	MusicPlayer(tcb::span<std::byte> data); | ||||
| 	MusicPlayer(const MusicPlayer& rhs) = delete; | ||||
| 	MusicPlayer(MusicPlayer&& rhs) noexcept; | ||||
| 
 | ||||
| 	MusicPlayer& operator=(const MusicPlayer& rhs) = delete; | ||||
| 	MusicPlayer& operator=(MusicPlayer&& rhs) noexcept; | ||||
| 
 | ||||
| 	virtual std::size_t generate(tcb::span<Sample<2>> buffer) override final; | ||||
| 
 | ||||
| 	void play(bool looping); | ||||
| 	void unpause(); | ||||
| 	void pause(); | ||||
| 	void stop(); | ||||
| 	void seek(float position_seconds); | ||||
| 	void fade_to(float gain, float seconds); | ||||
| 	void fade_from_to(float from, float to, float seconds); | ||||
| 	void internal_gain(float gain); | ||||
| 	void stop_fade(); | ||||
| 	void loop_point_seconds(float loop_point); | ||||
| 	bool playing() const; | ||||
| 	std::optional<MusicType> music_type() const; | ||||
| 	std::optional<float> duration_seconds() const; | ||||
| 	std::optional<float> loop_point_seconds() const; | ||||
| 	std::optional<float> position_seconds() const; | ||||
| 	bool fading() const; | ||||
| 
 | ||||
| 	virtual ~MusicPlayer() final; | ||||
| 
 | ||||
| private: | ||||
| 	class Impl; | ||||
| 
 | ||||
| 	std::unique_ptr<Impl> impl_; | ||||
| }; | ||||
| 
 | ||||
| } // namespace srb2::audio
 | ||||
| 
 | ||||
| #endif // __SRB2_AUDIO_MUSIC_PLAYER_HPP__
 | ||||
							
								
								
									
										194
									
								
								src/audio/ogg.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										194
									
								
								src/audio/ogg.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,194 @@ | |||
| // SONIC ROBO BLAST 2
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| // Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
 | ||||
| //
 | ||||
| // This program is free software distributed under the
 | ||||
| // terms of the GNU General Public License, version 2.
 | ||||
| // See the 'LICENSE' file for more details.
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| 
 | ||||
| #include "ogg.hpp" | ||||
| 
 | ||||
| #include <limits> | ||||
| 
 | ||||
| #include "../cxxutil.hpp" | ||||
| 
 | ||||
| using namespace srb2; | ||||
| using namespace srb2::audio; | ||||
| 
 | ||||
| StbVorbisException::StbVorbisException(int code) noexcept : code_(code) { | ||||
| } | ||||
| 
 | ||||
| const char* StbVorbisException::what() const noexcept { | ||||
| 	switch (code_) { | ||||
| 	case VORBIS__no_error: | ||||
| 		return "No error"; | ||||
| 	case VORBIS_need_more_data: | ||||
| 		return "Need more data"; | ||||
| 	case VORBIS_invalid_api_mixing: | ||||
| 		return "Invalid API mixing"; | ||||
| 	case VORBIS_outofmem: | ||||
| 		return "Out of memory"; | ||||
| 	case VORBIS_feature_not_supported: | ||||
| 		return "Feature not supported"; | ||||
| 	case VORBIS_too_many_channels: | ||||
| 		return "Too many channels"; | ||||
| 	case VORBIS_file_open_failure: | ||||
| 		return "File open failure"; | ||||
| 	case VORBIS_seek_without_length: | ||||
| 		return "Seek without length"; | ||||
| 	case VORBIS_unexpected_eof: | ||||
| 		return "Unexpected EOF"; | ||||
| 	case VORBIS_seek_invalid: | ||||
| 		return "Seek invalid"; | ||||
| 	case VORBIS_invalid_setup: | ||||
| 		return "Invalid setup"; | ||||
| 	case VORBIS_invalid_stream: | ||||
| 		return "Invalid stream"; | ||||
| 	case VORBIS_missing_capture_pattern: | ||||
| 		return "Missing capture pattern"; | ||||
| 	case VORBIS_invalid_stream_structure_version: | ||||
| 		return "Invalid stream structure version"; | ||||
| 	case VORBIS_continued_packet_flag_invalid: | ||||
| 		return "Continued packet flag invalid"; | ||||
| 	case VORBIS_incorrect_stream_serial_number: | ||||
| 		return "Incorrect stream serial number"; | ||||
| 	case VORBIS_invalid_first_page: | ||||
| 		return "Invalid first page"; | ||||
| 	case VORBIS_bad_packet_type: | ||||
| 		return "Bad packet type"; | ||||
| 	case VORBIS_cant_find_last_page: | ||||
| 		return "Can't find last page"; | ||||
| 	case VORBIS_seek_failed: | ||||
| 		return "Seek failed"; | ||||
| 	case VORBIS_ogg_skeleton_not_supported: | ||||
| 		return "OGG skeleton not supported"; | ||||
| 	default: | ||||
| 		return "Unrecognized error code"; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| Ogg::Ogg() noexcept : memory_data_(), instance_(nullptr) { | ||||
| } | ||||
| 
 | ||||
| Ogg::Ogg(std::vector<std::byte> data) : memory_data_(std::move(data)), instance_(nullptr) { | ||||
| 	_init_with_data(); | ||||
| } | ||||
| 
 | ||||
| Ogg::Ogg(tcb::span<std::byte> data) : memory_data_(data.begin(), data.end()), instance_(nullptr) { | ||||
| 	_init_with_data(); | ||||
| } | ||||
| 
 | ||||
| Ogg::Ogg(Ogg&& rhs) noexcept : memory_data_(), instance_(nullptr) { | ||||
| 	std::swap(memory_data_, rhs.memory_data_); | ||||
| 	std::swap(instance_, rhs.instance_); | ||||
| } | ||||
| 
 | ||||
| Ogg& Ogg::operator=(Ogg&& rhs) noexcept { | ||||
| 	std::swap(memory_data_, rhs.memory_data_); | ||||
| 	std::swap(instance_, rhs.instance_); | ||||
| 
 | ||||
| 	return *this; | ||||
| } | ||||
| 
 | ||||
| Ogg::~Ogg() { | ||||
| 	if (instance_) { | ||||
| 		stb_vorbis_close(instance_); | ||||
| 		instance_ = nullptr; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| std::size_t Ogg::get_samples(tcb::span<Sample<1>> buffer) { | ||||
| 	SRB2_ASSERT(instance_ != nullptr); | ||||
| 
 | ||||
| 	size_t read = stb_vorbis_get_samples_float_interleaved( | ||||
| 		instance_, 1, reinterpret_cast<float*>(buffer.data()), buffer.size() * 1); | ||||
| 
 | ||||
| 	return read; | ||||
| } | ||||
| 
 | ||||
| std::size_t Ogg::get_samples(tcb::span<Sample<2>> buffer) { | ||||
| 	SRB2_ASSERT(instance_ != nullptr); | ||||
| 
 | ||||
| 	size_t read = stb_vorbis_get_samples_float_interleaved( | ||||
| 		instance_, 2, reinterpret_cast<float*>(buffer.data()), buffer.size() * 2); | ||||
| 
 | ||||
| 	stb_vorbis_info info = stb_vorbis_get_info(instance_); | ||||
| 	if (info.channels == 1) { | ||||
| 		for (auto& sample : buffer.subspan(0, read)) { | ||||
| 			sample.amplitudes[1] = sample.amplitudes[0]; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return read; | ||||
| } | ||||
| 
 | ||||
| OggComment Ogg::comment() const { | ||||
| 	SRB2_ASSERT(instance_ != nullptr); | ||||
| 
 | ||||
| 	stb_vorbis_comment c_comment = stb_vorbis_get_comment(instance_); | ||||
| 
 | ||||
| 	return OggComment { | ||||
| 		std::string(c_comment.vendor), | ||||
| 		std::vector<std::string>(c_comment.comment_list, c_comment.comment_list + c_comment.comment_list_length)}; | ||||
| } | ||||
| 
 | ||||
| std::size_t Ogg::sample_rate() const { | ||||
| 	SRB2_ASSERT(instance_ != nullptr); | ||||
| 
 | ||||
| 	stb_vorbis_info info = stb_vorbis_get_info(instance_); | ||||
| 	return info.sample_rate; | ||||
| } | ||||
| 
 | ||||
| void Ogg::seek(std::size_t sample) { | ||||
| 	SRB2_ASSERT(instance_ != nullptr); | ||||
| 
 | ||||
| 	stb_vorbis_seek(instance_, sample); | ||||
| } | ||||
| 
 | ||||
| std::size_t Ogg::position() const { | ||||
| 	SRB2_ASSERT(instance_ != nullptr); | ||||
| 
 | ||||
| 	return stb_vorbis_get_sample_offset(instance_); | ||||
| } | ||||
| 
 | ||||
| float Ogg::position_seconds() const { | ||||
| 	return position() / static_cast<float>(sample_rate()); | ||||
| } | ||||
| 
 | ||||
| std::size_t Ogg::duration_samples() const { | ||||
| 	SRB2_ASSERT(instance_ != nullptr); | ||||
| 
 | ||||
| 	return stb_vorbis_stream_length_in_samples(instance_); | ||||
| } | ||||
| 
 | ||||
| float Ogg::duration_seconds() const { | ||||
| 	SRB2_ASSERT(instance_ != nullptr); | ||||
| 
 | ||||
| 	return stb_vorbis_stream_length_in_seconds(instance_); | ||||
| } | ||||
| 
 | ||||
| std::size_t Ogg::channels() const { | ||||
| 	SRB2_ASSERT(instance_ != nullptr); | ||||
| 
 | ||||
| 	stb_vorbis_info info = stb_vorbis_get_info(instance_); | ||||
| 	return info.channels; | ||||
| } | ||||
| 
 | ||||
| void Ogg::_init_with_data() { | ||||
| 	if (instance_) { | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	if (memory_data_.size() >= std::numeric_limits<int>::max()) | ||||
| 		throw std::logic_error("Buffer is too large for stb_vorbis"); | ||||
| 	if (memory_data_.size() == 0) | ||||
| 		throw std::logic_error("Insufficient data from stream"); | ||||
| 
 | ||||
| 	int vorbis_result; | ||||
| 	instance_ = stb_vorbis_open_memory( | ||||
| 		reinterpret_cast<const unsigned char*>(memory_data_.data()), memory_data_.size(), &vorbis_result, NULL); | ||||
| 
 | ||||
| 	if (vorbis_result != VORBIS__no_error) | ||||
| 		throw StbVorbisException(vorbis_result); | ||||
| } | ||||
							
								
								
									
										81
									
								
								src/audio/ogg.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								src/audio/ogg.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,81 @@ | |||
| // SONIC ROBO BLAST 2
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| // Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
 | ||||
| //
 | ||||
| // This program is free software distributed under the
 | ||||
| // terms of the GNU General Public License, version 2.
 | ||||
| // See the 'LICENSE' file for more details.
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| 
 | ||||
| #ifndef __SRB2_AUDIO_OGG_HPP__ | ||||
| #define __SRB2_AUDIO_OGG_HPP__ | ||||
| 
 | ||||
| #include <exception> | ||||
| #include <stdexcept> | ||||
| #include <vector> | ||||
| 
 | ||||
| #include <stb_vorbis.h> | ||||
| #include <tcb/span.hpp> | ||||
| 
 | ||||
| #include "../io/streams.hpp" | ||||
| #include "source.hpp" | ||||
| 
 | ||||
| namespace srb2::audio { | ||||
| 
 | ||||
| class StbVorbisException final : public std::exception { | ||||
| 	int code_; | ||||
| 
 | ||||
| public: | ||||
| 	explicit StbVorbisException(int code) noexcept; | ||||
| 
 | ||||
| 	virtual const char* what() const noexcept; | ||||
| }; | ||||
| 
 | ||||
| struct OggComment { | ||||
| 	std::string vendor; | ||||
| 	std::vector<std::string> comments; | ||||
| }; | ||||
| 
 | ||||
| class Ogg final { | ||||
| 	std::vector<std::byte> memory_data_; | ||||
| 	stb_vorbis* instance_; | ||||
| 
 | ||||
| public: | ||||
| 	Ogg() noexcept; | ||||
| 
 | ||||
| 	explicit Ogg(std::vector<std::byte> data); | ||||
| 	explicit Ogg(tcb::span<std::byte> data); | ||||
| 
 | ||||
| 	Ogg(const Ogg&) = delete; | ||||
| 	Ogg(Ogg&& rhs) noexcept; | ||||
| 
 | ||||
| 	Ogg& operator=(const Ogg&) = delete; | ||||
| 	Ogg& operator=(Ogg&& rhs) noexcept; | ||||
| 
 | ||||
| 	~Ogg(); | ||||
| 
 | ||||
| 	std::size_t get_samples(tcb::span<Sample<1>> buffer); | ||||
| 	std::size_t get_samples(tcb::span<Sample<2>> buffer); | ||||
| 	void seek(std::size_t sample); | ||||
| 	std::size_t position() const; | ||||
| 	float position_seconds() const; | ||||
| 
 | ||||
| 	OggComment comment() const; | ||||
| 	std::size_t sample_rate() const; | ||||
| 	std::size_t channels() const; | ||||
| 	std::size_t duration_samples() const; | ||||
| 	float duration_seconds() const; | ||||
| 
 | ||||
| private: | ||||
| 	void _init_with_data(); | ||||
| }; | ||||
| 
 | ||||
| template <typename I, typename std::enable_if_t<srb2::io::IsInputStreamV<I>, int> = 0> | ||||
| inline Ogg load_ogg(I& stream) { | ||||
| 	std::vector<std::byte> data = srb2::io::read_to_vec(stream); | ||||
| 	return Ogg {std::move(data)}; | ||||
| } | ||||
| 
 | ||||
| } // namespace srb2::audio
 | ||||
| 
 | ||||
| #endif // __SRB2_AUDIO_OGG_HPP__
 | ||||
							
								
								
									
										141
									
								
								src/audio/ogg_player.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								src/audio/ogg_player.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,141 @@ | |||
| // SONIC ROBO BLAST 2
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| // Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
 | ||||
| //
 | ||||
| // This program is free software distributed under the
 | ||||
| // terms of the GNU General Public License, version 2.
 | ||||
| // See the 'LICENSE' file for more details.
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| 
 | ||||
| #include "ogg_player.hpp" | ||||
| 
 | ||||
| #include <cmath> | ||||
| #include <limits> | ||||
| #include <optional> | ||||
| #include <stdexcept> | ||||
| #include <utility> | ||||
| 
 | ||||
| using namespace srb2; | ||||
| using namespace srb2::audio; | ||||
| 
 | ||||
| namespace { | ||||
| 
 | ||||
| std::optional<std::size_t> find_loop_point(const Ogg& ogg) { | ||||
| 	OggComment comment = ogg.comment(); | ||||
| 	std::size_t rate = ogg.sample_rate(); | ||||
| 	for (auto& comment : comment.comments) { | ||||
| 		if (comment.find("LOOPPOINT=") == 0) { | ||||
| 			std::string_view comment_view(comment); | ||||
| 			comment_view.remove_prefix(10); | ||||
| 			std::string copied {comment_view}; | ||||
| 
 | ||||
| 			try { | ||||
| 				int loop_point = std::stoi(copied); | ||||
| 				return loop_point; | ||||
| 			} catch (...) { | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		if (comment.find("LOOPMS=") == 0) { | ||||
| 			std::string_view comment_view(comment); | ||||
| 			comment_view.remove_prefix(7); | ||||
| 			std::string copied {comment_view}; | ||||
| 
 | ||||
| 			try { | ||||
| 				int loop_ms = std::stoi(copied); | ||||
| 				int loop_point = std::round(static_cast<double>(loop_ms) / (rate / 1000.)); | ||||
| 
 | ||||
| 				return loop_point; | ||||
| 			} catch (...) { | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return std::nullopt; | ||||
| } | ||||
| 
 | ||||
| } // namespace
 | ||||
| 
 | ||||
| template <size_t C> | ||||
| OggPlayer<C>::OggPlayer(Ogg&& ogg) noexcept | ||||
| 	: playing_(false), looping_(false), loop_point_(std::nullopt), ogg_(std::forward<Ogg>(ogg)) { | ||||
| 	loop_point_ = find_loop_point(ogg_); | ||||
| } | ||||
| 
 | ||||
| template <size_t C> | ||||
| OggPlayer<C>::OggPlayer(OggPlayer&& rhs) noexcept = default; | ||||
| 
 | ||||
| template <size_t C> | ||||
| OggPlayer<C>& OggPlayer<C>::operator=(OggPlayer&& rhs) noexcept = default; | ||||
| 
 | ||||
| template <size_t C> | ||||
| OggPlayer<C>::~OggPlayer() = default; | ||||
| 
 | ||||
| template <size_t C> | ||||
| std::size_t OggPlayer<C>::generate(tcb::span<Sample<C>> buffer) { | ||||
| 	if (!playing_) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	std::size_t total = 0; | ||||
| 	do { | ||||
| 		std::size_t read = ogg_.get_samples(buffer.subspan(total)); | ||||
| 		total += read; | ||||
| 
 | ||||
| 		if (read == 0 && !looping_) { | ||||
| 			playing_ = false; | ||||
| 			break; | ||||
| 		} | ||||
| 
 | ||||
| 		if (read == 0 && loop_point_) { | ||||
| 			ogg_.seek(*loop_point_); | ||||
| 		} | ||||
| 
 | ||||
| 		if (read == 0 && !loop_point_) { | ||||
| 			ogg_.seek(0); | ||||
| 		} | ||||
| 	} while (total < buffer.size()); | ||||
| 
 | ||||
| 	return total; | ||||
| } | ||||
| 
 | ||||
| template <size_t C> | ||||
| void OggPlayer<C>::seek(float position_seconds) { | ||||
| 	ogg_.seek(static_cast<std::size_t>(position_seconds * sample_rate())); | ||||
| } | ||||
| 
 | ||||
| template <size_t C> | ||||
| void OggPlayer<C>::loop_point_seconds(float loop_point) { | ||||
| 	std::size_t rate = sample_rate(); | ||||
| 	loop_point = static_cast<std::size_t>(std::round(loop_point * rate)); | ||||
| } | ||||
| 
 | ||||
| template <size_t C> | ||||
| void OggPlayer<C>::reset() { | ||||
| 	ogg_.seek(0); | ||||
| } | ||||
| 
 | ||||
| template <size_t C> | ||||
| std::size_t OggPlayer<C>::sample_rate() const { | ||||
| 	return ogg_.sample_rate(); | ||||
| } | ||||
| 
 | ||||
| template <size_t C> | ||||
| float OggPlayer<C>::duration_seconds() const { | ||||
| 	return ogg_.duration_seconds(); | ||||
| } | ||||
| 
 | ||||
| template <size_t C> | ||||
| std::optional<float> OggPlayer<C>::loop_point_seconds() const { | ||||
| 	if (!loop_point_) | ||||
| 		return std::nullopt; | ||||
| 
 | ||||
| 	return *loop_point_ / static_cast<float>(sample_rate()); | ||||
| } | ||||
| 
 | ||||
| template <size_t C> | ||||
| float OggPlayer<C>::position_seconds() const { | ||||
| 	return ogg_.position_seconds(); | ||||
| } | ||||
| 
 | ||||
| template class srb2::audio::OggPlayer<1>; | ||||
| template class srb2::audio::OggPlayer<2>; | ||||
							
								
								
									
										72
									
								
								src/audio/ogg_player.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								src/audio/ogg_player.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,72 @@ | |||
| // SONIC ROBO BLAST 2
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| // Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
 | ||||
| //
 | ||||
| // This program is free software distributed under the
 | ||||
| // terms of the GNU General Public License, version 2.
 | ||||
| // See the 'LICENSE' file for more details.
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| 
 | ||||
| #ifndef __SRB2_AUDIO_OGG_SOURCE_HPP__ | ||||
| #define __SRB2_AUDIO_OGG_SOURCE_HPP__ | ||||
| 
 | ||||
| #include <cstddef> | ||||
| #include <optional> | ||||
| #include <stdexcept> | ||||
| #include <string> | ||||
| #include <type_traits> | ||||
| #include <variant> | ||||
| #include <vector> | ||||
| 
 | ||||
| #include <stb_vorbis.h> | ||||
| #include <tcb/span.hpp> | ||||
| 
 | ||||
| #include "../io/streams.hpp" | ||||
| #include "ogg.hpp" | ||||
| #include "source.hpp" | ||||
| 
 | ||||
| namespace srb2::audio { | ||||
| 
 | ||||
| template <size_t C> | ||||
| class OggPlayer final : public Source<C> { | ||||
| 	bool playing_; | ||||
| 	bool looping_; | ||||
| 	std::optional<std::size_t> loop_point_; | ||||
| 	Ogg ogg_; | ||||
| 
 | ||||
| public: | ||||
| 	OggPlayer(Ogg&& ogg) noexcept; | ||||
| 
 | ||||
| 	OggPlayer(const OggPlayer&) = delete; | ||||
| 	OggPlayer(OggPlayer&& rhs) noexcept; | ||||
| 
 | ||||
| 	OggPlayer& operator=(const OggPlayer&) = delete; | ||||
| 	OggPlayer& operator=(OggPlayer&& rhs) noexcept; | ||||
| 
 | ||||
| 	virtual std::size_t generate(tcb::span<Sample<C>> buffer) override final; | ||||
| 
 | ||||
| 	bool looping() const { return looping_; } | ||||
| 
 | ||||
| 	void looping(bool looping) { looping_ = looping; } | ||||
| 
 | ||||
| 	bool playing() const { return playing_; } | ||||
| 	void playing(bool playing) { playing_ = playing; } | ||||
| 	void seek(float position_seconds); | ||||
| 	void loop_point_seconds(float loop_point); | ||||
| 
 | ||||
| 	void reset(); | ||||
| 	std::size_t sample_rate() const; | ||||
| 
 | ||||
| 	float duration_seconds() const; | ||||
| 	std::optional<float> loop_point_seconds() const; | ||||
| 	float position_seconds() const; | ||||
| 
 | ||||
| 	~OggPlayer(); | ||||
| }; | ||||
| 
 | ||||
| extern template class OggPlayer<1>; | ||||
| extern template class OggPlayer<2>; | ||||
| 
 | ||||
| } // namespace srb2::audio
 | ||||
| 
 | ||||
| #endif // __SRB2_AUDIO_OGG_SOURCE_HPP__
 | ||||
							
								
								
									
										81
									
								
								src/audio/resample.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								src/audio/resample.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,81 @@ | |||
| // SONIC ROBO BLAST 2
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| // Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
 | ||||
| //
 | ||||
| // This program is free software distributed under the
 | ||||
| // terms of the GNU General Public License, version 2.
 | ||||
| // See the 'LICENSE' file for more details.
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| 
 | ||||
| #include "resample.hpp" | ||||
| 
 | ||||
| #include <algorithm> | ||||
| #include <cmath> | ||||
| #include <memory> | ||||
| #include <utility> | ||||
| #include <vector> | ||||
| 
 | ||||
| using std::shared_ptr; | ||||
| using std::size_t; | ||||
| using std::vector; | ||||
| 
 | ||||
| using namespace srb2::audio; | ||||
| 
 | ||||
| template <size_t C> | ||||
| Resampler<C>::Resampler(std::shared_ptr<Source<C>>&& source, float ratio) | ||||
| 	: source_(std::forward<std::shared_ptr<Source<C>>>(source)), ratio_(ratio) { | ||||
| } | ||||
| 
 | ||||
| template <size_t C> | ||||
| Resampler<C>::Resampler(Resampler<C>&& r) = default; | ||||
| 
 | ||||
| template <size_t C> | ||||
| Resampler<C>::~Resampler() = default; | ||||
| 
 | ||||
| template <size_t C> | ||||
| Resampler<C>& Resampler<C>::operator=(Resampler<C>&& r) = default; | ||||
| 
 | ||||
| template <size_t C> | ||||
| size_t Resampler<C>::generate(tcb::span<Sample<C>> buffer) { | ||||
| 	if (!source_) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	if (ratio_ == 1.f) { | ||||
| 		// fast path - generate directly from source
 | ||||
| 		size_t source_read = source_->generate(buffer); | ||||
| 		return source_read; | ||||
| 	} | ||||
| 
 | ||||
| 	size_t written = 0; | ||||
| 
 | ||||
| 	while (written < buffer.size()) { | ||||
| 		// do we need a refill?
 | ||||
| 		if (buf_.size() == 0 || pos_ >= static_cast<int>(buf_.size() - 1)) { | ||||
| 			pos_ -= buf_.size(); | ||||
| 			last_ = buf_.size() == 0 ? Sample<C> {} : buf_.back(); | ||||
| 			buf_.clear(); | ||||
| 			buf_.resize(512); | ||||
| 			size_t source_read = source_->generate(buf_); | ||||
| 			buf_.resize(source_read); | ||||
| 			if (source_read == 0) { | ||||
| 				break; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		if (pos_ < 0) { | ||||
| 			buffer[written] = (buf_[0] - last_) * pos_frac_ + last_; | ||||
| 			advance(ratio_); | ||||
| 			written++; | ||||
| 			continue; | ||||
| 		} | ||||
| 
 | ||||
| 		buffer[written] = (buf_[pos_ + 1] - buf_[pos_]) * pos_frac_ + buf_[pos_]; | ||||
| 		advance(ratio_); | ||||
| 		written++; | ||||
| 	} | ||||
| 
 | ||||
| 	return written; | ||||
| } | ||||
| 
 | ||||
| template class srb2::audio::Resampler<1>; | ||||
| template class srb2::audio::Resampler<2>; | ||||
							
								
								
									
										63
									
								
								src/audio/resample.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								src/audio/resample.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,63 @@ | |||
| // SONIC ROBO BLAST 2
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| // Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
 | ||||
| //
 | ||||
| // This program is free software distributed under the
 | ||||
| // terms of the GNU General Public License, version 2.
 | ||||
| // See the 'LICENSE' file for more details.
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| 
 | ||||
| #ifndef __SRB2_AUDIO_RESAMPLE_HPP__ | ||||
| #define __SRB2_AUDIO_RESAMPLE_HPP__ | ||||
| 
 | ||||
| #include <cmath> | ||||
| #include <memory> | ||||
| #include <optional> | ||||
| #include <variant> | ||||
| #include <vector> | ||||
| 
 | ||||
| #include <tcb/span.hpp> | ||||
| 
 | ||||
| #include "sound_chunk.hpp" | ||||
| #include "source.hpp" | ||||
| 
 | ||||
| namespace srb2::audio { | ||||
| 
 | ||||
| template <size_t C> | ||||
| class Resampler : public Source<C> { | ||||
| public: | ||||
| 	Resampler(std::shared_ptr<Source<C>>&& source_, float ratio); | ||||
| 	Resampler(const Resampler<C>& r) = delete; | ||||
| 	Resampler(Resampler<C>&& r); | ||||
| 	virtual ~Resampler(); | ||||
| 
 | ||||
| 	virtual std::size_t generate(tcb::span<Sample<C>> buffer); | ||||
| 
 | ||||
| 	Resampler& operator=(const Resampler<C>& r) = delete; | ||||
| 	Resampler& operator=(Resampler<C>&& r); | ||||
| 
 | ||||
| private: | ||||
| 	std::shared_ptr<Source<C>> source_; | ||||
| 	float ratio_ {1.f}; | ||||
| 	std::vector<Sample<C>> buf_; | ||||
| 	Sample<C> last_; | ||||
| 	int pos_ {0}; | ||||
| 	float pos_frac_ {0.f}; | ||||
| 
 | ||||
| 	void advance(float samples) { | ||||
| 		pos_frac_ += samples; | ||||
| 		float integer; | ||||
| 		std::modf(pos_frac_, &integer); | ||||
| 		pos_ += integer; | ||||
| 		pos_frac_ -= integer; | ||||
| 	} | ||||
| 
 | ||||
| 	void refill(); | ||||
| }; | ||||
| 
 | ||||
| extern template class Resampler<1>; | ||||
| extern template class Resampler<2>; | ||||
| 
 | ||||
| } // namespace srb2::audio
 | ||||
| 
 | ||||
| #endif // __SRB2_AUDIO_RESAMPLE_HPP__
 | ||||
							
								
								
									
										78
									
								
								src/audio/sample.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								src/audio/sample.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,78 @@ | |||
| // SONIC ROBO BLAST 2
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| // Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
 | ||||
| //
 | ||||
| // This program is free software distributed under the
 | ||||
| // terms of the GNU General Public License, version 2.
 | ||||
| // See the 'LICENSE' file for more details.
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| 
 | ||||
| #ifndef __SRB2_AUDIO_SAMPLE_HPP__ | ||||
| #define __SRB2_AUDIO_SAMPLE_HPP__ | ||||
| 
 | ||||
| #include <cstddef> | ||||
| 
 | ||||
| namespace srb2::audio { | ||||
| 
 | ||||
| template <size_t C> | ||||
| struct Sample { | ||||
| 	std::array<float, C> amplitudes; | ||||
| 
 | ||||
| 	constexpr Sample& operator+=(const Sample& rhs) noexcept { | ||||
| 		for (std::size_t i = 0; i < C; i++) { | ||||
| 			amplitudes[i] += rhs.amplitudes[i]; | ||||
| 		} | ||||
| 		return *this; | ||||
| 	} | ||||
| 
 | ||||
| 	constexpr Sample& operator*=(float rhs) noexcept { | ||||
| 		for (std::size_t i = 0; i < C; i++) { | ||||
| 			amplitudes[i] *= rhs; | ||||
| 		} | ||||
| 		return *this; | ||||
| 	} | ||||
| }; | ||||
| 
 | ||||
| template <size_t C> | ||||
| constexpr Sample<C> operator+(const Sample<C>& lhs, const Sample<C>& rhs) noexcept { | ||||
| 	Sample<C> out; | ||||
| 	for (std::size_t i = 0; i < C; i++) { | ||||
| 		out.amplitudes[i] = lhs.amplitudes[i] + rhs.amplitudes[i]; | ||||
| 	} | ||||
| 	return out; | ||||
| } | ||||
| 
 | ||||
| template <size_t C> | ||||
| constexpr Sample<C> operator-(const Sample<C>& lhs, const Sample<C>& rhs) noexcept { | ||||
| 	Sample<C> out; | ||||
| 	for (std::size_t i = 0; i < C; i++) { | ||||
| 		out.amplitudes[i] = lhs.amplitudes[i] - rhs.amplitudes[i]; | ||||
| 	} | ||||
| 	return out; | ||||
| } | ||||
| 
 | ||||
| template <size_t C> | ||||
| constexpr Sample<C> operator*(const Sample<C>& lhs, float rhs) noexcept { | ||||
| 	Sample<C> out; | ||||
| 	for (std::size_t i = 0; i < C; i++) { | ||||
| 		out.amplitudes[i] = lhs.amplitudes[i] * rhs; | ||||
| 	} | ||||
| 	return out; | ||||
| } | ||||
| 
 | ||||
| template <class T> | ||||
| static constexpr float sample_to_float(T sample) noexcept; | ||||
| 
 | ||||
| template <> | ||||
| constexpr float sample_to_float<uint8_t>(uint8_t sample) noexcept { | ||||
| 	return (sample / 128.f) - 1.f; | ||||
| } | ||||
| 
 | ||||
| template <> | ||||
| constexpr float sample_to_float<int16_t>(int16_t sample) noexcept { | ||||
| 	return sample / 32768.f; | ||||
| } | ||||
| 
 | ||||
| } // namespace srb2::audio
 | ||||
| 
 | ||||
| #endif // __SRB2_AUDIO_SAMPLE_HPP__
 | ||||
							
								
								
									
										25
									
								
								src/audio/sound_chunk.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/audio/sound_chunk.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,25 @@ | |||
| // SONIC ROBO BLAST 2
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| // Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
 | ||||
| //
 | ||||
| // This program is free software distributed under the
 | ||||
| // terms of the GNU General Public License, version 2.
 | ||||
| // See the 'LICENSE' file for more details.
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| 
 | ||||
| #ifndef __SRB2_AUDIO_SOUND_CHUNK_HPP__ | ||||
| #define __SRB2_AUDIO_SOUND_CHUNK_HPP__ | ||||
| 
 | ||||
| #include <vector> | ||||
| 
 | ||||
| #include "source.hpp" | ||||
| 
 | ||||
| namespace srb2::audio { | ||||
| 
 | ||||
| struct SoundChunk { | ||||
| 	std::vector<Sample<1>> samples; | ||||
| }; | ||||
| 
 | ||||
| } // namespace srb2::audio
 | ||||
| 
 | ||||
| #endif // __SRB2_AUDIO_SOUND_CHUNK_HPP__
 | ||||
							
								
								
									
										72
									
								
								src/audio/sound_effect_player.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								src/audio/sound_effect_player.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,72 @@ | |||
| // SONIC ROBO BLAST 2
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| // Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
 | ||||
| //
 | ||||
| // This program is free software distributed under the
 | ||||
| // terms of the GNU General Public License, version 2.
 | ||||
| // See the 'LICENSE' file for more details.
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| 
 | ||||
| #include "sound_effect_player.hpp" | ||||
| 
 | ||||
| #include <algorithm> | ||||
| #include <cmath> | ||||
| #include <memory> | ||||
| 
 | ||||
| using std::shared_ptr; | ||||
| using std::size_t; | ||||
| 
 | ||||
| using srb2::audio::Sample; | ||||
| using srb2::audio::SoundEffectPlayer; | ||||
| using srb2::audio::Source; | ||||
| 
 | ||||
| size_t SoundEffectPlayer::generate(tcb::span<Sample<2>> buffer) { | ||||
| 	if (!chunk_) | ||||
| 		return 0; | ||||
| 	if (position_ >= chunk_->samples.size()) { | ||||
| 		return 0; | ||||
| 	} | ||||
| 
 | ||||
| 	size_t written = 0; | ||||
| 	for (; position_ < chunk_->samples.size() && written < buffer.size(); position_++) { | ||||
| 		float mono_sample = chunk_->samples[position_].amplitudes[0]; | ||||
| 
 | ||||
| 		float sep_pan = ((sep_ + 1.f) / 2.f) * (3.14159 / 2.f); | ||||
| 
 | ||||
| 		float left_scale = std::cos(sep_pan); | ||||
| 		float right_scale = std::sin(sep_pan); | ||||
| 		buffer[written] = {mono_sample * volume_ * left_scale, mono_sample * volume_ * right_scale}; | ||||
| 		written += 1; | ||||
| 	} | ||||
| 	return written; | ||||
| } | ||||
| 
 | ||||
| void SoundEffectPlayer::start(const SoundChunk* chunk, float volume, float sep) { | ||||
| 	this->update(volume, sep); | ||||
| 	position_ = 0; | ||||
| 	chunk_ = chunk; | ||||
| } | ||||
| 
 | ||||
| void SoundEffectPlayer::update(float volume, float sep) { | ||||
| 	volume_ = volume; | ||||
| 	sep_ = sep; | ||||
| } | ||||
| 
 | ||||
| void SoundEffectPlayer::reset() { | ||||
| 	position_ = 0; | ||||
| 	chunk_ = nullptr; | ||||
| } | ||||
| 
 | ||||
| bool SoundEffectPlayer::finished() const { | ||||
| 	if (!chunk_) | ||||
| 		return true; | ||||
| 	if (position_ >= chunk_->samples.size()) | ||||
| 		return true; | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| bool SoundEffectPlayer::is_playing_chunk(const SoundChunk* chunk) const { | ||||
| 	return chunk_ == chunk; | ||||
| } | ||||
| 
 | ||||
| SoundEffectPlayer::~SoundEffectPlayer() = default; | ||||
							
								
								
									
										46
									
								
								src/audio/sound_effect_player.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/audio/sound_effect_player.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,46 @@ | |||
| // SONIC ROBO BLAST 2
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| // Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
 | ||||
| //
 | ||||
| // This program is free software distributed under the
 | ||||
| // terms of the GNU General Public License, version 2.
 | ||||
| // See the 'LICENSE' file for more details.
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| 
 | ||||
| #ifndef __SRB2_AUDIO_SOUND_EFFECT_PLAYER_HPP__ | ||||
| #define __SRB2_AUDIO_SOUND_EFFECT_PLAYER_HPP__ | ||||
| 
 | ||||
| #include <cstddef> | ||||
| 
 | ||||
| #include <tcb/span.hpp> | ||||
| 
 | ||||
| #include "sound_chunk.hpp" | ||||
| #include "source.hpp" | ||||
| 
 | ||||
| namespace srb2::audio { | ||||
| 
 | ||||
| class SoundEffectPlayer : public Source<2> { | ||||
| public: | ||||
| 	virtual std::size_t generate(tcb::span<Sample<2>> buffer) override final; | ||||
| 
 | ||||
| 	virtual ~SoundEffectPlayer() final; | ||||
| 
 | ||||
| 	void start(const SoundChunk* chunk, float volume, float sep); | ||||
| 	void update(float volume, float sep); | ||||
| 	void reset(); | ||||
| 	bool finished() const; | ||||
| 
 | ||||
| 	bool is_playing_chunk(const SoundChunk* chunk) const; | ||||
| 
 | ||||
| private: | ||||
| 	float volume_; | ||||
| 	float sep_; | ||||
| 
 | ||||
| 	std::size_t position_; | ||||
| 
 | ||||
| 	const SoundChunk* chunk_; | ||||
| }; | ||||
| 
 | ||||
| } // namespace srb2::audio
 | ||||
| 
 | ||||
| #endif // __SRB2_AUDIO_SOUND_EFFECT_PLAYER_HPP__
 | ||||
							
								
								
									
										36
									
								
								src/audio/source.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/audio/source.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,36 @@ | |||
| // SONIC ROBO BLAST 2
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| // Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
 | ||||
| //
 | ||||
| // This program is free software distributed under the
 | ||||
| // terms of the GNU General Public License, version 2.
 | ||||
| // See the 'LICENSE' file for more details.
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| 
 | ||||
| #ifndef __SRB2_AUDIO_SOURCE_HPP__ | ||||
| #define __SRB2_AUDIO_SOURCE_HPP__ | ||||
| 
 | ||||
| #include <array> | ||||
| 
 | ||||
| #include <tcb/span.hpp> | ||||
| 
 | ||||
| #include "sample.hpp" | ||||
| 
 | ||||
| namespace srb2::audio { | ||||
| 
 | ||||
| template <size_t C> | ||||
| class Source { | ||||
| public: | ||||
| 	virtual std::size_t generate(tcb::span<Sample<C>> buffer) = 0; | ||||
| 
 | ||||
| 	virtual ~Source() = default; | ||||
| }; | ||||
| 
 | ||||
| // This audio DSP is Stereo, FP32 system-endian, 44100 Hz internally.
 | ||||
| // Conversions to other formats should be handled elsewhere.
 | ||||
| 
 | ||||
| constexpr const std::size_t kSampleRate = 44100; | ||||
| 
 | ||||
| } // namespace srb2::audio
 | ||||
| 
 | ||||
| #endif // __SRB2_AUDIO_SOURCE_HPP__
 | ||||
							
								
								
									
										264
									
								
								src/audio/wav.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										264
									
								
								src/audio/wav.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,264 @@ | |||
| // SONIC ROBO BLAST 2
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| // Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
 | ||||
| //
 | ||||
| // This program is free software distributed under the
 | ||||
| // terms of the GNU General Public License, version 2.
 | ||||
| // See the 'LICENSE' file for more details.
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| 
 | ||||
| #include "wav.hpp" | ||||
| 
 | ||||
| #include <algorithm> | ||||
| #include <optional> | ||||
| #include <stdexcept> | ||||
| 
 | ||||
| using namespace srb2; | ||||
| using srb2::audio::Wav; | ||||
| 
 | ||||
| namespace { | ||||
| 
 | ||||
| constexpr const uint32_t kMagicRIFF = 0x46464952; | ||||
| constexpr const uint32_t kMagicWAVE = 0x45564157; | ||||
| constexpr const uint32_t kMagicFmt = 0x20746d66; | ||||
| constexpr const uint32_t kMagicData = 0x61746164; | ||||
| 
 | ||||
| constexpr const uint16_t kFormatPcm = 1; | ||||
| 
 | ||||
| constexpr const std::size_t kRiffHeaderLength = 8; | ||||
| 
 | ||||
| struct RiffHeader { | ||||
| 	uint32_t magic; | ||||
| 	std::size_t filesize; | ||||
| }; | ||||
| 
 | ||||
| struct TagHeader { | ||||
| 	uint32_t type; | ||||
| 	std::size_t length; | ||||
| }; | ||||
| 
 | ||||
| struct FmtTag { | ||||
| 	uint16_t format; | ||||
| 	uint16_t channels; | ||||
| 	uint32_t rate; | ||||
| 	uint32_t bytes_per_second; | ||||
| 	uint32_t bytes_per_sample; | ||||
| 	uint16_t bit_width; | ||||
| }; | ||||
| 
 | ||||
| struct DataTag {}; | ||||
| 
 | ||||
| RiffHeader parse_riff_header(io::SpanStream& stream) { | ||||
| 	if (io::remaining(stream) < kRiffHeaderLength) | ||||
| 		throw std::runtime_error("insufficient bytes remaining in stream"); | ||||
| 
 | ||||
| 	RiffHeader ret; | ||||
| 	ret.magic = io::read_uint32(stream); | ||||
| 	ret.filesize = io::read_uint32(stream); | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| TagHeader parse_tag_header(io::SpanStream& stream) { | ||||
| 	if (io::remaining(stream) < 8) | ||||
| 		throw std::runtime_error("insufficient bytes remaining in stream"); | ||||
| 
 | ||||
| 	TagHeader header; | ||||
| 	header.type = io::read_uint32(stream); | ||||
| 	header.length = io::read_uint32(stream); | ||||
| 	return header; | ||||
| } | ||||
| 
 | ||||
| FmtTag parse_fmt_tag(io::SpanStream& stream) { | ||||
| 	if (io::remaining(stream) < 16) | ||||
| 		throw std::runtime_error("insufficient bytes in stream"); | ||||
| 
 | ||||
| 	FmtTag tag; | ||||
| 	tag.format = io::read_uint16(stream); | ||||
| 	tag.channels = io::read_uint16(stream); | ||||
| 	tag.rate = io::read_uint32(stream); | ||||
| 	tag.bytes_per_second = io::read_uint32(stream); | ||||
| 	tag.bytes_per_sample = io::read_uint16(stream); | ||||
| 	tag.bit_width = io::read_uint16(stream); | ||||
| 
 | ||||
| 	return tag; | ||||
| } | ||||
| 
 | ||||
| template <typename Visitor> | ||||
| void visit_tag(Visitor& visitor, io::SpanStream& stream, const TagHeader& header) { | ||||
| 	if (io::remaining(stream) < header.length) | ||||
| 		throw std::runtime_error("insufficient bytes in stream"); | ||||
| 
 | ||||
| 	const io::StreamSize start = stream.seek(io::SeekFrom::kCurrent, 0); | ||||
| 	const io::StreamSize dest = start + header.length; | ||||
| 
 | ||||
| 	switch (header.type) { | ||||
| 	case kMagicFmt: | ||||
| 		{ | ||||
| 			FmtTag fmt_tag {parse_fmt_tag(stream)}; | ||||
| 			visitor(fmt_tag); | ||||
| 			break; | ||||
| 		} | ||||
| 	case kMagicData: | ||||
| 		{ | ||||
| 			DataTag data_tag; | ||||
| 			visitor(data_tag); | ||||
| 			break; | ||||
| 		} | ||||
| 	default: | ||||
| 		// Unrecognized tags are ignored.
 | ||||
| 		break; | ||||
| 	} | ||||
| 
 | ||||
| 	stream.seek(io::SeekFrom::kStart, dest); | ||||
| } | ||||
| 
 | ||||
| std::vector<uint8_t> read_uint8_samples_from_stream(io::SpanStream& stream, std::size_t count) { | ||||
| 	std::vector<uint8_t> samples; | ||||
| 	samples.reserve(count); | ||||
| 	for (std::size_t i = 0; i < count; i++) { | ||||
| 		samples.push_back(io::read_uint8(stream)); | ||||
| 	} | ||||
| 	return samples; | ||||
| } | ||||
| 
 | ||||
| std::vector<int16_t> read_int16_samples_from_stream(io::SpanStream& stream, std::size_t count) { | ||||
| 	std::vector<int16_t> samples; | ||||
| 	samples.reserve(count); | ||||
| 	for (std::size_t i = 0; i < count; i++) { | ||||
| 		samples.push_back(io::read_int16(stream)); | ||||
| 	} | ||||
| 	return samples; | ||||
| } | ||||
| 
 | ||||
| template <typename... Ts> | ||||
| struct OverloadVisitor : Ts... { | ||||
| 	using Ts::operator()...; | ||||
| }; | ||||
| 
 | ||||
| template <typename... Ts> | ||||
| OverloadVisitor(Ts...) -> OverloadVisitor<Ts...>; | ||||
| 
 | ||||
| } // namespace
 | ||||
| 
 | ||||
| Wav::Wav() = default; | ||||
| 
 | ||||
| Wav::Wav(tcb::span<std::byte> data) { | ||||
| 	io::SpanStream stream {data}; | ||||
| 
 | ||||
| 	auto [magic, filesize] = parse_riff_header(stream); | ||||
| 
 | ||||
| 	if (magic != kMagicRIFF) { | ||||
| 		throw std::runtime_error("invalid RIFF magic"); | ||||
| 	} | ||||
| 
 | ||||
| 	if (io::remaining(stream) < filesize) { | ||||
| 		throw std::runtime_error("insufficient data in stream for RIFF's reported filesize"); | ||||
| 	} | ||||
| 
 | ||||
| 	const io::StreamSize riff_end = stream.seek(io::SeekFrom::kCurrent, 0) + filesize; | ||||
| 
 | ||||
| 	uint32_t type = io::read_uint32(stream); | ||||
| 	if (type != kMagicWAVE) { | ||||
| 		throw std::runtime_error("RIFF in stream is not a WAVE"); | ||||
| 	} | ||||
| 
 | ||||
| 	std::optional<FmtTag> read_fmt; | ||||
| 	std::variant<std::vector<uint8_t>, std::vector<int16_t>> interleaved_samples; | ||||
| 
 | ||||
| 	while (stream.seek(io::SeekFrom::kCurrent, 0) < riff_end) { | ||||
| 		TagHeader tag_header {parse_tag_header(stream)}; | ||||
| 		if (io::remaining(stream) < tag_header.length) { | ||||
| 			throw std::runtime_error("WAVE tag length exceeds stream length"); | ||||
| 		} | ||||
| 
 | ||||
| 		auto tag_visitor = OverloadVisitor { | ||||
| 			[&](const FmtTag& fmt) { | ||||
| 				if (read_fmt) { | ||||
| 					throw std::runtime_error("WAVE has multiple 'fmt' tags"); | ||||
| 				} | ||||
| 				if (fmt.format != kFormatPcm) { | ||||
| 					throw std::runtime_error("Unsupported WAVE format (only PCM is supported)"); | ||||
| 				} | ||||
| 				read_fmt = fmt; | ||||
| 			}, | ||||
| 			[&](const DataTag& data) { | ||||
| 				if (!read_fmt) { | ||||
| 					throw std::runtime_error("unable to read data tag because no fmt tag was read"); | ||||
| 				} | ||||
| 
 | ||||
| 				if (tag_header.length % read_fmt->bytes_per_sample != 0) { | ||||
| 					throw std::runtime_error("data tag length not divisible by bytes_per_sample"); | ||||
| 				} | ||||
| 
 | ||||
| 				const std::size_t sample_count = tag_header.length / read_fmt->bytes_per_sample; | ||||
| 
 | ||||
| 				switch (read_fmt->bit_width) { | ||||
| 				case 8: | ||||
| 					interleaved_samples = std::move(read_uint8_samples_from_stream(stream, sample_count)); | ||||
| 					break; | ||||
| 				case 16: | ||||
| 					interleaved_samples = std::move(read_int16_samples_from_stream(stream, sample_count)); | ||||
| 					break; | ||||
| 				default: | ||||
| 					throw std::runtime_error("unsupported sample amplitude bit width"); | ||||
| 				} | ||||
| 			}}; | ||||
| 
 | ||||
| 		visit_tag(tag_visitor, stream, tag_header); | ||||
| 	} | ||||
| 
 | ||||
| 	if (!read_fmt) { | ||||
| 		throw std::runtime_error("WAVE did not have a fmt tag"); | ||||
| 	} | ||||
| 
 | ||||
| 	interleaved_samples_ = std::move(interleaved_samples); | ||||
| 	channels_ = read_fmt->channels; | ||||
| 	sample_rate_ = read_fmt->rate; | ||||
| } | ||||
| 
 | ||||
| namespace { | ||||
| 
 | ||||
| template <typename T> | ||||
| std::size_t read_samples(std::size_t channels, | ||||
| 						 std::size_t offset, | ||||
| 						 const std::vector<T>& samples, | ||||
| 						 tcb::span<audio::Sample<1>> buffer) noexcept { | ||||
| 	const std::size_t offset_interleaved = offset * channels; | ||||
| 	const std::size_t samples_size = samples.size(); | ||||
| 	const std::size_t buffer_size = buffer.size(); | ||||
| 
 | ||||
| 	if (offset_interleaved >= samples_size) { | ||||
| 		return 0; | ||||
| 	} | ||||
| 
 | ||||
| 	const std::size_t remainder = (samples_size - offset_interleaved) / channels; | ||||
| 	const std::size_t samples_to_read = std::min(buffer_size, remainder); | ||||
| 
 | ||||
| 	for (std::size_t i = 0; i < samples_to_read; i++) { | ||||
| 		buffer[i].amplitudes[0] = 0.f; | ||||
| 		for (std::size_t j = 0; j < channels; j++) { | ||||
| 			buffer[i].amplitudes[0] += audio::sample_to_float(samples[i * channels + j + offset_interleaved]); | ||||
| 		} | ||||
| 		buffer[i].amplitudes[0] /= static_cast<float>(channels); | ||||
| 	} | ||||
| 
 | ||||
| 	return samples_to_read; | ||||
| } | ||||
| 
 | ||||
| } // namespace
 | ||||
| 
 | ||||
| std::size_t Wav::get_samples(std::size_t offset, tcb::span<audio::Sample<1>> buffer) const noexcept { | ||||
| 	auto samples_visitor = OverloadVisitor { | ||||
| 		[&](const std::vector<uint8_t>& samples) { return read_samples<uint8_t>(channels(), offset, samples, buffer); }, | ||||
| 		[&](const std::vector<int16_t>& samples) { | ||||
| 			return read_samples<int16_t>(channels(), offset, samples, buffer); | ||||
| 		}}; | ||||
| 
 | ||||
| 	return std::visit(samples_visitor, interleaved_samples_); | ||||
| } | ||||
| 
 | ||||
| std::size_t Wav::interleaved_length() const noexcept { | ||||
| 	auto samples_visitor = OverloadVisitor {[](const std::vector<uint8_t>& samples) { return samples.size(); }, | ||||
| 											[](const std::vector<int16_t>& samples) { return samples.size(); }}; | ||||
| 	return std::visit(samples_visitor, interleaved_samples_); | ||||
| } | ||||
							
								
								
									
										51
									
								
								src/audio/wav.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/audio/wav.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,51 @@ | |||
| // SONIC ROBO BLAST 2
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| // Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
 | ||||
| //
 | ||||
| // This program is free software distributed under the
 | ||||
| // terms of the GNU General Public License, version 2.
 | ||||
| // See the 'LICENSE' file for more details.
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| 
 | ||||
| #ifndef __SRB2_AUDIO_WAV_HPP__ | ||||
| #define __SRB2_AUDIO_WAV_HPP__ | ||||
| 
 | ||||
| #include <cstddef> | ||||
| #include <cstdint> | ||||
| #include <type_traits> | ||||
| #include <variant> | ||||
| #include <vector> | ||||
| 
 | ||||
| #include <tcb/span.hpp> | ||||
| 
 | ||||
| #include "../io/streams.hpp" | ||||
| #include "sample.hpp" | ||||
| 
 | ||||
| namespace srb2::audio { | ||||
| 
 | ||||
| class Wav final { | ||||
| 	std::variant<std::vector<uint8_t>, std::vector<int16_t>> interleaved_samples_; | ||||
| 	std::size_t channels_ = 1; | ||||
| 	std::size_t sample_rate_ = 44100; | ||||
| 
 | ||||
| public: | ||||
| 	Wav(); | ||||
| 
 | ||||
| 	explicit Wav(tcb::span<std::byte> data); | ||||
| 
 | ||||
| 	std::size_t get_samples(std::size_t offset, tcb::span<Sample<1>> buffer) const noexcept; | ||||
| 	std::size_t interleaved_length() const noexcept; | ||||
| 	std::size_t length() const noexcept { return interleaved_length() / channels(); }; | ||||
| 	std::size_t channels() const noexcept { return channels_; }; | ||||
| 	std::size_t sample_rate() const noexcept { return sample_rate_; }; | ||||
| }; | ||||
| 
 | ||||
| template <typename I, typename std::enable_if_t<srb2::io::IsInputStreamV<I>, int> = 0> | ||||
| inline Wav load_wav(I& stream) { | ||||
| 	std::vector<std::byte> data = srb2::io::read_to_vec(stream); | ||||
| 	return Wav {data}; | ||||
| } | ||||
| 
 | ||||
| } // namespace srb2::audio
 | ||||
| 
 | ||||
| #endif // __SRB2_AUDIO_WAV_HPP__
 | ||||
							
								
								
									
										45
									
								
								src/audio/wav_player.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								src/audio/wav_player.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,45 @@ | |||
| // SONIC ROBO BLAST 2
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| // Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
 | ||||
| //
 | ||||
| // This program is free software distributed under the
 | ||||
| // terms of the GNU General Public License, version 2.
 | ||||
| // See the 'LICENSE' file for more details.
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| 
 | ||||
| #include "wav_player.hpp" | ||||
| 
 | ||||
| using namespace srb2; | ||||
| 
 | ||||
| using srb2::audio::WavPlayer; | ||||
| 
 | ||||
| WavPlayer::WavPlayer() : WavPlayer(audio::Wav {}) { | ||||
| } | ||||
| 
 | ||||
| WavPlayer::WavPlayer(const WavPlayer& rhs) = default; | ||||
| 
 | ||||
| WavPlayer::WavPlayer(WavPlayer&& rhs) noexcept = default; | ||||
| 
 | ||||
| WavPlayer& WavPlayer::operator=(const WavPlayer& rhs) = default; | ||||
| 
 | ||||
| WavPlayer& WavPlayer::operator=(WavPlayer&& rhs) noexcept = default; | ||||
| 
 | ||||
| WavPlayer::WavPlayer(audio::Wav&& wav) noexcept : wav_(std::forward<Wav>(wav)), position_(0), looping_(false) { | ||||
| } | ||||
| 
 | ||||
| std::size_t WavPlayer::generate(tcb::span<audio::Sample<1>> buffer) { | ||||
| 	std::size_t samples_read = 0; | ||||
| 	while (samples_read < buffer.size()) { | ||||
| 		const std::size_t read_this_time = wav_.get_samples(position_, buffer.subspan(samples_read)); | ||||
| 		position_ += read_this_time; | ||||
| 		samples_read += read_this_time; | ||||
| 
 | ||||
| 		if (position_ > wav_.length() && looping_) { | ||||
| 			position_ = 0; | ||||
| 		} | ||||
| 		if (read_this_time == 0 && !looping_) { | ||||
| 			break; | ||||
| 		} | ||||
| 	} | ||||
| 	return samples_read; | ||||
| } | ||||
							
								
								
									
										49
									
								
								src/audio/wav_player.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								src/audio/wav_player.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,49 @@ | |||
| // SONIC ROBO BLAST 2
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| // Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
 | ||||
| //
 | ||||
| // This program is free software distributed under the
 | ||||
| // terms of the GNU General Public License, version 2.
 | ||||
| // See the 'LICENSE' file for more details.
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| 
 | ||||
| #ifndef __SRB2_AUDIO_WAV_PLAYER_HPP__ | ||||
| #define __SRB2_AUDIO_WAV_PLAYER_HPP__ | ||||
| 
 | ||||
| #include <cstddef> | ||||
| 
 | ||||
| #include <tcb/span.hpp> | ||||
| 
 | ||||
| #include "source.hpp" | ||||
| #include "wav.hpp" | ||||
| 
 | ||||
| namespace srb2::audio { | ||||
| 
 | ||||
| class WavPlayer final : public Source<1> { | ||||
| 	Wav wav_; | ||||
| 	std::size_t position_; | ||||
| 	bool looping_; | ||||
| 
 | ||||
| public: | ||||
| 	WavPlayer(); | ||||
| 	WavPlayer(const WavPlayer& rhs); | ||||
| 	WavPlayer(WavPlayer&& rhs) noexcept; | ||||
| 
 | ||||
| 	WavPlayer& operator=(const WavPlayer& rhs); | ||||
| 	WavPlayer& operator=(WavPlayer&& rhs) noexcept; | ||||
| 
 | ||||
| 	WavPlayer(Wav&& wav) noexcept; | ||||
| 
 | ||||
| 	virtual std::size_t generate(tcb::span<Sample<1>> buffer) override; | ||||
| 
 | ||||
| 	bool looping() const { return looping_; } | ||||
| 	void looping(bool looping) { looping_ = looping; } | ||||
| 
 | ||||
| 	std::size_t sample_rate() const { return wav_.sample_rate(); } | ||||
| 	float duration_seconds() const { return wav_.length() / static_cast<float>(wav_.sample_rate()); } | ||||
| 	void seek(float seconds) { position_ = seconds * wav_.sample_rate(); } | ||||
| }; | ||||
| 
 | ||||
| } // namespace srb2::audio
 | ||||
| 
 | ||||
| #endif // __SRB2_AUDIO_WAV_PLAYER_HPP__
 | ||||
							
								
								
									
										167
									
								
								src/audio/xmp.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										167
									
								
								src/audio/xmp.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,167 @@ | |||
| // SONIC ROBO BLAST 2
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| // Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
 | ||||
| //
 | ||||
| // This program is free software distributed under the
 | ||||
| // terms of the GNU General Public License, version 2.
 | ||||
| // See the 'LICENSE' file for more details.
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| 
 | ||||
| #include "xmp.hpp" | ||||
| 
 | ||||
| #include <limits> | ||||
| 
 | ||||
| #include "../cxxutil.hpp" | ||||
| 
 | ||||
| using namespace srb2; | ||||
| using namespace srb2::audio; | ||||
| 
 | ||||
| XmpException::XmpException(int code) : code_(code) { | ||||
| } | ||||
| 
 | ||||
| const char* XmpException::what() const noexcept { | ||||
| 	switch (code_) { | ||||
| 	case -XMP_ERROR_INTERNAL: | ||||
| 		return "XMP_ERROR_INTERNAL"; | ||||
| 	case -XMP_ERROR_FORMAT: | ||||
| 		return "XMP_ERROR_FORMAT"; | ||||
| 	case -XMP_ERROR_LOAD: | ||||
| 		return "XMP_ERROR_LOAD"; | ||||
| 	case -XMP_ERROR_DEPACK: | ||||
| 		return "XMP_ERROR_DEPACK"; | ||||
| 	case -XMP_ERROR_SYSTEM: | ||||
| 		return "XMP_ERROR_SYSTEM"; | ||||
| 	case -XMP_ERROR_INVALID: | ||||
| 		return "XMP_ERROR_INVALID"; | ||||
| 	case -XMP_ERROR_STATE: | ||||
| 		return "XMP_ERROR_STATE"; | ||||
| 	default: | ||||
| 		return "unknown"; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| template <size_t C> | ||||
| Xmp<C>::Xmp() : data_(), instance_(nullptr), module_loaded_(false), looping_(false) { | ||||
| } | ||||
| 
 | ||||
| template <size_t C> | ||||
| Xmp<C>::Xmp(std::vector<std::byte> data) | ||||
| 	: data_(std::move(data)), instance_(nullptr), module_loaded_(false), looping_(false) { | ||||
| 	_init(); | ||||
| } | ||||
| 
 | ||||
| template <size_t C> | ||||
| Xmp<C>::Xmp(tcb::span<std::byte> data) | ||||
| 	: data_(data.begin(), data.end()), instance_(nullptr), module_loaded_(false), looping_(false) { | ||||
| 	_init(); | ||||
| } | ||||
| 
 | ||||
| template <size_t C> | ||||
| Xmp<C>::Xmp(Xmp<C>&& rhs) noexcept : Xmp<C>() { | ||||
| 	std::swap(data_, rhs.data_); | ||||
| 	std::swap(instance_, rhs.instance_); | ||||
| 	std::swap(module_loaded_, rhs.module_loaded_); | ||||
| 	std::swap(looping_, rhs.looping_); | ||||
| } | ||||
| 
 | ||||
| template <size_t C> | ||||
| Xmp<C>& Xmp<C>::operator=(Xmp<C>&& rhs) noexcept { | ||||
| 	std::swap(data_, rhs.data_); | ||||
| 	std::swap(instance_, rhs.instance_); | ||||
| 	std::swap(module_loaded_, rhs.module_loaded_); | ||||
| 	std::swap(looping_, rhs.looping_); | ||||
| 
 | ||||
| 	return *this; | ||||
| }; | ||||
| 
 | ||||
| template <size_t C> | ||||
| Xmp<C>::~Xmp() { | ||||
| 	if (instance_) { | ||||
| 		xmp_free_context(instance_); | ||||
| 		instance_ = nullptr; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| template <size_t C> | ||||
| std::size_t Xmp<C>::play_buffer(tcb::span<std::array<int16_t, C>> buffer) { | ||||
| 	SRB2_ASSERT(instance_ != nullptr); | ||||
| 	SRB2_ASSERT(module_loaded_ == true); | ||||
| 
 | ||||
| 	int result = xmp_play_buffer(instance_, buffer.data(), buffer.size_bytes(), !looping_); | ||||
| 
 | ||||
| 	if (result == -XMP_END) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	if (result != 0) | ||||
| 		throw XmpException(result); | ||||
| 
 | ||||
| 	return buffer.size(); | ||||
| } | ||||
| 
 | ||||
| template <size_t C> | ||||
| void Xmp<C>::reset() { | ||||
| 	SRB2_ASSERT(instance_ != nullptr); | ||||
| 	SRB2_ASSERT(module_loaded_ == true); | ||||
| 
 | ||||
| 	xmp_restart_module(instance_); | ||||
| } | ||||
| 
 | ||||
| template <size_t C> | ||||
| float Xmp<C>::duration_seconds() const { | ||||
| 	SRB2_ASSERT(instance_ != nullptr); | ||||
| 	SRB2_ASSERT(module_loaded_ == true); | ||||
| 
 | ||||
| 	xmp_frame_info info; | ||||
| 	xmp_get_frame_info(instance_, &info); | ||||
| 	return static_cast<float>(info.total_time) / 1000.f; | ||||
| } | ||||
| 
 | ||||
| template <size_t C> | ||||
| void Xmp<C>::seek(int position_ms) { | ||||
| 	SRB2_ASSERT(instance_ != nullptr); | ||||
| 	SRB2_ASSERT(module_loaded_ == true); | ||||
| 
 | ||||
| 	int err = xmp_seek_time(instance_, position_ms); | ||||
| 	if (err != 0) | ||||
| 		throw XmpException(err); | ||||
| } | ||||
| 
 | ||||
| template <size_t C> | ||||
| void Xmp<C>::_init() { | ||||
| 	if (instance_) | ||||
| 		return; | ||||
| 
 | ||||
| 	if (data_.size() >= std::numeric_limits<long>::max()) | ||||
| 		throw std::logic_error("Buffer is too large for xmp"); | ||||
| 	if (data_.size() == 0) | ||||
| 		throw std::logic_error("Insufficient data from stream"); | ||||
| 
 | ||||
| 	instance_ = xmp_create_context(); | ||||
| 	if (instance_ == nullptr) { | ||||
| 		throw std::bad_alloc(); | ||||
| 	} | ||||
| 
 | ||||
| 	int result = xmp_load_module_from_memory(instance_, data_.data(), data_.size()); | ||||
| 	if (result != 0) { | ||||
| 		xmp_free_context(instance_); | ||||
| 		instance_ = nullptr; | ||||
| 		throw XmpException(result); | ||||
| 	} | ||||
| 	module_loaded_ = true; | ||||
| 
 | ||||
| 	int flags = 0; | ||||
| 	if constexpr (C == 1) { | ||||
| 		flags |= XMP_FORMAT_MONO; | ||||
| 	} | ||||
| 	result = xmp_start_player(instance_, 44100, flags); | ||||
| 	if (result != 0) { | ||||
| 		xmp_release_module(instance_); | ||||
| 		module_loaded_ = false; | ||||
| 		xmp_free_context(instance_); | ||||
| 		instance_ = nullptr; | ||||
| 		throw XmpException(result); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| template class srb2::audio::Xmp<1>; | ||||
| template class srb2::audio::Xmp<2>; | ||||
							
								
								
									
										78
									
								
								src/audio/xmp.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								src/audio/xmp.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,78 @@ | |||
| // SONIC ROBO BLAST 2
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| // Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
 | ||||
| //
 | ||||
| // This program is free software distributed under the
 | ||||
| // terms of the GNU General Public License, version 2.
 | ||||
| // See the 'LICENSE' file for more details.
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| 
 | ||||
| 	#ifndef __SRB2_AUDIO_XMP_HPP__ | ||||
| #define __SRB2_AUDIO_XMP_HPP__ | ||||
| 
 | ||||
| #include <array> | ||||
| #include <cstddef> | ||||
| #include <exception> | ||||
| #include <stdexcept> | ||||
| #include <utility> | ||||
| #include <vector> | ||||
| 
 | ||||
| #include <tcb/span.hpp> | ||||
| #include <xmp.h> | ||||
| 
 | ||||
| #include "../io/streams.hpp" | ||||
| 
 | ||||
| namespace srb2::audio { | ||||
| 
 | ||||
| class XmpException : public std::exception { | ||||
| 	int code_; | ||||
| 
 | ||||
| public: | ||||
| 	XmpException(int code); | ||||
| 	virtual const char* what() const noexcept override final; | ||||
| }; | ||||
| 
 | ||||
| template <size_t C> | ||||
| class Xmp final { | ||||
| 	std::vector<std::byte> data_; | ||||
| 	xmp_context instance_; | ||||
| 	bool module_loaded_; | ||||
| 	bool looping_; | ||||
| 
 | ||||
| public: | ||||
| 	Xmp(); | ||||
| 
 | ||||
| 	explicit Xmp(std::vector<std::byte> data); | ||||
| 	explicit Xmp(tcb::span<std::byte> data); | ||||
| 
 | ||||
| 	Xmp(const Xmp<C>&) = delete; | ||||
| 	Xmp(Xmp<C>&& rhs) noexcept; | ||||
| 
 | ||||
| 	Xmp& operator=(const Xmp&) = delete; | ||||
| 	Xmp& operator=(Xmp&& rhs) noexcept; | ||||
| 
 | ||||
| 	std::size_t play_buffer(tcb::span<std::array<int16_t, C>> buffer); | ||||
| 	bool looping() const { return looping_; }; | ||||
| 	void looping(bool looping) { looping_ = looping; }; | ||||
| 	void reset(); | ||||
| 	float duration_seconds() const; | ||||
| 	void seek(int position_ms); | ||||
| 
 | ||||
| 	~Xmp(); | ||||
| 
 | ||||
| private: | ||||
| 	void _init(); | ||||
| }; | ||||
| 
 | ||||
| extern template class Xmp<1>; | ||||
| extern template class Xmp<2>; | ||||
| 
 | ||||
| template <size_t C, typename I, typename std::enable_if_t<srb2::io::IsInputStreamV<I>, int> = 0> | ||||
| inline Xmp<C> load_xmp(I& stream) { | ||||
| 	std::vector<std::byte> data = srb2::io::read_to_vec(stream); | ||||
| 	return Xmp<C> {std::move(data)}; | ||||
| } | ||||
| 
 | ||||
| } // namespace srb2::audio
 | ||||
| 
 | ||||
| #endif // __SRB2_AUDIO_XMP_HPP__
 | ||||
							
								
								
									
										57
									
								
								src/audio/xmp_player.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								src/audio/xmp_player.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,57 @@ | |||
| // SONIC ROBO BLAST 2
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| // Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
 | ||||
| //
 | ||||
| // This program is free software distributed under the
 | ||||
| // terms of the GNU General Public License, version 2.
 | ||||
| // See the 'LICENSE' file for more details.
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| 
 | ||||
| #include "xmp_player.hpp" | ||||
| 
 | ||||
| #include <cmath> | ||||
| 
 | ||||
| using namespace srb2; | ||||
| using namespace srb2::audio; | ||||
| 
 | ||||
| template <size_t C> | ||||
| XmpPlayer<C>::XmpPlayer(Xmp<C>&& xmp) : xmp_(std::move(xmp)), buf_() { | ||||
| } | ||||
| 
 | ||||
| template <size_t C> | ||||
| XmpPlayer<C>::XmpPlayer(XmpPlayer&& rhs) noexcept = default; | ||||
| 
 | ||||
| template <size_t C> | ||||
| XmpPlayer<C>& XmpPlayer<C>::operator=(XmpPlayer<C>&& rhs) noexcept = default; | ||||
| 
 | ||||
| template <size_t C> | ||||
| XmpPlayer<C>::~XmpPlayer() = default; | ||||
| 
 | ||||
| template <size_t C> | ||||
| std::size_t XmpPlayer<C>::generate(tcb::span<Sample<C>> buffer) { | ||||
| 	buf_.resize(buffer.size()); | ||||
| 	std::size_t read = xmp_.play_buffer(tcb::make_span(buf_)); | ||||
| 	buf_.resize(read); | ||||
| 	std::size_t ret = std::min(buffer.size(), buf_.size()); | ||||
| 
 | ||||
| 	for (std::size_t i = 0; i < ret; i++) { | ||||
| 		for (std::size_t j = 0; j < C; j++) { | ||||
| 			buffer[i].amplitudes[j] = buf_[i][j] / 32768.f; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| template <size_t C> | ||||
| float XmpPlayer<C>::duration_seconds() const { | ||||
| 	return xmp_.duration_seconds(); | ||||
| } | ||||
| 
 | ||||
| template <size_t C> | ||||
| void XmpPlayer<C>::seek(float position_seconds) { | ||||
| 	xmp_.seek(static_cast<int>(std::round(position_seconds * 1000.f))); | ||||
| } | ||||
| 
 | ||||
| template class srb2::audio::XmpPlayer<1>; | ||||
| template class srb2::audio::XmpPlayer<2>; | ||||
							
								
								
									
										48
									
								
								src/audio/xmp_player.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								src/audio/xmp_player.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,48 @@ | |||
| // SONIC ROBO BLAST 2
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| // Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
 | ||||
| //
 | ||||
| // This program is free software distributed under the
 | ||||
| // terms of the GNU General Public License, version 2.
 | ||||
| // See the 'LICENSE' file for more details.
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| 
 | ||||
| #ifndef __SRB2_AUDIO_XMP_PLAYER_HPP__ | ||||
| #define __SRB2_AUDIO_XMP_PLAYER_HPP__ | ||||
| 
 | ||||
| #include "source.hpp" | ||||
| #include "xmp.hpp" | ||||
| 
 | ||||
| namespace srb2::audio { | ||||
| 
 | ||||
| template <size_t C> | ||||
| class XmpPlayer final : public Source<C> { | ||||
| 	Xmp<C> xmp_; | ||||
| 	std::vector<std::array<int16_t, C>> buf_; | ||||
| 
 | ||||
| public: | ||||
| 	XmpPlayer(Xmp<C>&& xmp); | ||||
| 
 | ||||
| 	XmpPlayer(const XmpPlayer<C>&) = delete; | ||||
| 	XmpPlayer(XmpPlayer<C>&& rhs) noexcept; | ||||
| 
 | ||||
| 	XmpPlayer<C>& operator=(const XmpPlayer<C>&) = delete; | ||||
| 	XmpPlayer<C>& operator=(XmpPlayer<C>&& rhs) noexcept; | ||||
| 
 | ||||
| 	~XmpPlayer(); | ||||
| 
 | ||||
| 	virtual std::size_t generate(tcb::span<Sample<C>> buffer) override final; | ||||
| 
 | ||||
| 	bool looping() { return xmp_.looping(); }; | ||||
| 	void looping(bool looping) { xmp_.looping(looping); } | ||||
| 	void reset() { xmp_.reset(); } | ||||
| 	float duration_seconds() const; | ||||
| 	void seek(float position_seconds); | ||||
| }; | ||||
| 
 | ||||
| extern template class XmpPlayer<1>; | ||||
| extern template class XmpPlayer<2>; | ||||
| 
 | ||||
| } // namespace srb2::audio
 | ||||
| 
 | ||||
| #endif // __SRB2_AUDIO_XMP_PLAYER_HPP__
 | ||||
|  | @ -1,3 +1,12 @@ | |||
| // SONIC ROBO BLAST 2
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| // Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
 | ||||
| //
 | ||||
| // This program is free software distributed under the
 | ||||
| // terms of the GNU General Public License, version 2.
 | ||||
| // See the 'LICENSE' file for more details.
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| 
 | ||||
| #include "streams.hpp" | ||||
| 
 | ||||
| template class srb2::io::ZlibInputStream<srb2::io::SpanStream>; | ||||
|  |  | |||
|  | @ -1,3 +1,12 @@ | |||
| // SONIC ROBO BLAST 2
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| // Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
 | ||||
| //
 | ||||
| // This program is free software distributed under the
 | ||||
| // terms of the GNU General Public License, version 2.
 | ||||
| // See the 'LICENSE' file for more details.
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| 
 | ||||
| #ifndef __SRB2_IO_STREAMS_HPP__ | ||||
| #define __SRB2_IO_STREAMS_HPP__ | ||||
| 
 | ||||
|  | @ -409,7 +418,7 @@ public: | |||
| 
 | ||||
| 		switch (seek_from) { | ||||
| 		case SeekFrom::kStart: | ||||
| 			if (offset < 0 || offset >= static_cast<StreamOffset>(span_.size())) { | ||||
| 			if (offset < 0) { | ||||
| 				throw std::logic_error("start offset is out of bounds"); | ||||
| 			} | ||||
| 			head = offset; | ||||
|  | @ -421,7 +430,7 @@ public: | |||
| 			head = span_.size() - offset; | ||||
| 			break; | ||||
| 		case SeekFrom::kCurrent: | ||||
| 			if (head_ + offset < 0 || head_ + offset >= span_.size()) { | ||||
| 			if (head_ + offset < 0) { | ||||
| 				throw std::logic_error("offset is out of bounds"); | ||||
| 			} | ||||
| 			head = head_ + offset; | ||||
|  | @ -480,7 +489,7 @@ public: | |||
| 
 | ||||
| 		switch (seek_from) { | ||||
| 		case SeekFrom::kStart: | ||||
| 			if (offset < 0 || offset >= static_cast<StreamOffset>(vec_.size())) { | ||||
| 			if (offset < 0) { | ||||
| 				throw std::logic_error("start offset is out of bounds"); | ||||
| 			} | ||||
| 			head = offset; | ||||
|  | @ -492,7 +501,7 @@ public: | |||
| 			head = vec_.size() - offset; | ||||
| 			break; | ||||
| 		case SeekFrom::kCurrent: | ||||
| 			if (head_ + offset < 0 || head_ + offset >= vec_.size()) { | ||||
| 			if (head_ + offset < 0) { | ||||
| 				throw std::logic_error("offset is out of bounds"); | ||||
| 			} | ||||
| 			head = head_ + offset; | ||||
|  |  | |||
|  | @ -1,12 +1,12 @@ | |||
| # Declare SDL2 interface sources | ||||
| 
 | ||||
| target_sources(SRB2SDL2 PRIVATE | ||||
| 	mixer_sound.c | ||||
| 	new_sound.cpp | ||||
| 	ogl_sdl.c | ||||
| 	i_threads.c | ||||
| 	i_net.c | ||||
| 	i_system.c | ||||
| 	i_main.c | ||||
| 	i_main.cpp | ||||
| 	i_video.c | ||||
| 	dosstr.c | ||||
| 	endtxt.c | ||||
|  | @ -57,9 +57,9 @@ if("${CMAKE_SYSTEM_NAME}" MATCHES Darwin) | |||
| endif() | ||||
| 
 | ||||
| if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}" AND NOT "${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}") | ||||
| 	target_link_libraries(SRB2SDL2 PRIVATE SDL2::SDL2-static SDL2_mixer::SDL2_mixer-static) | ||||
| 	target_link_libraries(SRB2SDL2 PRIVATE SDL2::SDL2-static) | ||||
| else() | ||||
| 	target_link_libraries(SRB2SDL2 PRIVATE SDL2::SDL2 SDL2_mixer::SDL2_mixer) | ||||
| 	target_link_libraries(SRB2SDL2 PRIVATE SDL2::SDL2) | ||||
| endif() | ||||
| 
 | ||||
| if("${CMAKE_SYSTEM_NAME}" MATCHES Linux) | ||||
|  |  | |||
|  | @ -23,6 +23,10 @@ | |||
| #include "../m_misc.h"/* path shit */ | ||||
| #include "../i_system.h" | ||||
| 
 | ||||
| #include <exception> | ||||
| #include <stdexcept> | ||||
| #include <string> | ||||
| 
 | ||||
| #if defined (__GNUC__) || defined (__unix__) | ||||
| #include <unistd.h> | ||||
| #endif | ||||
|  | @ -31,7 +35,9 @@ | |||
| #include <errno.h> | ||||
| #endif | ||||
| 
 | ||||
| extern "C" { | ||||
| #include "time.h" // For log timestamps
 | ||||
| } | ||||
| 
 | ||||
| #ifdef HAVE_SDL | ||||
| 
 | ||||
|  | @ -70,7 +76,9 @@ char logfilename[1024]; | |||
| #endif | ||||
| 
 | ||||
| #if defined (_WIN32) | ||||
| extern "C" { | ||||
| #include "../win32/win_dbg.h" | ||||
| } | ||||
| typedef BOOL (WINAPI *p_IsDebuggerPresent)(VOID); | ||||
| #endif | ||||
| 
 | ||||
|  | @ -151,20 +159,20 @@ static void InitLogging(void) | |||
| 		if (M_IsPathAbsolute(reldir)) | ||||
| 		{ | ||||
| 			left = snprintf(logfilename, sizeof logfilename, | ||||
| 					"%s"PATHSEP, reldir); | ||||
| 					"%s" PATHSEP, reldir); | ||||
| 		} | ||||
| 		else | ||||
| #ifdef DEFAULTDIR | ||||
| 		if (logdir) | ||||
| 		{ | ||||
| 			left = snprintf(logfilename, sizeof logfilename, | ||||
| 					"%s"PATHSEP DEFAULTDIR PATHSEP"%s"PATHSEP, logdir, reldir); | ||||
| 					"%s" PATHSEP DEFAULTDIR PATHSEP "%s" PATHSEP, logdir, reldir); | ||||
| 		} | ||||
| 		else | ||||
| #endif/*DEFAULTDIR*/ | ||||
| 		{ | ||||
| 			left = snprintf(logfilename, sizeof logfilename, | ||||
| 					"."PATHSEP"%s"PATHSEP, reldir); | ||||
| 					"." PATHSEP "%s" PATHSEP, reldir); | ||||
| 		} | ||||
| 
 | ||||
| 		strftime(&logfilename[left], sizeof logfilename - left, | ||||
|  | @ -208,6 +216,33 @@ ChDirToExe (void) | |||
| } | ||||
| #endif | ||||
| 
 | ||||
| static void walk_exception_stack(std::string& accum, bool nested) { | ||||
| 	if (nested) | ||||
| 		accum.append("\n  Caused by: Unknown exception"); | ||||
| 	else | ||||
| 		accum.append("Uncaught exception: Unknown exception"); | ||||
| } | ||||
| 
 | ||||
| static void walk_exception_stack(std::string& accum, const std::exception& ex, bool nested) { | ||||
| 	if (nested) | ||||
| 		accum.append("\n  Caused by: "); | ||||
| 	else | ||||
| 		accum.append("Uncaught exception: "); | ||||
| 
 | ||||
| 	accum.append("("); | ||||
| 	accum.append(typeid(ex).name()); | ||||
| 	accum.append(") "); | ||||
| 	accum.append(ex.what()); | ||||
| 
 | ||||
| 	try { | ||||
| 		std::rethrow_if_nested(ex); | ||||
| 	} catch (const std::exception& ex) { | ||||
| 		walk_exception_stack(accum, ex, true); | ||||
| 	} catch (...) { | ||||
| 		walk_exception_stack(accum, true); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /**	\brief	The main function
 | ||||
| 
 | ||||
|  | @ -268,6 +303,8 @@ int main(int argc, char **argv) | |||
| 	MakeCodeWritable(); | ||||
| #endif | ||||
| 
 | ||||
| 	try { | ||||
| 
 | ||||
| 	// startup SRB2
 | ||||
| 	CONS_Printf("Setting up Dr. Robotnik's Ring Racers...\n"); | ||||
| 	D_SRB2Main(); | ||||
|  | @ -279,6 +316,16 @@ int main(int argc, char **argv) | |||
| 	// never return
 | ||||
| 	D_SRB2Loop(); | ||||
| 
 | ||||
| 	} catch (const std::exception& ex) { | ||||
| 		std::string exception; | ||||
| 		walk_exception_stack(exception, ex, false); | ||||
| 		I_Error("%s", exception.c_str()); | ||||
| 	} catch (...) { | ||||
| 		std::string exception; | ||||
| 		walk_exception_stack(exception, false); | ||||
| 		I_Error("%s", exception.c_str()); | ||||
| 	} | ||||
| 
 | ||||
| #ifdef BUGTRAP | ||||
| 	// This is safe even if BT didn't start.
 | ||||
| 	ShutdownBugTrap(); | ||||
							
								
								
									
										665
									
								
								src/sdl/new_sound.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										665
									
								
								src/sdl/new_sound.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,665 @@ | |||
| // SONIC ROBO BLAST 2
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| // Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard
 | ||||
| //
 | ||||
| // This program is free software distributed under the
 | ||||
| // terms of the GNU General Public License, version 2.
 | ||||
| // See the 'LICENSE' file for more details.
 | ||||
| //-----------------------------------------------------------------------------
 | ||||
| 
 | ||||
| #include <algorithm> | ||||
| #include <cmath> | ||||
| #include <memory> | ||||
| 
 | ||||
| #include <SDL.h> | ||||
| 
 | ||||
| #include "../audio/chunk_load.hpp" | ||||
| #include "../audio/gain.hpp" | ||||
| #include "../audio/mixer.hpp" | ||||
| #include "../audio/music_player.hpp" | ||||
| #include "../audio/sound_chunk.hpp" | ||||
| #include "../audio/sound_effect_player.hpp" | ||||
| #include "../cxxutil.hpp" | ||||
| #include "../io/streams.hpp" | ||||
| 
 | ||||
| #include "../doomdef.h" | ||||
| #include "../i_sound.h" | ||||
| #include "../s_sound.h" | ||||
| #include "../sounds.h" | ||||
| #include "../w_wad.h" | ||||
| #include "../z_zone.h" | ||||
| 
 | ||||
| using std::make_shared; | ||||
| using std::make_unique; | ||||
| using std::shared_ptr; | ||||
| using std::unique_ptr; | ||||
| using std::vector; | ||||
| 
 | ||||
| using srb2::audio::Gain; | ||||
| using srb2::audio::Mixer; | ||||
| using srb2::audio::MusicPlayer; | ||||
| using srb2::audio::Sample; | ||||
| using srb2::audio::SoundChunk; | ||||
| using srb2::audio::SoundEffectPlayer; | ||||
| using srb2::audio::Source; | ||||
| using namespace srb2; | ||||
| using namespace srb2::io; | ||||
| 
 | ||||
| // extern in i_sound.h
 | ||||
| UINT8 sound_started = false; | ||||
| 
 | ||||
| static unique_ptr<Mixer<2>> master; | ||||
| static shared_ptr<Mixer<2>> mixer_sound_effects; | ||||
| static shared_ptr<Mixer<2>> mixer_music; | ||||
| static shared_ptr<MusicPlayer> music_player; | ||||
| static shared_ptr<Gain<2>> gain_sound_effects; | ||||
| static shared_ptr<Gain<2>> gain_music; | ||||
| 
 | ||||
| static vector<shared_ptr<SoundEffectPlayer>> sound_effect_channels; | ||||
| 
 | ||||
| static void (*music_fade_callback)(); | ||||
| 
 | ||||
| void* I_GetSfx(sfxinfo_t* sfx) { | ||||
| 	if (sfx->lumpnum == LUMPERROR) | ||||
| 		sfx->lumpnum = S_GetSfxLumpNum(sfx); | ||||
| 	sfx->length = W_LumpLength(sfx->lumpnum); | ||||
| 
 | ||||
| 	std::byte* lump = static_cast<std::byte*>(W_CacheLumpNum(sfx->lumpnum, PU_SOUND)); | ||||
| 	auto _ = srb2::finally([lump]() { Z_Free(lump); }); | ||||
| 
 | ||||
| 	tcb::span<std::byte> data_span(lump, sfx->length); | ||||
| 	std::optional<SoundChunk> chunk = srb2::audio::try_load_chunk(data_span); | ||||
| 
 | ||||
| 	if (!chunk) | ||||
| 		return nullptr; | ||||
| 
 | ||||
| 	SoundChunk* heap_chunk = new SoundChunk {std::move(*chunk)}; | ||||
| 
 | ||||
| 	return heap_chunk; | ||||
| } | ||||
| 
 | ||||
| void I_FreeSfx(sfxinfo_t* sfx) { | ||||
| 	if (sfx->data) { | ||||
| 		SoundChunk* chunk = static_cast<SoundChunk*>(sfx->data); | ||||
| 		auto _ = srb2::finally([chunk]() { delete chunk; }); | ||||
| 
 | ||||
| 		// Stop any channels playing this chunk
 | ||||
| 		for (auto& player : sound_effect_channels) { | ||||
| 			if (player->is_playing_chunk(chunk)) { | ||||
| 				player->reset(); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	sfx->data = nullptr; | ||||
| 	sfx->lumpnum = LUMPERROR; | ||||
| } | ||||
| 
 | ||||
| namespace { | ||||
| 
 | ||||
| class SdlAudioLockHandle { | ||||
| public: | ||||
| 	SdlAudioLockHandle() { SDL_LockAudio(); } | ||||
| 	~SdlAudioLockHandle() { SDL_UnlockAudio(); } | ||||
| }; | ||||
| 
 | ||||
| void audio_callback(void* userdata, Uint8* buffer, int len) { | ||||
| 	// The SDL Audio lock is implied to be held during callback.
 | ||||
| 
 | ||||
| 	try { | ||||
| 		Sample<2>* float_buffer = reinterpret_cast<Sample<2>*>(buffer); | ||||
| 		size_t float_len = len / 8; | ||||
| 
 | ||||
| 		for (size_t i = 0; i < float_len; i++) { | ||||
| 			float_buffer[i] = Sample<2> {0.f, 0.f}; | ||||
| 		} | ||||
| 
 | ||||
| 		if (!master) | ||||
| 			return; | ||||
| 
 | ||||
| 		master->generate(tcb::span {float_buffer, float_len}); | ||||
| 
 | ||||
| 		for (size_t i = 0; i < float_len; i++) { | ||||
| 			float_buffer[i] = { | ||||
| 				std::clamp(float_buffer[i].amplitudes[0], -1.f, 1.f), | ||||
| 				std::clamp(float_buffer[i].amplitudes[1], -1.f, 1.f), | ||||
| 			}; | ||||
| 		} | ||||
| 	} catch (...) { | ||||
| 	} | ||||
| 
 | ||||
| 	return; | ||||
| } | ||||
| 
 | ||||
| void initialize_sound() { | ||||
| 	if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { | ||||
| 		CONS_Alert(CONS_ERROR, "Error initializing SDL Audio: %s\n", SDL_GetError()); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	SDL_AudioSpec desired; | ||||
| 	desired.format = AUDIO_F32SYS; | ||||
| 	desired.channels = 2; | ||||
| 	desired.samples = 1024; | ||||
| 	desired.freq = 44100; | ||||
| 	desired.callback = audio_callback; | ||||
| 
 | ||||
| 	if (SDL_OpenAudio(&desired, NULL) < 0) { | ||||
| 		CONS_Alert(CONS_ERROR, "Failed to open SDL Audio device: %s\n", SDL_GetError()); | ||||
| 		SDL_QuitSubSystem(SDL_INIT_AUDIO); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	SDL_PauseAudio(SDL_FALSE); | ||||
| 
 | ||||
| 	{ | ||||
| 		SdlAudioLockHandle _; | ||||
| 
 | ||||
| 		master = make_unique<Mixer<2>>(); | ||||
| 		mixer_sound_effects = make_shared<Mixer<2>>(); | ||||
| 		mixer_music = make_shared<Mixer<2>>(); | ||||
| 		music_player = make_shared<MusicPlayer>(); | ||||
| 		gain_sound_effects = make_shared<Gain<2>>(); | ||||
| 		gain_music = make_shared<Gain<2>>(); | ||||
| 		gain_sound_effects->bind(mixer_sound_effects); | ||||
| 		gain_music->bind(mixer_music); | ||||
| 		master->add_source(gain_sound_effects); | ||||
| 		master->add_source(gain_music); | ||||
| 		mixer_music->add_source(music_player); | ||||
| 		for (size_t i = 0; i < static_cast<size_t>(cv_numChannels.value); i++) { | ||||
| 			shared_ptr<SoundEffectPlayer> player = make_shared<SoundEffectPlayer>(); | ||||
| 			sound_effect_channels.push_back(player); | ||||
| 			mixer_sound_effects->add_source(player); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	sound_started = true; | ||||
| } | ||||
| 
 | ||||
| } // namespace
 | ||||
| 
 | ||||
| void I_StartupSound(void) { | ||||
| 	if (!sound_started) | ||||
| 		initialize_sound(); | ||||
| } | ||||
| 
 | ||||
| void I_ShutdownSound(void) { | ||||
| 	SdlAudioLockHandle _; | ||||
| 
 | ||||
| 	for (auto& channel : sound_effect_channels) { | ||||
| 		*channel = audio::SoundEffectPlayer(); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| void I_UpdateSound(void) { | ||||
| 	// The SDL audio lock is re-entrant, so it is safe to lock twice
 | ||||
| 	// for the "fade to stop music" callback later.
 | ||||
| 	SdlAudioLockHandle _; | ||||
| 
 | ||||
| 	if (music_fade_callback && !music_player->fading()) { | ||||
| 		auto old_callback = music_fade_callback; | ||||
| 		music_fade_callback = nullptr; | ||||
| 		(old_callback()); | ||||
| 	} | ||||
| 	return; | ||||
| } | ||||
| 
 | ||||
| //
 | ||||
| //  SFX I/O
 | ||||
| //
 | ||||
| 
 | ||||
| INT32 I_StartSound(sfxenum_t id, UINT8 vol, UINT8 sep, UINT8 pitch, UINT8 priority, INT32 channel) { | ||||
| 	(void) pitch; | ||||
| 	(void) priority; | ||||
| 
 | ||||
| 	SdlAudioLockHandle _; | ||||
| 
 | ||||
| 	if (channel >= 0 && static_cast<size_t>(channel) >= sound_effect_channels.size()) | ||||
| 		return -1; | ||||
| 
 | ||||
| 	shared_ptr<SoundEffectPlayer> player_channel; | ||||
| 	if (channel < 0) { | ||||
| 		// find a free sfx channel
 | ||||
| 		for (size_t i = 0; i < sound_effect_channels.size(); i++) { | ||||
| 			if (sound_effect_channels[i]->finished()) { | ||||
| 				player_channel = sound_effect_channels[i]; | ||||
| 				channel = i; | ||||
| 				break; | ||||
| 			} | ||||
| 		} | ||||
| 	} else { | ||||
| 		player_channel = sound_effect_channels[channel]; | ||||
| 	} | ||||
| 
 | ||||
| 	if (!player_channel) | ||||
| 		return -1; | ||||
| 
 | ||||
| 	SoundChunk* chunk = static_cast<SoundChunk*>(S_sfx[id].data); | ||||
| 	if (chunk == nullptr) | ||||
| 		return -1; | ||||
| 
 | ||||
| 	float vol_float = static_cast<float>(vol) / 255.f; | ||||
| 	float sep_float = static_cast<float>(sep) / 127.f - 1.f; | ||||
| 
 | ||||
| 	player_channel->start(chunk, vol_float, sep_float); | ||||
| 
 | ||||
| 	return channel; | ||||
| } | ||||
| 
 | ||||
| void I_StopSound(INT32 handle) { | ||||
| 	SdlAudioLockHandle _; | ||||
| 
 | ||||
| 	if (sound_effect_channels.empty()) | ||||
| 		return; | ||||
| 
 | ||||
| 	if (handle < 0) | ||||
| 		return; | ||||
| 
 | ||||
| 	size_t index = handle; | ||||
| 
 | ||||
| 	if (index >= sound_effect_channels.size()) | ||||
| 		return; | ||||
| 
 | ||||
| 	sound_effect_channels[index]->reset(); | ||||
| } | ||||
| 
 | ||||
| boolean I_SoundIsPlaying(INT32 handle) { | ||||
| 	SdlAudioLockHandle _; | ||||
| 
 | ||||
| 	// Handle is channel index
 | ||||
| 	if (sound_effect_channels.empty()) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	if (handle < 0) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	size_t index = handle; | ||||
| 
 | ||||
| 	if (index >= sound_effect_channels.size()) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	return sound_effect_channels[index]->finished() ? 0 : 1; | ||||
| } | ||||
| 
 | ||||
| void I_UpdateSoundParams(INT32 handle, UINT8 vol, UINT8 sep, UINT8 pitch) { | ||||
| 	(void) pitch; | ||||
| 
 | ||||
| 	SdlAudioLockHandle _; | ||||
| 
 | ||||
| 	if (sound_effect_channels.empty()) | ||||
| 		return; | ||||
| 
 | ||||
| 	if (handle < 0) | ||||
| 		return; | ||||
| 
 | ||||
| 	size_t index = handle; | ||||
| 
 | ||||
| 	if (index >= sound_effect_channels.size()) | ||||
| 		return; | ||||
| 
 | ||||
| 	shared_ptr<SoundEffectPlayer>& channel = sound_effect_channels[index]; | ||||
| 	if (!channel->finished()) { | ||||
| 		float vol_float = static_cast<float>(vol) / 255.f; | ||||
| 		float sep_float = static_cast<float>(sep) / 127.f - 1.f; | ||||
| 		channel->update(vol_float, sep_float); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| void I_SetSfxVolume(int volume) { | ||||
| 	SdlAudioLockHandle _; | ||||
| 	float vol = static_cast<float>(volume) / 100.f; | ||||
| 
 | ||||
| 	if (gain_sound_effects) { | ||||
| 		gain_sound_effects->gain(vol * vol * vol); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| /// ------------------------
 | ||||
| //  MUSIC SYSTEM
 | ||||
| /// ------------------------
 | ||||
| 
 | ||||
| void I_InitMusic(void) { | ||||
| 	if (!sound_started) | ||||
| 		initialize_sound(); | ||||
| 
 | ||||
| 	SdlAudioLockHandle _; | ||||
| 
 | ||||
| 	*music_player = audio::MusicPlayer(); | ||||
| } | ||||
| 
 | ||||
| void I_ShutdownMusic(void) { | ||||
| 	SdlAudioLockHandle _; | ||||
| 
 | ||||
| 	*music_player = audio::MusicPlayer(); | ||||
| } | ||||
| 
 | ||||
| /// ------------------------
 | ||||
| //  MUSIC PROPERTIES
 | ||||
| /// ------------------------
 | ||||
| 
 | ||||
| musictype_t I_SongType(void) { | ||||
| 	if (!music_player) | ||||
| 		return MU_NONE; | ||||
| 
 | ||||
| 	SdlAudioLockHandle _; | ||||
| 
 | ||||
| 	std::optional<audio::MusicType> music_type = music_player->music_type(); | ||||
| 
 | ||||
| 	if (music_type == std::nullopt) { | ||||
| 		return MU_NONE; | ||||
| 	} | ||||
| 
 | ||||
| 	switch (*music_type) { | ||||
| 	case audio::MusicType::kOgg: | ||||
| 		return MU_OGG; | ||||
| 	case audio::MusicType::kGme: | ||||
| 		return MU_GME; | ||||
| 	case audio::MusicType::kMod: | ||||
| 		return MU_MOD; | ||||
| 	default: | ||||
| 		return MU_NONE; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| boolean I_SongPlaying(void) { | ||||
| 	if (!music_player) | ||||
| 		return false; | ||||
| 
 | ||||
| 	SdlAudioLockHandle _; | ||||
| 
 | ||||
| 	return music_player->music_type().has_value(); | ||||
| } | ||||
| 
 | ||||
| boolean I_SongPaused(void) { | ||||
| 	if (!music_player) | ||||
| 		return false; | ||||
| 
 | ||||
| 	SdlAudioLockHandle _; | ||||
| 
 | ||||
| 	return !music_player->playing(); | ||||
| } | ||||
| 
 | ||||
| /// ------------------------
 | ||||
| //  MUSIC EFFECTS
 | ||||
| /// ------------------------
 | ||||
| 
 | ||||
| boolean I_SetSongSpeed(float speed) { | ||||
| 	(void) speed; | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| /// ------------------------
 | ||||
| //  MUSIC SEEKING
 | ||||
| /// ------------------------
 | ||||
| 
 | ||||
| UINT32 I_GetSongLength(void) { | ||||
| 	if (!music_player) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	SdlAudioLockHandle _; | ||||
| 
 | ||||
| 	std::optional<float> duration = music_player->duration_seconds(); | ||||
| 
 | ||||
| 	if (!duration) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	return static_cast<UINT32>(std::round(*duration * 1000.f)); | ||||
| } | ||||
| 
 | ||||
| boolean I_SetSongLoopPoint(UINT32 looppoint) { | ||||
| 	if (!music_player) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	SdlAudioLockHandle _; | ||||
| 
 | ||||
| 	if (music_player->music_type() == audio::MusicType::kOgg) { | ||||
| 		music_player->loop_point_seconds(looppoint / 1000.f); | ||||
| 		return true; | ||||
| 	} | ||||
| 
 | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| UINT32 I_GetSongLoopPoint(void) { | ||||
| 	if (!music_player) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	SdlAudioLockHandle _; | ||||
| 
 | ||||
| 	std::optional<float> loop_point_seconds = music_player->loop_point_seconds(); | ||||
| 
 | ||||
| 	if (!loop_point_seconds) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	return static_cast<UINT32>(std::round(*loop_point_seconds * 1000.f)); | ||||
| } | ||||
| 
 | ||||
| boolean I_SetSongPosition(UINT32 position) { | ||||
| 	if (!music_player) | ||||
| 		return false; | ||||
| 
 | ||||
| 	SdlAudioLockHandle _; | ||||
| 
 | ||||
| 	music_player->seek(position / 1000.f); | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| UINT32 I_GetSongPosition(void) { | ||||
| 	if (!music_player) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	SdlAudioLockHandle _; | ||||
| 
 | ||||
| 	std::optional<float> position_seconds = music_player->position_seconds(); | ||||
| 
 | ||||
| 	if (!position_seconds) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	return static_cast<UINT32>(std::round(*position_seconds * 1000.f)); | ||||
| } | ||||
| 
 | ||||
| void I_UpdateSongLagThreshold(void) { | ||||
| } | ||||
| 
 | ||||
| void I_UpdateSongLagConditions(void) { | ||||
| } | ||||
| 
 | ||||
| /// ------------------------
 | ||||
| //  MUSIC PLAYBACK
 | ||||
| /// ------------------------
 | ||||
| 
 | ||||
| namespace { | ||||
| void print_walk_ex_stack(const std::exception& ex) { | ||||
| 	CONS_Alert(CONS_WARNING, "  Caused by: %s\n", ex.what()); | ||||
| 	try { | ||||
| 		std::rethrow_if_nested(ex); | ||||
| 	} catch (const std::exception& ex) { | ||||
| 		print_walk_ex_stack(ex); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| void print_ex(const std::exception& ex) { | ||||
| 	CONS_Alert(CONS_WARNING, "Exception loading music: %s\n", ex.what()); | ||||
| 	try { | ||||
| 		std::rethrow_if_nested(ex); | ||||
| 	} catch (const std::exception& ex) { | ||||
| 		print_walk_ex_stack(ex); | ||||
| 	} | ||||
| } | ||||
| } // namespace
 | ||||
| 
 | ||||
| boolean I_LoadSong(char* data, size_t len) { | ||||
| 	if (!music_player) | ||||
| 		return false; | ||||
| 
 | ||||
| 	tcb::span<std::byte> data_span(reinterpret_cast<std::byte*>(data), len); | ||||
| 	audio::MusicPlayer new_player; | ||||
| 	try { | ||||
| 		new_player = audio::MusicPlayer {data_span}; | ||||
| 	} catch (const std::exception& ex) { | ||||
| 		print_ex(ex); | ||||
| 		return false; | ||||
| 	} | ||||
| 
 | ||||
| 	if (music_fade_callback && music_player->fading()) { | ||||
| 		auto old_callback = music_fade_callback; | ||||
| 		music_fade_callback = nullptr; | ||||
| 		(old_callback)(); | ||||
| 	} | ||||
| 
 | ||||
| 	SdlAudioLockHandle _; | ||||
| 
 | ||||
| 	try { | ||||
| 		*music_player = std::move(new_player); | ||||
| 	} catch (const std::exception& ex) { | ||||
| 		print_ex(ex); | ||||
| 		return false; | ||||
| 	} | ||||
| 
 | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| void I_UnloadSong(void) { | ||||
| 	if (!music_player) | ||||
| 		return; | ||||
| 
 | ||||
| 	if (music_fade_callback && music_player->fading()) { | ||||
| 		auto old_callback = music_fade_callback; | ||||
| 		music_fade_callback = nullptr; | ||||
| 		(old_callback)(); | ||||
| 	} | ||||
| 
 | ||||
| 	SdlAudioLockHandle _; | ||||
| 
 | ||||
| 	*music_player = audio::MusicPlayer(); | ||||
| } | ||||
| 
 | ||||
| boolean I_PlaySong(boolean looping) { | ||||
| 	if (!music_player) | ||||
| 		return false; | ||||
| 
 | ||||
| 	SdlAudioLockHandle _; | ||||
| 
 | ||||
| 	music_player->play(looping); | ||||
| 
 | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| void I_StopSong(void) { | ||||
| 	if (!music_player) | ||||
| 		return; | ||||
| 
 | ||||
| 	SdlAudioLockHandle _; | ||||
| 
 | ||||
| 	music_player->stop(); | ||||
| } | ||||
| 
 | ||||
| void I_PauseSong(void) { | ||||
| 	if (!music_player) | ||||
| 		return; | ||||
| 
 | ||||
| 	SdlAudioLockHandle _; | ||||
| 
 | ||||
| 	music_player->pause(); | ||||
| } | ||||
| 
 | ||||
| void I_ResumeSong(void) { | ||||
| 	if (!music_player) | ||||
| 		return; | ||||
| 
 | ||||
| 	SdlAudioLockHandle _; | ||||
| 
 | ||||
| 	music_player->unpause(); | ||||
| } | ||||
| 
 | ||||
| void I_SetMusicVolume(int volume) { | ||||
| 	float vol = static_cast<float>(volume) / 100.f; | ||||
| 
 | ||||
| 	if (gain_music) { | ||||
| 		gain_music->gain(vol * vol * vol); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| boolean I_SetSongTrack(int track) { | ||||
| 	(void) track; | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| /// ------------------------
 | ||||
| //  MUSIC FADING
 | ||||
| /// ------------------------
 | ||||
| 
 | ||||
| void I_SetInternalMusicVolume(UINT8 volume) { | ||||
| 	if (!music_player) | ||||
| 		return; | ||||
| 
 | ||||
| 	SdlAudioLockHandle _; | ||||
| 
 | ||||
| 	float gain = volume / 100.f; | ||||
| 	music_player->internal_gain(gain); | ||||
| } | ||||
| 
 | ||||
| void I_StopFadingSong(void) { | ||||
| 	if (!music_player) | ||||
| 		return; | ||||
| 
 | ||||
| 	SdlAudioLockHandle _; | ||||
| 
 | ||||
| 	music_player->stop_fade(); | ||||
| } | ||||
| 
 | ||||
| boolean I_FadeSongFromVolume(UINT8 target_volume, UINT8 source_volume, UINT32 ms, void (*callback)(void)) { | ||||
| 	if (!music_player) | ||||
| 		return false; | ||||
| 
 | ||||
| 	SdlAudioLockHandle _; | ||||
| 
 | ||||
| 	float source_gain = source_volume / 100.f; | ||||
| 	float target_gain = target_volume / 100.f; | ||||
| 	float seconds = ms / 1000.f; | ||||
| 
 | ||||
| 	music_player->fade_from_to(source_gain, target_gain, seconds); | ||||
| 
 | ||||
| 	if (music_fade_callback) | ||||
| 		music_fade_callback(); | ||||
| 	music_fade_callback = callback; | ||||
| 
 | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| boolean I_FadeSong(UINT8 target_volume, UINT32 ms, void (*callback)(void)) { | ||||
| 	if (!music_player) | ||||
| 		return false; | ||||
| 
 | ||||
| 	SdlAudioLockHandle _; | ||||
| 
 | ||||
| 	float target_gain = target_volume / 100.f; | ||||
| 	float seconds = ms / 1000.f; | ||||
| 
 | ||||
| 	music_player->fade_to(target_gain, seconds); | ||||
| 
 | ||||
| 	if (music_fade_callback) | ||||
| 		music_fade_callback(); | ||||
| 	music_fade_callback = callback; | ||||
| 
 | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| static void stop_song_cb(void) { | ||||
| 	if (!music_player) | ||||
| 		return; | ||||
| 
 | ||||
| 	SdlAudioLockHandle _; | ||||
| 
 | ||||
| 	music_player->stop(); | ||||
| } | ||||
| 
 | ||||
| boolean I_FadeOutStopSong(UINT32 ms) { | ||||
| 	return I_FadeSong(0.f, ms, stop_song_cb); | ||||
| } | ||||
| 
 | ||||
| boolean I_FadeInPlaySong(UINT32 ms, boolean looping) { | ||||
| 	if (I_PlaySong(looping)) | ||||
| 		return I_FadeSongFromVolume(100, 0, ms, nullptr); | ||||
| 	else | ||||
| 		return false; | ||||
| } | ||||
							
								
								
									
										378
									
								
								thirdparty/CMakeLists.txt
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										378
									
								
								thirdparty/CMakeLists.txt
									
										
									
									
										vendored
									
									
								
							|  | @ -26,31 +26,6 @@ if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}") | |||
| 	) | ||||
| endif() | ||||
| 
 | ||||
| if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}") | ||||
| 	CPMAddPackage( | ||||
| 		NAME SDL2_mixer | ||||
| 		VERSION 2.6.2 | ||||
| 		URL "https://github.com/libsdl-org/SDL_mixer/archive/refs/tags/release-2.6.2.zip" | ||||
| 		EXCLUDE_FROM_ALL ON | ||||
| 		OPTIONS | ||||
| 			"BUILD_SHARED_LIBS ${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}" | ||||
| 			"SDL2MIXER_INSTALL OFF" | ||||
| 			"SDL2MIXER_DEPS_SHARED OFF" | ||||
| 			"SDL2MIXER_SAMPLES OFF" | ||||
| 			"SDL2MIXER_VENDORED ON" | ||||
| 			"SDL2MIXER_FLAC ON" | ||||
| 			"SDL2MIXER_FLAC_LIBFLAC OFF" | ||||
| 			"SDL2MIXER_FLAC_DRFLAC ON" | ||||
| 			"SDL2MIXER_MOD OFF" | ||||
| 			"SDL2MIXER_MP3 ON" | ||||
| 			"SDL2MIXER_MP3_DRMP3 ON" | ||||
| 			"SDL2MIXER_MIDI ON" | ||||
| 			"SDL2MIXER_OPUS OFF" | ||||
| 			"SDL2MIXER_VORBIS STB" | ||||
| 			"SDL2MIXER_WAVE ON" | ||||
| 	) | ||||
| endif() | ||||
| 
 | ||||
| if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}") | ||||
| 	CPMAddPackage( | ||||
| 		NAME ZLIB | ||||
|  | @ -221,298 +196,6 @@ if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}") | |||
| 	) | ||||
| endif() | ||||
| 
 | ||||
| if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}") | ||||
| 	CPMAddPackage( | ||||
| 		NAME openmpt | ||||
| 		VERSION 0.4.30 | ||||
| 		URL "https://github.com/OpenMPT/openmpt/archive/refs/tags/libopenmpt-0.4.30.zip" | ||||
| 		DOWNLOAD_ONLY ON | ||||
| 	) | ||||
| 
 | ||||
| 	if(openmpt_ADDED) | ||||
| 		set( | ||||
| 			openmpt_SOURCES | ||||
| 
 | ||||
| 			# minimp3 | ||||
| 			# -DMPT_WITH_MINIMP3 | ||||
| 			include/minimp3/minimp3.c | ||||
| 
 | ||||
| 			common/mptStringParse.cpp | ||||
| 			common/mptLibrary.cpp | ||||
| 			common/Logging.cpp | ||||
| 			common/Profiler.cpp | ||||
| 			common/version.cpp | ||||
| 			common/mptCPU.cpp | ||||
| 			common/ComponentManager.cpp | ||||
| 			common/mptOS.cpp | ||||
| 			common/serialization_utils.cpp | ||||
| 			common/mptStringFormat.cpp | ||||
| 			common/FileReader.cpp | ||||
| 			common/mptWine.cpp | ||||
| 			common/mptPathString.cpp | ||||
| 			common/mptAlloc.cpp | ||||
| 			common/mptUUID.cpp | ||||
| 			common/mptTime.cpp | ||||
| 			common/mptString.cpp | ||||
| 			common/mptFileIO.cpp | ||||
| 			common/mptStringBuffer.cpp | ||||
| 			common/mptRandom.cpp | ||||
| 			common/mptIO.cpp | ||||
| 			common/misc_util.cpp | ||||
| 
 | ||||
| 			common/mptCRC.h | ||||
| 			common/mptLibrary.h | ||||
| 			common/mptIO.h | ||||
| 			common/version.h | ||||
| 			common/stdafx.h | ||||
| 			common/ComponentManager.h | ||||
| 			common/Endianness.h | ||||
| 			common/mptStringFormat.h | ||||
| 			common/mptMutex.h | ||||
| 			common/mptUUID.h | ||||
| 			common/mptExceptionText.h | ||||
| 			common/BuildSettings.h | ||||
| 			common/mptAlloc.h | ||||
| 			common/mptTime.h | ||||
| 			common/FileReaderFwd.h | ||||
| 			common/Logging.h | ||||
| 			common/mptException.h | ||||
| 			common/mptWine.h | ||||
| 			common/mptStringBuffer.h | ||||
| 			common/misc_util.h | ||||
| 			common/mptBaseMacros.h | ||||
| 			common/mptMemory.h | ||||
| 			common/mptFileIO.h | ||||
| 			common/serialization_utils.h | ||||
| 			common/mptSpan.h | ||||
| 			common/mptThread.h | ||||
| 			common/FlagSet.h | ||||
| 			common/mptString.h | ||||
| 			common/mptStringParse.h | ||||
| 			common/mptBaseUtils.h | ||||
| 			common/mptRandom.h | ||||
| 			common/CompilerDetect.h | ||||
| 			common/FileReader.h | ||||
| 			common/mptAssert.h | ||||
| 			common/mptPathString.h | ||||
| 			common/Profiler.h | ||||
| 			common/mptOS.h | ||||
| 			common/mptBaseTypes.h | ||||
| 			common/mptCPU.h | ||||
| 			common/mptBufferIO.h | ||||
| 			common/versionNumber.h | ||||
| 
 | ||||
| 			soundlib/WAVTools.cpp | ||||
| 			soundlib/ITTools.cpp | ||||
| 			soundlib/AudioCriticalSection.cpp | ||||
| 			soundlib/Load_stm.cpp | ||||
| 			soundlib/MixerLoops.cpp | ||||
| 			soundlib/Load_dbm.cpp | ||||
| 			soundlib/ModChannel.cpp | ||||
| 			soundlib/Load_gdm.cpp | ||||
| 			soundlib/Snd_fx.cpp | ||||
| 			soundlib/Load_mid.cpp | ||||
| 			soundlib/mod_specifications.cpp | ||||
| 			soundlib/Snd_flt.cpp | ||||
| 			soundlib/Load_psm.cpp | ||||
| 			soundlib/Load_far.cpp | ||||
| 			soundlib/patternContainer.cpp | ||||
| 			soundlib/Load_med.cpp | ||||
| 			soundlib/Load_dmf.cpp | ||||
| 			soundlib/Paula.cpp | ||||
| 			soundlib/modcommand.cpp | ||||
| 			soundlib/Message.cpp | ||||
| 			soundlib/SoundFilePlayConfig.cpp | ||||
| 			soundlib/Load_uax.cpp | ||||
| 			soundlib/plugins/PlugInterface.cpp | ||||
| 			soundlib/plugins/LFOPlugin.cpp | ||||
| 			soundlib/plugins/PluginManager.cpp | ||||
| 			soundlib/plugins/DigiBoosterEcho.cpp | ||||
| 			soundlib/plugins/dmo/DMOPlugin.cpp | ||||
| 			soundlib/plugins/dmo/Flanger.cpp | ||||
| 			soundlib/plugins/dmo/Distortion.cpp | ||||
| 			soundlib/plugins/dmo/ParamEq.cpp | ||||
| 			soundlib/plugins/dmo/Gargle.cpp | ||||
| 			soundlib/plugins/dmo/I3DL2Reverb.cpp | ||||
| 			soundlib/plugins/dmo/Compressor.cpp | ||||
| 			soundlib/plugins/dmo/WavesReverb.cpp | ||||
| 			soundlib/plugins/dmo/Echo.cpp | ||||
| 			soundlib/plugins/dmo/Chorus.cpp | ||||
| 			soundlib/Load_ams.cpp | ||||
| 			soundlib/tuningbase.cpp | ||||
| 			soundlib/ContainerUMX.cpp | ||||
| 			soundlib/Load_ptm.cpp | ||||
| 			soundlib/ContainerXPK.cpp | ||||
| 			soundlib/SampleFormatMP3.cpp | ||||
| 			soundlib/tuning.cpp | ||||
| 			soundlib/Sndfile.cpp | ||||
| 			soundlib/ContainerMMCMP.cpp | ||||
| 			soundlib/Load_amf.cpp | ||||
| 			soundlib/Load_669.cpp | ||||
| 			soundlib/modsmp_ctrl.cpp | ||||
| 			soundlib/Load_mtm.cpp | ||||
| 			soundlib/OggStream.cpp | ||||
| 			soundlib/Load_plm.cpp | ||||
| 			soundlib/Tables.cpp | ||||
| 			soundlib/Load_c67.cpp | ||||
| 			soundlib/Load_mod.cpp | ||||
| 			soundlib/Load_sfx.cpp | ||||
| 			soundlib/Sndmix.cpp | ||||
| 			soundlib/load_j2b.cpp | ||||
| 			soundlib/ModSequence.cpp | ||||
| 			soundlib/SampleFormatFLAC.cpp | ||||
| 			soundlib/ModInstrument.cpp | ||||
| 			soundlib/Load_mo3.cpp | ||||
| 			soundlib/ModSample.cpp | ||||
| 			soundlib/Dlsbank.cpp | ||||
| 			soundlib/Load_itp.cpp | ||||
| 			soundlib/UpgradeModule.cpp | ||||
| 			soundlib/MIDIMacros.cpp | ||||
| 			soundlib/ContainerPP20.cpp | ||||
| 			soundlib/RowVisitor.cpp | ||||
| 			soundlib/Load_imf.cpp | ||||
| 			soundlib/SampleFormatVorbis.cpp | ||||
| 			soundlib/Load_dsm.cpp | ||||
| 			soundlib/Load_mt2.cpp | ||||
| 			soundlib/MixerSettings.cpp | ||||
| 			soundlib/S3MTools.cpp | ||||
| 			soundlib/Load_xm.cpp | ||||
| 			soundlib/MIDIEvents.cpp | ||||
| 			soundlib/pattern.cpp | ||||
| 			soundlib/Load_digi.cpp | ||||
| 			soundlib/Load_s3m.cpp | ||||
| 			soundlib/tuningCollection.cpp | ||||
| 			soundlib/SampleIO.cpp | ||||
| 			soundlib/Dither.cpp | ||||
| 			soundlib/Load_mdl.cpp | ||||
| 			soundlib/OPL.cpp | ||||
| 			soundlib/WindowedFIR.cpp | ||||
| 			soundlib/SampleFormats.cpp | ||||
| 			soundlib/Load_wav.cpp | ||||
| 			soundlib/Load_it.cpp | ||||
| 			soundlib/UMXTools.cpp | ||||
| 			soundlib/Load_stp.cpp | ||||
| 			soundlib/Load_okt.cpp | ||||
| 			soundlib/Load_ult.cpp | ||||
| 			soundlib/MixFuncTable.cpp | ||||
| 			soundlib/SampleFormatOpus.cpp | ||||
| 			soundlib/Fastmix.cpp | ||||
| 			soundlib/Tagging.cpp | ||||
| 			soundlib/ITCompression.cpp | ||||
| 			soundlib/Load_dtm.cpp | ||||
| 			soundlib/MPEGFrame.cpp | ||||
| 			soundlib/XMTools.cpp | ||||
| 			soundlib/SampleFormatMediaFoundation.cpp | ||||
| 			soundlib/InstrumentExtensions.cpp | ||||
| 
 | ||||
| 			soundlib/MixerInterface.h | ||||
| 			soundlib/SoundFilePlayConfig.h | ||||
| 			soundlib/ModSample.h | ||||
| 			soundlib/MIDIEvents.h | ||||
| 			soundlib/ModSampleCopy.h | ||||
| 			soundlib/patternContainer.h | ||||
| 			soundlib/ChunkReader.h | ||||
| 			soundlib/ITCompression.h | ||||
| 			soundlib/Dither.h | ||||
| 			soundlib/S3MTools.h | ||||
| 			soundlib/MPEGFrame.h | ||||
| 			soundlib/WAVTools.h | ||||
| 			soundlib/mod_specifications.h | ||||
| 			soundlib/ITTools.h | ||||
| 			soundlib/RowVisitor.h | ||||
| 			soundlib/plugins/PluginMixBuffer.h | ||||
| 			soundlib/plugins/PluginStructs.h | ||||
| 			soundlib/plugins/LFOPlugin.h | ||||
| 			soundlib/plugins/PlugInterface.h | ||||
| 			soundlib/plugins/DigiBoosterEcho.h | ||||
| 			soundlib/plugins/OpCodes.h | ||||
| 			soundlib/plugins/dmo/Echo.h | ||||
| 			soundlib/plugins/dmo/I3DL2Reverb.h | ||||
| 			soundlib/plugins/dmo/WavesReverb.h | ||||
| 			soundlib/plugins/dmo/ParamEq.h | ||||
| 			soundlib/plugins/dmo/Gargle.h | ||||
| 			soundlib/plugins/dmo/DMOPlugin.h | ||||
| 			soundlib/plugins/dmo/Chorus.h | ||||
| 			soundlib/plugins/dmo/Compressor.h | ||||
| 			soundlib/plugins/dmo/Distortion.h | ||||
| 			soundlib/plugins/dmo/Flanger.h | ||||
| 			soundlib/plugins/PluginManager.h | ||||
| 			soundlib/SampleIO.h | ||||
| 			soundlib/Container.h | ||||
| 			soundlib/ModSequence.h | ||||
| 			soundlib/UMXTools.h | ||||
| 			soundlib/Message.h | ||||
| 			soundlib/modcommand.h | ||||
| 			soundlib/XMTools.h | ||||
| 			soundlib/Snd_defs.h | ||||
| 			soundlib/MixFuncTable.h | ||||
| 			soundlib/pattern.h | ||||
| 			soundlib/modsmp_ctrl.h | ||||
| 			soundlib/Tagging.h | ||||
| 			soundlib/tuningcollection.h | ||||
| 			soundlib/Mixer.h | ||||
| 			soundlib/FloatMixer.h | ||||
| 			soundlib/AudioCriticalSection.h | ||||
| 			soundlib/Tables.h | ||||
| 			soundlib/tuningbase.h | ||||
| 			soundlib/WindowedFIR.h | ||||
| 			soundlib/Sndfile.h | ||||
| 			soundlib/Paula.h | ||||
| 			soundlib/ModInstrument.h | ||||
| 			soundlib/Dlsbank.h | ||||
| 			soundlib/IntMixer.h | ||||
| 			soundlib/OPL.h | ||||
| 			soundlib/Resampler.h | ||||
| 			soundlib/ModChannel.h | ||||
| 			soundlib/MixerSettings.h | ||||
| 			soundlib/AudioReadTarget.h | ||||
| 			soundlib/MixerLoops.h | ||||
| 			soundlib/tuning.h | ||||
| 			soundlib/MIDIMacros.h | ||||
| 			soundlib/OggStream.h | ||||
| 			soundlib/Loaders.h | ||||
| 			soundlib/BitReader.h | ||||
| 			soundlib/opal.h | ||||
| 
 | ||||
| 			sounddsp/AGC.cpp | ||||
| 			sounddsp/EQ.cpp | ||||
| 			sounddsp/DSP.cpp | ||||
| 			sounddsp/Reverb.cpp | ||||
| 			sounddsp/Reverb.h | ||||
| 			sounddsp/EQ.h | ||||
| 			sounddsp/DSP.h | ||||
| 			sounddsp/AGC.h | ||||
| 
 | ||||
| 			libopenmpt/libopenmpt_c.cpp | ||||
| 			libopenmpt/libopenmpt_cxx.cpp | ||||
| 			libopenmpt/libopenmpt_impl.cpp | ||||
| 			libopenmpt/libopenmpt_ext_impl.cpp | ||||
| 		) | ||||
| 		list(TRANSFORM openmpt_SOURCES PREPEND "${openmpt_SOURCE_DIR}/") | ||||
| 
 | ||||
| 		# -DLIBOPENMPT_BUILD | ||||
| 		configure_file("openmpt_svn_version.h" "svn_version.h") | ||||
| 		add_library(openmpt "${SRB2_INTERNAL_LIBRARY_TYPE}" ${openmpt_SOURCES} ${CMAKE_CURRENT_BINARY_DIR}/svn_version.h) | ||||
| 		if("${CMAKE_C_COMPILER_ID}" STREQUAL GNU OR "${CMAKE_C_COMPILER_ID}" STREQUAL Clang OR "${CMAKE_C_COMPILER_ID}" STREQUAL AppleClang) | ||||
| 			target_compile_options(openmpt PRIVATE "-g0") | ||||
| 		endif() | ||||
| 		if("${CMAKE_SYSTEM_NAME}" STREQUAL Windows AND "${CMAKE_C_COMPILER_ID}" STREQUAL MSVC) | ||||
| 			target_link_libraries(openmpt PRIVATE Rpcrt4) | ||||
| 		endif() | ||||
| 		target_compile_features(openmpt PRIVATE cxx_std_11) | ||||
| 		target_compile_definitions(openmpt PRIVATE -DLIBOPENMPT_BUILD) | ||||
| 
 | ||||
| 		target_include_directories(openmpt PRIVATE "${openmpt_SOURCE_DIR}/common") | ||||
| 		target_include_directories(openmpt PRIVATE "${openmpt_SOURCE_DIR}/src") | ||||
| 		target_include_directories(openmpt PRIVATE "${openmpt_SOURCE_DIR}/include") | ||||
| 		target_include_directories(openmpt PRIVATE "${openmpt_SOURCE_DIR}") | ||||
| 		target_include_directories(openmpt PRIVATE "${CMAKE_CURRENT_BINARY_DIR}") | ||||
| 
 | ||||
| 		# I wish this wasn't necessary, but it is | ||||
| 		target_include_directories(openmpt PUBLIC "${openmpt_SOURCE_DIR}") | ||||
| 	endif() | ||||
| endif() | ||||
| 
 | ||||
| if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}") | ||||
| 	CPMAddPackage( | ||||
| 		NAME libgme | ||||
|  | @ -605,4 +288,65 @@ if(DiscordRPC_ADDED) | |||
| 	endif() | ||||
| endif() | ||||
| 
 | ||||
| CPMAddPackage( | ||||
| 	NAME xmp-lite | ||||
| 	VERSION 4.5.0 | ||||
| 	URL "https://github.com/libxmp/libxmp/releases/download/libxmp-4.5.0/libxmp-lite-4.5.0.tar.gz" | ||||
| 	EXCLUDE_FROM_ALL ON | ||||
| 	DOWNLOAD_ONLY ON | ||||
| ) | ||||
| if(xmp-lite_ADDED) | ||||
| 	set(xmp_sources | ||||
| 		virtual.c | ||||
| 		format.c | ||||
| 		period.c | ||||
| 		player.c | ||||
| 		read_event.c | ||||
| 		misc.c | ||||
| 		dataio.c | ||||
| 		lfo.c | ||||
| 		scan.c | ||||
| 		control.c | ||||
| 		filter.c | ||||
| 		effects.c | ||||
| 		mixer.c | ||||
| 		mix_all.c | ||||
| 		load_helpers.c | ||||
| 		load.c | ||||
| 		hio.c | ||||
| 		smix.c | ||||
| 		memio.c | ||||
| 		win32.c | ||||
| 
 | ||||
| 		loaders/common.c | ||||
| 		loaders/itsex.c | ||||
| 		loaders/sample.c | ||||
| 		loaders/xm_load.c | ||||
| 		loaders/mod_load.c | ||||
| 		loaders/s3m_load.c | ||||
| 		loaders/it_load.c | ||||
| 	) | ||||
| 	list(TRANSFORM xmp_sources PREPEND "${xmp-lite_SOURCE_DIR}/src/") | ||||
| 
 | ||||
| 	add_library(xmp-lite "${SRB2_INTERNAL_LIBRARY_TYPE}" ${xmp_sources}) | ||||
| 
 | ||||
| 	target_compile_definitions(xmp-lite PRIVATE -D_REENTRANT -DLIBXMP_CORE_PLAYER -DLIBXMP_NO_PROWIZARD -DLIBXMP_NO_DEPACKERS) | ||||
| 	if("${SRB2_INTERNAL_LIBRARY_TYPE}" STREQUAL "STATIC") | ||||
| 		if(WIN32) | ||||
| 			# BUILDING_STATIC has to be public to work around a bug in xmp.h | ||||
| 			# which adds __declspec(dllimport) even when statically linking | ||||
| 			target_compile_definitions(xmp-lite PUBLIC -DBUILDING_STATIC) | ||||
| 		else() | ||||
| 			target_compile_definitions(xmp-lite PRIVATE -DBUILDING_STATIC) | ||||
| 		endif() | ||||
| 	else() | ||||
| 		target_compile_definitions(xmp-lite PRIVATE -DBUILDING_DLL) | ||||
| 	endif() | ||||
| 	target_include_directories(xmp-lite PRIVATE "${xmp-lite_SOURCE_DIR}/src") | ||||
| 	target_include_directories(xmp-lite PUBLIC "${xmp-lite_SOURCE_DIR}/include/libxmp-lite") | ||||
| 
 | ||||
| 	add_library(xmp-lite::xmp-lite ALIAS xmp-lite) | ||||
| endif() | ||||
| 
 | ||||
| add_subdirectory(tcbrindle_span) | ||||
| add_subdirectory(stb_vorbis) | ||||
|  |  | |||
							
								
								
									
										10
									
								
								thirdparty/openmpt_svn_version.h
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								thirdparty/openmpt_svn_version.h
									
										
									
									
										vendored
									
									
								
							|  | @ -1,10 +0,0 @@ | |||
| 
 | ||||
| #pragma once | ||||
| #define OPENMPT_VERSION_SVNVERSION "17963" | ||||
| #define OPENMPT_VERSION_REVISION 17963 | ||||
| #define OPENMPT_VERSION_DIRTY 0 | ||||
| #define OPENMPT_VERSION_MIXEDREVISIONS 0 | ||||
| #define OPENMPT_VERSION_URL "https://source.openmpt.org/svn/openmpt/tags/libopenmpt-0.4.32"
 | ||||
| #define OPENMPT_VERSION_DATE "2022-09-25T14:19:05.052596Z" | ||||
| #define OPENMPT_VERSION_IS_PACKAGE 1 | ||||
| 
 | ||||
							
								
								
									
										4
									
								
								thirdparty/stb_vorbis/CMakeLists.txt
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								thirdparty/stb_vorbis/CMakeLists.txt
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,4 @@ | |||
| # Update from https://github.com/nothings/stb | ||||
| # This doesn't use CPM because stb_vorbis.c has a weird header setup | ||||
| add_library(stb_vorbis STATIC stb_vorbis.c include/stb_vorbis.h) | ||||
| target_include_directories(stb_vorbis PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include") | ||||
							
								
								
									
										2
									
								
								thirdparty/stb_vorbis/include/stb_vorbis.h
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								thirdparty/stb_vorbis/include/stb_vorbis.h
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,2 @@ | |||
| #define STB_VORBIS_HEADER_ONLY | ||||
| #include "../stb_vorbis.c" | ||||
							
								
								
									
										5584
									
								
								thirdparty/stb_vorbis/stb_vorbis.c
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										5584
									
								
								thirdparty/stb_vorbis/stb_vorbis.c
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 Eidolon
						Eidolon