Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/ci-linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ jobs:
libogg-dev \
libopus-dev \
libopusfile-dev \
libsoxr-dev \
${{ inputs.libraries }} \
zlib1g-dev \
# EOF
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/ci-mingw.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ jobs:
mingw-w64-${{ inputs.arch }}-libogg
mingw-w64-${{ inputs.arch }}-opus
mingw-w64-${{ inputs.arch }}-opusfile
mingw-w64-${{ inputs.arch }}-libsoxr

- name: Install OpenGFX
shell: bash
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ jobs:
libopus-dev \
libopusfile-dev \
libsdl2-dev \
libsoxr-dev \
zlib1g-dev \
# EOF

Expand Down
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ if(NOT OPTION_DEDICATED)
endif()
endif()
find_package(OpusFile)
find_package(Soxr)
endif()
if(APPLE)
enable_language(OBJCXX)
Expand Down Expand Up @@ -326,6 +327,7 @@ if(NOT OPTION_DEDICATED)
link_package(ICU_i18n)
link_package(ICU_uc)
link_package(OpusFile TARGET OpusFile::opusfile)
link_package(Soxr)

if(SDL2_FOUND AND OPENGL_FOUND AND UNIX)
# SDL2 dynamically loads OpenGL if needed, so do not link to OpenGL when
Expand Down
32 changes: 32 additions & 0 deletions cmake/FindSoxr.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
include(FindPackageHandleStandardArgs)

find_library(Soxr_LIBRARY
NAMES soxr
)

set(Soxr_COMPILE_OPTIONS "" CACHE STRING "Extra compile options of soxr")

set(Soxr_LINK_LIBRARIES "" CACHE STRING "Extra link libraries of soxr")

set(Soxr_LINK_FLAGS "" CACHE STRING "Extra link flags of soxr")

find_path(Soxr_INCLUDE_PATH
NAMES soxr.h
)

find_package_handle_standard_args(Soxr
REQUIRED_VARS Soxr_LIBRARY Soxr_INCLUDE_PATH
)

if(Soxr_FOUND)
if(NOT TARGET Soxr::soxr)
add_library(Soxr::soxr UNKNOWN IMPORTED)
set_target_properties(Soxr::soxr PROPERTIES
IMPORTED_LOCATION "${Soxr_LIBRARY}"
INTERFACE_INCLUDE_DIRECTORIES "${Soxr_INCLUDE_PATH}"
INTERFACE_COMPILE_OPTIONS "${Soxr_COMPILE_OPTIONS}"
INTERFACE_LINK_LIBRARIES "${Soxr_LINK_LIBRARIES}"
INTERFACE_LINK_FLAGS "${Soxr_LINK_FLAGS}"
)
endif()
endif()
6 changes: 6 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ add_files(
CONDITION OpusFile_FOUND
)

add_files(
soundresampler_soxr.cpp
CONDITION Soxr_FOUND
)

