mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-21 23:20:20 +00:00
LibMedia+everywhere: Remove superfluous and unused audio code
We had numerous NiH-based implementations of audio formats and metadata that we now no longer need because we either don't make use of the code, or we replaced its implementation by FFmpeg.
This commit is contained in:
parent
57783eff24
commit
233b4f2ca8
Notes:
github-actions[bot]
2024-09-30 16:49:08 +00:00
Author: https://github.com/gmta Commit: https://github.com/LadybirdBrowser/ladybird/commit/233b4f2ca88 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/1575
46 changed files with 12 additions and 6530 deletions
|
@ -10,14 +10,6 @@
|
|||
# cmakedefine01 AUDIO_DEBUG
|
||||
#endif
|
||||
|
||||
#ifndef AWAVLOADER_DEBUG
|
||||
# cmakedefine01 AWAVLOADER_DEBUG
|
||||
#endif
|
||||
|
||||
#ifndef AFLACLOADER_DEBUG
|
||||
# cmakedefine01 AFLACLOADER_DEBUG
|
||||
#endif
|
||||
|
||||
#ifndef BMP_DEBUG
|
||||
# cmakedefine01 BMP_DEBUG
|
||||
#endif
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include <AK/Endian.h>
|
||||
#include <AK/MemoryStream.h>
|
||||
#include <AK/Optional.h>
|
||||
#include <AK/Time.h>
|
||||
#include <AK/Types.h>
|
||||
#include <LibCore/SharedCircularQueue.h>
|
||||
#include <LibMedia/Audio/Loader.h>
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
set(AUDIO_DEBUG ON)
|
||||
set(AWAVLOADER_DEBUG ON)
|
||||
set(AFLACLOADER_DEBUG ON)
|
||||
set(BMP_DEBUG ON)
|
||||
set(CACHE_DEBUG ON)
|
||||
set(CALLBACK_MACHINE_DEBUG ON)
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2023, kleines Filmröllchen <filmroellchen@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "AudioFuzzerCommon.h"
|
||||
#include <LibMedia/Audio/FlacLoader.h>
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size)
|
||||
{
|
||||
AK::set_debug_enabled(false);
|
||||
return fuzz_audio_loader<Audio::FlacLoaderPlugin>(data, size);
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2023, kleines Filmröllchen <filmroellchen@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "AudioFuzzerCommon.h"
|
||||
#include <LibMedia/Audio/MP3Loader.h>
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size)
|
||||
{
|
||||
AK::set_debug_enabled(false);
|
||||
return fuzz_audio_loader<Audio::MP3LoaderPlugin>(data, size);
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2023, kleines Filmröllchen <filmroellchen@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "AudioFuzzerCommon.h"
|
||||
#include <LibMedia/Audio/QOALoader.h>
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size)
|
||||
{
|
||||
AK::set_debug_enabled(false);
|
||||
return fuzz_audio_loader<Audio::QOALoaderPlugin>(data, size);
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2023, kleines Filmröllchen <filmroellchen@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "AudioFuzzerCommon.h"
|
||||
#include <LibMedia/Audio/WavLoader.h>
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size)
|
||||
{
|
||||
AK::set_debug_enabled(false);
|
||||
return fuzz_audio_loader<Audio::WavLoaderPlugin>(data, size);
|
||||
}
|
|
@ -6,7 +6,6 @@ set(FUZZER_TARGETS
|
|||
Brotli
|
||||
DeflateCompression
|
||||
DeflateDecompression
|
||||
FlacLoader
|
||||
GIFLoader
|
||||
GzipDecompression
|
||||
GzipRoundtrip
|
||||
|
@ -19,11 +18,9 @@ set(FUZZER_TARGETS
|
|||
LzmaRoundtrip
|
||||
MatroskaReader
|
||||
MD5
|
||||
MP3Loader
|
||||
PEM
|
||||
PNGLoader
|
||||
Poly1305
|
||||
QOALoader
|
||||
RegexECMA262
|
||||
RegexPosixBasic
|
||||
RegexPosixExtended
|
||||
|
@ -38,7 +35,6 @@ set(FUZZER_TARGETS
|
|||
TinyVGLoader
|
||||
URL
|
||||
WasmParser
|
||||
WAVLoader
|
||||
WebPLoader
|
||||
WOFF
|
||||
WOFF2
|
||||
|
@ -59,7 +55,6 @@ set(FUZZER_DEPENDENCIES_CSSParser LibWeb)
|
|||
set(FUZZER_DEPENDENCIES_DeflateCompression LibCompress)
|
||||
set(FUZZER_DEPENDENCIES_DeflateDecompression LibCompress)
|
||||
set(FUZZER_DEPENDENCIES_ELF LibELF)
|
||||
set(FUZZER_DEPENDENCIES_FlacLoader LibMedia)
|
||||
set(FUZZER_DEPENDENCIES_GIFLoader LibGfx)
|
||||
set(FUZZER_DEPENDENCIES_GzipDecompression LibCompress)
|
||||
set(FUZZER_DEPENDENCIES_GzipRoundtrip LibCompress)
|
||||
|
@ -71,11 +66,9 @@ set(FUZZER_DEPENDENCIES_LzmaDecompression LibArchive LibCompress)
|
|||
set(FUZZER_DEPENDENCIES_LzmaRoundtrip LibCompress)
|
||||
set(FUZZER_DEPENDENCIES_MatroskaReader LibMedia)
|
||||
set(FUZZER_DEPENDENCIES_MD5 LibCrypto)
|
||||
set(FUZZER_DEPENDENCIES_MP3Loader LibMedia)
|
||||
set(FUZZER_DEPENDENCIES_PEM LibCrypto)
|
||||
set(FUZZER_DEPENDENCIES_PNGLoader LibGfx)
|
||||
set(FUZZER_DEPENDENCIES_Poly1305 LibCrypto)
|
||||
set(FUZZER_DEPENDENCIES_QOALoader LibMedia)
|
||||
set(FUZZER_DEPENDENCIES_RegexECMA262 LibRegex)
|
||||
set(FUZZER_DEPENDENCIES_RegexPosixBasic LibRegex)
|
||||
set(FUZZER_DEPENDENCIES_RegexPosixExtended LibRegex)
|
||||
|
@ -91,7 +84,6 @@ set(FUZZER_DEPENDENCIES_TTF LibGfx)
|
|||
set(FUZZER_DEPENDENCIES_TinyVGLoader LibGfx)
|
||||
set(FUZZER_DEPENDENCIES_URL LibURL)
|
||||
set(FUZZER_DEPENDENCIES_WasmParser LibWasm)
|
||||
set(FUZZER_DEPENDENCIES_WAVLoader LibMedia)
|
||||
set(FUZZER_DEPENDENCIES_WebPLoader LibGfx)
|
||||
set(FUZZER_DEPENDENCIES_WOFF LibGfx)
|
||||
set(FUZZER_DEPENDENCIES_WOFF2 LibGfx)
|
||||
|
|
|
@ -220,8 +220,6 @@ write_cmake_config("ak_debug_gen") {
|
|||
output = "$root_gen_dir/AK/Debug.h"
|
||||
values = [
|
||||
"AUDIO_DEBUG=",
|
||||
"AWAVLOADER_DEBUG=",
|
||||
"AFLACLOADER_DEBUG=",
|
||||
"BINDINGS_GENERATOR_DEBUG=",
|
||||
"BMP_DEBUG=",
|
||||
"CACHE_DEBUG=",
|
||||
|
|
|
@ -4,17 +4,9 @@ import("//Meta/gn/build/libs/pulse/enable.gni")
|
|||
shared_library("LibMedia") {
|
||||
include_dirs = [ "//Userland/Libraries" ]
|
||||
sources = [
|
||||
"Audio/FlacLoader.cpp",
|
||||
"Audio/GenericTypes.cpp",
|
||||
"Audio/Loader.cpp",
|
||||
"Audio/MP3Loader.cpp",
|
||||
"Audio/Metadata.cpp",
|
||||
"Audio/PlaybackStream.cpp",
|
||||
"Audio/QOALoader.cpp",
|
||||
"Audio/QOATypes.cpp",
|
||||
"Audio/SampleFormats.cpp",
|
||||
"Audio/VorbisComment.cpp",
|
||||
"Audio/WavLoader.cpp",
|
||||
"Color/ColorConverter.cpp",
|
||||
"Color/ColorPrimaries.cpp",
|
||||
"Color/TransferCharacteristics.cpp",
|
||||
|
|
|
@ -11,9 +11,6 @@ foreach(source IN LISTS TEST_SOURCES)
|
|||
lagom_test("${source}" LibMedia LIBS LibMedia LibFileSystem WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
endforeach()
|
||||
|
||||
# The FLAC tests need a special working directory to find the test files
|
||||
lagom_test(TestFLACSpec.cpp LIBS LibMedia WORKING_DIRECTORY "${FLAC_TEST_PATH}/..")
|
||||
|
||||
if (HAVE_PULSEAUDIO)
|
||||
target_compile_definitions(TestPlaybackStream PRIVATE HAVE_PULSEAUDIO=1)
|
||||
endif()
|
||||
|
|
|
@ -1,58 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2022, kleines Filmröllchen <filmroellchen@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/LexicalPath.h>
|
||||
#include <LibCore/Directory.h>
|
||||
#include <LibMedia/Audio/FlacLoader.h>
|
||||
#include <LibTest/TestCase.h>
|
||||
|
||||
struct DiscoverFLACTestsHack {
|
||||
DiscoverFLACTestsHack()
|
||||
{
|
||||
// FIXME: Also run (our own) tests in this directory.
|
||||
(void)Core::Directory::for_each_entry("./FLAC/SpecTests"sv, Core::DirIterator::Flags::SkipParentAndBaseDir, [](auto const& entry, auto const& directory) -> ErrorOr<IterationDecision> {
|
||||
auto path = LexicalPath::join(directory.path().string(), entry.name);
|
||||
if (path.extension() == "flac"sv) {
|
||||
Test::add_test_case_to_suite(adopt_ref(*new ::Test::TestCase(
|
||||
ByteString::formatted("flac_spec_test_{}", path.basename()),
|
||||
[path = move(path)]() {
|
||||
auto file = Core::File::open(path.string(), Core::File::OpenMode::Read);
|
||||
if (file.is_error()) {
|
||||
FAIL(ByteString::formatted("{}", file.error()));
|
||||
return;
|
||||
}
|
||||
auto buffered_file = Core::InputBufferedFile::create(file.release_value());
|
||||
if (buffered_file.is_error()) {
|
||||
FAIL(ByteString::formatted("{}", buffered_file.error()));
|
||||
return;
|
||||
}
|
||||
auto result = Audio::FlacLoaderPlugin::create(buffered_file.release_value());
|
||||
if (result.is_error()) {
|
||||
FAIL(ByteString::formatted("{}", result.error()));
|
||||
return;
|
||||
}
|
||||
|
||||
auto loader = result.release_value();
|
||||
|
||||
while (true) {
|
||||
auto maybe_samples = loader->load_chunks(2 * MiB);
|
||||
if (maybe_samples.is_error()) {
|
||||
FAIL(ByteString::formatted("{}", maybe_samples.error()));
|
||||
return;
|
||||
}
|
||||
maybe_samples.value().remove_all_matching([](auto& chunk) { return chunk.is_empty(); });
|
||||
if (maybe_samples.value().is_empty())
|
||||
return;
|
||||
}
|
||||
},
|
||||
false)));
|
||||
}
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
}
|
||||
};
|
||||
// Hack taken from TEST_CASE; the above constructor will run as part of global initialization before the tests are actually executed
|
||||
static struct DiscoverFLACTestsHack hack;
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
#include <AK/ByteString.h>
|
||||
#include <LibMedia/Audio/WavLoader.h>
|
||||
#include <LibMedia/Audio/Loader.h>
|
||||
#include <LibTest/TestCase.h>
|
||||
|
||||
static void run_test(StringView file_name, int const num_samples, int const channels, u32 const rate)
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,119 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2021, kleines Filmröllchen <filmroellchen@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "FlacTypes.h"
|
||||
#include "Loader.h"
|
||||
#include <AK/BitStream.h>
|
||||
#include <AK/Error.h>
|
||||
#include <AK/Span.h>
|
||||
#include <AK/Types.h>
|
||||
|
||||
namespace Audio {
|
||||
|
||||
ALWAYS_INLINE u8 frame_channel_type_to_channel_count(FlacFrameChannelType channel_type);
|
||||
// Sign-extend an arbitrary-size signed number to 64 bit signed
|
||||
ALWAYS_INLINE i64 sign_extend(u32 n, u8 size);
|
||||
// Decodes the sign representation method used in Rice coding.
|
||||
// Numbers alternate between positive and negative: 0, 1, -1, 2, -2, 3, -3, 4, -4, 5, -5, ...
|
||||
ALWAYS_INLINE i32 rice_to_signed(u32 x);
|
||||
|
||||
// decoders
|
||||
// read a UTF-8 encoded number, even if it is not a valid codepoint
|
||||
ALWAYS_INLINE ErrorOr<u64> read_utf8_char(BigEndianInputBitStream& input);
|
||||
// decode a single number encoded with exponential golomb encoding of the specified order
|
||||
ALWAYS_INLINE ErrorOr<i32> decode_unsigned_exp_golomb(u8 order, BigEndianInputBitStream& bit_input);
|
||||
|
||||
// Loader for the Free Lossless Audio Codec (FLAC)
|
||||
// This loader supports all audio features of FLAC, although audio from more than two channels is discarded.
|
||||
// The loader currently supports the STREAMINFO, PADDING, and SEEKTABLE metadata blocks.
|
||||
// See: https://xiph.org/flac/documentation_format_overview.html
|
||||
// https://xiph.org/flac/format.html (identical to IETF draft version 2)
|
||||
// https://datatracker.ietf.org/doc/html/draft-ietf-cellar-flac-02 (all section numbers refer to this specification)
|
||||
// https://datatracker.ietf.org/doc/html/draft-ietf-cellar-flac-03 (newer IETF draft that uses incompatible numberings and names)
|
||||
class FlacLoaderPlugin : public LoaderPlugin {
|
||||
public:
|
||||
explicit FlacLoaderPlugin(NonnullOwnPtr<SeekableStream> stream);
|
||||
virtual ~FlacLoaderPlugin() override = default;
|
||||
|
||||
static bool sniff(SeekableStream& stream);
|
||||
static ErrorOr<NonnullOwnPtr<LoaderPlugin>, LoaderError> create(NonnullOwnPtr<SeekableStream>);
|
||||
|
||||
virtual ErrorOr<Vector<FixedArray<Sample>>, LoaderError> load_chunks(size_t samples_to_read_from_input) override;
|
||||
|
||||
virtual MaybeLoaderError reset() override;
|
||||
virtual MaybeLoaderError seek(int sample_index) override;
|
||||
|
||||
virtual int loaded_samples() override { return static_cast<int>(m_loaded_samples); }
|
||||
virtual int total_samples() override { return static_cast<int>(m_total_samples); }
|
||||
virtual u32 sample_rate() override { return m_sample_rate; }
|
||||
virtual u16 num_channels() override { return m_num_channels; }
|
||||
virtual ByteString format_name() override { return "FLAC (.flac)"; }
|
||||
virtual PcmSampleFormat pcm_format() override { return m_sample_format; }
|
||||
|
||||
bool is_fixed_blocksize_stream() const { return m_min_block_size == m_max_block_size; }
|
||||
bool sample_count_unknown() const { return m_total_samples == 0; }
|
||||
|
||||
private:
|
||||
MaybeLoaderError initialize();
|
||||
MaybeLoaderError parse_header();
|
||||
// Either returns the metadata block or sets error message.
|
||||
// Additionally, increments m_data_start_location past the read meta block.
|
||||
ErrorOr<FlacRawMetadataBlock, LoaderError> next_meta_block(BigEndianInputBitStream& bit_input);
|
||||
// Fetches and returns the next FLAC frame.
|
||||
LoaderSamples next_frame();
|
||||
// Helper of next_frame that fetches a sub frame's header
|
||||
ErrorOr<FlacSubframeHeader, LoaderError> next_subframe_header(BigEndianInputBitStream& bit_input, u8 channel_index);
|
||||
// Helper of next_frame that decompresses a subframe
|
||||
ErrorOr<void, LoaderError> parse_subframe(Vector<i64>& samples, FlacSubframeHeader& subframe_header, BigEndianInputBitStream& bit_input);
|
||||
// Subframe-internal data decoders (heavy lifting)
|
||||
ErrorOr<Vector<i64>, LoaderError> decode_fixed_lpc(FlacSubframeHeader& subframe, BigEndianInputBitStream& bit_input);
|
||||
ErrorOr<Vector<i64>, LoaderError> decode_verbatim(FlacSubframeHeader& subframe, BigEndianInputBitStream& bit_input);
|
||||
ErrorOr<void, LoaderError> decode_custom_lpc(Vector<i64>& decoded, FlacSubframeHeader& subframe, BigEndianInputBitStream& bit_input);
|
||||
MaybeLoaderError decode_residual(Vector<i64>& decoded, FlacSubframeHeader& subframe, BigEndianInputBitStream& bit_input);
|
||||
// decode a single rice partition that has its own rice parameter
|
||||
ALWAYS_INLINE ErrorOr<Vector<i64>, LoaderError> decode_rice_partition(u8 partition_type, u32 partitions, u32 partition_index, FlacSubframeHeader& subframe, BigEndianInputBitStream& bit_input);
|
||||
MaybeLoaderError load_seektable(FlacRawMetadataBlock&);
|
||||
// Note that failing to read a Vorbis comment block is not treated as an error of the FLAC loader, since metadata is optional.
|
||||
void load_vorbis_comment(FlacRawMetadataBlock&);
|
||||
MaybeLoaderError load_picture(FlacRawMetadataBlock&);
|
||||
|
||||
// Converters for special coding used in frame headers
|
||||
ALWAYS_INLINE ErrorOr<u32, LoaderError> convert_sample_count_code(u8 sample_count_code);
|
||||
ALWAYS_INLINE ErrorOr<u32, LoaderError> convert_sample_rate_code(u8 sample_rate_code);
|
||||
ALWAYS_INLINE ErrorOr<u8, LoaderError> convert_bit_depth_code(u8 bit_depth_code);
|
||||
|
||||
bool should_insert_seekpoint_at(u64 sample_index) const;
|
||||
|
||||
// Data obtained directly from the FLAC metadata: many values have specific bit counts
|
||||
u32 m_sample_rate { 0 }; // 20 bit
|
||||
u8 m_num_channels { 0 }; // 3 bit
|
||||
u8 m_bits_per_sample { 0 }; // 5 bits for the integer bit depth
|
||||
// Externally visible format; the smallest integer format that's larger than the precise bit depth.
|
||||
PcmSampleFormat m_sample_format;
|
||||
// Blocks are units of decoded audio data
|
||||
u16 m_min_block_size { 0 };
|
||||
u16 m_max_block_size { 0 };
|
||||
// Frames are units of encoded audio data, both of these are 24-bit
|
||||
u32 m_min_frame_size { 0 }; // 24 bit
|
||||
u32 m_max_frame_size { 0 }; // 24 bit
|
||||
u64 m_total_samples { 0 }; // 36 bit
|
||||
u8 m_md5_checksum[128 / 8]; // 128 bit (!)
|
||||
size_t m_loaded_samples { 0 };
|
||||
|
||||
// keep track of the start of the data in the FLAC stream to seek back more easily
|
||||
u64 m_data_start_location { 0 };
|
||||
Optional<FlacFrameHeader> m_current_frame;
|
||||
u64 m_current_sample_or_frame { 0 };
|
||||
SeekTable m_seektable;
|
||||
|
||||
// Keep around a few temporary buffers whose allocated space can be reused.
|
||||
// This is an empirical optimization since allocations and deallocations take a lot of time in the decoder.
|
||||
mutable Vector<Vector<i64>, 2> m_subframe_buffers;
|
||||
};
|
||||
|
||||
}
|
|
@ -1,164 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2021, kleines Filmröllchen <filmroellchen@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "SampleFormats.h"
|
||||
#include <AK/ByteBuffer.h>
|
||||
#include <AK/Types.h>
|
||||
#include <AK/Variant.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibCrypto/Checksum/CRC16.h>
|
||||
#include <LibCrypto/Checksum/CRC8.h>
|
||||
|
||||
namespace Audio {
|
||||
|
||||
// These are not the actual values stored in the file! They are marker constants instead, only used temporarily in the decoder.
|
||||
// 11.22.3. INTERCHANNEL SAMPLE BLOCK SIZE
|
||||
#define FLAC_BLOCKSIZE_AT_END_OF_HEADER_8 0xffffffff
|
||||
#define FLAC_BLOCKSIZE_AT_END_OF_HEADER_16 0xfffffffe
|
||||
// 11.22.4. SAMPLE RATE
|
||||
#define FLAC_SAMPLERATE_AT_END_OF_HEADER_8 0xffffffff
|
||||
#define FLAC_SAMPLERATE_AT_END_OF_HEADER_16 0xfffffffe
|
||||
#define FLAC_SAMPLERATE_AT_END_OF_HEADER_16X10 0xfffffffd
|
||||
|
||||
constexpr StringView flac_magic = "fLaC"sv;
|
||||
|
||||
// 11.22.11. FRAME CRC
|
||||
// The polynomial used here is known as CRC-8-CCITT.
|
||||
static constexpr u8 flac_polynomial = 0x07;
|
||||
using FlacFrameHeaderCRC = Crypto::Checksum::CRC8<flac_polynomial>;
|
||||
|
||||
// 11.23. FRAME_FOOTER
|
||||
// The polynomial used here is known as CRC-16-IBM.
|
||||
static constexpr u16 ibm_polynomial = 0xA001;
|
||||
using IBMCRC = Crypto::Checksum::CRC16<ibm_polynomial>;
|
||||
|
||||
static constexpr size_t flac_seekpoint_size = (64 + 64 + 16) / 8;
|
||||
|
||||
// 11.8 BLOCK_TYPE (7 bits)
|
||||
enum class FlacMetadataBlockType : u8 {
|
||||
STREAMINFO = 0, // Important data about the audio format
|
||||
PADDING = 1, // Non-data block to be ignored
|
||||
APPLICATION = 2, // Ignored
|
||||
SEEKTABLE = 3, // Seeking info, maybe to be used later
|
||||
VORBIS_COMMENT = 4, // Ignored
|
||||
CUESHEET = 5, // Ignored
|
||||
PICTURE = 6, // Ignored
|
||||
INVALID = 127, // Error
|
||||
};
|
||||
|
||||
// 11.22.5. CHANNEL ASSIGNMENT
|
||||
enum class FlacFrameChannelType : u8 {
|
||||
Mono = 0,
|
||||
Stereo = 1,
|
||||
StereoCenter = 2, // left, right, center
|
||||
Surround4p0 = 3, // front left/right, back left/right
|
||||
Surround5p0 = 4, // front left/right, center, back left/right
|
||||
Surround5p1 = 5, // front left/right, center, LFE, back left/right
|
||||
Surround6p1 = 6, // front left/right, center, LFE, back center, side left/right
|
||||
Surround7p1 = 7, // front left/right, center, LFE, back left/right, side left/right
|
||||
LeftSideStereo = 8, // channel coupling: left and difference
|
||||
RightSideStereo = 9, // channel coupling: difference and right
|
||||
MidSideStereo = 10, // channel coupling: center and difference
|
||||
// others are reserved
|
||||
};
|
||||
|
||||
// 11.25.1. SUBFRAME TYPE
|
||||
enum class FlacSubframeType : u8 {
|
||||
Constant = 0,
|
||||
Verbatim = 1,
|
||||
Fixed = 0b001000,
|
||||
LPC = 0b100000,
|
||||
// others are reserved
|
||||
};
|
||||
|
||||
// 11.30.1. RESIDUAL_CODING_METHOD
|
||||
enum class FlacResidualMode : u8 {
|
||||
Rice4Bit = 0,
|
||||
Rice5Bit = 1,
|
||||
};
|
||||
|
||||
// 11.6. METADATA_BLOCK
|
||||
struct FlacRawMetadataBlock {
|
||||
bool is_last_block;
|
||||
FlacMetadataBlockType type;
|
||||
u32 length; // 24 bits
|
||||
ByteBuffer data;
|
||||
|
||||
ErrorOr<void> write_to_stream(Stream&) const;
|
||||
};
|
||||
|
||||
enum class BlockingStrategy : u8 {
|
||||
Fixed = 0,
|
||||
Variable = 1,
|
||||
};
|
||||
|
||||
// Block sample count can be stored in one of 5 ways.
|
||||
enum class BlockSizeCategory : u8 {
|
||||
Reserved = 0b0000,
|
||||
S192 = 0b0001,
|
||||
// The formula for these four is 144 * (2^x), and it appears to be an MP3 compatibility feature.
|
||||
S576 = 0b0010,
|
||||
S1152 = 0b0011,
|
||||
S2304 = 0b0100,
|
||||
S4608 = 0b0101,
|
||||
// Actual size is stored later on.
|
||||
Uncommon8Bits = 0b0110,
|
||||
Uncommon16Bits = 0b0111,
|
||||
// Formula 2^x.
|
||||
S256 = 0b1000,
|
||||
S512 = 0b1001,
|
||||
S1024 = 0b1010,
|
||||
S2048 = 0b1011,
|
||||
S4096 = 0b1100,
|
||||
S8192 = 0b1101,
|
||||
S16384 = 0b1110,
|
||||
S32768 = 0b1111,
|
||||
};
|
||||
|
||||
// 11.22. FRAME_HEADER
|
||||
struct FlacFrameHeader {
|
||||
u32 sample_rate;
|
||||
// Referred to as “block size” in the specification.
|
||||
u16 sample_count;
|
||||
// If blocking strategy is fixed, this encodes the frame index instead of the sample index.
|
||||
u32 sample_or_frame_index;
|
||||
BlockingStrategy blocking_strategy;
|
||||
FlacFrameChannelType channels;
|
||||
u8 bit_depth;
|
||||
u8 checksum;
|
||||
|
||||
ErrorOr<void> write_to_stream(Stream&) const;
|
||||
};
|
||||
|
||||
// 11.25. SUBFRAME_HEADER
|
||||
struct FlacSubframeHeader {
|
||||
FlacSubframeType type;
|
||||
// order for fixed and LPC subframes
|
||||
u8 order;
|
||||
u8 wasted_bits_per_sample;
|
||||
u8 bits_per_sample;
|
||||
};
|
||||
|
||||
enum class FlacFixedLPC : size_t {
|
||||
Zero = 0,
|
||||
One = 1,
|
||||
Two = 2,
|
||||
Three = 3,
|
||||
Four = 4,
|
||||
};
|
||||
|
||||
struct FlacLPCEncodedSubframe {
|
||||
Vector<i64> warm_up_samples;
|
||||
Variant<Vector<i64>, FlacFixedLPC> coefficients;
|
||||
Vector<i64> residuals;
|
||||
size_t residual_cost_bits;
|
||||
// If we’re only using one Rice partition, this is the optimal order to use.
|
||||
u8 single_partition_optimal_order;
|
||||
};
|
||||
|
||||
}
|
|
@ -10,12 +10,7 @@ namespace Audio {
|
|||
|
||||
class ConnectionToServer;
|
||||
class Loader;
|
||||
struct Person;
|
||||
struct Metadata;
|
||||
class PlaybackStream;
|
||||
struct Sample;
|
||||
|
||||
template<typename SampleType>
|
||||
class ResampleHelper;
|
||||
|
||||
}
|
||||
|
|
|
@ -1,95 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2023, kleines Filmröllchen <filmroellchen@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "GenericTypes.h"
|
||||
#include <AK/BinarySearch.h>
|
||||
#include <AK/Error.h>
|
||||
#include <AK/Optional.h>
|
||||
|
||||
namespace Audio {
|
||||
|
||||
size_t SeekTable::size() const
|
||||
{
|
||||
return m_seek_points.size();
|
||||
}
|
||||
|
||||
ReadonlySpan<SeekPoint> SeekTable::seek_points() const
|
||||
{
|
||||
return m_seek_points.span();
|
||||
}
|
||||
|
||||
Vector<SeekPoint>& SeekTable::seek_points()
|
||||
{
|
||||
return m_seek_points;
|
||||
}
|
||||
|
||||
Optional<SeekPoint const&> SeekTable::seek_point_before(u64 sample_index) const
|
||||
{
|
||||
if (m_seek_points.is_empty())
|
||||
return {};
|
||||
size_t nearby_seek_point_index = 0;
|
||||
AK::binary_search(m_seek_points, sample_index, &nearby_seek_point_index, [](auto const& sample_index, auto const& seekpoint_candidate) {
|
||||
// Subtraction with i64 cast may cause overflow.
|
||||
if (sample_index > seekpoint_candidate.sample_index)
|
||||
return 1;
|
||||
if (sample_index == seekpoint_candidate.sample_index)
|
||||
return 0;
|
||||
return -1;
|
||||
});
|
||||
// Binary search will always give us a close index, but it may be too large or too small.
|
||||
// By doing the index adjustment in this order, we will always find a seek point before the given sample.
|
||||
while (nearby_seek_point_index < m_seek_points.size() - 1 && m_seek_points[nearby_seek_point_index].sample_index < sample_index)
|
||||
++nearby_seek_point_index;
|
||||
while (nearby_seek_point_index > 0 && m_seek_points[nearby_seek_point_index].sample_index > sample_index)
|
||||
--nearby_seek_point_index;
|
||||
if (m_seek_points[nearby_seek_point_index].sample_index > sample_index)
|
||||
return {};
|
||||
return m_seek_points[nearby_seek_point_index];
|
||||
}
|
||||
|
||||
Optional<u64> SeekTable::seek_point_sample_distance_around(u64 sample_index) const
|
||||
{
|
||||
if (m_seek_points.is_empty())
|
||||
return {};
|
||||
size_t nearby_seek_point_index = 0;
|
||||
AK::binary_search(m_seek_points, sample_index, &nearby_seek_point_index, [](auto const& sample_index, auto const& seekpoint_candidate) {
|
||||
// Subtraction with i64 cast may cause overflow.
|
||||
if (sample_index > seekpoint_candidate.sample_index)
|
||||
return 1;
|
||||
if (sample_index == seekpoint_candidate.sample_index)
|
||||
return 0;
|
||||
return -1;
|
||||
});
|
||||
|
||||
while (nearby_seek_point_index < m_seek_points.size() && m_seek_points[nearby_seek_point_index].sample_index <= sample_index)
|
||||
++nearby_seek_point_index;
|
||||
// There is no seek point beyond the sample index.
|
||||
if (nearby_seek_point_index >= m_seek_points.size())
|
||||
return {};
|
||||
auto upper_seek_point_index = nearby_seek_point_index;
|
||||
|
||||
while (nearby_seek_point_index > 0 && m_seek_points[nearby_seek_point_index].sample_index > sample_index)
|
||||
--nearby_seek_point_index;
|
||||
auto lower_seek_point_index = nearby_seek_point_index;
|
||||
|
||||
VERIFY(upper_seek_point_index >= lower_seek_point_index);
|
||||
return m_seek_points[upper_seek_point_index].sample_index - m_seek_points[lower_seek_point_index].sample_index;
|
||||
}
|
||||
|
||||
ErrorOr<void> SeekTable::insert_seek_point(SeekPoint seek_point)
|
||||
{
|
||||
if (auto previous_seek_point = seek_point_before(seek_point.sample_index); previous_seek_point.has_value() && previous_seek_point->sample_index == seek_point.sample_index) {
|
||||
// Do not insert a duplicate seek point.
|
||||
return {};
|
||||
}
|
||||
|
||||
// FIXME: This could be even faster if we used binary search while finding the insertion point.
|
||||
return m_seek_points.try_insert_before_matching(seek_point, [&](auto const& other_seek_point) {
|
||||
return seek_point.sample_index < other_seek_point.sample_index;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -1,79 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Lucas Chollet <lucas.chollet@free.fr>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/String.h>
|
||||
#include <AK/Vector.h>
|
||||
|
||||
namespace Audio {
|
||||
|
||||
// 11.20. PICTURE_TYPE (in Flac specification)
|
||||
enum class ID3PictureType : u32 {
|
||||
Other = 0,
|
||||
FileIcon = 1,
|
||||
OtherFileIcon = 2,
|
||||
FrontCover = 3,
|
||||
BackCover = 4,
|
||||
LeafletPage = 5,
|
||||
Media = 6,
|
||||
LeadArtist = 7,
|
||||
Artist = 8,
|
||||
Conductor = 9,
|
||||
Band = 10,
|
||||
Composer = 11,
|
||||
Lyricist = 12,
|
||||
RecordingLocation = 13,
|
||||
DuringRecording = 14,
|
||||
DuringPerformance = 15,
|
||||
MovieScreenCapture = 16,
|
||||
BrightColouredFish = 17,
|
||||
Illustration = 18,
|
||||
BandLogoType = 19,
|
||||
PublisherLogoType = 20,
|
||||
// others are reserved
|
||||
};
|
||||
|
||||
// Note: This was first implemented for Flac but is compatible with ID3v2
|
||||
struct PictureData {
|
||||
ID3PictureType type {};
|
||||
String mime_string {};
|
||||
String description_string {};
|
||||
|
||||
u32 width {};
|
||||
u32 height {};
|
||||
u32 color_depth {};
|
||||
u32 colors {};
|
||||
|
||||
Vector<u8> data;
|
||||
};
|
||||
|
||||
// A generic sample seek point within a file.
|
||||
struct SeekPoint {
|
||||
u64 sample_index;
|
||||
u64 byte_offset;
|
||||
};
|
||||
|
||||
class SeekTable {
|
||||
public:
|
||||
Optional<SeekPoint const&> seek_point_before(u64 sample_index) const;
|
||||
// Returns the distance between the closest two seek points around the sample index.
|
||||
// The lower seek point may be exactly at the sample index, but the upper seek point must be after the sample index.
|
||||
Optional<u64> seek_point_sample_distance_around(u64 sample_index) const;
|
||||
|
||||
size_t size() const;
|
||||
ReadonlySpan<SeekPoint> seek_points() const;
|
||||
Vector<SeekPoint>& seek_points();
|
||||
|
||||
ErrorOr<void> insert_seek_point(SeekPoint);
|
||||
|
||||
private:
|
||||
// Invariant: The list of seek points is always sorted.
|
||||
// This makes all operations, such as inserting and searching, faster.
|
||||
Vector<SeekPoint> m_seek_points;
|
||||
};
|
||||
|
||||
}
|
|
@ -7,10 +7,6 @@
|
|||
|
||||
#include "Loader.h"
|
||||
#include "FFmpegLoader.h"
|
||||
#include "FlacLoader.h"
|
||||
#include "MP3Loader.h"
|
||||
#include "QOALoader.h"
|
||||
#include "WavLoader.h"
|
||||
#include <AK/TypedTransfer.h>
|
||||
#include <LibCore/MappedFile.h>
|
||||
|
||||
|
@ -32,13 +28,9 @@ struct LoaderPluginInitializer {
|
|||
};
|
||||
|
||||
static constexpr LoaderPluginInitializer s_initializers[] = {
|
||||
{ FlacLoaderPlugin::sniff, FlacLoaderPlugin::create },
|
||||
{ QOALoaderPlugin::sniff, QOALoaderPlugin::create },
|
||||
#ifdef USE_FFMPEG
|
||||
{ FFmpegLoaderPlugin::sniff, FFmpegLoaderPlugin::create },
|
||||
#endif
|
||||
{ WavLoaderPlugin::sniff, WavLoaderPlugin::create },
|
||||
{ MP3LoaderPlugin::sniff, MP3LoaderPlugin::create },
|
||||
};
|
||||
|
||||
ErrorOr<NonnullRefPtr<Loader>, LoaderError> Loader::create(StringView path)
|
||||
|
|
|
@ -6,9 +6,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "GenericTypes.h"
|
||||
#include "LoaderError.h"
|
||||
#include "Metadata.h"
|
||||
#include "Sample.h"
|
||||
#include "SampleFormats.h"
|
||||
#include <AK/Error.h>
|
||||
|
@ -16,11 +14,9 @@
|
|||
#include <AK/NonnullOwnPtr.h>
|
||||
#include <AK/NonnullRefPtr.h>
|
||||
#include <AK/RefCounted.h>
|
||||
#include <AK/RefPtr.h>
|
||||
#include <AK/Span.h>
|
||||
#include <AK/Stream.h>
|
||||
#include <AK/StringView.h>
|
||||
#include <AK/Try.h>
|
||||
#include <AK/Vector.h>
|
||||
|
||||
namespace Audio {
|
||||
|
||||
|
@ -73,14 +69,8 @@ public:
|
|||
virtual ByteString format_name() = 0;
|
||||
virtual PcmSampleFormat pcm_format() = 0;
|
||||
|
||||
Metadata const& metadata() const { return m_metadata; }
|
||||
Vector<PictureData> const& pictures() const { return m_pictures; }
|
||||
|
||||
protected:
|
||||
NonnullOwnPtr<SeekableStream> m_stream;
|
||||
|
||||
Vector<PictureData> m_pictures;
|
||||
Metadata m_metadata;
|
||||
};
|
||||
|
||||
class Loader : public RefCounted<Loader> {
|
||||
|
@ -110,8 +100,6 @@ public:
|
|||
ByteString format_name() const { return m_plugin->format_name(); }
|
||||
u16 bits_per_sample() const { return pcm_bits_per_sample(m_plugin->pcm_format()); }
|
||||
PcmSampleFormat pcm_format() const { return m_plugin->pcm_format(); }
|
||||
Metadata const& metadata() const { return m_plugin->metadata(); }
|
||||
Vector<PictureData> const& pictures() const { return m_plugin->pictures(); }
|
||||
|
||||
private:
|
||||
static ErrorOr<NonnullOwnPtr<LoaderPlugin>, LoaderError> create_plugin(NonnullOwnPtr<SeekableStream> stream);
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Arne Elster <arne@elster.li>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Array.h>
|
||||
#include <AK/Math.h>
|
||||
#include <AK/Span.h>
|
||||
|
||||
namespace DSP {
|
||||
|
||||
template<size_t N>
|
||||
requires(N % 2 == 0) class MDCT {
|
||||
public:
|
||||
constexpr MDCT()
|
||||
{
|
||||
for (size_t n = 0; n < N; n++) {
|
||||
for (size_t k = 0; k < N / 2; k++) {
|
||||
m_phi[n][k] = AK::cos<float>(AK::Pi<float> / (2 * N) * (2 * static_cast<float>(n) + 1 + N / 2.0f) * static_cast<float>(2 * k + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void transform(ReadonlySpan<float> data, Span<float> output)
|
||||
{
|
||||
VERIFY(N == 2 * data.size());
|
||||
VERIFY(N == output.size());
|
||||
for (size_t n = 0; n < N; n++) {
|
||||
output[n] = 0;
|
||||
for (size_t k = 0; k < N / 2; k++) {
|
||||
output[n] += data[k] * m_phi[n][k];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
Array<Array<float, N / 2>, N> m_phi;
|
||||
};
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,895 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Arne Elster <arne@elster.li>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "MP3Loader.h"
|
||||
#include "MP3HuffmanTables.h"
|
||||
#include "MP3Tables.h"
|
||||
#include "MP3Types.h"
|
||||
#include <AK/Endian.h>
|
||||
#include <AK/FixedArray.h>
|
||||
#include <LibCore/File.h>
|
||||
|
||||
namespace Audio {
|
||||
|
||||
DSP::MDCT<12> MP3LoaderPlugin::s_mdct_12;
|
||||
DSP::MDCT<36> MP3LoaderPlugin::s_mdct_36;
|
||||
|
||||
MP3LoaderPlugin::MP3LoaderPlugin(NonnullOwnPtr<SeekableStream> stream)
|
||||
: LoaderPlugin(move(stream))
|
||||
{
|
||||
}
|
||||
|
||||
MaybeLoaderError MP3LoaderPlugin::skip_id3(SeekableStream& stream)
|
||||
{
|
||||
// FIXME: This is a bit of a hack until we have a proper ID3 reader and MP3 demuxer.
|
||||
// Based on https://mutagen-specs.readthedocs.io/en/latest/id3/id3v2.2.html
|
||||
char identifier_buffer[3] = { 0, 0, 0 };
|
||||
auto read_identifier = StringView(TRY(stream.read_some({ &identifier_buffer[0], sizeof(identifier_buffer) })));
|
||||
if (read_identifier == "ID3"sv) {
|
||||
[[maybe_unused]] auto version = TRY(stream.read_value<u8>());
|
||||
[[maybe_unused]] auto revision = TRY(stream.read_value<u8>());
|
||||
[[maybe_unused]] auto flags = TRY(stream.read_value<u8>());
|
||||
auto size = 0;
|
||||
for (auto i = 0; i < 4; i++) {
|
||||
// Each byte has a zeroed most significant bit to prevent it from looking like a sync code.
|
||||
auto byte = TRY(stream.read_value<u8>());
|
||||
size <<= 7;
|
||||
size |= byte & 0x7F;
|
||||
}
|
||||
TRY(stream.seek(size, SeekMode::FromCurrentPosition));
|
||||
} else if (read_identifier != "TAG"sv) {
|
||||
MUST(stream.seek(-static_cast<int>(read_identifier.length()), SeekMode::FromCurrentPosition));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
bool MP3LoaderPlugin::sniff(SeekableStream& stream)
|
||||
{
|
||||
auto skip_id3_result = skip_id3(stream);
|
||||
if (skip_id3_result.is_error())
|
||||
return false;
|
||||
return !synchronize_and_read_header(stream, 0).is_error();
|
||||
}
|
||||
|
||||
ErrorOr<NonnullOwnPtr<LoaderPlugin>, LoaderError> MP3LoaderPlugin::create(NonnullOwnPtr<SeekableStream> stream)
|
||||
{
|
||||
auto loader = make<MP3LoaderPlugin>(move(stream));
|
||||
TRY(loader->initialize());
|
||||
return loader;
|
||||
}
|
||||
|
||||
MaybeLoaderError MP3LoaderPlugin::initialize()
|
||||
{
|
||||
TRY(build_seek_table());
|
||||
|
||||
TRY(seek(0));
|
||||
auto header = TRY(synchronize_and_read_header());
|
||||
|
||||
m_sample_rate = header.samplerate;
|
||||
m_num_channels = header.channel_count();
|
||||
m_loaded_samples = 0;
|
||||
|
||||
TRY(seek(0));
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
MaybeLoaderError MP3LoaderPlugin::reset()
|
||||
{
|
||||
TRY(seek(0));
|
||||
m_synthesis_buffer = {};
|
||||
m_loaded_samples = 0;
|
||||
TRY(m_bit_reservoir.discard(m_bit_reservoir.used_buffer_size()));
|
||||
return {};
|
||||
}
|
||||
|
||||
MaybeLoaderError MP3LoaderPlugin::seek(int const position)
|
||||
{
|
||||
auto seek_entry = m_seek_table.seek_point_before(position);
|
||||
if (seek_entry.has_value()) {
|
||||
TRY(m_stream->seek(seek_entry->byte_offset, SeekMode::SetPosition));
|
||||
m_loaded_samples = seek_entry->sample_index;
|
||||
}
|
||||
m_synthesis_buffer = {};
|
||||
TRY(m_bit_reservoir.discard(m_bit_reservoir.used_buffer_size()));
|
||||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<Vector<FixedArray<Sample>>, LoaderError> MP3LoaderPlugin::load_chunks(size_t samples_to_read_from_input)
|
||||
{
|
||||
int samples_to_read = samples_to_read_from_input;
|
||||
Vector<FixedArray<Sample>> frames;
|
||||
while (samples_to_read > 0) {
|
||||
FixedArray<Sample> samples = TRY(FixedArray<Sample>::create(MP3::frame_size));
|
||||
|
||||
auto maybe_frame = read_next_frame();
|
||||
if (maybe_frame.is_error()) {
|
||||
if (m_stream->is_eof())
|
||||
return Vector<FixedArray<Sample>> {};
|
||||
return maybe_frame.release_error();
|
||||
}
|
||||
auto frame = maybe_frame.release_value();
|
||||
|
||||
bool const is_stereo = frame.header.channel_count() == 2;
|
||||
size_t current_frame_read = 0;
|
||||
for (; current_frame_read < MP3::granule_size; current_frame_read++) {
|
||||
auto const left_sample = frame.channels[0].granules[0].pcm[current_frame_read / 32][current_frame_read % 32];
|
||||
auto const right_sample = is_stereo ? frame.channels[1].granules[0].pcm[current_frame_read / 32][current_frame_read % 32] : left_sample;
|
||||
samples[current_frame_read] = Sample { left_sample, right_sample };
|
||||
samples_to_read--;
|
||||
}
|
||||
for (; current_frame_read < MP3::frame_size; current_frame_read++) {
|
||||
auto const left_sample = frame.channels[0].granules[1].pcm[(current_frame_read - MP3::granule_size) / 32][(current_frame_read - MP3::granule_size) % 32];
|
||||
auto const right_sample = is_stereo ? frame.channels[1].granules[1].pcm[(current_frame_read - MP3::granule_size) / 32][(current_frame_read - MP3::granule_size) % 32] : left_sample;
|
||||
samples[current_frame_read] = Sample { left_sample, right_sample };
|
||||
samples_to_read--;
|
||||
}
|
||||
m_loaded_samples += samples.size();
|
||||
TRY(frames.try_append(move(samples)));
|
||||
}
|
||||
|
||||
return frames;
|
||||
}
|
||||
|
||||
MaybeLoaderError MP3LoaderPlugin::build_seek_table()
|
||||
{
|
||||
VERIFY(MUST(m_stream->tell()) == 0);
|
||||
TRY(skip_id3(*m_stream));
|
||||
|
||||
int sample_count = 0;
|
||||
size_t frame_count = 0;
|
||||
m_seek_table = {};
|
||||
|
||||
while (true) {
|
||||
auto error_or_header = synchronize_and_read_header();
|
||||
if (error_or_header.is_error())
|
||||
break;
|
||||
|
||||
if (frame_count % 10 == 0) {
|
||||
auto frame_pos = TRY(m_stream->tell()) - error_or_header.value().header_size;
|
||||
TRY(m_seek_table.insert_seek_point({ static_cast<u64>(sample_count), frame_pos }));
|
||||
}
|
||||
|
||||
frame_count++;
|
||||
sample_count += MP3::frame_size;
|
||||
|
||||
TRY(m_stream->seek(error_or_header.value().frame_size - error_or_header.value().header_size, SeekMode::FromCurrentPosition));
|
||||
}
|
||||
m_total_samples = sample_count;
|
||||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<MP3::Header, LoaderError> MP3LoaderPlugin::read_header(SeekableStream& stream, size_t sample_index)
|
||||
{
|
||||
auto bitstream = BigEndianInputBitStream(MaybeOwned<Stream>(stream));
|
||||
if (TRY(bitstream.read_bits(4)) != 0xF)
|
||||
return LoaderError { LoaderError::Category::Format, sample_index, "Frame header did not start with sync code." };
|
||||
MP3::Header header;
|
||||
header.id = TRY(bitstream.read_bit());
|
||||
header.layer = MP3::Tables::LayerNumberLookup[TRY(bitstream.read_bits(2))];
|
||||
if (header.layer <= 0)
|
||||
return LoaderError { LoaderError::Category::Format, sample_index, "Frame header contains invalid layer number." };
|
||||
header.protection_bit = TRY(bitstream.read_bit());
|
||||
header.bitrate = MP3::Tables::BitratesPerLayerLookup[header.layer - 1][TRY(bitstream.read_bits(4))];
|
||||
if (header.bitrate <= 0)
|
||||
return LoaderError { LoaderError::Category::Format, sample_index, "Frame header contains invalid bitrate." };
|
||||
header.samplerate = MP3::Tables::SampleratesLookup[TRY(bitstream.read_bits(2))];
|
||||
if (header.samplerate <= 0)
|
||||
return LoaderError { LoaderError::Category::Format, sample_index, "Frame header contains invalid samplerate." };
|
||||
header.padding_bit = TRY(bitstream.read_bit());
|
||||
header.private_bit = TRY(bitstream.read_bit());
|
||||
header.mode = static_cast<MP3::Mode>(TRY(bitstream.read_bits(2)));
|
||||
header.mode_extension = static_cast<MP3::ModeExtension>(TRY(bitstream.read_bits(2)));
|
||||
header.copyright_bit = TRY(bitstream.read_bit());
|
||||
header.original_bit = TRY(bitstream.read_bit());
|
||||
header.emphasis = static_cast<MP3::Emphasis>(TRY(bitstream.read_bits(2)));
|
||||
header.header_size = 4;
|
||||
if (!header.protection_bit) {
|
||||
header.crc16 = TRY(bitstream.read_bits<u16>(16));
|
||||
header.header_size += 2;
|
||||
}
|
||||
header.frame_size = 144 * header.bitrate * 1000 / header.samplerate + header.padding_bit;
|
||||
header.slot_count = header.frame_size - ((header.channel_count() == 2 ? 32 : 17) + header.header_size);
|
||||
return header;
|
||||
}
|
||||
|
||||
ErrorOr<MP3::Header, LoaderError> MP3LoaderPlugin::synchronize_and_read_header(SeekableStream& stream, size_t sample_index)
|
||||
{
|
||||
while (!stream.is_eof()) {
|
||||
bool last_was_all_set = false;
|
||||
|
||||
while (!stream.is_eof()) {
|
||||
u8 byte = TRY(stream.read_value<u8>());
|
||||
if (last_was_all_set && (byte & 0xF0) == 0xF0) {
|
||||
// Seek back, since there is still data we have not consumed within the current byte.
|
||||
// read_header() will consume and check these 4 bits itself and then continue reading
|
||||
// the rest of the data from there.
|
||||
TRY(stream.seek(-1, SeekMode::FromCurrentPosition));
|
||||
break;
|
||||
}
|
||||
last_was_all_set = byte == 0xFF;
|
||||
}
|
||||
|
||||
auto header_start = TRY(stream.tell());
|
||||
auto header_result = read_header(stream, sample_index);
|
||||
if (header_result.is_error() || header_result.value().id != 1 || header_result.value().layer != 3) {
|
||||
TRY(stream.seek(header_start, SeekMode::SetPosition));
|
||||
continue;
|
||||
}
|
||||
return header_result.value();
|
||||
}
|
||||
return LoaderError { LoaderError::Category::Format, sample_index, "Failed to synchronize." };
|
||||
}
|
||||
|
||||
ErrorOr<MP3::Header, LoaderError> MP3LoaderPlugin::synchronize_and_read_header()
|
||||
{
|
||||
return MP3LoaderPlugin::synchronize_and_read_header(*m_stream, m_loaded_samples);
|
||||
}
|
||||
|
||||
ErrorOr<MP3::MP3Frame, LoaderError> MP3LoaderPlugin::read_next_frame()
|
||||
{
|
||||
return read_frame_data(TRY(synchronize_and_read_header()));
|
||||
}
|
||||
|
||||
ErrorOr<MP3::MP3Frame, LoaderError> MP3LoaderPlugin::read_frame_data(MP3::Header const& header)
|
||||
{
|
||||
MP3::MP3Frame frame { header };
|
||||
|
||||
TRY(read_side_information(frame));
|
||||
|
||||
auto maybe_buffer = ByteBuffer::create_uninitialized(header.slot_count);
|
||||
if (maybe_buffer.is_error())
|
||||
return LoaderError { LoaderError::Category::IO, m_loaded_samples, "Out of memory" };
|
||||
auto& buffer = maybe_buffer.value();
|
||||
|
||||
size_t old_reservoir_size = m_bit_reservoir.used_buffer_size();
|
||||
TRY(m_stream->read_until_filled(buffer));
|
||||
TRY(m_bit_reservoir.write_until_depleted(buffer));
|
||||
|
||||
// If we don't have enough data in the reservoir to process this frame, skip it (but keep the data).
|
||||
if (old_reservoir_size < static_cast<size_t>(frame.main_data_begin))
|
||||
return frame;
|
||||
|
||||
TRY(m_bit_reservoir.discard(old_reservoir_size - frame.main_data_begin));
|
||||
|
||||
BigEndianInputBitStream reservoir_stream { MaybeOwned<Stream>(m_bit_reservoir) };
|
||||
|
||||
for (size_t granule_index = 0; granule_index < 2; granule_index++) {
|
||||
for (size_t channel_index = 0; channel_index < header.channel_count(); channel_index++) {
|
||||
size_t scale_factor_size = TRY(read_scale_factors(frame, reservoir_stream, granule_index, channel_index));
|
||||
TRY(read_huffman_data(frame, reservoir_stream, granule_index, channel_index, scale_factor_size));
|
||||
if (frame.channels[channel_index].granules[granule_index].block_type == MP3::BlockType::Short) {
|
||||
reorder_samples(frame.channels[channel_index].granules[granule_index], frame.header.samplerate);
|
||||
|
||||
// Only reduce alias for lowest 2 bands as they're long.
|
||||
// Afaik this is not mentioned in the ISO spec, but it is addressed in the
|
||||
// changelog for the ISO compliance tests.
|
||||
if (frame.channels[channel_index].granules[granule_index].mixed_block_flag)
|
||||
reduce_alias(frame.channels[channel_index].granules[granule_index], 36);
|
||||
} else {
|
||||
reduce_alias(frame.channels[channel_index].granules[granule_index]);
|
||||
}
|
||||
}
|
||||
|
||||
if (header.mode == MP3::Mode::JointStereo) {
|
||||
process_stereo(frame, granule_index);
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t granule_index = 0; granule_index < 2; granule_index++) {
|
||||
for (size_t channel_index = 0; channel_index < header.channel_count(); channel_index++) {
|
||||
auto& granule = frame.channels[channel_index].granules[granule_index];
|
||||
|
||||
for (size_t i = 0; i < MP3::granule_size; i += 18) {
|
||||
MP3::BlockType block_type = granule.block_type;
|
||||
if (i < 36 && granule.mixed_block_flag) {
|
||||
// ISO/IEC 11172-3: if mixed_block_flag is set, the lowest two subbands are transformed with normal window.
|
||||
block_type = MP3::BlockType::Normal;
|
||||
}
|
||||
|
||||
Array<float, 36> output;
|
||||
transform_samples_to_time(granule.samples, i, output, block_type);
|
||||
|
||||
int const subband_index = i / 18;
|
||||
for (size_t sample_index = 0; sample_index < 18; sample_index++) {
|
||||
// overlap add
|
||||
granule.filter_bank_input[subband_index][sample_index] = output[sample_index] + m_last_values[channel_index][subband_index][sample_index];
|
||||
m_last_values[channel_index][subband_index][sample_index] = output[sample_index + 18];
|
||||
|
||||
// frequency inversion
|
||||
if (subband_index % 2 == 1 && sample_index % 2 == 1)
|
||||
granule.filter_bank_input[subband_index][sample_index] *= -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Array<float, 32> in_samples;
|
||||
for (size_t channel_index = 0; channel_index < frame.header.channel_count(); channel_index++) {
|
||||
for (size_t granule_index = 0; granule_index < 2; granule_index++) {
|
||||
auto& granule = frame.channels[channel_index].granules[granule_index];
|
||||
for (size_t sample_index = 0; sample_index < 18; sample_index++) {
|
||||
for (size_t band_index = 0; band_index < 32; band_index++) {
|
||||
in_samples[band_index] = granule.filter_bank_input[band_index][sample_index];
|
||||
}
|
||||
synthesis(m_synthesis_buffer[channel_index], in_samples, granule.pcm[sample_index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
MaybeLoaderError MP3LoaderPlugin::read_side_information(MP3::MP3Frame& frame)
|
||||
{
|
||||
auto bitstream = BigEndianInputBitStream(MaybeOwned<Stream>(*m_stream));
|
||||
|
||||
frame.main_data_begin = TRY(bitstream.read_bits(9));
|
||||
|
||||
if (frame.header.channel_count() == 1) {
|
||||
frame.private_bits = TRY(bitstream.read_bits(5));
|
||||
} else {
|
||||
frame.private_bits = TRY(bitstream.read_bits(3));
|
||||
}
|
||||
|
||||
for (size_t channel_index = 0; channel_index < frame.header.channel_count(); channel_index++) {
|
||||
for (size_t scale_factor_selection_info_band = 0; scale_factor_selection_info_band < 4; scale_factor_selection_info_band++) {
|
||||
frame.channels[channel_index].scale_factor_selection_info[scale_factor_selection_info_band] = TRY(bitstream.read_bit());
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t granule_index = 0; granule_index < 2; granule_index++) {
|
||||
for (size_t channel_index = 0; channel_index < frame.header.channel_count(); channel_index++) {
|
||||
auto& granule = frame.channels[channel_index].granules[granule_index];
|
||||
granule.part_2_3_length = TRY(bitstream.read_bits(12));
|
||||
granule.big_values = TRY(bitstream.read_bits(9));
|
||||
granule.global_gain = TRY(bitstream.read_bits(8));
|
||||
granule.scalefac_compress = TRY(bitstream.read_bits(4));
|
||||
granule.window_switching_flag = TRY(bitstream.read_bit());
|
||||
if (granule.window_switching_flag) {
|
||||
granule.block_type = static_cast<MP3::BlockType>(TRY(bitstream.read_bits(2)));
|
||||
granule.mixed_block_flag = TRY(bitstream.read_bit());
|
||||
for (size_t region = 0; region < 2; region++)
|
||||
granule.table_select[region] = TRY(bitstream.read_bits(5));
|
||||
for (size_t window = 0; window < 3; window++)
|
||||
granule.sub_block_gain[window] = TRY(bitstream.read_bits(3));
|
||||
granule.region0_count = (granule.block_type == MP3::BlockType::Short && !granule.mixed_block_flag) ? 8 : 7;
|
||||
granule.region1_count = 36;
|
||||
} else {
|
||||
for (size_t region = 0; region < 3; region++)
|
||||
granule.table_select[region] = TRY(bitstream.read_bits(5));
|
||||
granule.region0_count = TRY(bitstream.read_bits(4));
|
||||
granule.region1_count = TRY(bitstream.read_bits(3));
|
||||
}
|
||||
granule.preflag = TRY(bitstream.read_bit());
|
||||
granule.scalefac_scale = TRY(bitstream.read_bit());
|
||||
granule.count1table_select = TRY(bitstream.read_bit());
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
// From ISO/IEC 11172-3 (2.4.3.4.7.1)
|
||||
Array<float, MP3::granule_size> MP3LoaderPlugin::calculate_frame_exponents(MP3::MP3Frame const& frame, size_t granule_index, size_t channel_index)
|
||||
{
|
||||
Array<float, MP3::granule_size> exponents;
|
||||
|
||||
auto fill_band = [&exponents](float exponent, size_t start, size_t end) {
|
||||
for (size_t j = start; j <= end; j++) {
|
||||
exponents[j] = exponent;
|
||||
}
|
||||
};
|
||||
|
||||
auto const& channel = frame.channels[channel_index];
|
||||
auto const& granule = frame.channels[channel_index].granules[granule_index];
|
||||
|
||||
auto const scale_factor_bands = get_scalefactor_bands(granule, frame.header.samplerate);
|
||||
float const scale_factor_multiplier = granule.scalefac_scale ? 1 : 0.5;
|
||||
int const gain = granule.global_gain - 210;
|
||||
|
||||
if (granule.block_type != MP3::BlockType::Short) {
|
||||
for (size_t band_index = 0; band_index < 22; band_index++) {
|
||||
float const exponent = gain / 4.0f - (scale_factor_multiplier * (channel.scale_factors[band_index] + granule.preflag * MP3::Tables::Pretab[band_index]));
|
||||
fill_band(AK::pow<float>(2.0, exponent), scale_factor_bands[band_index].start, scale_factor_bands[band_index].end);
|
||||
}
|
||||
} else {
|
||||
size_t band_index = 0;
|
||||
size_t sample_count = 0;
|
||||
|
||||
if (granule.mixed_block_flag) {
|
||||
while (sample_count < 36) {
|
||||
float const exponent = gain / 4.0f - (scale_factor_multiplier * (channel.scale_factors[band_index] + granule.preflag * MP3::Tables::Pretab[band_index]));
|
||||
fill_band(AK::pow<float>(2.0, exponent), scale_factor_bands[band_index].start, scale_factor_bands[band_index].end);
|
||||
sample_count += scale_factor_bands[band_index].width;
|
||||
band_index++;
|
||||
}
|
||||
}
|
||||
|
||||
float const gain0 = (gain - 8 * granule.sub_block_gain[0]) / 4.0;
|
||||
float const gain1 = (gain - 8 * granule.sub_block_gain[1]) / 4.0;
|
||||
float const gain2 = (gain - 8 * granule.sub_block_gain[2]) / 4.0;
|
||||
|
||||
while (sample_count < MP3::granule_size && band_index < scale_factor_bands.size()) {
|
||||
float const exponent0 = gain0 - (scale_factor_multiplier * channel.scale_factors[band_index + 0]);
|
||||
float const exponent1 = gain1 - (scale_factor_multiplier * channel.scale_factors[band_index + 1]);
|
||||
float const exponent2 = gain2 - (scale_factor_multiplier * channel.scale_factors[band_index + 2]);
|
||||
|
||||
fill_band(AK::pow<float>(2.0, exponent0), scale_factor_bands[band_index + 0].start, scale_factor_bands[band_index + 0].end);
|
||||
sample_count += scale_factor_bands[band_index + 0].width;
|
||||
fill_band(AK::pow<float>(2.0, exponent1), scale_factor_bands[band_index + 1].start, scale_factor_bands[band_index + 1].end);
|
||||
sample_count += scale_factor_bands[band_index + 1].width;
|
||||
fill_band(AK::pow<float>(2.0, exponent2), scale_factor_bands[band_index + 2].start, scale_factor_bands[band_index + 2].end);
|
||||
sample_count += scale_factor_bands[band_index + 2].width;
|
||||
|
||||
band_index += 3;
|
||||
}
|
||||
|
||||
while (sample_count < MP3::granule_size)
|
||||
exponents[sample_count++] = 0;
|
||||
}
|
||||
return exponents;
|
||||
}
|
||||
|
||||
ErrorOr<size_t, LoaderError> MP3LoaderPlugin::read_scale_factors(MP3::MP3Frame& frame, BigEndianInputBitStream& reservoir, size_t granule_index, size_t channel_index)
|
||||
{
|
||||
auto& channel = frame.channels[channel_index];
|
||||
auto const& granule = channel.granules[granule_index];
|
||||
size_t band_index = 0;
|
||||
size_t bits_read = 0;
|
||||
|
||||
if (granule.window_switching_flag && granule.block_type == MP3::BlockType::Short) {
|
||||
if (granule.mixed_block_flag) {
|
||||
for (size_t i = 0; i < 8; i++) {
|
||||
auto const bits = MP3::Tables::ScalefacCompressSlen1[granule.scalefac_compress];
|
||||
channel.scale_factors[band_index++] = TRY(reservoir.read_bits(bits));
|
||||
bits_read += bits;
|
||||
}
|
||||
for (size_t i = 3; i < 12; i++) {
|
||||
auto const bits = i <= 5 ? MP3::Tables::ScalefacCompressSlen1[granule.scalefac_compress] : MP3::Tables::ScalefacCompressSlen2[granule.scalefac_compress];
|
||||
channel.scale_factors[band_index++] = TRY(reservoir.read_bits(bits));
|
||||
channel.scale_factors[band_index++] = TRY(reservoir.read_bits(bits));
|
||||
channel.scale_factors[band_index++] = TRY(reservoir.read_bits(bits));
|
||||
bits_read += 3 * bits;
|
||||
}
|
||||
} else {
|
||||
for (size_t i = 0; i < 12; i++) {
|
||||
auto const bits = i <= 5 ? MP3::Tables::ScalefacCompressSlen1[granule.scalefac_compress] : MP3::Tables::ScalefacCompressSlen2[granule.scalefac_compress];
|
||||
channel.scale_factors[band_index++] = TRY(reservoir.read_bits(bits));
|
||||
channel.scale_factors[band_index++] = TRY(reservoir.read_bits(bits));
|
||||
channel.scale_factors[band_index++] = TRY(reservoir.read_bits(bits));
|
||||
bits_read += 3 * bits;
|
||||
}
|
||||
}
|
||||
channel.scale_factors[band_index++] = 0;
|
||||
channel.scale_factors[band_index++] = 0;
|
||||
channel.scale_factors[band_index++] = 0;
|
||||
} else {
|
||||
if ((channel.scale_factor_selection_info[0] == 0) || (granule_index == 0)) {
|
||||
for (band_index = 0; band_index < 6; band_index++) {
|
||||
auto const bits = MP3::Tables::ScalefacCompressSlen1[granule.scalefac_compress];
|
||||
channel.scale_factors[band_index] = TRY(reservoir.read_bits(bits));
|
||||
bits_read += bits;
|
||||
}
|
||||
}
|
||||
if ((channel.scale_factor_selection_info[1] == 0) || (granule_index == 0)) {
|
||||
for (band_index = 6; band_index < 11; band_index++) {
|
||||
auto const bits = MP3::Tables::ScalefacCompressSlen1[granule.scalefac_compress];
|
||||
channel.scale_factors[band_index] = TRY(reservoir.read_bits(bits));
|
||||
bits_read += bits;
|
||||
}
|
||||
}
|
||||
if ((channel.scale_factor_selection_info[2] == 0) || (granule_index == 0)) {
|
||||
for (band_index = 11; band_index < 16; band_index++) {
|
||||
auto const bits = MP3::Tables::ScalefacCompressSlen2[granule.scalefac_compress];
|
||||
channel.scale_factors[band_index] = TRY(reservoir.read_bits(bits));
|
||||
bits_read += bits;
|
||||
}
|
||||
}
|
||||
if ((channel.scale_factor_selection_info[3] == 0) || (granule_index == 0)) {
|
||||
for (band_index = 16; band_index < 21; band_index++) {
|
||||
auto const bits = MP3::Tables::ScalefacCompressSlen2[granule.scalefac_compress];
|
||||
channel.scale_factors[band_index] = TRY(reservoir.read_bits(bits));
|
||||
bits_read += bits;
|
||||
}
|
||||
}
|
||||
channel.scale_factors[21] = 0;
|
||||
}
|
||||
|
||||
return bits_read;
|
||||
}
|
||||
|
||||
MaybeLoaderError MP3LoaderPlugin::read_huffman_data(MP3::MP3Frame& frame, BigEndianInputBitStream& reservoir, size_t granule_index, size_t channel_index, size_t granule_bits_read)
|
||||
{
|
||||
auto const exponents = calculate_frame_exponents(frame, granule_index, channel_index);
|
||||
auto& granule = frame.channels[channel_index].granules[granule_index];
|
||||
|
||||
auto const scale_factor_bands = get_scalefactor_bands(granule, frame.header.samplerate);
|
||||
size_t const scale_factor_band_index1 = granule.region0_count + 1;
|
||||
size_t const scale_factor_band_index2 = min(scale_factor_bands.size() - 1, scale_factor_band_index1 + granule.region1_count + 1);
|
||||
|
||||
bool const is_short_granule = granule.window_switching_flag && granule.block_type == MP3::BlockType::Short;
|
||||
size_t const region1_start = is_short_granule ? 36 : scale_factor_bands[scale_factor_band_index1].start;
|
||||
size_t const region2_start = is_short_granule ? MP3::granule_size : scale_factor_bands[scale_factor_band_index2].start;
|
||||
|
||||
auto requantize = [](int const sample, float const exponent) -> float {
|
||||
int const sign = sample < 0 ? -1 : 1;
|
||||
int const magnitude = AK::abs(sample);
|
||||
return sign * AK::pow<float>(static_cast<float>(magnitude), 4 / 3.0) * exponent;
|
||||
};
|
||||
|
||||
size_t count = 0;
|
||||
|
||||
// 2.4.3.4.6: "Decoding is done until all Huffman code bits have been decoded
|
||||
// or until quantized values representing 576 frequency lines have been decoded,
|
||||
// whichever comes first."
|
||||
auto max_count = min(granule.big_values * 2, MP3::granule_size);
|
||||
|
||||
for (; count < max_count; count += 2) {
|
||||
MP3::Tables::Huffman::HuffmanTreeXY const* tree = nullptr;
|
||||
|
||||
if (count < region1_start) {
|
||||
tree = &MP3::Tables::Huffman::HuffmanTreesXY[granule.table_select[0]];
|
||||
} else if (count < region2_start) {
|
||||
tree = &MP3::Tables::Huffman::HuffmanTreesXY[granule.table_select[1]];
|
||||
} else {
|
||||
tree = &MP3::Tables::Huffman::HuffmanTreesXY[granule.table_select[2]];
|
||||
}
|
||||
|
||||
if (!tree || tree->nodes.is_empty()) {
|
||||
return LoaderError { LoaderError::Category::Format, m_loaded_samples, "Frame references invalid huffman table." };
|
||||
}
|
||||
|
||||
// Assumption: There's enough bits to read. 32 is just a placeholder for "unlimited".
|
||||
// There are no 32 bit long huffman codes in the tables.
|
||||
auto const entry = MP3::Tables::Huffman::huffman_decode(reservoir, tree->nodes, 32);
|
||||
granule_bits_read += entry.bits_read;
|
||||
if (!entry.code.has_value())
|
||||
return LoaderError { LoaderError::Category::Format, m_loaded_samples, "Frame contains invalid huffman data." };
|
||||
int x = entry.code->symbol.x;
|
||||
int y = entry.code->symbol.y;
|
||||
|
||||
if (x == 15 && tree->linbits > 0) {
|
||||
x += TRY(reservoir.read_bits(tree->linbits));
|
||||
granule_bits_read += tree->linbits;
|
||||
}
|
||||
if (x != 0) {
|
||||
if (TRY(reservoir.read_bit()))
|
||||
x = -x;
|
||||
granule_bits_read++;
|
||||
}
|
||||
|
||||
if (y == 15 && tree->linbits > 0) {
|
||||
y += TRY(reservoir.read_bits(tree->linbits));
|
||||
granule_bits_read += tree->linbits;
|
||||
}
|
||||
if (y != 0) {
|
||||
if (TRY(reservoir.read_bit()))
|
||||
y = -y;
|
||||
granule_bits_read++;
|
||||
}
|
||||
|
||||
granule.samples[count + 0] = requantize(x, exponents[count + 0]);
|
||||
granule.samples[count + 1] = requantize(y, exponents[count + 1]);
|
||||
}
|
||||
|
||||
ReadonlySpan<MP3::Tables::Huffman::HuffmanNode<MP3::Tables::Huffman::HuffmanVWXY>> count1table = granule.count1table_select ? MP3::Tables::Huffman::TreeB : MP3::Tables::Huffman::TreeA;
|
||||
|
||||
// count1 is not known. We have to read huffman encoded values
|
||||
// until we've exhausted the granule's bits. We know the size of
|
||||
// the granule from part2_3_length, which is the number of bits
|
||||
// used for scalefactors and huffman data (in the granule).
|
||||
while (granule_bits_read < granule.part_2_3_length && count <= MP3::granule_size - 4) {
|
||||
auto const entry = MP3::Tables::Huffman::huffman_decode(reservoir, count1table, granule.part_2_3_length - granule_bits_read);
|
||||
granule_bits_read += entry.bits_read;
|
||||
if (!entry.code.has_value())
|
||||
return LoaderError { LoaderError::Category::Format, m_loaded_samples, "Frame contains invalid huffman data." };
|
||||
int v = entry.code->symbol.v;
|
||||
if (v != 0) {
|
||||
if (granule_bits_read >= granule.part_2_3_length)
|
||||
break;
|
||||
if (TRY(reservoir.read_bit()))
|
||||
v = -v;
|
||||
granule_bits_read++;
|
||||
}
|
||||
int w = entry.code->symbol.w;
|
||||
if (w != 0) {
|
||||
if (granule_bits_read >= granule.part_2_3_length)
|
||||
break;
|
||||
if (TRY(reservoir.read_bit()))
|
||||
w = -w;
|
||||
granule_bits_read++;
|
||||
}
|
||||
int x = entry.code->symbol.x;
|
||||
if (x != 0) {
|
||||
if (granule_bits_read >= granule.part_2_3_length)
|
||||
break;
|
||||
if (TRY(reservoir.read_bit()))
|
||||
x = -x;
|
||||
granule_bits_read++;
|
||||
}
|
||||
int y = entry.code->symbol.y;
|
||||
if (y != 0) {
|
||||
if (granule_bits_read >= granule.part_2_3_length)
|
||||
break;
|
||||
if (TRY(reservoir.read_bit()))
|
||||
y = -y;
|
||||
granule_bits_read++;
|
||||
}
|
||||
|
||||
granule.samples[count + 0] = requantize(v, exponents[count + 0]);
|
||||
granule.samples[count + 1] = requantize(w, exponents[count + 1]);
|
||||
granule.samples[count + 2] = requantize(x, exponents[count + 2]);
|
||||
granule.samples[count + 3] = requantize(y, exponents[count + 3]);
|
||||
|
||||
count += 4;
|
||||
}
|
||||
|
||||
if (granule_bits_read > granule.part_2_3_length) {
|
||||
return LoaderError { LoaderError::Category::Format, m_loaded_samples, "Read too many bits from bit reservoir." };
|
||||
}
|
||||
|
||||
// 2.4.3.4.6: "If there are more Huffman code bits than necessary to decode 576 values
|
||||
// they are regarded as stuffing bits and discarded."
|
||||
for (size_t i = granule_bits_read; i < granule.part_2_3_length; i++) {
|
||||
TRY(reservoir.read_bit());
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void MP3LoaderPlugin::reorder_samples(MP3::Granule& granule, u32 sample_rate)
|
||||
{
|
||||
float tmp[MP3::granule_size] = {};
|
||||
size_t band_index = 0;
|
||||
size_t subband_index = 0;
|
||||
|
||||
auto scale_factor_bands = get_scalefactor_bands(granule, sample_rate);
|
||||
|
||||
if (granule.mixed_block_flag) {
|
||||
while (subband_index < 36) {
|
||||
for (size_t frequency_line_index = 0; frequency_line_index < scale_factor_bands[band_index].width; frequency_line_index++) {
|
||||
tmp[subband_index] = granule.samples[subband_index];
|
||||
subband_index++;
|
||||
}
|
||||
band_index++;
|
||||
}
|
||||
}
|
||||
|
||||
while (subband_index < MP3::granule_size && band_index <= 36) {
|
||||
for (size_t frequency_line_index = 0; frequency_line_index < scale_factor_bands[band_index].width; frequency_line_index++) {
|
||||
tmp[subband_index++] = granule.samples[scale_factor_bands[band_index + 0].start + frequency_line_index];
|
||||
tmp[subband_index++] = granule.samples[scale_factor_bands[band_index + 1].start + frequency_line_index];
|
||||
tmp[subband_index++] = granule.samples[scale_factor_bands[band_index + 2].start + frequency_line_index];
|
||||
}
|
||||
band_index += 3;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < MP3::granule_size; i++)
|
||||
granule.samples[i] = tmp[i];
|
||||
}
|
||||
|
||||
void MP3LoaderPlugin::reduce_alias(MP3::Granule& granule, size_t max_subband_index)
|
||||
{
|
||||
for (size_t subband = 0; subband < max_subband_index - 18; subband += 18) {
|
||||
for (size_t i = 0; i < 8; i++) {
|
||||
size_t const idx1 = subband + 17 - i;
|
||||
size_t const idx2 = subband + 18 + i;
|
||||
auto const d1 = granule.samples[idx1];
|
||||
auto const d2 = granule.samples[idx2];
|
||||
granule.samples[idx1] = d1 * MP3::Tables::AliasReductionCs[i] - d2 * MP3::Tables::AliasReductionCa[i];
|
||||
granule.samples[idx2] = d2 * MP3::Tables::AliasReductionCs[i] + d1 * MP3::Tables::AliasReductionCa[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MP3LoaderPlugin::process_stereo(MP3::MP3Frame& frame, size_t granule_index)
|
||||
{
|
||||
size_t band_index_ms_start = 0;
|
||||
size_t band_index_ms_end = 0;
|
||||
size_t band_index_intensity_start = 0;
|
||||
size_t band_index_intensity_end = 0;
|
||||
auto& granule_left = frame.channels[0].granules[granule_index];
|
||||
auto& granule_right = frame.channels[1].granules[granule_index];
|
||||
|
||||
auto get_last_nonempty_band = [](Span<float> samples, ReadonlySpan<MP3::Tables::ScaleFactorBand> bands) -> size_t {
|
||||
size_t last_nonempty_band = 0;
|
||||
|
||||
for (size_t i = 0; i < bands.size(); i++) {
|
||||
bool is_empty = true;
|
||||
for (size_t l = bands[i].start; l < bands[i].end; l++) {
|
||||
if (samples[l] != 0) {
|
||||
is_empty = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!is_empty)
|
||||
last_nonempty_band = i;
|
||||
}
|
||||
|
||||
return last_nonempty_band;
|
||||
};
|
||||
|
||||
auto process_ms_stereo = [&](MP3::Tables::ScaleFactorBand const& band) {
|
||||
float const SQRT_2 = AK::sqrt(2.0);
|
||||
for (size_t i = band.start; i <= band.end; i++) {
|
||||
float const m = granule_left.samples[i];
|
||||
float const s = granule_right.samples[i];
|
||||
granule_left.samples[i] = (m + s) / SQRT_2;
|
||||
granule_right.samples[i] = (m - s) / SQRT_2;
|
||||
}
|
||||
};
|
||||
|
||||
auto process_intensity_stereo = [&](MP3::Tables::ScaleFactorBand const& band, float intensity_stereo_ratio) {
|
||||
for (size_t i = band.start; i <= band.end; i++) {
|
||||
// Superflous empty scale factor band.
|
||||
if (i >= MP3::granule_size)
|
||||
continue;
|
||||
float const sample_left = granule_left.samples[i];
|
||||
float const coeff_l = intensity_stereo_ratio / (1 + intensity_stereo_ratio);
|
||||
float const coeff_r = 1 / (1 + intensity_stereo_ratio);
|
||||
granule_left.samples[i] = sample_left * coeff_l;
|
||||
granule_right.samples[i] = sample_left * coeff_r;
|
||||
}
|
||||
};
|
||||
|
||||
auto scale_factor_bands = get_scalefactor_bands(granule_right, frame.header.samplerate);
|
||||
|
||||
if (has_flag(frame.header.mode_extension, MP3::ModeExtension::MsStereo)) {
|
||||
band_index_ms_start = 0;
|
||||
band_index_ms_end = scale_factor_bands.size();
|
||||
}
|
||||
|
||||
if (has_flag(frame.header.mode_extension, MP3::ModeExtension::IntensityStereo)) {
|
||||
band_index_intensity_start = get_last_nonempty_band(granule_right.samples, scale_factor_bands);
|
||||
band_index_intensity_end = scale_factor_bands.size();
|
||||
band_index_ms_end = band_index_intensity_start;
|
||||
}
|
||||
|
||||
for (size_t band_index = band_index_ms_start; band_index < band_index_ms_end; band_index++) {
|
||||
process_ms_stereo(scale_factor_bands[band_index]);
|
||||
}
|
||||
|
||||
for (size_t band_index = band_index_intensity_start; band_index < band_index_intensity_end; band_index++) {
|
||||
auto const intensity_stereo_position = frame.channels[1].scale_factors[band_index];
|
||||
if (intensity_stereo_position == 7) {
|
||||
if (has_flag(frame.header.mode_extension, MP3::ModeExtension::MsStereo))
|
||||
process_ms_stereo(scale_factor_bands[band_index]);
|
||||
continue;
|
||||
}
|
||||
float const intensity_stereo_ratio = AK::tan(intensity_stereo_position * AK::Pi<float> / 12);
|
||||
process_intensity_stereo(scale_factor_bands[band_index], intensity_stereo_ratio);
|
||||
}
|
||||
}
|
||||
|
||||
void MP3LoaderPlugin::transform_samples_to_time(Array<float, MP3::granule_size> const& input, size_t input_offset, Array<float, 36>& output, MP3::BlockType block_type)
|
||||
{
|
||||
if (block_type == MP3::BlockType::Short) {
|
||||
size_t const N = 12;
|
||||
Array<float, N * 3> temp_out;
|
||||
Array<float, N / 2> temp_in;
|
||||
|
||||
for (size_t k = 0; k < N / 2; k++)
|
||||
temp_in[k] = input[input_offset + 3 * k + 0];
|
||||
s_mdct_12.transform(temp_in, Span<float>(temp_out).slice(0, N));
|
||||
for (size_t i = 0; i < N; i++)
|
||||
temp_out[i + 0] *= MP3::Tables::WindowBlockTypeShort[i];
|
||||
|
||||
for (size_t k = 0; k < N / 2; k++)
|
||||
temp_in[k] = input[input_offset + 3 * k + 1];
|
||||
s_mdct_12.transform(temp_in, Span<float>(temp_out).slice(12, N));
|
||||
for (size_t i = 0; i < N; i++)
|
||||
temp_out[i + 12] *= MP3::Tables::WindowBlockTypeShort[i];
|
||||
|
||||
for (size_t k = 0; k < N / 2; k++)
|
||||
temp_in[k] = input[input_offset + 3 * k + 2];
|
||||
s_mdct_12.transform(temp_in, Span<float>(temp_out).slice(24, N));
|
||||
for (size_t i = 0; i < N; i++)
|
||||
temp_out[i + 24] *= MP3::Tables::WindowBlockTypeShort[i];
|
||||
|
||||
Span<float> idmct1 = Span<float>(temp_out).slice(0, 12);
|
||||
Span<float> idmct2 = Span<float>(temp_out).slice(12, 12);
|
||||
Span<float> idmct3 = Span<float>(temp_out).slice(24, 12);
|
||||
for (size_t i = 0; i < 6; i++)
|
||||
output[i] = 0;
|
||||
for (size_t i = 6; i < 12; i++)
|
||||
output[i] = idmct1[i - 6];
|
||||
for (size_t i = 12; i < 18; i++)
|
||||
output[i] = idmct1[i - 6] + idmct2[i - 12];
|
||||
for (size_t i = 18; i < 24; i++)
|
||||
output[i] = idmct2[i - 12] + idmct3[i - 18];
|
||||
for (size_t i = 24; i < 30; i++)
|
||||
output[i] = idmct3[i - 18];
|
||||
for (size_t i = 30; i < 36; i++)
|
||||
output[i] = 0;
|
||||
|
||||
} else {
|
||||
s_mdct_36.transform(ReadonlySpan<float>(input).slice(input_offset, 18), output);
|
||||
for (size_t i = 0; i < 36; i++) {
|
||||
switch (block_type) {
|
||||
case MP3::BlockType::Normal:
|
||||
output[i] *= MP3::Tables::WindowBlockTypeNormal[i];
|
||||
break;
|
||||
case MP3::BlockType::Start:
|
||||
output[i] *= MP3::Tables::WindowBlockTypeStart[i];
|
||||
break;
|
||||
case MP3::BlockType::End:
|
||||
output[i] *= MP3::Tables::WindowBlockTypeEnd[i];
|
||||
break;
|
||||
case MP3::BlockType::Short:
|
||||
VERIFY_NOT_REACHED();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ISO/IEC 11172-3 (Figure A.2)
|
||||
void MP3LoaderPlugin::synthesis(Array<float, 1024>& V, Array<float, 32>& samples, Array<float, 32>& result)
|
||||
{
|
||||
for (size_t i = 1023; i >= 64; i--) {
|
||||
V[i] = V[i - 64];
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < 64; i++) {
|
||||
V[i] = 0;
|
||||
for (size_t k = 0; k < 32; k++) {
|
||||
float const N = MP3::Tables::SynthesisSubbandFilterCoefficients[i][k];
|
||||
V[i] += N * samples[k];
|
||||
}
|
||||
}
|
||||
|
||||
Array<float, 512> U;
|
||||
for (size_t i = 0; i < 8; i++) {
|
||||
for (size_t j = 0; j < 32; j++) {
|
||||
U[i * 64 + j] = V[i * 128 + j];
|
||||
U[i * 64 + 32 + j] = V[i * 128 + 96 + j];
|
||||
}
|
||||
}
|
||||
|
||||
Array<float, 512> W;
|
||||
for (size_t i = 0; i < 512; i++) {
|
||||
W[i] = U[i] * MP3::Tables::WindowSynthesis[i];
|
||||
}
|
||||
|
||||
for (size_t j = 0; j < 32; j++) {
|
||||
result[j] = 0;
|
||||
for (size_t k = 0; k < 16; k++) {
|
||||
result[j] += W[j + 32 * k];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ReadonlySpan<MP3::Tables::ScaleFactorBand> MP3LoaderPlugin::get_scalefactor_bands(MP3::Granule const& granule, int samplerate)
|
||||
{
|
||||
switch (granule.block_type) {
|
||||
case MP3::BlockType::Short:
|
||||
switch (samplerate) {
|
||||
case 32000:
|
||||
return granule.mixed_block_flag ? MP3::Tables::ScaleFactorBandMixed32000 : MP3::Tables::ScaleFactorBandShort32000;
|
||||
case 44100:
|
||||
return granule.mixed_block_flag ? MP3::Tables::ScaleFactorBandMixed44100 : MP3::Tables::ScaleFactorBandShort44100;
|
||||
case 48000:
|
||||
return granule.mixed_block_flag ? MP3::Tables::ScaleFactorBandMixed48000 : MP3::Tables::ScaleFactorBandShort48000;
|
||||
}
|
||||
break;
|
||||
case MP3::BlockType::Normal:
|
||||
[[fallthrough]];
|
||||
case MP3::BlockType::Start:
|
||||
[[fallthrough]];
|
||||
case MP3::BlockType::End:
|
||||
switch (samplerate) {
|
||||
case 32000:
|
||||
return MP3::Tables::ScaleFactorBandLong32000;
|
||||
case 44100:
|
||||
return MP3::Tables::ScaleFactorBandLong44100;
|
||||
case 48000:
|
||||
return MP3::Tables::ScaleFactorBandLong48000;
|
||||
}
|
||||
}
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,77 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Arne Elster <arne@elster.li>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Loader.h"
|
||||
#include "MDCT.h"
|
||||
#include "MP3Types.h"
|
||||
#include <AK/BitStream.h>
|
||||
#include <AK/MemoryStream.h>
|
||||
|
||||
namespace Audio {
|
||||
|
||||
namespace MP3::Tables {
|
||||
struct ScaleFactorBand;
|
||||
}
|
||||
|
||||
class MP3LoaderPlugin : public LoaderPlugin {
|
||||
public:
|
||||
explicit MP3LoaderPlugin(NonnullOwnPtr<SeekableStream> stream);
|
||||
virtual ~MP3LoaderPlugin() = default;
|
||||
|
||||
static bool sniff(SeekableStream& stream);
|
||||
static ErrorOr<NonnullOwnPtr<LoaderPlugin>, LoaderError> create(NonnullOwnPtr<SeekableStream>);
|
||||
|
||||
virtual ErrorOr<Vector<FixedArray<Sample>>, LoaderError> load_chunks(size_t samples_to_read_from_input) override;
|
||||
|
||||
virtual MaybeLoaderError reset() override;
|
||||
virtual MaybeLoaderError seek(int const position) override;
|
||||
|
||||
virtual int loaded_samples() override { return m_loaded_samples; }
|
||||
virtual int total_samples() override { return m_total_samples; }
|
||||
virtual u32 sample_rate() override { return m_sample_rate; }
|
||||
virtual u16 num_channels() override { return m_num_channels; }
|
||||
virtual PcmSampleFormat pcm_format() override { return m_sample_format; }
|
||||
virtual ByteString format_name() override { return "MP3 (.mp3)"; }
|
||||
|
||||
private:
|
||||
MaybeLoaderError initialize();
|
||||
static MaybeLoaderError skip_id3(SeekableStream& stream);
|
||||
static MaybeLoaderError synchronize(SeekableStream& stream, size_t sample_index);
|
||||
static ErrorOr<MP3::Header, LoaderError> read_header(SeekableStream& stream, size_t sample_index);
|
||||
static ErrorOr<MP3::Header, LoaderError> synchronize_and_read_header(SeekableStream& stream, size_t sample_index);
|
||||
ErrorOr<MP3::Header, LoaderError> synchronize_and_read_header();
|
||||
MaybeLoaderError build_seek_table();
|
||||
ErrorOr<MP3::MP3Frame, LoaderError> read_next_frame();
|
||||
ErrorOr<MP3::MP3Frame, LoaderError> read_frame_data(MP3::Header const&);
|
||||
MaybeLoaderError read_side_information(MP3::MP3Frame&);
|
||||
ErrorOr<size_t, LoaderError> read_scale_factors(MP3::MP3Frame&, BigEndianInputBitStream& reservoir, size_t granule_index, size_t channel_index);
|
||||
MaybeLoaderError read_huffman_data(MP3::MP3Frame&, BigEndianInputBitStream& reservoir, size_t granule_index, size_t channel_index, size_t granule_bits_read);
|
||||
static AK::Array<float, 576> calculate_frame_exponents(MP3::MP3Frame const&, size_t granule_index, size_t channel_index);
|
||||
static void reorder_samples(MP3::Granule&, u32 sample_rate);
|
||||
static void reduce_alias(MP3::Granule&, size_t max_subband_index = 576);
|
||||
static void process_stereo(MP3::MP3Frame&, size_t granule_index);
|
||||
static void transform_samples_to_time(Array<float, 576> const& input, size_t input_offset, Array<float, 36>& output, MP3::BlockType block_type);
|
||||
static void synthesis(Array<float, 1024>& V, Array<float, 32>& samples, Array<float, 32>& result);
|
||||
static ReadonlySpan<MP3::Tables::ScaleFactorBand> get_scalefactor_bands(MP3::Granule const&, int samplerate);
|
||||
|
||||
SeekTable m_seek_table;
|
||||
AK::Array<AK::Array<AK::Array<float, 18>, 32>, 2> m_last_values {};
|
||||
AK::Array<AK::Array<float, 1024>, 2> m_synthesis_buffer {};
|
||||
static DSP::MDCT<36> s_mdct_36;
|
||||
static DSP::MDCT<12> s_mdct_12;
|
||||
|
||||
u32 m_sample_rate { 0 };
|
||||
u8 m_num_channels { 0 };
|
||||
PcmSampleFormat m_sample_format { PcmSampleFormat::Int16 };
|
||||
int m_total_samples { 0 };
|
||||
size_t m_loaded_samples { 0 };
|
||||
|
||||
AllocatingMemoryStream m_bit_reservoir;
|
||||
};
|
||||
|
||||
}
|
|
@ -1,447 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Arne Elster <arne@elster.li>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Array.h>
|
||||
#include <AK/HashMap.h>
|
||||
#include <AK/Math.h>
|
||||
|
||||
namespace Audio::MP3::Tables {
|
||||
|
||||
// ISO/IEC 11172-3 (2.4.2.3)
|
||||
Array<int, 4> LayerNumberLookup { -1, 3, 2, 1 };
|
||||
|
||||
// ISO/IEC 11172-3 (2.4.2.3)
|
||||
Array<Array<int, 16>, 3> BitratesPerLayerLookup { {
|
||||
{ 0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, -1 }, // Layer I
|
||||
{ 0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, -1 }, // Layer II
|
||||
{ 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, -1 } // Layer III
|
||||
} };
|
||||
|
||||
// ISO/IEC 11172-3 (2.4.2.3)
|
||||
Array<int, 4> SampleratesLookup { { 44100, 48000, 32000, -1 } };
|
||||
|
||||
// ISO/IEC 11172-3 (2.4.2.7)
|
||||
Array<int, 16> ScalefacCompressSlen1 { { 0, 0, 0, 0, 3, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4 } };
|
||||
Array<int, 16> ScalefacCompressSlen2 { { 0, 1, 2, 3, 0, 1, 2, 3, 1, 2, 3, 1, 2, 3, 2, 3 } };
|
||||
|
||||
// ISO/IEC 11172-3 (Table B.6)
|
||||
Array<int, 22> Pretab { { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 3, 3, 3, 2, 0 } };
|
||||
|
||||
// ISO/IEC 11172-3 (Table B.9)
|
||||
Array<float, 8> AliasReductionCoefficients {
|
||||
-0.6,
|
||||
-0.535,
|
||||
-0.33,
|
||||
-0.185,
|
||||
-0.095,
|
||||
-0.041,
|
||||
-0.0142,
|
||||
-0.0032
|
||||
};
|
||||
|
||||
// This is using the cs[i] formula taken from ISO/IEC 11172-3 (below Table B.9)
|
||||
Array<float, 8> AliasReductionCs { {
|
||||
1.0f / AK::sqrt<float>(1 + AK::pow<float>(AliasReductionCoefficients[0], 2.0)),
|
||||
1.0f / AK::sqrt<float>(1 + AK::pow<float>(AliasReductionCoefficients[1], 2.0)),
|
||||
1.0f / AK::sqrt<float>(1 + AK::pow<float>(AliasReductionCoefficients[2], 2.0)),
|
||||
1.0f / AK::sqrt<float>(1 + AK::pow<float>(AliasReductionCoefficients[3], 2.0)),
|
||||
1.0f / AK::sqrt<float>(1 + AK::pow<float>(AliasReductionCoefficients[4], 2.0)),
|
||||
1.0f / AK::sqrt<float>(1 + AK::pow<float>(AliasReductionCoefficients[5], 2.0)),
|
||||
1.0f / AK::sqrt<float>(1 + AK::pow<float>(AliasReductionCoefficients[6], 2.0)),
|
||||
1.0f / AK::sqrt<float>(1 + AK::pow<float>(AliasReductionCoefficients[7], 2.0)),
|
||||
} };
|
||||
|
||||
// This is using the ca[i] formula taken from ISO/IEC 11172-3 (below Table B.9)
|
||||
Array<float, 8> AliasReductionCa { {
|
||||
AliasReductionCoefficients[0] / AK::sqrt<float>(1 + AK::pow<float>(AliasReductionCoefficients[0], 2.0)),
|
||||
AliasReductionCoefficients[1] / AK::sqrt<float>(1 + AK::pow<float>(AliasReductionCoefficients[1], 2.0)),
|
||||
AliasReductionCoefficients[2] / AK::sqrt<float>(1 + AK::pow<float>(AliasReductionCoefficients[2], 2.0)),
|
||||
AliasReductionCoefficients[3] / AK::sqrt<float>(1 + AK::pow<float>(AliasReductionCoefficients[3], 2.0)),
|
||||
AliasReductionCoefficients[4] / AK::sqrt<float>(1 + AK::pow<float>(AliasReductionCoefficients[4], 2.0)),
|
||||
AliasReductionCoefficients[5] / AK::sqrt<float>(1 + AK::pow<float>(AliasReductionCoefficients[5], 2.0)),
|
||||
AliasReductionCoefficients[6] / AK::sqrt<float>(1 + AK::pow<float>(AliasReductionCoefficients[6], 2.0)),
|
||||
AliasReductionCoefficients[7] / AK::sqrt<float>(1 + AK::pow<float>(AliasReductionCoefficients[7], 2.0)),
|
||||
} };
|
||||
|
||||
struct ScaleFactorBand {
|
||||
size_t width;
|
||||
size_t start;
|
||||
size_t end;
|
||||
};
|
||||
|
||||
template<auto sizes, size_t offset = 0>
|
||||
constexpr auto MakeShortScaleFactorBandArray()
|
||||
{
|
||||
constexpr size_t N = sizes.size();
|
||||
Array<ScaleFactorBand, 3 * N> result {};
|
||||
size_t start = offset;
|
||||
|
||||
for (size_t i = 0; i < N; i++) {
|
||||
result[3 * i + 0] = { sizes[i], start, start + sizes[i] - 1 };
|
||||
start += sizes[i];
|
||||
result[3 * i + 1] = { sizes[i], start, start + sizes[i] - 1 };
|
||||
start += sizes[i];
|
||||
result[3 * i + 2] = { sizes[i], start, start + sizes[i] - 1 };
|
||||
start += sizes[i];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
template<auto sizes>
|
||||
constexpr auto MakeLongScaleFactorBandArray()
|
||||
{
|
||||
constexpr size_t N = sizes.size();
|
||||
Array<ScaleFactorBand, N> result {};
|
||||
size_t start = 0;
|
||||
|
||||
for (size_t i = 0; i < N; i++) {
|
||||
result[i] = { sizes[i], start, start + sizes[i] - 1 };
|
||||
start += sizes[i];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
template<auto sizes_long, auto sizes_short>
|
||||
constexpr auto MakeMixedScaleFactorBandArray()
|
||||
{
|
||||
constexpr size_t N = sizes_long.size() + sizes_short.size() * 3 + 1;
|
||||
Array<ScaleFactorBand, N> result {};
|
||||
|
||||
constexpr auto long_bands = MakeLongScaleFactorBandArray<sizes_long>();
|
||||
constexpr auto short_bands = MakeShortScaleFactorBandArray<sizes_short, long_bands.last().end + 1>();
|
||||
|
||||
for (size_t i = 0; i < long_bands.size(); i++) {
|
||||
result[i] = long_bands[i];
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < short_bands.size(); i++) {
|
||||
result[i + long_bands.size()] = short_bands[i];
|
||||
}
|
||||
|
||||
for (size_t i = long_bands.size() + short_bands.size(); i < N; i++) {
|
||||
result[i] = { 0, 576, 576 };
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// ISO/IEC 11172-3 (Table B.8)
|
||||
constexpr auto ScaleFactorBandShort32000 = MakeShortScaleFactorBandArray<Array<size_t, 13> { 4, 4, 4, 4, 6, 8, 12, 16, 20, 26, 34, 42, 12 }>();
|
||||
constexpr auto ScaleFactorBandShort44100 = MakeShortScaleFactorBandArray<Array<size_t, 13> { 4, 4, 4, 4, 6, 8, 10, 12, 14, 18, 22, 30, 56 }>();
|
||||
constexpr auto ScaleFactorBandShort48000 = MakeShortScaleFactorBandArray<Array<size_t, 13> { 4, 4, 4, 4, 6, 6, 10, 12, 14, 16, 20, 26, 66 }>();
|
||||
|
||||
constexpr auto ScaleFactorBandMixed32000 = MakeMixedScaleFactorBandArray<Array<size_t, 8> { 4, 4, 4, 4, 4, 4, 6, 6 }, Array<size_t, 10> { 4, 6, 8, 12, 16, 20, 26, 34, 42, 12 }>();
|
||||
constexpr auto ScaleFactorBandMixed44100 = MakeMixedScaleFactorBandArray<Array<size_t, 8> { 4, 4, 4, 4, 4, 4, 6, 6 }, Array<size_t, 10> { 4, 6, 8, 10, 12, 14, 18, 22, 30, 56 }>();
|
||||
constexpr auto ScaleFactorBandMixed48000 = MakeMixedScaleFactorBandArray<Array<size_t, 8> { 4, 4, 4, 4, 4, 4, 6, 6 }, Array<size_t, 10> { 4, 6, 6, 10, 12, 14, 16, 20, 26, 66 }>();
|
||||
|
||||
constexpr auto ScaleFactorBandLong32000 = MakeLongScaleFactorBandArray<Array<size_t, 23> { 4, 4, 4, 4, 4, 4, 6, 6, 8, 10, 12, 16, 20, 24, 30, 38, 46, 56, 68, 84, 102, 26, 0 }>();
|
||||
constexpr auto ScaleFactorBandLong44100 = MakeLongScaleFactorBandArray<Array<size_t, 23> { 4, 4, 4, 4, 4, 4, 6, 6, 8, 8, 10, 12, 16, 20, 24, 28, 34, 42, 50, 54, 76, 158, 0 }>();
|
||||
constexpr auto ScaleFactorBandLong48000 = MakeLongScaleFactorBandArray<Array<size_t, 23> { 4, 4, 4, 4, 4, 4, 6, 6, 6, 8, 10, 12, 16, 18, 22, 28, 34, 40, 46, 54, 54, 192, 0 }>();
|
||||
|
||||
// ISO/IEC 11172-3 (2.4.3.4.10.3 a)
|
||||
Array<float, 36> WindowBlockTypeNormal { {
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (0 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (1 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (2 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (3 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (4 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (5 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (6 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (7 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (8 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (9 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (10 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (11 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (12 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (13 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (14 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (15 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (16 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (17 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (18 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (19 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (20 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (21 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (22 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (23 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (24 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (25 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (26 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (27 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (28 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (29 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (30 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (31 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (32 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (33 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (34 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (35 + 0.5f)),
|
||||
} };
|
||||
|
||||
// ISO/IEC 11172-3 (2.4.3.4.10.3 b)
|
||||
AK::Array<float, 36> WindowBlockTypeStart { {
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (0 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (1 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (2 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (3 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (4 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (5 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (6 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (7 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (8 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (9 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (10 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (11 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (12 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (13 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (14 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (15 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (16 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (17 + 0.5f)),
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
AK::sin<float>(AK::Pi<float> / 12 * (24 - 18 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 12 * (25 - 18 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 12 * (26 - 18 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 12 * (27 - 18 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 12 * (28 - 18 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 12 * (29 - 18 + 0.5f)),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
} };
|
||||
|
||||
// ISO/IEC 11172-3 (2.4.3.4.10.3 d)
|
||||
AK::Array<float, 36> WindowBlockTypeShort { {
|
||||
AK::sin<float>(AK::Pi<float> / 12 * (0 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 12 * (1 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 12 * (2 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 12 * (3 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 12 * (4 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 12 * (5 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 12 * (6 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 12 * (7 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 12 * (8 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 12 * (9 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 12 * (10 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 12 * (11 + 0.5f)),
|
||||
|
||||
AK::sin<float>(AK::Pi<float> / 12 * (0 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 12 * (1 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 12 * (2 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 12 * (3 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 12 * (4 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 12 * (5 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 12 * (6 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 12 * (7 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 12 * (8 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 12 * (9 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 12 * (10 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 12 * (11 + 0.5f)),
|
||||
|
||||
AK::sin<float>(AK::Pi<float> / 12 * (0 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 12 * (1 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 12 * (2 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 12 * (3 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 12 * (4 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 12 * (5 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 12 * (6 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 12 * (7 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 12 * (8 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 12 * (9 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 12 * (10 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 12 * (11 + 0.5f)),
|
||||
} };
|
||||
|
||||
// ISO/IEC 11172-3 (2.4.3.4.10.3 c)
|
||||
AK::Array<float, 36> WindowBlockTypeEnd { {
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
AK::sin<float>(AK::Pi<float> / 12 * (6 - 6 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 12 * (7 - 6 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 12 * (8 - 6 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 12 * (9 - 6 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 12 * (10 - 6 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 12 * (11 - 6 + 0.5f)),
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (18 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (19 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (20 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (21 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (22 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (23 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (24 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (25 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (26 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (27 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (28 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (29 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (30 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (31 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (32 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (33 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (34 + 0.5f)),
|
||||
AK::sin<float>(AK::Pi<float> / 36 * (35 + 0.5f)),
|
||||
} };
|
||||
|
||||
// ISO/IEC 11172-3 (Table B.3)
|
||||
AK::Array<float, 512> WindowSynthesis {
|
||||
0.000000000, -0.000015259, -0.000015259, -0.000015259, -0.000015259, -0.000015259, -0.000015259, -0.000030518,
|
||||
-0.000030518, -0.000030518, -0.000030518, -0.000045776, -0.000045776, -0.000061035, -0.000061035, -0.000076294,
|
||||
-0.000076294, -0.000091553, -0.000106812, -0.000106812, -0.000122070, -0.000137329, -0.000152588, -0.000167847,
|
||||
-0.000198364, -0.000213623, -0.000244141, -0.000259399, -0.000289917, -0.000320435, -0.000366211, -0.000396729,
|
||||
-0.000442505, -0.000473022, -0.000534058, -0.000579834, -0.000625610, -0.000686646, -0.000747681, -0.000808716,
|
||||
-0.000885010, -0.000961304, -0.001037598, -0.001113892, -0.001205444, -0.001296997, -0.001388550, -0.001480103,
|
||||
-0.001586914, -0.001693726, -0.001785278, -0.001907349, -0.002014160, -0.002120972, -0.002243042, -0.002349854,
|
||||
-0.002456665, -0.002578735, -0.002685547, -0.002792358, -0.002899170, -0.002990723, -0.003082275, -0.003173828,
|
||||
0.003250122, 0.003326416, 0.003387451, 0.003433228, 0.003463745, 0.003479004, 0.003479004, 0.003463745,
|
||||
0.003417969, 0.003372192, 0.003280640, 0.003173828, 0.003051758, 0.002883911, 0.002700806, 0.002487183,
|
||||
0.002227783, 0.001937866, 0.001617432, 0.001266479, 0.000869751, 0.000442505, -0.000030518, -0.000549316,
|
||||
-0.001098633, -0.001693726, -0.002334595, -0.003005981, -0.003723145, -0.004486084, -0.005294800, -0.006118774,
|
||||
-0.007003784, -0.007919312, -0.008865356, -0.009841919, -0.010848999, -0.011886597, -0.012939453, -0.014022827,
|
||||
-0.015121460, -0.016235352, -0.017349243, -0.018463135, -0.019577026, -0.020690918, -0.021789551, -0.022857666,
|
||||
-0.023910522, -0.024932861, -0.025909424, -0.026840210, -0.027725220, -0.028533936, -0.029281616, -0.029937744,
|
||||
-0.030532837, -0.031005859, -0.031387329, -0.031661987, -0.031814575, -0.031845093, -0.031738281, -0.031478882,
|
||||
0.031082153, 0.030517578, 0.029785156, 0.028884888, 0.027801514, 0.026535034, 0.025085449, 0.023422241,
|
||||
0.021575928, 0.019531250, 0.017257690, 0.014801025, 0.012115479, 0.009231567, 0.006134033, 0.002822876,
|
||||
-0.000686646, -0.004394531, -0.008316040, -0.012420654, -0.016708374, -0.021179199, -0.025817871, -0.030609131,
|
||||
-0.035552979, -0.040634155, -0.045837402, -0.051132202, -0.056533813, -0.061996460, -0.067520142, -0.073059082,
|
||||
-0.078628540, -0.084182739, -0.089706421, -0.095169067, -0.100540161, -0.105819702, -0.110946655, -0.115921021,
|
||||
-0.120697021, -0.125259399, -0.129562378, -0.133590698, -0.137298584, -0.140670776, -0.143676758, -0.146255493,
|
||||
-0.148422241, -0.150115967, -0.151306152, -0.151962280, -0.152069092, -0.151596069, -0.150497437, -0.148773193,
|
||||
-0.146362305, -0.143264771, -0.139450073, -0.134887695, -0.129577637, -0.123474121, -0.116577148, -0.108856201,
|
||||
0.100311279, 0.090927124, 0.080688477, 0.069595337, 0.057617187, 0.044784546, 0.031082153, 0.016510010,
|
||||
0.001068115, -0.015228271, -0.032379150, -0.050354004, -0.069168091, -0.088775635, -0.109161377, -0.130310059,
|
||||
-0.152206421, -0.174789429, -0.198059082, -0.221984863, -0.246505737, -0.271591187, -0.297210693, -0.323318481,
|
||||
-0.349868774, -0.376800537, -0.404083252, -0.431655884, -0.459472656, -0.487472534, -0.515609741, -0.543823242,
|
||||
-0.572036743, -0.600219727, -0.628295898, -0.656219482, -0.683914185, -0.711318970, -0.738372803, -0.765029907,
|
||||
-0.791213989, -0.816864014, -0.841949463, -0.866363525, -0.890090942, -0.913055420, -0.935195923, -0.956481934,
|
||||
-0.976852417, -0.996246338, -1.014617920, -1.031936646, -1.048156738, -1.063217163, -1.077117920, -1.089782715,
|
||||
-1.101211548, -1.111373901, -1.120223999, -1.127746582, -1.133926392, -1.138763428, -1.142211914, -1.144287109,
|
||||
1.144989014, 1.144287109, 1.142211914, 1.138763428, 1.133926392, 1.127746582, 1.120223999, 1.111373901,
|
||||
1.101211548, 1.089782715, 1.077117920, 1.063217163, 1.048156738, 1.031936646, 1.014617920, 0.996246338,
|
||||
0.976852417, 0.956481934, 0.935195923, 0.913055420, 0.890090942, 0.866363525, 0.841949463, 0.816864014,
|
||||
0.791213989, 0.765029907, 0.738372803, 0.711318970, 0.683914185, 0.656219482, 0.628295898, 0.600219727,
|
||||
0.572036743, 0.543823242, 0.515609741, 0.487472534, 0.459472656, 0.431655884, 0.404083252, 0.376800537,
|
||||
0.349868774, 0.323318481, 0.297210693, 0.271591187, 0.246505737, 0.221984863, 0.198059082, 0.174789429,
|
||||
0.152206421, 0.130310059, 0.109161377, 0.088775635, 0.069168091, 0.050354004, 0.032379150, 0.015228271,
|
||||
-0.001068115, -0.016510010, -0.031082153, -0.044784546, -0.057617187, -0.069595337, -0.080688477, -0.090927124,
|
||||
0.100311279, 0.108856201, 0.116577148, 0.123474121, 0.129577637, 0.134887695, 0.139450073, 0.143264771,
|
||||
0.146362305, 0.148773193, 0.150497437, 0.151596069, 0.152069092, 0.151962280, 0.151306152, 0.150115967,
|
||||
0.148422241, 0.146255493, 0.143676758, 0.140670776, 0.137298584, 0.133590698, 0.129562378, 0.125259399,
|
||||
0.120697021, 0.115921021, 0.110946655, 0.105819702, 0.100540161, 0.095169067, 0.089706421, 0.084182739,
|
||||
0.078628540, 0.073059082, 0.067520142, 0.061996460, 0.056533813, 0.051132202, 0.045837402, 0.040634155,
|
||||
0.035552979, 0.030609131, 0.025817871, 0.021179199, 0.016708374, 0.012420654, 0.008316040, 0.004394531,
|
||||
0.000686646, -0.002822876, -0.006134033, -0.009231567, -0.012115479, -0.014801025, -0.017257690, -0.019531250,
|
||||
-0.021575928, -0.023422241, -0.025085449, -0.026535034, -0.027801514, -0.028884888, -0.029785156, -0.030517578,
|
||||
0.031082153, 0.031478882, 0.031738281, 0.031845093, 0.031814575, 0.031661987, 0.031387329, 0.031005859,
|
||||
0.030532837, 0.029937744, 0.029281616, 0.028533936, 0.027725220, 0.026840210, 0.025909424, 0.024932861,
|
||||
0.023910522, 0.022857666, 0.021789551, 0.020690918, 0.019577026, 0.018463135, 0.017349243, 0.016235352,
|
||||
0.015121460, 0.014022827, 0.012939453, 0.011886597, 0.010848999, 0.009841919, 0.008865356, 0.007919312,
|
||||
0.007003784, 0.006118774, 0.005294800, 0.004486084, 0.003723145, 0.003005981, 0.002334595, 0.001693726,
|
||||
0.001098633, 0.000549316, 0.000030518, -0.000442505, -0.000869751, -0.001266479, -0.001617432, -0.001937866,
|
||||
-0.002227783, -0.002487183, -0.002700806, -0.002883911, -0.003051758, -0.003173828, -0.003280640, -0.003372192,
|
||||
-0.003417969, -0.003463745, -0.003479004, -0.003479004, -0.003463745, -0.003433228, -0.003387451, -0.003326416,
|
||||
0.003250122, 0.003173828, 0.003082275, 0.002990723, 0.002899170, 0.002792358, 0.002685547, 0.002578735,
|
||||
0.002456665, 0.002349854, 0.002243042, 0.002120972, 0.002014160, 0.001907349, 0.001785278, 0.001693726,
|
||||
0.001586914, 0.001480103, 0.001388550, 0.001296997, 0.001205444, 0.001113892, 0.001037598, 0.000961304,
|
||||
0.000885010, 0.000808716, 0.000747681, 0.000686646, 0.000625610, 0.000579834, 0.000534058, 0.000473022,
|
||||
0.000442505, 0.000396729, 0.000366211, 0.000320435, 0.000289917, 0.000259399, 0.000244141, 0.000213623,
|
||||
0.000198364, 0.000167847, 0.000152588, 0.000137329, 0.000122070, 0.000106812, 0.000106812, 0.000091553,
|
||||
0.000076294, 0.000076294, 0.000061035, 0.000061035, 0.000045776, 0.000045776, 0.000030518, 0.000030518,
|
||||
0.000030518, 0.000030518, 0.000015259, 0.000015259, 0.000015259, 0.000015259, 0.000015259, 0.000015259
|
||||
};
|
||||
|
||||
// ISO/IEC 11172-3 (2.4.3.2.2)
|
||||
// cos((16 + i) * (2 * k + 1) * pi / 64.0), k=0..31, i=0..63
|
||||
Array<Array<float, 32>, 64> SynthesisSubbandFilterCoefficients = {
|
||||
Array<float, 32> { { 0.7071067811865476, -0.7071067811865475, -0.7071067811865477, 0.7071067811865474, 0.7071067811865477, -0.7071067811865467, -0.7071067811865471, 0.7071067811865466, 0.7071067811865472, -0.7071067811865465, -0.7071067811865474, 0.7071067811865464, 0.7071067811865475, -0.7071067811865464, -0.7071067811865476, 0.7071067811865462, 0.7071067811865476, -0.7071067811865461, -0.7071067811865477, 0.707106781186546, 0.7071067811865503, -0.707106781186546, -0.7071067811865479, 0.7071067811865483, 0.7071067811865505, -0.7071067811865458, -0.707106781186548, 0.7071067811865482, 0.7071067811865507, -0.7071067811865456, -0.7071067811865482, 0.707106781186548 } },
|
||||
Array<float, 32> { { 0.6715589548470183, -0.8032075314806448, -0.5141027441932218, 0.9039892931234431, 0.33688985339222005, -0.9700312531945441, -0.14673047445536166, 0.9987954562051724, -0.04906767432741729, -0.9891765099647811, 0.24298017990326243, 0.9415440651830208, -0.42755509343028003, -0.8577286100002726, 0.5956993044924337, 0.7409511253549602, -0.7409511253549589, -0.5956993044924354, 0.8577286100002715, 0.4275550934302851, -0.9415440651830214, -0.24298017990326443, 0.9891765099647806, 0.04906767432742292, -0.9987954562051724, 0.1467304744553596, 0.9700312531945451, -0.3368898533922206, -0.903989293123444, 0.5141027441932186, 0.8032075314806442, -0.6715589548470177 } },
|
||||
Array<float, 32> { { 0.6343932841636455, -0.8819212643483549, -0.29028467725446244, 0.9951847266721969, -0.09801714032955997, -0.9569403357322087, 0.47139673682599736, 0.7730104533627377, -0.773010453362737, -0.4713967368259983, 0.9569403357322089, 0.09801714032956282, -0.995184726672197, 0.2902846772544622, 0.8819212643483563, -0.6343932841636443, -0.6343932841636459, 0.8819212643483553, 0.29028467725446433, -0.9951847266721968, 0.09801714032956063, 0.9569403357322086, -0.47139673682599326, -0.7730104533627394, 0.7730104533627351, 0.4713967368259993, -0.9569403357322086, -0.09801714032956038, 0.9951847266721968, -0.2902846772544578, -0.8819212643483568, 0.6343932841636434 } },
|
||||
Array<float, 32> { { 0.5956993044924335, -0.9415440651830207, -0.04906767432741803, 0.9700312531945441, -0.5141027441932214, -0.6715589548470181, 0.903989293123443, 0.1467304744553618, -0.9891765099647811, 0.42755509343028014, 0.7409511253549601, -0.8577286100002717, -0.24298017990326395, 0.9987954562051724, -0.33688985339221794, -0.8032075314806458, 0.8032075314806444, 0.33688985339222016, -0.9987954562051723, 0.24298017990326515, 0.8577286100002729, -0.7409511253549561, -0.4275550934302822, 0.9891765099647806, -0.146730474455363, -0.903989293123444, 0.6715589548470151, 0.5141027441932219, -0.9700312531945433, 0.04906767432741926, 0.9415440651830214, -0.5956993044924298 } },
|
||||
Array<float, 32> { { 0.5555702330196023, -0.9807852804032304, 0.1950903220161283, 0.8314696123025455, -0.8314696123025451, -0.19509032201612803, 0.9807852804032307, -0.5555702330196015, -0.5555702330196026, 0.9807852804032304, -0.19509032201612858, -0.8314696123025449, 0.8314696123025438, 0.19509032201613036, -0.9807852804032308, 0.5555702330196011, 0.5555702330196061, -0.9807852804032297, 0.19509032201612447, 0.8314696123025471, -0.8314696123025435, -0.19509032201613097, 0.9807852804032309, -0.5555702330196005, -0.5555702330196036, 0.9807852804032302, -0.19509032201612736, -0.8314696123025456, 0.8314696123025451, 0.19509032201612808, -0.9807852804032303, 0.555570233019603 } },
|
||||
Array<float, 32> { { 0.5141027441932217, -0.9987954562051724, 0.42755509343028214, 0.5956993044924332, -0.989176509964781, 0.3368898533922202, 0.6715589548470182, -0.9700312531945441, 0.24298017990326243, 0.7409511253549601, -0.9415440651830203, 0.14673047445536033, 0.8032075314806457, -0.9039892931234428, 0.04906767432741668, 0.8577286100002728, -0.8577286100002696, -0.04906767432741925, 0.9039892931234453, -0.8032075314806442, -0.1467304744553664, 0.9415440651830211, -0.740951125354956, -0.24298017990326493, 0.9700312531945451, -0.6715589548470177, -0.3368898533922243, 0.9891765099647811, -0.5956993044924298, -0.42755509343028286, 0.9987954562051726, -0.5141027441932149 } },
|
||||
Array<float, 32> { { 0.4713967368259978, -0.9951847266721969, 0.6343932841636456, 0.29028467725446255, -0.9569403357322087, 0.773010453362737, 0.09801714032956081, -0.8819212643483562, 0.8819212643483555, -0.09801714032956124, -0.7730104533627368, 0.9569403357322088, -0.2902846772544621, -0.6343932841636459, 0.9951847266721968, -0.4713967368259935, -0.47139673682599587, 0.9951847266721967, -0.6343932841636466, -0.2902846772544613, 0.9569403357322086, -0.7730104533627373, -0.09801714032956038, 0.881921264348355, -0.8819212643483548, 0.0980171403295599, 0.7730104533627375, -0.9569403357322085, 0.29028467725446083, 0.634393284163647, -0.9951847266721959, 0.47139673682598915 } },
|
||||
Array<float, 32> { { 0.4275550934302822, -0.970031253194544, 0.803207531480645, -0.04906767432741754, -0.7409511253549599, 0.989176509964781, -0.5141027441932212, -0.33688985339221955, 0.9415440651830208, -0.8577286100002717, 0.14673047445536033, 0.6715589548470199, -0.9987954562051723, 0.5956993044924335, 0.24298017990326776, -0.9039892931234438, 0.9039892931234441, -0.2429801799032616, -0.5956993044924329, 0.9987954562051726, -0.6715589548470179, -0.14673047445536663, 0.8577286100002731, -0.941544065183021, 0.33688985339221694, 0.5141027441932221, -0.9891765099647817, 0.740951125354958, 0.04906767432741681, -0.8032075314806467, 0.9700312531945422, -0.42755509343028464 } },
|
||||
Array<float, 32> { { 0.38268343236508984, -0.9238795325112868, 0.9238795325112865, -0.3826834323650899, -0.38268343236509056, 0.9238795325112867, -0.9238795325112864, 0.38268343236508956, 0.3826834323650909, -0.9238795325112876, 0.9238795325112868, -0.3826834323650892, -0.3826834323650912, 0.9238795325112877, -0.9238795325112854, 0.38268343236508556, 0.3826834323650883, -0.9238795325112865, 0.9238795325112866, -0.3826834323650885, -0.38268343236509195, 0.9238795325112881, -0.9238795325112851, 0.3826834323650849, 0.382683432365089, -0.9238795325112868, 0.9238795325112863, -0.3826834323650813, -0.3826834323650926, 0.9238795325112856, -0.9238795325112849, 0.3826834323650908 } },
|
||||
Array<float, 32> { { 0.33688985339222005, -0.8577286100002721, 0.9891765099647809, -0.6715589548470177, 0.04906767432741742, 0.5956993044924335, -0.9700312531945443, 0.9039892931234429, -0.42755509343028003, -0.24298017990326395, 0.8032075314806457, -0.9987954562051723, 0.7409511253549588, -0.1467304744553635, -0.5141027441932244, 0.941544065183021, -0.9415440651830213, 0.5141027441932188, 0.146730474455363, -0.7409511253549584, 0.9987954562051726, -0.8032075314806439, 0.24298017990326443, 0.42755509343028597, -0.9039892931234442, 0.9700312531945441, -0.5956993044924352, -0.04906767432742757, 0.6715589548470239, -0.9891765099647817, 0.8577286100002706, -0.33688985339221944 } },
|
||||
Array<float, 32> { { 0.29028467725446233, -0.7730104533627371, 0.9951847266721969, -0.8819212643483548, 0.47139673682599736, 0.09801714032956081, -0.6343932841636456, 0.9569403357322094, -0.9569403357322089, 0.6343932841636444, -0.09801714032956099, -0.47139673682599875, 0.8819212643483564, -0.9951847266721968, 0.7730104533627352, -0.2902846772544582, -0.2902846772544613, 0.7730104533627373, -0.9951847266721972, 0.8819212643483533, -0.4713967368259928, -0.09801714032956063, 0.6343932841636468, -0.9569403357322098, 0.9569403357322074, -0.6343932841636404, 0.09801714032955235, 0.47139673682599387, -0.8819212643483538, 0.9951847266721969, -0.7730104533627365, 0.2902846772544601 } },
|
||||
Array<float, 32> { { 0.24298017990326398, -0.6715589548470187, 0.9415440651830209, -0.989176509964781, 0.8032075314806448, -0.4275550934302818, -0.04906767432741852, 0.5141027441932239, -0.8577286100002726, 0.9987954562051724, -0.9039892931234428, 0.5956993044924335, -0.1467304744553635, -0.3368898533922236, 0.7409511253549605, -0.9700312531945442, 0.9700312531945442, -0.740951125354956, 0.33688985339221716, 0.14673047445536325, -0.5956993044924334, 0.9039892931234457, -0.9987954562051722, 0.8577286100002709, -0.5141027441932149, 0.049067674327418764, 0.4275550934302864, -0.8032075314806426, 0.9891765099647812, -0.9415440651830184, 0.6715589548470194, -0.24298017990325993 } },
|
||||
Array<float, 32> { { 0.19509032201612833, -0.5555702330196022, 0.8314696123025455, -0.9807852804032307, 0.9807852804032304, -0.831469612302545, 0.5555702330196015, -0.19509032201612858, -0.19509032201613025, 0.5555702330196028, -0.831469612302545, 0.9807852804032309, -0.9807852804032297, 0.8314696123025456, -0.5555702330196007, 0.19509032201612425, 0.1950903220161276, -0.5555702330196036, 0.8314696123025475, -0.9807852804032303, 0.9807852804032301, -0.8314696123025431, 0.555570233019603, -0.1950903220161269, -0.19509032201612497, 0.5555702330196073, -0.8314696123025459, 0.9807852804032298, -0.9807852804032293, 0.8314696123025446, -0.5555702330196052, 0.19509032201612256 } },
|
||||
Array<float, 32> { { 0.14673047445536175, -0.4275550934302825, 0.6715589548470188, -0.8577286100002723, 0.9700312531945443, -0.9987954562051724, 0.9415440651830204, -0.8032075314806446, 0.5956993044924337, -0.33688985339221794, 0.04906767432741668, 0.24298017990326776, -0.5141027441932244, 0.7409511253549605, -0.9039892931234439, 0.989176509964781, -0.989176509964781, 0.9039892931234439, -0.7409511253549558, 0.5141027441932183, -0.24298017990326087, -0.04906767432742023, 0.33688985339222133, -0.5956993044924337, 0.8032075314806446, -0.9415440651830204, 0.9987954562051723, -0.9700312531945448, 0.8577286100002668, -0.6715589548470114, 0.4275550934302745, -0.14673047445535428 } },
|
||||
Array<float, 32> { { 0.09801714032956077, -0.29028467725446244, 0.471396736825998, -0.6343932841636454, 0.7730104533627377, -0.8819212643483562, 0.9569403357322094, -0.995184726672197, 0.9951847266721968, -0.9569403357322088, 0.8819212643483553, -0.7730104533627375, 0.6343932841636439, -0.47139673682599326, 0.2902846772544615, -0.09801714032955673, -0.09801714032956038, 0.29028467725446505, -0.4713967368259965, 0.6343932841636468, -0.7730104533627399, 0.8819212643483553, -0.9569403357322078, 0.9951847266721968, -0.9951847266721966, 0.9569403357322073, -0.881921264348351, 0.7730104533627387, -0.6343932841636453, 0.47139673682599476, -0.29028467725445634, 0.09801714032955137 } },
|
||||
Array<float, 32> { { 0.049067674327418126, -0.1467304744553623, 0.24298017990326423, -0.336889853392221, 0.4275550934302828, -0.5141027441932238, 0.595699304492435, -0.6715589548470199, 0.7409511253549602, -0.8032075314806458, 0.8577286100002728, -0.9039892931234438, 0.941544065183021, -0.9700312531945442, 0.989176509964781, -0.9987954562051724, 0.9987954562051724, -0.989176509964781, 0.9700312531945441, -0.941544065183021, 0.9039892931234437, -0.8577286100002726, 0.8032075314806414, -0.7409511253549601, 0.6715589548470144, -0.5956993044924349, 0.5141027441932174, -0.4275550934302842, 0.3368898533922158, -0.2429801799032666, 0.14673047445535767, -0.049067674327421214 } },
|
||||
Array<float, 32> { { 6.123233995736766e-17, -1.8369701987210297e-16, 3.061616997868383e-16, -4.286263797015736e-16, 5.51091059616309e-16, -2.4499125789312946e-15, -9.803364199544708e-16, -2.6948419387607653e-15, -7.354070601250002e-16, -2.939771298590236e-15, -4.904777002955296e-16, -3.1847006584197066e-15, -2.45548340466059e-16, -3.4296300182491773e-15, -6.189806365883577e-19, -3.674559378078648e-15, 2.443103791928823e-16, -3.919488737908119e-15, 4.892397390223529e-16, -4.164418097737589e-15, 7.839596456452825e-15, -4.40934745756706e-15, 9.790984586812941e-16, 2.4511505402044715e-15, 8.329455176111767e-15, -4.899206177226001e-15, 1.4689571783402355e-15, 1.96129182054553e-15, 8.819313895770708e-15, -5.389064896884942e-15, 1.9588158979991767e-15, 1.471433100886589e-15 } },
|
||||
Array<float, 32> { { -0.04906767432741801, 0.14673047445536194, -0.2429801799032628, 0.3368898533922202, -0.4275550934302818, 0.5141027441932227, -0.5956993044924338, 0.6715589548470184, -0.7409511253549589, 0.8032075314806444, -0.8577286100002696, 0.9039892931234441, -0.9415440651830213, 0.9700312531945442, -0.989176509964781, 0.9987954562051724, -0.9987954562051724, 0.9891765099647811, -0.9700312531945443, 0.9415440651830214, -0.9039892931234473, 0.8577286100002698, -0.8032075314806426, 0.7409511253549568, -0.6715589548470162, 0.5956993044924314, -0.51410274419322, 0.42755509343028064, -0.336889853392219, 0.24298017990326326, -0.14673047445536155, 0.04906767432741827 } },
|
||||
Array<float, 32> { { -0.09801714032956065, 0.29028467725446205, -0.4713967368259975, 0.6343932841636447, -0.773010453362737, 0.8819212643483555, -0.9569403357322089, 0.9951847266721968, -0.995184726672197, 0.9569403357322095, -0.8819212643483565, 0.7730104533627371, -0.634393284163649, 0.4713967368259993, -0.2902846772544615, 0.09801714032956405, 0.0980171403295599, -0.29028467725445756, 0.47139673682599564, -0.6343932841636404, 0.773010453362739, -0.8819212643483545, 0.9569403357322073, -0.9951847266721959, 0.9951847266721969, -0.9569403357322102, 0.8819212643483592, -0.7730104533627362, 0.6343932841636479, -0.47139673682600425, 0.2902846772544601, -0.09801714032956259 } },
|
||||
Array<float, 32> { { -0.14673047445536164, 0.42755509343028214, -0.6715589548470177, 0.8577286100002719, -0.9700312531945441, 0.9987954562051724, -0.9415440651830209, 0.8032075314806457, -0.5956993044924354, 0.33688985339222016, -0.04906767432741925, -0.2429801799032616, 0.5141027441932188, -0.740951125354956, 0.9039892931234439, -0.989176509964781, 0.9891765099647811, -0.9039892931234442, 0.7409511253549612, -0.5141027441932254, 0.2429801799032623, 0.04906767432741142, -0.33688985339221944, 0.5956993044924263, -0.8032075314806432, 0.9415440651830218, -0.9987954562051722, 0.9700312531945438, -0.8577286100002759, 0.6715589548470194, -0.42755509343029086, 0.14673047445536544 } },
|
||||
Array<float, 32> { { -0.1950903220161282, 0.5555702330196018, -0.8314696123025451, 0.9807852804032304, -0.9807852804032307, 0.8314696123025448, -0.5555702330196027, 0.19509032201613036, 0.19509032201612822, -0.555570233019601, 0.8314696123025456, -0.9807852804032295, 0.9807852804032309, -0.8314696123025455, 0.5555702330196066, -0.19509032201613144, -0.19509032201612714, 0.555570233019603, -0.8314696123025429, 0.9807852804032301, -0.9807852804032304, 0.831469612302544, -0.5555702330196105, 0.19509032201613602, 0.19509032201612256, -0.5555702330195991, 0.8314696123025443, -0.9807852804032305, 0.98078528040323, -0.8314696123025506, 0.5555702330196085, -0.1950903220161336 } },
|
||||
Array<float, 32> { { -0.24298017990326387, 0.6715589548470183, -0.9415440651830205, 0.9891765099647811, -0.8032075314806455, 0.4275550934302814, 0.049067674327416926, -0.5141027441932223, 0.8577286100002715, -0.9987954562051723, 0.9039892931234453, -0.5956993044924329, 0.146730474455363, 0.33688985339221716, -0.7409511253549558, 0.9700312531945441, -0.9700312531945443, 0.7409511253549612, -0.33688985339221805, -0.14673047445536205, 0.5956993044924321, -0.9039892931234419, 0.9987954562051726, -0.8577286100002757, 0.5141027441932292, -0.04906767432742855, -0.42755509343028375, 0.8032075314806449, -0.9891765099647807, 0.941544065183022, -0.6715589548470223, 0.24298017990327087 } },
|
||||
Array<float, 32> { { -0.29028467725446216, 0.7730104533627367, -0.9951847266721969, 0.8819212643483553, -0.4713967368259983, -0.09801714032956124, 0.6343932841636444, -0.9569403357322088, 0.9569403357322095, -0.6343932841636489, 0.09801714032956356, 0.47139673682599625, -0.8819212643483549, 0.9951847266721968, -0.7730104533627398, 0.2902846772544653, 0.29028467725446083, -0.7730104533627368, 0.9951847266721963, -0.8819212643483538, 0.47139673682600036, 0.09801714032955186, -0.6343932841636453, 0.9569403357322072, -0.9569403357322082, 0.6343932841636479, -0.09801714032956942, -0.47139673682599736, 0.8819212643483522, -0.9951847266721966, 0.773010453362739, -0.2902846772544709 } },
|
||||
Array<float, 32> { { -0.33688985339221994, 0.857728610000272, -0.9891765099647811, 0.6715589548470182, -0.04906767432741852, -0.5956993044924338, 0.9700312531945435, -0.9039892931234437, 0.4275550934302851, 0.24298017990326515, -0.8032075314806442, 0.9987954562051726, -0.7409511253549584, 0.14673047445536325, 0.5141027441932183, -0.941544065183021, 0.9415440651830214, -0.5141027441932254, -0.14673047445536205, 0.7409511253549529, -0.9987954562051722, 0.8032075314806449, -0.24298017990327322, -0.4275550934302776, 0.9039892931234431, -0.9700312531945464, 0.5956993044924377, 0.0490676743274173, -0.6715589548470108, 0.9891765099647801, -0.8577286100002726, 0.33688985339223004 } },
|
||||
Array<float, 32> { { -0.3826834323650897, 0.9238795325112865, -0.9238795325112867, 0.38268343236509067, 0.38268343236508956, -0.923879532511287, 0.9238795325112876, -0.3826834323650912, -0.382683432365089, 0.9238795325112867, -0.9238795325112865, 0.3826834323650885, 0.3826834323650851, -0.9238795325112851, 0.9238795325112881, -0.3826834323650924, -0.3826834323650813, 0.9238795325112835, -0.9238795325112897, 0.3826834323650962, 0.382683432365084, -0.9238795325112846, 0.9238795325112886, -0.3826834323650935, -0.38268343236508673, 0.9238795325112857, -0.9238795325112874, 0.3826834323650908, 0.38268343236508945, -0.9238795325112868, 0.9238795325112863, -0.38268343236508806 } },
|
||||
Array<float, 32> { { -0.42755509343028186, 0.970031253194544, -0.8032075314806454, 0.0490676743274184, 0.7409511253549591, -0.9891765099647809, 0.5141027441932241, 0.33688985339221783, -0.9415440651830214, 0.8577286100002729, -0.1467304744553664, -0.6715589548470179, 0.9987954562051726, -0.5956993044924334, -0.24298017990326087, 0.9039892931234437, -0.9039892931234473, 0.2429801799032623, 0.5956993044924321, -0.9987954562051722, 0.6715589548470242, 0.14673047445536494, -0.8577286100002721, 0.9415440651830218, -0.33688985339222594, -0.5141027441932137, 0.9891765099647812, -0.7409511253549601, -0.04906767432741338, 0.8032075314806403, -0.9700312531945466, 0.427555093430282 } },
|
||||
Array<float, 32> { { -0.4713967368259977, 0.9951847266721969, -0.6343932841636454, -0.29028467725446255, 0.9569403357322089, -0.7730104533627368, -0.09801714032956099, 0.8819212643483553, -0.8819212643483565, 0.09801714032956356, 0.7730104533627351, -0.9569403357322097, 0.29028467725446505, 0.6343932841636434, -0.9951847266721972, 0.4713967368259999, 0.47139673682598915, -0.9951847266721966, 0.6343932841636528, 0.2902846772544601, -0.9569403357322062, 0.7730104533627383, 0.09801714032955137, -0.881921264348354, 0.8819212643483594, -0.09801714032956259, -0.7730104533627312, 0.9569403357322094, -0.2902846772544709, -0.6343932841636442, 0.9951847266721977, -0.47139673682601163 } },
|
||||
Array<float, 32> { { -0.5141027441932217, 0.9987954562051724, -0.4275550934302827, -0.5956993044924326, 0.9891765099647809, -0.33688985339221983, -0.6715589548470184, 0.9700312531945441, -0.24298017990326443, -0.7409511253549561, 0.9415440651830211, -0.14673047445536663, -0.8032075314806439, 0.9039892931234457, -0.04906767432742023, -0.8577286100002726, 0.8577286100002698, 0.04906767432741142, -0.9039892931234419, 0.8032075314806449, 0.14673047445536494, -0.9415440651830181, 0.7409511253549621, 0.2429801799032628, -0.9700312531945445, 0.6715589548470249, 0.33688985339221483, -0.9891765099647807, 0.5956993044924325, 0.42755509343027315, -0.9987954562051727, 0.5141027441932245 } },
|
||||
Array<float, 32> { { -0.555570233019602, 0.9807852804032304, -0.19509032201612803, -0.831469612302545, 0.8314696123025448, 0.19509032201612844, -0.9807852804032303, 0.5555702330196061, 0.5555702330196038, -0.9807852804032302, 0.1950903220161276, 0.8314696123025452, -0.8314696123025456, -0.19509032201612714, 0.9807852804032301, -0.5555702330196102, -0.5555702330196056, 0.9807852804032298, -0.19509032201612544, -0.8314696123025465, 0.8314696123025443, 0.1950903220161293, -0.9807852804032305, 0.5555702330196024, 0.5555702330196015, -0.9807852804032308, 0.19509032201613025, 0.8314696123025438, -0.831469612302547, -0.19509032201612447, 0.9807852804032268, -0.5555702330196183 } },
|
||||
Array<float, 32> { { -0.5956993044924334, 0.9415440651830209, 0.04906767432741742, -0.9700312531945441, 0.5141027441932239, 0.6715589548470184, -0.9039892931234437, -0.1467304744553635, 0.9891765099647806, -0.4275550934302822, -0.740951125354956, 0.8577286100002731, 0.24298017990326443, -0.9987954562051722, 0.33688985339222133, 0.8032075314806414, -0.8032075314806426, -0.33688985339221944, 0.9987954562051726, -0.24298017990327322, -0.8577286100002721, 0.7409511253549621, 0.42755509343027404, -0.9891765099647809, 0.14673047445536544, 0.9039892931234398, -0.6715589548470173, -0.5141027441932191, 0.9700312531945459, -0.04906767432741582, -0.9415440651830153, 0.5956993044924388 } },
|
||||
Array<float, 32> { { -0.6343932841636454, 0.881921264348355, 0.29028467725446266, -0.9951847266721969, 0.09801714032956282, 0.9569403357322088, -0.47139673682599875, -0.7730104533627375, 0.7730104533627371, 0.47139673682599625, -0.9569403357322097, -0.09801714032955648, 0.9951847266721964, -0.290284677254462, -0.8819212643483513, 0.6343932841636472, 0.6343932841636483, -0.8819212643483573, -0.2902846772544634, 0.9951847266721976, -0.09801714032956209, -0.956940335732206, 0.47139673682600125, 0.7730104533627381, -0.7730104533627412, -0.4713967368259969, 0.9569403357322115, 0.09801714032955722, -0.9951847266721972, 0.2902846772544681, 0.8819212643483483, -0.6343932841636412 } },
|
||||
Array<float, 32> { { -0.6715589548470184, 0.8032075314806453, 0.5141027441932213, -0.9039892931234434, -0.33688985339221816, 0.970031253194544, 0.1467304744553601, -0.9987954562051724, 0.04906767432742292, 0.9891765099647806, -0.24298017990326493, -0.941544065183021, 0.42755509343028597, 0.8577286100002709, -0.5956993044924337, -0.7409511253549601, 0.7409511253549568, 0.5956993044924263, -0.8577286100002757, -0.4275550934302776, 0.9415440651830218, 0.2429801799032628, -0.9891765099647809, -0.04906767432742072, 0.998795456205172, -0.1467304744553693, -0.9700312531945426, 0.3368898533922236, 0.9039892931234365, -0.5141027441932339, -0.8032075314806376, 0.671558954847026 } },
|
||||
Array<float, 32> { { -0.7071067811865475, 0.7071067811865477, 0.7071067811865466, -0.7071067811865474, -0.7071067811865464, 0.7071067811865476, 0.707106781186546, -0.7071067811865479, -0.7071067811865458, 0.7071067811865507, 0.707106781186548, -0.7071067811865483, -0.7071067811865452, 0.7071067811865511, 0.7071067811865425, -0.7071067811865539, -0.7071067811865498, 0.7071067811865467, 0.707106781186547, -0.7071067811865495, -0.7071067811865442, 0.7071067811865522, 0.7071067811865415, -0.707106781186555, -0.7071067811865487, 0.7071067811865477, 0.707106781186546, -0.7071067811865606, -0.7071067811865432, 0.7071067811865432, 0.7071067811865405, -0.707106781186546 } },
|
||||
Array<float, 32> { { -0.7409511253549589, 0.5956993044924332, 0.8577286100002719, -0.4275550934302813, -0.9415440651830203, 0.2429801799032641, 0.9891765099647806, -0.04906767432741925, -0.9987954562051724, -0.146730474455363, 0.9700312531945451, 0.33688985339221694, -0.9039892931234442, -0.5141027441932149, 0.8032075314806446, 0.6715589548470144, -0.6715589548470162, -0.8032075314806432, 0.5141027441932292, 0.9039892931234431, -0.33688985339222594, -0.9700312531945445, 0.14673047445536544, 0.998795456205172, 0.04906767432741681, -0.989176509964782, -0.24298017990326515, 0.9415440651830271, 0.4275550934302855, -0.8577286100002731, -0.595699304492427, 0.7409511253549683 } },
|
||||
Array<float, 32> { { -0.773010453362737, 0.471396736825998, 0.9569403357322085, -0.0980171403295627, -0.995184726672197, -0.2902846772544621, 0.8819212643483564, 0.6343932841636439, -0.634393284163649, -0.8819212643483549, 0.29028467725446505, 0.9951847266721964, 0.09801714032955966, -0.9569403357322078, -0.47139673682599215, 0.7730104533627381, 0.7730104533627387, -0.47139673682600386, -0.9569403357322082, 0.09801714032955867, 0.9951847266721977, 0.29028467725445917, -0.8819212643483545, -0.6343932841636388, 0.6343932841636487, 0.8819212643483552, -0.2902846772544578, -0.995184726672195, -0.098017140329546, 0.9569403357322118, 0.4713967368259926, -0.7730104533627378 } },
|
||||
Array<float, 32> { { -0.8032075314806448, 0.33688985339222005, 0.9987954562051724, 0.24298017990326243, -0.8577286100002726, -0.7409511253549589, 0.4275550934302851, 0.9891765099647806, 0.1467304744553596, -0.903989293123444, -0.6715589548470177, 0.5141027441932221, 0.9700312531945441, 0.049067674327418764, -0.9415440651830204, -0.5956993044924349, 0.5956993044924314, 0.9415440651830218, -0.04906767432742855, -0.9700312531945464, -0.5141027441932137, 0.6715589548470249, 0.9039892931234398, -0.1467304744553693, -0.989176509964782, -0.42755509343027626, 0.7409511253549631, 0.857728610000262, -0.24298017990326848, -0.9987954562051733, -0.3368898533922167, 0.8032075314806552 } },
|
||||
Array<float, 32> { { -0.8314696123025453, 0.19509032201612878, 0.9807852804032307, 0.5555702330196015, -0.5555702330196027, -0.9807852804032303, -0.19509032201612808, 0.8314696123025471, 0.8314696123025455, -0.19509032201613122, -0.9807852804032303, -0.5555702330196002, 0.5555702330196071, 0.9807852804032301, 0.19509032201612303, -0.83146961230255, -0.8314696123025465, 0.19509032201612928, 0.9807852804032313, 0.5555702330195958, -0.5555702330196114, -0.9807852804032304, -0.19509032201612497, 0.8314696123025489, 0.8314696123025397, -0.1950903220161413, -0.9807852804032337, -0.5555702330196093, 0.5555702330195978, 0.9807852804032309, 0.1950903220161269, -0.8314696123025479 } },
|
||||
Array<float, 32> { { -0.857728610000272, 0.049067674327418154, 0.9039892931234434, 0.8032075314806447, -0.14673047445536216, -0.9415440651830209, -0.7409511253549563, 0.242980179903268, 0.9700312531945451, 0.6715589548470151, -0.3368898533922243, -0.9891765099647817, -0.5956993044924352, 0.4275550934302864, 0.9987954562051723, 0.5141027441932174, -0.51410274419322, -0.9987954562051722, -0.42755509343028375, 0.5956993044924377, 0.9891765099647812, 0.33688985339221483, -0.6715589548470173, -0.9700312531945426, -0.24298017990326515, 0.7409511253549631, 0.9415440651830211, 0.1467304744553698, -0.8032075314806528, -0.9039892931234407, -0.049067674327418764, 0.8577286100002681 } },
|
||||
Array<float, 32> { { -0.8819212643483549, -0.09801714032955997, 0.7730104533627377, 0.9569403357322089, 0.2902846772544622, -0.6343932841636459, -0.9951847266721968, -0.47139673682599326, 0.4713967368259993, 0.9951847266721968, 0.6343932841636434, -0.290284677254462, -0.9569403357322078, -0.7730104533627321, 0.09801714032956502, 0.8819212643483556, 0.8819212643483558, 0.09801714032955137, -0.7730104533627409, -0.9569403357322079, -0.29028467725446244, 0.634393284163654, 0.9951847266721962, 0.4713967368259935, -0.47139673682601163, -0.9951847266721967, -0.634393284163638, 0.29028467725445495, 0.9569403357322098, 0.7730104533627278, -0.0980171403295577, -0.8819212643483589 } },
|
||||
Array<float, 32> { { -0.9039892931234433, -0.2429801799032628, 0.5956993044924335, 0.9987954562051724, 0.6715589548470184, -0.14673047445536241, -0.857728610000271, -0.9415440651830213, -0.3368898533922206, 0.5141027441932219, 0.9891765099647811, 0.740951125354958, -0.04906767432742757, -0.8032075314806426, -0.9700312531945448, -0.4275550934302842, 0.42755509343028064, 0.9700312531945438, 0.8032075314806449, 0.0490676743274173, -0.7409511253549601, -0.9891765099647807, -0.5141027441932191, 0.3368898533922236, 0.9415440651830271, 0.857728610000262, 0.1467304744553698, -0.6715589548470129, -0.9987954562051727, -0.595699304492438, 0.24298017990325896, 0.9039892931234415 } },
|
||||
Array<float, 32> { { -0.9238795325112867, -0.3826834323650899, 0.38268343236509067, 0.9238795325112875, 0.9238795325112868, 0.3826834323650891, -0.38268343236509145, -0.9238795325112865, -0.9238795325112852, -0.3826834323650883, 0.382683432365089, 0.9238795325112882, 0.9238795325112835, 0.3826834323650908, -0.38268343236509306, -0.9238795325112898, -0.9238795325112873, -0.38268343236508673, 0.3826834323650971, 0.9238795325112862, 0.9238795325112856, 0.3826834323650826, -0.38268343236508806, -0.9238795325112878, -0.9238795325112893, -0.38268343236507857, 0.38268343236509217, 0.9238795325112841, 0.9238795325112822, 0.3826834323650876, -0.38268343236508306, -0.9238795325112912 } },
|
||||
Array<float, 32> { { -0.9415440651830207, -0.5141027441932214, 0.1467304744553618, 0.7409511253549601, 0.9987954562051724, 0.8032075314806444, 0.24298017990326515, -0.4275550934302822, -0.903989293123444, -0.9700312531945433, -0.5956993044924298, 0.04906767432741681, 0.6715589548470239, 0.9891765099647812, 0.8577286100002668, 0.3368898533922158, -0.336889853392219, -0.8577286100002759, -0.9891765099647807, -0.6715589548470108, -0.04906767432741338, 0.5956993044924325, 0.9700312531945459, 0.9039892931234365, 0.4275550934302855, -0.24298017990326848, -0.8032075314806528, -0.9987954562051727, -0.7409511253549578, -0.14673047445535137, 0.5141027441932381, 0.9415440651830205 } },
|
||||
Array<float, 32> { { -0.9569403357322088, -0.6343932841636448, -0.09801714032955972, 0.4713967368259984, 0.8819212643483563, 0.9951847266721968, 0.7730104533627352, 0.2902846772544615, -0.2902846772544615, -0.7730104533627398, -0.9951847266721972, -0.8819212643483513, -0.47139673682599215, 0.09801714032956502, 0.6343932841636476, 0.9569403357322092, 0.9569403357322092, 0.6343932841636476, 0.09801714032955088, -0.4713967368260047, -0.8819212643483579, -0.9951847266721965, -0.7730104533627352, -0.2902846772544615, 0.2902846772544615, 0.7730104533627352, 0.9951847266721965, 0.8819212643483579, 0.47139673682597966, -0.09801714032957916, -0.6343932841636586, -0.9569403357322133 } },
|
||||
Array<float, 32> { { -0.970031253194544, -0.7409511253549593, -0.3368898533922201, 0.14673047445536203, 0.5956993044924352, 0.9039892931234438, 0.9987954562051724, 0.8577286100002712, 0.5141027441932186, 0.04906767432741926, -0.42755509343028286, -0.8032075314806467, -0.9891765099647817, -0.9415440651830184, -0.6715589548470114, -0.2429801799032666, 0.24298017990326326, 0.6715589548470194, 0.941544065183022, 0.9891765099647801, 0.8032075314806403, 0.42755509343027315, -0.04906767432741582, -0.5141027441932339, -0.8577286100002731, -0.9987954562051733, -0.9039892931234407, -0.595699304492438, -0.14673047445535137, 0.33688985339221855, 0.740951125354969, 0.9700312531945446 } },
|
||||
Array<float, 32> { { -0.9807852804032304, -0.8314696123025451, -0.5555702330196015, -0.19509032201612858, 0.19509032201613036, 0.5555702330196061, 0.8314696123025471, 0.9807852804032309, 0.9807852804032302, 0.8314696123025451, 0.555570233019603, 0.19509032201613025, -0.19509032201613216, -0.5555702330196105, -0.8314696123025462, -0.980785280403232, -0.9807852804032305, -0.8314696123025421, -0.5555702330196044, -0.19509032201612497, 0.19509032201613746, 0.5555702330196032, 0.8314696123025413, 0.9807852804032302, 0.9807852804032294, 0.8314696123025391, 0.5555702330195881, 0.1950903220161336, -0.1950903220161288, -0.5555702330196076, -0.8314696123025522, -0.9807852804032341 } },
|
||||
Array<float, 32> { { -0.989176509964781, -0.9039892931234431, -0.7409511253549592, -0.5141027441932225, -0.24298017990326207, 0.04906767432742268, 0.3368898533922204, 0.5956993044924359, 0.8032075314806442, 0.9415440651830214, 0.9987954562051726, 0.9700312531945422, 0.8577286100002706, 0.6715589548470194, 0.4275550934302745, 0.14673047445535767, -0.14673047445536155, -0.42755509343029086, -0.6715589548470223, -0.8577286100002726, -0.9700312531945466, -0.9987954562051727, -0.9415440651830153, -0.8032075314806376, -0.595699304492427, -0.3368898533922167, -0.049067674327418764, 0.24298017990325896, 0.5141027441932381, 0.740951125354969, 0.9039892931234478, 0.9891765099647819 } },
|
||||
Array<float, 32> { { -0.9951847266721968, -0.9569403357322085, -0.8819212643483547, -0.7730104533627357, -0.6343932841636443, -0.4713967368259935, -0.2902846772544582, -0.09801714032955673, 0.09801714032956405, 0.2902846772544653, 0.4713967368259999, 0.6343932841636472, 0.7730104533627381, 0.8819212643483556, 0.9569403357322092, 0.9951847266721969, 0.9951847266721969, 0.9569403357322089, 0.8819212643483554, 0.7730104533627378, 0.6343932841636468, 0.47139673682599953, 0.29028467725445123, 0.09801714032956356, -0.09801714032957136, -0.2902846772544587, -0.4713967368260064, -0.6343932841636418, -0.7730104533627428, -0.8819212643483524, -0.9569403357322113, -0.9951847266721963 } },
|
||||
Array<float, 32> { { -0.9987954562051724, -0.989176509964781, -0.9700312531945441, -0.9415440651830203, -0.9039892931234428, -0.8577286100002696, -0.8032075314806442, -0.740951125354956, -0.6715589548470177, -0.5956993044924298, -0.5141027441932149, -0.42755509343028464, -0.33688985339221944, -0.24298017990325993, -0.14673047445535428, -0.049067674327421214, 0.04906767432741827, 0.14673047445536544, 0.24298017990327087, 0.33688985339223004, 0.427555093430282, 0.5141027441932245, 0.5956993044924388, 0.671558954847026, 0.7409511253549683, 0.8032075314806552, 0.8577286100002681, 0.9039892931234415, 0.9415440651830205, 0.9700312531945446, 0.9891765099647819, 0.9987954562051728 } },
|
||||
Array<float, 32> { { -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0 } },
|
||||
Array<float, 32> { { -0.9987954562051724, -0.9891765099647811, -0.9700312531945443, -0.9415440651830209, -0.9039892931234437, -0.857728610000271, -0.803207531480646, -0.7409511253549584, -0.6715589548470208, -0.5956993044924335, -0.5141027441932254, -0.4275550934302833, -0.33688985339221855, -0.24298017990327322, -0.14673047445536833, -0.0490676743274217, 0.0490676743274173, 0.14673047445536397, 0.24298017990325518, 0.3368898533922144, 0.42755509343027936, 0.5141027441932094, 0.5956993044924357, 0.6715589548470122, 0.7409511253549459, 0.8032075314806435, 0.857728610000265, 0.9039892931234449, 0.9415440651830181, 0.9700312531945394, 0.9891765099647807, 0.9987954562051717 } },
|
||||
Array<float, 32> { { -0.9951847266721969, -0.9569403357322087, -0.8819212643483562, -0.7730104533627368, -0.6343932841636459, -0.47139673682599587, -0.2902846772544613, -0.09801714032956038, 0.0980171403295599, 0.29028467725446083, 0.47139673682598915, 0.6343932841636483, 0.7730104533627387, 0.8819212643483558, 0.9569403357322092, 0.9951847266721969, 0.9951847266721969, 0.9569403357322094, 0.8819212643483564, 0.7730104533627393, 0.63439328416366, 0.47139673682599, 0.29028467725445495, 0.0980171403295538, -0.09801714032956649, -0.29028467725446716, -0.47139673682600125, -0.6343932841636479, -0.7730104533627383, -0.8819212643483556, -0.9569403357322089, -0.9951847266721968 } },
|
||||
Array<float, 32> { { -0.989176509964781, -0.9039892931234433, -0.74095112535496, -0.514102744193224, -0.2429801799032642, 0.049067674327419986, 0.3368898533922174, 0.5956993044924329, 0.8032075314806417, 0.9415440651830198, 0.9987954562051724, 0.9700312531945453, 0.8577286100002701, 0.6715589548470191, 0.4275550934302873, 0.1467304744553722, -0.14673047445536058, -0.4275550934302767, -0.6715589548470104, -0.857728610000264, -0.9700312531945425, -0.9987954562051722, -0.9415440651830261, -0.8032075314806487, -0.5956993044924309, -0.33688985339223515, -0.049067674327424635, 0.2429801799032666, 0.5141027441932078, 0.7409511253549544, 0.9039892931234444, 0.9891765099647786 } },
|
||||
Array<float, 32> { { -0.9807852804032304, -0.8314696123025456, -0.5555702330196026, -0.19509032201613025, 0.19509032201612822, 0.5555702330196038, 0.8314696123025455, 0.9807852804032302, 0.980785280403231, 0.8314696123025477, 0.5555702330196073, 0.1950903220161288, -0.1950903220161192, -0.5555702330195991, -0.8314696123025462, -0.9807852804032291, -0.9807852804032308, -0.8314696123025509, -0.5555702330196061, -0.1950903220161413, 0.19509032201613458, 0.5555702330196003, 0.8314696123025391, 0.9807852804032267, 0.9807852804032304, 0.83146961230255, 0.5555702330196166, 0.19509032201612592, -0.1950903220161221, -0.5555702330195897, -0.8314696123025479, -0.9807852804032297 } },
|
||||
Array<float, 32> { { -0.970031253194544, -0.7409511253549599, -0.33688985339221955, 0.14673047445536033, 0.5956993044924335, 0.9039892931234441, 0.9987954562051726, 0.8577286100002731, 0.5141027441932221, 0.04906767432741681, -0.42755509343028464, -0.8032075314806391, -0.9891765099647798, -0.9415440651830229, -0.671558954847022, -0.24298017990326706, 0.2429801799032623, 0.6715589548470184, 0.9415440651830214, 0.9891765099647826, 0.8032075314806505, 0.4275550934302891, -0.049067674327411916, -0.5141027441932179, -0.8577286100002706, -0.9987954562051723, -0.9039892931234431, -0.5956993044924317, -0.14673047445535817, 0.336889853392225, 0.7409511253549447, 0.9700312531945392 } },
|
||||
Array<float, 32> { { -0.9569403357322089, -0.6343932841636454, -0.0980171403295627, 0.4713967368259969, 0.8819212643483553, 0.9951847266721967, 0.7730104533627373, 0.29028467725446505, -0.29028467725445756, -0.7730104533627368, -0.9951847266721966, -0.8819212643483573, -0.47139673682600386, 0.09801714032955137, 0.6343932841636476, 0.9569403357322089, 0.9569403357322094, 0.6343932841636487, 0.09801714032956697, -0.47139673682599, -0.8819212643483566, -0.9951847266721981, -0.7730104533627378, -0.2902846772544793, 0.29028467725445684, 0.7730104533627409, 0.9951847266721959, 0.8819212643483543, 0.47139673682601074, -0.0980171403295577, -0.6343932841636305, -0.9569403357322067 } },
|
||||
Array<float, 32> { { -0.9415440651830208, -0.514102744193222, 0.14673047445536058, 0.7409511253549589, 0.9987954562051723, 0.8032075314806439, 0.24298017990326823, -0.4275550934302789, -0.9039892931234422, -0.9700312531945444, -0.5956993044924396, 0.04906767432741828, 0.671558954847014, 0.9891765099647812, 0.8577286100002741, 0.3368898533922296, -0.33688985339221805, -0.8577286100002678, -0.989176509964781, -0.6715589548470231, -0.049067674327430505, 0.5956993044924184, 0.9700312531945449, 0.9039892931234444, 0.42755509343028997, -0.24298017990324947, -0.8032075314806324, -0.9987954562051723, -0.7409511253549624, -0.1467304744553727, 0.514102744193207, 0.9415440651830225 } },
|
||||
Array<float, 32> { { -0.9238795325112868, -0.38268343236509056, 0.38268343236508956, 0.9238795325112868, 0.9238795325112877, 0.3826834323650883, -0.3826834323650885, -0.9238795325112851, -0.9238795325112868, -0.3826834323650926, 0.3826834323650908, 0.9238795325112833, 0.9238795325112886, 0.38268343236509034, -0.3826834323650799, -0.9238795325112843, -0.9238795325112876, -0.38268343236508806, 0.3826834323650822, 0.9238795325112852, 0.9238795325112867, 0.3826834323650858, -0.38268343236507135, -0.9238795325112807, -0.9238795325112912, -0.38268343236509667, 0.38268343236508673, 0.9238795325112871, 0.9238795325112849, 0.38268343236510755, -0.38268343236507585, -0.9238795325112826 } },
|
||||
Array<float, 32> { { -0.9039892931234434, -0.24298017990326348, 0.5956993044924339, 0.9987954562051723, 0.6715589548470174, -0.14673047445536325, -0.8577286100002693, -0.9415440651830225, -0.33688985339222455, 0.5141027441932179, 0.9891765099647803, 0.7409511253549618, -0.04906767432741436, -0.8032075314806429, -0.9700312531945448, -0.42755509343028464, 0.4275550934302798, 0.9700312531945434, 0.8032075314806546, 0.04906767432741974, -0.7409511253549486, -0.9891765099647811, -0.5141027441932347, 0.33688985339221944, 0.9415440651830159, 0.8577286100002721, 0.1467304744553756, -0.6715589548470188, -0.9987954562051731, -0.5956993044924325, 0.24298017990325138, 0.903989293123444 } },
|
||||
Array<float, 32> { { -0.881921264348355, -0.09801714032956069, 0.7730104533627358, 0.9569403357322095, 0.29028467725446433, -0.6343932841636466, -0.9951847266721972, -0.4713967368259965, 0.47139673682599564, 0.9951847266721963, 0.6343932841636528, -0.2902846772544634, -0.9569403357322082, -0.7730104533627409, 0.09801714032955088, 0.8819212643483554, 0.8819212643483564, 0.09801714032956697, -0.7730104533627397, -0.9569403357322087, -0.2902846772544653, 0.6343932841636404, 0.9951847266721979, 0.4713967368260099, -0.4713967368259822, -0.9951847266721948, -0.6343932841636426, 0.29028467725446244, 0.9569403357322078, 0.7730104533627414, -0.0980171403295499, -0.8819212643483483 } },
|
||||
Array<float, 32> { { -0.8577286100002721, 0.04906767432741742, 0.9039892931234429, 0.8032075314806457, -0.1467304744553635, -0.9415440651830213, -0.7409511253549584, 0.24298017990326443, 0.9700312531945441, 0.6715589548470239, -0.33688985339221944, -0.9891765099647798, -0.5956993044924345, 0.42755509343027404, 0.9987954562051723, 0.5141027441932301, -0.5141027441932191, -0.998795456205173, -0.4275550934302855, 0.5956993044924357, 0.9891765099647838, 0.33688985339223143, -0.6715589548470144, -0.9700312531945436, -0.2429801799032837, 0.7409511253549499, 0.9415440651830231, 0.14673047445536203, -0.8032075314806318, -0.9039892931234499, -0.04906767432742659, 0.8577286100002711 } },
|
||||
Array<float, 32> { { -0.8314696123025455, 0.1950903220161272, 0.9807852804032304, 0.5555702330196028, -0.555570233019601, -0.9807852804032302, -0.19509032201613122, 0.8314696123025451, 0.8314696123025477, -0.19509032201611967, -0.9807852804032293, -0.5555702330196048, 0.555570233019602, 0.98078528040323, 0.195090322016137, -0.8314696123025419, -0.831469612302547, 0.19509032201612786, 0.9807852804032281, 0.5555702330195978, -0.5555702330195971, -0.9807852804032339, -0.1950903220161288, 0.8314696123025386, 0.8314696123025425, -0.1950903220161221, -0.980785280403227, -0.5555702330196027, 0.5555702330195922, 0.9807852804032294, 0.19509032201613458, -0.8314696123025354 } },
|
||||
Array<float, 32> { { -0.8032075314806449, 0.3368898533922202, 0.9987954562051724, 0.2429801799032641, -0.8577286100002696, -0.7409511253549583, 0.4275550934302822, 0.9891765099647811, 0.14673047445537077, -0.903989293123445, -0.6715589548470162, 0.5141027441932233, 0.9700312531945438, 0.04906767432741827, -0.9415440651830204, -0.5956993044924352, 0.5956993044924306, 0.9415440651830271, -0.0490676743274266, -0.9700312531945459, -0.5141027441932162, 0.6715589548470224, 0.9039892931234415, -0.14673047445536494, -0.9891765099647812, -0.4275550934302811, 0.7409511253549591, 0.8577286100002726, -0.24298017990326182, -0.9987954562051722, -0.33688985339222405, 0.8032075314806417 } },
|
||||
Array<float, 32> { { -0.7730104533627371, 0.47139673682599736, 0.9569403357322094, -0.09801714032956099, -0.9951847266721968, -0.2902846772544613, 0.8819212643483533, 0.6343932841636468, -0.6343932841636404, -0.8819212643483538, 0.2902846772544601, 0.9951847266721976, 0.09801714032955867, -0.9569403357322079, -0.4713967368260047, 0.7730104533627378, 0.7730104533627393, -0.47139673682599, -0.9569403357322087, 0.0980171403295421, 0.9951847266721959, 0.29028467725446244, -0.881921264348346, -0.6343932841636533, 0.6343932841636449, 0.8819212643483644, -0.2902846772544521, -0.995184726672197, -0.09801714032958111, 0.9569403357322056, 0.47139673682599953, -0.7730104533627234 } },
|
||||
Array<float, 32> { { -0.7409511253549591, 0.5956993044924327, 0.8577286100002725, -0.4275550934302798, -0.9415440651830222, 0.24298017990326493, 0.9891765099647811, -0.049067674327415586, -0.9987954562051722, -0.14673047445536058, 0.9700312531945421, 0.3368898533922222, -0.9039892931234447, -0.5141027441932267, 0.8032075314806446, 0.6715589548470253, -0.6715589548470154, -0.803207531480644, 0.5141027441932153, 0.9039892931234503, -0.33688985339222316, -0.9700312531945453, 0.1467304744553475, 0.9987954562051722, 0.0490676743274217, -0.9891765099647791, -0.24298017990328463, 0.9415440651830201, 0.42755509343029174, -0.857728610000262, -0.5956993044924334, 0.7409511253549532 } },
|
||||
};
|
||||
|
||||
}
|
|
@ -1,107 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Arne Elster <arne@elster.li>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Array.h>
|
||||
#include <AK/EnumBits.h>
|
||||
#include <AK/FixedArray.h>
|
||||
|
||||
namespace Audio::MP3 {
|
||||
|
||||
constexpr size_t const frame_size = 1152;
|
||||
// 576 samples.
|
||||
constexpr size_t const granule_size = frame_size / 2;
|
||||
|
||||
enum class Mode {
|
||||
Stereo = 0,
|
||||
JointStereo = 1,
|
||||
DualChannel = 2,
|
||||
SingleChannel = 3,
|
||||
};
|
||||
|
||||
enum class ModeExtension {
|
||||
Stereo = 0,
|
||||
IntensityStereo = 1,
|
||||
MsStereo = 2,
|
||||
};
|
||||
AK_ENUM_BITWISE_OPERATORS(ModeExtension)
|
||||
|
||||
enum class Emphasis {
|
||||
None = 0,
|
||||
Microseconds_50_15 = 1,
|
||||
Reserved = 2,
|
||||
CCITT_J17 = 3,
|
||||
};
|
||||
|
||||
enum class BlockType {
|
||||
Normal = 0,
|
||||
Start = 1,
|
||||
Short = 2,
|
||||
End = 3,
|
||||
};
|
||||
|
||||
struct Header {
|
||||
i32 id { 0 };
|
||||
i32 layer { 0 };
|
||||
bool protection_bit { false };
|
||||
i32 bitrate { 0 };
|
||||
i32 samplerate { 0 };
|
||||
bool padding_bit { false };
|
||||
bool private_bit { false };
|
||||
Mode mode { Mode::Stereo };
|
||||
ModeExtension mode_extension { ModeExtension::Stereo };
|
||||
bool copyright_bit { false };
|
||||
bool original_bit { false };
|
||||
Emphasis emphasis { Emphasis::None };
|
||||
u16 crc16 { 0 };
|
||||
size_t header_size { 0 };
|
||||
size_t frame_size { 0 };
|
||||
size_t slot_count { 0 };
|
||||
|
||||
size_t channel_count() const { return mode == Mode::SingleChannel ? 1 : 2; }
|
||||
};
|
||||
|
||||
struct Granule {
|
||||
Array<float, MP3::granule_size> samples;
|
||||
Array<Array<float, 18>, 32> filter_bank_input;
|
||||
Array<Array<float, 32>, 18> pcm;
|
||||
u32 part_2_3_length { 0 };
|
||||
u32 big_values { 0 };
|
||||
u32 global_gain { 0 };
|
||||
u32 scalefac_compress { 0 };
|
||||
bool window_switching_flag { false };
|
||||
BlockType block_type { BlockType::Normal };
|
||||
bool mixed_block_flag { false };
|
||||
Array<int, 3> table_select;
|
||||
Array<int, 3> sub_block_gain;
|
||||
u32 region0_count { 0 };
|
||||
u32 region1_count { 0 };
|
||||
bool preflag { false };
|
||||
bool scalefac_scale { false };
|
||||
bool count1table_select { false };
|
||||
};
|
||||
|
||||
struct Channel {
|
||||
Array<Granule, 2> granules;
|
||||
Array<int, 39> scale_factors;
|
||||
Array<int, 4> scale_factor_selection_info;
|
||||
};
|
||||
|
||||
struct MP3Frame {
|
||||
Header header;
|
||||
FixedArray<Channel> channels;
|
||||
off_t main_data_begin { 0 };
|
||||
u32 private_bits { 0 };
|
||||
|
||||
MP3Frame(Header header)
|
||||
: header(header)
|
||||
, channels(FixedArray<Channel>::must_create_but_fixme_should_propagate_errors(header.channel_count()))
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
}
|
|
@ -1,82 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2023, kleines Filmröllchen <filmroellchen@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "Metadata.h"
|
||||
#include <AK/Assertions.h>
|
||||
#include <LibCore/Version.h>
|
||||
|
||||
namespace Audio {
|
||||
|
||||
bool Person::is_artist() const
|
||||
{
|
||||
return role == Person::Role::Artist
|
||||
|| role == Person::Role::Composer
|
||||
|| role == Person::Role::Conductor
|
||||
|| role == Person::Role::Lyricist
|
||||
|| role == Person::Role::Performer;
|
||||
}
|
||||
|
||||
Optional<StringView> Person::name_for_role() const
|
||||
{
|
||||
switch (role) {
|
||||
case Role::Artist:
|
||||
case Role::Performer:
|
||||
return {};
|
||||
case Role::Lyricist:
|
||||
return "Lyricist"sv;
|
||||
case Role::Conductor:
|
||||
return "Conductor"sv;
|
||||
case Role::Publisher:
|
||||
return "Publisher"sv;
|
||||
case Role::Engineer:
|
||||
return "Engineer"sv;
|
||||
case Role::Composer:
|
||||
return "Composer"sv;
|
||||
}
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
Optional<String> Metadata::first_artist() const
|
||||
{
|
||||
auto artist = people.find_if([](auto const& person) { return person.is_artist(); });
|
||||
if (artist.is_end())
|
||||
return {};
|
||||
return artist->name;
|
||||
}
|
||||
|
||||
ErrorOr<Optional<String>> Metadata::all_artists(StringView concatenate_with) const
|
||||
{
|
||||
// FIXME: This entire function could be similar to TRY(TRY(people.filter(...).try_map(...)).join(concatenate_with)) if these functional iterator transformers existed :^)
|
||||
Vector<String> artist_texts;
|
||||
TRY(artist_texts.try_ensure_capacity(people.size()));
|
||||
for (auto const& person : people) {
|
||||
if (!person.is_artist())
|
||||
continue;
|
||||
if (auto role_name = person.name_for_role(); role_name.has_value())
|
||||
artist_texts.unchecked_append(TRY(String::formatted("{} ({})", person.name, role_name.release_value())));
|
||||
else
|
||||
artist_texts.unchecked_append(person.name);
|
||||
}
|
||||
if (artist_texts.is_empty())
|
||||
return Optional<String> {};
|
||||
return String::join(concatenate_with, artist_texts);
|
||||
}
|
||||
|
||||
ErrorOr<void> Metadata::add_miscellaneous(String const& field, String value)
|
||||
{
|
||||
// FIXME: Since try_ensure does not return a reference to the contained value, we have to retrieve it separately.
|
||||
// This is a try_ensure bug that should be fixed.
|
||||
(void)TRY(miscellaneous.try_ensure(field, []() { return Vector<String> {}; }));
|
||||
auto& values_for_field = miscellaneous.get(field).release_value();
|
||||
return values_for_field.try_append(move(value));
|
||||
}
|
||||
|
||||
ErrorOr<void> Metadata::add_person(Person::Role role, String name)
|
||||
{
|
||||
return people.try_append(Person { role, move(name) });
|
||||
}
|
||||
|
||||
}
|
|
@ -1,68 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2023, kleines Filmröllchen <filmroellchen@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/HashMap.h>
|
||||
#include <AK/LexicalPath.h>
|
||||
#include <AK/Optional.h>
|
||||
#include <AK/String.h>
|
||||
#include <AK/Time.h>
|
||||
#include <AK/Variant.h>
|
||||
|
||||
namespace Audio {
|
||||
|
||||
struct Person {
|
||||
enum class Role {
|
||||
Artist,
|
||||
Performer,
|
||||
Lyricist,
|
||||
Conductor,
|
||||
Publisher,
|
||||
Engineer,
|
||||
Composer,
|
||||
};
|
||||
Role role;
|
||||
String name;
|
||||
|
||||
// Whether this person has creative involvement with the song (so not only Role::Artist!).
|
||||
// This list is subjective and is intended to keep the artist display text in applications relevant.
|
||||
// It is used for first_artist and all_artists in Metadata.
|
||||
bool is_artist() const;
|
||||
|
||||
Optional<StringView> name_for_role() const;
|
||||
};
|
||||
|
||||
// Audio metadata of the original format must be equivalently reconstructible from this struct.
|
||||
// That means, (if the format allows it) fields can appear in a different order, but all fields must be present with the original values,
|
||||
// including duplicate fields where allowed by the format.
|
||||
struct Metadata {
|
||||
using Year = unsigned;
|
||||
|
||||
ErrorOr<void> add_miscellaneous(String const& field, String value);
|
||||
ErrorOr<void> add_person(Person::Role role, String name);
|
||||
Optional<String> first_artist() const;
|
||||
ErrorOr<Optional<String>> all_artists(StringView concatenate_with = ", "sv) const;
|
||||
|
||||
Optional<String> title;
|
||||
Optional<String> subtitle;
|
||||
Optional<unsigned> track_number;
|
||||
Optional<String> album;
|
||||
Optional<String> genre;
|
||||
Optional<String> comment;
|
||||
Optional<String> isrc;
|
||||
Optional<String> encoder;
|
||||
Optional<String> copyright;
|
||||
Optional<float> bpm;
|
||||
// FIXME: Until the time data structure situation is solved in a good way, we don't parse ISO 8601 time specifications.
|
||||
Optional<String> unparsed_time;
|
||||
Vector<Person> people;
|
||||
|
||||
// Any other metadata, using the format-specific field names. This ensures reproducibility.
|
||||
HashMap<String, Vector<String>> miscellaneous;
|
||||
};
|
||||
|
||||
}
|
|
@ -1,103 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2023, kleines Filmröllchen <filmroellchen@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Sample.h"
|
||||
#include <AK/Concepts.h>
|
||||
#include <AK/FixedArray.h>
|
||||
|
||||
namespace Audio {
|
||||
|
||||
// Downmixes any number of channels to stereo, under the assumption that standard channel layout is followed:
|
||||
// 1 channel = mono
|
||||
// 2 channels = stereo (left, right)
|
||||
// 3 channels = left, right, center
|
||||
// 4 channels = front left/right, back left/right
|
||||
// 5 channels = front left/right, center, back left/right
|
||||
// 6 channels = front left/right, center, LFE, back left/right
|
||||
// 7 channels = front left/right, center, LFE, back center, side left/right
|
||||
// 8 channels = front left/right, center, LFE, back left/right, side left/right
|
||||
// Additionally, performs sample rescaling to go from integer samples to floating-point samples.
|
||||
template<ArrayLike<i64> ChannelType, ArrayLike<ChannelType> InputType>
|
||||
ErrorOr<FixedArray<Sample>> downmix_surround_to_stereo(InputType const& input, float sample_scale_factor)
|
||||
{
|
||||
if (input.size() == 0)
|
||||
return Error::from_string_literal("Cannot resample from 0 channels");
|
||||
|
||||
auto channel_count = input.size();
|
||||
auto sample_count = input[0].size();
|
||||
|
||||
FixedArray<Sample> output = TRY(FixedArray<Sample>::create(sample_count));
|
||||
|
||||
// FIXME: We could figure out a better way to mix the channels, possibly spatially, but for now:
|
||||
// - Center and LFE channels are added to both left and right.
|
||||
// - All left channels are added together on the left, all right channels are added together on the right.
|
||||
switch (channel_count) {
|
||||
case 1:
|
||||
for (auto i = 0u; i < sample_count; ++i)
|
||||
output[i] = Sample { input[0][i] * sample_scale_factor };
|
||||
break;
|
||||
case 2:
|
||||
for (auto i = 0u; i < sample_count; ++i)
|
||||
output[i] = Sample {
|
||||
input[0][i] * sample_scale_factor,
|
||||
input[1][i] * sample_scale_factor
|
||||
};
|
||||
break;
|
||||
case 3:
|
||||
for (auto i = 0u; i < sample_count; ++i)
|
||||
output[i] = Sample {
|
||||
input[0][i] * sample_scale_factor + input[2][i] * sample_scale_factor,
|
||||
input[1][i] * sample_scale_factor + input[2][i] * sample_scale_factor
|
||||
};
|
||||
break;
|
||||
case 4:
|
||||
for (auto i = 0u; i < sample_count; ++i)
|
||||
output[i] = Sample {
|
||||
input[0][i] * sample_scale_factor + input[2][i] * sample_scale_factor,
|
||||
input[1][i] * sample_scale_factor + input[3][i] * sample_scale_factor
|
||||
};
|
||||
break;
|
||||
case 5:
|
||||
for (auto i = 0u; i < sample_count; ++i)
|
||||
output[i] = Sample {
|
||||
input[0][i] * sample_scale_factor + input[3][i] * sample_scale_factor + input[2][i] * sample_scale_factor,
|
||||
input[1][i] * sample_scale_factor + input[4][i] * sample_scale_factor + input[2][i] * sample_scale_factor
|
||||
};
|
||||
break;
|
||||
case 6:
|
||||
for (auto i = 0u; i < sample_count; ++i) {
|
||||
output[i] = Sample {
|
||||
input[0][i] * sample_scale_factor + input[4][i] * sample_scale_factor + input[2][i] * sample_scale_factor + input[3][i] * sample_scale_factor,
|
||||
input[1][i] * sample_scale_factor + input[5][i] * sample_scale_factor + input[2][i] * sample_scale_factor + input[3][i] * sample_scale_factor
|
||||
};
|
||||
}
|
||||
break;
|
||||
case 7:
|
||||
for (auto i = 0u; i < sample_count; ++i) {
|
||||
output[i] = Sample {
|
||||
input[0][i] * sample_scale_factor + input[5][i] * sample_scale_factor + input[2][i] * sample_scale_factor + input[3][i] * sample_scale_factor + input[4][i] * sample_scale_factor,
|
||||
input[1][i] * sample_scale_factor + input[6][i] * sample_scale_factor + input[2][i] * sample_scale_factor + input[3][i] * sample_scale_factor + input[4][i] * sample_scale_factor
|
||||
};
|
||||
}
|
||||
break;
|
||||
case 8:
|
||||
for (auto i = 0u; i < sample_count; ++i) {
|
||||
output[i] = Sample {
|
||||
input[0][i] * sample_scale_factor + input[4][i] * sample_scale_factor + input[6][i] * sample_scale_factor + input[2][i] * sample_scale_factor + input[3][i] * sample_scale_factor,
|
||||
input[1][i] * sample_scale_factor + input[5][i] * sample_scale_factor + input[7][i] * sample_scale_factor + input[2][i] * sample_scale_factor + input[3][i] * sample_scale_factor
|
||||
};
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return Error::from_string_literal("Invalid number of channels greater than 8");
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,249 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2023, kleines Filmröllchen <filmroellchen@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "QOALoader.h"
|
||||
#include "Loader.h"
|
||||
#include "LoaderError.h"
|
||||
#include "QOATypes.h"
|
||||
#include <AK/Array.h>
|
||||
#include <AK/Assertions.h>
|
||||
#include <AK/Endian.h>
|
||||
#include <AK/FixedArray.h>
|
||||
#include <AK/MemoryStream.h>
|
||||
#include <AK/Stream.h>
|
||||
#include <AK/Types.h>
|
||||
#include <LibCore/File.h>
|
||||
|
||||
namespace Audio {
|
||||
|
||||
QOALoaderPlugin::QOALoaderPlugin(NonnullOwnPtr<AK::SeekableStream> stream)
|
||||
: LoaderPlugin(move(stream))
|
||||
{
|
||||
}
|
||||
|
||||
bool QOALoaderPlugin::sniff(SeekableStream& stream)
|
||||
{
|
||||
auto maybe_qoa = stream.read_value<BigEndian<u32>>();
|
||||
return !maybe_qoa.is_error() && maybe_qoa.value() == QOA::magic;
|
||||
}
|
||||
|
||||
ErrorOr<NonnullOwnPtr<LoaderPlugin>, LoaderError> QOALoaderPlugin::create(NonnullOwnPtr<SeekableStream> stream)
|
||||
{
|
||||
auto loader = make<QOALoaderPlugin>(move(stream));
|
||||
TRY(loader->initialize());
|
||||
return loader;
|
||||
}
|
||||
|
||||
MaybeLoaderError QOALoaderPlugin::initialize()
|
||||
{
|
||||
TRY(parse_header());
|
||||
TRY(reset());
|
||||
return {};
|
||||
}
|
||||
|
||||
MaybeLoaderError QOALoaderPlugin::parse_header()
|
||||
{
|
||||
u32 header_magic = TRY(m_stream->read_value<BigEndian<u32>>());
|
||||
if (header_magic != QOA::magic)
|
||||
return LoaderError { LoaderError::Category::Format, 0, "QOA header: Magic number must be 'qoaf'" };
|
||||
|
||||
m_total_samples = TRY(m_stream->read_value<BigEndian<u32>>());
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
MaybeLoaderError QOALoaderPlugin::load_one_frame(Span<Sample>& target, IsFirstFrame is_first_frame)
|
||||
{
|
||||
QOA::FrameHeader header = TRY(m_stream->read_value<QOA::FrameHeader>());
|
||||
|
||||
if (header.num_channels > 8)
|
||||
dbgln("QOALoader: Warning: QOA frame at {} has more than 8 channels ({}), this is not supported by the reference implementation.", TRY(m_stream->tell()) - sizeof(QOA::FrameHeader), header.num_channels);
|
||||
if (header.num_channels == 0)
|
||||
return LoaderError { LoaderError::Category::Format, TRY(m_stream->tell()), "QOA frame: Number of channels must be greater than 0" };
|
||||
if (header.sample_count > QOA::max_frame_samples)
|
||||
return LoaderError { LoaderError::Category::Format, TRY(m_stream->tell()), "QOA frame: Too many samples in frame" };
|
||||
|
||||
// We weren't given a large enough buffer; signal that we didn't write anything and return.
|
||||
if (header.sample_count > target.size()) {
|
||||
target = target.trim(0);
|
||||
TRY(m_stream->seek(-sizeof(QOA::frame_header_size), AK::SeekMode::FromCurrentPosition));
|
||||
return {};
|
||||
}
|
||||
|
||||
target = target.trim(header.sample_count);
|
||||
|
||||
auto lms_states = TRY(FixedArray<QOA::LMSState>::create(header.num_channels));
|
||||
for (size_t channel = 0; channel < header.num_channels; ++channel) {
|
||||
auto history_packed = TRY(m_stream->read_value<BigEndian<u64>>());
|
||||
auto weights_packed = TRY(m_stream->read_value<BigEndian<u64>>());
|
||||
lms_states[channel] = { history_packed, weights_packed };
|
||||
}
|
||||
|
||||
// We pre-allocate very large arrays here, but that's the last allocation of the QOA loader!
|
||||
// Everything else is just shuffling data around.
|
||||
// (We will also be using all of the arrays in every frame but the last one.)
|
||||
auto channels = TRY((FixedArray<Array<i16, QOA::max_frame_samples>>::create(header.num_channels)));
|
||||
|
||||
// There's usually (and at maximum) 256 slices per channel, but less at the very end.
|
||||
// If the final slice would be partial, we still need to decode it; integer division would tell us that this final slice doesn't exist.
|
||||
auto const slice_count = static_cast<size_t>(ceil(static_cast<double>(header.sample_count) / static_cast<double>(QOA::slice_samples)));
|
||||
VERIFY(slice_count <= QOA::max_slices_per_frame);
|
||||
|
||||
// Observe the loop nesting: Slices are channel-interleaved.
|
||||
for (size_t slice = 0; slice < slice_count; ++slice) {
|
||||
for (size_t channel = 0; channel < header.num_channels; ++channel) {
|
||||
auto slice_samples = channels[channel].span().slice(slice * QOA::slice_samples, QOA::slice_samples);
|
||||
TRY(read_one_slice(lms_states[channel], slice_samples));
|
||||
}
|
||||
}
|
||||
|
||||
if (is_first_frame == IsFirstFrame::Yes) {
|
||||
m_num_channels = header.num_channels;
|
||||
m_sample_rate = header.sample_rate;
|
||||
} else {
|
||||
if (m_sample_rate != header.sample_rate)
|
||||
return LoaderError { LoaderError::Category::Unimplemented, TRY(m_stream->tell()), "QOA: Differing sample rate in non-initial frame" };
|
||||
if (m_num_channels != header.num_channels)
|
||||
m_has_uniform_channel_count = false;
|
||||
}
|
||||
|
||||
switch (header.num_channels) {
|
||||
case 1:
|
||||
for (size_t sample = 0; sample < header.sample_count; ++sample)
|
||||
target[sample] = Sample { static_cast<float>(channels[0][sample]) / static_cast<float>(NumericLimits<i16>::max()) };
|
||||
break;
|
||||
// FIXME: Combine surround channels sensibly, FlacLoader has the same simplification at the moment.
|
||||
case 2:
|
||||
case 3:
|
||||
case 4:
|
||||
case 5:
|
||||
case 6:
|
||||
case 7:
|
||||
case 8:
|
||||
default:
|
||||
for (size_t sample = 0; sample < header.sample_count; ++sample) {
|
||||
target[sample] = {
|
||||
static_cast<float>(channels[0][sample]) / static_cast<float>(NumericLimits<i16>::max()),
|
||||
static_cast<float>(channels[1][sample]) / static_cast<float>(NumericLimits<i16>::max()),
|
||||
};
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<Vector<FixedArray<Sample>>, LoaderError> QOALoaderPlugin::load_chunks(size_t samples_to_read_from_input)
|
||||
{
|
||||
ssize_t const remaining_samples = static_cast<ssize_t>(m_total_samples - m_loaded_samples);
|
||||
if (remaining_samples <= 0)
|
||||
return Vector<FixedArray<Sample>> {};
|
||||
size_t const samples_to_read = min(samples_to_read_from_input, remaining_samples);
|
||||
auto is_first_frame = m_loaded_samples == 0 ? IsFirstFrame::Yes : IsFirstFrame::No;
|
||||
|
||||
Vector<FixedArray<Sample>> frames;
|
||||
size_t current_loaded_samples = 0;
|
||||
|
||||
while (current_loaded_samples < samples_to_read) {
|
||||
auto samples = TRY(FixedArray<Sample>::create(QOA::max_frame_samples));
|
||||
auto slice_to_load_into = samples.span();
|
||||
TRY(this->load_one_frame(slice_to_load_into, is_first_frame));
|
||||
is_first_frame = IsFirstFrame::No;
|
||||
VERIFY(slice_to_load_into.size() <= QOA::max_frame_samples);
|
||||
current_loaded_samples += slice_to_load_into.size();
|
||||
if (slice_to_load_into.size() != samples.size()) {
|
||||
auto smaller_samples = TRY(FixedArray<Sample>::create(slice_to_load_into));
|
||||
samples.swap(smaller_samples);
|
||||
}
|
||||
TRY(frames.try_append(move(samples)));
|
||||
|
||||
if (slice_to_load_into.size() != samples.size())
|
||||
break;
|
||||
}
|
||||
m_loaded_samples += current_loaded_samples;
|
||||
|
||||
return frames;
|
||||
}
|
||||
|
||||
MaybeLoaderError QOALoaderPlugin::reset()
|
||||
{
|
||||
TRY(m_stream->seek(QOA::header_size, AK::SeekMode::SetPosition));
|
||||
m_loaded_samples = 0;
|
||||
// Read the first frame, then seek back to the beginning. This is necessary since the first frame contains the sample rate and channel count.
|
||||
auto frame_samples = TRY(FixedArray<Sample>::create(QOA::max_frame_samples));
|
||||
auto span = frame_samples.span();
|
||||
TRY(load_one_frame(span, IsFirstFrame::Yes));
|
||||
|
||||
TRY(m_stream->seek(QOA::header_size, AK::SeekMode::SetPosition));
|
||||
m_loaded_samples = 0;
|
||||
return {};
|
||||
}
|
||||
|
||||
MaybeLoaderError QOALoaderPlugin::seek(int sample_index)
|
||||
{
|
||||
if (sample_index == 0 && m_loaded_samples == 0)
|
||||
return {};
|
||||
// A QOA file consists of 8 bytes header followed by a number of usually fixed-size frames.
|
||||
// This fixed bitrate allows us to seek in constant time.
|
||||
if (!m_has_uniform_channel_count)
|
||||
return LoaderError { LoaderError::Category::Unimplemented, TRY(m_stream->tell()), "QOA with non-uniform channel count is currently not seekable"sv };
|
||||
/// FIXME: Change the Loader API to use size_t.
|
||||
VERIFY(sample_index >= 0);
|
||||
// We seek to the frame "before"; i.e. the frame that contains that sample.
|
||||
auto const frame_of_sample = static_cast<size_t>(AK::floor<double>(static_cast<double>(sample_index) / static_cast<double>(QOA::max_frame_samples)));
|
||||
auto const frame_size = QOA::frame_header_size + m_num_channels * (QOA::lms_state_size + sizeof(QOA::PackedSlice) * QOA::max_slices_per_frame);
|
||||
auto const byte_index = QOA::header_size + frame_of_sample * frame_size;
|
||||
TRY(m_stream->seek(byte_index, AK::SeekMode::SetPosition));
|
||||
m_loaded_samples = frame_of_sample * QOA::max_frame_samples;
|
||||
return {};
|
||||
}
|
||||
|
||||
MaybeLoaderError QOALoaderPlugin::read_one_slice(QOA::LMSState& lms_state, Span<i16>& samples)
|
||||
{
|
||||
VERIFY(samples.size() == QOA::slice_samples);
|
||||
|
||||
auto packed_slice = TRY(m_stream->read_value<BigEndian<u64>>());
|
||||
auto unpacked_slice = unpack_slice(packed_slice);
|
||||
|
||||
for (size_t i = 0; i < QOA::slice_samples; ++i) {
|
||||
auto const residual = unpacked_slice.residuals[i];
|
||||
auto const predicted = lms_state.predict();
|
||||
auto const dequantized = QOA::dequantization_table[unpacked_slice.scale_factor_index][residual];
|
||||
auto const reconstructed = clamp(predicted + dequantized, QOA::sample_minimum, QOA::sample_maximum);
|
||||
samples[i] = static_cast<i16>(reconstructed);
|
||||
lms_state.update(reconstructed, dequantized);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
QOA::UnpackedSlice QOALoaderPlugin::unpack_slice(QOA::PackedSlice packed_slice)
|
||||
{
|
||||
size_t const scale_factor_index = (packed_slice >> 60) & 0b1111;
|
||||
Array<u8, 20> residuals = {};
|
||||
auto shifted_slice = packed_slice << 4;
|
||||
|
||||
for (size_t i = 0; i < QOA::slice_samples; ++i) {
|
||||
residuals[i] = static_cast<u8>((shifted_slice >> 61) & 0b111);
|
||||
shifted_slice <<= 3;
|
||||
}
|
||||
|
||||
return {
|
||||
.scale_factor_index = scale_factor_index,
|
||||
.residuals = residuals,
|
||||
};
|
||||
}
|
||||
|
||||
i16 QOALoaderPlugin::qoa_divide(i16 value, i16 scale_factor)
|
||||
{
|
||||
auto const reciprocal = QOA::reciprocal_table[scale_factor];
|
||||
auto const n = (value * reciprocal + (1 << 15)) >> 16;
|
||||
// Rounding away from zero gives better quantization for small values.
|
||||
auto const n_rounded = n + (static_cast<int>(value > 0) - static_cast<int>(value < 0)) - (static_cast<int>(n > 0) - static_cast<int>(n < 0));
|
||||
return static_cast<i16>(n_rounded);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2023, kleines Filmröllchen <filmroellchen@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Loader.h"
|
||||
#include "QOATypes.h"
|
||||
#include "SampleFormats.h"
|
||||
#include <AK/Error.h>
|
||||
#include <AK/NonnullOwnPtr.h>
|
||||
#include <AK/Span.h>
|
||||
#include <AK/Types.h>
|
||||
|
||||
namespace Audio {
|
||||
|
||||
// Decoder for the Quite Okay Audio (QOA) format.
|
||||
// NOTE: The QOA format is not finalized yet and this decoder might not be fully spec-compliant as of 2023-02-02.
|
||||
//
|
||||
// https://github.com/phoboslab/qoa/blob/master/qoa.h
|
||||
class QOALoaderPlugin : public LoaderPlugin {
|
||||
public:
|
||||
explicit QOALoaderPlugin(NonnullOwnPtr<AK::SeekableStream> stream);
|
||||
virtual ~QOALoaderPlugin() override = default;
|
||||
|
||||
static bool sniff(SeekableStream& stream);
|
||||
static ErrorOr<NonnullOwnPtr<LoaderPlugin>, LoaderError> create(NonnullOwnPtr<SeekableStream>);
|
||||
|
||||
virtual ErrorOr<Vector<FixedArray<Sample>>, LoaderError> load_chunks(size_t samples_to_read_from_input) override;
|
||||
|
||||
virtual MaybeLoaderError reset() override;
|
||||
virtual MaybeLoaderError seek(int sample_index) override;
|
||||
|
||||
virtual int loaded_samples() override { return static_cast<int>(m_loaded_samples); }
|
||||
virtual int total_samples() override { return static_cast<int>(m_total_samples); }
|
||||
virtual u32 sample_rate() override { return m_sample_rate; }
|
||||
virtual u16 num_channels() override { return m_num_channels; }
|
||||
virtual ByteString format_name() override { return "Quite Okay Audio (.qoa)"; }
|
||||
virtual PcmSampleFormat pcm_format() override { return PcmSampleFormat::Int16; }
|
||||
|
||||
private:
|
||||
enum class IsFirstFrame : bool {
|
||||
Yes = true,
|
||||
No = false,
|
||||
};
|
||||
|
||||
MaybeLoaderError initialize();
|
||||
MaybeLoaderError parse_header();
|
||||
|
||||
MaybeLoaderError load_one_frame(Span<Sample>& target, IsFirstFrame is_first_frame = IsFirstFrame::No);
|
||||
// Updates predictor values in lms_state so the next slice can reuse the same state.
|
||||
MaybeLoaderError read_one_slice(QOA::LMSState& lms_state, Span<i16>& samples);
|
||||
static ALWAYS_INLINE QOA::UnpackedSlice unpack_slice(QOA::PackedSlice packed_slice);
|
||||
|
||||
// QOA's division routine for scaling residuals before final quantization.
|
||||
static ALWAYS_INLINE i16 qoa_divide(i16 value, i16 scale_factor);
|
||||
|
||||
// Because QOA has dynamic sample rate and channel count, we only use the sample rate and channel count from the first frame.
|
||||
u32 m_sample_rate { 0 };
|
||||
u8 m_num_channels { 0 };
|
||||
// If this is the case (the reference encoder even enforces it at the moment)
|
||||
bool m_has_uniform_channel_count { true };
|
||||
|
||||
size_t m_loaded_samples { 0 };
|
||||
size_t m_total_samples { 0 };
|
||||
};
|
||||
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2023, kleines Filmröllchen <filmroellchen@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "QOATypes.h"
|
||||
#include <AK/Endian.h>
|
||||
#include <AK/Stream.h>
|
||||
|
||||
namespace Audio::QOA {
|
||||
|
||||
ErrorOr<FrameHeader> FrameHeader::read_from_stream(Stream& stream)
|
||||
{
|
||||
FrameHeader header;
|
||||
header.num_channels = TRY(stream.read_value<u8>());
|
||||
u8 sample_rate[3];
|
||||
// Enforce the order of the reads here, since the order of expression evaluations further down is implementation-defined.
|
||||
sample_rate[0] = TRY(stream.read_value<u8>());
|
||||
sample_rate[1] = TRY(stream.read_value<u8>());
|
||||
sample_rate[2] = TRY(stream.read_value<u8>());
|
||||
header.sample_rate = (sample_rate[0] << 16) | (sample_rate[1] << 8) | sample_rate[2];
|
||||
header.sample_count = TRY(stream.read_value<BigEndian<u16>>());
|
||||
header.frame_size = TRY(stream.read_value<BigEndian<u16>>());
|
||||
return header;
|
||||
}
|
||||
|
||||
LMSState::LMSState(u64 history_packed, u64 weights_packed)
|
||||
{
|
||||
for (size_t i = 0; i < lms_history; ++i) {
|
||||
// The casts ensure proper sign extension.
|
||||
history[i] = static_cast<i16>(history_packed >> 48);
|
||||
history_packed <<= 16;
|
||||
weights[i] = static_cast<i16>(weights_packed >> 48);
|
||||
weights_packed <<= 16;
|
||||
}
|
||||
}
|
||||
|
||||
i32 LMSState::predict() const
|
||||
{
|
||||
// The spec specifies that overflows are not allowed, but we do a safe thing anyways.
|
||||
Checked<i32> prediction = 0;
|
||||
for (size_t i = 0; i < lms_history; ++i)
|
||||
prediction.saturating_add(Checked<i32>::saturating_mul(history[i], weights[i]));
|
||||
return prediction.value() >> 13;
|
||||
}
|
||||
|
||||
void LMSState::update(i32 sample, i32 residual)
|
||||
{
|
||||
i32 delta = residual >> 4;
|
||||
for (size_t i = 0; i < lms_history; ++i)
|
||||
weights[i] += history[i] < 0 ? -delta : delta;
|
||||
|
||||
for (size_t i = 0; i < lms_history - 1; ++i)
|
||||
history[i] = history[i + 1];
|
||||
history[lms_history - 1] = sample;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,142 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2023, kleines Filmröllchen <filmroellchen@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Array.h>
|
||||
#include <AK/Forward.h>
|
||||
#include <AK/Math.h>
|
||||
#include <AK/Types.h>
|
||||
#include <math.h>
|
||||
|
||||
namespace Audio::QOA {
|
||||
|
||||
// 'qoaf'
|
||||
static constexpr u32 const magic = 0x716f6166;
|
||||
|
||||
static constexpr size_t const header_size = sizeof(u64);
|
||||
|
||||
struct FrameHeader {
|
||||
u8 num_channels;
|
||||
u32 sample_rate; // 24 bits
|
||||
u16 sample_count;
|
||||
// TODO: might be removed and/or replaced
|
||||
u16 frame_size;
|
||||
|
||||
static ErrorOr<FrameHeader> read_from_stream(Stream& stream);
|
||||
};
|
||||
|
||||
static constexpr size_t const frame_header_size = sizeof(u64);
|
||||
|
||||
// Least mean squares (LMS) predictor FIR filter size.
|
||||
static constexpr size_t const lms_history = 4;
|
||||
|
||||
static constexpr size_t const lms_state_size = 2 * lms_history * sizeof(u16);
|
||||
|
||||
// Only used for internal purposes; intermediate LMS states can be beyond 16 bits.
|
||||
struct LMSState {
|
||||
i32 history[lms_history] { 0, 0, 0, 0 };
|
||||
i32 weights[lms_history] { 0, 0, 0, 0 };
|
||||
|
||||
LMSState() = default;
|
||||
LMSState(u64 history_packed, u64 weights_packed);
|
||||
|
||||
i32 predict() const;
|
||||
void update(i32 sample, i32 residual);
|
||||
};
|
||||
|
||||
using PackedSlice = u64;
|
||||
|
||||
// A QOA slice in a more directly readable format, unpacked from the stored 64-bit format.
|
||||
struct UnpackedSlice {
|
||||
size_t scale_factor_index; // 4 bits packed
|
||||
Array<u8, 20> residuals; // 3 bits packed
|
||||
};
|
||||
|
||||
// Samples within a 64-bit slice.
|
||||
static constexpr size_t const slice_samples = 20;
|
||||
static constexpr size_t const max_slices_per_frame = 256;
|
||||
static constexpr size_t const max_frame_samples = slice_samples * max_slices_per_frame;
|
||||
|
||||
// Defined as clamping limits by the spec.
|
||||
static constexpr i32 const sample_minimum = -32768;
|
||||
static constexpr i32 const sample_maximum = 32767;
|
||||
|
||||
// Quantization and scale factor tables computed from formulas given in qoa.h
|
||||
|
||||
constexpr Array<int, 17> generate_scale_factor_table()
|
||||
{
|
||||
Array<int, 17> scalefactor_table;
|
||||
for (size_t s = 0; s < 17; ++s)
|
||||
scalefactor_table[s] = static_cast<int>(AK::round<double>(AK::pow<double>(static_cast<double>(s + 1), 2.75)));
|
||||
|
||||
return scalefactor_table;
|
||||
}
|
||||
|
||||
// FIXME: Get rid of the literal table once Clang understands our constexpr pow() and round() implementations.
|
||||
#if defined(AK_COMPILER_CLANG)
|
||||
static constexpr Array<int, 17> scale_factor_table = {
|
||||
1, 7, 21, 45, 84, 138, 211, 304, 421, 562, 731, 928, 1157, 1419, 1715, 2048
|
||||
};
|
||||
#else
|
||||
static constexpr Array<int, 17> scale_factor_table = generate_scale_factor_table();
|
||||
#endif
|
||||
|
||||
constexpr Array<int, 17> generate_reciprocal_table()
|
||||
{
|
||||
Array<int, 17> reciprocal_table;
|
||||
for (size_t s = 0; s < 17; ++s) {
|
||||
reciprocal_table[s] = ((1 << 16) + scale_factor_table[s] - 1) / scale_factor_table[s];
|
||||
}
|
||||
return reciprocal_table;
|
||||
}
|
||||
|
||||
constexpr Array<Array<int, 8>, 16> generate_dequantization_table()
|
||||
{
|
||||
Array<Array<int, 8>, 16> dequantization_table;
|
||||
constexpr Array<double, 8> float_dequantization_table = { 0.75, -0.75, 2.5, -2.5, 4.5, -4.5, 7, -7 };
|
||||
for (size_t scale = 0; scale < 16; ++scale) {
|
||||
for (size_t quantization = 0; quantization < 8; ++quantization)
|
||||
dequantization_table[scale][quantization] = static_cast<int>(AK::round<double>(
|
||||
static_cast<double>(scale_factor_table[scale]) * float_dequantization_table[quantization]));
|
||||
}
|
||||
return dequantization_table;
|
||||
}
|
||||
|
||||
#if defined(AK_COMPILER_CLANG)
|
||||
static constexpr Array<int, 17> reciprocal_table = {
|
||||
65536, 9363, 3121, 1457, 781, 475, 311, 216, 156, 117, 90, 71, 57, 47, 39, 32
|
||||
};
|
||||
static constexpr Array<Array<int, 8>, 16> dequantization_table = {
|
||||
Array<int, 8> { 1, -1, 3, -3, 5, -5, 7, -7 },
|
||||
{ 5, -5, 18, -18, 32, -32, 49, -49 },
|
||||
{ 16, -16, 53, -53, 95, -95, 147, -147 },
|
||||
{ 34, -34, 113, -113, 203, -203, 315, -315 },
|
||||
{ 63, -63, 210, -210, 378, -378, 588, -588 },
|
||||
{ 104, -104, 345, -345, 621, -621, 966, -966 },
|
||||
{ 158, -158, 528, -528, 950, -950, 1477, -1477 },
|
||||
{ 228, -228, 760, -760, 1368, -1368, 2128, -2128 },
|
||||
{ 316, -316, 1053, -1053, 1895, -1895, 2947, -2947 },
|
||||
{ 422, -422, 1405, -1405, 2529, -2529, 3934, -3934 },
|
||||
{ 548, -548, 1828, -1828, 3290, -3290, 5117, -5117 },
|
||||
{ 696, -696, 2320, -2320, 4176, -4176, 6496, -6496 },
|
||||
{ 868, -868, 2893, -2893, 5207, -5207, 8099, -8099 },
|
||||
{ 1064, -1064, 3548, -3548, 6386, -6386, 9933, -9933 },
|
||||
{ 1286, -1286, 4288, -4288, 7718, -7718, 12005, -12005 },
|
||||
{ 1536, -1536, 5120, -5120, 9216, -9216, 14336, -14336 },
|
||||
};
|
||||
#else
|
||||
static constexpr Array<int, 17> reciprocal_table = generate_reciprocal_table();
|
||||
static constexpr Array<Array<int, 8>, 16> dequantization_table = generate_dequantization_table();
|
||||
#endif
|
||||
|
||||
static constexpr Array<int, 17> quantization_table = {
|
||||
7, 7, 7, 5, 5, 3, 3, 1, // -8 ..-1
|
||||
0, // 0
|
||||
0, 2, 2, 4, 4, 6, 6, 6 // 1 .. 8
|
||||
};
|
||||
|
||||
}
|
|
@ -1,100 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2022, kleines Filmröllchen <filmroellchen@serenityos.org>.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Concepts.h>
|
||||
#include <AK/Types.h>
|
||||
#include <AK/Vector.h>
|
||||
|
||||
namespace Audio {
|
||||
|
||||
// Small helper to resample from one playback rate to another
|
||||
// This isn't really "smart", in that we just insert (or drop) samples.
|
||||
// Should do better...
|
||||
template<typename SampleType>
|
||||
class ResampleHelper {
|
||||
public:
|
||||
ResampleHelper(u32 source, u32 target)
|
||||
: m_source(source)
|
||||
, m_target(target)
|
||||
{
|
||||
VERIFY(source > 0);
|
||||
VERIFY(target > 0);
|
||||
}
|
||||
|
||||
// To be used as follows:
|
||||
// while the resampler doesn't need a new sample, read_sample(current) and store the resulting samples.
|
||||
// as long as the resampler needs a new sample, process_sample(current)
|
||||
|
||||
// Stores a new sample
|
||||
void process_sample(SampleType sample_l, SampleType sample_r)
|
||||
{
|
||||
m_last_sample_l = sample_l;
|
||||
m_last_sample_r = sample_r;
|
||||
m_current_ratio += m_target;
|
||||
}
|
||||
|
||||
// Assigns the given sample to its correct value and returns false if there is a new sample required
|
||||
bool read_sample(SampleType& next_l, SampleType& next_r)
|
||||
{
|
||||
if (m_current_ratio >= m_source) {
|
||||
m_current_ratio -= m_source;
|
||||
next_l = m_last_sample_l;
|
||||
next_r = m_last_sample_r;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template<ArrayLike<SampleType> Samples>
|
||||
ErrorOr<Vector<SampleType>> try_resample(Samples&& to_resample)
|
||||
{
|
||||
Vector<SampleType> resampled;
|
||||
TRY(try_resample_into_end(resampled, forward<Samples>(to_resample)));
|
||||
return resampled;
|
||||
}
|
||||
|
||||
template<ArrayLike<SampleType> Samples, size_t vector_inline_capacity = 0>
|
||||
ErrorOr<void> try_resample_into_end(Vector<SampleType, vector_inline_capacity>& destination, Samples&& to_resample)
|
||||
{
|
||||
float ratio = (m_source > m_target) ? static_cast<float>(m_source) / m_target : static_cast<float>(m_target) / m_source;
|
||||
TRY(destination.try_ensure_capacity(destination.size() + to_resample.size() * ratio));
|
||||
for (auto sample : to_resample) {
|
||||
process_sample(sample, sample);
|
||||
|
||||
while (read_sample(sample, sample))
|
||||
destination.unchecked_append(sample);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
template<ArrayLike<SampleType> Samples>
|
||||
Vector<SampleType> resample(Samples&& to_resample)
|
||||
{
|
||||
return MUST(try_resample(forward<Samples>(to_resample)));
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
m_current_ratio = 0;
|
||||
m_last_sample_l = {};
|
||||
m_last_sample_r = {};
|
||||
}
|
||||
|
||||
u32 source() const { return m_source; }
|
||||
u32 target() const { return m_target; }
|
||||
|
||||
private:
|
||||
u32 const m_source;
|
||||
u32 const m_target;
|
||||
u32 m_current_ratio { 0 };
|
||||
SampleType m_last_sample_l {};
|
||||
SampleType m_last_sample_r {};
|
||||
};
|
||||
|
||||
}
|
|
@ -27,31 +27,4 @@ u16 pcm_bits_per_sample(PcmSampleFormat format)
|
|||
}
|
||||
}
|
||||
|
||||
bool is_integer_format(PcmSampleFormat format)
|
||||
{
|
||||
return format == PcmSampleFormat::Uint8 || format == PcmSampleFormat::Int16 || format == PcmSampleFormat::Int24 || format == PcmSampleFormat::Int32;
|
||||
}
|
||||
|
||||
Optional<PcmSampleFormat> integer_sample_format_for(u16 bits_per_sample)
|
||||
{
|
||||
switch (bits_per_sample) {
|
||||
case 8:
|
||||
return PcmSampleFormat::Uint8;
|
||||
case 16:
|
||||
return PcmSampleFormat::Int16;
|
||||
case 24:
|
||||
return PcmSampleFormat::Int24;
|
||||
case 32:
|
||||
return PcmSampleFormat::Int32;
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
ByteString sample_format_name(PcmSampleFormat format)
|
||||
{
|
||||
bool is_float = format == PcmSampleFormat::Float32 || format == PcmSampleFormat::Float64;
|
||||
return ByteString::formatted("PCM {}bit {}", pcm_bits_per_sample(format), is_float ? "Float" : "LE");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -23,7 +23,5 @@ enum class PcmSampleFormat : u8 {
|
|||
|
||||
// Most of the read code only cares about how many bits to read or write
|
||||
u16 pcm_bits_per_sample(PcmSampleFormat format);
|
||||
bool is_integer_format(PcmSampleFormat format);
|
||||
Optional<PcmSampleFormat> integer_sample_format_for(u16 bits_per_sample);
|
||||
ByteString sample_format_name(PcmSampleFormat format);
|
||||
|
||||
}
|
||||
|
|
|
@ -1,189 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2023, kleines Filmröllchen <filmroellchen@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "VorbisComment.h"
|
||||
#include <AK/Endian.h>
|
||||
#include <AK/MemoryStream.h>
|
||||
#include <AK/Span.h>
|
||||
#include <AK/String.h>
|
||||
|
||||
namespace Audio {
|
||||
|
||||
static StringView vorbis_field_for_role(Person::Role role)
|
||||
{
|
||||
static HashMap<Person::Role, StringView> role_map {
|
||||
{ Person::Role::Artist, "ARTIST"sv },
|
||||
{ Person::Role::Performer, "PERFORMER"sv },
|
||||
{ Person::Role::Lyricist, "LYRICIST"sv },
|
||||
{ Person::Role::Conductor, "CONDUCTOR"sv },
|
||||
{ Person::Role::Publisher, "PUBLISHER"sv },
|
||||
{ Person::Role::Engineer, "ENCODED-BY"sv },
|
||||
{ Person::Role::Composer, "COMPOSER"sv },
|
||||
};
|
||||
return role_map.get(role).value();
|
||||
}
|
||||
|
||||
// "Content vector format"
|
||||
static ErrorOr<void> read_vorbis_field(Metadata& metadata_to_write_into, String const& unparsed_user_comment)
|
||||
{
|
||||
// Technically the field name has to be ASCII, but we just accept all UTF-8.
|
||||
auto field_name_and_contents = TRY(unparsed_user_comment.split_limit('=', 2));
|
||||
|
||||
if (field_name_and_contents.size() != 2)
|
||||
return Error::from_string_literal("User comment does not contain '='");
|
||||
auto contents = field_name_and_contents.take_last();
|
||||
auto field_name = TRY(field_name_and_contents.take_first().to_uppercase());
|
||||
|
||||
// Some of these are taken from https://age.hobba.nl/audio/tag_frame_reference.html
|
||||
if (field_name == "TITLE"sv) {
|
||||
if (metadata_to_write_into.title.has_value())
|
||||
TRY(metadata_to_write_into.add_miscellaneous(field_name, contents));
|
||||
else
|
||||
metadata_to_write_into.title = contents;
|
||||
} else if (field_name == "VERSION"sv) {
|
||||
if (metadata_to_write_into.subtitle.has_value())
|
||||
TRY(metadata_to_write_into.add_miscellaneous(field_name, contents));
|
||||
else
|
||||
metadata_to_write_into.subtitle = contents;
|
||||
} else if (field_name == "ALBUM"sv) {
|
||||
if (metadata_to_write_into.album.has_value())
|
||||
TRY(metadata_to_write_into.add_miscellaneous(field_name, contents));
|
||||
else
|
||||
metadata_to_write_into.album = contents;
|
||||
} else if (field_name == "COPYRIGHT"sv) {
|
||||
if (metadata_to_write_into.copyright.has_value())
|
||||
TRY(metadata_to_write_into.add_miscellaneous(field_name, contents));
|
||||
else
|
||||
metadata_to_write_into.copyright = contents;
|
||||
} else if (field_name == "ISRC"sv) {
|
||||
if (metadata_to_write_into.isrc.has_value())
|
||||
TRY(metadata_to_write_into.add_miscellaneous(field_name, contents));
|
||||
else
|
||||
metadata_to_write_into.isrc = contents;
|
||||
} else if (field_name == "GENRE"sv) {
|
||||
if (metadata_to_write_into.genre.has_value())
|
||||
TRY(metadata_to_write_into.add_miscellaneous(field_name, contents));
|
||||
else
|
||||
metadata_to_write_into.genre = contents;
|
||||
} else if (field_name == "COMMENT"sv) {
|
||||
if (metadata_to_write_into.comment.has_value())
|
||||
TRY(metadata_to_write_into.add_miscellaneous(field_name, contents));
|
||||
else
|
||||
metadata_to_write_into.comment = contents;
|
||||
} else if (field_name == "TRACKNUMBER"sv) {
|
||||
if (metadata_to_write_into.track_number.has_value())
|
||||
TRY(metadata_to_write_into.add_miscellaneous(field_name, contents));
|
||||
else if (auto maybe_number = contents.to_number<unsigned>(); maybe_number.has_value())
|
||||
metadata_to_write_into.track_number = maybe_number.release_value();
|
||||
else
|
||||
TRY(metadata_to_write_into.add_miscellaneous(field_name, contents));
|
||||
} else if (field_name == "DATE"sv) {
|
||||
if (metadata_to_write_into.unparsed_time.has_value())
|
||||
TRY(metadata_to_write_into.add_miscellaneous(field_name, contents));
|
||||
else
|
||||
metadata_to_write_into.unparsed_time = contents;
|
||||
} else if (field_name == vorbis_field_for_role(Person::Role::Performer)) {
|
||||
TRY(metadata_to_write_into.add_person(Person::Role::Performer, contents));
|
||||
} else if (field_name == vorbis_field_for_role(Person::Role::Artist)) {
|
||||
TRY(metadata_to_write_into.add_person(Person::Role::Artist, contents));
|
||||
} else if (field_name == vorbis_field_for_role(Person::Role::Composer)) {
|
||||
TRY(metadata_to_write_into.add_person(Person::Role::Composer, contents));
|
||||
} else if (field_name == vorbis_field_for_role(Person::Role::Conductor)) {
|
||||
TRY(metadata_to_write_into.add_person(Person::Role::Conductor, contents));
|
||||
} else if (field_name == vorbis_field_for_role(Person::Role::Lyricist)) {
|
||||
TRY(metadata_to_write_into.add_person(Person::Role::Lyricist, contents));
|
||||
} else if (field_name == "ORGANIZATION"sv) {
|
||||
TRY(metadata_to_write_into.add_person(Person::Role::Publisher, contents));
|
||||
} else if (field_name == vorbis_field_for_role(Person::Role::Publisher)) {
|
||||
TRY(metadata_to_write_into.add_person(Person::Role::Publisher, contents));
|
||||
} else if (field_name == vorbis_field_for_role(Person::Role::Engineer)) {
|
||||
TRY(metadata_to_write_into.add_person(Person::Role::Engineer, contents));
|
||||
} else {
|
||||
TRY(metadata_to_write_into.add_miscellaneous(field_name, contents));
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<Metadata, LoaderError> load_vorbis_comment(ByteBuffer const& vorbis_comment)
|
||||
{
|
||||
FixedMemoryStream stream { vorbis_comment };
|
||||
auto vendor_length = TRY(stream.read_value<LittleEndian<u32>>());
|
||||
Vector<u8> raw_vendor_string;
|
||||
TRY(raw_vendor_string.try_resize(vendor_length));
|
||||
TRY(stream.read_until_filled(raw_vendor_string));
|
||||
auto vendor_string = TRY(String::from_utf8(StringView { raw_vendor_string.span() }));
|
||||
|
||||
Metadata metadata;
|
||||
metadata.encoder = move(vendor_string);
|
||||
|
||||
auto user_comment_count = TRY(stream.read_value<LittleEndian<u32>>());
|
||||
for (size_t i = 0; i < user_comment_count; ++i) {
|
||||
auto user_comment_length = TRY(stream.read_value<LittleEndian<u32>>());
|
||||
Vector<u8> raw_user_comment;
|
||||
TRY(raw_user_comment.try_resize(user_comment_length));
|
||||
TRY(stream.read_until_filled(raw_user_comment));
|
||||
auto unparsed_user_comment = TRY(String::from_utf8(StringView { raw_user_comment.span() }));
|
||||
TRY(read_vorbis_field(metadata, unparsed_user_comment));
|
||||
}
|
||||
|
||||
return metadata;
|
||||
}
|
||||
|
||||
struct VorbisCommentPair {
|
||||
String field_name;
|
||||
String contents;
|
||||
};
|
||||
|
||||
static ErrorOr<Vector<VorbisCommentPair>> make_vorbis_user_comments(Metadata const& metadata)
|
||||
{
|
||||
Vector<VorbisCommentPair> user_comments;
|
||||
|
||||
auto add_if_present = [&](auto field_name, auto const& value) -> ErrorOr<void> {
|
||||
if (value.has_value())
|
||||
TRY(user_comments.try_append(VorbisCommentPair { field_name, TRY(String::formatted("{}", value.value())) }));
|
||||
return {};
|
||||
};
|
||||
|
||||
TRY(add_if_present("TITLE"_string, metadata.title));
|
||||
TRY(add_if_present("VERSION"_string, metadata.subtitle));
|
||||
TRY(add_if_present("ALBUM"_string, metadata.album));
|
||||
TRY(add_if_present("COPYRIGHT"_string, metadata.copyright));
|
||||
TRY(add_if_present("ISRC"_string, metadata.isrc));
|
||||
TRY(add_if_present("GENRE"_string, metadata.genre));
|
||||
TRY(add_if_present("COMMENT"_string, metadata.comment));
|
||||
TRY(add_if_present("TRACKNUMBER"_string, metadata.track_number));
|
||||
TRY(add_if_present("DATE"_string, metadata.unparsed_time));
|
||||
|
||||
for (auto const& person : metadata.people)
|
||||
TRY(user_comments.try_append(VorbisCommentPair { TRY(String::from_utf8(vorbis_field_for_role(person.role))), person.name }));
|
||||
|
||||
for (auto const& field : metadata.miscellaneous) {
|
||||
for (auto const& value : field.value)
|
||||
TRY(user_comments.try_append(VorbisCommentPair { field.key, value }));
|
||||
}
|
||||
|
||||
return user_comments;
|
||||
}
|
||||
|
||||
ErrorOr<void> write_vorbis_comment(Metadata const& metadata, Stream& target)
|
||||
{
|
||||
auto encoder = metadata.encoder.value_or({}).bytes();
|
||||
TRY(target.write_value<LittleEndian<u32>>(encoder.size()));
|
||||
TRY(target.write_until_depleted(encoder));
|
||||
|
||||
auto vorbis_user_comments = TRY(make_vorbis_user_comments(metadata));
|
||||
TRY(target.write_value<LittleEndian<u32>>(vorbis_user_comments.size()));
|
||||
for (auto const& field : vorbis_user_comments) {
|
||||
auto const serialized_field = TRY(String::formatted("{}={}", field.field_name, field.contents));
|
||||
TRY(target.write_value<LittleEndian<u32>>(serialized_field.bytes().size()));
|
||||
TRY(target.write_until_depleted(serialized_field.bytes()));
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2023, kleines Filmröllchen <filmroellchen@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "LoaderError.h"
|
||||
#include "Metadata.h"
|
||||
#include <AK/ByteBuffer.h>
|
||||
|
||||
namespace Audio {
|
||||
|
||||
// https://www.xiph.org/vorbis/doc/v-comment.html
|
||||
ErrorOr<Metadata, LoaderError> load_vorbis_comment(ByteBuffer const& vorbis_comment);
|
||||
ErrorOr<void> write_vorbis_comment(Metadata const& metadata, Stream& target);
|
||||
|
||||
}
|
|
@ -1,337 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2021-2023, kleines Filmröllchen <filmroellchen@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "WavLoader.h"
|
||||
#include "LoaderError.h"
|
||||
#include "WavTypes.h"
|
||||
#include <AK/Debug.h>
|
||||
#include <AK/Endian.h>
|
||||
#include <AK/FixedArray.h>
|
||||
#include <AK/MemoryStream.h>
|
||||
#include <AK/NonnullOwnPtr.h>
|
||||
#include <AK/NumericLimits.h>
|
||||
#include <AK/Try.h>
|
||||
|
||||
namespace Audio {
|
||||
|
||||
WavLoaderPlugin::WavLoaderPlugin(NonnullOwnPtr<SeekableStream> stream)
|
||||
: LoaderPlugin(move(stream))
|
||||
{
|
||||
}
|
||||
|
||||
bool WavLoaderPlugin::sniff(SeekableStream& stream)
|
||||
{
|
||||
auto riff = stream.read_value<RIFF::ChunkID>();
|
||||
if (riff.is_error())
|
||||
return false;
|
||||
if (riff.value() != RIFF::riff_magic)
|
||||
return false;
|
||||
|
||||
auto size = stream.read_value<LittleEndian<u32>>();
|
||||
if (size.is_error())
|
||||
return false;
|
||||
|
||||
auto wave = stream.read_value<RIFF::ChunkID>();
|
||||
return !wave.is_error() && wave.value() == Wav::wave_subformat_id;
|
||||
}
|
||||
|
||||
ErrorOr<NonnullOwnPtr<LoaderPlugin>, LoaderError> WavLoaderPlugin::create(NonnullOwnPtr<SeekableStream> stream)
|
||||
{
|
||||
auto loader = make<WavLoaderPlugin>(move(stream));
|
||||
TRY(loader->parse_header());
|
||||
return loader;
|
||||
}
|
||||
|
||||
template<typename SampleReader>
|
||||
MaybeLoaderError WavLoaderPlugin::read_samples_from_stream(Stream& stream, SampleReader read_sample, FixedArray<Sample>& samples) const
|
||||
{
|
||||
switch (m_num_channels) {
|
||||
case 1:
|
||||
for (auto& sample : samples)
|
||||
sample = Sample(TRY(read_sample(stream)));
|
||||
break;
|
||||
case 2:
|
||||
for (auto& sample : samples) {
|
||||
auto left_channel_sample = TRY(read_sample(stream));
|
||||
auto right_channel_sample = TRY(read_sample(stream));
|
||||
sample = Sample(left_channel_sample, right_channel_sample);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
// There's no i24 type + we need to do the endianness conversion manually anyways.
|
||||
static ErrorOr<double> read_sample_int24(Stream& stream)
|
||||
{
|
||||
i32 sample1 = TRY(stream.read_value<u8>());
|
||||
i32 sample2 = TRY(stream.read_value<u8>());
|
||||
i32 sample3 = TRY(stream.read_value<u8>());
|
||||
|
||||
i32 value = 0;
|
||||
value = sample1;
|
||||
value |= sample2 << 8;
|
||||
value |= sample3 << 16;
|
||||
// Sign extend the value, as it can currently not have the correct sign.
|
||||
value = (value << 8) >> 8;
|
||||
// Range of value is now -2^23 to 2^23-1 and we can rescale normally.
|
||||
return static_cast<double>(value) / static_cast<double>((1 << 23) - 1);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static ErrorOr<double> read_sample(Stream& stream)
|
||||
{
|
||||
T sample { 0 };
|
||||
TRY(stream.read_until_filled(Bytes { &sample, sizeof(T) }));
|
||||
// Remap integer samples to normalized floating-point range of -1 to 1.
|
||||
if constexpr (IsIntegral<T>) {
|
||||
if constexpr (NumericLimits<T>::is_signed()) {
|
||||
// Signed integer samples are centered around zero, so this division is enough.
|
||||
return static_cast<double>(AK::convert_between_host_and_little_endian(sample)) / static_cast<double>(NumericLimits<T>::max());
|
||||
} else {
|
||||
// Unsigned integer samples, on the other hand, need to be shifted to center them around zero.
|
||||
// The first division therefore remaps to the range 0 to 2.
|
||||
return static_cast<double>(AK::convert_between_host_and_little_endian(sample)) / (static_cast<double>(NumericLimits<T>::max()) / 2.0) - 1.0;
|
||||
}
|
||||
} else {
|
||||
return static_cast<double>(AK::convert_between_host_and_little_endian(sample));
|
||||
}
|
||||
}
|
||||
|
||||
LoaderSamples WavLoaderPlugin::samples_from_pcm_data(ReadonlyBytes data, size_t samples_to_read) const
|
||||
{
|
||||
FixedArray<Sample> samples = TRY(FixedArray<Sample>::create(samples_to_read));
|
||||
FixedMemoryStream stream { data };
|
||||
|
||||
switch (m_sample_format) {
|
||||
case PcmSampleFormat::Uint8:
|
||||
TRY(read_samples_from_stream(stream, read_sample<u8>, samples));
|
||||
break;
|
||||
case PcmSampleFormat::Int16:
|
||||
TRY(read_samples_from_stream(stream, read_sample<i16>, samples));
|
||||
break;
|
||||
case PcmSampleFormat::Int24:
|
||||
TRY(read_samples_from_stream(stream, read_sample_int24, samples));
|
||||
break;
|
||||
case PcmSampleFormat::Float32:
|
||||
TRY(read_samples_from_stream(stream, read_sample<float>, samples));
|
||||
break;
|
||||
case PcmSampleFormat::Float64:
|
||||
TRY(read_samples_from_stream(stream, read_sample<double>, samples));
|
||||
break;
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
ErrorOr<Vector<FixedArray<Sample>>, LoaderError> WavLoaderPlugin::load_chunks(size_t samples_to_read_from_input)
|
||||
{
|
||||
auto remaining_samples = m_total_samples - m_loaded_samples;
|
||||
if (remaining_samples <= 0)
|
||||
return Vector<FixedArray<Sample>> {};
|
||||
|
||||
// One "sample" contains data from all channels.
|
||||
// In the Wave spec, this is also called a block.
|
||||
size_t bytes_per_sample
|
||||
= m_num_channels * pcm_bits_per_sample(m_sample_format) / 8;
|
||||
|
||||
auto samples_to_read = min(samples_to_read_from_input, remaining_samples);
|
||||
auto bytes_to_read = samples_to_read * bytes_per_sample;
|
||||
|
||||
dbgln_if(AWAVLOADER_DEBUG, "Read {} bytes WAV with num_channels {} sample rate {}, "
|
||||
"bits per sample {}, sample format {}",
|
||||
bytes_to_read, m_num_channels, m_sample_rate,
|
||||
pcm_bits_per_sample(m_sample_format), sample_format_name(m_sample_format));
|
||||
|
||||
auto sample_data = TRY(ByteBuffer::create_zeroed(bytes_to_read));
|
||||
TRY(m_stream->read_until_filled(sample_data.bytes()));
|
||||
|
||||
// m_loaded_samples should contain the amount of actually loaded samples
|
||||
m_loaded_samples += samples_to_read;
|
||||
Vector<FixedArray<Sample>> samples;
|
||||
TRY(samples.try_append(TRY(samples_from_pcm_data(sample_data.bytes(), samples_to_read))));
|
||||
return samples;
|
||||
}
|
||||
|
||||
MaybeLoaderError WavLoaderPlugin::seek(int sample_index)
|
||||
{
|
||||
dbgln_if(AWAVLOADER_DEBUG, "seek sample_index {}", sample_index);
|
||||
if (sample_index < 0 || sample_index >= static_cast<int>(m_total_samples))
|
||||
return LoaderError { LoaderError::Category::Internal, m_loaded_samples, "Seek outside the sample range" };
|
||||
|
||||
size_t sample_offset = m_byte_offset_of_data_samples + static_cast<size_t>(sample_index * m_num_channels * (pcm_bits_per_sample(m_sample_format) / 8));
|
||||
|
||||
TRY(m_stream->seek(sample_offset, SeekMode::SetPosition));
|
||||
|
||||
m_loaded_samples = sample_index;
|
||||
return {};
|
||||
}
|
||||
|
||||
// Specification reference: http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html
|
||||
MaybeLoaderError WavLoaderPlugin::parse_header()
|
||||
{
|
||||
#define CHECK(check, category, msg) \
|
||||
do { \
|
||||
if (!(check)) { \
|
||||
return LoaderError { category, static_cast<size_t>(TRY(m_stream->tell())), ByteString::formatted("WAV header: {}", msg) }; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
auto file_header = TRY(m_stream->read_value<RIFF::FileHeader>());
|
||||
CHECK(file_header.magic() == RIFF::riff_magic, LoaderError::Category::Format, "RIFF header magic invalid");
|
||||
CHECK(file_header.subformat == Wav::wave_subformat_id, LoaderError::Category::Format, "WAVE subformat id invalid");
|
||||
|
||||
auto format_chunk = TRY(m_stream->read_value<RIFF::OwnedChunk>());
|
||||
CHECK(format_chunk.id().as_ascii_string() == Wav::format_chunk_id, LoaderError::Category::Format, "FMT chunk id invalid");
|
||||
|
||||
auto format_stream = format_chunk.data_stream();
|
||||
u16 audio_format = TRY(format_stream.read_value<LittleEndian<u16>>());
|
||||
CHECK(audio_format == to_underlying(Wav::WaveFormat::Pcm) || audio_format == to_underlying(Wav::WaveFormat::IEEEFloat) || audio_format == to_underlying(Wav::WaveFormat::Extensible),
|
||||
LoaderError::Category::Unimplemented, "Audio format not supported");
|
||||
|
||||
m_num_channels = TRY(format_stream.read_value<LittleEndian<u16>>());
|
||||
CHECK(m_num_channels == 1 || m_num_channels == 2, LoaderError::Category::Unimplemented, "Channel count");
|
||||
|
||||
m_sample_rate = TRY(format_stream.read_value<LittleEndian<u32>>());
|
||||
// Data rate; can be ignored.
|
||||
TRY(format_stream.read_value<LittleEndian<u32>>());
|
||||
u16 block_size_bytes = TRY(format_stream.read_value<LittleEndian<u16>>());
|
||||
|
||||
u16 bits_per_sample = TRY(format_stream.read_value<LittleEndian<u16>>());
|
||||
|
||||
if (audio_format == to_underlying(Wav::WaveFormat::Extensible)) {
|
||||
CHECK(format_chunk.size() == 40, LoaderError::Category::Format, "Extensible fmt size is not 40 bytes");
|
||||
|
||||
// Discard everything until the GUID.
|
||||
// We've already read 16 bytes from the stream. The GUID starts in another 8 bytes.
|
||||
TRY(format_stream.read_value<LittleEndian<u64>>());
|
||||
|
||||
// Get the underlying audio format from the first two bytes of GUID
|
||||
u16 guid_subformat = TRY(format_stream.read_value<LittleEndian<u16>>());
|
||||
CHECK(guid_subformat == to_underlying(Wav::WaveFormat::Pcm) || guid_subformat == to_underlying(Wav::WaveFormat::IEEEFloat), LoaderError::Category::Unimplemented, "GUID SubFormat not supported");
|
||||
|
||||
audio_format = guid_subformat;
|
||||
}
|
||||
|
||||
if (audio_format == to_underlying(Wav::WaveFormat::Pcm)) {
|
||||
CHECK(bits_per_sample == 8 || bits_per_sample == 16 || bits_per_sample == 24, LoaderError::Category::Unimplemented, "PCM bits per sample not supported");
|
||||
|
||||
// We only support 8-24 bit audio right now because other formats are uncommon
|
||||
if (bits_per_sample == 8) {
|
||||
m_sample_format = PcmSampleFormat::Uint8;
|
||||
} else if (bits_per_sample == 16) {
|
||||
m_sample_format = PcmSampleFormat::Int16;
|
||||
} else if (bits_per_sample == 24) {
|
||||
m_sample_format = PcmSampleFormat::Int24;
|
||||
}
|
||||
} else if (audio_format == to_underlying(Wav::WaveFormat::IEEEFloat)) {
|
||||
CHECK(bits_per_sample == 32 || bits_per_sample == 64, LoaderError::Category::Unimplemented, "Float bits per sample not supported");
|
||||
|
||||
// Again, only the common 32 and 64 bit
|
||||
if (bits_per_sample == 32) {
|
||||
m_sample_format = PcmSampleFormat::Float32;
|
||||
} else if (bits_per_sample == 64) {
|
||||
m_sample_format = PcmSampleFormat::Float64;
|
||||
}
|
||||
}
|
||||
|
||||
CHECK(block_size_bytes == (m_num_channels * (bits_per_sample / 8)), LoaderError::Category::Format, "Block size invalid");
|
||||
|
||||
dbgln_if(AWAVLOADER_DEBUG, "WAV format {} at {} bit, {} channels, rate {}Hz ",
|
||||
sample_format_name(m_sample_format), pcm_bits_per_sample(m_sample_format), m_num_channels, m_sample_rate);
|
||||
|
||||
// Read all chunks before DATA.
|
||||
bool found_data = false;
|
||||
while (!found_data) {
|
||||
auto chunk_header = TRY(m_stream->read_value<RIFF::ChunkID>());
|
||||
if (chunk_header == Wav::data_chunk_id) {
|
||||
found_data = true;
|
||||
} else {
|
||||
TRY(m_stream->seek(-RIFF::chunk_id_size, SeekMode::FromCurrentPosition));
|
||||
auto chunk = TRY(m_stream->read_value<RIFF::OwnedChunk>());
|
||||
if (chunk.id() == RIFF::list_chunk_id) {
|
||||
auto maybe_list = chunk.data_stream().read_value<RIFF::OwnedList>();
|
||||
if (maybe_list.is_error()) {
|
||||
dbgln("WAV Warning: LIST chunk invalid, error: {}", maybe_list.release_error());
|
||||
continue;
|
||||
}
|
||||
|
||||
auto list = maybe_list.release_value();
|
||||
if (list.type == Wav::info_chunk_id) {
|
||||
auto maybe_error = load_wav_info_block(move(list.chunks));
|
||||
if (maybe_error.is_error())
|
||||
dbgln("WAV Warning: INFO chunk invalid, error: {}", maybe_error.release_error());
|
||||
|
||||
} else {
|
||||
dbgln("Unhandled WAV list of type {} with {} subchunks", list.type.as_ascii_string(), list.chunks.size());
|
||||
}
|
||||
} else {
|
||||
dbgln_if(AWAVLOADER_DEBUG, "Unhandled WAV chunk of type {}, size {} bytes", chunk.id().as_ascii_string(), chunk.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
u32 data_size = TRY(m_stream->read_value<LittleEndian<u32>>());
|
||||
CHECK(found_data, LoaderError::Category::Format, "Found no data chunk");
|
||||
|
||||
m_total_samples = data_size / block_size_bytes;
|
||||
|
||||
dbgln_if(AWAVLOADER_DEBUG, "WAV data size {}, bytes per sample {}, total samples {}",
|
||||
data_size,
|
||||
block_size_bytes,
|
||||
m_total_samples);
|
||||
|
||||
m_byte_offset_of_data_samples = TRY(m_stream->tell());
|
||||
return {};
|
||||
}
|
||||
|
||||
// http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/Docs/riffmci.pdf page 23 (LIST type)
|
||||
// We only recognize the relevant official metadata types; types added in later errata of RIFF are not relevant for audio.
|
||||
MaybeLoaderError WavLoaderPlugin::load_wav_info_block(Vector<RIFF::OwnedChunk> info_chunks)
|
||||
{
|
||||
for (auto const& chunk : info_chunks) {
|
||||
auto chunk_id = chunk.id();
|
||||
auto metadata_name = chunk_id.as_ascii_string();
|
||||
// Chunk contents are zero-terminated strings "ZSTR", so we just drop the null terminator.
|
||||
StringView metadata_text { chunk.data().trim(chunk.size() - 1) };
|
||||
// Note that we assume chunks to be unique, since that seems to almost always be the case.
|
||||
// Worst case we just drop some metadata.
|
||||
if (metadata_name == "IART"sv) {
|
||||
// Artists are combined together with semicolons, at least when you edit them in Windows File Explorer.
|
||||
auto artists = metadata_text.split_view(";"sv);
|
||||
for (auto artist : artists)
|
||||
TRY(m_metadata.add_person(Person::Role::Artist, TRY(String::from_utf8(artist))));
|
||||
} else if (metadata_name == "ICMT"sv) {
|
||||
m_metadata.comment = TRY(String::from_utf8(metadata_text));
|
||||
} else if (metadata_name == "ICOP"sv) {
|
||||
m_metadata.copyright = TRY(String::from_utf8(metadata_text));
|
||||
} else if (metadata_name == "ICRD"sv) {
|
||||
m_metadata.unparsed_time = TRY(String::from_utf8(metadata_text));
|
||||
} else if (metadata_name == "IENG"sv) {
|
||||
TRY(m_metadata.add_person(Person::Role::Engineer, TRY(String::from_utf8(metadata_text))));
|
||||
} else if (metadata_name == "IGNR"sv) {
|
||||
m_metadata.genre = TRY(String::from_utf8(metadata_text));
|
||||
} else if (metadata_name == "INAM"sv) {
|
||||
m_metadata.title = TRY(String::from_utf8(metadata_text));
|
||||
} else if (metadata_name == "IPRD"sv) {
|
||||
m_metadata.album = TRY(String::from_utf8(metadata_text));
|
||||
} else if (metadata_name == "ISFT"sv) {
|
||||
m_metadata.encoder = TRY(String::from_utf8(metadata_text));
|
||||
} else if (metadata_name == "ISRC"sv) {
|
||||
TRY(m_metadata.add_person(Person::Role::Publisher, TRY(String::from_utf8(metadata_text))));
|
||||
} else {
|
||||
TRY(m_metadata.add_miscellaneous(TRY(String::from_utf8(metadata_name)), TRY(String::from_utf8(metadata_text))));
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
}
|
|
@ -1,64 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2021, kleines Filmröllchen <filmroellchen@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Loader.h"
|
||||
#include <AK/ByteString.h>
|
||||
#include <AK/FixedArray.h>
|
||||
#include <AK/OwnPtr.h>
|
||||
#include <AK/RefPtr.h>
|
||||
#include <AK/Span.h>
|
||||
#include <AK/StringView.h>
|
||||
#include <LibRIFF/RIFF.h>
|
||||
|
||||
namespace Audio {
|
||||
|
||||
// Loader for the WAVE (file extension .wav) uncompressed audio file format.
|
||||
// WAVE uses the Microsoft RIFF container.
|
||||
// Original RIFF Spec, without later extensions: https://www.aelius.com/njh/wavemetatools/doc/riffmci.pdf
|
||||
// More concise WAVE information plus various spec links: http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html
|
||||
class WavLoaderPlugin : public LoaderPlugin {
|
||||
public:
|
||||
explicit WavLoaderPlugin(NonnullOwnPtr<SeekableStream> stream);
|
||||
|
||||
static bool sniff(SeekableStream& stream);
|
||||
static ErrorOr<NonnullOwnPtr<LoaderPlugin>, LoaderError> create(NonnullOwnPtr<SeekableStream>);
|
||||
|
||||
virtual ErrorOr<Vector<FixedArray<Sample>>, LoaderError> load_chunks(size_t samples_to_read_from_input) override;
|
||||
|
||||
virtual MaybeLoaderError reset() override { return seek(0); }
|
||||
|
||||
// sample_index 0 is the start of the raw audio sample data
|
||||
// within the file/stream.
|
||||
virtual MaybeLoaderError seek(int sample_index) override;
|
||||
|
||||
virtual int loaded_samples() override { return static_cast<int>(m_loaded_samples); }
|
||||
virtual int total_samples() override { return static_cast<int>(m_total_samples); }
|
||||
virtual u32 sample_rate() override { return m_sample_rate; }
|
||||
virtual u16 num_channels() override { return m_num_channels; }
|
||||
virtual ByteString format_name() override { return "RIFF WAVE (.wav)"; }
|
||||
virtual PcmSampleFormat pcm_format() override { return m_sample_format; }
|
||||
|
||||
private:
|
||||
MaybeLoaderError parse_header();
|
||||
MaybeLoaderError load_wav_info_block(Vector<RIFF::OwnedChunk> info_chunks);
|
||||
|
||||
LoaderSamples samples_from_pcm_data(ReadonlyBytes data, size_t samples_to_read) const;
|
||||
template<typename SampleReader>
|
||||
MaybeLoaderError read_samples_from_stream(Stream& stream, SampleReader read_sample, FixedArray<Sample>& samples) const;
|
||||
|
||||
u32 m_sample_rate { 0 };
|
||||
u16 m_num_channels { 0 };
|
||||
PcmSampleFormat m_sample_format;
|
||||
size_t m_byte_offset_of_data_samples { 0 };
|
||||
|
||||
size_t m_loaded_samples { 0 };
|
||||
size_t m_total_samples { 0 };
|
||||
};
|
||||
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2023, the SerenityOS developers.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/StringView.h>
|
||||
|
||||
namespace Audio::Wav {
|
||||
|
||||
static constexpr StringView const wave_subformat_id = "WAVE"sv;
|
||||
static constexpr StringView const data_chunk_id = "data"sv;
|
||||
static constexpr StringView const info_chunk_id = "INFO"sv;
|
||||
static constexpr StringView const format_chunk_id = "fmt "sv;
|
||||
|
||||
// Constants for handling WAVE header data.
|
||||
enum class WaveFormat : u32 {
|
||||
Pcm = 0x0001, // WAVE_FORMAT_PCM
|
||||
IEEEFloat = 0x0003, // WAVE_FORMAT_IEEE_FLOAT
|
||||
ALaw = 0x0006, // 8-bit ITU-T G.711 A-law
|
||||
MuLaw = 0x0007, // 8-bit ITU-T G.711 µ-law
|
||||
Extensible = 0xFFFE, // Determined by SubFormat
|
||||
};
|
||||
|
||||
}
|
|
@ -1,17 +1,9 @@
|
|||
include(ffmpeg)
|
||||
|
||||
set(SOURCES
|
||||
Audio/GenericTypes.cpp
|
||||
Audio/SampleFormats.cpp
|
||||
Audio/Loader.cpp
|
||||
Audio/WavLoader.cpp
|
||||
Audio/FlacLoader.cpp
|
||||
Audio/Metadata.cpp
|
||||
Audio/MP3Loader.cpp
|
||||
Audio/PlaybackStream.cpp
|
||||
Audio/QOALoader.cpp
|
||||
Audio/QOATypes.cpp
|
||||
Audio/VorbisComment.cpp
|
||||
Audio/SampleFormats.cpp
|
||||
Color/ColorConverter.cpp
|
||||
Color/ColorPrimaries.cpp
|
||||
Color/TransferCharacteristics.cpp
|
||||
|
|
|
@ -195,20 +195,17 @@ Bindings::CanPlayTypeResult HTMLMediaElement::can_play_type(StringView type) con
|
|||
}
|
||||
|
||||
if (mime_type.has_value() && mime_type->type() == "audio"sv) {
|
||||
if (mime_type->subtype() == "flac"sv)
|
||||
return Bindings::CanPlayTypeResult::Probably;
|
||||
if (mime_type->subtype() == "mp3"sv)
|
||||
return Bindings::CanPlayTypeResult::Probably;
|
||||
// "Maybe" because we support mp3, but "mpeg" can also refer to MP1 and MP2.
|
||||
if (mime_type->subtype() == "mpeg"sv)
|
||||
return Bindings::CanPlayTypeResult::Maybe;
|
||||
if (mime_type->subtype() == "mp3"sv)
|
||||
if (mime_type->subtype() == "ogg"sv)
|
||||
return Bindings::CanPlayTypeResult::Probably;
|
||||
if (mime_type->subtype() == "wav"sv)
|
||||
return Bindings::CanPlayTypeResult::Probably;
|
||||
if (mime_type->subtype() == "flac"sv)
|
||||
return Bindings::CanPlayTypeResult::Probably;
|
||||
// "Maybe" because we support Ogg Vorbis, but "ogg" can contain other codecs
|
||||
if (mime_type->subtype() == "ogg"sv)
|
||||
return Bindings::CanPlayTypeResult::Maybe;
|
||||
if (mime_type->subtype() == "qoa"sv)
|
||||
return Bindings::CanPlayTypeResult::Probably;
|
||||
return Bindings::CanPlayTypeResult::Maybe;
|
||||
}
|
||||
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/Time.h>
|
||||
#include <LibMedia/Audio/Loader.h>
|
||||
#include <LibMedia/Audio/Resampler.h>
|
||||
#include <LibMedia/Audio/Sample.h>
|
||||
#include <LibWeb/Platform/AudioCodecPlugin.h>
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
#include <AK/MemoryStream.h>
|
||||
#include <AK/String.h>
|
||||
#include <AK/WeakPtr.h>
|
||||
#include <LibCore/EventLoop.h>
|
||||
#include <LibCore/ThreadedPromise.h>
|
||||
|
|
Loading…
Reference in a new issue