add_files(
aircraft.h
aircraft_cmd.cpp
Expand Down Expand Up @@ -448,6 +453,7 @@ add_files(
soundloader_type.h
soundloader_raw.cpp
soundloader_wav.cpp
soundresampler_type.h
source_type.h
sprite.cpp
sprite.h
Expand Down
5 changes: 5 additions & 0 deletions src/mixer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,11 @@ bool MxInitialize(uint rate)
return true;
}

uint32_t MxGetRate()
{
return _play_rate;
}

void SetEffectVolume(uint8_t volume)
{
_effect_vol.store(volume, std::memory_order_relaxed);
Expand Down
1 change: 1 addition & 0 deletions src/mixer.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ struct MixerChannel;
typedef void(*MxStreamCallback)(int16_t *buffer, size_t samples);

bool MxInitialize(uint rate);
uint32_t MxGetRate();
void MxMixSamples(void *buffer, uint samples);

MixerChannel *MxAllocateChannel();
Expand Down
11 changes: 11 additions & 0 deletions src/soundloader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,16 @@
#include "sound_type.h"
#include "soundloader_type.h"
#include "soundloader_func.h"
#include "soundresampler_type.h"
#include "string_func.h"
#include "mixer.h"
#include "newgrf_sound.h"
#include "random_access_file_type.h"

#include "safeguards.h"

template class ProviderManager<SoundLoader>;
template class ProviderManager<SoundResampler>;

bool LoadSoundData(SoundEntry &sound, bool new_format, SoundID sound_id, const std::string &name)
{
Expand All @@ -43,6 +46,14 @@ bool LoadSoundData(SoundEntry &sound, bool new_format, SoundID sound_id, const s

Debug(grf, 2, "LoadSound [{}]: channels {}, sample rate {}, bits per sample {}, length {}", sound.file->GetSimplifiedFilename(), sound.channels, sound.rate, sound.bits_per_sample, sound.file_size);

/* Convert sample rate if needed. */
const uint32_t play_rate = MxGetRate();
if (play_rate != sound.rate) {
for (auto &resampler : ProviderManager<SoundResampler>::GetProviders()) {
if (resampler->Resample(sound, play_rate)) break;
}
}

/* Mixer always requires an extra sample at the end for the built-in linear resampler. */
sound.data->resize(sound.data->size() + sound.channels * sound.bits_per_sample / 8);
sound.data->shrink_to_fit();
Expand Down
91 changes: 91 additions & 0 deletions src/soundresampler_soxr.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* This file is part of OpenTTD.
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
*/

/** @file soundresampler_soxr.cpp SOXR sound resampler. */

#include "stdafx.h"
#include "core/math_func.hpp"
#include "debug.h"
#include "sound_type.h"
#include "soundresampler_type.h"

#include <soxr.h>
#include <thread>

#include "safeguards.h"

class SoundResampler_Soxr : public SoundResampler {
public:
SoundResampler_Soxr() : SoundResampler("soxr", "SOXR sound resampler", 0) {}

/**
* Convert samples from 8 bits to 16 bits.
* @param in Vector of samples to convert.
* @param out Vector to place converted samples.
* @pre out vector must be exactly twice the size of in vector.
*/
static void ConvertInt8toInt16(std::vector<std::byte> &in, std::vector<std::byte> &out)
{
assert(std::size(out) == std::size(in) * 2);

auto out_it = std::begin(out);
for (const std::byte &value : in) {
if constexpr (std::endian::native != std::endian::little) {
*out_it++ = value;
*out_it++ = std::byte{0};
} else {
*out_it++ = std::byte{0};
*out_it++ = value;
}
}
}

bool Resample(SoundEntry &sound, uint32_t play_rate) override
{
/* The sound data to work on. */
std::vector<std::byte> &data = *sound.data;

std::vector<std::byte> tmp;
if (sound.bits_per_sample == 16) {
/* No conversion necessary so just move from sound data to temporary buffer. */
data.swap(tmp);
} else {
/* SoxR cannot resample 8-bit audio, so convert from 8-bit to 16-bit into temporary buffer. */
tmp.resize(std::size(data) * sizeof(int16_t));
ConvertInt8toInt16(data, tmp);
sound.bits_per_sample = 16;
}

/* Resize buffer ensuring it is correctly aligned. */
uint align = sound.channels * sound.bits_per_sample / 8;
data.resize(Align(std::size(tmp) * play_rate / sound.rate, align));

soxr_error_t error = soxr_oneshot(sound.rate, play_rate, sound.channels,
std::data(tmp), std::size(tmp) / align, nullptr,
std::data(data), std::size(data) / align, nullptr,
&this->io, &this->quality, &this->runtime);

if (error != nullptr) {
/* Could not resample, try using the original data as-is without resampling instead. */
Debug(misc, 0, "Failed to resample: {}", soxr_strerror(error));
data.swap(tmp);
} else {
sound.rate = play_rate;
}

return true;
}

private:
static SoundResampler_Soxr instance;

const soxr_io_spec_t io = soxr_io_spec(SOXR_INT16_I, SOXR_INT16_I); // 16-bit input and output.
const soxr_quality_spec_t quality = soxr_quality_spec(SOXR_VHQ, 0); // Use 'Very high quality'.
const soxr_runtime_spec_t runtime = soxr_runtime_spec(std::thread::hardware_concurrency()); // Enable multi-threading.
};

/* static */ SoundResampler_Soxr SoundResampler_Soxr::instance{};
32 changes: 32 additions & 0 deletions src/soundresampler_type.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* This file is part of OpenTTD.
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
*/

/** @file soundresampler_type.h Types related to sound resamplers. */

#ifndef SOUNDRESAMPLER_TYPE_H
#define SOUNDRESAMPLER_TYPE_H

#include "provider_manager.h"
#include "sound_type.h"

/** Base interface for a SoundResampler implementation. */
class SoundResampler : public PriorityBaseProvider<SoundResampler> {
public:
SoundResampler(std::string_view name, std::string_view description, int priority) : PriorityBaseProvider<SoundResampler>(name, description, priority)
{
ProviderManager<SoundResampler>::Register(*this);
}

virtual ~SoundResampler()
{
ProviderManager<SoundResampler>::Unregister(*this);
}

virtual bool Resample(SoundEntry &sound, uint32_t play_rate) = 0;
};

#endif /* SOUNDRESAMPLER_TYPE_H */
3 changes: 2 additions & 1 deletion src/vehicle_cmd.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -849,7 +849,8 @@ std::tuple<CommandCost, VehicleID> CmdCloneVehicle(DoCommandFlags flags, TileInd
CommandCost ret = CheckOwnership(v->owner);
if (ret.Failed()) return { ret, VehicleID::Invalid() };

if (v->type == VEH_TRAIN && (!v->IsFrontEngine() || Train::From(v)->crash_anim_pos >= 4400)) return { CMD_ERROR, VehicleID::Invalid() };
/* Crashed trains can only be cloned before cleanup begins. */
if (v->type == VEH_TRAIN && (!v->IsFrontEngine() || Train::From(v)->crash_anim_pos >= 4400)) return { CommandCost(STR_ERROR_VEHICLE_IS_DESTROYED), VehicleID::Invalid() };

/* check that we can allocate enough vehicles */
if (!flags.Test(DoCommandFlag::Execute)) {
Expand Down
3 changes: 3 additions & 0 deletions vcpkg.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@
{
"name": "opusfile"
},
{
"name": "soxr"
},
{
"name": "sdl2",
"platform": "linux"
Expand Down
Loading