mirror of
https://github.com/jorio/Pomme.git
synced 2025-02-17 07:32:11 +00:00
Import source from Nanosaur port
This commit is contained in:
parent
481ab70ef3
commit
5fc6598b47
87
CMakeLists.txt
Normal file
87
CMakeLists.txt
Normal file
@ -0,0 +1,87 @@
|
||||
cmake_minimum_required(VERSION 3.17)
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
project(Pomme CXX)
|
||||
|
||||
set(POMME_SRCDIR src)
|
||||
|
||||
set(POMME_SOURCES
|
||||
${POMME_SRCDIR}/Pomme.cpp
|
||||
${POMME_SRCDIR}/Pomme.h
|
||||
${POMME_SRCDIR}/PommeDebug.cpp
|
||||
${POMME_SRCDIR}/PommeDebug.h
|
||||
${POMME_SRCDIR}/PommeEnums.h
|
||||
${POMME_SRCDIR}/PommeFiles.h
|
||||
${POMME_SRCDIR}/PommeGraphics.h
|
||||
${POMME_SRCDIR}/PommeInit.h
|
||||
${POMME_SRCDIR}/PommeInput.h
|
||||
${POMME_SRCDIR}/PommeSound.h
|
||||
${POMME_SRCDIR}/PommeTime.h
|
||||
${POMME_SRCDIR}/PommeTypes.h
|
||||
${POMME_SRCDIR}/PommeVideo.h
|
||||
${POMME_SRCDIR}/Files/Files.cpp
|
||||
${POMME_SRCDIR}/Files/HostVolume.cpp
|
||||
${POMME_SRCDIR}/Files/HostVolume.h
|
||||
${POMME_SRCDIR}/Files/Resources.cpp
|
||||
${POMME_SRCDIR}/Files/Volume.h
|
||||
${POMME_SRCDIR}/Graphics/ARGBPixmap.cpp
|
||||
${POMME_SRCDIR}/Graphics/Color.cpp
|
||||
${POMME_SRCDIR}/Graphics/Graphics.cpp
|
||||
${POMME_SRCDIR}/Graphics/PICT.cpp
|
||||
${POMME_SRCDIR}/Graphics/SysFont.h
|
||||
${POMME_SRCDIR}/Graphics/SystemPalettes.cpp
|
||||
${POMME_SRCDIR}/Input/SDLInput.cpp
|
||||
${POMME_SRCDIR}/Memory/Memory.cpp
|
||||
${POMME_SRCDIR}/Sound/AIFF.cpp
|
||||
${POMME_SRCDIR}/Sound/cmixer.cpp
|
||||
${POMME_SRCDIR}/Sound/cmixer.h
|
||||
${POMME_SRCDIR}/Sound/IMA4.cpp
|
||||
${POMME_SRCDIR}/Sound/MACE.cpp
|
||||
${POMME_SRCDIR}/Sound/SoundManager.cpp
|
||||
${POMME_SRCDIR}/Sound/xlaw.cpp
|
||||
${POMME_SRCDIR}/Time/TimeManager.cpp
|
||||
${POMME_SRCDIR}/Utilities/BigEndianIStream.cpp
|
||||
${POMME_SRCDIR}/Utilities/FixedPool.h
|
||||
${POMME_SRCDIR}/Utilities/GrowablePool.h
|
||||
${POMME_SRCDIR}/Utilities/IEEEExtended.cpp
|
||||
${POMME_SRCDIR}/Utilities/IEEEExtended.h
|
||||
${POMME_SRCDIR}/Utilities/memstream.cpp
|
||||
${POMME_SRCDIR}/Utilities/memstream.h
|
||||
${POMME_SRCDIR}/Utilities/StringUtils.cpp
|
||||
${POMME_SRCDIR}/Utilities/StringUtils.h
|
||||
${POMME_SRCDIR}/Utilities/structpack.cpp
|
||||
${POMME_SRCDIR}/Utilities/structpack.h
|
||||
${POMME_SRCDIR}/Video/Cinepak.cpp
|
||||
${POMME_SRCDIR}/Video/Cinepak.h
|
||||
${POMME_SRCDIR}/Video/moov.cpp
|
||||
$<$<BOOL:${WIN32}>:${POMME_SRCDIR}/Platform/Windows/PommeWindows.cpp>
|
||||
$<$<BOOL:${WIN32}>:${POMME_SRCDIR}/Platform/Windows/PommeWindows.h>
|
||||
)
|
||||
|
||||
add_library(${PROJECT_NAME} ${POMME_SOURCES})
|
||||
|
||||
find_package(SDL2 REQUIRED)
|
||||
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE
|
||||
${SDL2_INCLUDE_DIRS}
|
||||
${POMME_SRCDIR}
|
||||
)
|
||||
|
||||
if(MSVC)
|
||||
target_compile_options(${PROJECT_NAME} PRIVATE
|
||||
/EHa
|
||||
/W4
|
||||
/wd4100 # unreferenced formal parameter
|
||||
/wd4201 # nonstandard extension
|
||||
/wd4244 # conversion from double to float
|
||||
/wd4458 # declaration of variable hides class member
|
||||
)
|
||||
else()
|
||||
target_compile_options(${PROJECT_NAME} PRIVATE
|
||||
-Wall
|
||||
-Wextra
|
||||
-Wshadow
|
||||
-Wno-multichar
|
||||
-Wno-unused-parameter
|
||||
-fexceptions
|
||||
)
|
||||
endif()
|
9
src/CompilerSupport/filesystem.h
Normal file
9
src/CompilerSupport/filesystem.h
Normal file
@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#if defined(__cplusplus) && __cplusplus >= 201703L && defined(__has_include) && __has_include(<filesystem>)
|
||||
#include <filesystem>
|
||||
namespace fs = std::filesystem;
|
||||
#else
|
||||
#include "CompilerSupport/filesystem_implementation.hpp"
|
||||
namespace fs = ghc::filesystem;
|
||||
#endif
|
5689
src/CompilerSupport/filesystem_implementation.hpp
Normal file
5689
src/CompilerSupport/filesystem_implementation.hpp
Normal file
File diff suppressed because it is too large
Load Diff
8
src/CompilerSupport/span.h
Normal file
8
src/CompilerSupport/span.h
Normal file
@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#if defined(__cplusplus) && __cplusplus >= 201703L && defined(__has_include) && __has_include(<span>)
|
||||
#include <span>
|
||||
#else
|
||||
#include "CompilerSupport/span_implementation.hpp"
|
||||
#endif
|
||||
|
611
src/CompilerSupport/span_implementation.hpp
Normal file
611
src/CompilerSupport/span_implementation.hpp
Normal file
@ -0,0 +1,611 @@
|
||||
/*
|
||||
This is an implementation of C++20's std::span
|
||||
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/n4820.pdf
|
||||
*/
|
||||
|
||||
// Copyright Tristan Brindle 2018.
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file ../../LICENSE_1_0.txt or copy at
|
||||
// https://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
#ifndef TCB_SPAN_HPP_INCLUDED
|
||||
#define TCB_SPAN_HPP_INCLUDED
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
|
||||
#ifndef TCB_SPAN_NO_EXCEPTIONS
|
||||
// Attempt to discover whether we're being compiled with exception support
|
||||
#if !(defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND))
|
||||
#define TCB_SPAN_NO_EXCEPTIONS
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef TCB_SPAN_NO_EXCEPTIONS
|
||||
#include <cstdio>
|
||||
#include <stdexcept>
|
||||
#endif
|
||||
|
||||
// Various feature test macros
|
||||
|
||||
#ifndef TCB_SPAN_NAMESPACE_NAME
|
||||
#define TCB_SPAN_NAMESPACE_NAME std
|
||||
#endif
|
||||
|
||||
#if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)
|
||||
#define TCB_SPAN_HAVE_CPP17
|
||||
#endif
|
||||
|
||||
#if __cplusplus >= 201402L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201402L)
|
||||
#define TCB_SPAN_HAVE_CPP14
|
||||
#endif
|
||||
|
||||
namespace TCB_SPAN_NAMESPACE_NAME {
|
||||
|
||||
// Establish default contract checking behavior
|
||||
#if !defined(TCB_SPAN_THROW_ON_CONTRACT_VIOLATION) && \
|
||||
!defined(TCB_SPAN_TERMINATE_ON_CONTRACT_VIOLATION) && \
|
||||
!defined(TCB_SPAN_NO_CONTRACT_CHECKING)
|
||||
#if defined(NDEBUG) || !defined(TCB_SPAN_HAVE_CPP14)
|
||||
#define TCB_SPAN_NO_CONTRACT_CHECKING
|
||||
#else
|
||||
#define TCB_SPAN_TERMINATE_ON_CONTRACT_VIOLATION
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(TCB_SPAN_THROW_ON_CONTRACT_VIOLATION)
|
||||
struct contract_violation_error : std::logic_error {
|
||||
explicit contract_violation_error(const char* msg) : std::logic_error(msg)
|
||||
{}
|
||||
};
|
||||
|
||||
inline void contract_violation(const char* msg)
|
||||
{
|
||||
throw contract_violation_error(msg);
|
||||
}
|
||||
|
||||
#elif defined(TCB_SPAN_TERMINATE_ON_CONTRACT_VIOLATION)
|
||||
[[noreturn]] inline void contract_violation(const char* /*unused*/)
|
||||
{
|
||||
std::terminate();
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !defined(TCB_SPAN_NO_CONTRACT_CHECKING)
|
||||
#define TCB_SPAN_STRINGIFY(cond) #cond
|
||||
#define TCB_SPAN_EXPECT(cond) \
|
||||
cond ? (void) 0 : contract_violation("Expected " TCB_SPAN_STRINGIFY(cond))
|
||||
#else
|
||||
#define TCB_SPAN_EXPECT(cond)
|
||||
#endif
|
||||
|
||||
#if defined(TCB_SPAN_HAVE_CPP17) || defined(__cpp_inline_variables)
|
||||
#define TCB_SPAN_INLINE_VAR inline
|
||||
#else
|
||||
#define TCB_SPAN_INLINE_VAR
|
||||
#endif
|
||||
|
||||
#if defined(TCB_SPAN_HAVE_CPP14) || \
|
||||
(defined(__cpp_constexpr) && __cpp_constexpr >= 201304)
|
||||
#define TCB_SPAN_HAVE_CPP14_CONSTEXPR
|
||||
#endif
|
||||
|
||||
#if defined(TCB_SPAN_HAVE_CPP14_CONSTEXPR)
|
||||
#define TCB_SPAN_CONSTEXPR14 constexpr
|
||||
#else
|
||||
#define TCB_SPAN_CONSTEXPR14
|
||||
#endif
|
||||
|
||||
#if defined(TCB_SPAN_HAVE_CPP14_CONSTEXPR) && \
|
||||
(!defined(_MSC_VER) || _MSC_VER > 1900)
|
||||
#define TCB_SPAN_CONSTEXPR_ASSIGN constexpr
|
||||
#else
|
||||
#define TCB_SPAN_CONSTEXPR_ASSIGN
|
||||
#endif
|
||||
|
||||
#if defined(TCB_SPAN_NO_CONTRACT_CHECKING)
|
||||
#define TCB_SPAN_CONSTEXPR11 constexpr
|
||||
#else
|
||||
#define TCB_SPAN_CONSTEXPR11 TCB_SPAN_CONSTEXPR14
|
||||
#endif
|
||||
|
||||
#if defined(TCB_SPAN_HAVE_CPP17) || defined(__cpp_deduction_guides)
|
||||
#define TCB_SPAN_HAVE_DEDUCTION_GUIDES
|
||||
#endif
|
||||
|
||||
#if defined(TCB_SPAN_HAVE_CPP17) || defined(__cpp_lib_byte)
|
||||
#define TCB_SPAN_HAVE_STD_BYTE
|
||||
#endif
|
||||
|
||||
#if defined(TCB_SPAN_HAVE_CPP17) || defined(__cpp_lib_array_constexpr)
|
||||
#define TCB_SPAN_HAVE_CONSTEXPR_STD_ARRAY_ETC
|
||||
#endif
|
||||
|
||||
#if defined(TCB_SPAN_HAVE_CONSTEXPR_STD_ARRAY_ETC)
|
||||
#define TCB_SPAN_ARRAY_CONSTEXPR constexpr
|
||||
#else
|
||||
#define TCB_SPAN_ARRAY_CONSTEXPR
|
||||
#endif
|
||||
|
||||
#ifdef TCB_SPAN_HAVE_STD_BYTE
|
||||
using byte = std::byte;
|
||||
#else
|
||||
using byte = unsigned char;
|
||||
#endif
|
||||
|
||||
#if defined(TCB_SPAN_HAVE_CPP17)
|
||||
#define TCB_SPAN_NODISCARD [[nodiscard]]
|
||||
#else
|
||||
#define TCB_SPAN_NODISCARD
|
||||
#endif
|
||||
|
||||
TCB_SPAN_INLINE_VAR constexpr std::size_t dynamic_extent = SIZE_MAX;
|
||||
|
||||
template <typename ElementType, std::size_t Extent = dynamic_extent>
|
||||
class span;
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename E, std::size_t S>
|
||||
struct span_storage {
|
||||
constexpr span_storage() noexcept = default;
|
||||
|
||||
constexpr span_storage(E* p_ptr, std::size_t /*unused*/) noexcept
|
||||
: ptr(p_ptr)
|
||||
{}
|
||||
|
||||
E* ptr = nullptr;
|
||||
static constexpr std::size_t size = S;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
struct span_storage<E, dynamic_extent> {
|
||||
constexpr span_storage() noexcept = default;
|
||||
|
||||
constexpr span_storage(E* p_ptr, std::size_t p_size) noexcept
|
||||
: ptr(p_ptr), size(p_size)
|
||||
{}
|
||||
|
||||
E* ptr = nullptr;
|
||||
std::size_t size = 0;
|
||||
};
|
||||
|
||||
// Reimplementation of C++17 std::size() and std::data()
|
||||
#if defined(TCB_SPAN_HAVE_CPP17) || \
|
||||
defined(__cpp_lib_nonmember_container_access)
|
||||
using std::data;
|
||||
using std::size;
|
||||
#else
|
||||
template <class C>
|
||||
constexpr auto size(const C& c) -> decltype(c.size())
|
||||
{
|
||||
return c.size();
|
||||
}
|
||||
|
||||
template <class T, std::size_t N>
|
||||
constexpr std::size_t size(const T (&)[N]) noexcept
|
||||
{
|
||||
return N;
|
||||
}
|
||||
|
||||
template <class C>
|
||||
constexpr auto data(C& c) -> decltype(c.data())
|
||||
{
|
||||
return c.data();
|
||||
}
|
||||
|
||||
template <class C>
|
||||
constexpr auto data(const C& c) -> decltype(c.data())
|
||||
{
|
||||
return c.data();
|
||||
}
|
||||
|
||||
template <class T, std::size_t N>
|
||||
constexpr T* data(T (&array)[N]) noexcept
|
||||
{
|
||||
return array;
|
||||
}
|
||||
|
||||
template <class E>
|
||||
constexpr const E* data(std::initializer_list<E> il) noexcept
|
||||
{
|
||||
return il.begin();
|
||||
}
|
||||
#endif // TCB_SPAN_HAVE_CPP17
|
||||
|
||||
#if defined(TCB_SPAN_HAVE_CPP17) || defined(__cpp_lib_void_t)
|
||||
using std::void_t;
|
||||
#else
|
||||
template <typename...>
|
||||
using void_t = void;
|
||||
#endif
|
||||
|
||||
template <typename T>
|
||||
using uncvref_t =
|
||||
typename std::remove_cv<typename std::remove_reference<T>::type>::type;
|
||||
|
||||
template <typename>
|
||||
struct is_span : std::false_type {};
|
||||
|
||||
template <typename T, std::size_t S>
|
||||
struct is_span<span<T, S>> : std::true_type {};
|
||||
|
||||
template <typename>
|
||||
struct is_std_array : std::false_type {};
|
||||
|
||||
template <typename T, std::size_t N>
|
||||
struct is_std_array<std::array<T, N>> : std::true_type {};
|
||||
|
||||
template <typename, typename = void>
|
||||
struct has_size_and_data : std::false_type {};
|
||||
|
||||
template <typename T>
|
||||
struct has_size_and_data<T, void_t<decltype(detail::size(std::declval<T>())),
|
||||
decltype(detail::data(std::declval<T>()))>>
|
||||
: std::true_type {};
|
||||
|
||||
template <typename C, typename U = uncvref_t<C>>
|
||||
struct is_container {
|
||||
static constexpr bool value =
|
||||
!is_span<U>::value && !is_std_array<U>::value &&
|
||||
!std::is_array<U>::value && has_size_and_data<C>::value;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
using remove_pointer_t = typename std::remove_pointer<T>::type;
|
||||
|
||||
template <typename, typename, typename = void>
|
||||
struct is_container_element_type_compatible : std::false_type {};
|
||||
|
||||
template <typename T, typename E>
|
||||
struct is_container_element_type_compatible<
|
||||
T, E,
|
||||
typename std::enable_if<
|
||||
!std::is_same<typename std::remove_cv<decltype(
|
||||
detail::data(std::declval<T>()))>::type,
|
||||
void>::value>::type>
|
||||
: std::is_convertible<
|
||||
remove_pointer_t<decltype(detail::data(std::declval<T>()))> (*)[],
|
||||
E (*)[]> {};
|
||||
|
||||
template <typename, typename = size_t>
|
||||
struct is_complete : std::false_type {};
|
||||
|
||||
template <typename T>
|
||||
struct is_complete<T, decltype(sizeof(T))> : std::true_type {};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <typename ElementType, std::size_t Extent>
|
||||
class span {
|
||||
static_assert(std::is_object<ElementType>::value,
|
||||
"A span's ElementType must be an object type (not a "
|
||||
"reference type or void)");
|
||||
static_assert(detail::is_complete<ElementType>::value,
|
||||
"A span's ElementType must be a complete type (not a forward "
|
||||
"declaration)");
|
||||
static_assert(!std::is_abstract<ElementType>::value,
|
||||
"A span's ElementType cannot be an abstract class type");
|
||||
|
||||
using storage_type = detail::span_storage<ElementType, Extent>;
|
||||
|
||||
public:
|
||||
// constants and types
|
||||
using element_type = ElementType;
|
||||
using value_type = typename std::remove_cv<ElementType>::type;
|
||||
using size_type = std::size_t;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using pointer = element_type*;
|
||||
using const_pointer = const element_type*;
|
||||
using reference = element_type&;
|
||||
using const_reference = const element_type&;
|
||||
using iterator = pointer;
|
||||
using reverse_iterator = std::reverse_iterator<iterator>;
|
||||
|
||||
static constexpr size_type extent = Extent;
|
||||
|
||||
// [span.cons], span constructors, copy, assignment, and destructor
|
||||
template <
|
||||
std::size_t E = Extent,
|
||||
typename std::enable_if<(E == dynamic_extent || E <= 0), int>::type = 0>
|
||||
constexpr span() noexcept
|
||||
{}
|
||||
|
||||
TCB_SPAN_CONSTEXPR11 span(pointer ptr, size_type count)
|
||||
: storage_(ptr, count)
|
||||
{
|
||||
TCB_SPAN_EXPECT(extent == dynamic_extent || count == extent);
|
||||
}
|
||||
|
||||
TCB_SPAN_CONSTEXPR11 span(pointer first_elem, pointer last_elem)
|
||||
: storage_(first_elem, last_elem - first_elem)
|
||||
{
|
||||
TCB_SPAN_EXPECT(extent == dynamic_extent ||
|
||||
last_elem - first_elem ==
|
||||
static_cast<std::ptrdiff_t>(extent));
|
||||
}
|
||||
|
||||
template <std::size_t N, std::size_t E = Extent,
|
||||
typename std::enable_if<
|
||||
(E == dynamic_extent || N == E) &&
|
||||
detail::is_container_element_type_compatible<
|
||||
element_type (&)[N], ElementType>::value,
|
||||
int>::type = 0>
|
||||
constexpr span(element_type (&arr)[N]) noexcept : storage_(arr, N)
|
||||
{}
|
||||
|
||||
template <std::size_t N, std::size_t E = Extent,
|
||||
typename std::enable_if<
|
||||
(E == dynamic_extent || N == E) &&
|
||||
detail::is_container_element_type_compatible<
|
||||
std::array<value_type, N>&, ElementType>::value,
|
||||
int>::type = 0>
|
||||
TCB_SPAN_ARRAY_CONSTEXPR span(std::array<value_type, N>& arr) noexcept
|
||||
: storage_(arr.data(), N)
|
||||
{}
|
||||
|
||||
template <std::size_t N, std::size_t E = Extent,
|
||||
typename std::enable_if<
|
||||
(E == dynamic_extent || N == E) &&
|
||||
detail::is_container_element_type_compatible<
|
||||
const std::array<value_type, N>&, ElementType>::value,
|
||||
int>::type = 0>
|
||||
TCB_SPAN_ARRAY_CONSTEXPR span(const std::array<value_type, N>& arr) noexcept
|
||||
: storage_(arr.data(), N)
|
||||
{}
|
||||
|
||||
template <
|
||||
typename Container, std::size_t E = Extent,
|
||||
typename std::enable_if<
|
||||
E == dynamic_extent && detail::is_container<Container>::value &&
|
||||
detail::is_container_element_type_compatible<
|
||||
Container&, ElementType>::value,
|
||||
int>::type = 0>
|
||||
constexpr span(Container& cont)
|
||||
: storage_(detail::data(cont), detail::size(cont))
|
||||
{}
|
||||
|
||||
template <
|
||||
typename Container, std::size_t E = Extent,
|
||||
typename std::enable_if<
|
||||
E == dynamic_extent && detail::is_container<Container>::value &&
|
||||
detail::is_container_element_type_compatible<
|
||||
const Container&, ElementType>::value,
|
||||
int>::type = 0>
|
||||
constexpr span(const Container& cont)
|
||||
: storage_(detail::data(cont), detail::size(cont))
|
||||
{}
|
||||
|
||||
constexpr span(const span& other) noexcept = default;
|
||||
|
||||
template <typename OtherElementType, std::size_t OtherExtent,
|
||||
typename std::enable_if<
|
||||
(Extent == OtherExtent || Extent == dynamic_extent) &&
|
||||
std::is_convertible<OtherElementType (*)[],
|
||||
ElementType (*)[]>::value,
|
||||
int>::type = 0>
|
||||
constexpr span(const span<OtherElementType, OtherExtent>& other) noexcept
|
||||
: storage_(other.data(), other.size())
|
||||
{}
|
||||
|
||||
~span() noexcept = default;
|
||||
|
||||
TCB_SPAN_CONSTEXPR_ASSIGN span&
|
||||
operator=(const span& other) noexcept = default;
|
||||
|
||||
// [span.sub], span subviews
|
||||
template <std::size_t Count>
|
||||
TCB_SPAN_CONSTEXPR11 span<element_type, Count> first() const
|
||||
{
|
||||
TCB_SPAN_EXPECT(Count <= size());
|
||||
return {data(), Count};
|
||||
}
|
||||
|
||||
template <std::size_t Count>
|
||||
TCB_SPAN_CONSTEXPR11 span<element_type, Count> last() const
|
||||
{
|
||||
TCB_SPAN_EXPECT(Count <= size());
|
||||
return {data() + (size() - Count), Count};
|
||||
}
|
||||
|
||||
template <std::size_t Offset, std::size_t Count = dynamic_extent>
|
||||
using subspan_return_t =
|
||||
span<ElementType, Count != dynamic_extent
|
||||
? Count
|
||||
: (Extent != dynamic_extent ? Extent - Offset
|
||||
: dynamic_extent)>;
|
||||
|
||||
template <std::size_t Offset, std::size_t Count = dynamic_extent>
|
||||
TCB_SPAN_CONSTEXPR11 subspan_return_t<Offset, Count> subspan() const
|
||||
{
|
||||
TCB_SPAN_EXPECT(Offset <= size() &&
|
||||
(Count == dynamic_extent || Offset + Count <= size()));
|
||||
return {data() + Offset,
|
||||
Count != dynamic_extent ? Count : size() - Offset};
|
||||
}
|
||||
|
||||
TCB_SPAN_CONSTEXPR11 span<element_type, dynamic_extent>
|
||||
first(size_type count) const
|
||||
{
|
||||
TCB_SPAN_EXPECT(count <= size());
|
||||
return {data(), count};
|
||||
}
|
||||
|
||||
TCB_SPAN_CONSTEXPR11 span<element_type, dynamic_extent>
|
||||
last(size_type count) const
|
||||
{
|
||||
TCB_SPAN_EXPECT(count <= size());
|
||||
return {data() + (size() - count), count};
|
||||
}
|
||||
|
||||
TCB_SPAN_CONSTEXPR11 span<element_type, dynamic_extent>
|
||||
subspan(size_type offset, size_type count = dynamic_extent) const
|
||||
{
|
||||
TCB_SPAN_EXPECT(offset <= size() &&
|
||||
(count == dynamic_extent || offset + count <= size()));
|
||||
return {data() + offset,
|
||||
count == dynamic_extent ? size() - offset : count};
|
||||
}
|
||||
|
||||
// [span.obs], span observers
|
||||
constexpr size_type size() const noexcept { return storage_.size; }
|
||||
|
||||
constexpr size_type size_bytes() const noexcept
|
||||
{
|
||||
return size() * sizeof(element_type);
|
||||
}
|
||||
|
||||
TCB_SPAN_NODISCARD constexpr bool empty() const noexcept
|
||||
{
|
||||
return size() == 0;
|
||||
}
|
||||
|
||||
// [span.elem], span element access
|
||||
TCB_SPAN_CONSTEXPR11 reference operator[](size_type idx) const
|
||||
{
|
||||
TCB_SPAN_EXPECT(idx < size());
|
||||
return *(data() + idx);
|
||||
}
|
||||
|
||||
TCB_SPAN_CONSTEXPR11 reference front() const
|
||||
{
|
||||
TCB_SPAN_EXPECT(!empty());
|
||||
return *data();
|
||||
}
|
||||
|
||||
TCB_SPAN_CONSTEXPR11 reference back() const
|
||||
{
|
||||
TCB_SPAN_EXPECT(!empty());
|
||||
return *(data() + (size() - 1));
|
||||
}
|
||||
|
||||
constexpr pointer data() const noexcept { return storage_.ptr; }
|
||||
|
||||
// [span.iterators], span iterator support
|
||||
constexpr iterator begin() const noexcept { return data(); }
|
||||
|
||||
constexpr iterator end() const noexcept { return data() + size(); }
|
||||
|
||||
TCB_SPAN_ARRAY_CONSTEXPR reverse_iterator rbegin() const noexcept
|
||||
{
|
||||
return reverse_iterator(end());
|
||||
}
|
||||
|
||||
TCB_SPAN_ARRAY_CONSTEXPR reverse_iterator rend() const noexcept
|
||||
{
|
||||
return reverse_iterator(begin());
|
||||
}
|
||||
|
||||
private:
|
||||
storage_type storage_{};
|
||||
};
|
||||
|
||||
#ifdef TCB_SPAN_HAVE_DEDUCTION_GUIDES
|
||||
|
||||
/* Deduction Guides */
|
||||
template <class T, size_t N>
|
||||
span(T (&)[N])->span<T, N>;
|
||||
|
||||
template <class T, size_t N>
|
||||
span(std::array<T, N>&)->span<T, N>;
|
||||
|
||||
template <class T, size_t N>
|
||||
span(const std::array<T, N>&)->span<const T, N>;
|
||||
|
||||
template <class Container>
|
||||
span(Container&)->span<typename Container::value_type>;
|
||||
|
||||
template <class Container>
|
||||
span(const Container&)->span<const typename Container::value_type>;
|
||||
|
||||
#endif // TCB_HAVE_DEDUCTION_GUIDES
|
||||
|
||||
template <typename ElementType, std::size_t Extent>
|
||||
constexpr span<ElementType, Extent>
|
||||
make_span(span<ElementType, Extent> s) noexcept
|
||||
{
|
||||
return s;
|
||||
}
|
||||
|
||||
template <typename T, std::size_t N>
|
||||
constexpr span<T, N> make_span(T (&arr)[N]) noexcept
|
||||
{
|
||||
return {arr};
|
||||
}
|
||||
|
||||
template <typename T, std::size_t N>
|
||||
TCB_SPAN_ARRAY_CONSTEXPR span<T, N> make_span(std::array<T, N>& arr) noexcept
|
||||
{
|
||||
return {arr};
|
||||
}
|
||||
|
||||
template <typename T, std::size_t N>
|
||||
TCB_SPAN_ARRAY_CONSTEXPR span<const T, N>
|
||||
make_span(const std::array<T, N>& arr) noexcept
|
||||
{
|
||||
return {arr};
|
||||
}
|
||||
|
||||
template <typename Container>
|
||||
constexpr span<typename Container::value_type> make_span(Container& cont)
|
||||
{
|
||||
return {cont};
|
||||
}
|
||||
|
||||
template <typename Container>
|
||||
constexpr span<const typename Container::value_type>
|
||||
make_span(const Container& cont)
|
||||
{
|
||||
return {cont};
|
||||
}
|
||||
|
||||
template <typename ElementType, std::size_t Extent>
|
||||
span<const byte, ((Extent == dynamic_extent) ? dynamic_extent
|
||||
: sizeof(ElementType) * Extent)>
|
||||
as_bytes(span<ElementType, Extent> s) noexcept
|
||||
{
|
||||
return {reinterpret_cast<const byte*>(s.data()), s.size_bytes()};
|
||||
}
|
||||
|
||||
template <
|
||||
class ElementType, size_t Extent,
|
||||
typename std::enable_if<!std::is_const<ElementType>::value, int>::type = 0>
|
||||
span<byte, ((Extent == dynamic_extent) ? dynamic_extent
|
||||
: sizeof(ElementType) * Extent)>
|
||||
as_writable_bytes(span<ElementType, Extent> s) noexcept
|
||||
{
|
||||
return {reinterpret_cast<byte*>(s.data()), s.size_bytes()};
|
||||
}
|
||||
|
||||
template <std::size_t N, typename E, std::size_t S>
|
||||
constexpr auto get(span<E, S> s) -> decltype(s[N])
|
||||
{
|
||||
return s[N];
|
||||
}
|
||||
|
||||
} // namespace TCB_SPAN_NAMESPACE_NAME
|
||||
|
||||
namespace std {
|
||||
|
||||
template <typename ElementType, size_t Extent>
|
||||
class tuple_size<TCB_SPAN_NAMESPACE_NAME::span<ElementType, Extent>>
|
||||
: public integral_constant<size_t, Extent> {};
|
||||
|
||||
template <typename ElementType>
|
||||
class tuple_size<TCB_SPAN_NAMESPACE_NAME::span<
|
||||
ElementType, TCB_SPAN_NAMESPACE_NAME::dynamic_extent>>; // not defined
|
||||
|
||||
template <size_t I, typename ElementType, size_t Extent>
|
||||
class tuple_element<I, TCB_SPAN_NAMESPACE_NAME::span<ElementType, Extent>> {
|
||||
public:
|
||||
static_assert(Extent != TCB_SPAN_NAMESPACE_NAME::dynamic_extent &&
|
||||
I < Extent,
|
||||
"");
|
||||
using type = ElementType;
|
||||
};
|
||||
|
||||
} // end namespace std
|
||||
|
||||
#endif // TCB_SPAN_HPP_INCLUDED
|
337
src/Files/Files.cpp
Normal file
337
src/Files/Files.cpp
Normal file
@ -0,0 +1,337 @@
|
||||
#include "Pomme.h"
|
||||
#include "Utilities/BigEndianIStream.h"
|
||||
#include "Utilities/GrowablePool.h"
|
||||
#include "Utilities/memstream.h"
|
||||
#include "PommeFiles.h"
|
||||
#include "Files/Volume.h"
|
||||
#include "Files/HostVolume.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include "CompilerSupport/filesystem.h"
|
||||
|
||||
#if _WIN32
|
||||
#include "Platform/Windows/PommeWindows.h"
|
||||
#endif
|
||||
|
||||
using namespace Pomme;
|
||||
using namespace Pomme::Files;
|
||||
|
||||
#define LOG POMME_GENLOG(POMME_DEBUG_FILES, "FILE")
|
||||
|
||||
constexpr int MAX_VOLUMES = 32767; // vRefNum is a signed short
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// State
|
||||
|
||||
static Pomme::GrowablePool<std::unique_ptr<ForkHandle>, SInt16, 0x7FFF> openFiles;
|
||||
|
||||
static std::vector<std::unique_ptr<Volume>> volumes;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Utilities
|
||||
|
||||
bool Pomme::Files::IsRefNumLegal(short refNum)
|
||||
{
|
||||
return openFiles.IsAllocated(refNum);
|
||||
}
|
||||
|
||||
std::iostream& Pomme::Files::GetStream(short refNum)
|
||||
{
|
||||
if (!IsRefNumLegal(refNum))
|
||||
{
|
||||
throw std::runtime_error("illegal refNum");
|
||||
}
|
||||
return openFiles[refNum]->GetStream();
|
||||
}
|
||||
|
||||
void Pomme::Files::CloseStream(short refNum)
|
||||
{
|
||||
if (!IsRefNumLegal(refNum))
|
||||
{
|
||||
throw std::runtime_error("illegal refNum");
|
||||
}
|
||||
openFiles[refNum].reset(nullptr);
|
||||
openFiles.Dispose(refNum);
|
||||
LOG << "Stream #" << refNum << " closed\n";
|
||||
}
|
||||
|
||||
bool Pomme::Files::IsStreamOpen(short refNum)
|
||||
{
|
||||
if (!IsRefNumLegal(refNum))
|
||||
{
|
||||
throw std::runtime_error("illegal refNum");
|
||||
}
|
||||
return openFiles[refNum].get() != nullptr;
|
||||
// return openFiles[refNum].stream.is_open();
|
||||
}
|
||||
|
||||
bool Pomme::Files::IsStreamPermissionAllowed(short refNum, char perm)
|
||||
{
|
||||
if (!IsRefNumLegal(refNum))
|
||||
{
|
||||
throw std::runtime_error("illegal refNum");
|
||||
}
|
||||
return (perm & openFiles[refNum]->permission) == perm;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Init
|
||||
|
||||
void Pomme::Files::Init()
|
||||
{
|
||||
auto hostVolume = std::make_unique<HostVolume>(0);
|
||||
volumes.push_back(std::move(hostVolume));
|
||||
|
||||
short systemRefNum = openFiles.Alloc();
|
||||
if (systemRefNum != 0)
|
||||
{
|
||||
throw std::logic_error("expecting 0 for system refnum");
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Implementation
|
||||
|
||||
bool IsVolumeLegal(short vRefNum)
|
||||
{
|
||||
return vRefNum >= 0 && (unsigned short) vRefNum < volumes.size();
|
||||
}
|
||||
|
||||
OSErr FSMakeFSSpec(short vRefNum, long dirID, const char* cstrFileName, FSSpec* spec)
|
||||
{
|
||||
if (!IsVolumeLegal(vRefNum))
|
||||
return nsvErr;
|
||||
|
||||
return volumes.at(vRefNum)->FSMakeFSSpec(dirID, cstrFileName, spec);
|
||||
}
|
||||
|
||||
static OSErr OpenFork(const FSSpec* spec, ForkType forkType, char permission, short* refNum)
|
||||
{
|
||||
if (openFiles.IsFull())
|
||||
return tmfoErr;
|
||||
if (!IsVolumeLegal(spec->vRefNum))
|
||||
return nsvErr;
|
||||
short newRefNum = openFiles.Alloc();
|
||||
auto& handlePtr = openFiles[newRefNum];
|
||||
OSErr rc = volumes.at(spec->vRefNum)->OpenFork(spec, forkType, permission, handlePtr);
|
||||
if (rc != noErr)
|
||||
{
|
||||
openFiles.Dispose(newRefNum);
|
||||
newRefNum = -1;
|
||||
LOG << "Failed to open " << spec->cName << "\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG << "Stream #" << newRefNum << " opened: " << spec->cName << ", " << (forkType == DataFork ? "data" : "rsrc") << "\n";
|
||||
}
|
||||
if (refNum)
|
||||
{
|
||||
*refNum = newRefNum;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
OSErr FSpOpenDF(const FSSpec* spec, char permission, short* refNum)
|
||||
{
|
||||
return OpenFork(spec, ForkType::DataFork, permission, refNum);
|
||||
}
|
||||
|
||||
OSErr FSpOpenRF(const FSSpec* spec, char permission, short* refNum)
|
||||
{
|
||||
return OpenFork(spec, ForkType::ResourceFork, permission, refNum);
|
||||
}
|
||||
|
||||
OSErr FindFolder(short vRefNum, OSType folderType, Boolean createFolder, short* foundVRefNum, long* foundDirID)
|
||||
{
|
||||
if (vRefNum != kOnSystemDisk)
|
||||
{
|
||||
throw std::runtime_error("FindFolder only supports kOnSystemDisk");
|
||||
}
|
||||
|
||||
fs::path path;
|
||||
|
||||
switch (folderType)
|
||||
{
|
||||
case kPreferencesFolderType:
|
||||
{
|
||||
#ifdef _WIN32
|
||||
path = Pomme::Platform::Windows::GetPreferencesFolder();
|
||||
#elif defined(__APPLE__)
|
||||
const char *home = getenv("HOME");
|
||||
if (!home) {
|
||||
return fnfErr;
|
||||
}
|
||||
path = fs::path(home) / "Library" / "Preferences";
|
||||
#else
|
||||
const char* home = getenv("XDG_CONFIG_HOME");
|
||||
if (home)
|
||||
{
|
||||
path = std::filesystem::path(home);
|
||||
}
|
||||
else
|
||||
{
|
||||
home = getenv("HOME");
|
||||
if (!home)
|
||||
{
|
||||
return fnfErr;
|
||||
}
|
||||
path = std::filesystem::path(home) / ".config";
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
TODO2("folder type '" << Pomme::FourCCString(folderType) << "' isn't supported yet");
|
||||
return fnfErr;
|
||||
}
|
||||
|
||||
path = path.lexically_normal();
|
||||
|
||||
bool exists = fs::exists(path);
|
||||
|
||||
if (exists && !fs::is_directory(path))
|
||||
{
|
||||
return dupFNErr;
|
||||
}
|
||||
if (!exists && createFolder)
|
||||
{
|
||||
fs::create_directories(path);
|
||||
}
|
||||
|
||||
*foundVRefNum = 0;//GetVolumeID(path);
|
||||
*foundDirID = dynamic_cast<HostVolume*>(volumes.at(0).get())->GetDirectoryID(path);
|
||||
return noErr;
|
||||
}
|
||||
|
||||
OSErr DirCreate(short vRefNum, long parentDirID, const char* cstrDirectoryName, long* createdDirID)
|
||||
{
|
||||
return IsVolumeLegal(vRefNum)
|
||||
? volumes.at(vRefNum)->DirCreate(parentDirID, cstrDirectoryName, createdDirID)
|
||||
: (OSErr)nsvErr;
|
||||
}
|
||||
|
||||
OSErr FSpCreate(const FSSpec* spec, OSType creator, OSType fileType, ScriptCode scriptTag)
|
||||
{
|
||||
return IsVolumeLegal(spec->vRefNum)
|
||||
? volumes.at(spec->vRefNum)->FSpCreate(spec, creator, fileType, scriptTag)
|
||||
: (OSErr)nsvErr;
|
||||
}
|
||||
|
||||
OSErr FSpDelete(const FSSpec* spec)
|
||||
{
|
||||
return IsVolumeLegal(spec->vRefNum)
|
||||
? volumes.at(spec->vRefNum)->FSpDelete(spec)
|
||||
: (OSErr)nsvErr;
|
||||
}
|
||||
|
||||
OSErr ResolveAlias(const FSSpec* spec, AliasHandle alias, FSSpec* target, Boolean* wasChanged)
|
||||
{
|
||||
*wasChanged = false;
|
||||
|
||||
int aliasSize = GetHandleSize(alias);
|
||||
|
||||
// the target FN is at offset 50, and the target FN is a Str63, so 50+64
|
||||
if (aliasSize < 50 + 64)
|
||||
{
|
||||
std::cerr << "unexpected size of alias: " << aliasSize << "\n";
|
||||
return unimpErr;
|
||||
}
|
||||
|
||||
memstream istr(*alias, GetHandleSize(alias));
|
||||
Pomme::BigEndianIStream f(istr);
|
||||
|
||||
f.Skip(4); // application signature
|
||||
|
||||
if (f.Read<UInt16>() != aliasSize)
|
||||
{
|
||||
std::cerr << "unexpected size field in alias\n";
|
||||
return unimpErr;
|
||||
}
|
||||
|
||||
if (f.Read<UInt16>() != 2)
|
||||
{
|
||||
std::cerr << "unexpected alias version number\n";
|
||||
return unimpErr;
|
||||
}
|
||||
|
||||
auto kind = f.Read<UInt16>();
|
||||
if (kind > 2)
|
||||
{
|
||||
std::cerr << "unsupported alias kind " << kind << "\n";
|
||||
return unimpErr;
|
||||
}
|
||||
|
||||
f.Skip(28); // volume name pascal string
|
||||
f.Skip(4); // volume creation date
|
||||
f.Skip(2); // volume signature (RW, BD, H+)
|
||||
f.Skip(2); // drive type (0=HD, 1=network, 2=400K FD, 3=800K FD, 4=1.4M FD, 5=misc.ejectable)
|
||||
f.Skip(4); // parent directory ID
|
||||
|
||||
auto targetFilename = f.ReadPascalString_FixedLengthRecord(63);
|
||||
|
||||
return FSMakeFSSpec(spec->vRefNum, spec->parID, targetFilename.c_str(), target);
|
||||
}
|
||||
|
||||
OSErr FSRead(short refNum, long* count, Ptr buffPtr)
|
||||
{
|
||||
if (*count < 0) return paramErr;
|
||||
if (!IsRefNumLegal(refNum)) return rfNumErr;
|
||||
if (!IsStreamOpen(refNum)) return fnOpnErr;
|
||||
if (!IsStreamPermissionAllowed(refNum, fsRdPerm)) return ioErr;
|
||||
|
||||
auto& f = GetStream(refNum);
|
||||
f.read(buffPtr, *count);
|
||||
*count = (long) f.gcount();
|
||||
if (f.eof()) return eofErr;
|
||||
|
||||
return noErr;
|
||||
}
|
||||
|
||||
OSErr FSWrite(short refNum, long* count, Ptr buffPtr)
|
||||
{
|
||||
if (*count < 0) return paramErr;
|
||||
if (!IsRefNumLegal(refNum)) return rfNumErr;
|
||||
if (!IsStreamOpen(refNum)) return fnOpnErr;
|
||||
if (!IsStreamPermissionAllowed(refNum, fsWrPerm)) return wrPermErr;
|
||||
|
||||
auto& f = GetStream(refNum);
|
||||
f.write(buffPtr, *count);
|
||||
|
||||
return noErr;
|
||||
}
|
||||
|
||||
OSErr FSClose(short refNum)
|
||||
{
|
||||
if (!IsRefNumLegal(refNum))
|
||||
return rfNumErr;
|
||||
if (!IsStreamOpen(refNum))
|
||||
return fnOpnErr;
|
||||
CloseStream(refNum);
|
||||
return noErr;
|
||||
}
|
||||
|
||||
OSErr GetEOF(short refNum, long* logEOF)
|
||||
{
|
||||
if (!IsRefNumLegal(refNum)) return rfNumErr;
|
||||
if (!IsStreamOpen(refNum)) return fnOpnErr;
|
||||
|
||||
auto& f = GetStream(refNum);
|
||||
StreamPosGuard guard(f);
|
||||
f.seekg(0, std::ios::end);
|
||||
*logEOF = (long) f.tellg();
|
||||
|
||||
return noErr;
|
||||
}
|
||||
|
||||
OSErr SetEOF(short refNum, long logEOF)
|
||||
{
|
||||
TODO();
|
||||
return unimpErr;
|
||||
}
|
||||
|
||||
FSSpec Pomme::Files::HostPathToFSSpec(const fs::path& fullPath)
|
||||
{
|
||||
return dynamic_cast<HostVolume*>(volumes[0].get())->ToFSSpec(fullPath);
|
||||
}
|
343
src/Files/HostVolume.cpp
Normal file
343
src/Files/HostVolume.cpp
Normal file
@ -0,0 +1,343 @@
|
||||
#include "PommeEnums.h"
|
||||
#include "PommeDebug.h"
|
||||
#include "PommeFiles.h"
|
||||
#include "Files/HostVolume.h"
|
||||
#include "Utilities/BigEndianIStream.h"
|
||||
#include "Utilities/StringUtils.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
#define LOG POMME_GENLOG(POMME_DEBUG_FILES, "HOST")
|
||||
|
||||
using namespace Pomme;
|
||||
using namespace Pomme::Files;
|
||||
|
||||
struct HostForkHandle : public ForkHandle
|
||||
{
|
||||
std::fstream backingStream;
|
||||
|
||||
public:
|
||||
HostForkHandle(ForkType theForkType, char perm, fs::path& path)
|
||||
: ForkHandle(theForkType, perm)
|
||||
{
|
||||
std::ios::openmode openmode = std::ios::binary;
|
||||
if (permission & fsWrPerm) openmode |= std::ios::out;
|
||||
if (permission & fsRdPerm) openmode |= std::ios::in;
|
||||
|
||||
backingStream = std::fstream(path, openmode);
|
||||
}
|
||||
|
||||
virtual ~HostForkHandle() = default;
|
||||
|
||||
virtual std::iostream& GetStream() override
|
||||
{
|
||||
return backingStream;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
HostVolume::HostVolume(short vRefNum)
|
||||
: Volume(vRefNum)
|
||||
{
|
||||
// default directory (ID 0)
|
||||
directories.push_back(fs::current_path());
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Public utilities
|
||||
|
||||
long HostVolume::GetDirectoryID(const fs::path& dirPath)
|
||||
{
|
||||
if (fs::exists(dirPath) && !fs::is_directory(dirPath))
|
||||
{
|
||||
std::cerr << "Warning: GetDirectoryID should only be used on directories! " << dirPath << "\n";
|
||||
}
|
||||
|
||||
auto it = std::find(directories.begin(), directories.end(), dirPath);
|
||||
if (it != directories.end())
|
||||
{
|
||||
return std::distance(directories.begin(), it);
|
||||
}
|
||||
|
||||
directories.emplace_back(dirPath);
|
||||
LOG << "directory " << directories.size() - 1 << ": " << dirPath << "\n";
|
||||
return (long) directories.size() - 1;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Internal utilities
|
||||
|
||||
fs::path HostVolume::ToPath(long parID, const std::string& name)
|
||||
{
|
||||
fs::path path = directories[parID] / AsU8(name);
|
||||
return path.lexically_normal();
|
||||
}
|
||||
|
||||
FSSpec HostVolume::ToFSSpec(const fs::path& fullPath)
|
||||
{
|
||||
auto parentPath = fullPath;
|
||||
parentPath.remove_filename();
|
||||
|
||||
FSSpec spec;
|
||||
spec.vRefNum = volumeID;
|
||||
spec.parID = GetDirectoryID(parentPath);
|
||||
snprintf(spec.cName, 256, "%s", (const char*) fullPath.filename().u8string().c_str());
|
||||
return spec;
|
||||
}
|
||||
|
||||
static void ADFJumpToResourceFork(std::istream& stream)
|
||||
{
|
||||
auto f = Pomme::BigEndianIStream(stream);
|
||||
|
||||
if (0x0005160700020000ULL != f.Read<UInt64>())
|
||||
{
|
||||
throw std::runtime_error("No ADF magic");
|
||||
}
|
||||
f.Skip(16);
|
||||
auto numOfEntries = f.Read<UInt16>();
|
||||
|
||||
for (int i = 0; i < numOfEntries; i++)
|
||||
{
|
||||
auto entryID = f.Read<UInt32>();
|
||||
auto offset = f.Read<UInt32>();
|
||||
f.Skip(4); // length
|
||||
if (entryID == 2)
|
||||
{
|
||||
// Found entry ID 2 (resource fork)
|
||||
f.Goto(offset);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw std::runtime_error("Didn't find entry ID=2 in ADF");
|
||||
}
|
||||
|
||||
OSErr HostVolume::OpenFork(const FSSpec* spec, ForkType forkType, char permission, std::unique_ptr<ForkHandle>& handle)
|
||||
{
|
||||
if (permission == fsCurPerm)
|
||||
{
|
||||
TODO2("fsCurPerm not implemented yet");
|
||||
return unimpErr;
|
||||
}
|
||||
|
||||
if ((permission & fsWrPerm) && forkType != ForkType::DataFork)
|
||||
{
|
||||
TODO2("opening resource fork for writing isn't implemented yet");
|
||||
return unimpErr;
|
||||
}
|
||||
|
||||
auto path = ToPath(spec->parID, spec->cName);
|
||||
|
||||
if (forkType == DataFork)
|
||||
{
|
||||
if (!fs::is_regular_file(path))
|
||||
{
|
||||
return fnfErr;
|
||||
}
|
||||
handle = std::make_unique<HostForkHandle>(DataFork, permission, path);
|
||||
return noErr;
|
||||
}
|
||||
else
|
||||
{
|
||||
// We want to open a resource fork on the host volume.
|
||||
// It is likely stored under one of the following names: ._NAME, NAME.rsrc, or NAME/..namedfork/rsrc
|
||||
|
||||
auto specName = path.filename().u8string();
|
||||
|
||||
struct
|
||||
{
|
||||
u8string filename;
|
||||
bool isAppleDoubleFile;
|
||||
} candidates[] =
|
||||
{
|
||||
// "._NAME": ADF contained in zips created by macOS's built-in archiver
|
||||
{ u8"._" + specName, true },
|
||||
|
||||
// "NAME.rsrc": ADF extracted from StuffIt/CompactPro archives by unar
|
||||
{ specName + u8".rsrc", true },
|
||||
|
||||
#if __APPLE__
|
||||
// "NAME/..namedfork/rsrc": macOS-specific way to access true resource forks (not ADF)
|
||||
{ specName + u8"/..namedfork/rsrc", false },
|
||||
#endif
|
||||
};
|
||||
|
||||
path.remove_filename();
|
||||
|
||||
for (auto c : candidates)
|
||||
{
|
||||
auto candidatePath = path / c.filename;
|
||||
if (!fs::is_regular_file(candidatePath))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
path = candidatePath;
|
||||
handle = std::make_unique<HostForkHandle>(ResourceFork, permission, path);
|
||||
if (c.isAppleDoubleFile)
|
||||
{
|
||||
ADFJumpToResourceFork(handle->GetStream());
|
||||
}
|
||||
return noErr;
|
||||
}
|
||||
}
|
||||
|
||||
return fnfErr;
|
||||
}
|
||||
|
||||
static bool CaseInsensitiveAppendToPath(
|
||||
fs::path& path,
|
||||
const std::string& element,
|
||||
bool skipFiles = false)
|
||||
{
|
||||
fs::path naiveConcat = path / AsU8(element);
|
||||
|
||||
if (!fs::exists(path))
|
||||
{
|
||||
path = naiveConcat;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fs::exists(naiveConcat))
|
||||
{
|
||||
path = naiveConcat;
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto ELEMENT = UppercaseCopy(element);
|
||||
|
||||
for (const auto& candidate : fs::directory_iterator(path))
|
||||
{
|
||||
if (skipFiles && !candidate.is_directory())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
auto f = FromU8(candidate.path().filename().u8string());
|
||||
|
||||
// It might be an AppleDouble resource fork ("._file" or "file.rsrc")
|
||||
if (candidate.is_regular_file())
|
||||
{
|
||||
if (f.starts_with("._"))
|
||||
{
|
||||
f = f.substr(2);
|
||||
}
|
||||
else if (f.ends_with(".rsrc"))
|
||||
{
|
||||
f = f.substr(0, f.length() - 5);
|
||||
}
|
||||
}
|
||||
|
||||
if (ELEMENT == UppercaseCopy(f))
|
||||
{
|
||||
path /= AsU8(f);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
path = naiveConcat;
|
||||
return false;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Implementation
|
||||
|
||||
OSErr HostVolume::FSMakeFSSpec(long dirID, const std::string& fileName, FSSpec* spec)
|
||||
{
|
||||
if (dirID < 0 || (unsigned long) dirID >= directories.size())
|
||||
{
|
||||
throw std::runtime_error("HostVolume::FSMakeFSSpec: directory ID not registered.");
|
||||
}
|
||||
|
||||
auto path = directories[dirID];
|
||||
auto suffix = fileName;
|
||||
|
||||
// Case-insensitive sanitization
|
||||
bool exists = fs::exists(path);
|
||||
std::string::size_type begin = (suffix.at(0) == ':') ? 1 : 0;
|
||||
|
||||
// Iterate on path elements between colons
|
||||
while (begin < suffix.length())
|
||||
{
|
||||
auto end = suffix.find(":", begin);
|
||||
|
||||
bool isLeaf = end == std::string::npos; // no ':' found => end of path
|
||||
if (isLeaf) end = suffix.length();
|
||||
|
||||
if (end == begin) // "::" => parent directory
|
||||
{
|
||||
path = path.parent_path();
|
||||
}
|
||||
else
|
||||
{
|
||||
auto element = suffix.substr(begin, end - begin);
|
||||
exists = CaseInsensitiveAppendToPath(path, element, !isLeaf);
|
||||
}
|
||||
|
||||
// +1: jump over current colon
|
||||
begin = end + 1;
|
||||
}
|
||||
|
||||
path = path.lexically_normal();
|
||||
|
||||
LOG << path << "\n";
|
||||
|
||||
*spec = ToFSSpec(path);
|
||||
|
||||
return exists ? noErr : fnfErr;
|
||||
}
|
||||
|
||||
OSErr HostVolume::DirCreate(long parentDirID, const std::string& directoryName, long* createdDirID)
|
||||
{
|
||||
const auto path = ToPath(parentDirID, directoryName);
|
||||
|
||||
if (fs::exists(path))
|
||||
{
|
||||
if (fs::is_directory(path))
|
||||
{
|
||||
LOG << __func__ << ": directory already exists: " << path << "\n";
|
||||
return noErr;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << __func__ << ": a file with the same name already exists: " << path << "\n";
|
||||
return bdNamErr;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
fs::create_directory(path);
|
||||
}
|
||||
catch (const fs::filesystem_error& e)
|
||||
{
|
||||
std::cerr << __func__ << " threw " << e.what() << "\n";
|
||||
return ioErr;
|
||||
}
|
||||
|
||||
if (createdDirID)
|
||||
{
|
||||
*createdDirID = GetDirectoryID(path);
|
||||
}
|
||||
|
||||
LOG << __func__ << ": created " << path << "\n";
|
||||
return noErr;
|
||||
}
|
||||
|
||||
OSErr HostVolume::FSpCreate(const FSSpec* spec, OSType creator, OSType fileType, ScriptCode scriptTag)
|
||||
{
|
||||
std::ofstream df(ToPath(spec->parID, spec->cName));
|
||||
df.close();
|
||||
// TODO: we could write an AppleDouble file to save the creator/filetype.
|
||||
return noErr;
|
||||
}
|
||||
|
||||
OSErr HostVolume::FSpDelete(const FSSpec* spec)
|
||||
{
|
||||
auto path = ToPath(spec->parID, spec->cName);
|
||||
|
||||
if (fs::remove(path))
|
||||
return noErr;
|
||||
else
|
||||
return fnfErr;
|
||||
}
|
44
src/Files/HostVolume.h
Normal file
44
src/Files/HostVolume.h
Normal file
@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#include "Files/Volume.h"
|
||||
#include "CompilerSupport/filesystem.h"
|
||||
#include <vector>
|
||||
|
||||
namespace Pomme::Files
|
||||
{
|
||||
/**
|
||||
* Volume implementation that lets the Mac app access files
|
||||
* on the host system's filesystem.
|
||||
*/
|
||||
class HostVolume : public Volume
|
||||
{
|
||||
std::vector<fs::path> directories;
|
||||
|
||||
fs::path ToPath(long parID, const std::string& name);
|
||||
|
||||
public:
|
||||
explicit HostVolume(short vRefNum);
|
||||
|
||||
virtual ~HostVolume() = default;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Utilities
|
||||
|
||||
long GetDirectoryID(const fs::path& dirPath);
|
||||
|
||||
FSSpec ToFSSpec(const fs::path& fullPath);
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Toolbox API Implementation
|
||||
|
||||
OSErr FSMakeFSSpec(long dirID, const std::string& fileName, FSSpec* spec) override;
|
||||
|
||||
OSErr OpenFork(const FSSpec* spec, ForkType forkType, char permission, std::unique_ptr<ForkHandle>& stream) override;
|
||||
|
||||
OSErr FSpCreate(const FSSpec* spec, OSType creator, OSType fileType, ScriptCode scriptTag) override;
|
||||
|
||||
OSErr FSpDelete(const FSSpec* spec) override;
|
||||
|
||||
OSErr DirCreate(long parentDirID, const std::string& directoryName, long* createdDirID) override;
|
||||
};
|
||||
}
|
304
src/Files/Resources.cpp
Normal file
304
src/Files/Resources.cpp
Normal file
@ -0,0 +1,304 @@
|
||||
#include "Pomme.h"
|
||||
#include "PommeFiles.h"
|
||||
#include "Utilities/BigEndianIStream.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
#define LOG POMME_GENLOG(POMME_DEBUG_RESOURCES, "RSRC")
|
||||
|
||||
using namespace Pomme;
|
||||
using namespace Pomme::Files;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// State
|
||||
|
||||
static OSErr lastResError = noErr;
|
||||
|
||||
static std::vector<ResourceFork> rezSearchStack;
|
||||
|
||||
static int rezSearchStackIndex = 0;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Internal
|
||||
|
||||
static void ResourceAssert(bool condition, const char* message)
|
||||
{
|
||||
if (!condition)
|
||||
{
|
||||
throw std::runtime_error(message);
|
||||
}
|
||||
}
|
||||
|
||||
static ResourceFork& GetCurRF()
|
||||
{
|
||||
return rezSearchStack[rezSearchStackIndex];
|
||||
}
|
||||
|
||||
#if 0
|
||||
static void PrintStack(const char* msg) {
|
||||
LOG << "------ RESOURCE SEARCH STACK " << msg << " -------\n";
|
||||
for (int i = int(rezSearchStack.size() - 1); i >= 0; i--) {
|
||||
LOG << (rezSearchStackIndex == i? " =====> " : " ")
|
||||
<< " StackPos=" << i << " "
|
||||
<< " RefNum=" << rezSearchStack[i].fileRefNum << " "
|
||||
// << Pomme::Files::GetHostFilename(rezSearchStack[i].fileRefNum)
|
||||
<< "\n";
|
||||
}
|
||||
LOG << "------------------------------------\n";
|
||||
}
|
||||
|
||||
static void DumpResource(ResourceMetadata& meta)
|
||||
{
|
||||
Handle handle = NewHandle(meta.size);
|
||||
auto& fork = Pomme::Files::GetStream(meta.forkRefNum);
|
||||
fork.seekg(meta.dataOffset, std::ios::beg);
|
||||
fork.read(*handle, meta.size);
|
||||
|
||||
std::stringstream fn;
|
||||
fn << "rezdump/" << meta.id << "_" << meta.name << "." << Pomme::FourCCString(meta.type, '_');
|
||||
std::ofstream dump(fn.str(), std::ofstream::binary);
|
||||
dump.write(*handle, meta.size);
|
||||
dump.close();
|
||||
std::cout << "wrote " << fn.str() << "\n";
|
||||
|
||||
DisposeHandle(handle);
|
||||
}
|
||||
#endif
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Resource file management
|
||||
|
||||
OSErr ResError(void)
|
||||
{
|
||||
return lastResError;
|
||||
}
|
||||
|
||||
short FSpOpenResFile(const FSSpec* spec, char permission)
|
||||
{
|
||||
short slot;
|
||||
|
||||
lastResError = FSpOpenRF(spec, permission, &slot);
|
||||
|
||||
if (noErr != lastResError)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto f = Pomme::BigEndianIStream(Pomme::Files::GetStream(slot));
|
||||
auto resForkOff = f.Tell();
|
||||
|
||||
// ----------------
|
||||
// Load resource fork
|
||||
|
||||
rezSearchStack.emplace_back();
|
||||
rezSearchStackIndex = int(rezSearchStack.size() - 1);
|
||||
GetCurRF().fileRefNum = slot;
|
||||
GetCurRF().resourceMap.clear();
|
||||
|
||||
// -------------------
|
||||
// Resource Header
|
||||
UInt32 dataSectionOff = f.Read<UInt32>() + resForkOff;
|
||||
UInt32 mapSectionOff = f.Read<UInt32>() + resForkOff;
|
||||
f.Skip(4); // UInt32 dataSectionLen
|
||||
f.Skip(4); // UInt32 mapSectionLen
|
||||
f.Skip(112 + 128); // system- (112) and app- (128) reserved data
|
||||
|
||||
ResourceAssert(f.Tell() == dataSectionOff, "FSpOpenResFile: Unexpected data offset");
|
||||
|
||||
f.Goto(mapSectionOff);
|
||||
|
||||
// map header
|
||||
f.Skip(16 + 4 + 2); // junk
|
||||
f.Skip(2); // UInt16 fileAttr
|
||||
UInt32 typeListOff = f.Read<UInt16>() + mapSectionOff;
|
||||
UInt32 resNameListOff = f.Read<UInt16>() + mapSectionOff;
|
||||
|
||||
// all resource types
|
||||
int nResTypes = 1 + f.Read<UInt16>();
|
||||
for (int i = 0; i < nResTypes; i++)
|
||||
{
|
||||
OSType resType = f.Read<OSType>();
|
||||
int resCount = f.Read<UInt16>() + 1;
|
||||
UInt32 resRefListOff = f.Read<UInt16>() + typeListOff;
|
||||
|
||||
// The guard will rewind the file cursor to the pos in the next iteration
|
||||
auto guard1 = f.GuardPos();
|
||||
|
||||
f.Goto(resRefListOff);
|
||||
|
||||
for (int j = 0; j < resCount; j++)
|
||||
{
|
||||
SInt16 resID = f.Read<UInt16>();
|
||||
UInt16 resNameRelativeOff = f.Read<UInt16>();
|
||||
UInt32 resPackedAttr = f.Read<UInt32>();
|
||||
f.Skip(4); // junk
|
||||
|
||||
// The guard will rewind the file cursor to the pos in the next iteration
|
||||
auto guard2 = f.GuardPos();
|
||||
|
||||
// unpack attributes
|
||||
Byte resFlags = (resPackedAttr & 0xFF000000) >> 24;
|
||||
UInt32 resDataOff = (resPackedAttr & 0x00FFFFFF) + dataSectionOff;
|
||||
|
||||
// Check compressed flag
|
||||
ResourceAssert(!(resFlags & 1), "FSpOpenResFile: Compressed resources not supported yet");
|
||||
|
||||
// Fetch name
|
||||
std::string name;
|
||||
if (resNameRelativeOff != 0xFFFF)
|
||||
{
|
||||
f.Goto(resNameListOff + resNameRelativeOff);
|
||||
name = f.ReadPascalString();
|
||||
}
|
||||
|
||||
// Fetch size
|
||||
f.Goto(resDataOff);
|
||||
SInt32 size = f.Read<SInt32>();
|
||||
|
||||
ResourceMetadata resMetadata;
|
||||
resMetadata.forkRefNum = slot;
|
||||
resMetadata.type = resType;
|
||||
resMetadata.id = resID;
|
||||
resMetadata.flags = resFlags;
|
||||
resMetadata.dataOffset = resDataOff + 4;
|
||||
resMetadata.size = size;
|
||||
resMetadata.name = name;
|
||||
GetCurRF().resourceMap[resType][resID] = resMetadata;
|
||||
}
|
||||
}
|
||||
|
||||
//PrintStack(__func__);
|
||||
|
||||
return slot;
|
||||
}
|
||||
|
||||
void UseResFile(short refNum)
|
||||
{
|
||||
// See MoreMacintoshToolbox:1-69
|
||||
|
||||
lastResError = unimpErr;
|
||||
|
||||
ResourceAssert(refNum != 0, "UseResFile: Using the System file's resource fork is not implemented.");
|
||||
ResourceAssert(refNum >= 0, "UseResFile: Illegal refNum");
|
||||
ResourceAssert(IsStreamOpen(refNum), "UseResFile: Resource stream not open");
|
||||
|
||||
for (size_t i = 0; i < rezSearchStack.size(); i++)
|
||||
{
|
||||
if (rezSearchStack[i].fileRefNum == refNum)
|
||||
{
|
||||
lastResError = noErr;
|
||||
rezSearchStackIndex = i;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
std::cerr << "no RF open with refNum " << rfNumErr << "\n";
|
||||
lastResError = rfNumErr;
|
||||
}
|
||||
|
||||
short CurResFile()
|
||||
{
|
||||
return GetCurRF().fileRefNum;
|
||||
}
|
||||
|
||||
void CloseResFile(short refNum)
|
||||
{
|
||||
ResourceAssert(refNum != 0, "CloseResFile: Closing the System file's resource fork is not implemented.");
|
||||
ResourceAssert(refNum >= 0, "CloseResFile: Illegal refNum");
|
||||
ResourceAssert(IsStreamOpen(refNum), "CloseResFile: Resource stream not open");
|
||||
|
||||
//UpdateResFile(refNum); // MMT:1-110
|
||||
Pomme::Files::CloseStream(refNum);
|
||||
|
||||
auto it = rezSearchStack.begin();
|
||||
while (it != rezSearchStack.end())
|
||||
{
|
||||
if (it->fileRefNum == refNum)
|
||||
it = rezSearchStack.erase(it);
|
||||
else
|
||||
it++;
|
||||
}
|
||||
|
||||
rezSearchStackIndex = std::min(rezSearchStackIndex, (int) rezSearchStack.size() - 1);
|
||||
}
|
||||
|
||||
short Count1Resources(ResType theType)
|
||||
{
|
||||
lastResError = noErr;
|
||||
|
||||
try
|
||||
{
|
||||
return (short) GetCurRF().resourceMap.at(theType).size();
|
||||
}
|
||||
catch (std::out_of_range&)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
Handle GetResource(ResType theType, short theID)
|
||||
{
|
||||
lastResError = noErr;
|
||||
|
||||
for (int i = rezSearchStackIndex; i >= 0; i--)
|
||||
{
|
||||
const auto& fork = rezSearchStack[i];
|
||||
|
||||
if (fork.resourceMap.end() == fork.resourceMap.find(theType))
|
||||
continue;
|
||||
|
||||
auto& resourcesOfType = fork.resourceMap.at(theType);
|
||||
if (resourcesOfType.end() == resourcesOfType.find(theID))
|
||||
continue;
|
||||
|
||||
const auto& meta = fork.resourceMap.at(theType).at(theID);
|
||||
auto& forkStream = Pomme::Files::GetStream(rezSearchStack[i].fileRefNum);
|
||||
Handle handle = NewHandle(meta.size);
|
||||
forkStream.seekg(meta.dataOffset, std::ios::beg);
|
||||
forkStream.read(*handle, meta.size);
|
||||
return handle;
|
||||
}
|
||||
|
||||
lastResError = resNotFound;
|
||||
return nil;
|
||||
}
|
||||
|
||||
void ReleaseResource(Handle theResource)
|
||||
{
|
||||
DisposeHandle(theResource);
|
||||
}
|
||||
|
||||
void RemoveResource(Handle theResource)
|
||||
{
|
||||
DisposeHandle(theResource);
|
||||
TODO();
|
||||
}
|
||||
|
||||
void AddResource(Handle theData, ResType theType, short theID, const char* name)
|
||||
{
|
||||
TODO();
|
||||
}
|
||||
|
||||
void WriteResource(Handle theResource)
|
||||
{
|
||||
TODO();
|
||||
}
|
||||
|
||||
void DetachResource(Handle theResource)
|
||||
{
|
||||
lastResError = noErr;
|
||||
ONCE(TODOMINOR());
|
||||
}
|
||||
|
||||
long GetResourceSizeOnDisk(Handle theResource)
|
||||
{
|
||||
TODO();
|
||||
return -1;
|
||||
}
|
||||
|
||||
long SizeResource(Handle theResource)
|
||||
{
|
||||
return GetResourceSizeOnDisk(theResource);
|
||||
}
|
59
src/Files/Volume.h
Normal file
59
src/Files/Volume.h
Normal file
@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace Pomme::Files
|
||||
{
|
||||
enum ForkType
|
||||
{
|
||||
DataFork,
|
||||
ResourceFork
|
||||
};
|
||||
|
||||
struct ForkHandle
|
||||
{
|
||||
public:
|
||||
ForkType forkType;
|
||||
char permission;
|
||||
|
||||
protected:
|
||||
ForkHandle(ForkType _forkType, char _permission)
|
||||
: forkType(_forkType)
|
||||
, permission(_permission)
|
||||
{}
|
||||
|
||||
public:
|
||||
virtual std::iostream& GetStream() = 0;
|
||||
|
||||
virtual ~ForkHandle() = default;
|
||||
};
|
||||
|
||||
/**
|
||||
* Base class for volumes through which the Mac app is given access to files.
|
||||
*/
|
||||
class Volume
|
||||
{
|
||||
protected:
|
||||
short volumeID;
|
||||
|
||||
public:
|
||||
Volume(short vid)
|
||||
: volumeID(vid)
|
||||
{}
|
||||
|
||||
virtual ~Volume() = default;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Toolbox API Implementation
|
||||
|
||||
virtual OSErr FSMakeFSSpec(long dirID, const std::string& suffix, FSSpec* spec) = 0;
|
||||
|
||||
virtual OSErr OpenFork(const FSSpec* spec, ForkType forkType, char permission, std::unique_ptr<ForkHandle>& handle) = 0;
|
||||
|
||||
virtual OSErr FSpCreate(const FSSpec* spec, OSType creator, OSType fileType, ScriptCode scriptTag) = 0;
|
||||
|
||||
virtual OSErr FSpDelete(const FSSpec* spec) = 0;
|
||||
|
||||
virtual OSErr DirCreate(long parentDirID, const std::string& directoryName, long* createdDirID) = 0;
|
||||
};
|
||||
}
|
73
src/Graphics/ARGBPixmap.cpp
Normal file
73
src/Graphics/ARGBPixmap.cpp
Normal file
@ -0,0 +1,73 @@
|
||||
#include "PommeGraphics.h"
|
||||
#include <iostream>
|
||||
|
||||
using namespace Pomme::Graphics;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Pixmap
|
||||
|
||||
ARGBPixmap::ARGBPixmap()
|
||||
: width(0)
|
||||
, height(0)
|
||||
, data(0)
|
||||
{
|
||||
}
|
||||
|
||||
ARGBPixmap::ARGBPixmap(int w, int h)
|
||||
: width(w)
|
||||
, height(h)
|
||||
, data(w * h * 4, 0xAA)
|
||||
{
|
||||
Fill(255, 0, 255);
|
||||
}
|
||||
|
||||
ARGBPixmap::ARGBPixmap(ARGBPixmap&& other) noexcept
|
||||
: width(other.width)
|
||||
, height(other.height)
|
||||
, data(std::move(other.data))
|
||||
{
|
||||
other.width = -1;
|
||||
other.height = -1;
|
||||
}
|
||||
|
||||
ARGBPixmap& ARGBPixmap::operator=(ARGBPixmap&& other) noexcept
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
width = other.width;
|
||||
height = other.height;
|
||||
data = std::move(other.data);
|
||||
other.width = -1;
|
||||
other.height = -1;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void ARGBPixmap::Fill(UInt8 red, UInt8 green, UInt8 blue, UInt8 alpha)
|
||||
{
|
||||
for (int i = 0; i < width * height * 4; i += 4)
|
||||
{
|
||||
data[i + 0] = alpha;
|
||||
data[i + 1] = red;
|
||||
data[i + 2] = green;
|
||||
data[i + 3] = blue;
|
||||
}
|
||||
}
|
||||
|
||||
void ARGBPixmap::Plot(int x, int y, UInt32 color)
|
||||
{
|
||||
if (x < 0 || y < 0 || x >= width || y >= height)
|
||||
{
|
||||
throw std::out_of_range("ARGBPixmap::Plot: out of bounds");
|
||||
}
|
||||
else
|
||||
{
|
||||
*(UInt32*) &data[4 * (y * width + x)] = color;
|
||||
}
|
||||
}
|
||||
|
||||
void ARGBPixmap::WriteTGA(const char* path) const
|
||||
{
|
||||
DumpTGA(path, width, height, (const char*) data.data());
|
||||
}
|
||||
|
27
src/Graphics/Color.cpp
Normal file
27
src/Graphics/Color.cpp
Normal file
@ -0,0 +1,27 @@
|
||||
#include "PommeGraphics.h"
|
||||
|
||||
using namespace Pomme::Graphics;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Color
|
||||
|
||||
Color::Color(uint8_t r_, uint8_t g_, uint8_t b_)
|
||||
: a(0xFF)
|
||||
, r(r_)
|
||||
, g(g_)
|
||||
, b(b_)
|
||||
{}
|
||||
|
||||
Color::Color(uint8_t r_, uint8_t g_, uint8_t b_, uint8_t a_)
|
||||
: a(a_)
|
||||
, r(r_)
|
||||
, g(g_)
|
||||
, b(b_)
|
||||
{}
|
||||
|
||||
Color::Color()
|
||||
: a(0xFF)
|
||||
, r(0xFF)
|
||||
, g(0x00)
|
||||
, b(0xFF)
|
||||
{}
|
641
src/Graphics/Graphics.cpp
Normal file
641
src/Graphics/Graphics.cpp
Normal file
@ -0,0 +1,641 @@
|
||||
#include "Pomme.h"
|
||||
#include "PommeGraphics.h"
|
||||
#include "SysFont.h"
|
||||
#include "Utilities/memstream.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <SDL.h>
|
||||
|
||||
using namespace Pomme;
|
||||
using namespace Pomme::Graphics;
|
||||
|
||||
static bool IntersectRects(const Rect* r1, Rect* r2)
|
||||
{
|
||||
r2->left = std::max(r1->left, r2->left);
|
||||
r2->right = std::min(r1->right, r2->right);
|
||||
r2->top = std::max(r1->top, r2->top);
|
||||
r2->bottom = std::min(r1->bottom, r2->bottom);
|
||||
return r2->left < r2->right && r2->top < r2->bottom;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------- -
|
||||
// Types
|
||||
|
||||
struct GrafPortImpl
|
||||
{
|
||||
GrafPort port;
|
||||
ARGBPixmap pixels;
|
||||
bool dirty;
|
||||
Rect dirtyRect;
|
||||
PixMap macpm;
|
||||
PixMap* macpmPtr;
|
||||
|
||||
GrafPortImpl(const Rect boundsRect)
|
||||
: port({boundsRect, this})
|
||||
, pixels(boundsRect.right - boundsRect.left, boundsRect.bottom - boundsRect.top)
|
||||
, dirty(false)
|
||||
{
|
||||
macpm = {};
|
||||
macpm.bounds = boundsRect;
|
||||
macpm.pixelSize = 32;
|
||||
macpm._impl = (Ptr) &pixels;
|
||||
macpmPtr = &macpm;
|
||||
}
|
||||
|
||||
void DamageRegion(const Rect& r)
|
||||
{
|
||||
if (!dirty)
|
||||
{
|
||||
dirtyRect = r;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Already dirty, expand existing dirty rect
|
||||
dirtyRect.top = std::min(dirtyRect.top, r.top);
|
||||
dirtyRect.left = std::min(dirtyRect.left, r.left);
|
||||
dirtyRect.bottom = std::max(dirtyRect.bottom, r.bottom);
|
||||
dirtyRect.right = std::max(dirtyRect.right, r.right);
|
||||
}
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
void DamageRegion(SInt16 x, SInt16 y, SInt16 w, SInt16 h)
|
||||
{
|
||||
Rect r = {y, x, static_cast<SInt16>(y + h), static_cast<SInt16>(x + w)};
|
||||
DamageRegion(r);
|
||||
}
|
||||
|
||||
~GrafPortImpl()
|
||||
{
|
||||
macpm._impl = nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------- -
|
||||
// Internal State
|
||||
|
||||
static std::unique_ptr<GrafPortImpl> screenPort = nullptr;
|
||||
static GrafPortImpl* curPort = nullptr;
|
||||
static UInt32 penFG = 0xFF'FF'00'FF;
|
||||
static UInt32 penBG = 0xFF'00'00'FF;
|
||||
static int penX = 0;
|
||||
static int penY = 0;
|
||||
|
||||
// ---------------------------------------------------------------------------- -
|
||||
// Globals
|
||||
|
||||
extern "C" {
|
||||
SDL_Window* gSDLWindow = nullptr;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------- -
|
||||
// Initialization
|
||||
|
||||
CGrafPtr Pomme::Graphics::GetScreenPort(void)
|
||||
{
|
||||
return &screenPort->port;
|
||||
}
|
||||
|
||||
void Pomme::Graphics::Init(const char* windowTitle, int windowWidth, int windowHeight)
|
||||
{
|
||||
if (0 != SDL_Init(SDL_INIT_VIDEO))
|
||||
{
|
||||
throw std::runtime_error("Couldn't initialize SDL video subsystem.");
|
||||
}
|
||||
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
||||
#ifndef _WIN32
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
|
||||
#endif
|
||||
|
||||
gSDLWindow = SDL_CreateWindow(
|
||||
windowTitle,
|
||||
SDL_WINDOWPOS_UNDEFINED,
|
||||
SDL_WINDOWPOS_UNDEFINED,
|
||||
windowWidth,
|
||||
windowHeight,
|
||||
SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_SHOWN);
|
||||
|
||||
if (!gSDLWindow)
|
||||
{
|
||||
throw std::runtime_error("Couldn't create SDL window.");
|
||||
}
|
||||
|
||||
Rect boundsRect = {0, 0, 480, 640};
|
||||
screenPort = std::make_unique<GrafPortImpl>(boundsRect);
|
||||
curPort = screenPort.get();
|
||||
}
|
||||
|
||||
void Pomme::Graphics::Shutdown()
|
||||
{
|
||||
SDL_DestroyWindow(gSDLWindow);
|
||||
gSDLWindow = nullptr;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------- -
|
||||
// Internal utils
|
||||
|
||||
static UInt32 GetEightColorPaletteValue(long color)
|
||||
{
|
||||
switch (color) {
|
||||
case whiteColor: return clut4[0];
|
||||
case yellowColor: return clut4[1];
|
||||
case redColor: return clut4[3];
|
||||
case magentaColor: return clut4[4];
|
||||
case blackColor: return clut4[15];
|
||||
case cyanColor: return clut4[7];
|
||||
case greenColor: return clut4[8]; // I'm assuming this is light green rather than dark
|
||||
case blueColor: return clut4[6];
|
||||
default: return 0xFF'FF'00'FF;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------- -
|
||||
// PICT resources
|
||||
|
||||
PicHandle GetPicture(short PICTresourceID)
|
||||
{
|
||||
Handle rawResource = GetResource('PICT', PICTresourceID);
|
||||
if (rawResource == nil)
|
||||
return nil;
|
||||
memstream substream(*rawResource, GetHandleSize(rawResource));
|
||||
ARGBPixmap pm = ReadPICT(substream, false);
|
||||
ReleaseResource(rawResource);
|
||||
|
||||
// Tack the data onto the end of the Picture struct,
|
||||
// so that DisposeHandle frees both the Picture and the data.
|
||||
PicHandle ph = (PicHandle) NewHandle(int(sizeof(Picture) + pm.data.size()));
|
||||
|
||||
Picture& pic = **ph;
|
||||
Ptr pixels = (Ptr) *ph + sizeof(Picture);
|
||||
|
||||
pic.picFrame = Rect{0, 0, (SInt16) pm.height, (SInt16) pm.width};
|
||||
pic.picSize = -1;
|
||||
pic.__pomme_pixelsARGB32 = pixels;
|
||||
|
||||
memcpy(pic.__pomme_pixelsARGB32, pm.data.data(), pm.data.size());
|
||||
|
||||
return ph;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------- -
|
||||
// Rect
|
||||
|
||||
void SetRect(Rect* r, short left, short top, short right, short bottom)
|
||||
{
|
||||
r->left = left;
|
||||
r->top = top;
|
||||
r->right = right;
|
||||
r->bottom = bottom;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------- -
|
||||
// GWorld
|
||||
|
||||
static inline GrafPortImpl& GetImpl(GWorldPtr offscreenGWorld)
|
||||
{
|
||||
return *(GrafPortImpl*) offscreenGWorld->_impl;
|
||||
}
|
||||
|
||||
static inline ARGBPixmap& GetImpl(PixMapPtr pixMap)
|
||||
{
|
||||
return *(ARGBPixmap*) pixMap->_impl;
|
||||
}
|
||||
|
||||
OSErr NewGWorld(GWorldPtr* offscreenGWorld, short pixelDepth, const Rect* boundsRect, void* junk1, void* junk2, long junk3)
|
||||
{
|
||||
GrafPortImpl* impl = new GrafPortImpl(*boundsRect);
|
||||
*offscreenGWorld = &impl->port;
|
||||
return noErr;
|
||||
}
|
||||
|
||||
void DisposeGWorld(GWorldPtr offscreenGWorld)
|
||||
{
|
||||
delete &GetImpl(offscreenGWorld);
|
||||
}
|
||||
|
||||
void GetGWorld(CGrafPtr* port, GDHandle* gdh)
|
||||
{
|
||||
*port = &curPort->port;
|
||||
*gdh = nil;
|
||||
}
|
||||
|
||||
void SetGWorld(CGrafPtr port, GDHandle gdh)
|
||||
{
|
||||
SetPort(port);
|
||||
}
|
||||
|
||||
PixMapHandle GetGWorldPixMap(GWorldPtr offscreenGWorld)
|
||||
{
|
||||
return &GetImpl(offscreenGWorld).macpmPtr;
|
||||
}
|
||||
|
||||
Ptr GetPixBaseAddr(PixMapHandle pm)
|
||||
{
|
||||
return (Ptr) GetImpl(*pm).data.data();
|
||||
}
|
||||
|
||||
Boolean LockPixels(PixMapHandle pm)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void UnlockPixels(PixMapHandle pm)
|
||||
{
|
||||
// no-op
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------- -
|
||||
// Port
|
||||
|
||||
void SetPort(GrafPtr port)
|
||||
{
|
||||
curPort = &GetImpl(port);
|
||||
}
|
||||
|
||||
void GetPort(GrafPtr* outPort)
|
||||
{
|
||||
*outPort = &curPort->port;
|
||||
}
|
||||
|
||||
Boolean IsPortDamaged(void)
|
||||
{
|
||||
return curPort->dirty;
|
||||
}
|
||||
|
||||
void GetPortDamageRegion(Rect* r)
|
||||
{
|
||||
*r = curPort->dirtyRect;
|
||||
}
|
||||
|
||||
void ClearPortDamage(void)
|
||||
{
|
||||
curPort->dirty = false;
|
||||
}
|
||||
|
||||
void DamagePortRegion(const Rect* r)
|
||||
{
|
||||
curPort->DamageRegion(*r);
|
||||
}
|
||||
|
||||
void DumpPortTGA(const char* outPath)
|
||||
{
|
||||
curPort->pixels.WriteTGA(outPath);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------- -
|
||||
// Pen state manipulation
|
||||
|
||||
void MoveTo(short h, short v)
|
||||
{
|
||||
penX = h;
|
||||
penY = v;
|
||||
}
|
||||
|
||||
void ForeColor(long color)
|
||||
{
|
||||
penFG = GetEightColorPaletteValue(color);
|
||||
}
|
||||
|
||||
void BackColor(long color)
|
||||
{
|
||||
penBG = GetEightColorPaletteValue(color);
|
||||
}
|
||||
|
||||
void GetForeColor(RGBColor* rgb)
|
||||
{
|
||||
rgb->red = (penFG >> 16 & 0xFF) << 8;
|
||||
rgb->green = (penFG >> 8 & 0xFF) << 8;
|
||||
rgb->blue = (penFG & 0xFF) << 8;
|
||||
}
|
||||
|
||||
void RGBBackColor(const RGBColor* color)
|
||||
{
|
||||
penBG
|
||||
= 0xFF'00'00'00
|
||||
| ((color->red >> 8) << 16)
|
||||
| ((color->green >> 8) << 8)
|
||||
| (color->blue >> 8)
|
||||
;
|
||||
}
|
||||
|
||||
void RGBForeColor(const RGBColor* color)
|
||||
{
|
||||
penFG
|
||||
= 0xFF'00'00'00
|
||||
| ((color->red >> 8) << 16)
|
||||
| ((color->green >> 8) << 8)
|
||||
| (color->blue >> 8)
|
||||
;
|
||||
}
|
||||
|
||||
void RGBBackColor2(const UInt32 color)
|
||||
{
|
||||
penBG = 0xFF000000 | (color & 0x00FFFFFF);
|
||||
}
|
||||
|
||||
void RGBForeColor2(const UInt32 color)
|
||||
{
|
||||
penFG = 0xFF000000 | (color & 0x00FFFFFF);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------- -
|
||||
// Paint
|
||||
|
||||
static void _FillRect(const int left, const int top, const int right, const int bottom, UInt32 fillColor)
|
||||
{
|
||||
if (!curPort)
|
||||
{
|
||||
throw std::runtime_error("_FillRect: no port set");
|
||||
}
|
||||
|
||||
Rect dstRect;
|
||||
dstRect.left = left;
|
||||
dstRect.top = top;
|
||||
dstRect.right = right;
|
||||
dstRect.bottom = bottom;
|
||||
Rect clippedDstRect = dstRect;
|
||||
if (!IntersectRects(&curPort->port.portRect, &clippedDstRect))
|
||||
{
|
||||
return;
|
||||
}
|
||||
curPort->DamageRegion(clippedDstRect);
|
||||
|
||||
fillColor = ByteswapScalar(fillColor); // convert to big-endian
|
||||
|
||||
UInt32* dst = curPort->pixels.GetPtr(clippedDstRect.left, clippedDstRect.top);
|
||||
|
||||
for (int y = clippedDstRect.top; y < clippedDstRect.bottom; y++)
|
||||
{
|
||||
for (int x = 0; x < clippedDstRect.right - clippedDstRect.left; x++)
|
||||
{
|
||||
dst[x] = fillColor;
|
||||
}
|
||||
dst += curPort->pixels.width;
|
||||
}
|
||||
}
|
||||
|
||||
void PaintRect(const struct Rect* r)
|
||||
{
|
||||
_FillRect(r->left, r->top, r->right, r->bottom, penFG);
|
||||
}
|
||||
|
||||
void EraseRect(const struct Rect* r)
|
||||
{
|
||||
_FillRect(r->left, r->top, r->right, r->bottom, penBG);
|
||||
}
|
||||
|
||||
void LineTo(short x1, short y1)
|
||||
{
|
||||
auto color = ByteswapScalar(penFG);
|
||||
|
||||
auto offx = curPort->port.portRect.left;
|
||||
auto offy = curPort->port.portRect.top;
|
||||
|
||||
int x0 = penX;
|
||||
int y0 = penY;
|
||||
int dx = std::abs(x1 - x0);
|
||||
int sx = x0 < x1 ? 1 : -1;
|
||||
int dy = -std::abs(y1 - y0);
|
||||
int sy = y0 < y1 ? 1 : -1;
|
||||
int err = dx + dy;
|
||||
curPort->DamageRegion(penX, penY, dx, dy);
|
||||
while (1)
|
||||
{
|
||||
curPort->pixels.Plot(x0 - offx, y0 - offy, color);
|
||||
if (x0 == x1 && y0 == y1) break;
|
||||
int e2 = 2 * err;
|
||||
if (e2 >= dy)
|
||||
{
|
||||
err += dy;
|
||||
x0 += sx;
|
||||
}
|
||||
if (e2 <= dx)
|
||||
{
|
||||
err += dx;
|
||||
y0 += sy;
|
||||
}
|
||||
}
|
||||
penX = x0;
|
||||
penY = y0;
|
||||
}
|
||||
|
||||
void FrameRect(const Rect* r)
|
||||
{
|
||||
auto color = ByteswapScalar(penFG);
|
||||
auto& pm = curPort->pixels;
|
||||
auto offx = curPort->port.portRect.left;
|
||||
auto offy = curPort->port.portRect.top;
|
||||
|
||||
for (int x = r->left; x < r->right; x++) pm.Plot(x - offx, r->top - offy, color);
|
||||
for (int x = r->left; x < r->right; x++) pm.Plot(x - offx, r->bottom - 1 - offy, color);
|
||||
for (int y = r->top; y < r->bottom; y++) pm.Plot(r->left - offx, y - offy, color);
|
||||
for (int y = r->top; y < r->bottom; y++) pm.Plot(r->right - 1 - offx, y - offy, color);
|
||||
|
||||
curPort->DamageRegion(*r);
|
||||
}
|
||||
|
||||
void Pomme::Graphics::DrawARGBPixmap(int left, int top, ARGBPixmap& pixmap)
|
||||
{
|
||||
if (!curPort)
|
||||
{
|
||||
throw std::runtime_error("DrawARGBPixmap: no port set");
|
||||
}
|
||||
|
||||
Rect dstRect;
|
||||
dstRect.left = left;
|
||||
dstRect.top = top;
|
||||
dstRect.right = left + pixmap.width;
|
||||
dstRect.bottom = top + pixmap.height;
|
||||
Rect clippedDstRect = dstRect;
|
||||
if (!IntersectRects(&curPort->port.portRect, &clippedDstRect))
|
||||
{
|
||||
return; // wholly outside bounds
|
||||
}
|
||||
curPort->DamageRegion(clippedDstRect);
|
||||
|
||||
UInt32* src = pixmap.GetPtr(clippedDstRect.left - dstRect.left, clippedDstRect.top - dstRect.top);
|
||||
UInt32* dst = curPort->pixels.GetPtr(clippedDstRect.left, clippedDstRect.top);
|
||||
|
||||
for (int y = clippedDstRect.top; y < clippedDstRect.bottom; y++)
|
||||
{
|
||||
memcpy(dst, src, Width(clippedDstRect) * sizeof(UInt32));
|
||||
dst += curPort->pixels.width;
|
||||
src += pixmap.width;
|
||||
}
|
||||
}
|
||||
|
||||
void DrawPicture(PicHandle myPicture, const Rect* dstRect)
|
||||
{
|
||||
auto& pic = **myPicture;
|
||||
|
||||
UInt32* srcPixels = (UInt32*) pic.__pomme_pixelsARGB32;
|
||||
|
||||
int dstWidth = Width(*dstRect);
|
||||
int dstHeight = Height(*dstRect);
|
||||
int srcWidth = Width(pic.picFrame);
|
||||
int srcHeight = Height(pic.picFrame);
|
||||
|
||||
if (srcWidth != dstWidth || srcHeight != dstHeight)
|
||||
TODOFATAL2("we only support dstRect with the same width/height as the source picture");
|
||||
|
||||
for (int y = 0; y < dstHeight; y++)
|
||||
{
|
||||
memcpy(
|
||||
curPort->pixels.GetPtr(dstRect->left, dstRect->top + y),
|
||||
srcPixels + y * srcWidth,
|
||||
4 * dstWidth);
|
||||
}
|
||||
|
||||
curPort->DamageRegion(*dstRect);
|
||||
}
|
||||
|
||||
void CopyBits(
|
||||
const PixMap* srcBits,
|
||||
PixMap* dstBits,
|
||||
const Rect* srcRect,
|
||||
const Rect* dstRect,
|
||||
short mode,
|
||||
void* maskRgn
|
||||
)
|
||||
{
|
||||
auto& srcPM = GetImpl((PixMapPtr) srcBits);
|
||||
auto& dstPM = GetImpl(dstBits);
|
||||
|
||||
const auto& srcBounds = srcBits->bounds;
|
||||
const auto& dstBounds = dstBits->bounds;
|
||||
|
||||
int srcRectWidth = Width(*srcRect);
|
||||
int srcRectHeight = Height(*srcRect);
|
||||
int dstRectWidth = Width(*dstRect);
|
||||
int dstRectHeight = Height(*dstRect);
|
||||
|
||||
if (srcRectWidth != dstRectWidth || srcRectHeight != dstRectHeight)
|
||||
TODOFATAL2("can only copy between rects of same dimensions");
|
||||
|
||||
for (int y = 0; y < srcRectHeight; y++)
|
||||
{
|
||||
memcpy(
|
||||
dstPM.GetPtr(dstRect->left - dstBounds.left, dstRect->top - dstBounds.top + y),
|
||||
srcPM.GetPtr(srcRect->left - srcBounds.left, srcRect->top - srcBounds.top + y),
|
||||
4 * srcRectWidth
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------- -
|
||||
// Text rendering
|
||||
|
||||
short TextWidthC(const char* cstr)
|
||||
{
|
||||
if (!cstr) return 0;
|
||||
|
||||
int totalWidth = -SysFont::charSpacing;
|
||||
for (; *cstr; cstr++)
|
||||
{
|
||||
totalWidth += SysFont::charSpacing;
|
||||
totalWidth += SysFont::GetGlyph(*cstr).width;
|
||||
}
|
||||
return totalWidth;
|
||||
}
|
||||
|
||||
void DrawStringC(const char* cstr)
|
||||
{
|
||||
if (!cstr) return;
|
||||
|
||||
_FillRect(
|
||||
penX,
|
||||
penY - SysFont::ascend,
|
||||
penX + TextWidthC(cstr),
|
||||
penY + SysFont::descend,
|
||||
penBG
|
||||
);
|
||||
|
||||
penX -= SysFont::charSpacing;
|
||||
for (; *cstr; cstr++)
|
||||
{
|
||||
penX += SysFont::charSpacing;
|
||||
DrawChar(*cstr);
|
||||
}
|
||||
}
|
||||
|
||||
void DrawChar(char c)
|
||||
{
|
||||
UInt32 fg = ByteswapScalar(penFG);
|
||||
|
||||
auto& glyph = SysFont::GetGlyph(c);
|
||||
|
||||
// Theoretical coordinates of top-left corner of glyph (may be outside port bounds!)
|
||||
Rect dstRect;
|
||||
dstRect.left = penX - SysFont::leftMargin;
|
||||
dstRect.top = penY - SysFont::ascend;
|
||||
dstRect.right = dstRect.left + SysFont::widthBits;
|
||||
dstRect.bottom = dstRect.top + SysFont::rows;
|
||||
|
||||
Rect clippedDstRect = dstRect;
|
||||
if (!IntersectRects(&curPort->port.portRect, &clippedDstRect))
|
||||
{
|
||||
return; // wholly outside bounds
|
||||
}
|
||||
curPort->DamageRegion(clippedDstRect);
|
||||
|
||||
// Glyph boundaries
|
||||
int minCol = clippedDstRect.left - dstRect.left;
|
||||
int minRow = clippedDstRect.top - dstRect.top;
|
||||
|
||||
auto* dst2 = curPort->pixels.GetPtr(clippedDstRect.left, clippedDstRect.top);
|
||||
|
||||
for (int glyphY = minRow; glyphY < minRow + Height(clippedDstRect); glyphY++)
|
||||
{
|
||||
auto rowBits = glyph.bits[glyphY];
|
||||
|
||||
rowBits >>= minCol;
|
||||
|
||||
auto* dstRow = dst2;
|
||||
|
||||
for (int glyphX = minCol; glyphX < minCol + Width(clippedDstRect); glyphX++)
|
||||
{
|
||||
if (rowBits & 1)
|
||||
{
|
||||
*dstRow = fg;
|
||||
}
|
||||
rowBits >>= 1;
|
||||
dstRow++;
|
||||
}
|
||||
|
||||
dst2 += curPort->pixels.width;
|
||||
}
|
||||
|
||||
penX += glyph.width;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Icons
|
||||
|
||||
void Pomme::Graphics::SetWindowIconFromIcl8Resource(short icl8ID)
|
||||
{
|
||||
Handle macIcon = GetResource('icl8', icl8ID);
|
||||
if (1024 != GetHandleSize(macIcon))
|
||||
{
|
||||
throw std::invalid_argument("icl8 resource has incorrect size");
|
||||
}
|
||||
|
||||
const int width = 32;
|
||||
const int height = 32;
|
||||
|
||||
SDL_Surface* icon = SDL_CreateRGBSurface(0, width, height, 32, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000);
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
uint32_t* out = (uint32_t*) ((char*) icon->pixels + icon->pitch * y);
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
unsigned char pixel = (*macIcon)[y * width + x];
|
||||
*out++ = pixel == 0 ? 0 : Pomme::Graphics::clut8[pixel];
|
||||
}
|
||||
}
|
||||
SDL_SetWindowIcon(gSDLWindow, icon);
|
||||
SDL_FreeSurface(icon);
|
||||
DisposeHandle(macIcon);
|
||||
}
|
466
src/Graphics/PICT.cpp
Normal file
466
src/Graphics/PICT.cpp
Normal file
@ -0,0 +1,466 @@
|
||||
#include "Pomme.h"
|
||||
#include "PommeGraphics.h"
|
||||
#include "Utilities/BigEndianIStream.h"
|
||||
|
||||
#include <list>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
using namespace Pomme;
|
||||
using namespace Pomme::Graphics;
|
||||
|
||||
#define LOG POMME_GENLOG(POMME_DEBUG_PICT, "PICT")
|
||||
#define LOG_NOPREFIX POMME_GENLOG_NOPREFIX(POMME_DEBUG_PICT)
|
||||
|
||||
class PICTException : public std::runtime_error
|
||||
{
|
||||
public:
|
||||
PICTException(const char* m) : std::runtime_error(m)
|
||||
{}
|
||||
};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Rect helpers
|
||||
|
||||
Rect ReadRect(BigEndianIStream& f)
|
||||
{
|
||||
Rect r;
|
||||
r.top = f.Read<SInt16>();
|
||||
r.left = f.Read<SInt16>();
|
||||
r.bottom = f.Read<SInt16>();
|
||||
r.right = f.Read<SInt16>();
|
||||
return r;
|
||||
}
|
||||
|
||||
bool operator==(const Rect& r1, const Rect& r2)
|
||||
{
|
||||
return
|
||||
r1.top == r2.top &&
|
||||
r1.left == r2.left &&
|
||||
r1.bottom == r2.bottom &&
|
||||
r1.right == r2.right;
|
||||
}
|
||||
|
||||
bool operator!=(const Rect& r1, const Rect& r2)
|
||||
{
|
||||
return
|
||||
r1.top != r2.top ||
|
||||
r1.left != r2.left ||
|
||||
r1.bottom != r2.bottom ||
|
||||
r1.right != r2.right;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& s, const Rect& r)
|
||||
{
|
||||
return s << "T" << r.top << "L" << r.left << "B" << r.bottom << "R" << r.right;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Dump Targa
|
||||
|
||||
void Pomme::Graphics::DumpTGA(const char* path, short width, short height, const char* argbData)
|
||||
{
|
||||
std::ofstream tga(path);
|
||||
short tgaHdr[] = {0, 2, 0, 0, 0, 0, (short) width, (short) height, 0x2020};
|
||||
tga.write((const char*) tgaHdr, sizeof(tgaHdr));
|
||||
for (int i = 0; i < 4 * width * height; i += 4)
|
||||
{
|
||||
tga.put(argbData[i + 3]); //b
|
||||
tga.put(argbData[i + 2]); //g
|
||||
tga.put(argbData[i + 1]); //r
|
||||
tga.put(argbData[i + 0]); //a
|
||||
}
|
||||
tga.close();
|
||||
std::cout << "wrote " << path << "\n";
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// PackBits
|
||||
|
||||
template<typename T>
|
||||
static std::vector<T> UnpackBits(BigEndianIStream& f, UInt16 rowbytes, int packedLength)
|
||||
{
|
||||
//LOG << "UnpackBits rowbytes=" << rowbytes << " packedlength=" << packedLength << "\n";
|
||||
|
||||
std::vector<T> unpacked;
|
||||
|
||||
if (rowbytes < 8)
|
||||
{
|
||||
// Bits aren't compressed
|
||||
LOG << "Bits aren't compressed\n";
|
||||
for (int j = 0; j < packedLength; j += sizeof(T))
|
||||
{
|
||||
unpacked.push_back(f.Read<T>());
|
||||
}
|
||||
return unpacked;
|
||||
}
|
||||
|
||||
for (int j = 0; j < packedLength;)
|
||||
{
|
||||
Byte FlagCounter = f.Read<Byte>();
|
||||
|
||||
if (FlagCounter == 0x80)
|
||||
{
|
||||
// special case: repeat value of 0. Apple says ignore.
|
||||
j++;
|
||||
}
|
||||
else if (FlagCounter & 0x80)
|
||||
{
|
||||
// Packed data.
|
||||
int len = ((FlagCounter ^ 0xFF) & 0xFF) + 2;
|
||||
auto item = f.Read<T>();
|
||||
for (int k = 0; k < len; k++)
|
||||
{
|
||||
unpacked.push_back(item);
|
||||
}
|
||||
j += 1 + sizeof(T);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Unpacked data
|
||||
int len = (FlagCounter & 0xFF) + 1;
|
||||
for (int k = 0; k < len; k++)
|
||||
{
|
||||
unpacked.push_back(f.Read<T>());
|
||||
}
|
||||
j += 1 + len * sizeof(T);
|
||||
}
|
||||
}
|
||||
|
||||
return unpacked;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Unpack PICT pixmap formats
|
||||
|
||||
template<typename T>
|
||||
static std::vector<T> UnpackAllRows(BigEndianIStream& f, int w, int h, UInt16 rowbytes, std::size_t expectedItemCount)
|
||||
{
|
||||
LOG << "UnpackBits<" << typeid(T).name() << ">";
|
||||
|
||||
std::vector<T> data;
|
||||
data.reserve(expectedItemCount);
|
||||
for (int y = 0; y < h; y++)
|
||||
{
|
||||
int packedRowBytes = rowbytes > 250 ? f.Read<UInt16>() : f.Read<UInt8>();
|
||||
std::vector<T> rowPixels = UnpackBits<T>(f, rowbytes, packedRowBytes);
|
||||
data.insert(data.end(), rowPixels.begin(), rowPixels.end());
|
||||
}
|
||||
|
||||
if (expectedItemCount != data.size())
|
||||
{
|
||||
throw PICTException("UnpackAllRows: unexpected item count");
|
||||
}
|
||||
|
||||
LOG_NOPREFIX << "\n";
|
||||
return data;
|
||||
}
|
||||
|
||||
// Unpack pixel type 0 (8-bit indexed)
|
||||
static ARGBPixmap Unpack0(BigEndianIStream& f, int w, int h, const std::vector<Color>& palette)
|
||||
{
|
||||
auto unpacked = UnpackAllRows<UInt8>(f, w, h, w, w * h);
|
||||
ARGBPixmap dst(w, h);
|
||||
dst.data.clear();
|
||||
LOG << "indexed to RGBA";
|
||||
for (uint8_t px : unpacked)
|
||||
{
|
||||
if (px >= palette.size())
|
||||
{
|
||||
throw PICTException("Unpack0: illegal color index in pixmap");
|
||||
}
|
||||
Color c = palette[px];
|
||||
dst.data.push_back(c.a);
|
||||
dst.data.push_back(c.r);
|
||||
dst.data.push_back(c.g);
|
||||
dst.data.push_back(c.b);
|
||||
}
|
||||
LOG_NOPREFIX << "\n";
|
||||
return dst;
|
||||
}
|
||||
|
||||
// Unpack pixel type 4 (16 bits, chunky)
|
||||
static ARGBPixmap Unpack3(BigEndianIStream& f, int w, int h, UInt16 rowbytes)
|
||||
{
|
||||
auto unpacked = UnpackAllRows<UInt16>(f, w, h, rowbytes, w * h);
|
||||
ARGBPixmap dst(w, h);
|
||||
dst.data.clear();
|
||||
dst.data.reserve(unpacked.size() * 4);
|
||||
LOG << "Chunky16 to RGBA";
|
||||
for (UInt16 px : unpacked)
|
||||
{
|
||||
dst.data.push_back(0xFF); // a
|
||||
dst.data.push_back(((px >> 10) & 0x1F) << 3); // r
|
||||
dst.data.push_back(((px >> 5) & 0x1F) << 3); // g
|
||||
dst.data.push_back(((px >> 0) & 0x1F) << 3); // b
|
||||
}
|
||||
LOG_NOPREFIX << "\n";
|
||||
return dst;
|
||||
}
|
||||
|
||||
// Unpack pixel type 4 (24 or 32 bits, planar)
|
||||
static ARGBPixmap Unpack4(BigEndianIStream& f, int w, int h, UInt16 rowbytes, int numPlanes)
|
||||
{
|
||||
auto unpacked = UnpackAllRows<Byte>(f, w, h, rowbytes, numPlanes * w * h);
|
||||
ARGBPixmap dst(w, h);
|
||||
dst.data.clear();
|
||||
LOG << "Planar" << numPlanes*8 << " to RGBA";
|
||||
for (int y = 0; y < h; y++)
|
||||
{
|
||||
if (numPlanes == 3)
|
||||
{
|
||||
for (int x = 0; x < w; x++)
|
||||
{
|
||||
dst.data.push_back(0xFF);
|
||||
dst.data.push_back(unpacked[y*w*3 + x + w*0]); // red
|
||||
dst.data.push_back(unpacked[y*w*3 + x + w*1]); // grn
|
||||
dst.data.push_back(unpacked[y*w*3 + x + w*2]); // blu
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int x = 0; x < w; x++)
|
||||
{
|
||||
dst.data.push_back(unpacked[y*w*3 + x + w*0]); // alpha
|
||||
dst.data.push_back(unpacked[y*w*3 + x + w*1]); // red
|
||||
dst.data.push_back(unpacked[y*w*3 + x + w*2]); // grn
|
||||
dst.data.push_back(unpacked[y*w*3 + x + w*3]); // blu
|
||||
}
|
||||
}
|
||||
}
|
||||
LOG_NOPREFIX << "\n";
|
||||
return dst;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// PICT header
|
||||
|
||||
static ARGBPixmap ReadPICTBits(BigEndianIStream& f, int opcode, const Rect& canvasRect)
|
||||
{
|
||||
bool directBitsOpcode = opcode == 0x009A || opcode == 0x009B;
|
||||
|
||||
//printf("@@@ ReadPictBits %04x ", opcode); LOG << "canvasRect: " << canvasRect << "\n";
|
||||
|
||||
if (directBitsOpcode) f.Skip(4); //skip junk
|
||||
int rowbytes = f.Read<SInt16>();
|
||||
//LOG << "**** rowbytes = " << rowbytes << "\n";
|
||||
|
||||
Rect frameRect = ReadRect(f);
|
||||
LOG << "frameRect " << frameRect << "\n";
|
||||
if (frameRect != canvasRect)
|
||||
throw PICTException("frame dims != canvas dims 1");
|
||||
|
||||
int packType = -1;
|
||||
int pixelSize = -1;
|
||||
int componentCount = -1;
|
||||
|
||||
SInt16 pixmapVersion = f.Read<SInt16>();
|
||||
packType = f.Read<SInt16>();
|
||||
SInt32 packSize = f.Read<SInt32>();
|
||||
Fixed hResolution = f.Read<Fixed >();
|
||||
Fixed vResolution = f.Read<Fixed >();
|
||||
SInt16 pixelType = f.Read<SInt16>();
|
||||
pixelSize = f.Read<UInt16>();
|
||||
componentCount = f.Read<UInt16>();
|
||||
SInt16 componentSize = f.Read<SInt16>();
|
||||
SInt32 planeBytes = f.Read<SInt32>();
|
||||
SInt32 table = f.Read<SInt32>();
|
||||
if (directBitsOpcode || (rowbytes & 0x8000) != 0)
|
||||
{
|
||||
f.Skip(4);
|
||||
|
||||
LOG << "----PICT PIXMAP----"
|
||||
<< "\n\tpixmap version " << pixmapVersion
|
||||
<< "\n\tpack type " << packType
|
||||
<< "\n\tpack size " << packSize
|
||||
<< "\n\tresolution " << (hResolution>>16) << "x" << (vResolution>>16)
|
||||
<< "\n\tpixel type " << pixelType
|
||||
<< "\n\tbpp " << pixelSize
|
||||
<< "\n\tcomponent count " << componentCount
|
||||
<< "\n\tcomponent size " << componentSize
|
||||
<< "\n\tplane bytes " << planeBytes
|
||||
<< "\n\ttable " << table
|
||||
<< "\n";
|
||||
|
||||
if (pixelSize > 32) throw PICTException("pixmap invalid bpp");
|
||||
if (componentCount > 4) throw PICTException("pixmap invalid component count");
|
||||
if (componentSize <= 0) throw PICTException("pixmap invalid component size");
|
||||
}
|
||||
|
||||
auto palette = std::vector<Color>();
|
||||
if (!directBitsOpcode)
|
||||
{
|
||||
f.Skip(4);
|
||||
UInt16 flags = f.Read<UInt16>();
|
||||
int nColors = 1 + f.Read<UInt16>();
|
||||
|
||||
LOG << "Colormap: " << nColors << " colors\n";
|
||||
if (nColors <= 0 || nColors > 256) throw PICTException("unsupported palette size");
|
||||
|
||||
palette.resize(nColors);
|
||||
|
||||
for (int i = 0; i < nColors; i++)
|
||||
{
|
||||
int index;
|
||||
if (flags & 0x8000)
|
||||
{
|
||||
// ignore junk index (usually just set to 0)
|
||||
f.Skip(2);
|
||||
index = i;
|
||||
}
|
||||
else
|
||||
{
|
||||
index = f.Read<UInt16>();
|
||||
if (index >= nColors)
|
||||
throw PICTException("illegal color index in palette definition");
|
||||
}
|
||||
|
||||
UInt8 r = (f.Read<UInt16>() >> 8) & 0xFF;
|
||||
UInt8 g = (f.Read<UInt16>() >> 8) & 0xFF;
|
||||
UInt8 b = (f.Read<UInt16>() >> 8) & 0xFF;
|
||||
palette[index] = Color(r, g, b);
|
||||
}
|
||||
}
|
||||
|
||||
Rect srcRect = ReadRect(f);
|
||||
Rect dstRect = ReadRect(f);
|
||||
if (srcRect != dstRect) throw PICTException("unsupported src/dst rects");
|
||||
if (srcRect != canvasRect) throw PICTException("unsupported src/dst rects that aren't the same as the canvas rect");
|
||||
f.Skip(2);
|
||||
|
||||
if (opcode == 0x0091 || opcode == 0x0099 || opcode == 0x009b)
|
||||
{
|
||||
throw PICTException("unimplemented opcode");
|
||||
}
|
||||
|
||||
if (!directBitsOpcode && (rowbytes & 0x8000) == 0)
|
||||
{
|
||||
throw PICTException("negative rowbytes");
|
||||
}
|
||||
else
|
||||
{
|
||||
int cw = Width(canvasRect);
|
||||
int ch = Height(canvasRect);
|
||||
|
||||
switch (packType)
|
||||
{
|
||||
case 0: // 8-bit indexed color, packed bytewise
|
||||
return Unpack0(f, cw, ch, palette);
|
||||
|
||||
case 3: // 16-bit color, stored chunky, packed pixelwise
|
||||
return Unpack3(f, cw, ch, rowbytes);
|
||||
|
||||
case 4: // 24- or 32-bit color, stored planar, packed bytewise
|
||||
return Unpack4(f, cw, ch, rowbytes, componentCount);
|
||||
|
||||
default:
|
||||
throw PICTException("don't know how to unpack this pixel size");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ARGBPixmap Pomme::Graphics::ReadPICT(std::istream& theF, bool skip512)
|
||||
{
|
||||
BigEndianIStream f(theF);
|
||||
|
||||
LOG << "-----------------------------\n";
|
||||
auto startOff = f.Tell();
|
||||
|
||||
if (skip512)
|
||||
f.Skip(512); // junk
|
||||
|
||||
f.Skip(2); // Version 1 picture size. Meaningless for "modern" picts that can easily exceed 65,535 bytes.
|
||||
|
||||
Rect canvasRect = ReadRect(f);
|
||||
if (Width(canvasRect) < 0 || Height(canvasRect) < 0)
|
||||
{
|
||||
LOG << "canvas rect: " << canvasRect << "\n";
|
||||
throw PICTException("invalid width/height");
|
||||
}
|
||||
|
||||
LOG << std::dec << "Pict canvas: " << canvasRect << "\n";
|
||||
|
||||
if (0x0011 != f.Read<SInt16>())
|
||||
throw PICTException("didn't find version opcode in PICT header");
|
||||
if (0x02 != f.Read<Byte>()) throw PICTException("unrecognized PICT version");
|
||||
if (0xFF != f.Read<Byte>()) throw PICTException("bad PICT header");
|
||||
|
||||
ARGBPixmap pm(0, 0);
|
||||
bool readPixmap = false;
|
||||
|
||||
while (true)
|
||||
{
|
||||
int opcode;
|
||||
|
||||
// Align position to short
|
||||
f.Skip((f.Tell() - startOff) % 2);
|
||||
|
||||
opcode = f.Read<SInt16>();
|
||||
|
||||
//printf("~~~~~ OPCODE %04X ~~~~~\n", opcode);
|
||||
|
||||
// Skip reserved opcodes
|
||||
if (opcode >= 0x0100 && opcode <= 0x7FFF)
|
||||
{
|
||||
f.Skip((opcode >> 7) & 0xFF);
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (opcode)
|
||||
{
|
||||
case 0x0C00:
|
||||
f.Skip(12);
|
||||
break;
|
||||
|
||||
case 0x0000: // nop
|
||||
case 0x001E: // DefHilite
|
||||
case 0x0048: // frameSameRRect
|
||||
LOG << __func__ << ": skipping opcode " << opcode << "\n";
|
||||
break;
|
||||
|
||||
case 0x001F: // OpColor
|
||||
LOG << __func__ << ": skipping opcode " << opcode << "\n";
|
||||
f.Skip(6);
|
||||
break;
|
||||
|
||||
case 0x0001: // clip
|
||||
{
|
||||
auto length = f.Read<UInt16>();
|
||||
if (length != 0x0A) f.Skip(length - 2);
|
||||
Rect frameRect = ReadRect(f);
|
||||
//LOG << "CLIP:" << frameRect << "\n";
|
||||
if (frameRect.left < 0 || frameRect.top < 0) throw PICTException("illegal frame rect");
|
||||
if (frameRect != canvasRect)
|
||||
{
|
||||
std::cerr << "Warning: clip rect " << frameRect << " isn't the same as the canvas rect " << canvasRect << ", using clip rect\n";
|
||||
canvasRect = frameRect;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x0098: // PackBitsRect
|
||||
case 0x009A: // DirectBitsRect
|
||||
if (readPixmap)
|
||||
throw PICTException("already read one pixmap!");
|
||||
pm = ReadPICTBits(f, opcode, canvasRect);
|
||||
readPixmap = true;
|
||||
break;
|
||||
|
||||
case 0x00A1: // Long comment
|
||||
f.Skip(2);
|
||||
f.Skip(f.Read<UInt16>());
|
||||
break;
|
||||
|
||||
case 0x00FF: // done
|
||||
case 0xFFFF:
|
||||
return pm;
|
||||
|
||||
default:
|
||||
std::cerr << "unsupported opcode " << opcode << " at offset " << f.Tell() << "\n";
|
||||
throw PICTException("unsupported PICT opcode");
|
||||
}
|
||||
}
|
||||
|
||||
return pm;
|
||||
}
|
122
src/Graphics/SysFont.h
Normal file
122
src/Graphics/SysFont.h
Normal file
@ -0,0 +1,122 @@
|
||||
namespace Pomme::SysFont
|
||||
{
|
||||
static const int firstCodepoint = 32;
|
||||
static const int lastCodepoint = 126;
|
||||
static const int charSpacing = 2;
|
||||
static const int rows = 14;
|
||||
static const int leftMargin = 2;
|
||||
static const int ascend = 12;
|
||||
static const int descend = 2;
|
||||
static const int widthBits = 16;
|
||||
|
||||
static const struct Glyph
|
||||
{
|
||||
short width;
|
||||
unsigned short bits[14];
|
||||
} glyphs[95] = {
|
||||
{ 2, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } }, //space
|
||||
{ 3, { 0, 0, 0, 24, 24, 24, 24, 24, 24, 24, 0, 24, 0, 0 } }, //!
|
||||
{ 4, { 0, 0, 0, 40, 40, 40, 0, 0, 0, 0, 0, 0, 0, 0 } }, //"
|
||||
{ 8, { 0, 0, 288, 288,1016, 144, 144, 144, 508, 72, 72, 0, 0, 0 } }, //#
|
||||
{ 7, { 0, 0, 32, 504, 44, 44, 44, 248, 416, 416, 416, 252, 32, 0 } }, //$
|
||||
{ 9, { 0, 0, 0, 256, 280, 164, 164, 88, 832,1184,1184, 784, 16, 0 } }, //%
|
||||
{ 7, { 0, 0, 0, 120, 76, 12, 408, 204, 204, 204, 204, 120, 0, 0 } }, //&
|
||||
{ 2, { 0, 0, 0, 12, 12, 8, 4, 0, 0, 0, 0, 0, 0, 0 } }, //'
|
||||
{ 4, { 0, 0, 48, 24, 24, 12, 12, 12, 12, 12, 24, 24, 48, 0 } }, //(
|
||||
{ 4, { 0, 0, 12, 24, 24, 48, 48, 48, 48, 48, 24, 24, 12, 0 } }, //)
|
||||
{ 7, { 0, 0, 0, 0, 0, 216, 112, 508, 112, 216, 0, 0, 0, 0 } }, //*
|
||||
{ 6, { 0, 0, 0, 0, 0, 48, 48, 252, 252, 48, 48, 0, 0, 0 } }, //+
|
||||
{ 2, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 8, 4 } }, //,
|
||||
{ 4, { 0, 0, 0, 0, 0, 0, 0, 60, 60, 0, 0, 0, 0, 0 } }, //-
|
||||
{ 3, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 24, 0, 0 } }, //.
|
||||
{ 6, { 0, 0, 0, 192, 192, 96, 96, 48, 48, 24, 24, 12, 12, 0 } }, ///
|
||||
{ 6, { 0, 0, 0, 120, 204, 204, 236, 220, 204, 204, 204, 120, 0, 0 } }, //0
|
||||
{ 6, { 0, 0, 0, 56, 48, 48, 48, 48, 48, 48, 48, 252, 0, 0 } }, //1
|
||||
{ 6, { 0, 0, 0, 124, 192, 192, 192, 96, 48, 24, 12, 252, 0, 0 } }, //2
|
||||
{ 6, { 0, 0, 0, 124, 192, 192, 112, 192, 192, 192, 192, 124, 0, 0 } }, //3
|
||||
{ 6, { 0, 0, 0, 192, 224, 208, 200, 204, 204, 252, 192, 192, 0, 0 } }, //4
|
||||
{ 6, { 0, 0, 0, 252, 12, 12, 124, 192, 192, 192, 192, 124, 0, 0 } }, //5
|
||||
{ 6, { 0, 0, 0, 248, 12, 12, 124, 204, 204, 204, 204, 120, 0, 0 } }, //6
|
||||
{ 6, { 0, 0, 0, 252, 192, 224, 112, 56, 24, 24, 24, 24, 0, 0 } }, //7
|
||||
{ 6, { 0, 0, 0, 120, 204, 204, 204, 120, 204, 204, 204, 120, 0, 0 } }, //8
|
||||
{ 6, { 0, 0, 0, 120, 204, 204, 204, 248, 192, 192, 192, 120, 0, 0 } }, //9
|
||||
{ 3, { 0, 0, 0, 0, 0, 0, 24, 24, 0, 0, 24, 24, 0, 0 } }, //:
|
||||
{ 3, { 0, 0, 0, 0, 0, 0, 24, 24, 0, 0, 24, 24, 16, 8 } }, //;
|
||||
{ 4, { 0, 0, 0, 0, 0, 32, 48, 24, 12, 24, 48, 32, 0, 0 } }, //<
|
||||
{ 5, { 0, 0, 0, 0, 0, 0, 0, 124, 0, 124, 0, 0, 0, 0 } }, //=
|
||||
{ 4, { 0, 0, 0, 0, 0, 4, 12, 24, 48, 24, 12, 4, 0, 0 } }, //>
|
||||
{ 7, { 0, 0, 0, 240, 408, 408, 448, 224, 96, 96, 0, 96, 0, 0 } }, //?
|
||||
{ 7, { 0, 0, 0, 0, 240, 264, 356, 340, 244, 4, 56, 0, 0, 0 } }, //@
|
||||
{ 6, { 0, 0, 0, 120, 204, 204, 204, 252, 204, 204, 204, 204, 0, 0 } }, //A
|
||||
{ 6, { 0, 0, 0, 124, 204, 204, 204, 124, 204, 204, 204, 124, 0, 0 } }, //B
|
||||
{ 5, { 0, 0, 0, 120, 12, 12, 12, 12, 12, 12, 12, 120, 0, 0 } }, //C
|
||||
{ 6, { 0, 0, 0, 124, 204, 204, 204, 204, 204, 204, 204, 124, 0, 0 } }, //D
|
||||
{ 5, { 0, 0, 0, 124, 12, 12, 12, 60, 12, 12, 12, 124, 0, 0 } }, //E
|
||||
{ 5, { 0, 0, 0, 124, 12, 12, 12, 60, 12, 12, 12, 12, 0, 0 } }, //F
|
||||
{ 6, { 0, 0, 0, 248, 12, 12, 12, 204, 204, 204, 204, 184, 0, 0 } }, //G
|
||||
{ 6, { 0, 0, 0, 204, 204, 204, 204, 252, 204, 204, 204, 204, 0, 0 } }, //H
|
||||
{ 4, { 0, 0, 0, 60, 24, 24, 24, 24, 24, 24, 24, 60, 0, 0 } }, //I
|
||||
{ 6, { 0, 0, 0, 192, 192, 192, 192, 192, 192, 204, 204, 120, 0, 0 } }, //J
|
||||
{ 7, { 0, 0, 0, 396, 204, 108, 60, 28, 60, 108, 204, 396, 0, 0 } }, //K
|
||||
{ 5, { 0, 0, 0, 12, 12, 12, 12, 12, 12, 12, 12, 124, 0, 0 } }, //L
|
||||
{ 10, { 0, 0, 0,2044,3276,3276,3276,3276,3276,3276,3276,3276, 0, 0 } }, //M
|
||||
{ 6, { 0, 0, 0, 124, 204, 204, 204, 204, 204, 204, 204, 204, 0, 0 } }, //N
|
||||
{ 6, { 0, 0, 0, 120, 204, 204, 204, 204, 204, 204, 204, 120, 0, 0 } }, //O
|
||||
{ 6, { 0, 0, 0, 124, 204, 204, 204, 124, 12, 12, 12, 12, 0, 0 } }, //P
|
||||
{ 6, { 0, 0, 0, 120, 204, 204, 204, 204, 204, 204, 204, 120, 224, 0 } }, //Q
|
||||
{ 6, { 0, 0, 0, 124, 204, 204, 204, 124, 204, 204, 204, 204, 0, 0 } }, //R
|
||||
{ 6, { 0, 0, 0, 248, 12, 12, 28, 120, 224, 192, 192, 124, 0, 0 } }, //S
|
||||
{ 6, { 0, 0, 0, 252, 48, 48, 48, 48, 48, 48, 48, 48, 0, 0 } }, //T
|
||||
{ 6, { 0, 0, 0, 204, 204, 204, 204, 204, 204, 204, 204, 120, 0, 0 } }, //U
|
||||
{ 6, { 0, 0, 0, 204, 204, 204, 204, 204, 204, 204, 76, 60, 0, 0 } }, //V
|
||||
{ 10, { 0, 0, 0,3276,3276,3276,3276,3276,3276,3276,1228,1020, 0, 0 } }, //W
|
||||
{ 6, { 0, 0, 0, 204, 204, 204, 204, 120, 204, 204, 204, 204, 0, 0 } }, //X
|
||||
{ 6, { 0, 0, 0, 204, 204, 204, 204, 120, 48, 48, 48, 48, 0, 0 } }, //Y
|
||||
{ 6, { 0, 0, 0, 252, 192, 192, 96, 48, 24, 12, 12, 252, 0, 0 } }, //Z
|
||||
{ 3, { 0, 0, 28, 12, 12, 12, 12, 12, 12, 12, 12, 12, 28, 0 } }, //[
|
||||
{ 6, { 0, 0, 0, 12, 12, 24, 24, 48, 48, 96, 96, 192, 192, 0 } }, //backslash
|
||||
{ 3, { 0, 0, 28, 24, 24, 24, 24, 24, 24, 24, 24, 24, 28, 0 } }, //]
|
||||
{ 7, { 0, 0, 0, 32, 112, 216, 396, 0, 0, 0, 0, 0, 0, 0 } }, //^
|
||||
{ 5, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 124, 0 } }, //_
|
||||
{ 2, { 0, 0, 0, 12, 12, 4, 8, 0, 0, 0, 0, 0, 0, 0 } }, //`
|
||||
{ 6, { 0, 0, 0, 0, 0, 120, 192, 248, 204, 204, 204, 184, 0, 0 } }, //a
|
||||
{ 6, { 0, 0, 0, 12, 12, 124, 204, 204, 204, 204, 204, 116, 0, 0 } }, //b
|
||||
{ 5, { 0, 0, 0, 0, 0, 120, 12, 12, 12, 12, 12, 120, 0, 0 } }, //c
|
||||
{ 6, { 0, 0, 0, 192, 192, 248, 204, 204, 204, 204, 204, 184, 0, 0 } }, //d
|
||||
{ 6, { 0, 0, 0, 0, 0, 120, 204, 204, 252, 12, 12, 248, 0, 0 } }, //e
|
||||
{ 5, { 0, 0, 0, 112, 24, 60, 24, 24, 24, 24, 24, 24, 0, 0 } }, //f
|
||||
{ 6, { 0, 0, 0, 0, 0, 184, 204, 204, 204, 204, 204, 248, 192, 124 } }, //g
|
||||
{ 6, { 0, 0, 0, 12, 12, 108, 220, 204, 204, 204, 204, 204, 0, 0 } }, //h
|
||||
{ 2, { 0, 0, 0, 12, 0, 12, 12, 12, 12, 12, 12, 12, 0, 0 } }, //i
|
||||
{ 2, { 0, 0, 0, 12, 0, 12, 12, 12, 12, 12, 12, 12, 12, 6 } }, //j
|
||||
{ 6, { 0, 0, 0, 12, 12, 204, 108, 60, 28, 60, 108, 204, 0, 0 } }, //k
|
||||
{ 3, { 0, 0, 0, 12, 12, 12, 12, 12, 12, 12, 12, 24, 0, 0 } }, //l
|
||||
{ 10, { 0, 0, 0, 0, 0,1908,3276,3276,3276,3276,3276,3276, 0, 0 } }, //m
|
||||
{ 6, { 0, 0, 0, 0, 0, 116, 204, 204, 204, 204, 204, 204, 0, 0 } }, //n
|
||||
{ 6, { 0, 0, 0, 0, 0, 120, 204, 204, 204, 204, 204, 120, 0, 0 } }, //o
|
||||
{ 6, { 0, 0, 0, 0, 0, 116, 204, 204, 204, 204, 204, 124, 12, 12 } }, //p
|
||||
{ 6, { 0, 0, 0, 0, 0, 184, 204, 204, 204, 204, 204, 248, 192, 192 } }, //q
|
||||
{ 5, { 0, 0, 0, 0, 0, 108, 28, 12, 12, 12, 12, 12, 0, 0 } }, //r
|
||||
{ 6, { 0, 0, 0, 0, 0, 248, 12, 12, 120, 192, 192, 124, 0, 0 } }, //s
|
||||
{ 4, { 0, 0, 0, 24, 24, 60, 24, 24, 24, 24, 24, 48, 0, 0 } }, //t
|
||||
{ 6, { 0, 0, 0, 0, 0, 204, 204, 204, 204, 204, 204, 184, 0, 0 } }, //u
|
||||
{ 6, { 0, 0, 0, 0, 0, 204, 204, 204, 204, 204, 76, 60, 0, 0 } }, //v
|
||||
{ 10, { 0, 0, 0, 0, 0,3276,3276,3276,3276,3276,1228,1020, 0, 0 } }, //w
|
||||
{ 6, { 0, 0, 0, 0, 0, 204, 204, 204, 120, 204, 204, 204, 0, 0 } }, //x
|
||||
{ 6, { 0, 0, 0, 0, 0, 204, 204, 204, 204, 204, 204, 248, 192, 124 } }, //y
|
||||
{ 6, { 0, 0, 0, 0, 0, 252, 192, 96, 48, 24, 12, 252, 0, 0 } }, //z
|
||||
{ 4, { 0, 0, 48, 24, 24, 24, 24, 12, 24, 24, 24, 24, 48, 0 } }, //{
|
||||
{ 2, { 0, 0, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12 } }, //|
|
||||
{ 4, { 0, 0, 12, 24, 24, 24, 24, 48, 24, 24, 24, 24, 12, 0 } }, //}
|
||||
};
|
||||
|
||||
static const Glyph& placeholder = glyphs['?' - firstCodepoint];
|
||||
|
||||
static inline const Glyph& GetGlyph(char codePoint)
|
||||
{
|
||||
if (codePoint < firstCodepoint || codePoint > lastCodepoint)
|
||||
return placeholder;
|
||||
else
|
||||
return glyphs[codePoint - firstCodepoint];
|
||||
}
|
||||
}
|
43
src/Graphics/SystemPalettes.cpp
Normal file
43
src/Graphics/SystemPalettes.cpp
Normal file
@ -0,0 +1,43 @@
|
||||
#include "PommeGraphics.h"
|
||||
|
||||
const uint32_t Pomme::Graphics::clut8[256] =
|
||||
{
|
||||
0xFFFFFFFF, 0xFFFFFFCC, 0xFFFFFF99, 0xFFFFFF66, 0xFFFFFF33, 0xFFFFFF00, 0xFFFFCCFF, 0xFFFFCCCC,
|
||||
0xFFFFCC99, 0xFFFFCC66, 0xFFFFCC33, 0xFFFFCC00, 0xFFFF99FF, 0xFFFF99CC, 0xFFFF9999, 0xFFFF9966,
|
||||
0xFFFF9933, 0xFFFF9900, 0xFFFF66FF, 0xFFFF66CC, 0xFFFF6699, 0xFFFF6666, 0xFFFF6633, 0xFFFF6600,
|
||||
0xFFFF33FF, 0xFFFF33CC, 0xFFFF3399, 0xFFFF3366, 0xFFFF3333, 0xFFFF3300, 0xFFFF00FF, 0xFFFF00CC,
|
||||
0xFFFF0099, 0xFFFF0066, 0xFFFF0033, 0xFFFF0000, 0xFFCCFFFF, 0xFFCCFFCC, 0xFFCCFF99, 0xFFCCFF66,
|
||||
0xFFCCFF33, 0xFFCCFF00, 0xFFCCCCFF, 0xFFCCCCCC, 0xFFCCCC99, 0xFFCCCC66, 0xFFCCCC33, 0xFFCCCC00,
|
||||
0xFFCC99FF, 0xFFCC99CC, 0xFFCC9999, 0xFFCC9966, 0xFFCC9933, 0xFFCC9900, 0xFFCC66FF, 0xFFCC66CC,
|
||||
0xFFCC6699, 0xFFCC6666, 0xFFCC6633, 0xFFCC6600, 0xFFCC33FF, 0xFFCC33CC, 0xFFCC3399, 0xFFCC3366,
|
||||
0xFFCC3333, 0xFFCC3300, 0xFFCC00FF, 0xFFCC00CC, 0xFFCC0099, 0xFFCC0066, 0xFFCC0033, 0xFFCC0000,
|
||||
0xFF99FFFF, 0xFF99FFCC, 0xFF99FF99, 0xFF99FF66, 0xFF99FF33, 0xFF99FF00, 0xFF99CCFF, 0xFF99CCCC,
|
||||
0xFF99CC99, 0xFF99CC66, 0xFF99CC33, 0xFF99CC00, 0xFF9999FF, 0xFF9999CC, 0xFF999999, 0xFF999966,
|
||||
0xFF999933, 0xFF999900, 0xFF9966FF, 0xFF9966CC, 0xFF996699, 0xFF996666, 0xFF996633, 0xFF996600,
|
||||
0xFF9933FF, 0xFF9933CC, 0xFF993399, 0xFF993366, 0xFF993333, 0xFF993300, 0xFF9900FF, 0xFF9900CC,
|
||||
0xFF990099, 0xFF990066, 0xFF990033, 0xFF990000, 0xFF66FFFF, 0xFF66FFCC, 0xFF66FF99, 0xFF66FF66,
|
||||
0xFF66FF33, 0xFF66FF00, 0xFF66CCFF, 0xFF66CCCC, 0xFF66CC99, 0xFF66CC66, 0xFF66CC33, 0xFF66CC00,
|
||||
0xFF6699FF, 0xFF6699CC, 0xFF669999, 0xFF669966, 0xFF669933, 0xFF669900, 0xFF6666FF, 0xFF6666CC,
|
||||
0xFF666699, 0xFF666666, 0xFF666633, 0xFF666600, 0xFF6633FF, 0xFF6633CC, 0xFF663399, 0xFF663366,
|
||||
0xFF663333, 0xFF663300, 0xFF6600FF, 0xFF6600CC, 0xFF660099, 0xFF660066, 0xFF660033, 0xFF660000,
|
||||
0xFF33FFFF, 0xFF33FFCC, 0xFF33FF99, 0xFF33FF66, 0xFF33FF33, 0xFF33FF00, 0xFF33CCFF, 0xFF33CCCC,
|
||||
0xFF33CC99, 0xFF33CC66, 0xFF33CC33, 0xFF33CC00, 0xFF3399FF, 0xFF3399CC, 0xFF339999, 0xFF339966,
|
||||
0xFF339933, 0xFF339900, 0xFF3366FF, 0xFF3366CC, 0xFF336699, 0xFF336666, 0xFF336633, 0xFF336600,
|
||||
0xFF3333FF, 0xFF3333CC, 0xFF333399, 0xFF333366, 0xFF333333, 0xFF333300, 0xFF3300FF, 0xFF3300CC,
|
||||
0xFF330099, 0xFF330066, 0xFF330033, 0xFF330000, 0xFF00FFFF, 0xFF00FFCC, 0xFF00FF99, 0xFF00FF66,
|
||||
0xFF00FF33, 0xFF00FF00, 0xFF00CCFF, 0xFF00CCCC, 0xFF00CC99, 0xFF00CC66, 0xFF00CC33, 0xFF00CC00,
|
||||
0xFF0099FF, 0xFF0099CC, 0xFF009999, 0xFF009966, 0xFF009933, 0xFF009900, 0xFF0066FF, 0xFF0066CC,
|
||||
0xFF006699, 0xFF006666, 0xFF006633, 0xFF006600, 0xFF0033FF, 0xFF0033CC, 0xFF003399, 0xFF003366,
|
||||
0xFF003333, 0xFF003300, 0xFF0000FF, 0xFF0000CC, 0xFF000099, 0xFF000066, 0xFF000033, 0xFFEE0000,
|
||||
0xFFDD0000, 0xFFBB0000, 0xFFAA0000, 0xFF880000, 0xFF770000, 0xFF550000, 0xFF440000, 0xFF220000,
|
||||
0xFF110000, 0xFF00EE00, 0xFF00DD00, 0xFF00BB00, 0xFF00AA00, 0xFF008800, 0xFF007700, 0xFF005500,
|
||||
0xFF004400, 0xFF002200, 0xFF001100, 0xFF0000EE, 0xFF0000DD, 0xFF0000BB, 0xFF0000AA, 0xFF000088,
|
||||
0xFF000077, 0xFF000055, 0xFF000044, 0xFF000022, 0xFF000011, 0xFFEEEEEE, 0xFFDDDDDD, 0xFFBBBBBB,
|
||||
0xFFAAAAAA, 0xFF888888, 0xFF777777, 0xFF555555, 0xFF444444, 0xFF222222, 0xFF111111, 0xFF000000,
|
||||
};
|
||||
|
||||
const uint32_t Pomme::Graphics::clut4[16] =
|
||||
{
|
||||
0xffffffff, 0xfffcf305, 0xffff6402, 0xffdd0806, 0xfff20884, 0xff4600a5, 0xff0000d4, 0xff02abea,
|
||||
0xff1fb714, 0xff006411, 0xff562c05, 0xff90713a, 0xffc0c0c0, 0xff808080, 0xff404040, 0xff000000,
|
||||
};
|
191
src/Input/SDLInput.cpp
Normal file
191
src/Input/SDLInput.cpp
Normal file
@ -0,0 +1,191 @@
|
||||
#include <algorithm>
|
||||
|
||||
#include "Pomme.h"
|
||||
#include "PommeInput.h"
|
||||
|
||||
#include <SDL_events.h>
|
||||
#include <SDL_keyboard.h>
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Input
|
||||
|
||||
static char scancodeLookupTable[256];
|
||||
|
||||
static void InitScancodeLookupTable()
|
||||
{
|
||||
const unsigned char NO_MAC_VK = 0xFF;
|
||||
|
||||
memset(scancodeLookupTable, NO_MAC_VK, sizeof(scancodeLookupTable));
|
||||
char* T = scancodeLookupTable;
|
||||
|
||||
T[0] = NO_MAC_VK; // 0
|
||||
T[1] = NO_MAC_VK;
|
||||
T[2] = NO_MAC_VK;
|
||||
T[3] = NO_MAC_VK;
|
||||
T[SDL_SCANCODE_A] = kVK_ANSI_A;
|
||||
T[SDL_SCANCODE_B] = kVK_ANSI_B;
|
||||
T[SDL_SCANCODE_C] = kVK_ANSI_C;
|
||||
T[SDL_SCANCODE_D] = kVK_ANSI_D;
|
||||
T[SDL_SCANCODE_E] = kVK_ANSI_E;
|
||||
T[SDL_SCANCODE_F] = kVK_ANSI_F;
|
||||
T[SDL_SCANCODE_G] = kVK_ANSI_G; // 10
|
||||
T[SDL_SCANCODE_H] = kVK_ANSI_H;
|
||||
T[SDL_SCANCODE_I] = kVK_ANSI_I;
|
||||
T[SDL_SCANCODE_J] = kVK_ANSI_J;
|
||||
T[SDL_SCANCODE_K] = kVK_ANSI_K;
|
||||
T[SDL_SCANCODE_L] = kVK_ANSI_L;
|
||||
T[SDL_SCANCODE_M] = kVK_ANSI_M;
|
||||
T[SDL_SCANCODE_N] = kVK_ANSI_N;
|
||||
T[SDL_SCANCODE_O] = kVK_ANSI_O;
|
||||
T[SDL_SCANCODE_P] = kVK_ANSI_P;
|
||||
T[SDL_SCANCODE_Q] = kVK_ANSI_Q; // 20
|
||||
T[SDL_SCANCODE_R] = kVK_ANSI_R;
|
||||
T[SDL_SCANCODE_S] = kVK_ANSI_S;
|
||||
T[SDL_SCANCODE_T] = kVK_ANSI_T;
|
||||
T[SDL_SCANCODE_U] = kVK_ANSI_U;
|
||||
T[SDL_SCANCODE_V] = kVK_ANSI_V;
|
||||
T[SDL_SCANCODE_W] = kVK_ANSI_W;
|
||||
T[SDL_SCANCODE_X] = kVK_ANSI_X;
|
||||
T[SDL_SCANCODE_Y] = kVK_ANSI_Y;
|
||||
T[SDL_SCANCODE_Z] = kVK_ANSI_Z;
|
||||
T[SDL_SCANCODE_1] = kVK_ANSI_1; // 30
|
||||
T[SDL_SCANCODE_2] = kVK_ANSI_2;
|
||||
T[SDL_SCANCODE_3] = kVK_ANSI_3;
|
||||
T[SDL_SCANCODE_4] = kVK_ANSI_4;
|
||||
T[SDL_SCANCODE_5] = kVK_ANSI_5;
|
||||
T[SDL_SCANCODE_6] = kVK_ANSI_6;
|
||||
T[SDL_SCANCODE_7] = kVK_ANSI_7;
|
||||
T[SDL_SCANCODE_8] = kVK_ANSI_8;
|
||||
T[SDL_SCANCODE_9] = kVK_ANSI_9;
|
||||
T[SDL_SCANCODE_0] = kVK_ANSI_0;
|
||||
T[SDL_SCANCODE_RETURN] = kVK_Return; // 40
|
||||
T[SDL_SCANCODE_ESCAPE] = kVK_Escape;
|
||||
T[SDL_SCANCODE_BACKSPACE] = kVK_Delete;
|
||||
T[SDL_SCANCODE_TAB] = kVK_Tab;
|
||||
T[SDL_SCANCODE_SPACE] = kVK_Space;
|
||||
T[SDL_SCANCODE_MINUS] = kVK_ANSI_Minus;
|
||||
T[SDL_SCANCODE_EQUALS] = kVK_ANSI_Equal;
|
||||
T[SDL_SCANCODE_LEFTBRACKET] = kVK_ANSI_LeftBracket;
|
||||
T[SDL_SCANCODE_RIGHTBRACKET]= kVK_ANSI_RightBracket;
|
||||
T[SDL_SCANCODE_BACKSLASH] = kVK_ANSI_Backslash;
|
||||
T[SDL_SCANCODE_NONUSHASH] = NO_MAC_VK; // 50
|
||||
T[SDL_SCANCODE_SEMICOLON] = kVK_ANSI_Semicolon;
|
||||
T[SDL_SCANCODE_APOSTROPHE] = kVK_ANSI_Quote;
|
||||
T[SDL_SCANCODE_GRAVE] = kVK_ANSI_Grave;
|
||||
T[SDL_SCANCODE_COMMA] = kVK_ANSI_Comma;
|
||||
T[SDL_SCANCODE_PERIOD] = kVK_ANSI_Period;
|
||||
T[SDL_SCANCODE_SLASH] = kVK_ANSI_Slash;
|
||||
T[SDL_SCANCODE_CAPSLOCK] = kVK_CapsLock;
|
||||
T[SDL_SCANCODE_F1] = kVK_F1;
|
||||
T[SDL_SCANCODE_F2] = kVK_F2;
|
||||
T[SDL_SCANCODE_F3] = kVK_F3; // 60
|
||||
T[SDL_SCANCODE_F4] = kVK_F4;
|
||||
T[SDL_SCANCODE_F5] = kVK_F5;
|
||||
T[SDL_SCANCODE_F6] = kVK_F6;
|
||||
T[SDL_SCANCODE_F7] = kVK_F7;
|
||||
T[SDL_SCANCODE_F8] = kVK_F8;
|
||||
T[SDL_SCANCODE_F9] = kVK_F9;
|
||||
T[SDL_SCANCODE_F10] = kVK_F10;
|
||||
T[SDL_SCANCODE_F11] = kVK_F11;
|
||||
T[SDL_SCANCODE_F12] = kVK_F12;
|
||||
T[SDL_SCANCODE_PRINTSCREEN] = NO_MAC_VK; // could be F13 // 70
|
||||
T[SDL_SCANCODE_SCROLLLOCK] = NO_MAC_VK; // could be F14
|
||||
T[SDL_SCANCODE_PAUSE] = NO_MAC_VK; // could be F15
|
||||
T[SDL_SCANCODE_INSERT] = kVK_Help; // 'help' is there on an ext'd kbd
|
||||
T[SDL_SCANCODE_HOME] = kVK_Home;
|
||||
T[SDL_SCANCODE_PAGEUP] = kVK_PageUp;
|
||||
T[SDL_SCANCODE_DELETE] = kVK_ForwardDelete;
|
||||
T[SDL_SCANCODE_END] = kVK_End;
|
||||
T[SDL_SCANCODE_PAGEDOWN] = kVK_PageDown;
|
||||
T[SDL_SCANCODE_RIGHT] = kVK_RightArrow;
|
||||
T[SDL_SCANCODE_LEFT] = kVK_LeftArrow; // 80
|
||||
T[SDL_SCANCODE_DOWN] = kVK_DownArrow;
|
||||
T[SDL_SCANCODE_UP] = kVK_UpArrow;
|
||||
T[SDL_SCANCODE_NUMLOCKCLEAR]= kVK_ANSI_KeypadClear; // 83
|
||||
T[SDL_SCANCODE_KP_DIVIDE] = kVK_ANSI_KeypadDivide; // 84
|
||||
T[SDL_SCANCODE_KP_MULTIPLY] = kVK_ANSI_KeypadMultiply;
|
||||
T[SDL_SCANCODE_KP_MINUS] = kVK_ANSI_KeypadMinus;
|
||||
T[SDL_SCANCODE_KP_PLUS] = kVK_ANSI_KeypadPlus;
|
||||
T[SDL_SCANCODE_KP_ENTER] = kVK_ANSI_KeypadEnter;
|
||||
T[SDL_SCANCODE_KP_1] = kVK_ANSI_Keypad1;
|
||||
T[SDL_SCANCODE_KP_2] = kVK_ANSI_Keypad2; // 90
|
||||
T[SDL_SCANCODE_KP_3] = kVK_ANSI_Keypad3;
|
||||
T[SDL_SCANCODE_KP_4] = kVK_ANSI_Keypad4;
|
||||
T[SDL_SCANCODE_KP_5] = kVK_ANSI_Keypad5;
|
||||
T[SDL_SCANCODE_KP_6] = kVK_ANSI_Keypad6;
|
||||
T[SDL_SCANCODE_KP_7] = kVK_ANSI_Keypad7;
|
||||
T[SDL_SCANCODE_KP_8] = kVK_ANSI_Keypad8;
|
||||
T[SDL_SCANCODE_KP_9] = kVK_ANSI_Keypad9;
|
||||
T[SDL_SCANCODE_KP_0] = kVK_ANSI_Keypad0;
|
||||
T[SDL_SCANCODE_KP_PERIOD] = kVK_ANSI_KeypadDecimal;
|
||||
T[SDL_SCANCODE_NONUSBACKSLASH] = NO_MAC_VK; // 100
|
||||
T[SDL_SCANCODE_APPLICATION] = NO_MAC_VK;
|
||||
T[SDL_SCANCODE_POWER] = NO_MAC_VK;
|
||||
T[SDL_SCANCODE_KP_EQUALS] = kVK_ANSI_KeypadEquals; // 103
|
||||
T[SDL_SCANCODE_F13] = kVK_F13; // 104
|
||||
T[SDL_SCANCODE_F14] = kVK_F14;
|
||||
T[SDL_SCANCODE_F15] = kVK_F15;
|
||||
T[SDL_SCANCODE_F16] = kVK_F16;
|
||||
T[SDL_SCANCODE_F17] = kVK_F17;
|
||||
T[SDL_SCANCODE_F18] = kVK_F18;
|
||||
T[SDL_SCANCODE_F19] = kVK_F19; // 110
|
||||
T[SDL_SCANCODE_F20] = kVK_F20; // 111
|
||||
T[SDL_SCANCODE_F21] = NO_MAC_VK;
|
||||
T[SDL_SCANCODE_F22] = NO_MAC_VK;
|
||||
T[SDL_SCANCODE_F23] = NO_MAC_VK;
|
||||
T[SDL_SCANCODE_F24] = NO_MAC_VK; // 115
|
||||
T[SDL_SCANCODE_HELP] = kVK_Help; // also INSERT
|
||||
|
||||
// --snip--
|
||||
|
||||
T[SDL_SCANCODE_LCTRL] = kVK_Control; // 224
|
||||
T[SDL_SCANCODE_LSHIFT] = kVK_Shift;
|
||||
T[SDL_SCANCODE_LALT] = kVK_Option;
|
||||
T[SDL_SCANCODE_LGUI] = kVK_Command;
|
||||
T[SDL_SCANCODE_RCTRL] = kVK_RightControl;
|
||||
T[SDL_SCANCODE_RSHIFT] = kVK_RightShift;
|
||||
T[SDL_SCANCODE_RALT] = kVK_RightOption;
|
||||
T[SDL_SCANCODE_RGUI] = kVK_Command; // no right command I guess
|
||||
}
|
||||
|
||||
void GetKeys(KeyMap km)
|
||||
{
|
||||
km[0] = 0;
|
||||
km[1] = 0;
|
||||
km[2] = 0;
|
||||
km[3] = 0;
|
||||
|
||||
SDL_PumpEvents();
|
||||
int numkeys = 0;
|
||||
const UInt8* keystate = SDL_GetKeyboardState(&numkeys);
|
||||
|
||||
numkeys = std::min((int) sizeof(scancodeLookupTable), numkeys);
|
||||
|
||||
for (int sdlScancode = 0; sdlScancode < numkeys; sdlScancode++)
|
||||
{
|
||||
if (!keystate[sdlScancode])
|
||||
continue;
|
||||
int vk = scancodeLookupTable[sdlScancode];
|
||||
if (vk == 0xFF)
|
||||
continue;
|
||||
int byteNo = vk >> 5; // =vk/32
|
||||
int bitNo = vk & 31;
|
||||
km[byteNo] |= 1 << bitNo;
|
||||
}
|
||||
|
||||
#if POMME_DEBUG_INPUT
|
||||
if (km[0] || km[1] || km[2] || km[3])
|
||||
printf("GK %08x%08x%08x%08x\n", km[0], km[1], km[2], km[3]);
|
||||
#endif
|
||||
}
|
||||
|
||||
Boolean Button(void)
|
||||
{
|
||||
ONCE(TODOMINOR());
|
||||
return false;
|
||||
}
|
||||
|
||||
void Pomme::Input::Init()
|
||||
{
|
||||
InitScancodeLookupTable();
|
||||
}
|
185
src/Memory/Memory.cpp
Normal file
185
src/Memory/Memory.cpp
Normal file
@ -0,0 +1,185 @@
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <cstring>
|
||||
|
||||
#include "Pomme.h"
|
||||
#include "Utilities/FixedPool.h"
|
||||
|
||||
#define LOG POMME_GENLOG(POMME_DEBUG_MEMORY, "MEMO")
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Implementation-specific stuff
|
||||
|
||||
static const int HANDLE_MAGIC_LENGTH = 8;
|
||||
static const char HANDLE_MAGIC[HANDLE_MAGIC_LENGTH] = "LIVEhdl";
|
||||
static const char HANDLE_MAGIC_DEAD[HANDLE_MAGIC_LENGTH] = "DEADhdl";
|
||||
|
||||
struct BlockDescriptor
|
||||
{
|
||||
Ptr buf;
|
||||
char magic[HANDLE_MAGIC_LENGTH];
|
||||
Size size;
|
||||
};
|
||||
|
||||
// these must not move around
|
||||
static Pomme::FixedPool<BlockDescriptor, UInt16, 1000> blocks;
|
||||
|
||||
static BlockDescriptor* HandleToBlock(Handle h)
|
||||
{
|
||||
auto bd = (BlockDescriptor*) h;
|
||||
if (0 != memcmp(bd->magic, HANDLE_MAGIC, HANDLE_MAGIC_LENGTH))
|
||||
throw std::runtime_error("corrupted handle");
|
||||
return bd;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Memory: Handle
|
||||
|
||||
Handle NewHandle(Size s)
|
||||
{
|
||||
if (s < 0) throw std::invalid_argument("trying to alloc negative size handle");
|
||||
|
||||
BlockDescriptor* block = blocks.Alloc();
|
||||
block->buf = new char[s];
|
||||
memcpy(block->magic, HANDLE_MAGIC, HANDLE_MAGIC_LENGTH);
|
||||
block->size = s;
|
||||
|
||||
if ((Ptr) &block->buf != (Ptr) block)
|
||||
throw std::runtime_error("buffer address mismatches block address");
|
||||
|
||||
LOG << (void*) block->buf << ", size " << s << "\n";
|
||||
|
||||
return &block->buf;
|
||||
}
|
||||
|
||||
Handle NewHandleClear(Size s)
|
||||
{
|
||||
Handle h = NewHandle(s);
|
||||
memset(*h, 0, s);
|
||||
return h;
|
||||
}
|
||||
|
||||
Handle TempNewHandle(Size s, OSErr* err)
|
||||
{
|
||||
Handle h = NewHandle(s);
|
||||
*err = noErr;
|
||||
return h;
|
||||
}
|
||||
|
||||
Size GetHandleSize(Handle h)
|
||||
{
|
||||
return HandleToBlock(h)->size;
|
||||
}
|
||||
|
||||
void SetHandleSize(Handle handle, Size byteCount)
|
||||
{
|
||||
TODOFATAL();
|
||||
}
|
||||
|
||||
void DisposeHandle(Handle h)
|
||||
{
|
||||
LOG << (void*) *h << "\n";
|
||||
BlockDescriptor* b = HandleToBlock(h);
|
||||
delete[] b->buf;
|
||||
b->buf = 0;
|
||||
b->size = -1;
|
||||
memcpy(b->magic, HANDLE_MAGIC_DEAD, HANDLE_MAGIC_LENGTH);
|
||||
blocks.Dispose(b);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Memory: Ptr
|
||||
|
||||
Ptr NewPtr(Size byteCount)
|
||||
{
|
||||
if (byteCount < 0) throw std::invalid_argument("trying to NewPtr negative size");
|
||||
return new char[byteCount];
|
||||
}
|
||||
|
||||
Ptr NewPtrSys(Size byteCount)
|
||||
{
|
||||
if (byteCount < 0) throw std::invalid_argument("trying to NewPtrSys negative size");
|
||||
return new char[byteCount];
|
||||
}
|
||||
|
||||
void DisposePtr(Ptr p)
|
||||
{
|
||||
delete[] p;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Memory: BlockMove
|
||||
|
||||
void BlockMove(const void* srcPtr, void* destPtr, Size byteCount)
|
||||
{
|
||||
memcpy(destPtr, srcPtr, byteCount);
|
||||
}
|
||||
|
||||
void BlockMoveData(const void* srcPtr, void* destPtr, Size byteCount)
|
||||
{
|
||||
TODOFATAL();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// No-op memory junk
|
||||
|
||||
void MaxApplZone(void)
|
||||
{
|
||||
// No-op
|
||||
}
|
||||
|
||||
void MoreMasters(void)
|
||||
{
|
||||
// No-op
|
||||
}
|
||||
|
||||
Size CompactMem(Size)
|
||||
{
|
||||
// No-op
|
||||
// TODO: what should we actually return?
|
||||
return 0;
|
||||
}
|
||||
|
||||
Size CompactMemSys(Size)
|
||||
{
|
||||
// No-op
|
||||
// TODO: what should we actually return?
|
||||
return 0;
|
||||
}
|
||||
|
||||
void PurgeMem(Size)
|
||||
{
|
||||
// No-op
|
||||
}
|
||||
|
||||
void PurgeMemSys(Size)
|
||||
{
|
||||
// No-op
|
||||
}
|
||||
|
||||
Size MaxMem(Size*)
|
||||
{
|
||||
// No-op
|
||||
// TODO: what should we actually return?
|
||||
return 0;
|
||||
}
|
||||
|
||||
void HNoPurge(Handle)
|
||||
{
|
||||
// No-op
|
||||
}
|
||||
|
||||
void HLock(Handle)
|
||||
{
|
||||
// No-op
|
||||
}
|
||||
|
||||
void HLockHi(Handle)
|
||||
{
|
||||
// No-op
|
||||
}
|
||||
|
||||
void NoPurgePixels(PixMapHandle)
|
||||
{
|
||||
// No-op
|
||||
}
|
17
src/Platform/Windows/PommeWindows.cpp
Normal file
17
src/Platform/Windows/PommeWindows.cpp
Normal file
@ -0,0 +1,17 @@
|
||||
#include "Platform/Windows/PommeWindows.h"
|
||||
|
||||
#include <shlobj.h>
|
||||
|
||||
std::filesystem::path Pomme::Platform::Windows::GetPreferencesFolder()
|
||||
{
|
||||
wchar_t* wpath = nullptr;
|
||||
SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, nullptr, &wpath);
|
||||
auto path = std::filesystem::path(wpath);
|
||||
CoTaskMemFree(static_cast<void*>(wpath));
|
||||
return path;
|
||||
}
|
||||
|
||||
void Pomme::Platform::Windows::SysBeep()
|
||||
{
|
||||
MessageBeep(0);
|
||||
}
|
10
src/Platform/Windows/PommeWindows.h
Normal file
10
src/Platform/Windows/PommeWindows.h
Normal file
@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
namespace Pomme::Platform::Windows
|
||||
{
|
||||
std::filesystem::path GetPreferencesFolder();
|
||||
|
||||
void SysBeep();
|
||||
}
|
82
src/Pomme.cpp
Normal file
82
src/Pomme.cpp
Normal file
@ -0,0 +1,82 @@
|
||||
#include <iostream>
|
||||
#include <cstring>
|
||||
|
||||
#include "Pomme.h"
|
||||
#include "PommeInit.h"
|
||||
#include "PommeTime.h"
|
||||
#include "PommeFiles.h"
|
||||
#include "PommeGraphics.h"
|
||||
#include "PommeSound.h"
|
||||
#include "PommeInput.h"
|
||||
|
||||
#include "SDL.h"
|
||||
|
||||
#if _WIN32
|
||||
#include "Platform/Windows/PommeWindows.h"
|
||||
#endif
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Our own utils
|
||||
|
||||
const char* Pomme::QuitRequest::what() const noexcept
|
||||
{
|
||||
return "the user has requested to quit the application";
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Misc
|
||||
|
||||
void ExitToShell()
|
||||
{
|
||||
throw Pomme::QuitRequest();
|
||||
}
|
||||
|
||||
void SysBeep(short duration)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
Pomme::Platform::Windows::SysBeep();
|
||||
#else
|
||||
TODOMINOR();
|
||||
#endif
|
||||
}
|
||||
|
||||
void FlushEvents(short, short)
|
||||
{
|
||||
TODOMINOR();
|
||||
}
|
||||
|
||||
void NumToStringC(long theNum, Str255 theString)
|
||||
{
|
||||
snprintf(theString, 256, "%ld", theNum);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Mouse cursor
|
||||
|
||||
void InitCursor()
|
||||
{
|
||||
SDL_ShowCursor(1);
|
||||
}
|
||||
|
||||
void HideCursor()
|
||||
{
|
||||
SDL_ShowCursor(0);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Our own init
|
||||
|
||||
void Pomme::Init(const char* windowName)
|
||||
{
|
||||
Pomme::Time::Init();
|
||||
Pomme::Files::Init();
|
||||
Pomme::Graphics::Init(windowName, 640, 480);
|
||||
Pomme::Sound::Init();
|
||||
Pomme::Input::Init();
|
||||
}
|
||||
|
||||
void Pomme::Shutdown()
|
||||
{
|
||||
Pomme::Sound::Shutdown();
|
||||
Pomme::Graphics::Shutdown();
|
||||
}
|
336
src/Pomme.h
Normal file
336
src/Pomme.h
Normal file
@ -0,0 +1,336 @@
|
||||
#pragma once
|
||||
|
||||
#include "PommeTypes.h"
|
||||
#include "PommeEnums.h"
|
||||
#include "PommeDebug.h"
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Structure unpacking
|
||||
|
||||
#include "Utilities/structpack.h"
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// PowerPC intrinsics
|
||||
|
||||
#define __fres(x) (1.0f/x)
|
||||
#define __fabs(x) fabs(x)
|
||||
#define __frsqrte(x) (1.0f/sqrtf(x))
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Source code compat
|
||||
|
||||
#define nil NULL
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// FSSpec
|
||||
|
||||
OSErr FSMakeFSSpec(short vRefNum, long dirID, const char* cstrFileName, FSSpec* spec);
|
||||
|
||||
short FSpOpenResFile(const FSSpec* spec, char permission);
|
||||
|
||||
// Open a file's data fork
|
||||
OSErr FSpOpenDF(const FSSpec* spec, char permission, short* refNum);
|
||||
|
||||
// Open a file's resource fork
|
||||
OSErr FSpOpenRF(const FSSpec* spec, char permission, short* refNum);
|
||||
|
||||
OSErr FSpCreate(const FSSpec* spec, OSType creator, OSType fileType, ScriptCode scriptTag);
|
||||
|
||||
OSErr FSpDelete(const FSSpec* spec);
|
||||
|
||||
OSErr ResolveAlias(const FSSpec* spec, AliasHandle alias, FSSpec* target, Boolean* wasChanged);
|
||||
|
||||
OSErr FindFolder(short vRefNum, OSType folderType, Boolean createFolder, short* foundVRefNum, long* foundDirID);
|
||||
|
||||
OSErr DirCreate(short vRefNum, long parentDirID, const char* cstrDirectoryName, long* createdDirID);
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// File I/O
|
||||
|
||||
OSErr FSRead(short refNum, long* count, Ptr buffPtr);
|
||||
|
||||
OSErr FSWrite(short refNum, long* count, Ptr buffPtr);
|
||||
|
||||
OSErr FSClose(short refNum);
|
||||
|
||||
OSErr GetEOF(short refNum, long* logEOF);
|
||||
|
||||
OSErr SetEOF(short refNum, long logEOF);
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Resource file management
|
||||
|
||||
// MoreMacintoshToolbox.pdf p174
|
||||
OSErr ResError(void);
|
||||
|
||||
void UseResFile(short refNum);
|
||||
|
||||
// Gets the file reference number of the current resource file.
|
||||
short CurResFile();
|
||||
|
||||
void CloseResFile(short refNum);
|
||||
|
||||
// Returns total number of resources of the given type
|
||||
// in the current resource file only.
|
||||
short Count1Resources(ResType);
|
||||
|
||||
Handle GetResource(ResType theType, short theID);
|
||||
|
||||
void ReleaseResource(Handle theResource);
|
||||
|
||||
void RemoveResource(Handle theResource);
|
||||
|
||||
void AddResource(Handle theData, ResType theType, short theID, const char* name);
|
||||
|
||||
void WriteResource(Handle theResource);
|
||||
|
||||
void DetachResource(Handle theResource);
|
||||
|
||||
long GetResourceSizeOnDisk(Handle);
|
||||
|
||||
long SizeResource(Handle);
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// QuickDraw 2D
|
||||
|
||||
void SetRect(Rect* r, short left, short top, short right, short bottom);
|
||||
|
||||
PicHandle GetPicture(short PICTresourceID);
|
||||
|
||||
void DisposeGWorld(GWorldPtr offscreenGWorld);
|
||||
|
||||
// IM:QD:6-16
|
||||
QDErr NewGWorld(
|
||||
GWorldPtr* offscreenGWorld,
|
||||
short pixelDepth,
|
||||
const Rect* boundsRect,
|
||||
void* junk1, // CTabHandle cTable
|
||||
void* junk2, // GDHandle aGDevice
|
||||
long junk3 // long flags
|
||||
);
|
||||
|
||||
void GetGWorld(CGrafPtr* port, GDHandle* gdh);
|
||||
|
||||
void SetGWorld(CGrafPtr port, GDHandle gdh);
|
||||
|
||||
void SetPort(GrafPtr port);
|
||||
|
||||
void GetPort(GrafPtr* port);
|
||||
|
||||
void MoveTo(short h, short v);
|
||||
|
||||
void GetForeColor(RGBColor* rgb);
|
||||
|
||||
void ForeColor(long color);
|
||||
|
||||
void BackColor(long color);
|
||||
|
||||
void RGBBackColor(const RGBColor* color);
|
||||
|
||||
void RGBForeColor(const RGBColor* color);
|
||||
|
||||
// Pomme extension (not part of the original Toolbox API).
|
||||
void RGBBackColor2(UInt32 color);
|
||||
|
||||
// Pomme extension (not part of the original Toolbox API).
|
||||
void RGBForeColor2(UInt32 color);
|
||||
|
||||
void PaintRect(const Rect* r);
|
||||
|
||||
void EraseRect(const Rect* r);
|
||||
|
||||
void LineTo(short h, short v);
|
||||
|
||||
void FrameRect(const Rect*);
|
||||
|
||||
//short TextWidth(const char* textBuf, short firstByte, short byteCount);
|
||||
|
||||
short TextWidthC(const char* cstr);
|
||||
|
||||
void DrawChar(char c);
|
||||
|
||||
void DrawStringC(const char* cstr);
|
||||
|
||||
// IM:QD:7-44
|
||||
void DrawPicture(PicHandle myPicture, const Rect* dstRect);
|
||||
|
||||
// IM:QD:6-31
|
||||
PixMapHandle GetGWorldPixMap(GWorldPtr offscreenGWorld);
|
||||
|
||||
// IM:QD:6-38
|
||||
Ptr GetPixBaseAddr(PixMapHandle pm);
|
||||
|
||||
void CopyBits(
|
||||
const PixMap* srcBits,
|
||||
PixMap* dstBits,
|
||||
const Rect* srcRect,
|
||||
const Rect* dstRect,
|
||||
short mode,
|
||||
void* maskRgn
|
||||
);
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// QuickDraw 2D extensions
|
||||
|
||||
// Returns true if the current port is "damaged".
|
||||
// Pomme extension (not part of the original Toolbox API).
|
||||
Boolean IsPortDamaged(void);
|
||||
|
||||
// Stores current port's damaged region into "r".
|
||||
// You should only call this after having checked that IsPortDamaged() is true.
|
||||
// Pomme extension (not part of the original Toolbox API).
|
||||
void GetPortDamageRegion(Rect* r);
|
||||
|
||||
// Sets current port as undamaged.
|
||||
// Pomme extension (not part of the original Toolbox API).
|
||||
void ClearPortDamage(void);
|
||||
|
||||
// Extends the current port's damage region to include the given rectangle.
|
||||
// Pomme extension (not part of the original Toolbox API).
|
||||
void DamagePortRegion(const Rect*);
|
||||
|
||||
// Writes the current port to a Targa image.
|
||||
// Pomme extension (not part of the original Toolbox API).
|
||||
void DumpPortTGA(const char* path);
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Misc
|
||||
|
||||
void ExitToShell();
|
||||
|
||||
void SysBeep(short duration);
|
||||
|
||||
void FlushEvents(short, short);
|
||||
|
||||
void NumToStringC(long theNum, Str255 theString);
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Input
|
||||
|
||||
void GetKeys(KeyMap);
|
||||
|
||||
Boolean Button(void);
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Memory: No-op
|
||||
|
||||
void MaxApplZone(void);
|
||||
|
||||
void MoreMasters(void);
|
||||
|
||||
Size CompactMem(Size);
|
||||
|
||||
// Compact system heap zone manually.
|
||||
Size CompactMemSys(Size);
|
||||
|
||||
void PurgeMem(Size);
|
||||
|
||||
void PurgeMemSys(Size);
|
||||
|
||||
Size MaxMem(Size* grow);
|
||||
|
||||
void HNoPurge(Handle);
|
||||
|
||||
void HLock(Handle);
|
||||
|
||||
void HLockHi(Handle);
|
||||
|
||||
void NoPurgePixels(PixMapHandle);
|
||||
|
||||
// To prevent the base address for an offscreen pixel image from being moved
|
||||
// while you draw into or copy from its pixel map.
|
||||
Boolean LockPixels(PixMapHandle); // shall return true always
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Memory: Handle
|
||||
|
||||
Handle NewHandle(Size);
|
||||
|
||||
// Allocate prezeroed memory
|
||||
Handle NewHandleClear(Size);
|
||||
|
||||
// Allocate temp memory
|
||||
Handle TempNewHandle(Size, OSErr*);
|
||||
|
||||
Size GetHandleSize(Handle);
|
||||
|
||||
// Change the logical size of the relocatable block corresponding to a handle
|
||||
void SetHandleSize(Handle, Size);
|
||||
|
||||
void DisposeHandle(Handle);
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Memory: Ptr
|
||||
|
||||
Ptr NewPtr(Size);
|
||||
|
||||
Ptr NewPtrSys(Size);
|
||||
|
||||
void DisposePtr(Ptr p);
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Memory: BlockMove
|
||||
|
||||
// Copies a sequence of bytes from one location in memory to another
|
||||
void BlockMove(const void* srcPtr, void* destPtr, Size byteCount);
|
||||
|
||||
void BlockMoveData(const void* srcPtr, void* destPtr, Size byteCount);
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Time Manager
|
||||
|
||||
// Number of seconds elapsed since 1904-01-01 00:00
|
||||
void GetDateTime(unsigned long* secs);
|
||||
|
||||
// Number of usecs elapsed since system startup
|
||||
void Microseconds(UnsignedWide* microTickCount);
|
||||
|
||||
// Number of ticks elapsed since system startup (1 tick = approx. 1/60 of a second)
|
||||
UInt32 TickCount();
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Mouse cursor
|
||||
|
||||
void HideCursor();
|
||||
|
||||
void InitCursor();
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Sound Manager
|
||||
|
||||
OSErr GetDefaultOutputVolume(long*);
|
||||
|
||||
OSErr SetDefaultOutputVolume(long);
|
||||
|
||||
OSErr SndNewChannel(SndChannelPtr* chan, short synth, long init, SndCallBackProcPtr userRoutine);
|
||||
|
||||
OSErr SndDisposeChannel(SndChannelPtr chan, Boolean quietNow);
|
||||
|
||||
OSErr SndChannelStatus(SndChannelPtr chan, short theLength, SCStatusPtr theStatus);
|
||||
|
||||
OSErr SndDoImmediate(SndChannelPtr chan, const SndCommand* cmd);
|
||||
|
||||
OSErr GetSoundHeaderOffset(SndListHandle sndHandle, long* offset);
|
||||
|
||||
OSErr SndStartFilePlay(SndChannelPtr chan, short fRefNum, short resNum, long bufferSize, Ptr theBuffer, /*AudioSelectionPtr*/ void* theSelection, FilePlayCompletionUPP theCompletion, Boolean async);
|
||||
|
||||
OSErr SndPauseFilePlay(SndChannelPtr chan);
|
||||
|
||||
OSErr SndStopFilePlay(SndChannelPtr chan, Boolean quietNow);
|
||||
|
||||
NumVersion SndSoundManagerVersion();
|
||||
|
||||
// Pomme extension
|
||||
Boolean Pomme_DecompressSoundResource(SndListHandle* sndHandlePtr, long* offsetToHeader);
|
||||
|
||||
// Pomme extension
|
||||
void Pomme_PauseLoopingChannels(Boolean pause);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
57
src/PommeDebug.cpp
Normal file
57
src/PommeDebug.cpp
Normal file
@ -0,0 +1,57 @@
|
||||
#include "PommeDebug.h"
|
||||
|
||||
#include <SDL.h>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
|
||||
void ImplementMe(const char* fn, std::string msg, int severity)
|
||||
{
|
||||
if (severity >= 0)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "[TODO] \x1b[1m" << fn << "\x1b[22m";
|
||||
if (!msg.empty())
|
||||
{
|
||||
ss << ": " << msg;
|
||||
}
|
||||
auto str = ss.str();
|
||||
std::cerr << (severity > 0 ? "\x1b[31m" : "\x1b[33m") << str << "\x1b[0m\n";
|
||||
}
|
||||
|
||||
if (severity >= 2)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << fn << "()";
|
||||
if (!msg.empty()) ss << "\n" << msg;
|
||||
|
||||
auto str = ss.str();
|
||||
|
||||
int mbflags = SDL_MESSAGEBOX_ERROR;
|
||||
if (severity == 0) mbflags = SDL_MESSAGEBOX_INFORMATION;
|
||||
if (severity == 1) mbflags = SDL_MESSAGEBOX_WARNING;
|
||||
|
||||
SDL_ShowSimpleMessageBox(mbflags, "Source port TODO", str.c_str(), nullptr);
|
||||
}
|
||||
|
||||
if (severity >= 2)
|
||||
{
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
std::string Pomme::FourCCString(uint32_t t, char filler)
|
||||
{
|
||||
char b[5];
|
||||
*(uint32_t*) b = t;
|
||||
#if !(TARGET_RT_BIGENDIAN)
|
||||
std::reverse(b, b + 4);
|
||||
#endif
|
||||
// replace non-ascii with '?'
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
char c = b[i];
|
||||
if (c < ' ' || c > '~') b[i] = filler;
|
||||
}
|
||||
b[4] = '\0';
|
||||
return b;
|
||||
}
|
51
src/PommeDebug.h
Normal file
51
src/PommeDebug.h
Normal file
@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
#if __cplusplus
|
||||
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
|
||||
#define POMME_DEBUG_MEMORY false
|
||||
#define POMME_DEBUG_SOUND false
|
||||
#define POMME_DEBUG_PICT false
|
||||
#define POMME_DEBUG_FILES false
|
||||
#define POMME_DEBUG_RESOURCES false
|
||||
#define POMME_DEBUG_INPUT false
|
||||
|
||||
#define POMME_GENLOG(define, prefix) if (!define) {} else std::cout << "[" << prefix << "] " << __func__ << ":\t"
|
||||
#define POMME_GENLOG_NOPREFIX(define) if (!define) {} else std::cout
|
||||
|
||||
namespace Pomme
|
||||
{
|
||||
std::string FourCCString(uint32_t t, char filler = '?');
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// My todos
|
||||
|
||||
void ImplementMe(const char* fn, std::string msg, int severity);
|
||||
|
||||
#define TODOCUSTOM(message, severity) { \
|
||||
std::stringstream ss; \
|
||||
ss << message; \
|
||||
ImplementMe(__func__, ss.str(), severity); \
|
||||
}
|
||||
|
||||
#define TODOMINOR() TODOCUSTOM("", 0)
|
||||
#define TODOMINOR2(x) TODOCUSTOM(x, 0)
|
||||
#define TODO() TODOCUSTOM("", 1)
|
||||
#define TODO2(x) TODOCUSTOM(x, 1)
|
||||
#define TODOFATAL() TODOCUSTOM("", 2)
|
||||
#define TODOFATAL2(x) TODOCUSTOM(x, 2)
|
||||
|
||||
#define ONCE(x) { \
|
||||
static bool once = false; \
|
||||
if (!once) { \
|
||||
once = true; \
|
||||
{x} \
|
||||
printf(" \x1b[90m\\__ this todo won't be shown again\x1b[0m\n"); \
|
||||
} \
|
||||
}
|
||||
|
||||
|
||||
#endif
|
397
src/PommeEnums.h
Normal file
397
src/PommeEnums.h
Normal file
@ -0,0 +1,397 @@
|
||||
#pragma once
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Filesystem permission char
|
||||
|
||||
enum EFSPermissions
|
||||
{
|
||||
fsCurPerm = 0,
|
||||
fsRdPerm = 1,
|
||||
fsWrPerm = 2,
|
||||
fsRdWrPerm = 3,
|
||||
};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Folder types
|
||||
|
||||
enum
|
||||
{
|
||||
kSystemFolderType = 'macs',
|
||||
kDesktopFolderType = 'desk',
|
||||
kSystemDesktopFolderType = 'sdsk',
|
||||
kTrashFolderType = 'trsh',
|
||||
kSystemTrashFolderType = 'strs',
|
||||
kWhereToEmptyTrashFolderType = 'empt',
|
||||
kPrintMonitorDocsFolderType = 'prnt',
|
||||
kStartupFolderType = 'strt',
|
||||
kShutdownFolderType = 'shdf',
|
||||
kAppleMenuFolderType = 'amnu',
|
||||
kControlPanelFolderType = 'ctrl',
|
||||
kSystemControlPanelFolderType = 'sctl',
|
||||
kExtensionFolderType = 'extn',
|
||||
kFontsFolderType = 'font',
|
||||
kPreferencesFolderType = 'pref',
|
||||
kSystemPreferencesFolderType = 'sprf',
|
||||
kTemporaryFolderType = 'temp'
|
||||
};
|
||||
|
||||
enum {
|
||||
kOnSystemDisk = -32768L,
|
||||
kOnAppropriateDisk = -32767,
|
||||
kSystemDomain = -32766,
|
||||
kLocalDomain = -32765,
|
||||
kNetworkDomain = -32764,
|
||||
kUserDomain = -32763,
|
||||
kClassicDomain = -32762
|
||||
};
|
||||
|
||||
#define kCreateFolder true
|
||||
#define kDontCreateFolder false
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Error codes (OSErr)
|
||||
|
||||
enum EErrors
|
||||
{
|
||||
noErr = 0,
|
||||
|
||||
unimpErr = -4, // unimplemented core routine
|
||||
|
||||
controlErr = -17, // I/O System Errors
|
||||
statusErr = -18, // I/O System Errors
|
||||
readErr = -19, // I/O System Errors
|
||||
writErr = -20, // I/O System Errors
|
||||
badUnitErr = -21, // I/O System Errors
|
||||
unitEmptyErr = -22, // I/O System Errors
|
||||
openErr = -23, // I/O System Errors
|
||||
closErr = -24, // I/O System Errors
|
||||
|
||||
abortErr = -27, // IO call aborted by KillIO
|
||||
notOpenErr = -28, // Couldn't rd/wr/ctl/sts cause driver not opened
|
||||
dirFulErr = -33, // File directory full
|
||||
dskFulErr = -34, // Disk or volume full
|
||||
nsvErr = -35, // Volume doesn't exist
|
||||
ioErr = -36, // I/O error
|
||||
bdNamErr = -37, // Bad file or volume name
|
||||
fnOpnErr = -38, // File not open
|
||||
eofErr = -39, // End-of-file reached
|
||||
posErr = -40, // Attempt to position mark before start of file
|
||||
mFulErr = -41, // Memory full (open) or file won't fit (load)
|
||||
tmfoErr = -42, // Too many files open
|
||||
fnfErr = -43, // File not found (FSSpec is still valid)
|
||||
wPrErr = -44, // Volume is hardware-locked
|
||||
fLckdErr = -45, // File is locked
|
||||
vLckdErr = -46, // Volume is software-locked
|
||||
fBsyErr = -47, // File is busy
|
||||
dupFNErr = -48, // Duplicate filename
|
||||
opWrErr = -49, // File already open for writing
|
||||
paramErr = -50, // Invalid value passed in parameter
|
||||
rfNumErr = -51, // Invalid reference number
|
||||
gfpErr = -52, // Error during a GetFPos family function
|
||||
volOffLinErr = -53, // Volume is offline
|
||||
permErr = -54, // Permission error
|
||||
volOnLinErr = -55, // Volume already online
|
||||
nsDrvErr = -56, // No such Drive
|
||||
noMacDskErr = -57, // Foreign disk
|
||||
extFSErr = -58, // Volume belongs to external file system
|
||||
fsRnErr = -59, // Couldn't rename
|
||||
badMDBErr = -60, // Bad master directory block
|
||||
wrPermErr = -61, // Read/write permission doesn't allow writing
|
||||
|
||||
memROZWarn = -99, // soft error in ROZ
|
||||
memROZError = -99, // hard error in ROZ
|
||||
memROZErr = -99, // hard error in ROZ
|
||||
memFullErr = -108, // Not enough room in heap zone
|
||||
nilHandleErr = -109, // Master Pointer was NIL in HandleZone or other
|
||||
memWZErr = -111, // WhichZone failed (applied to free block)
|
||||
memPurErr = -112, // trying to purge a locked or non-purgeable block
|
||||
memAdrErr = -110, // address was odd; or out of range
|
||||
memAZErr = -113, // Address in zone check failed
|
||||
memPCErr = -114, // Pointer Check failed
|
||||
memBCErr = -115, // Block Check failed
|
||||
memSCErr = -116, // Size Check failed
|
||||
memLockedErr = -117, // trying to move a locked block (MoveHHi)
|
||||
|
||||
dirNFErr = -120, // Directory not found
|
||||
tmwdoErr = -121, // Too many working directories open
|
||||
badMovErr = -122, // Couldn't move
|
||||
wrgVolTypErr = -123, // Unrecognized volume (not HFS)
|
||||
volGoneErr = -124, // Server volume disconnected
|
||||
fsDSIntErr = -127, // Non-hardware internal file system error
|
||||
|
||||
userCanceledErr = -128,
|
||||
|
||||
badExtResource = -185, // extended resource has a bad format
|
||||
CantDecompress = -186, // resource bent ("the bends") - can't decompress a compressed resource
|
||||
resourceInMemory= -188, // Resource already in memory
|
||||
writingPastEnd = -189, // Writing past end of file
|
||||
inputOutOfBounds= -190, // Offset of Count out of bounds
|
||||
resNotFound = -192, // Resource not found
|
||||
resFNotFound = -193, // Resource file not found
|
||||
addResFailed = -194, // AddResource failed
|
||||
addRefFailed = -195, // AddReference failed
|
||||
rmvResFailed = -196, // RmveResource failed
|
||||
rmvRefFailed = -197, // RmveReference failed
|
||||
resAttrErr = -198, // attribute inconsistent with operation
|
||||
mapReadErr = -199, // map inconsistent with operation
|
||||
|
||||
// Sound Manager error codes
|
||||
|
||||
notEnoughHardwareErr = -201,
|
||||
queueFull = -203,
|
||||
resProblem = -204,
|
||||
badChannel = -205, // channel is corrupt or unusable
|
||||
badFormat = -206, // resource is corrupt or unusable
|
||||
notEnoughBufferSpace = -207, // insufficient memory available
|
||||
badFileFormat = -208, // file is corrupt or unusable, or not AIFF or AIFF-C
|
||||
channelBusy = -209,
|
||||
buffersTooSmall = -210,
|
||||
siInvalidCompression = -223,
|
||||
};
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Script Manager enums
|
||||
|
||||
enum EScriptManager
|
||||
{
|
||||
smSystemScript = -1,
|
||||
smCurrentScript = -2,
|
||||
smAllScripts = -3
|
||||
};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
enum EEvents
|
||||
{
|
||||
everyEvent = ~0
|
||||
};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Memory enums
|
||||
|
||||
enum EMemory
|
||||
{
|
||||
maxSize = 0x7FFFFFF0 // the largest block possible
|
||||
};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Resource types
|
||||
|
||||
enum EResTypes
|
||||
{
|
||||
rAliasType = 'alis',
|
||||
};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Sound Manager enums
|
||||
|
||||
enum ESndPitch
|
||||
{
|
||||
kMiddleC = 60L,
|
||||
};
|
||||
|
||||
enum ESndSynth
|
||||
{
|
||||
squareWaveSynth = 1,
|
||||
waveTableSynth = 3,
|
||||
sampledSynth = 5
|
||||
};
|
||||
|
||||
enum ESndInit
|
||||
{
|
||||
initChanLeft = 0x0002, // left stereo channel
|
||||
initChanRight = 0x0003, // right stereo channel
|
||||
initMono = 0x0080, // monophonic channel
|
||||
initStereo = 0x00C0, // stereo channel
|
||||
initMACE3 = 0x0300, // 3:1 compression
|
||||
initMACE6 = 0x0400, // 6:1 compression
|
||||
initNoInterp = 0x0004, // no linear interpolation
|
||||
initNoDrop = 0x0008, // no drop-sample conversion
|
||||
};
|
||||
|
||||
// Sound commands
|
||||
enum ESndCmds
|
||||
{
|
||||
nullCmd = 0,
|
||||
initCmd = 1,
|
||||
freeCmd = 2,
|
||||
quietCmd = 3,
|
||||
flushCmd = 4,
|
||||
reInitCmd = 5,
|
||||
waitCmd = 10,
|
||||
pauseCmd = 11,
|
||||
resumeCmd = 12,
|
||||
callBackCmd = 13,
|
||||
syncCmd = 14,
|
||||
availableCmd = 24,
|
||||
versionCmd = 25,
|
||||
totalLoadCmd = 26,
|
||||
loadCmd = 27,
|
||||
freqDurationCmd = 40,
|
||||
restCmd = 41,
|
||||
freqCmd = 42,
|
||||
ampCmd = 43,
|
||||
timbreCmd = 44,
|
||||
getAmpCmd = 45,
|
||||
volumeCmd = 46, // sound manager 3.0 or later only
|
||||
getVolumeCmd = 47, // sound manager 3.0 or later only
|
||||
clockComponentCmd = 50, // sound manager 3.2.1 or later only
|
||||
getClockComponentCmd = 51, // sound manager 3.2.1 or later only
|
||||
scheduledSoundCmd = 52, // sound manager 3.3 or later only
|
||||
linkSoundComponentsCmd = 53, // sound manager 3.3 or later only
|
||||
waveTableCmd = 60,
|
||||
phaseCmd = 61,
|
||||
soundCmd = 80,
|
||||
bufferCmd = 81,
|
||||
rateCmd = 82,
|
||||
continueCmd = 83,
|
||||
doubleBufferCmd = 84,
|
||||
getRateCmd = 85,
|
||||
rateMultiplierCmd = 86,
|
||||
getRateMultiplierCmd = 87,
|
||||
sizeCmd = 90, // obsolete command
|
||||
convertCmd = 91, // obsolete MACE command
|
||||
pommeSetLoopCmd = 0x7001,
|
||||
// Do not define commands above 0x7FFF -- the high bit means a 'snd ' resource has associated sound data
|
||||
};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Keyboard enums
|
||||
|
||||
enum
|
||||
{
|
||||
// key positions on US keyboard
|
||||
kVK_ANSI_A = 0x00,
|
||||
kVK_ANSI_S = 0x01,
|
||||
kVK_ANSI_D = 0x02,
|
||||
kVK_ANSI_F = 0x03,
|
||||
kVK_ANSI_H = 0x04,
|
||||
kVK_ANSI_G = 0x05,
|
||||
kVK_ANSI_Z = 0x06,
|
||||
kVK_ANSI_X = 0x07,
|
||||
kVK_ANSI_C = 0x08,
|
||||
kVK_ANSI_V = 0x09,
|
||||
kVK_ANSI_B = 0x0B,
|
||||
kVK_ANSI_Q = 0x0C,
|
||||
kVK_ANSI_W = 0x0D,
|
||||
kVK_ANSI_E = 0x0E,
|
||||
kVK_ANSI_R = 0x0F,
|
||||
kVK_ANSI_Y = 0x10,
|
||||
kVK_ANSI_T = 0x11,
|
||||
kVK_ANSI_1 = 0x12,
|
||||
kVK_ANSI_2 = 0x13,
|
||||
kVK_ANSI_3 = 0x14,
|
||||
kVK_ANSI_4 = 0x15,
|
||||
kVK_ANSI_6 = 0x16,
|
||||
kVK_ANSI_5 = 0x17,
|
||||
kVK_ANSI_Equal = 0x18,
|
||||
kVK_ANSI_9 = 0x19,
|
||||
kVK_ANSI_7 = 0x1A,
|
||||
kVK_ANSI_Minus = 0x1B,
|
||||
kVK_ANSI_8 = 0x1C,
|
||||
kVK_ANSI_0 = 0x1D,
|
||||
kVK_ANSI_RightBracket = 0x1E,
|
||||
kVK_ANSI_O = 0x1F,
|
||||
kVK_ANSI_U = 0x20,
|
||||
kVK_ANSI_LeftBracket = 0x21,
|
||||
kVK_ANSI_I = 0x22,
|
||||
kVK_ANSI_P = 0x23,
|
||||
kVK_ANSI_L = 0x25,
|
||||
kVK_ANSI_J = 0x26,
|
||||
kVK_ANSI_Quote = 0x27,
|
||||
kVK_ANSI_K = 0x28,
|
||||
kVK_ANSI_Semicolon = 0x29,
|
||||
kVK_ANSI_Backslash = 0x2A,
|
||||
kVK_ANSI_Comma = 0x2B,
|
||||
kVK_ANSI_Slash = 0x2C,
|
||||
kVK_ANSI_N = 0x2D,
|
||||
kVK_ANSI_M = 0x2E,
|
||||
kVK_ANSI_Period = 0x2F,
|
||||
kVK_ANSI_Grave = 0x32,
|
||||
kVK_ANSI_KeypadDecimal = 0x41,
|
||||
kVK_ANSI_KeypadMultiply = 0x43,
|
||||
kVK_ANSI_KeypadPlus = 0x45,
|
||||
kVK_ANSI_KeypadClear = 0x47,
|
||||
kVK_ANSI_KeypadDivide = 0x4B,
|
||||
kVK_ANSI_KeypadEnter = 0x4C,
|
||||
kVK_ANSI_KeypadMinus = 0x4E,
|
||||
kVK_ANSI_KeypadEquals = 0x51,
|
||||
kVK_ANSI_Keypad0 = 0x52,
|
||||
kVK_ANSI_Keypad1 = 0x53,
|
||||
kVK_ANSI_Keypad2 = 0x54,
|
||||
kVK_ANSI_Keypad3 = 0x55,
|
||||
kVK_ANSI_Keypad4 = 0x56,
|
||||
kVK_ANSI_Keypad5 = 0x57,
|
||||
kVK_ANSI_Keypad6 = 0x58,
|
||||
kVK_ANSI_Keypad7 = 0x59,
|
||||
kVK_ANSI_Keypad8 = 0x5B,
|
||||
kVK_ANSI_Keypad9 = 0x5C
|
||||
};
|
||||
|
||||
// keycodes for keys that are independent of keyboard layout
|
||||
enum
|
||||
{
|
||||
kVK_Return = 0x24,
|
||||
kVK_Tab = 0x30,
|
||||
kVK_Space = 0x31,
|
||||
kVK_Delete = 0x33,
|
||||
kVK_Escape = 0x35,
|
||||
kVK_Command = 0x37,
|
||||
kVK_Shift = 0x38,
|
||||
kVK_CapsLock = 0x39,
|
||||
kVK_Option = 0x3A,
|
||||
kVK_Control = 0x3B,
|
||||
kVK_RightShift = 0x3C,
|
||||
kVK_RightOption = 0x3D,
|
||||
kVK_RightControl = 0x3E,
|
||||
kVK_Function = 0x3F,
|
||||
kVK_F17 = 0x40,
|
||||
kVK_VolumeUp = 0x48,
|
||||
kVK_VolumeDown = 0x49,
|
||||
kVK_Mute = 0x4A,
|
||||
kVK_F18 = 0x4F,
|
||||
kVK_F19 = 0x50,
|
||||
kVK_F20 = 0x5A,
|
||||
kVK_F5 = 0x60,
|
||||
kVK_F6 = 0x61,
|
||||
kVK_F7 = 0x62,
|
||||
kVK_F3 = 0x63,
|
||||
kVK_F8 = 0x64,
|
||||
kVK_F9 = 0x65,
|
||||
kVK_F11 = 0x67,
|
||||
kVK_F13 = 0x69,
|
||||
kVK_F16 = 0x6A,
|
||||
kVK_F14 = 0x6B,
|
||||
kVK_F10 = 0x6D,
|
||||
kVK_F12 = 0x6F,
|
||||
kVK_F15 = 0x71,
|
||||
kVK_Help = 0x72,
|
||||
kVK_Home = 0x73,
|
||||
kVK_PageUp = 0x74,
|
||||
kVK_ForwardDelete = 0x75,
|
||||
kVK_F4 = 0x76,
|
||||
kVK_End = 0x77,
|
||||
kVK_F2 = 0x78,
|
||||
kVK_PageDown = 0x79,
|
||||
kVK_F1 = 0x7A,
|
||||
kVK_LeftArrow = 0x7B,
|
||||
kVK_RightArrow = 0x7C,
|
||||
kVK_DownArrow = 0x7D,
|
||||
kVK_UpArrow = 0x7E
|
||||
};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// QD2D
|
||||
|
||||
enum
|
||||
{
|
||||
whiteColor = 30,
|
||||
blackColor = 33,
|
||||
yellowColor = 69,
|
||||
magentaColor = 137,
|
||||
redColor = 205,
|
||||
cyanColor = 273,
|
||||
greenColor = 341,
|
||||
blueColor = 409,
|
||||
};
|
41
src/PommeFiles.h
Normal file
41
src/PommeFiles.h
Normal file
@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include "PommeTypes.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include "CompilerSupport/filesystem.h"
|
||||
|
||||
namespace Pomme::Files
|
||||
{
|
||||
struct ResourceMetadata
|
||||
{
|
||||
short forkRefNum;
|
||||
OSType type;
|
||||
SInt16 id;
|
||||
Byte flags;
|
||||
SInt32 size;
|
||||
UInt32 dataOffset;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
struct ResourceFork
|
||||
{
|
||||
SInt16 fileRefNum;
|
||||
std::map<ResType, std::map<SInt16, ResourceMetadata> > resourceMap;
|
||||
};
|
||||
|
||||
void Init();
|
||||
|
||||
bool IsRefNumLegal(short refNum);
|
||||
|
||||
bool IsStreamOpen(short refNum);
|
||||
|
||||
bool IsStreamPermissionAllowed(short refNum, char perm);
|
||||
|
||||
std::iostream& GetStream(short refNum);
|
||||
|
||||
void CloseStream(short refNum);
|
||||
|
||||
FSSpec HostPathToFSSpec(const fs::path& fullPath);
|
||||
}
|
66
src/PommeGraphics.h
Normal file
66
src/PommeGraphics.h
Normal file
@ -0,0 +1,66 @@
|
||||
#pragma once
|
||||
|
||||
#include "PommeTypes.h"
|
||||
#include <istream>
|
||||
#include <vector>
|
||||
|
||||
namespace Pomme::Graphics
|
||||
{
|
||||
struct Color
|
||||
{
|
||||
UInt8 a, r, g, b;
|
||||
|
||||
Color(UInt8 red, UInt8 green, UInt8 blue);
|
||||
|
||||
Color(UInt8 red, UInt8 green, UInt8 blue, UInt8 alpha);
|
||||
|
||||
Color();
|
||||
};
|
||||
|
||||
struct ARGBPixmap
|
||||
{
|
||||
int width;
|
||||
int height;
|
||||
std::vector<Byte> data;
|
||||
|
||||
ARGBPixmap();
|
||||
|
||||
ARGBPixmap(int w, int h);
|
||||
|
||||
ARGBPixmap(ARGBPixmap&& other) noexcept;
|
||||
|
||||
ARGBPixmap& operator=(ARGBPixmap&& other) noexcept;
|
||||
|
||||
void Fill(UInt8 red, UInt8 green, UInt8 blue, UInt8 alpha = 0xFF);
|
||||
|
||||
void Plot(int x, int y, UInt32 color);
|
||||
|
||||
void WriteTGA(const char* path) const;
|
||||
|
||||
inline UInt32* GetPtr(int x, int y)
|
||||
{ return (UInt32*) &data.data()[4 * (y * width + x)]; }
|
||||
};
|
||||
|
||||
void Init(const char* windowTitle, int windowWidth, int windowHeight);
|
||||
|
||||
void Shutdown();
|
||||
|
||||
ARGBPixmap ReadPICT(std::istream& f, bool skip512 = true);
|
||||
|
||||
void DumpTGA(const char* path, short width, short height, const char* argbData);
|
||||
|
||||
void DrawARGBPixmap(int left, int top, ARGBPixmap& p);
|
||||
|
||||
CGrafPtr GetScreenPort(void);
|
||||
|
||||
void SetWindowIconFromIcl8Resource(short i);
|
||||
|
||||
inline int Width(const Rect& r)
|
||||
{ return r.right - r.left; }
|
||||
|
||||
inline int Height(const Rect& r)
|
||||
{ return r.bottom - r.top; }
|
||||
|
||||
extern const uint32_t clut8[256];
|
||||
extern const uint32_t clut4[16];
|
||||
}
|
16
src/PommeInit.h
Normal file
16
src/PommeInit.h
Normal file
@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
namespace Pomme
|
||||
{
|
||||
// Throw this exception to interrupt the game's main loop
|
||||
class QuitRequest : public std::exception
|
||||
{
|
||||
public:
|
||||
virtual const char* what() const noexcept;
|
||||
};
|
||||
|
||||
void Init(const char* applName);
|
||||
|
||||
void Shutdown();
|
||||
}
|
||||
|
6
src/PommeInput.h
Normal file
6
src/PommeInput.h
Normal file
@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace Pomme::Input
|
||||
{
|
||||
void Init();
|
||||
}
|
70
src/PommeSound.h
Normal file
70
src/PommeSound.h
Normal file
@ -0,0 +1,70 @@
|
||||
#pragma once
|
||||
|
||||
#include "CompilerSupport/span.h"
|
||||
#include <vector>
|
||||
#include <istream>
|
||||
#include <memory>
|
||||
#include "Sound/cmixer.h"
|
||||
|
||||
namespace Pomme::Sound
|
||||
{
|
||||
void Init();
|
||||
|
||||
void Shutdown();
|
||||
|
||||
void ReadAIFF(std::istream& input, cmixer::WavStream& output);
|
||||
|
||||
class Codec
|
||||
{
|
||||
public:
|
||||
virtual ~Codec()
|
||||
{}
|
||||
|
||||
virtual int SamplesPerPacket() = 0;
|
||||
|
||||
virtual int BytesPerPacket() = 0;
|
||||
|
||||
virtual void Decode(const int nChannels, const std::span<const char> input, const std::span<char> output) = 0;
|
||||
};
|
||||
|
||||
class MACE : public Codec
|
||||
{
|
||||
public:
|
||||
int SamplesPerPacket() override
|
||||
{ return 6; }
|
||||
|
||||
int BytesPerPacket() override
|
||||
{ return 2; }
|
||||
|
||||
void Decode(const int nChannels, const std::span<const char> input, const std::span<char> output) override;
|
||||
};
|
||||
|
||||
class IMA4 : public Codec
|
||||
{
|
||||
public:
|
||||
int SamplesPerPacket() override
|
||||
{ return 64; }
|
||||
|
||||
int BytesPerPacket() override
|
||||
{ return 34; }
|
||||
|
||||
void Decode(const int nChannels, const std::span<const char> input, const std::span<char> output) override;
|
||||
};
|
||||
|
||||
class xlaw : public Codec
|
||||
{
|
||||
const int16_t* xlawToPCM;
|
||||
public:
|
||||
xlaw(uint32_t codecFourCC);
|
||||
|
||||
int SamplesPerPacket() override
|
||||
{ return 1; }
|
||||
|
||||
int BytesPerPacket() override
|
||||
{ return 1; }
|
||||
|
||||
void Decode(const int nChannels, const std::span<const char> input, const std::span<char> output) override;
|
||||
};
|
||||
|
||||
std::unique_ptr<Pomme::Sound::Codec> GetCodec(uint32_t fourCC);
|
||||
}
|
6
src/PommeTime.h
Normal file
6
src/PommeTime.h
Normal file
@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace Pomme::Time
|
||||
{
|
||||
void Init();
|
||||
}
|
258
src/PommeTypes.h
Normal file
258
src/PommeTypes.h
Normal file
@ -0,0 +1,258 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Integer types
|
||||
|
||||
typedef char SignedByte;
|
||||
typedef char SInt8;
|
||||
typedef short SInt16;
|
||||
typedef int SInt32;
|
||||
typedef long long SInt64;
|
||||
|
||||
typedef unsigned char Byte;
|
||||
typedef unsigned char UInt8;
|
||||
typedef unsigned char Boolean;
|
||||
typedef unsigned short UInt16;
|
||||
typedef unsigned int UInt32;
|
||||
typedef unsigned long long UInt64;
|
||||
|
||||
#if TARGET_RT_BIGENDIAN
|
||||
typedef struct { UInt32 hi, lo; } UnsignedWide;
|
||||
#else
|
||||
typedef struct { UInt32 lo, hi; } UnsignedWide;
|
||||
#endif
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Fixed/fract types
|
||||
|
||||
typedef SInt32 Fixed;
|
||||
typedef SInt32 Fract;
|
||||
typedef UInt32 UnsignedFixed;
|
||||
typedef SInt16 ShortFixed;
|
||||
typedef Fixed* FixedPtr;
|
||||
typedef Fract* FractPtr;
|
||||
typedef UnsignedFixed* UnsignedFixedPtr;
|
||||
typedef ShortFixed* ShortFixedPtr;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Basic system types
|
||||
|
||||
typedef SInt16 OSErr;
|
||||
typedef SInt32 OSStatus;
|
||||
typedef void* LogicalAddress;
|
||||
typedef const void* ConstLogicalAddress;
|
||||
typedef void* PhysicalAddress;
|
||||
typedef UInt8* BytePtr;
|
||||
typedef unsigned long ByteCount;
|
||||
typedef unsigned long ByteOffset;
|
||||
typedef SInt32 Duration;
|
||||
typedef UnsignedWide AbsoluteTime;
|
||||
typedef UInt32 OptionBits;
|
||||
typedef unsigned long ItemCount;
|
||||
typedef UInt32 PBVersion;
|
||||
typedef SInt16 ScriptCode;
|
||||
typedef SInt16 LangCode;
|
||||
typedef SInt16 RegionCode;
|
||||
typedef UInt32 FourCharCode;
|
||||
typedef FourCharCode OSType;
|
||||
typedef FourCharCode ResType;
|
||||
typedef OSType* OSTypePtr;
|
||||
typedef ResType* ResTypePtr;
|
||||
typedef char* Ptr; // Pointer to a non-relocatable block
|
||||
typedef Ptr* Handle; // Pointer to a master pointer to a relocatable block
|
||||
typedef long Size; // Number of bytes in a block (signed for historical reasons)
|
||||
typedef void (*ProcPtr);
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// (Pascal) String types
|
||||
|
||||
typedef char Str32[33];
|
||||
typedef char Str255[256];
|
||||
typedef char* StringPtr;
|
||||
typedef const char* ConstStr255Param;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Point & Rect types
|
||||
|
||||
typedef struct Point { SInt16 v, h; } Point;
|
||||
typedef struct Rect { SInt16 top, left, bottom, right; } Rect;
|
||||
|
||||
typedef Point* PointPtr;
|
||||
typedef Rect* RectPtr;
|
||||
|
||||
typedef struct FixedPoint { Fixed x, y; } FixedPoint;
|
||||
typedef struct FixedRect { Fixed left, top, right, bottom; } FixedRect;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// FSSpec types
|
||||
|
||||
typedef struct FSSpec
|
||||
{
|
||||
// Volume reference number of the volume containing the specified file or directory.
|
||||
short vRefNum;
|
||||
|
||||
// Parent directory ID of the specified file or directory (the directory ID of the directory containing the given file or directory).
|
||||
long parID;
|
||||
|
||||
// The name of the specified file or directory. In Carbon, this name must be a leaf name; the name cannot contain a semicolon.
|
||||
// WARNING: this is a C string encoded as UTF-8, NOT a pascal string encoded as MacRoman!
|
||||
// Mac application code expecting "name" (the pascal string) must be adjusted.
|
||||
Str255 cName;
|
||||
} FSSpec;
|
||||
|
||||
typedef Handle AliasHandle;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// QuickDraw types
|
||||
|
||||
typedef SInt16 QDErr;
|
||||
|
||||
typedef struct RGBColor
|
||||
{
|
||||
UInt16 red;
|
||||
UInt16 green;
|
||||
UInt16 blue;
|
||||
} RGBColor;
|
||||
|
||||
typedef struct Picture
|
||||
{
|
||||
// Version 1 size.
|
||||
// Not used for version 2 PICTs, as the size may easily exceed 16 bits.
|
||||
SInt16 picSize;
|
||||
|
||||
Rect picFrame;
|
||||
|
||||
// Raw raster image decoded from PICT opcodes. Shouldn't be accessed
|
||||
// directly by the Mac application as it is stored in a format internal
|
||||
// to the Pomme implementation for rendering (typically ARGB32).
|
||||
Ptr __pomme_pixelsARGB32;
|
||||
} Picture;
|
||||
|
||||
|
||||
typedef Picture* PicPtr;
|
||||
typedef PicPtr* PicHandle;
|
||||
typedef Handle GDHandle; // GDevice handle. Game code doesn't care about GDevice internals, so we just alias a generic Handle
|
||||
// http://mirror.informatimago.com/next/developer.apple.com/documentation/Carbon/Reference/QuickDraw_Ref/qdref_main/data_type_41.html
|
||||
typedef struct PixMap
|
||||
{
|
||||
Rect bounds;
|
||||
short pixelSize;
|
||||
Ptr _impl; // Points to ARGBPixmap
|
||||
} PixMap;
|
||||
typedef PixMap* PixMapPtr;
|
||||
typedef PixMapPtr* PixMapHandle;
|
||||
typedef struct GrafPort
|
||||
{
|
||||
Rect portRect;
|
||||
void* _impl; // Points to GrafPortImpl
|
||||
} GrafPort;
|
||||
typedef GrafPort* GrafPtr;
|
||||
typedef GrafPtr WindowPtr;
|
||||
typedef GrafPort CGrafPort;
|
||||
typedef GrafPtr CGrafPtr;
|
||||
typedef CGrafPtr GWorldPtr;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Sound Manager types
|
||||
|
||||
typedef struct SndCommand
|
||||
{
|
||||
unsigned short cmd;
|
||||
short param1;
|
||||
union {
|
||||
long param2;
|
||||
Ptr ptr; // pomme addition to pass 64-bit clean pointers
|
||||
};
|
||||
} SndCommand;
|
||||
|
||||
typedef struct SCStatus
|
||||
{
|
||||
UnsignedFixed scStartTime; // starting time for play from disk (based on audio selection record)
|
||||
UnsignedFixed scEndTime; // ending time for play from disk (based on audio selection record)
|
||||
UnsignedFixed scCurrentTime; // current time for play from disk
|
||||
Boolean scChannelBusy; // true if channel is processing commands
|
||||
Boolean scChannelDisposed; // reserved
|
||||
Boolean scChannelPaused; // true if channel is paused
|
||||
Boolean scUnused; // reserved
|
||||
unsigned long scChannelAttributes; // attributes of this channel
|
||||
long scCPULoad; // cpu load for this channel ("obsolete")
|
||||
} SCStatus;
|
||||
|
||||
typedef struct SndChannel* SndChannelPtr;
|
||||
|
||||
typedef void (*SndCallBackProcPtr)(SndChannelPtr chan, SndCommand* cmd);
|
||||
// for pomme implementation purposes we don't care about the 68000/ppc specifics of universal procedure pointers
|
||||
typedef SndCallBackProcPtr SndCallbackUPP;
|
||||
|
||||
typedef struct SndChannel
|
||||
{
|
||||
SndChannelPtr nextChan;
|
||||
Ptr firstMod; // reserved for the Sound Manager (Pomme: used as internal ptr)
|
||||
SndCallBackProcPtr callBack;
|
||||
long long userInfo; // free for application's use (Pomme: made it 64 bit so app can store ptrs)
|
||||
#if 0
|
||||
long wait; // The following is for internal Sound Manager use only.
|
||||
SndCommand cmdInProgress;
|
||||
short flags;
|
||||
short qLength;
|
||||
short qHead;
|
||||
short qTail;
|
||||
SndCommand queue[128];
|
||||
#endif
|
||||
} SndChannel;
|
||||
|
||||
typedef struct ModRef
|
||||
{
|
||||
unsigned short modNumber;
|
||||
long modInit;
|
||||
} ModRef;
|
||||
|
||||
typedef struct SndListResource
|
||||
{
|
||||
short format;
|
||||
short numModifiers;
|
||||
// flexible array hack
|
||||
ModRef modifierPart[1];
|
||||
short numCommands;
|
||||
// flexible array hack
|
||||
SndCommand commandPart[1];
|
||||
UInt8 dataPart[1];
|
||||
} SndListResource;
|
||||
|
||||
typedef SCStatus* SCStatusPtr;
|
||||
typedef SndListResource* SndListPtr;
|
||||
typedef SndListPtr* SndListHandle;
|
||||
typedef SndListHandle SndListHndl;
|
||||
typedef void(*FilePlayCompletionProcPtr)(SndChannelPtr chan);
|
||||
typedef FilePlayCompletionProcPtr FilePlayCompletionUPP;
|
||||
#define NewFilePlayCompletionProc(userRoutine) (userRoutine)
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Keyboard input types
|
||||
|
||||
typedef UInt32 KeyMap[4];
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// 'vers' resource
|
||||
|
||||
#if TARGET_RT_BIG_ENDIAN
|
||||
//BCD encoded, e.g. "4.2.1a3" is 0x04214003
|
||||
typedef struct NumVersion
|
||||
{
|
||||
UInt8 majorRev; // 1st part of version number in BCD
|
||||
UInt8 minorAndBugRev; // 2nd & 3rd part of version number share a byte
|
||||
UInt8 stage; // stage code: dev, alpha, beta, final
|
||||
UInt8 nonRelRev; // revision level of non-released version
|
||||
} NumVersion;
|
||||
#else
|
||||
typedef struct NumVersion
|
||||
{
|
||||
UInt8 nonRelRev; // revision level of non-released version
|
||||
UInt8 stage; // stage code: dev, alpha, beta, final
|
||||
UInt8 minorAndBugRev; // 2nd & 3rd part of version number share a byte
|
||||
UInt8 majorRev; // 1st part of version number in BCD
|
||||
} NumVersion;
|
||||
#endif
|
29
src/PommeVideo.h
Normal file
29
src/PommeVideo.h
Normal file
@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include "PommeTypes.h"
|
||||
#include "Sound/cmixer.h"
|
||||
|
||||
#include <istream>
|
||||
#include <queue>
|
||||
#include <vector>
|
||||
|
||||
namespace Pomme::Video
|
||||
{
|
||||
struct Movie
|
||||
{
|
||||
int width;
|
||||
int height;
|
||||
FourCharCode videoFormat;
|
||||
float videoFrameRate;
|
||||
std::queue<std::vector<unsigned char>> videoFrames;
|
||||
|
||||
FourCharCode audioFormat;
|
||||
int audioSampleRate;
|
||||
int audioBitDepth;
|
||||
int audioNChannels;
|
||||
cmixer::WavStream audioStream;
|
||||
unsigned audioSampleCount;
|
||||
};
|
||||
|
||||
Movie ReadMoov(std::istream& f);
|
||||
}
|
88
src/Sound/AIFF.cpp
Normal file
88
src/Sound/AIFF.cpp
Normal file
@ -0,0 +1,88 @@
|
||||
#include "PommeSound.h"
|
||||
#include "Utilities/BigEndianIStream.h"
|
||||
#include <cstdint>
|
||||
|
||||
static void AIFFAssert(bool condition, const char* message)
|
||||
{
|
||||
if (!condition)
|
||||
{
|
||||
throw std::runtime_error(message);
|
||||
}
|
||||
}
|
||||
|
||||
void Pomme::Sound::ReadAIFF(std::istream& input, cmixer::WavStream& output)
|
||||
{
|
||||
BigEndianIStream f(input);
|
||||
|
||||
AIFFAssert('FORM' == f.Read<uint32_t>(), "AIFF: invalid FORM");
|
||||
auto formSize = f.Read<uint32_t>();
|
||||
auto endOfForm = f.Tell() + std::streampos(formSize);
|
||||
auto formType = f.Read<uint32_t>();
|
||||
AIFFAssert(formType == 'AIFF' || formType == 'AIFC', "AIFF: not an AIFF or AIFC file");
|
||||
|
||||
// COMM chunk contents
|
||||
int nChannels = 0;
|
||||
int nPackets = 0;
|
||||
int sampleRate = 0;
|
||||
uint32_t compressionType = 'NONE';
|
||||
|
||||
bool gotCOMM = false;
|
||||
|
||||
while (f.Tell() != endOfForm)
|
||||
{
|
||||
auto ckID = f.Read<uint32_t>();
|
||||
auto ckSize = f.Read<uint32_t>();
|
||||
std::streampos endOfChunk = f.Tell() + std::streampos(ckSize);
|
||||
|
||||
switch (ckID)
|
||||
{
|
||||
case 'FVER':
|
||||
{
|
||||
auto timestamp = f.Read<uint32_t>();
|
||||
AIFFAssert(timestamp == 0xA2805140u, "AIFF: unrecognized FVER");
|
||||
break;
|
||||
}
|
||||
|
||||
case 'COMM': // common chunk, 2-85
|
||||
{
|
||||
nChannels = f.Read<uint16_t>();
|
||||
nPackets = f.Read<uint32_t>();
|
||||
f.Skip(2); // sample bit depth (UInt16)
|
||||
sampleRate = (int)f.Read80BitFloat();
|
||||
if (formType == 'AIFC')
|
||||
{
|
||||
compressionType = f.Read<uint32_t>();
|
||||
f.ReadPascalString(); // This is a human-friendly compression name. Skip it.
|
||||
}
|
||||
gotCOMM = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case 'SSND':
|
||||
{
|
||||
AIFFAssert(gotCOMM, "AIFF: reached SSND before COMM");
|
||||
AIFFAssert(0 == f.Read<uint64_t>(), "AIFF: unexpected offset/blockSize in SSND");
|
||||
// sampled sound data is here
|
||||
|
||||
const int ssndSize = ckSize - 8;
|
||||
auto ssnd = std::vector<char>(ssndSize);
|
||||
f.Read(ssnd.data(), ssndSize);
|
||||
|
||||
// TODO: if the compression type is 'NONE' (raw PCM), just init the WavStream without decoding (not needed for Nanosaur though)
|
||||
auto codec = Pomme::Sound::GetCodec(compressionType);
|
||||
auto spanIn = std::span(ssnd);
|
||||
auto spanOut = output.GetBuffer(nChannels * nPackets * codec->SamplesPerPacket() * 2);
|
||||
codec->Decode(nChannels, spanIn, spanOut);
|
||||
output.Init(sampleRate, 16, nChannels, false, spanOut);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
f.Goto(int(endOfChunk));
|
||||
break;
|
||||
}
|
||||
|
||||
AIFFAssert(f.Tell() == endOfChunk, "AIFF: incorrect end-of-chunk position");
|
||||
}
|
||||
}
|
||||
|
161
src/Sound/IMA4.cpp
Normal file
161
src/Sound/IMA4.cpp
Normal file
@ -0,0 +1,161 @@
|
||||
// Adapted from ffmpeg. Look at libavcodec/adpcm{,_data}.{c,h}
|
||||
|
||||
/*
|
||||
* Portions Copyright (c) 2001-2003 The FFmpeg project
|
||||
*
|
||||
* FFmpeg is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* FFmpeg is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with FFmpeg; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include "PommeSound.h"
|
||||
|
||||
#include <vector>
|
||||
#include <cassert>
|
||||
#include <algorithm>
|
||||
|
||||
const int8_t ff_adpcm_index_table[16] = {
|
||||
-1, -1, -1, -1, 2, 4, 6, 8,
|
||||
-1, -1, -1, -1, 2, 4, 6, 8,
|
||||
};
|
||||
|
||||
const int16_t ff_adpcm_step_table[89] = {
|
||||
7, 8, 9, 10, 11, 12, 13, 14, 16, 17,
|
||||
19, 21, 23, 25, 28, 31, 34, 37, 41, 45,
|
||||
50, 55, 60, 66, 73, 80, 88, 97, 107, 118,
|
||||
130, 143, 157, 173, 190, 209, 230, 253, 279, 307,
|
||||
337, 371, 408, 449, 494, 544, 598, 658, 724, 796,
|
||||
876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066,
|
||||
2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358,
|
||||
5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899,
|
||||
15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767
|
||||
};
|
||||
|
||||
struct ADPCMChannelStatus
|
||||
{
|
||||
int predictor;
|
||||
int16_t step_index;
|
||||
int step;
|
||||
};
|
||||
|
||||
static inline int sign_extend(int val, unsigned bits)
|
||||
{
|
||||
unsigned shift = 8 * sizeof(int) - bits;
|
||||
union { unsigned u; int s; } v = { (unsigned)val << shift };
|
||||
return v.s >> shift;
|
||||
}
|
||||
|
||||
static inline int adpcm_ima_qt_expand_nibble(ADPCMChannelStatus* c, int nibble, int shift)
|
||||
{
|
||||
int step_index;
|
||||
int predictor;
|
||||
int diff, step;
|
||||
|
||||
step = ff_adpcm_step_table[c->step_index];
|
||||
step_index = c->step_index + ff_adpcm_index_table[nibble];
|
||||
step_index = std::clamp(step_index, 0, 88);
|
||||
|
||||
diff = step >> 3;
|
||||
if (nibble & 4) diff += step;
|
||||
if (nibble & 2) diff += step >> 1;
|
||||
if (nibble & 1) diff += step >> 2;
|
||||
|
||||
if (nibble & 8)
|
||||
predictor = c->predictor - diff;
|
||||
else
|
||||
predictor = c->predictor + diff;
|
||||
|
||||
c->predictor = std::clamp(predictor, -32768, 32767);
|
||||
c->step_index = step_index;
|
||||
|
||||
return c->predictor;
|
||||
}
|
||||
|
||||
// In QuickTime, IMA is encoded by chunks of 34 bytes (=64 samples). Channel data is interleaved per-chunk.
|
||||
static void DecodeIMA4Chunk(
|
||||
const uint8_t** input,
|
||||
int16_t** output,
|
||||
std::vector<ADPCMChannelStatus>& ctx)
|
||||
{
|
||||
const size_t nChannels = ctx.size();
|
||||
const unsigned char* in = *input;
|
||||
int16_t* out = *output;
|
||||
|
||||
for (size_t chan = 0; chan < nChannels; chan++)
|
||||
{
|
||||
ADPCMChannelStatus& cs = ctx[chan];
|
||||
|
||||
// Bits 15-7 are the _top_ 9 bits of the 16-bit initial predictor value
|
||||
int predictor = sign_extend((in[0] << 8) | in[1], 16);
|
||||
int step_index = predictor & 0x7F;
|
||||
predictor &= ~0x7F;
|
||||
|
||||
in += 2;
|
||||
|
||||
if (cs.step_index == step_index)
|
||||
{
|
||||
int diff = predictor - cs.predictor;
|
||||
if (diff < 0x00) diff = -diff;
|
||||
if (diff > 0x7f) goto update;
|
||||
}
|
||||
else
|
||||
{
|
||||
update:
|
||||
cs.step_index = step_index;
|
||||
cs.predictor = predictor;
|
||||
}
|
||||
|
||||
if (cs.step_index > 88)
|
||||
throw std::invalid_argument("step_index[chan]>88!");
|
||||
|
||||
size_t pos = chan;
|
||||
for (int m = 0; m < 32; m++)
|
||||
{
|
||||
int byte = (uint8_t) (*in++);
|
||||
out[pos] = adpcm_ima_qt_expand_nibble(&cs, byte & 0x0F, 3);
|
||||
pos += nChannels;
|
||||
out[pos] = adpcm_ima_qt_expand_nibble(&cs, byte >> 4, 3);
|
||||
pos += nChannels;
|
||||
}
|
||||
}
|
||||
|
||||
*input = in;
|
||||
*output += 64 * nChannels;
|
||||
}
|
||||
|
||||
void Pomme::Sound::IMA4::Decode(
|
||||
const int nChannels,
|
||||
const std::span<const char> input,
|
||||
const std::span<char> output)
|
||||
{
|
||||
if (input.size() % 34 != 0)
|
||||
throw std::invalid_argument("odd input buffer size");
|
||||
|
||||
const size_t nChunks = input.size() / (34 * nChannels);
|
||||
const size_t nSamples = 64 * nChunks;
|
||||
|
||||
if (output.size() != nSamples * nChannels * 2)
|
||||
throw std::invalid_argument("incorrect output size");
|
||||
|
||||
const uint8_t* in = reinterpret_cast<const unsigned char*>(input.data());
|
||||
int16_t* out = reinterpret_cast<int16_t*>(output.data());
|
||||
std::vector<ADPCMChannelStatus> ctx(nChannels);
|
||||
|
||||
for (size_t chunk = 0; chunk < nChunks; chunk++)
|
||||
{
|
||||
DecodeIMA4Chunk(&in, &out, ctx);
|
||||
}
|
||||
|
||||
assert(reinterpret_cast<const char*>(in) == input.data() + input.size());
|
||||
assert(reinterpret_cast<char*>(out) == output.data() + output.size());
|
||||
}
|
229
src/Sound/MACE.cpp
Normal file
229
src/Sound/MACE.cpp
Normal file
@ -0,0 +1,229 @@
|
||||
// Adapted from ffmpeg
|
||||
|
||||
// ---- Begin ffmpeg copyright notices ----
|
||||
|
||||
/*
|
||||
* MACE decoder
|
||||
* Copyright (c) 2002 Laszlo Torok <torokl@alpha.dfmk.hu>
|
||||
*
|
||||
* This file is part of FFmpeg.
|
||||
*
|
||||
* FFmpeg is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* FFmpeg is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with FFmpeg; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
/*
|
||||
* Adapted to libavcodec by Francois Revol <revol@free.fr>
|
||||
* (removed 68k REG stuff, changed types, added some statics and consts,
|
||||
* libavcodec api, context stuff, interlaced stereo out).
|
||||
*/
|
||||
|
||||
// ---- End ffmpeg copyright notices ----
|
||||
|
||||
#include "PommeSound.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
static const int16_t MACEtab1[] = {-13, 8, 76, 222, 222, 76, 8, -13};
|
||||
|
||||
static const int16_t MACEtab3[] = {-18, 140, 140, -18};
|
||||
|
||||
static const int16_t MACEtab2[][4] = {
|
||||
{ 37, 116, 206, 330}, { 39, 121, 216, 346},
|
||||
{ 41, 127, 225, 361}, { 42, 132, 235, 377},
|
||||
{ 44, 137, 245, 392}, { 46, 144, 256, 410},
|
||||
{ 48, 150, 267, 428}, { 51, 157, 280, 449},
|
||||
{ 53, 165, 293, 470}, { 55, 172, 306, 490},
|
||||
{ 58, 179, 319, 511}, { 60, 187, 333, 534},
|
||||
{ 63, 195, 348, 557}, { 66, 205, 364, 583},
|
||||
{ 69, 214, 380, 609}, { 72, 223, 396, 635},
|
||||
{ 75, 233, 414, 663}, { 79, 244, 433, 694},
|
||||
{ 82, 254, 453, 725}, { 86, 265, 472, 756},
|
||||
{ 90, 278, 495, 792}, { 94, 290, 516, 826},
|
||||
{ 98, 303, 538, 862}, { 102, 316, 562, 901},
|
||||
{ 107, 331, 588, 942}, { 112, 345, 614, 983},
|
||||
{ 117, 361, 641, 1027}, { 122, 377, 670, 1074},
|
||||
{ 127, 394, 701, 1123}, { 133, 411, 732, 1172},
|
||||
{ 139, 430, 764, 1224}, { 145, 449, 799, 1280},
|
||||
{ 152, 469, 835, 1337}, { 159, 490, 872, 1397},
|
||||
{ 166, 512, 911, 1459}, { 173, 535, 951, 1523},
|
||||
{ 181, 558, 993, 1590}, { 189, 584, 1038, 1663},
|
||||
{ 197, 610, 1085, 1738}, { 206, 637, 1133, 1815},
|
||||
{ 215, 665, 1183, 1895}, { 225, 695, 1237, 1980},
|
||||
{ 235, 726, 1291, 2068}, { 246, 759, 1349, 2161},
|
||||
{ 257, 792, 1409, 2257}, { 268, 828, 1472, 2357},
|
||||
{ 280, 865, 1538, 2463}, { 293, 903, 1606, 2572},
|
||||
{ 306, 944, 1678, 2688}, { 319, 986, 1753, 2807},
|
||||
{ 334, 1030, 1832, 2933}, { 349, 1076, 1914, 3065},
|
||||
{ 364, 1124, 1999, 3202}, { 380, 1174, 2088, 3344},
|
||||
{ 398, 1227, 2182, 3494}, { 415, 1281, 2278, 3649},
|
||||
{ 434, 1339, 2380, 3811}, { 453, 1398, 2486, 3982},
|
||||
{ 473, 1461, 2598, 4160}, { 495, 1526, 2714, 4346},
|
||||
{ 517, 1594, 2835, 4540}, { 540, 1665, 2961, 4741},
|
||||
{ 564, 1740, 3093, 4953}, { 589, 1818, 3232, 5175},
|
||||
{ 615, 1898, 3375, 5405}, { 643, 1984, 3527, 5647},
|
||||
{ 671, 2072, 3683, 5898}, { 701, 2164, 3848, 6161},
|
||||
{ 733, 2261, 4020, 6438}, { 766, 2362, 4199, 6724},
|
||||
{ 800, 2467, 4386, 7024}, { 836, 2578, 4583, 7339},
|
||||
{ 873, 2692, 4786, 7664}, { 912, 2813, 5001, 8008},
|
||||
{ 952, 2938, 5223, 8364}, { 995, 3070, 5457, 8739},
|
||||
{ 1039, 3207, 5701, 9129}, { 1086, 3350, 5956, 9537},
|
||||
{ 1134, 3499, 6220, 9960}, { 1185, 3655, 6497, 10404},
|
||||
{ 1238, 3818, 6788, 10869}, { 1293, 3989, 7091, 11355},
|
||||
{ 1351, 4166, 7407, 11861}, { 1411, 4352, 7738, 12390},
|
||||
{ 1474, 4547, 8084, 12946}, { 1540, 4750, 8444, 13522},
|
||||
{ 1609, 4962, 8821, 14126}, { 1680, 5183, 9215, 14756},
|
||||
{ 1756, 5415, 9626, 15415}, { 1834, 5657, 10057, 16104},
|
||||
{ 1916, 5909, 10505, 16822}, { 2001, 6173, 10975, 17574},
|
||||
{ 2091, 6448, 11463, 18356}, { 2184, 6736, 11974, 19175},
|
||||
{ 2282, 7037, 12510, 20032}, { 2383, 7351, 13068, 20926},
|
||||
{ 2490, 7679, 13652, 21861}, { 2601, 8021, 14260, 22834},
|
||||
{ 2717, 8380, 14897, 23854}, { 2838, 8753, 15561, 24918},
|
||||
{ 2965, 9144, 16256, 26031}, { 3097, 9553, 16982, 27193},
|
||||
{ 3236, 9979, 17740, 28407}, { 3380, 10424, 18532, 29675},
|
||||
{ 3531, 10890, 19359, 31000}, { 3688, 11375, 20222, 32382},
|
||||
{ 3853, 11883, 21125, 32767}, { 4025, 12414, 22069, 32767},
|
||||
{ 4205, 12967, 23053, 32767}, { 4392, 13546, 24082, 32767},
|
||||
{ 4589, 14151, 25157, 32767}, { 4793, 14783, 26280, 32767},
|
||||
{ 5007, 15442, 27452, 32767}, { 5231, 16132, 28678, 32767},
|
||||
{ 5464, 16851, 29957, 32767}, { 5708, 17603, 31294, 32767},
|
||||
{ 5963, 18389, 32691, 32767}, { 6229, 19210, 32767, 32767},
|
||||
{ 6507, 20067, 32767, 32767}, { 6797, 20963, 32767, 32767},
|
||||
{ 7101, 21899, 32767, 32767}, { 7418, 22876, 32767, 32767},
|
||||
{ 7749, 23897, 32767, 32767}, { 8095, 24964, 32767, 32767},
|
||||
{ 8456, 26078, 32767, 32767}, { 8833, 27242, 32767, 32767},
|
||||
{ 9228, 28457, 32767, 32767}, { 9639, 29727, 32767, 32767}
|
||||
};
|
||||
|
||||
static const int16_t MACEtab4[][2] = {
|
||||
{ 64, 216}, { 67, 226}, { 70, 236}, { 74, 246},
|
||||
{ 77, 257}, { 80, 268}, { 84, 280}, { 88, 294},
|
||||
{ 92, 307}, { 96, 321}, { 100, 334}, { 104, 350},
|
||||
{ 109, 365}, { 114, 382}, { 119, 399}, { 124, 416},
|
||||
{ 130, 434}, { 136, 454}, { 142, 475}, { 148, 495},
|
||||
{ 155, 519}, { 162, 541}, { 169, 564}, { 176, 590},
|
||||
{ 185, 617}, { 193, 644}, { 201, 673}, { 210, 703},
|
||||
{ 220, 735}, { 230, 767}, { 240, 801}, { 251, 838},
|
||||
{ 262, 876}, { 274, 914}, { 286, 955}, { 299, 997},
|
||||
{ 312, 1041}, { 326, 1089}, { 341, 1138}, { 356, 1188},
|
||||
{ 372, 1241}, { 388, 1297}, { 406, 1354}, { 424, 1415},
|
||||
{ 443, 1478}, { 462, 1544}, { 483, 1613}, { 505, 1684},
|
||||
{ 527, 1760}, { 551, 1838}, { 576, 1921}, { 601, 2007},
|
||||
{ 628, 2097}, { 656, 2190}, { 686, 2288}, { 716, 2389},
|
||||
{ 748, 2496}, { 781, 2607}, { 816, 2724}, { 853, 2846},
|
||||
{ 891, 2973}, { 930, 3104}, { 972, 3243}, { 1016, 3389},
|
||||
{ 1061, 3539}, { 1108, 3698}, { 1158, 3862}, { 1209, 4035},
|
||||
{ 1264, 4216}, { 1320, 4403}, { 1379, 4599}, { 1441, 4806},
|
||||
{ 1505, 5019}, { 1572, 5244}, { 1642, 5477}, { 1715, 5722},
|
||||
{ 1792, 5978}, { 1872, 6245}, { 1955, 6522}, { 2043, 6813},
|
||||
{ 2134, 7118}, { 2229, 7436}, { 2329, 7767}, { 2432, 8114},
|
||||
{ 2541, 8477}, { 2655, 8854}, { 2773, 9250}, { 2897, 9663},
|
||||
{ 3026, 10094}, { 3162, 10546}, { 3303, 11016}, { 3450, 11508},
|
||||
{ 3604, 12020}, { 3765, 12556}, { 3933, 13118}, { 4108, 13703},
|
||||
{ 4292, 14315}, { 4483, 14953}, { 4683, 15621}, { 4892, 16318},
|
||||
{ 5111, 17046}, { 5339, 17807}, { 5577, 18602}, { 5826, 19433},
|
||||
{ 6086, 20300}, { 6358, 21205}, { 6642, 22152}, { 6938, 23141},
|
||||
{ 7248, 24173}, { 7571, 25252}, { 7909, 26380}, { 8262, 27557},
|
||||
{ 8631, 28786}, { 9016, 30072}, { 9419, 31413}, { 9839, 32767},
|
||||
{ 10278, 32767}, { 10737, 32767}, { 11216, 32767}, { 11717, 32767},
|
||||
{ 12240, 32767}, { 12786, 32767}, { 13356, 32767}, { 13953, 32767},
|
||||
{ 14576, 32767}, { 15226, 32767}, { 15906, 32767}, { 16615, 32767}
|
||||
};
|
||||
|
||||
static const struct
|
||||
{
|
||||
const int16_t *tab1;
|
||||
const int16_t *tab2;
|
||||
int stride;
|
||||
} tabs[] = {
|
||||
{MACEtab1, &MACEtab2[0][0], 4},
|
||||
{MACEtab3, &MACEtab4[0][0], 2},
|
||||
{MACEtab1, &MACEtab2[0][0], 4}
|
||||
};
|
||||
|
||||
#define QT_8S_2_16S(x) (((x) & 0xFF00) | (((x) >> 8) & 0xFF))
|
||||
|
||||
struct ChannelData
|
||||
{
|
||||
int16_t index, factor, prev2, previous, level;
|
||||
};
|
||||
|
||||
struct MACEContext
|
||||
{
|
||||
ChannelData chd[2];
|
||||
};
|
||||
|
||||
static inline int16_t mace_broken_clip_int16(int n)
|
||||
{
|
||||
if (n > 32767)
|
||||
return 32767;
|
||||
else if (n < -32768)
|
||||
return -32767;
|
||||
else
|
||||
return n;
|
||||
}
|
||||
|
||||
static int16_t read_table(ChannelData* chd, uint8_t val, int tab_idx)
|
||||
{
|
||||
int16_t current;
|
||||
|
||||
if (val < tabs[tab_idx].stride)
|
||||
current = tabs[tab_idx].tab2[((chd->index & 0x7f0) >> 4) * tabs[tab_idx].stride + val];
|
||||
else
|
||||
current = - 1 - tabs[tab_idx].tab2[((chd->index & 0x7f0) >> 4)*tabs[tab_idx].stride + 2*tabs[tab_idx].stride-val-1];
|
||||
|
||||
if (( chd->index += tabs[tab_idx].tab1[val]-(chd->index >> 5) ) < 0)
|
||||
chd->index = 0;
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
void Pomme::Sound::MACE::Decode(
|
||||
const int nChannels,
|
||||
const std::span<const char> input,
|
||||
const std::span<char> output)
|
||||
{
|
||||
if (input.size() % (nChannels * 2) != 0)
|
||||
throw std::invalid_argument("odd input buffer size");
|
||||
|
||||
size_t nSamples = 3 * int(input.size()) / nChannels;
|
||||
|
||||
if (output.size() != nSamples * nChannels * 2)
|
||||
throw std::invalid_argument("incorrect output size");
|
||||
|
||||
int16_t* out = reinterpret_cast<int16_t*>(output.data());
|
||||
|
||||
MACEContext ctx = {};
|
||||
|
||||
for (int chan = 0; chan < nChannels; chan++)
|
||||
for (size_t j = 0; j < input.size() / (nChannels * 2); j++)
|
||||
for (int k = 0; k < 2; k++)
|
||||
{
|
||||
uint8_t pkt = (uint8_t)input[(chan * 2) + (j * nChannels * 2) + k];
|
||||
|
||||
int val2[3] = { pkt & 7, (pkt >> 3) & 3, pkt >> 5 };
|
||||
|
||||
for (int l = 0; l < 3; l++)
|
||||
{
|
||||
int16_t current = read_table(&ctx.chd[chan], (uint8_t)val2[l], l);
|
||||
current = mace_broken_clip_int16(current + ctx.chd[chan].level);
|
||||
ctx.chd[chan].level = current - (current >> 3);
|
||||
current = QT_8S_2_16S(current);
|
||||
*out = current;
|
||||
out++;
|
||||
}
|
||||
}
|
||||
|
||||
assert(reinterpret_cast<char*>(out) == output.data() + output.size());
|
||||
}
|
782
src/Sound/SoundManager.cpp
Normal file
782
src/Sound/SoundManager.cpp
Normal file
@ -0,0 +1,782 @@
|
||||
#include "Pomme.h"
|
||||
#include "PommeFiles.h"
|
||||
#include "Sound/cmixer.h"
|
||||
#include "PommeSound.h"
|
||||
#include "Utilities/BigEndianIStream.h"
|
||||
#include "Utilities/memstream.h"
|
||||
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
|
||||
#define LOG POMME_GENLOG(POMME_DEBUG_SOUND, "SOUN")
|
||||
#define LOG_NOPREFIX POMME_GENLOG_NOPREFIX(POMME_DEBUG_SOUND)
|
||||
|
||||
static struct ChannelImpl* headChan = nullptr;
|
||||
static int nManagedChans = 0;
|
||||
static double midiNoteFrequencies[128];
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Cookie-cutter sound command list.
|
||||
// Used to generate 'snd ' resources.
|
||||
|
||||
static const uint8_t kSampledSoundCommandList[20] = {
|
||||
0,1, // format
|
||||
0,1, // modifier count
|
||||
0,5, // modifier "sampled synth"
|
||||
0,0,0,0, // init bits
|
||||
0,1, // command count
|
||||
0x80,soundCmd, // command soundCmd (high bit set)
|
||||
0,0, // param1
|
||||
0,0,0,20, // param2 (offset)
|
||||
// Sample data follows
|
||||
};
|
||||
|
||||
constexpr int kSampledSoundCommandListLength = sizeof(kSampledSoundCommandList);
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// 'snd ' resource header
|
||||
|
||||
struct SampledSoundHeader
|
||||
{
|
||||
UInt32 zero;
|
||||
union // the meaning of union this is decided by the encoding type
|
||||
{
|
||||
SInt32 stdSH_nBytes;
|
||||
SInt32 cmpSH_nChannels;
|
||||
SInt32 extSH_nChannels;
|
||||
SInt32 nativeSH_nBytes;
|
||||
};
|
||||
Fixed fixedSampleRate;
|
||||
UInt32 loopStart;
|
||||
UInt32 loopEnd;
|
||||
Byte encoding;
|
||||
Byte baseFrequency; // 0-127, see Table 2-2, IM:S:2-43
|
||||
|
||||
unsigned int sampleRate() const
|
||||
{
|
||||
return (static_cast<unsigned int>(fixedSampleRate) >> 16) & 0xFFFF;
|
||||
}
|
||||
};
|
||||
|
||||
static_assert(sizeof(SampledSoundHeader) >= 22 && sizeof(SampledSoundHeader) <= 24,
|
||||
"unexpected SampledSoundHeader size");
|
||||
|
||||
constexpr int kSampledSoundHeaderLength = 22;
|
||||
constexpr const char* kSampledSoundHeaderPackFormat = "IiIIIbb";
|
||||
|
||||
enum SampledSoundEncoding
|
||||
{
|
||||
stdSH = 0x00,
|
||||
nativeSH_mono16 = 0x10, // pomme extension
|
||||
nativeSH_stereo16 = 0x11, // pomme extension
|
||||
cmpSH = 0xFE,
|
||||
extSH = 0xFF,
|
||||
};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Internal channel info
|
||||
|
||||
struct ChannelImpl
|
||||
{
|
||||
private:
|
||||
ChannelImpl* prev;
|
||||
ChannelImpl* next;
|
||||
|
||||
public:
|
||||
SndChannelPtr macChannel;
|
||||
|
||||
bool macChannelStructAllocatedByPomme;
|
||||
cmixer::WavStream source;
|
||||
FilePlayCompletionProcPtr onComplete;
|
||||
|
||||
Byte baseNote = kMiddleC;
|
||||
Byte playbackNote = kMiddleC;
|
||||
double pitchMult = 1;
|
||||
bool temporaryPause = false;
|
||||
|
||||
ChannelImpl(SndChannelPtr _macChannel, bool transferMacChannelOwnership)
|
||||
: macChannel(_macChannel)
|
||||
, macChannelStructAllocatedByPomme(transferMacChannelOwnership)
|
||||
, source()
|
||||
, onComplete(nullptr)
|
||||
, baseNote(kMiddleC)
|
||||
, playbackNote(kMiddleC)
|
||||
, pitchMult(1.0)
|
||||
{
|
||||
macChannel->firstMod = (Ptr) this;
|
||||
|
||||
Link(); // Link chan into our list of managed chans
|
||||
}
|
||||
|
||||
~ChannelImpl()
|
||||
{
|
||||
Unlink();
|
||||
|
||||
macChannel->firstMod = nullptr;
|
||||
|
||||
if (macChannelStructAllocatedByPomme)
|
||||
{
|
||||
delete macChannel;
|
||||
}
|
||||
}
|
||||
|
||||
void Recycle()
|
||||
{
|
||||
source.Clear();
|
||||
baseNote = kMiddleC;
|
||||
playbackNote = kMiddleC;
|
||||
pitchMult = 1;
|
||||
temporaryPause = false;
|
||||
}
|
||||
|
||||
void ApplyPitch()
|
||||
{
|
||||
if (!source.active)
|
||||
{
|
||||
return;
|
||||
}
|
||||
double baseFreq = midiNoteFrequencies[baseNote];
|
||||
double playbackFreq = midiNoteFrequencies[playbackNote];
|
||||
source.SetPitch(pitchMult * playbackFreq / baseFreq);
|
||||
}
|
||||
|
||||
ChannelImpl* GetPrev() const
|
||||
{
|
||||
return prev;
|
||||
}
|
||||
|
||||
ChannelImpl* GetNext() const
|
||||
{
|
||||
return next;
|
||||
}
|
||||
|
||||
void SetPrev(ChannelImpl* newPrev)
|
||||
{
|
||||
prev = newPrev;
|
||||
}
|
||||
|
||||
void SetNext(ChannelImpl* newNext)
|
||||
{
|
||||
next = newNext;
|
||||
macChannel->nextChan = newNext ? newNext->macChannel : nullptr;
|
||||
}
|
||||
|
||||
void Link()
|
||||
{
|
||||
if (!headChan)
|
||||
{
|
||||
SetNext(nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(nullptr == headChan->GetPrev());
|
||||
headChan->SetPrev(this);
|
||||
SetNext(headChan);
|
||||
}
|
||||
|
||||
headChan = this;
|
||||
SetPrev(nullptr);
|
||||
|
||||
nManagedChans++;
|
||||
}
|
||||
|
||||
void Unlink()
|
||||
{
|
||||
if (headChan == this)
|
||||
{
|
||||
headChan = GetNext();
|
||||
}
|
||||
|
||||
if (nullptr != GetPrev())
|
||||
{
|
||||
GetPrev()->SetNext(GetNext());
|
||||
}
|
||||
|
||||
if (nullptr != GetNext())
|
||||
{
|
||||
GetNext()->SetPrev(GetPrev());
|
||||
}
|
||||
|
||||
SetPrev(nullptr);
|
||||
SetNext(nullptr);
|
||||
|
||||
nManagedChans--;
|
||||
}
|
||||
};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Internal utilities
|
||||
|
||||
static inline ChannelImpl& GetImpl(SndChannelPtr chan)
|
||||
{
|
||||
return *(ChannelImpl*) chan->firstMod;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// MIDI note utilities
|
||||
|
||||
// Note: these names are according to IM:S:2-43.
|
||||
// These names won't match real-world names.
|
||||
// E.g. for note 67 (A 440Hz), this will return "A6", whereas the real-world
|
||||
// convention for that note is "A4".
|
||||
static std::string GetMidiNoteName(int i)
|
||||
{
|
||||
static const char* gamme[12] = {"A", "A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#"};
|
||||
|
||||
int octave = 1 + (i + 3) / 12;
|
||||
int semitonesFromA = (i + 3) % 12;
|
||||
|
||||
std::stringstream ss;
|
||||
ss << gamme[semitonesFromA] << octave;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
static void InitMidiFrequencyTable()
|
||||
{
|
||||
// powers of twelfth root of two
|
||||
double gamme[12];
|
||||
gamme[0] = 1.0;
|
||||
for (int i = 1; i < 12; i++)
|
||||
{
|
||||
gamme[i] = gamme[i - 1] * 1.059630943592952646;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 128; i++)
|
||||
{
|
||||
int octave = 1 + (i + 3) / 12; // A440 and middle C are in octave 7
|
||||
int semitone = (i + 3) % 12; // halfsteps up from A in current octave
|
||||
if (octave < 7)
|
||||
midiNoteFrequencies[i] = gamme[semitone] * 440.0 / (1 << (7 - octave)); // 440/(2**octaveDiff)
|
||||
else
|
||||
midiNoteFrequencies[i] = gamme[semitone] * 440.0 * (1 << (octave - 7)); // 440*(2**octaveDiff)
|
||||
//LOG << i << "\t" << GetMidiNoteName(i) << "\t" << midiNoteFrequencies[i] << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Sound Manager
|
||||
|
||||
OSErr GetDefaultOutputVolume(long* stereoLevel)
|
||||
{
|
||||
unsigned short g = (unsigned short) (cmixer::GetMasterGain() * 256.0);
|
||||
*stereoLevel = (g << 16) | g;
|
||||
return noErr;
|
||||
}
|
||||
|
||||
// See IM:S:2-139, "Controlling Volume Levels".
|
||||
OSErr SetDefaultOutputVolume(long stereoLevel)
|
||||
{
|
||||
unsigned short left = 0xFFFF & stereoLevel;
|
||||
unsigned short right = 0xFFFF & (stereoLevel >> 16);
|
||||
if (right != left)
|
||||
TODOMINOR2("setting different volumes for left & right is not implemented");
|
||||
LOG << left / 256.0 << "\n";
|
||||
cmixer::SetMasterGain(left / 256.0);
|
||||
return noErr;
|
||||
}
|
||||
|
||||
// IM:S:2-127
|
||||
OSErr SndNewChannel(SndChannelPtr* macChanPtr, short synth, long init, SndCallBackProcPtr userRoutine)
|
||||
{
|
||||
if (synth != sampledSynth)
|
||||
{
|
||||
TODO2("unimplemented synth type " << sampledSynth);
|
||||
return unimpErr;
|
||||
}
|
||||
|
||||
//---------------------------
|
||||
// Allocate Mac channel record if needed
|
||||
|
||||
bool transferMacChannelOwnership = false;
|
||||
|
||||
if (!*macChanPtr)
|
||||
{
|
||||
*macChanPtr = new SndChannel;
|
||||
(**macChanPtr) = {};
|
||||
transferMacChannelOwnership = true;
|
||||
}
|
||||
|
||||
//---------------------------
|
||||
// Set up
|
||||
|
||||
(**macChanPtr).callBack = userRoutine;
|
||||
new ChannelImpl(*macChanPtr, transferMacChannelOwnership);
|
||||
|
||||
//---------------------------
|
||||
// Done
|
||||
|
||||
LOG << "New channel created, init = $" << std::hex << init << std::dec << ", total managed channels = " << nManagedChans << "\n";
|
||||
|
||||
return noErr;
|
||||
}
|
||||
|
||||
// IM:S:2-129
|
||||
OSErr SndDisposeChannel(SndChannelPtr macChanPtr, Boolean quietNow)
|
||||
{
|
||||
if (!quietNow)
|
||||
{
|
||||
TODO2("SndDisposeChannel: quietNow == false is not implemented");
|
||||
}
|
||||
delete &GetImpl(macChanPtr);
|
||||
return noErr;
|
||||
}
|
||||
|
||||
OSErr SndChannelStatus(SndChannelPtr chan, short theLength, SCStatusPtr theStatus)
|
||||
{
|
||||
*theStatus = {};
|
||||
|
||||
auto& source = GetImpl(chan).source;
|
||||
|
||||
theStatus->scChannelPaused = source.GetState() == cmixer::CM_STATE_PAUSED;
|
||||
theStatus->scChannelBusy = source.GetState() == cmixer::CM_STATE_PLAYING;
|
||||
|
||||
return noErr;
|
||||
}
|
||||
|
||||
static void ProcessSoundCmd(SndChannelPtr chan, const Ptr sndhdr)
|
||||
{
|
||||
// Install a sampled sound as a voice in a channel. If the high bit of the
|
||||
// command is set, param2 is interpreted as an offset from the beginning of
|
||||
// the 'snd ' resource containing the command to the sound header. If the
|
||||
// high bit is not set, param2 is interpreted as a pointer to the sound
|
||||
// header.
|
||||
|
||||
auto& impl = GetImpl(chan);
|
||||
|
||||
impl.Recycle();
|
||||
|
||||
// PACKED RECORD
|
||||
memstream headerInput(sndhdr, kSampledSoundHeaderLength + 42);
|
||||
Pomme::BigEndianIStream f(headerInput);
|
||||
|
||||
SampledSoundHeader sh;
|
||||
f.Read(reinterpret_cast<char*>(&sh), kSampledSoundHeaderLength);
|
||||
ByteswapStructs(kSampledSoundHeaderPackFormat, kSampledSoundHeaderLength, 1, reinterpret_cast<char*>(&sh));
|
||||
|
||||
if (sh.zero != 0)
|
||||
{
|
||||
// The first field can be a pointer to the sampled-sound data.
|
||||
// In practice it's always gonna be 0.
|
||||
TODOFATAL2("expected 0 at the beginning of an snd");
|
||||
}
|
||||
|
||||
int sampleRate = sh.sampleRate();
|
||||
impl.baseNote = sh.baseFrequency;
|
||||
|
||||
LOG << sampleRate << "Hz, " << GetMidiNoteName(sh.baseFrequency) << ", loop " << sh.loopStart << "->" << sh.loopEnd << ", ";
|
||||
|
||||
switch (sh.encoding)
|
||||
{
|
||||
case 0x00: // stdSH - standard sound header - IM:S:2-104
|
||||
{
|
||||
// noncompressed sample data (8-bit mono) from this point on
|
||||
char* here = sndhdr + f.Tell();
|
||||
impl.source.Init(sampleRate, 8, 1, false, std::span<char>(here, sh.stdSH_nBytes));
|
||||
LOG_NOPREFIX << "stdSH: 8-bit mono, " << sh.stdSH_nBytes << " frames\n";
|
||||
break;
|
||||
}
|
||||
|
||||
case nativeSH_mono16:
|
||||
case nativeSH_stereo16:
|
||||
{
|
||||
int nChannels = sh.encoding == nativeSH_mono16 ? 1 : 2;
|
||||
char* here = sndhdr + f.Tell();
|
||||
auto span = std::span(here, sh.nativeSH_nBytes);
|
||||
impl.source.Init(sampleRate, 16, nChannels, false, span);
|
||||
LOG_NOPREFIX << "nativeSH\n";
|
||||
break;
|
||||
}
|
||||
|
||||
case 0xFF: // extSH - extended sound header - IM:S:2-106
|
||||
{
|
||||
// fields that follow baseFrequency
|
||||
SInt32 nFrames = f.Read<SInt32>();
|
||||
f.Skip(22);
|
||||
SInt16 bitDepth = f.Read<SInt16>();
|
||||
f.Skip(14);
|
||||
|
||||
int nBytes = sh.extSH_nChannels * nFrames * bitDepth / 8;
|
||||
|
||||
// noncompressed sample data (big endian) from this point on
|
||||
char* here = sndhdr + f.Tell();
|
||||
|
||||
LOG_NOPREFIX << "extSH: " << bitDepth << "-bit " << (sh.extSH_nChannels == 1? "mono": "stereo") << ", " << nFrames << " frames\n";
|
||||
|
||||
impl.source.Init(sampleRate, bitDepth, sh.extSH_nChannels, true, std::span<char>(here, nBytes));
|
||||
break;
|
||||
}
|
||||
|
||||
case 0xFE: // cmpSH - compressed sound header - IM:S:2-108
|
||||
{
|
||||
// fields that follow baseFrequency
|
||||
SInt32 nCompressedChunks = f.Read<SInt32>();
|
||||
f.Skip(14);
|
||||
OSType format = f.Read<OSType>();
|
||||
f.Skip(20);
|
||||
|
||||
if (format == 0)
|
||||
{
|
||||
// Assume MACE-3. It should've been set in the init options in the snd pre-header,
|
||||
// but Nanosaur doesn't actually init the sound channels for MACE-3. So I guess the Mac
|
||||
// assumes by default that any unspecified compression is MACE-3.
|
||||
// If it wasn't MACE-3, it would've been caught by GetSoundHeaderOffset.
|
||||
format = 'MAC3';
|
||||
}
|
||||
|
||||
// compressed sample data from this point on
|
||||
char* here = sndhdr + f.Tell();
|
||||
|
||||
std::cout << "cmpSH: " << Pomme::FourCCString(format) << " " << (sh.cmpSH_nChannels == 1 ? "mono" : "stereo") << ", " << nCompressedChunks << " ck\n";
|
||||
|
||||
std::unique_ptr<Pomme::Sound::Codec> codec = Pomme::Sound::GetCodec(format);
|
||||
|
||||
// Decompress
|
||||
int nBytesIn = sh.cmpSH_nChannels * nCompressedChunks * codec->BytesPerPacket();
|
||||
int nBytesOut = sh.cmpSH_nChannels * nCompressedChunks * codec->SamplesPerPacket() * 2;
|
||||
|
||||
auto spanIn = std::span(here, nBytesIn);
|
||||
auto spanOut = impl.source.GetBuffer(nBytesOut);
|
||||
|
||||
codec->Decode(sh.cmpSH_nChannels, spanIn, spanOut);
|
||||
impl.source.Init(sampleRate, 16, sh.cmpSH_nChannels, false, spanOut);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
TODOFATAL2("unsupported snd header encoding " << (int)sh.encoding);
|
||||
}
|
||||
|
||||
if (sh.loopEnd - sh.loopStart <= 1)
|
||||
{
|
||||
// don't loop
|
||||
}
|
||||
else if (sh.loopStart == 0)
|
||||
{
|
||||
impl.source.SetLoop(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
TODO2("looping on a portion of the snd isn't supported yet");
|
||||
impl.source.SetLoop(true);
|
||||
}
|
||||
|
||||
impl.ApplyPitch();
|
||||
impl.temporaryPause = false;
|
||||
impl.source.Play();
|
||||
}
|
||||
|
||||
OSErr SndDoImmediate(SndChannelPtr chan, const SndCommand* cmd)
|
||||
{
|
||||
auto& impl = GetImpl(chan);
|
||||
|
||||
// Discard the high bit of the command (it indicates whether an 'snd ' resource has associated data).
|
||||
switch (cmd->cmd & 0x7FFF)
|
||||
{
|
||||
case nullCmd:
|
||||
break;
|
||||
|
||||
case flushCmd:
|
||||
// flushCmd is a no-op for now because we don't support queuing commands--
|
||||
// all commands are executed immediately in the current implementation.
|
||||
break;
|
||||
|
||||
case quietCmd:
|
||||
impl.source.Stop();
|
||||
break;
|
||||
|
||||
case soundCmd:
|
||||
ProcessSoundCmd(chan, cmd->ptr);
|
||||
break;
|
||||
|
||||
case ampCmd:
|
||||
impl.source.SetGain(cmd->param1 / 255.0);
|
||||
LOG << "ampCmd " << impl.source.gain << "\n";
|
||||
break;
|
||||
|
||||
case freqCmd:
|
||||
LOG << "freqCmd " << cmd->param2 << " " << GetMidiNoteName(cmd->param2) << " " << midiNoteFrequencies[cmd->param2] << "\n";
|
||||
impl.playbackNote = Byte(cmd->param2);
|
||||
impl.ApplyPitch();
|
||||
break;
|
||||
|
||||
case rateCmd:
|
||||
// IM:S says it's a fixed-point multiplier of 22KHz, but Nanosaur uses rate "1" everywhere,
|
||||
// even for sounds sampled at 44Khz, so I'm treating it as just a pitch multiplier.
|
||||
impl.pitchMult = cmd->param2 / 65536.0;
|
||||
impl.ApplyPitch();
|
||||
break;
|
||||
|
||||
case pommeSetLoopCmd:
|
||||
impl.source.SetLoop(cmd->param1);
|
||||
break;
|
||||
|
||||
default:
|
||||
TODOMINOR2(cmd->cmd << "(" << cmd->param1 << "," << cmd->param2 << ")");
|
||||
}
|
||||
|
||||
return noErr;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static void Expect(const T a, const T b, const char* msg)
|
||||
{
|
||||
if (a != b)
|
||||
throw std::runtime_error(msg);
|
||||
}
|
||||
|
||||
// IM:S:2-58 "MyGetSoundHeaderOffset"
|
||||
OSErr GetSoundHeaderOffset(SndListHandle sndHandle, long* offset)
|
||||
{
|
||||
memstream sndStream((Ptr) *sndHandle, GetHandleSize((Handle) sndHandle));
|
||||
Pomme::BigEndianIStream f(sndStream);
|
||||
|
||||
// Skip everything before sound commands
|
||||
Expect<SInt16>(1, f.Read<SInt16>(), "'snd ' format");
|
||||
Expect<SInt16>(1, f.Read<SInt16>(), "'snd ' modifier count");
|
||||
Expect<SInt16>(5, f.Read<SInt16>(), "'snd ' sampledSynth");
|
||||
UInt32 initBits = f.Read<UInt32>();
|
||||
|
||||
if (initBits & initMACE6)
|
||||
TODOFATAL2("MACE-6 not supported yet");
|
||||
|
||||
SInt16 nCmds = f.Read<SInt16>();
|
||||
//LOG << nCmds << " commands\n";
|
||||
for (; nCmds >= 1; nCmds--)
|
||||
{
|
||||
UInt16 cmd = f.Read<UInt16>();
|
||||
f.Skip(2); // SInt16 param1
|
||||
SInt32 param2 = f.Read<SInt32>();
|
||||
cmd &= 0x7FFF; // See IM:S:2-75
|
||||
// When a sound command contained in an 'snd ' resource has associated sound data,
|
||||
// the high bit of the command is set. This changes the meaning of the param2 field of the
|
||||
// command from a pointer to a location in RAM to an offset value that specifies the offset
|
||||
// in bytes from the resource's beginning to the location of the associated sound data (such
|
||||
// as a sampled sound header).
|
||||
if (cmd == bufferCmd || cmd == soundCmd)
|
||||
{
|
||||
*offset = param2;
|
||||
return noErr;
|
||||
}
|
||||
}
|
||||
|
||||
LOG << "didn't find offset in snd resource\n";
|
||||
return badFormat;
|
||||
}
|
||||
|
||||
OSErr SndStartFilePlay(
|
||||
SndChannelPtr chan,
|
||||
short fRefNum,
|
||||
short resNum,
|
||||
long bufferSize,
|
||||
Ptr theBuffer,
|
||||
/*AudioSelectionPtr*/ void* theSelection,
|
||||
FilePlayCompletionUPP theCompletion,
|
||||
Boolean async)
|
||||
{
|
||||
if (resNum != 0)
|
||||
{
|
||||
TODO2("playing snd resource not implemented yet, resource " << resNum);
|
||||
return unimpErr;
|
||||
}
|
||||
|
||||
if (!chan)
|
||||
{
|
||||
if (async) // async requires passing in a channel
|
||||
return badChannel;
|
||||
TODO2("nullptr chan for sync play, check IM:S:1-37");
|
||||
return unimpErr;
|
||||
}
|
||||
|
||||
if (theSelection)
|
||||
{
|
||||
TODO2("audio selection record not implemented");
|
||||
return unimpErr;
|
||||
}
|
||||
|
||||
auto& impl = GetImpl(chan);
|
||||
impl.Recycle();
|
||||
|
||||
auto& stream = Pomme::Files::GetStream(fRefNum);
|
||||
// Rewind -- the file might've been fully played already and we might just be trying to loop it
|
||||
stream.seekg(0, std::ios::beg);
|
||||
Pomme::Sound::ReadAIFF(stream, impl.source);
|
||||
|
||||
if (theCompletion)
|
||||
{
|
||||
impl.source.onComplete = [=]() { theCompletion(chan); };
|
||||
}
|
||||
|
||||
impl.temporaryPause = false;
|
||||
impl.source.Play();
|
||||
|
||||
if (!async)
|
||||
{
|
||||
while (impl.source.GetState() != cmixer::CM_STATE_STOPPED)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
}
|
||||
theCompletion(chan);
|
||||
impl.Recycle();
|
||||
return noErr;
|
||||
}
|
||||
|
||||
return noErr;
|
||||
}
|
||||
|
||||
OSErr SndPauseFilePlay(SndChannelPtr chan)
|
||||
{
|
||||
// TODO: check that chan is being used for play from disk
|
||||
GetImpl(chan).source.TogglePause();
|
||||
return noErr;
|
||||
}
|
||||
|
||||
OSErr SndStopFilePlay(SndChannelPtr chan, Boolean quietNow)
|
||||
{
|
||||
// TODO: check that chan is being used for play from disk
|
||||
if (!quietNow)
|
||||
TODO2("quietNow==false not supported yet, sound will be cut off immediately instead");
|
||||
GetImpl(chan).source.Stop();
|
||||
return noErr;
|
||||
}
|
||||
|
||||
NumVersion SndSoundManagerVersion()
|
||||
{
|
||||
NumVersion v = {};
|
||||
v.majorRev = 3;
|
||||
v.minorAndBugRev = 9;
|
||||
v.stage = 0x80;
|
||||
v.nonRelRev = 0;
|
||||
return v;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Extension: decompress
|
||||
|
||||
Boolean Pomme_DecompressSoundResource(SndListHandle* sndHandlePtr, long* offsetToHeader)
|
||||
{
|
||||
// Prep the BE reader on the header.
|
||||
Ptr sndhdr = (Ptr) (**sndHandlePtr) + *offsetToHeader;
|
||||
memstream headerInput(sndhdr, kSampledSoundHeaderLength + 42);
|
||||
Pomme::BigEndianIStream f(headerInput);
|
||||
|
||||
// Read in SampledSoundHeader and unpack it.
|
||||
SampledSoundHeader sh;
|
||||
f.Read(reinterpret_cast<char*>(&sh), kSampledSoundHeaderLength);
|
||||
ByteswapStructs(kSampledSoundHeaderPackFormat, kSampledSoundHeaderLength, 1, reinterpret_cast<char*>(&sh));
|
||||
|
||||
// We only handle cmpSH (compressed) 'snd ' resources.
|
||||
if (sh.encoding != cmpSH)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Fields that follow SampledSoundHeader when the encoding is cmpSH.
|
||||
const auto nCompressedChunks = f.Read<SInt32>();
|
||||
f.Skip(14);
|
||||
const auto format = f.Read<OSType>();
|
||||
f.Skip(20);
|
||||
|
||||
// Compressed sample data in the input stream from this point on.
|
||||
|
||||
const char* here = sndhdr + f.Tell();
|
||||
|
||||
int outInitialSize = kSampledSoundCommandListLength + kSampledSoundHeaderLength;
|
||||
|
||||
std::unique_ptr<Pomme::Sound::Codec> codec = Pomme::Sound::GetCodec(format);
|
||||
|
||||
// Decompress
|
||||
const int nBytesIn = sh.cmpSH_nChannels * nCompressedChunks * codec->BytesPerPacket();
|
||||
const int nBytesOut = sh.cmpSH_nChannels * nCompressedChunks * codec->SamplesPerPacket() * 2;
|
||||
SndListHandle outHandle = (SndListHandle) NewHandle(outInitialSize + nBytesOut);
|
||||
auto spanIn = std::span(here, nBytesIn);
|
||||
auto spanOut = std::span((char*) *outHandle + outInitialSize, nBytesOut);
|
||||
codec->Decode(sh.cmpSH_nChannels, spanIn, spanOut);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Now we have the PCM data.
|
||||
// Put the output 'snd ' resource together.
|
||||
|
||||
SampledSoundHeader shOut = sh;
|
||||
shOut.zero = 0;
|
||||
shOut.encoding = sh.cmpSH_nChannels == 2 ? nativeSH_stereo16 : nativeSH_mono16;
|
||||
shOut.nativeSH_nBytes = nBytesOut;
|
||||
ByteswapStructs(kSampledSoundHeaderPackFormat, kSampledSoundHeaderLength, 1, reinterpret_cast<char*>(&shOut));
|
||||
|
||||
memcpy(*outHandle, kSampledSoundCommandList, kSampledSoundCommandListLength);
|
||||
memcpy((char*) *outHandle + kSampledSoundCommandListLength, &shOut, kSampledSoundHeaderLength);
|
||||
|
||||
// Nuke compressed sound handle, replace it with the decopmressed one we've just created
|
||||
DisposeHandle((Handle) *sndHandlePtr);
|
||||
*sndHandlePtr = outHandle;
|
||||
*offsetToHeader = kSampledSoundCommandListLength;
|
||||
|
||||
long offsetCheck = 0;
|
||||
OSErr err = GetSoundHeaderOffset(outHandle, &offsetCheck);
|
||||
if (err != noErr || offsetCheck != kSampledSoundCommandListLength)
|
||||
{
|
||||
throw std::runtime_error("Incorrect decompressed sound header offset");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Extension: pause/unpause looping channels
|
||||
|
||||
void Pomme_PauseLoopingChannels(Boolean pause)
|
||||
{
|
||||
for (auto* chan = headChan; chan; chan = chan->GetNext())
|
||||
{
|
||||
auto& source = chan->source;
|
||||
if (pause && source.state == cmixer::CM_STATE_PLAYING && !chan->temporaryPause)
|
||||
{
|
||||
source.Pause();
|
||||
chan->temporaryPause = true;
|
||||
}
|
||||
else if (!pause && source.state == cmixer::CM_STATE_PAUSED && chan->temporaryPause)
|
||||
{
|
||||
source.Play();
|
||||
chan->temporaryPause = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Init Sound Manager
|
||||
|
||||
void Pomme::Sound::Init()
|
||||
{
|
||||
InitMidiFrequencyTable();
|
||||
cmixer::InitWithSDL();
|
||||
}
|
||||
|
||||
void Pomme::Sound::Shutdown()
|
||||
{
|
||||
cmixer::ShutdownWithSDL();
|
||||
while (headChan)
|
||||
{
|
||||
SndDisposeChannel(headChan->macChannel, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::unique_ptr<Pomme::Sound::Codec> Pomme::Sound::GetCodec(uint32_t fourCC)
|
||||
{
|
||||
switch (fourCC)
|
||||
{
|
||||
case 0: // Assume MACE-3 by default.
|
||||
case 'MAC3':
|
||||
return std::make_unique<Pomme::Sound::MACE>();
|
||||
case 'ima4':
|
||||
return std::make_unique<Pomme::Sound::IMA4>();
|
||||
case 'alaw':
|
||||
case 'ulaw':
|
||||
return std::make_unique<Pomme::Sound::xlaw>(fourCC);
|
||||
default:
|
||||
throw std::runtime_error("Unknown audio codec: " + Pomme::FourCCString(fourCC));
|
||||
}
|
||||
}
|
649
src/Sound/cmixer.cpp
Normal file
649
src/Sound/cmixer.cpp
Normal file
@ -0,0 +1,649 @@
|
||||
// Adapted from cmixer by rxi (https://github.com/rxi/cmixer)
|
||||
|
||||
/*
|
||||
** Copyright (c) 2017 rxi
|
||||
**
|
||||
** Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
** of this software and associated documentation files (the "Software"), to
|
||||
** deal in the Software without restriction, including without limitation the
|
||||
** rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
** sell copies of the Software, and to permit persons to whom the Software is
|
||||
** furnished to do so, subject to the following conditions:
|
||||
**
|
||||
** The above copyright notice and this permission notice shall be included in
|
||||
** all copies or substantial portions of the Software.
|
||||
**
|
||||
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
** FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
** IN THE SOFTWARE.
|
||||
**/
|
||||
|
||||
#include "Sound/cmixer.h"
|
||||
#include "Utilities/structpack.h"
|
||||
#include <SDL.h>
|
||||
|
||||
#include <vector>
|
||||
#include <fstream>
|
||||
#include <list>
|
||||
|
||||
using namespace cmixer;
|
||||
|
||||
#define CLAMP(x, a, b) ((x) < (a) ? (a) : (x) > (b) ? (b) : (x))
|
||||
#define MIN(a, b) ((a) < (b) ? (a) : (b))
|
||||
#define MAX(a, b) ((a) > (b) ? (a) : (b))
|
||||
|
||||
#define FX_BITS (12)
|
||||
#define FX_UNIT (1 << FX_BITS)
|
||||
#define FX_MASK (FX_UNIT - 1)
|
||||
#define FX_FROM_FLOAT(f) ((long)((f) * FX_UNIT))
|
||||
#define DOUBLE_FROM_FX(f) ((double)f / FX_UNIT)
|
||||
#define FX_LERP(a, b, p) ((a) + ((((b) - (a)) * (p)) >> FX_BITS))
|
||||
|
||||
#define BUFFER_MASK (BUFFER_SIZE - 1)
|
||||
|
||||
static constexpr bool INTERPOLATED_RESAMPLING = false;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Global mixer
|
||||
|
||||
static struct Mixer
|
||||
{
|
||||
SDL_mutex* sdlAudioMutex;
|
||||
|
||||
std::list<Source*> sources; // Linked list of active (playing) sources
|
||||
int32_t pcmmixbuf[BUFFER_SIZE]; // Internal master buffer
|
||||
int samplerate; // Master samplerate
|
||||
int gain; // Master gain (fixed point)
|
||||
|
||||
void Init(int samplerate);
|
||||
|
||||
void Process(int16_t* dst, int len);
|
||||
|
||||
void Lock();
|
||||
|
||||
void Unlock();
|
||||
|
||||
void SetMasterGain(double newGain);
|
||||
} gMixer = {};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Global init/shutdown
|
||||
|
||||
static bool sdlAudioSubSystemInited = false;
|
||||
static SDL_AudioDeviceID sdlDeviceID = 0;
|
||||
|
||||
void cmixer::InitWithSDL()
|
||||
{
|
||||
if (sdlAudioSubSystemInited)
|
||||
throw std::runtime_error("SDL audio subsystem already inited");
|
||||
|
||||
if (0 != SDL_InitSubSystem(SDL_INIT_AUDIO))
|
||||
throw std::runtime_error("couldn't init SDL audio subsystem");
|
||||
|
||||
sdlAudioSubSystemInited = true;
|
||||
|
||||
// Init SDL audio
|
||||
SDL_AudioSpec fmt = {};
|
||||
fmt.freq = 44100;
|
||||
fmt.format = AUDIO_S16;
|
||||
fmt.channels = 2;
|
||||
fmt.samples = 1024;
|
||||
fmt.callback = [](void* udata, Uint8* stream, int size)
|
||||
{
|
||||
gMixer.Process((int16_t*) stream, size / 2);
|
||||
};
|
||||
|
||||
SDL_AudioSpec got;
|
||||
sdlDeviceID = SDL_OpenAudioDevice(NULL, 0, &fmt, &got, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE);
|
||||
if (!sdlDeviceID)
|
||||
throw std::runtime_error(SDL_GetError());
|
||||
|
||||
// Init library
|
||||
gMixer.Init(got.freq);
|
||||
gMixer.SetMasterGain(0.5);
|
||||
|
||||
// Start audio
|
||||
SDL_PauseAudioDevice(sdlDeviceID, 0);
|
||||
}
|
||||
|
||||
void cmixer::ShutdownWithSDL()
|
||||
{
|
||||
if (sdlDeviceID)
|
||||
{
|
||||
SDL_CloseAudioDevice(sdlDeviceID);
|
||||
sdlDeviceID = 0;
|
||||
}
|
||||
if (gMixer.sdlAudioMutex)
|
||||
{
|
||||
SDL_DestroyMutex(gMixer.sdlAudioMutex);
|
||||
gMixer.sdlAudioMutex = nullptr;
|
||||
}
|
||||
if (sdlAudioSubSystemInited)
|
||||
{
|
||||
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
||||
sdlAudioSubSystemInited = false;
|
||||
}
|
||||
}
|
||||
|
||||
double cmixer::GetMasterGain()
|
||||
{
|
||||
return DOUBLE_FROM_FX(gMixer.gain);
|
||||
}
|
||||
|
||||
void cmixer::SetMasterGain(double newGain)
|
||||
{
|
||||
gMixer.SetMasterGain(newGain);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Global mixer impl
|
||||
|
||||
void Mixer::Lock()
|
||||
{
|
||||
SDL_LockMutex(sdlAudioMutex);
|
||||
}
|
||||
|
||||
void Mixer::Unlock()
|
||||
{
|
||||
SDL_UnlockMutex(sdlAudioMutex);
|
||||
}
|
||||
|
||||
void Mixer::Init(int newSamplerate)
|
||||
{
|
||||
sdlAudioMutex = SDL_CreateMutex();
|
||||
|
||||
samplerate = newSamplerate;
|
||||
gain = FX_UNIT;
|
||||
}
|
||||
|
||||
void Mixer::SetMasterGain(double newGain)
|
||||
{
|
||||
if (newGain < 0)
|
||||
newGain = 0;
|
||||
gain = FX_FROM_FLOAT(newGain);
|
||||
}
|
||||
|
||||
void Mixer::Process(int16_t* dst, int len)
|
||||
{
|
||||
// Process in chunks of BUFFER_SIZE if `len` is larger than BUFFER_SIZE
|
||||
while (len > BUFFER_SIZE)
|
||||
{
|
||||
Process(dst, BUFFER_SIZE);
|
||||
dst += BUFFER_SIZE;
|
||||
len -= BUFFER_SIZE;
|
||||
}
|
||||
|
||||
// Zeroset internal buffer
|
||||
memset(pcmmixbuf, 0, len * sizeof(pcmmixbuf[0]));
|
||||
|
||||
// Process active sources
|
||||
Lock();
|
||||
for (auto si = sources.begin(); si != sources.end();)
|
||||
{
|
||||
auto& s = **si;
|
||||
s.Process(len);
|
||||
// Remove source from list if it is no longer playing
|
||||
if (s.state != CM_STATE_PLAYING)
|
||||
{
|
||||
s.active = false;
|
||||
si = sources.erase(si);
|
||||
}
|
||||
else
|
||||
{
|
||||
++si;
|
||||
}
|
||||
}
|
||||
Unlock();
|
||||
|
||||
// Copy internal buffer to destination and clip
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
int x = (pcmmixbuf[i] * gain) >> FX_BITS;
|
||||
dst[i] = CLAMP(x, -32768, 32767);
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Source implementation
|
||||
|
||||
Source::Source()
|
||||
{
|
||||
active = false;
|
||||
Clear();
|
||||
}
|
||||
|
||||
void Source::Clear()
|
||||
{
|
||||
samplerate = 0;
|
||||
length = 0;
|
||||
end = 0;
|
||||
state = CM_STATE_STOPPED;
|
||||
position = 0;
|
||||
lgain = 0;
|
||||
rgain = 0;
|
||||
rate = 0;
|
||||
nextfill = 0;
|
||||
loop = false;
|
||||
rewind = true;
|
||||
// DON'T touch active. The source may still be in gMixer!
|
||||
gain = 0;
|
||||
pan = 0;
|
||||
onComplete = nullptr;
|
||||
}
|
||||
|
||||
void Source::Init(int theSampleRate, int theLength)
|
||||
{
|
||||
this->samplerate = theSampleRate;
|
||||
this->length = theLength;
|
||||
SetGain(1);
|
||||
SetPan(0);
|
||||
SetPitch(1);
|
||||
SetLoop(false);
|
||||
Stop();
|
||||
}
|
||||
|
||||
Source::~Source()
|
||||
{
|
||||
gMixer.Lock();
|
||||
if (active)
|
||||
{
|
||||
gMixer.sources.remove(this);
|
||||
}
|
||||
gMixer.Unlock();
|
||||
}
|
||||
|
||||
void Source::Rewind()
|
||||
{
|
||||
Rewind2();
|
||||
position = 0;
|
||||
rewind = false;
|
||||
end = length;
|
||||
nextfill = 0;
|
||||
}
|
||||
|
||||
void Source::FillBuffer(int offset, int fillLength)
|
||||
{
|
||||
FillBuffer(pcmbuf + offset, fillLength);
|
||||
}
|
||||
|
||||
void Source::Process(int len)
|
||||
{
|
||||
int32_t* dst = gMixer.pcmmixbuf;
|
||||
|
||||
// Do rewind if flag is set
|
||||
if (rewind)
|
||||
{
|
||||
Rewind();
|
||||
}
|
||||
|
||||
// Don't process if not playing
|
||||
if (state != CM_STATE_PLAYING)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Process audio
|
||||
while (len > 0)
|
||||
{
|
||||
// Get current position frame
|
||||
int frame = int(position >> FX_BITS);
|
||||
|
||||
// Fill buffer if required
|
||||
if (frame + 3 >= nextfill)
|
||||
{
|
||||
FillBuffer((nextfill * 2) & BUFFER_MASK, BUFFER_SIZE / 2);
|
||||
nextfill += BUFFER_SIZE / 4;
|
||||
}
|
||||
|
||||
// Handle reaching the end of the playthrough
|
||||
if (frame >= end)
|
||||
{
|
||||
// As streams continiously fill the raw buffer in a loop we simply
|
||||
// increment the end idx by one length and continue reading from it for
|
||||
// another play-through
|
||||
end = frame + this->length;
|
||||
// Set state and stop processing if we're not set to loop
|
||||
if (!loop)
|
||||
{
|
||||
state = CM_STATE_STOPPED;
|
||||
if (onComplete != nullptr)
|
||||
onComplete();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Work out how many frames we should process in the loop
|
||||
int n = MIN(nextfill - 2, end) - frame;
|
||||
int count = (n << FX_BITS) / rate;
|
||||
count = MAX(count, 1);
|
||||
count = MIN(count, len / 2);
|
||||
len -= count * 2;
|
||||
|
||||
// Add audio to master buffer
|
||||
if (rate == FX_UNIT)
|
||||
{
|
||||
// Add audio to buffer -- basic
|
||||
n = frame * 2;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
dst[0] += (pcmbuf[(n ) & BUFFER_MASK] * lgain) >> FX_BITS;
|
||||
dst[1] += (pcmbuf[(n + 1) & BUFFER_MASK] * rgain) >> FX_BITS;
|
||||
n += 2;
|
||||
dst += 2;
|
||||
}
|
||||
this->position += count * FX_UNIT;
|
||||
}
|
||||
else if (INTERPOLATED_RESAMPLING)
|
||||
{
|
||||
// Resample audio (with linear interpolation) and add to buffer
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
n = int(position >> FX_BITS) * 2;
|
||||
int p = position & FX_MASK;
|
||||
int a = pcmbuf[(n ) & BUFFER_MASK];
|
||||
int b = pcmbuf[(n + 2) & BUFFER_MASK];
|
||||
dst[0] += (FX_LERP(a, b, p) * lgain) >> FX_BITS;
|
||||
n++;
|
||||
a = pcmbuf[(n ) & BUFFER_MASK];
|
||||
b = pcmbuf[(n + 2) & BUFFER_MASK];
|
||||
dst[1] += (FX_LERP(a, b, p) * rgain) >> FX_BITS;
|
||||
position += rate;
|
||||
dst += 2;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Resample audio (without interpolation) and add to buffer
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
n = int(position >> FX_BITS) * 2;
|
||||
dst[0] += (pcmbuf[(n ) & BUFFER_MASK] * lgain) >> FX_BITS;
|
||||
dst[1] += (pcmbuf[(n + 1) & BUFFER_MASK] * rgain) >> FX_BITS;
|
||||
position += rate;
|
||||
dst += 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double Source::GetLength() const
|
||||
{
|
||||
return length / (double) samplerate;
|
||||
}
|
||||
|
||||
double Source::GetPosition() const
|
||||
{
|
||||
return ((position >> FX_BITS) % length) / (double) samplerate;
|
||||
}
|
||||
|
||||
int Source::GetState() const
|
||||
{
|
||||
return state;
|
||||
}
|
||||
|
||||
void Source::RecalcGains()
|
||||
{
|
||||
double l = this->gain * (pan <= 0. ? 1. : 1. - pan);
|
||||
double r = this->gain * (pan >= 0. ? 1. : 1. + pan);
|
||||
this->lgain = FX_FROM_FLOAT(l);
|
||||
this->rgain = FX_FROM_FLOAT(r);
|
||||
}
|
||||
|
||||
void Source::SetGain(double newGain)
|
||||
{
|
||||
gain = newGain;
|
||||
RecalcGains();
|
||||
}
|
||||
|
||||
void Source::SetPan(double newPan)
|
||||
{
|
||||
pan = CLAMP(newPan, -1.0, 1.0);
|
||||
RecalcGains();
|
||||
}
|
||||
|
||||
void Source::SetPitch(double newPitch)
|
||||
{
|
||||
double newRate;
|
||||
if (newPitch > 0.)
|
||||
{
|
||||
newRate = samplerate / (double) gMixer.samplerate * newPitch;
|
||||
}
|
||||
else
|
||||
{
|
||||
newRate = 0.001;
|
||||
}
|
||||
rate = FX_FROM_FLOAT(newRate);
|
||||
}
|
||||
|
||||
void Source::SetLoop(bool newLoop)
|
||||
{
|
||||
loop = newLoop;
|
||||
}
|
||||
|
||||
void Source::Play()
|
||||
{
|
||||
gMixer.Lock();
|
||||
state = CM_STATE_PLAYING;
|
||||
if (!active)
|
||||
{
|
||||
active = true;
|
||||
gMixer.sources.push_front(this);
|
||||
}
|
||||
gMixer.Unlock();
|
||||
}
|
||||
|
||||
void Source::Pause()
|
||||
{
|
||||
state = CM_STATE_PAUSED;
|
||||
}
|
||||
|
||||
void Source::TogglePause()
|
||||
{
|
||||
if (state == CM_STATE_PAUSED)
|
||||
Play();
|
||||
else if (state == CM_STATE_PLAYING)
|
||||
Pause();
|
||||
}
|
||||
|
||||
void Source::Stop()
|
||||
{
|
||||
state = CM_STATE_STOPPED;
|
||||
rewind = true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// WavStream implementation
|
||||
|
||||
#define WAV_PROCESS_LOOP(X) \
|
||||
while (n--) \
|
||||
{ \
|
||||
X \
|
||||
dst += 2; \
|
||||
idx++; \
|
||||
}
|
||||
|
||||
WavStream::WavStream()
|
||||
: Source()
|
||||
, bitdepth(0)
|
||||
, channels(0)
|
||||
, bigEndian(false)
|
||||
, idx(0)
|
||||
, userBuffer()
|
||||
{}
|
||||
|
||||
void WavStream::Clear()
|
||||
{
|
||||
Source::Clear();
|
||||
bitdepth = 0;
|
||||
channels = 0;
|
||||
bigEndian = false;
|
||||
idx = 0;
|
||||
userBuffer.clear();
|
||||
}
|
||||
|
||||
void WavStream::Init(
|
||||
int theSampleRate,
|
||||
int theBitDepth,
|
||||
int theNChannels,
|
||||
bool theBigEndian,
|
||||
std::span<char> theSpan)
|
||||
{
|
||||
Clear();
|
||||
Source::Init(theSampleRate, int((theSpan.size() / (theBitDepth / 8)) / theNChannels));
|
||||
this->bitdepth = theBitDepth;
|
||||
this->channels = theNChannels;
|
||||
this->idx = 0;
|
||||
this->span = theSpan;
|
||||
this->bigEndian = theBigEndian;
|
||||
}
|
||||
|
||||
std::span<char> WavStream::GetBuffer(int nBytesOut)
|
||||
{
|
||||
userBuffer.clear();
|
||||
userBuffer.reserve(nBytesOut);
|
||||
return std::span(userBuffer.data(), nBytesOut);
|
||||
}
|
||||
|
||||
std::span<char> WavStream::SetBuffer(std::vector<char>&& data)
|
||||
{
|
||||
userBuffer = std::move(data);
|
||||
return std::span(userBuffer.data(), userBuffer.size());
|
||||
}
|
||||
|
||||
void WavStream::Rewind2()
|
||||
{
|
||||
idx = 0;
|
||||
}
|
||||
|
||||
void WavStream::FillBuffer(int16_t* dst, int len)
|
||||
{
|
||||
int x, n;
|
||||
|
||||
len /= 2;
|
||||
|
||||
while (len > 0)
|
||||
{
|
||||
n = MIN(len, length - idx);
|
||||
len -= n;
|
||||
if (bigEndian && bitdepth == 16 && channels == 1)
|
||||
{
|
||||
WAV_PROCESS_LOOP({
|
||||
dst[0] = dst[1] = ByteswapScalar(data16()[idx]);
|
||||
});
|
||||
}
|
||||
else if (bigEndian && bitdepth == 16 && channels == 2)
|
||||
{
|
||||
WAV_PROCESS_LOOP({
|
||||
x = idx * 2;
|
||||
dst[0] = ByteswapScalar(data16()[x]);
|
||||
dst[1] = ByteswapScalar(data16()[x + 1]);
|
||||
});
|
||||
}
|
||||
else if (bitdepth == 16 && channels == 1)
|
||||
{
|
||||
WAV_PROCESS_LOOP({
|
||||
dst[0] = dst[1] = data16()[idx];
|
||||
});
|
||||
}
|
||||
else if (bitdepth == 16 && channels == 2)
|
||||
{
|
||||
WAV_PROCESS_LOOP({
|
||||
x = idx * 2;
|
||||
dst[0] = data16()[x];
|
||||
dst[1] = data16()[x + 1];
|
||||
});
|
||||
}
|
||||
else if (bitdepth == 8 && channels == 1)
|
||||
{
|
||||
WAV_PROCESS_LOOP({
|
||||
dst[0] = dst[1] = (data8()[idx] - 128) << 8;
|
||||
});
|
||||
}
|
||||
else if (bitdepth == 8 && channels == 2)
|
||||
{
|
||||
WAV_PROCESS_LOOP({
|
||||
x = idx * 2;
|
||||
dst[0] = (data8()[x] - 128) << 8;
|
||||
dst[1] = (data8()[x + 1] - 128) << 8;
|
||||
});
|
||||
}
|
||||
// Loop back and continue filling buffer if we didn't fill the buffer
|
||||
if (len > 0)
|
||||
{
|
||||
idx = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
//-----------------------------------------------------------------------------
|
||||
// LoadWAVFromFile for testing
|
||||
|
||||
static std::vector<char> LoadFile(char const* filename)
|
||||
{
|
||||
std::ifstream ifs(filename, std::ios::binary | std::ios::ate);
|
||||
auto pos = ifs.tellg();
|
||||
std::vector<char> bytes(pos);
|
||||
ifs.seekg(0, std::ios::beg);
|
||||
ifs.read(&bytes[0], pos);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
static const char* FindChunk(const char* data, int len, const char* id, int* size)
|
||||
{
|
||||
// TODO : Error handling on malformed wav file
|
||||
int idlen = strlen(id);
|
||||
const char* p = data + 12;
|
||||
next:
|
||||
*size = *((cm_UInt32*)(p + 4));
|
||||
if (memcmp(p, id, idlen)) {
|
||||
p += 8 + *size;
|
||||
if (p > data + len) return NULL;
|
||||
goto next;
|
||||
}
|
||||
return p + 8;
|
||||
}
|
||||
|
||||
WavStream cmixer::LoadWAVFromFile(const char* path)
|
||||
{
|
||||
int sz;
|
||||
auto filebuf = LoadFile(path);
|
||||
auto len = filebuf.size();
|
||||
const char* data = filebuf.data();
|
||||
const char* p = (char*)data;
|
||||
|
||||
// Check header
|
||||
if (memcmp(p, "RIFF", 4) || memcmp(p + 8, "WAVE", 4))
|
||||
throw std::invalid_argument("bad wav header");
|
||||
|
||||
// Find fmt subchunk
|
||||
p = FindChunk(data, len, "fmt ", &sz);
|
||||
if (!p)
|
||||
throw std::invalid_argument("no fmt subchunk");
|
||||
|
||||
// Load fmt info
|
||||
int format = *((cm_UInt16*)(p));
|
||||
int channels = *((cm_UInt16*)(p + 2));
|
||||
int samplerate = *((cm_UInt32*)(p + 4));
|
||||
int bitdepth = *((cm_UInt16*)(p + 14));
|
||||
if (format != 1)
|
||||
throw std::invalid_argument("unsupported format");
|
||||
if (channels == 0 || samplerate == 0 || bitdepth == 0)
|
||||
throw std::invalid_argument("bad format");
|
||||
|
||||
// Find data subchunk
|
||||
p = FindChunk(data, len, "data", &sz);
|
||||
if (!p)
|
||||
throw std::invalid_argument("no data subchunk");
|
||||
|
||||
return WavStream(
|
||||
samplerate,
|
||||
bitdepth,
|
||||
channels,
|
||||
std::vector<char>(p, p + sz));
|
||||
}
|
||||
#endif
|
155
src/Sound/cmixer.h
Normal file
155
src/Sound/cmixer.h
Normal file
@ -0,0 +1,155 @@
|
||||
// Adapted from cmixer by rxi (https://github.com/rxi/cmixer)
|
||||
|
||||
/*
|
||||
** Copyright (c) 2017 rxi
|
||||
**
|
||||
** Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
** of this software and associated documentation files (the "Software"), to
|
||||
** deal in the Software without restriction, including without limitation the
|
||||
** rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
** sell copies of the Software, and to permit persons to whom the Software is
|
||||
** furnished to do so, subject to the following conditions:
|
||||
**
|
||||
** The above copyright notice and this permission notice shall be included in
|
||||
** all copies or substantial portions of the Software.
|
||||
**
|
||||
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
** FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
** IN THE SOFTWARE.
|
||||
**/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <cstdint>
|
||||
#include "CompilerSupport/span.h"
|
||||
|
||||
#define BUFFER_SIZE (512)
|
||||
|
||||
namespace cmixer
|
||||
{
|
||||
|
||||
enum
|
||||
{
|
||||
CM_STATE_STOPPED,
|
||||
CM_STATE_PLAYING,
|
||||
CM_STATE_PAUSED
|
||||
};
|
||||
|
||||
struct Source
|
||||
{
|
||||
int16_t pcmbuf[BUFFER_SIZE]; // Internal buffer with raw stereo PCM
|
||||
int samplerate; // Stream's native samplerate
|
||||
int length; // Stream's length in frames
|
||||
int end; // End index for the current play-through
|
||||
int state; // Current state (playing|paused|stopped)
|
||||
int64_t position; // Current playhead position (fixed point)
|
||||
int lgain, rgain; // Left and right gain (fixed point)
|
||||
int rate; // Playback rate (fixed point)
|
||||
int nextfill; // Next frame idx where the buffer needs to be filled
|
||||
bool loop; // Whether the source will loop when `end` is reached
|
||||
bool rewind; // Whether the source will rewind before playing
|
||||
bool active; // Whether the source is part of `sources` list
|
||||
double gain; // Gain set by `cm_set_gain()`
|
||||
double pan; // Pan set by `cm_set_pan()`
|
||||
std::function<void()> onComplete; // Callback
|
||||
|
||||
protected:
|
||||
Source();
|
||||
|
||||
void Init(int samplerate, int length);
|
||||
|
||||
virtual void Rewind2() = 0;
|
||||
|
||||
virtual void FillBuffer(int16_t* buffer, int length) = 0;
|
||||
|
||||
public:
|
||||
virtual ~Source();
|
||||
|
||||
virtual void Clear();
|
||||
|
||||
void Rewind();
|
||||
|
||||
void RecalcGains();
|
||||
|
||||
void FillBuffer(int offset, int length);
|
||||
|
||||
void Process(int len);
|
||||
|
||||
double GetLength() const;
|
||||
|
||||
double GetPosition() const;
|
||||
|
||||
int GetState() const;
|
||||
|
||||
void SetGain(double gain);
|
||||
|
||||
void SetPan(double pan);
|
||||
|
||||
void SetPitch(double pitch);
|
||||
|
||||
void SetLoop(bool loop);
|
||||
|
||||
void Play();
|
||||
|
||||
void Pause();
|
||||
|
||||
void TogglePause();
|
||||
|
||||
void Stop();
|
||||
};
|
||||
|
||||
class WavStream : public Source
|
||||
{
|
||||
int bitdepth;
|
||||
int channels;
|
||||
bool bigEndian;
|
||||
int idx;
|
||||
std::span<char> span;
|
||||
std::vector<char> userBuffer;
|
||||
|
||||
void Rewind2() override;
|
||||
|
||||
void FillBuffer(int16_t* buffer, int length) override;
|
||||
|
||||
inline uint8_t* data8() const
|
||||
{ return reinterpret_cast<uint8_t*>(span.data()); }
|
||||
|
||||
inline int16_t* data16() const
|
||||
{ return reinterpret_cast<int16_t*>(span.data()); }
|
||||
|
||||
public:
|
||||
WavStream();
|
||||
|
||||
virtual void Clear() override;
|
||||
|
||||
void Init(
|
||||
int theSampleRate,
|
||||
int theBitDepth,
|
||||
int nChannels,
|
||||
bool bigEndian,
|
||||
std::span<char> data
|
||||
);
|
||||
|
||||
std::span<char> GetBuffer(int nBytesOut);
|
||||
|
||||
std::span<char> SetBuffer(std::vector<char>&& data);
|
||||
};
|
||||
|
||||
|
||||
void InitWithSDL();
|
||||
|
||||
void ShutdownWithSDL();
|
||||
|
||||
double GetMasterGain();
|
||||
|
||||
void SetMasterGain(double);
|
||||
|
||||
WavStream LoadWAVFromFile(const char* path);
|
||||
|
||||
}
|
91
src/Sound/xlaw.cpp
Normal file
91
src/Sound/xlaw.cpp
Normal file
@ -0,0 +1,91 @@
|
||||
#include "PommeSound.h"
|
||||
|
||||
// Conversion tables to obtain 16-bit PCM from 8-bit a-law/mu-law.
|
||||
// These tables are valid for *-law input bytes [0...127].
|
||||
// For inputs [-127...-1], mirror the table and negate the output.
|
||||
//
|
||||
// These tables were generated using alaw2linear() and ulaw2linear()
|
||||
// from ffmpeg/libavcodec/pcm_tablegen.h.
|
||||
|
||||
static const int16_t alawToPCM[128] = {
|
||||
-5504, -5248, -6016, -5760, -4480, -4224, -4992, -4736,
|
||||
-7552, -7296, -8064, -7808, -6528, -6272, -7040, -6784,
|
||||
-2752, -2624, -3008, -2880, -2240, -2112, -2496, -2368,
|
||||
-3776, -3648, -4032, -3904, -3264, -3136, -3520, -3392,
|
||||
-22016, -20992, -24064, -23040, -17920, -16896, -19968, -18944,
|
||||
-30208, -29184, -32256, -31232, -26112, -25088, -28160, -27136,
|
||||
-11008, -10496, -12032, -11520, -8960, -8448, -9984, -9472,
|
||||
-15104, -14592, -16128, -15616, -13056, -12544, -14080, -13568,
|
||||
-344, -328, -376, -360, -280, -264, -312, -296,
|
||||
-472, -456, -504, -488, -408, -392, -440, -424,
|
||||
-88, -72, -120, -104, -24, -8, -56, -40,
|
||||
-216, -200, -248, -232, -152, -136, -184, -168,
|
||||
-1376, -1312, -1504, -1440, -1120, -1056, -1248, -1184,
|
||||
-1888, -1824, -2016, -1952, -1632, -1568, -1760, -1696,
|
||||
-688, -656, -752, -720, -560, -528, -624, -592,
|
||||
-944, -912, -1008, -976, -816, -784, -880, -848,
|
||||
};
|
||||
|
||||
static const int16_t ulawToPCM[128] = {
|
||||
-32124, -31100, -30076, -29052, -28028, -27004, -25980, -24956,
|
||||
-23932, -22908, -21884, -20860, -19836, -18812, -17788, -16764,
|
||||
-15996, -15484, -14972, -14460, -13948, -13436, -12924, -12412,
|
||||
-11900, -11388, -10876, -10364, -9852, -9340, -8828, -8316,
|
||||
-7932, -7676, -7420, -7164, -6908, -6652, -6396, -6140,
|
||||
-5884, -5628, -5372, -5116, -4860, -4604, -4348, -4092,
|
||||
-3900, -3772, -3644, -3516, -3388, -3260, -3132, -3004,
|
||||
-2876, -2748, -2620, -2492, -2364, -2236, -2108, -1980,
|
||||
-1884, -1820, -1756, -1692, -1628, -1564, -1500, -1436,
|
||||
-1372, -1308, -1244, -1180, -1116, -1052, -988, -924,
|
||||
-876, -844, -812, -780, -748, -716, -684, -652,
|
||||
-620, -588, -556, -524, -492, -460, -428, -396,
|
||||
-372, -356, -340, -324, -308, -292, -276, -260,
|
||||
-244, -228, -212, -196, -180, -164, -148, -132,
|
||||
-120, -112, -104, -96, -88, -80, -72, -64,
|
||||
-56, -48, -40, -32, -24, -16, -8, -0,
|
||||
};
|
||||
|
||||
Pomme::Sound::xlaw::xlaw(uint32_t codecFourCC)
|
||||
{
|
||||
switch (codecFourCC)
|
||||
{
|
||||
case 'ulaw':
|
||||
xlawToPCM = ulawToPCM;
|
||||
break;
|
||||
case 'alaw':
|
||||
xlawToPCM = alawToPCM;
|
||||
break;
|
||||
default:
|
||||
throw std::runtime_error("unknown xlaw fourCC");
|
||||
}
|
||||
}
|
||||
|
||||
void Pomme::Sound::xlaw::Decode(
|
||||
const int nChannels,
|
||||
const std::span<const char> input,
|
||||
const std::span<char> output)
|
||||
{
|
||||
if (2 * input.size() != output.size())
|
||||
{
|
||||
throw std::runtime_error("ulaw: incorrect input/output buffer sizes");
|
||||
}
|
||||
|
||||
const std::span<int16_t> out16(
|
||||
reinterpret_cast<int16_t*>(output.data()),
|
||||
input.size());
|
||||
|
||||
for (size_t i = 0; i < input.size(); i++)
|
||||
{
|
||||
int8_t b = input[i]; // SIGNED!
|
||||
if (b < 0)
|
||||
{
|
||||
// Mirror table and negate output
|
||||
out16[i] = -xlawToPCM[128 + b];
|
||||
}
|
||||
else
|
||||
{
|
||||
out16[i] = xlawToPCM[b];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
42
src/Time/TimeManager.cpp
Normal file
42
src/Time/TimeManager.cpp
Normal file
@ -0,0 +1,42 @@
|
||||
#include "Pomme.h"
|
||||
#include "PommeTime.h"
|
||||
#include "PommeTypes.h"
|
||||
#include "PommeDebug.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <ctime>
|
||||
#include <iomanip>
|
||||
|
||||
std::chrono::time_point<std::chrono::high_resolution_clock> bootTP;
|
||||
|
||||
// timestamp (from unix epoch) of the mac epoch, Jan 1, 1904, 00:00:00
|
||||
constexpr int JANUARY_1_1904 = -2'082'844'800;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Time Manager
|
||||
|
||||
void Pomme::Time::Init()
|
||||
{
|
||||
bootTP = std::chrono::high_resolution_clock::now();
|
||||
}
|
||||
|
||||
void GetDateTime(unsigned long* secs)
|
||||
{
|
||||
*secs = (unsigned long) (std::time(nullptr) + JANUARY_1_1904);
|
||||
}
|
||||
|
||||
void Microseconds(UnsignedWide* usecs)
|
||||
{
|
||||
auto now = std::chrono::high_resolution_clock::now();
|
||||
auto usecs1 = std::chrono::duration_cast<std::chrono::microseconds>(now - bootTP);
|
||||
auto usecs2 = usecs1.count();
|
||||
usecs->lo = usecs2 & 0xFFFFFFFFL;
|
||||
usecs->hi = (usecs2 >> 32) & 0xFFFFFFFFL;
|
||||
}
|
||||
|
||||
UInt32 TickCount()
|
||||
{
|
||||
TODO();
|
||||
return 0;
|
||||
}
|
85
src/Utilities/BigEndianIStream.cpp
Normal file
85
src/Utilities/BigEndianIStream.cpp
Normal file
@ -0,0 +1,85 @@
|
||||
#include "Utilities/BigEndianIStream.h"
|
||||
#include "Utilities/IEEEExtended.h"
|
||||
|
||||
Pomme::StreamPosGuard::StreamPosGuard(std::istream& theStream) :
|
||||
stream(theStream)
|
||||
, backup(theStream.tellg())
|
||||
, active(true)
|
||||
{
|
||||
}
|
||||
|
||||
Pomme::StreamPosGuard::~StreamPosGuard()
|
||||
{
|
||||
if (active)
|
||||
{
|
||||
stream.seekg(backup, std::ios_base::beg);
|
||||
}
|
||||
}
|
||||
|
||||
void Pomme::StreamPosGuard::Cancel()
|
||||
{
|
||||
active = false;
|
||||
}
|
||||
|
||||
Pomme::BigEndianIStream::BigEndianIStream(std::istream& theStream) :
|
||||
stream(theStream)
|
||||
{
|
||||
}
|
||||
|
||||
void Pomme::BigEndianIStream::Read(char* dst, size_t n)
|
||||
{
|
||||
stream.read(dst, n);
|
||||
if (stream.eof())
|
||||
{
|
||||
throw std::out_of_range("Read past end of stream!");
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<unsigned char> Pomme::BigEndianIStream::ReadBytes(size_t n)
|
||||
{
|
||||
std::vector<unsigned char> buf(n);
|
||||
Read(reinterpret_cast<char*>(buf.data()), n);
|
||||
return buf;
|
||||
}
|
||||
|
||||
std::string Pomme::BigEndianIStream::ReadPascalString()
|
||||
{
|
||||
int length = Read<uint8_t>();
|
||||
auto bytes = ReadBytes(length);
|
||||
bytes.push_back('\0');
|
||||
return std::string((const char*) &bytes.data()[0]);
|
||||
}
|
||||
|
||||
std::string Pomme::BigEndianIStream::ReadPascalString_FixedLengthRecord(const int maxChars)
|
||||
{
|
||||
int length = Read<uint8_t>();
|
||||
char buf[256];
|
||||
stream.read(buf, maxChars);
|
||||
return std::string(buf, length);
|
||||
}
|
||||
|
||||
double Pomme::BigEndianIStream::Read80BitFloat()
|
||||
{
|
||||
auto bytes = ReadBytes(10);
|
||||
return ConvertFromIeeeExtended((unsigned char*) bytes.data());
|
||||
}
|
||||
|
||||
void Pomme::BigEndianIStream::Goto(std::streamoff absoluteOffset)
|
||||
{
|
||||
stream.seekg(absoluteOffset, std::ios_base::beg);
|
||||
}
|
||||
|
||||
void Pomme::BigEndianIStream::Skip(size_t n)
|
||||
{
|
||||
stream.seekg(n, std::ios_base::cur);
|
||||
}
|
||||
|
||||
std::streampos Pomme::BigEndianIStream::Tell() const
|
||||
{
|
||||
return stream.tellg();
|
||||
}
|
||||
|
||||
Pomme::StreamPosGuard Pomme::BigEndianIStream::GuardPos()
|
||||
{
|
||||
return Pomme::StreamPosGuard(stream);
|
||||
}
|
62
src/Utilities/BigEndianIStream.h
Normal file
62
src/Utilities/BigEndianIStream.h
Normal file
@ -0,0 +1,62 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <istream>
|
||||
#include <vector>
|
||||
|
||||
namespace Pomme
|
||||
{
|
||||
class StreamPosGuard
|
||||
{
|
||||
std::istream& stream;
|
||||
const std::streampos backup;
|
||||
bool active;
|
||||
|
||||
public:
|
||||
StreamPosGuard(std::istream& f);
|
||||
|
||||
~StreamPosGuard();
|
||||
|
||||
void Cancel();
|
||||
};
|
||||
|
||||
class BigEndianIStream
|
||||
{
|
||||
std::istream& stream;
|
||||
|
||||
public:
|
||||
BigEndianIStream(std::istream& theStream);
|
||||
|
||||
void Read(char* dst, size_t n);
|
||||
|
||||
void Skip(size_t n);
|
||||
|
||||
void Goto(std::streamoff absoluteOffset);
|
||||
|
||||
std::streampos Tell() const;
|
||||
|
||||
StreamPosGuard GuardPos();
|
||||
|
||||
std::vector<unsigned char> ReadBytes(size_t n);
|
||||
|
||||
std::string ReadPascalString();
|
||||
|
||||
std::string ReadPascalString_FixedLengthRecord(const int maxChars);
|
||||
|
||||
double Read80BitFloat();
|
||||
|
||||
template<typename T>
|
||||
T Read()
|
||||
{
|
||||
char b[sizeof(T)];
|
||||
Read(b, sizeof(T));
|
||||
#if !(TARGET_RT_BIGENDIAN)
|
||||
if constexpr (sizeof(T) > 1)
|
||||
{
|
||||
std::reverse(b, b + sizeof(T));
|
||||
}
|
||||
#endif
|
||||
return *(T*) b;
|
||||
}
|
||||
};
|
||||
}
|
46
src/Utilities/FixedPool.h
Normal file
46
src/Utilities/FixedPool.h
Normal file
@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
namespace Pomme {
|
||||
|
||||
template<typename TObj, typename TId, int MAX>
|
||||
class FixedPool
|
||||
{
|
||||
// fixed size array so that pointers to the elements don't move around in memory
|
||||
// (unlike how a vector might move around elements on resize)
|
||||
TObj pool[MAX];
|
||||
std::vector<TId> freeIDs;
|
||||
int inUse, inUsePeak;
|
||||
|
||||
public:
|
||||
FixedPool()
|
||||
{
|
||||
inUse = 0;
|
||||
inUsePeak = 0;
|
||||
freeIDs.reserve(MAX);
|
||||
for (int i = MAX - 1; i >= 0; i--)
|
||||
freeIDs.push_back(i);
|
||||
}
|
||||
|
||||
TObj* Alloc()
|
||||
{
|
||||
if (freeIDs.empty())
|
||||
throw std::length_error("pool exhausted");
|
||||
TId id = freeIDs.back();
|
||||
freeIDs.pop_back();
|
||||
inUse++;
|
||||
if (inUse > inUsePeak)
|
||||
inUsePeak = inUse;
|
||||
return &pool[id];
|
||||
}
|
||||
|
||||
void Dispose(TObj* obj)
|
||||
{
|
||||
intptr_t id = obj - &pool[0];
|
||||
if (id < 0 || id >= MAX)
|
||||
throw std::invalid_argument("obj isn't stored in pool");
|
||||
inUse--;
|
||||
freeIDs.push_back((TId)id);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
90
src/Utilities/GrowablePool.h
Normal file
90
src/Utilities/GrowablePool.h
Normal file
@ -0,0 +1,90 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace Pomme
|
||||
{
|
||||
|
||||
template<typename TObj, typename TId, int MAX>
|
||||
class GrowablePool
|
||||
{
|
||||
std::vector<TObj> pool;
|
||||
std::vector<bool> isInUse;
|
||||
std::vector<TId> freeIDs;
|
||||
TId inUse, inUsePeak;
|
||||
|
||||
public:
|
||||
GrowablePool()
|
||||
{
|
||||
inUse = 0;
|
||||
inUsePeak = 0;
|
||||
}
|
||||
|
||||
TId Alloc()
|
||||
{
|
||||
if (IsFull()) throw std::length_error("too many items allocated");
|
||||
|
||||
inUse++;
|
||||
if (inUse > inUsePeak)
|
||||
{
|
||||
inUsePeak = inUse;
|
||||
}
|
||||
|
||||
if (!freeIDs.empty())
|
||||
{
|
||||
auto id = freeIDs.back();
|
||||
freeIDs.pop_back();
|
||||
isInUse[id] = true;
|
||||
return id;
|
||||
}
|
||||
else
|
||||
{
|
||||
pool.emplace_back();
|
||||
isInUse.push_back(true);
|
||||
return TId(pool.size() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
void Dispose(TId id)
|
||||
{
|
||||
if (!IsAllocated(id)) throw std::invalid_argument("id isn't allocated");
|
||||
inUse--;
|
||||
freeIDs.push_back(id);
|
||||
isInUse[id] = false;
|
||||
Compact();
|
||||
}
|
||||
|
||||
void Compact()
|
||||
{
|
||||
while (!freeIDs.empty() && freeIDs.back() == (TId) pool.size() - 1)
|
||||
{
|
||||
freeIDs.pop_back();
|
||||
pool.pop_back();
|
||||
isInUse.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
TObj& operator[](TId id)
|
||||
{
|
||||
if (!IsAllocated(id)) throw std::invalid_argument("id isn't allocated");
|
||||
return pool[id];
|
||||
}
|
||||
|
||||
const TObj& operator[](TId id) const
|
||||
{
|
||||
if (!IsAllocated(id)) throw std::invalid_argument("id isn't allocated");
|
||||
return pool[id];
|
||||
}
|
||||
|
||||
bool IsFull() const
|
||||
{
|
||||
return inUse >= MAX;
|
||||
}
|
||||
|
||||
bool IsAllocated(TId id) const
|
||||
{
|
||||
return id >= 0 && (unsigned) id < pool.size() && isInUse[id];
|
||||
}
|
||||
};
|
||||
|
||||
}
|
126
src/Utilities/IEEEExtended.cpp
Normal file
126
src/Utilities/IEEEExtended.cpp
Normal file
@ -0,0 +1,126 @@
|
||||
/* Copyright (C) 1988-1991 Apple Computer, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Machine-independent I/O routines for IEEE floating-point numbers.
|
||||
*
|
||||
* NaN's and infinities are converted to HUGE_VAL or HUGE, which
|
||||
* happens to be infinity on IEEE machines. Unfortunately, it is
|
||||
* impossible to preserve NaN's in a machine-independent way.
|
||||
* Infinities are, however, preserved on IEEE machines.
|
||||
*
|
||||
* These routines have been tested on the following machines:
|
||||
* Apple Macintosh, MPW 3.1 C compiler
|
||||
* Apple Macintosh, THINK C compiler
|
||||
* Silicon Graphics IRIS, MIPS compiler
|
||||
* Cray X/MP and Y/MP
|
||||
* Digital Equipment VAX
|
||||
*
|
||||
*
|
||||
* Implemented by Malcolm Slaney and Ken Turkowski.
|
||||
*
|
||||
* Malcolm Slaney contributions during 1988-1990 include big- and little-
|
||||
* endian file I/O, conversion to and from Motorola's extended 80-bit
|
||||
* floating-point format, and conversions to and from IEEE single-
|
||||
* precision floating-point format.
|
||||
*
|
||||
* In 1991, Ken Turkowski implemented the conversions to and from
|
||||
* IEEE double-precision format, added more precision to the extended
|
||||
* conversions, and accommodated conversions involving +/- infinity,
|
||||
* NaN's, and denormalized numbers.
|
||||
*/
|
||||
|
||||
#include <math.h>
|
||||
#include "IEEEExtended.h"
|
||||
|
||||
#ifndef HUGE_VAL
|
||||
# define HUGE_VAL HUGE
|
||||
#endif
|
||||
|
||||
#define FloatToUnsigned(f) ((unsigned long)(((long)(f - 2147483648.0)) + 2147483647L) + 1)
|
||||
#define UnsignedToFloat(u) (((double)((long)(u - 2147483647L - 1))) + 2147483648.0)
|
||||
|
||||
void ConvertToIeeeExtended(double num, char* bytes)
|
||||
{
|
||||
int sign;
|
||||
int expon;
|
||||
double fMant, fsMant;
|
||||
unsigned long hiMant, loMant;
|
||||
|
||||
if (num < 0) {
|
||||
sign = 0x8000;
|
||||
num *= -1;
|
||||
} else {
|
||||
sign = 0;
|
||||
}
|
||||
|
||||
if (num == 0) {
|
||||
expon = 0; hiMant = 0; loMant = 0;
|
||||
}
|
||||
else {
|
||||
fMant = frexp(num, &expon);
|
||||
if ((expon > 16384) || !(fMant < 1)) { /* Infinity or NaN */
|
||||
expon = sign|0x7FFF; hiMant = 0; loMant = 0; /* infinity */
|
||||
}
|
||||
else { /* Finite */
|
||||
expon += 16382;
|
||||
if (expon < 0) { /* denormalized */
|
||||
fMant = ldexp(fMant, expon);
|
||||
expon = 0;
|
||||
}
|
||||
expon |= sign;
|
||||
fMant = ldexp(fMant, 32);
|
||||
fsMant = floor(fMant);
|
||||
hiMant = FloatToUnsigned(fsMant);
|
||||
fMant = ldexp(fMant - fsMant, 32);
|
||||
fsMant = floor(fMant);
|
||||
loMant = FloatToUnsigned(fsMant);
|
||||
}
|
||||
}
|
||||
|
||||
bytes[0] = char(expon >> 8);
|
||||
bytes[1] = char(expon);
|
||||
bytes[2] = char(hiMant >> 24);
|
||||
bytes[3] = char(hiMant >> 16);
|
||||
bytes[4] = char(hiMant >> 8);
|
||||
bytes[5] = char(hiMant);
|
||||
bytes[6] = char(loMant >> 24);
|
||||
bytes[7] = char(loMant >> 16);
|
||||
bytes[8] = char(loMant >> 8);
|
||||
bytes[9] = char(loMant);
|
||||
}
|
||||
|
||||
double ConvertFromIeeeExtended(const unsigned char* bytes /* LCN */)
|
||||
{
|
||||
double f;
|
||||
int expon;
|
||||
unsigned long hiMant, loMant;
|
||||
|
||||
expon = ((bytes[0] & 0x7F) << 8) | (bytes[1] & 0xFF);
|
||||
hiMant = ((unsigned long)(bytes[2] & 0xFF) << 24)
|
||||
| ((unsigned long)(bytes[3] & 0xFF) << 16)
|
||||
| ((unsigned long)(bytes[4] & 0xFF) << 8)
|
||||
| ((unsigned long)(bytes[5] & 0xFF));
|
||||
loMant = ((unsigned long)(bytes[6] & 0xFF) << 24)
|
||||
| ((unsigned long)(bytes[7] & 0xFF) << 16)
|
||||
| ((unsigned long)(bytes[8] & 0xFF) << 8)
|
||||
| ((unsigned long)(bytes[9] & 0xFF));
|
||||
|
||||
if (expon == 0 && hiMant == 0 && loMant == 0) {
|
||||
f = 0;
|
||||
}
|
||||
else {
|
||||
if (expon == 0x7FFF) { /* Infinity or NaN */
|
||||
f = HUGE_VAL;
|
||||
}
|
||||
else {
|
||||
expon -= 16383;
|
||||
f = ldexp(UnsignedToFloat(hiMant), expon-=31);
|
||||
f += ldexp(UnsignedToFloat(loMant), expon-=32);
|
||||
}
|
||||
}
|
||||
|
||||
if (bytes[0] & 0x80)
|
||||
return -f;
|
||||
else
|
||||
return f;
|
||||
}
|
5
src/Utilities/IEEEExtended.h
Normal file
5
src/Utilities/IEEEExtended.h
Normal file
@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
void ConvertToIeeeExtended(double num, char* bytes);
|
||||
|
||||
double ConvertFromIeeeExtended(const unsigned char* bytes);
|
28
src/Utilities/StringUtils.cpp
Normal file
28
src/Utilities/StringUtils.cpp
Normal file
@ -0,0 +1,28 @@
|
||||
#include "Utilities/StringUtils.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
|
||||
std::string UppercaseCopy(const std::string& in)
|
||||
{
|
||||
std::string out;
|
||||
std::transform(
|
||||
in.begin(),
|
||||
in.end(),
|
||||
std::back_inserter(out),
|
||||
[](unsigned char c) -> unsigned char
|
||||
{
|
||||
return (c >= 'a' && c <= 'z') ? ('A' + c - 'a') : c;
|
||||
});
|
||||
return out;
|
||||
}
|
||||
|
||||
u8string AsU8(const std::string s)
|
||||
{
|
||||
return u8string(s.begin(), s.end());
|
||||
}
|
||||
|
||||
std::string FromU8(const u8string u8s)
|
||||
{
|
||||
return std::string(u8s.begin(), u8s.end());
|
||||
}
|
15
src/Utilities/StringUtils.h
Normal file
15
src/Utilities/StringUtils.h
Normal file
@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#if defined(__cplusplus) && __cplusplus > 201703L && defined(__cpp_lib_char8_t) && __cpp_lib_char8_t >= 201907L
|
||||
typedef std::u8string u8string;
|
||||
#else
|
||||
typedef std::string u8string;
|
||||
#endif
|
||||
|
||||
std::string UppercaseCopy(const std::string&);
|
||||
|
||||
u8string AsU8(const std::string);
|
||||
|
||||
std::string FromU8(const u8string);
|
80
src/Utilities/memstream.cpp
Normal file
80
src/Utilities/memstream.cpp
Normal file
@ -0,0 +1,80 @@
|
||||
#include "memstream.h"
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// membuf
|
||||
|
||||
membuf::membuf(char* p, size_t n)
|
||||
{
|
||||
begin = p;
|
||||
end = p + n;
|
||||
setg(p, p, p + n);
|
||||
setp(p, p + n);
|
||||
}
|
||||
|
||||
membuf::membuf(std::vector<char>& vector)
|
||||
: membuf(vector.data(), vector.size())
|
||||
{
|
||||
}
|
||||
|
||||
std::streambuf::pos_type membuf::seekoff(off_type off, std::ios_base::seekdir dir, std::ios_base::openmode which)
|
||||
{
|
||||
pos_type ret = 0;
|
||||
|
||||
if ((which & std::ios_base::in) > 0)
|
||||
{
|
||||
if (dir == std::ios_base::cur)
|
||||
{
|
||||
gbump((int32_t) off);
|
||||
}
|
||||
else if (dir == std::ios_base::end)
|
||||
{
|
||||
setg(begin, end + off, end);
|
||||
}
|
||||
else if (dir == std::ios_base::beg)
|
||||
{
|
||||
setg(begin, begin + off, end);
|
||||
}
|
||||
|
||||
ret = gptr() - eback();
|
||||
}
|
||||
|
||||
if ((which & std::ios_base::out) > 0)
|
||||
{
|
||||
if (dir == std::ios_base::cur)
|
||||
{
|
||||
pbump((int32_t) off);
|
||||
}
|
||||
else if (dir == std::ios_base::end)
|
||||
{
|
||||
setp(begin, end + off);
|
||||
}
|
||||
else if (dir == std::ios_base::beg)
|
||||
{
|
||||
setp(begin, begin + off);
|
||||
}
|
||||
|
||||
ret = pptr() - pbase();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::streambuf::pos_type membuf::seekpos(std::streampos pos, std::ios_base::openmode mode)
|
||||
{
|
||||
return seekoff(pos - pos_type(off_type(0)), std::ios_base::beg, mode);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// memstream
|
||||
|
||||
memstream::memstream(char* p, size_t n)
|
||||
: membuf(p, n)
|
||||
, std::iostream(this)
|
||||
{
|
||||
}
|
||||
|
||||
memstream::memstream(std::vector<char>& data)
|
||||
: membuf(data)
|
||||
, std::iostream(this)
|
||||
{
|
||||
}
|
34
src/Utilities/memstream.h
Normal file
34
src/Utilities/memstream.h
Normal file
@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <streambuf>
|
||||
#include <vector>
|
||||
|
||||
class membuf : public std::basic_streambuf<char>
|
||||
{
|
||||
char* begin;
|
||||
char* end;
|
||||
|
||||
public:
|
||||
membuf(char* p, size_t n);
|
||||
|
||||
explicit membuf(std::vector<char>&);
|
||||
|
||||
virtual ~membuf() override = default;
|
||||
|
||||
virtual pos_type seekoff(off_type off, std::ios_base::seekdir dir, std::ios_base::openmode which = std::ios_base::in) override;
|
||||
|
||||
virtual pos_type seekpos(std::streampos pos, std::ios_base::openmode mode) override;
|
||||
};
|
||||
|
||||
class memstream
|
||||
: membuf
|
||||
, public std::iostream
|
||||
{
|
||||
public:
|
||||
memstream(char* p, size_t n);
|
||||
|
||||
explicit memstream(std::vector<char>& data);
|
||||
|
||||
virtual ~memstream() = default;
|
||||
};
|
122
src/Utilities/structpack.cpp
Normal file
122
src/Utilities/structpack.cpp
Normal file
@ -0,0 +1,122 @@
|
||||
#include "structpack.h"
|
||||
#include <algorithm>
|
||||
#include <stdexcept>
|
||||
|
||||
static int Unpack(const char* format, char* buffer)
|
||||
{
|
||||
int totalBytes = 0;
|
||||
int repeat = 0;
|
||||
|
||||
for (const char* c2 = format; *c2; c2++)
|
||||
{
|
||||
char c = *c2;
|
||||
int fieldLength = -1;
|
||||
|
||||
switch (c)
|
||||
{
|
||||
case '>': // big endian indicator (for compat with python's struct) - OK just ignore
|
||||
continue;
|
||||
|
||||
case ' ':
|
||||
case '\r':
|
||||
case '\n':
|
||||
case '\t':
|
||||
continue;
|
||||
|
||||
case '0': case '1': case '2': case '3': case '4':
|
||||
case '5': case '6': case '7': case '8': case '9':
|
||||
if (repeat) repeat *= 10;
|
||||
repeat += c - '0';
|
||||
continue;
|
||||
|
||||
case 'x': // pad byte
|
||||
case 'c': // char
|
||||
case 'b': // signed char
|
||||
case 'B': // unsigned char
|
||||
case '?': // bool
|
||||
fieldLength = 1;
|
||||
break;
|
||||
|
||||
case 'h': // short
|
||||
case 'H': // unsigned short
|
||||
fieldLength = 2;
|
||||
break;
|
||||
|
||||
case 'i': // int
|
||||
case 'I': // unsigned int
|
||||
case 'l': // long
|
||||
case 'L': // unsigned long
|
||||
case 'f': // float
|
||||
fieldLength = 4;
|
||||
break;
|
||||
|
||||
case 'q': // long long
|
||||
case 'Q': // unsigned long long
|
||||
case 'd': // double
|
||||
fieldLength = 8;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw std::invalid_argument("unknown format char in structpack format");
|
||||
}
|
||||
|
||||
if (totalBytes % fieldLength != 0)
|
||||
{
|
||||
throw std::invalid_argument("illegal word alignment in structpack format");
|
||||
}
|
||||
|
||||
if (!repeat)
|
||||
repeat = 1;
|
||||
|
||||
bool doSwap = fieldLength > 1;
|
||||
|
||||
if (buffer)
|
||||
{
|
||||
if (doSwap)
|
||||
{
|
||||
for (int i = 0; i < repeat; i++)
|
||||
{
|
||||
std::reverse(buffer, buffer + fieldLength);
|
||||
buffer += fieldLength;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer += repeat * fieldLength;
|
||||
}
|
||||
}
|
||||
|
||||
totalBytes += repeat * fieldLength;
|
||||
repeat = 0;
|
||||
}
|
||||
|
||||
return totalBytes;
|
||||
}
|
||||
|
||||
int ByteswapStructs(const char* format, int structSize, int structCount, void* buffer)
|
||||
{
|
||||
char* byteBuffer = (char*) buffer;
|
||||
int totalBytes = 0;
|
||||
for (int i = 0; i < structCount; i++)
|
||||
{
|
||||
int newSize = Unpack(format, byteBuffer);
|
||||
byteBuffer += newSize;
|
||||
totalBytes += newSize;
|
||||
}
|
||||
if (totalBytes != structSize * structCount)
|
||||
{
|
||||
throw std::invalid_argument("unexpected length after byteswap");
|
||||
}
|
||||
return totalBytes;
|
||||
}
|
||||
|
||||
int ByteswapInts(int intSize, int intCount, void* buffer)
|
||||
{
|
||||
char* byteBuffer = (char*) buffer;
|
||||
for (int i = 0; i < intCount; i++)
|
||||
{
|
||||
std::reverse(byteBuffer, byteBuffer + intSize);
|
||||
byteBuffer += intSize;
|
||||
}
|
||||
return intCount * intSize;
|
||||
}
|
53
src/Utilities/structpack.h
Normal file
53
src/Utilities/structpack.h
Normal file
@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
template<typename T> T ByteswapScalar(T x)
|
||||
{
|
||||
#if TARGET_RT_BIGENDIAN
|
||||
return x;
|
||||
#else
|
||||
char* b = (char*)&x;
|
||||
std::reverse(b, b + sizeof(T));
|
||||
return x;
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
int ByteswapStructs(const char* format, int structSize, int structCount, void* buffer);
|
||||
|
||||
int ByteswapInts(int intSize, int intCount, void* buffer);
|
||||
|
||||
static inline uint16_t Byteswap16(const void* p)
|
||||
{
|
||||
uint16_t v;
|
||||
v =
|
||||
(*(const uint8_t*) p)
|
||||
| ((*(const uint8_t*) p + 1) << 8);
|
||||
return v;
|
||||
}
|
||||
|
||||
static inline uint32_t Byteswap32(const void* p)
|
||||
{
|
||||
uint32_t v;
|
||||
v =
|
||||
(*(const uint8_t*) p)
|
||||
| ((*(const uint8_t*) p + 1) << 8)
|
||||
| ((*(const uint8_t*) p + 2) << 16)
|
||||
| ((*(const uint8_t*) p + 3) << 24);
|
||||
return v;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
400
src/Video/Cinepak.cpp
Normal file
400
src/Video/Cinepak.cpp
Normal file
@ -0,0 +1,400 @@
|
||||
// Adapted from ffmpeg
|
||||
|
||||
// ---- Begin ffmpeg copyright notices ----
|
||||
|
||||
/*
|
||||
* Cinepak Video Decoder
|
||||
* Copyright (C) 2003 The FFmpeg project
|
||||
*
|
||||
* This file is part of FFmpeg.
|
||||
*
|
||||
* FFmpeg is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* FFmpeg is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with FFmpeg; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Cinepak video decoder
|
||||
* @author Ewald Snel <ewald@rambo.its.tudelft.nl>
|
||||
*
|
||||
* Cinepak colorspace support (c) 2013 Rl, Aetey Global Technologies AB
|
||||
* @author Cinepak colorspace, Rl, Aetey Global Technologies AB
|
||||
*/
|
||||
|
||||
// ---- End ffmpeg copyright notices ----
|
||||
|
||||
#include "Video/Cinepak.h"
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
|
||||
class CinepakException : public std::runtime_error
|
||||
{
|
||||
public:
|
||||
CinepakException(const char* m) : std::runtime_error(m)
|
||||
{}
|
||||
};
|
||||
|
||||
static uint8_t av_clip_uint8(int x)
|
||||
{
|
||||
return x > 255 ? 255 : (x < 0 ? 0 : x);
|
||||
}
|
||||
|
||||
static uint16_t AV_RB16(const uint8_t* in)
|
||||
{
|
||||
return ((uint16_t)in[0] << 8)
|
||||
| ((uint16_t)in[1]);
|
||||
}
|
||||
|
||||
static uint32_t AV_RB24(const uint8_t* in)
|
||||
{
|
||||
return ((uint32_t)in[0] << 16)
|
||||
| ((uint32_t)in[1] << 8)
|
||||
| (uint32_t)in[2];
|
||||
}
|
||||
|
||||
static uint32_t AV_RB32(const uint8_t* in)
|
||||
{
|
||||
return ((uint32_t)in[0] << 24)
|
||||
| ((uint32_t)in[1] << 16)
|
||||
| ((uint32_t)in[2] << 8)
|
||||
| (uint32_t)in[3];
|
||||
}
|
||||
|
||||
static void cinepak_decode_codebook (cvid_codebook *codebook,
|
||||
int chunk_id, int size, const uint8_t *data)
|
||||
{
|
||||
const uint8_t *eod = (data + size);
|
||||
uint32_t flag, mask;
|
||||
int i, n;
|
||||
uint8_t *p;
|
||||
|
||||
/* check if this chunk contains 4- or 6-element vectors */
|
||||
n = (chunk_id & 0x04) ? 4 : 6;
|
||||
flag = 0;
|
||||
mask = 0;
|
||||
|
||||
p = codebook[0];
|
||||
for (i=0; i < 256; i++) {
|
||||
if ((chunk_id & 0x01) && !(mask >>= 1)) {
|
||||
if ((data + 4) > eod)
|
||||
break;
|
||||
|
||||
flag = AV_RB32 (data);
|
||||
data += 4;
|
||||
mask = 0x80000000;
|
||||
}
|
||||
|
||||
if (!(chunk_id & 0x01) || (flag & mask)) {
|
||||
int k, kk;
|
||||
|
||||
if ((data + n) > eod)
|
||||
break;
|
||||
|
||||
for (k = 0; k < 4; ++k) {
|
||||
int r = *data++;
|
||||
for (kk = 0; kk < 3; ++kk)
|
||||
*p++ = r;
|
||||
}
|
||||
if (n == 6) {
|
||||
int r, g, b, u, v;
|
||||
u = *(int8_t *)data++;
|
||||
v = *(int8_t *)data++;
|
||||
p -= 12;
|
||||
for(k=0; k<4; ++k) {
|
||||
r = *p++ + v*2;
|
||||
g = *p++ - (u/2) - v;
|
||||
b = *p + u*2;
|
||||
p -= 2;
|
||||
*p++ = av_clip_uint8(r);
|
||||
*p++ = av_clip_uint8(g);
|
||||
*p++ = av_clip_uint8(b);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
p += 12;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void cinepak_decode_vectors (
|
||||
CinepakContext *s,
|
||||
cvid_strip *strip,
|
||||
int chunk_id,
|
||||
int size,
|
||||
const uint8_t *data)
|
||||
{
|
||||
const uint8_t *eod = (data + size);
|
||||
uint32_t flag, mask;
|
||||
uint8_t *cb0, *cb1, *cb2, *cb3;
|
||||
int x, y;
|
||||
uint8_t *ip0, *ip1, *ip2, *ip3;
|
||||
|
||||
flag = 0;
|
||||
mask = 0;
|
||||
|
||||
for (y=strip->y1; y < strip->y2; y+=4) {
|
||||
|
||||
/* take care of y dimension not being multiple of 4, such streams exist */
|
||||
ip0 = ip1 = ip2 = ip3 = s->frame_data0 +
|
||||
(strip->x1*3) + (y * s->frame_linesize0);
|
||||
if(s->avctx_height - y > 1) {
|
||||
ip1 = ip0 + s->frame_linesize0;
|
||||
if(s->avctx_height - y > 2) {
|
||||
ip2 = ip1 + s->frame_linesize0;
|
||||
if(s->avctx_height - y > 3) {
|
||||
ip3 = ip2 + s->frame_linesize0;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* to get the correct picture for not-multiple-of-4 cases let us fill each
|
||||
* block from the bottom up, thus possibly overwriting the bottommost line
|
||||
* more than once but ending with the correct data in place
|
||||
* (instead of in-loop checking) */
|
||||
|
||||
for (x=strip->x1; x < strip->x2; x+=4) {
|
||||
if ((chunk_id & 0x01) && !(mask >>= 1)) {
|
||||
if ((data + 4) > eod)
|
||||
throw CinepakException("invalid data");
|
||||
|
||||
flag = AV_RB32 (data);
|
||||
data += 4;
|
||||
mask = 0x80000000;
|
||||
}
|
||||
|
||||
if (!(chunk_id & 0x01) || (flag & mask)) {
|
||||
if (!(chunk_id & 0x02) && !(mask >>= 1)) {
|
||||
if ((data + 4) > eod)
|
||||
throw CinepakException("invalid data");
|
||||
|
||||
flag = AV_RB32 (data);
|
||||
data += 4;
|
||||
mask = 0x80000000;
|
||||
}
|
||||
|
||||
if ((chunk_id & 0x02) || (~flag & mask)) {
|
||||
uint8_t *p;
|
||||
if (data >= eod)
|
||||
throw CinepakException("invalid data");
|
||||
|
||||
p = strip->v1_codebook[*data++];
|
||||
|
||||
p += 6;
|
||||
memcpy(ip3 + 0, p, 3); memcpy(ip3 + 3, p, 3);
|
||||
memcpy(ip2 + 0, p, 3); memcpy(ip2 + 3, p, 3);
|
||||
p += 3; /* ... + 9 */
|
||||
memcpy(ip3 + 6, p, 3); memcpy(ip3 + 9, p, 3);
|
||||
memcpy(ip2 + 6, p, 3); memcpy(ip2 + 9, p, 3);
|
||||
p -= 9; /* ... + 0 */
|
||||
memcpy(ip1 + 0, p, 3); memcpy(ip1 + 3, p, 3);
|
||||
memcpy(ip0 + 0, p, 3); memcpy(ip0 + 3, p, 3);
|
||||
p += 3; /* ... + 3 */
|
||||
memcpy(ip1 + 6, p, 3); memcpy(ip1 + 9, p, 3);
|
||||
memcpy(ip0 + 6, p, 3); memcpy(ip0 + 9, p, 3);
|
||||
|
||||
} else if (flag & mask) {
|
||||
if ((data + 4) > eod)
|
||||
throw CinepakException("invalid data");
|
||||
|
||||
cb0 = strip->v4_codebook[*data++];
|
||||
cb1 = strip->v4_codebook[*data++];
|
||||
cb2 = strip->v4_codebook[*data++];
|
||||
cb3 = strip->v4_codebook[*data++];
|
||||
|
||||
memcpy(ip3 + 0, cb2 + 6, 6);
|
||||
memcpy(ip3 + 6, cb3 + 6, 6);
|
||||
memcpy(ip2 + 0, cb2 + 0, 6);
|
||||
memcpy(ip2 + 6, cb3 + 0, 6);
|
||||
memcpy(ip1 + 0, cb0 + 6, 6);
|
||||
memcpy(ip1 + 6, cb1 + 6, 6);
|
||||
memcpy(ip0 + 0, cb0 + 0, 6);
|
||||
memcpy(ip0 + 6, cb1 + 0, 6);
|
||||
}
|
||||
}
|
||||
|
||||
ip0 += 12; ip1 += 12;
|
||||
ip2 += 12; ip3 += 12;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void cinepak_decode_strip (
|
||||
CinepakContext *s,
|
||||
cvid_strip *strip,
|
||||
const uint8_t *data,
|
||||
int size)
|
||||
{
|
||||
const uint8_t *eod = (data + size);
|
||||
int chunk_id, chunk_size;
|
||||
|
||||
/* coordinate sanity checks */
|
||||
if (strip->x2 > s->width ||
|
||||
strip->y2 > s->height ||
|
||||
strip->x1 >= strip->x2 || strip->y1 >= strip->y2)
|
||||
throw CinepakException("invalid data");
|
||||
|
||||
while ((data + 4) <= eod) {
|
||||
chunk_id = data[0];
|
||||
chunk_size = AV_RB24 (&data[1]) - 4;
|
||||
if(chunk_size < 0)
|
||||
throw CinepakException("invalid data");
|
||||
|
||||
data += 4;
|
||||
chunk_size = ((data + chunk_size) > eod) ? (eod - data) : chunk_size;
|
||||
|
||||
switch (chunk_id) {
|
||||
|
||||
case 0x20:
|
||||
case 0x21:
|
||||
case 0x24:
|
||||
case 0x25:
|
||||
cinepak_decode_codebook (strip->v4_codebook, chunk_id, chunk_size, data);
|
||||
break;
|
||||
|
||||
case 0x22:
|
||||
case 0x23:
|
||||
case 0x26:
|
||||
case 0x27:
|
||||
cinepak_decode_codebook (strip->v1_codebook, chunk_id, chunk_size, data);
|
||||
break;
|
||||
|
||||
case 0x30:
|
||||
case 0x31:
|
||||
case 0x32:
|
||||
cinepak_decode_vectors (s, strip, chunk_id, chunk_size, data);
|
||||
return;
|
||||
}
|
||||
|
||||
data += chunk_size;
|
||||
}
|
||||
|
||||
throw CinepakException("invalid data");
|
||||
}
|
||||
|
||||
void cinepak_predecode_check (CinepakContext *s)
|
||||
{
|
||||
int num_strips;
|
||||
int encoded_buf_size;
|
||||
|
||||
num_strips = AV_RB16 (&s->data[8]);
|
||||
encoded_buf_size = AV_RB24(&s->data[1]);
|
||||
|
||||
if (s->size < encoded_buf_size)
|
||||
throw CinepakException("invalid data");
|
||||
|
||||
if (s->size < 10 /*+ s->sega_film_skip_bytes*/ + num_strips * 12)
|
||||
throw CinepakException("invalid data");
|
||||
|
||||
if (num_strips) {
|
||||
const uint8_t* data = s->data + 10; //+ s->sega_film_skip_bytes;
|
||||
int strip_size = AV_RB24 (data + 1);
|
||||
if (strip_size < 12 || strip_size > encoded_buf_size)
|
||||
throw CinepakException("invalid data");
|
||||
}
|
||||
}
|
||||
|
||||
static void cinepak_decode (CinepakContext *s)
|
||||
{
|
||||
const uint8_t *eod = (s->data + s->size);
|
||||
int i, strip_size, frame_flags, num_strips;
|
||||
int y0 = 0;
|
||||
|
||||
frame_flags = s->data[0];
|
||||
num_strips = AV_RB16 (&s->data[8]);
|
||||
|
||||
s->data += 10;
|
||||
|
||||
num_strips = std::min(num_strips, CINEPAK_MAX_STRIPS);
|
||||
|
||||
// s->frame->key_frame = 0;
|
||||
|
||||
for (i=0; i < num_strips; i++) {
|
||||
if ((s->data + 12) > eod)
|
||||
throw CinepakException("invalid data");
|
||||
|
||||
s->strips[i].id = s->data[0];
|
||||
/* zero y1 means "relative to the previous stripe" */
|
||||
if (!(s->strips[i].y1 = AV_RB16 (&s->data[4])))
|
||||
s->strips[i].y2 = (s->strips[i].y1 = y0) + AV_RB16 (&s->data[8]);
|
||||
else
|
||||
s->strips[i].y2 = AV_RB16 (&s->data[8]);
|
||||
s->strips[i].x1 = AV_RB16 (&s->data[6]);
|
||||
s->strips[i].x2 = AV_RB16 (&s->data[10]);
|
||||
|
||||
// if (s->strips[i].id == 0x10)
|
||||
// s->frame->key_frame = 1;
|
||||
|
||||
strip_size = AV_RB24 (&s->data[1]) - 12;
|
||||
if (strip_size < 0)
|
||||
throw CinepakException("invalid data");
|
||||
s->data += 12;
|
||||
strip_size = ((s->data + strip_size) > eod) ? (eod - s->data) : strip_size;
|
||||
|
||||
if ((i > 0) && !(frame_flags & 0x01)) {
|
||||
memcpy (s->strips[i].v4_codebook, s->strips[i-1].v4_codebook,
|
||||
sizeof(s->strips[i].v4_codebook));
|
||||
memcpy (s->strips[i].v1_codebook, s->strips[i-1].v1_codebook,
|
||||
sizeof(s->strips[i].v1_codebook));
|
||||
}
|
||||
|
||||
cinepak_decode_strip (s, &s->strips[i], s->data, strip_size);
|
||||
|
||||
s->data += strip_size;
|
||||
y0 = s->strips[i].y2;
|
||||
}
|
||||
}
|
||||
|
||||
CinepakContext::CinepakContext(int _width, int _height)
|
||||
: strips(CINEPAK_MAX_STRIPS)
|
||||
{
|
||||
avctx_width = _width;
|
||||
avctx_height = _height;
|
||||
width = (avctx_width + 3) & ~3;
|
||||
height = (avctx_height + 3) & ~3;
|
||||
|
||||
frame_data0 = new uint8_t[width * height * 3];
|
||||
frame_linesize0 = width*3;
|
||||
if (!frame_data0)
|
||||
throw CinepakException("couldn't allocate frame");
|
||||
}
|
||||
|
||||
void CinepakContext::DecodeFrame(const uint8_t* packet_data, const int packet_size)
|
||||
{
|
||||
if (packet_size < 10)
|
||||
throw CinepakException("invalid data -- input buffer too small?");
|
||||
|
||||
this->data = packet_data;
|
||||
this->size = packet_size;
|
||||
|
||||
int num_strips = AV_RB16 (&this->data[8]);
|
||||
|
||||
//Empty frame, do not waste time
|
||||
if (!num_strips)
|
||||
return;
|
||||
|
||||
cinepak_predecode_check(this);
|
||||
cinepak_decode(this);
|
||||
}
|
||||
|
||||
CinepakContext::~CinepakContext()
|
||||
{
|
||||
delete[] frame_data0;
|
||||
}
|
||||
|
||||
void CinepakContext::DumpFrameTGA(const char* outFN)
|
||||
{
|
||||
std::ofstream out(outFN, std::ios::out | std::ios::binary);
|
||||
uint16_t TGAhead[] = { 0, 2, 0, 0, 0, 0, (uint16_t)width, (uint16_t)height, 24 };
|
||||
out.write(reinterpret_cast<char*>(&TGAhead), sizeof(TGAhead));
|
||||
out.write(reinterpret_cast<char*>(frame_data0), width*height*3);
|
||||
}
|
42
src/Video/Cinepak.h
Normal file
42
src/Video/Cinepak.h
Normal file
@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#define CINEPAK_MAX_STRIPS 32
|
||||
|
||||
typedef uint8_t cvid_codebook[12];
|
||||
|
||||
struct cvid_strip
|
||||
{
|
||||
uint16_t id;
|
||||
uint16_t x1, y1;
|
||||
uint16_t x2, y2;
|
||||
cvid_codebook v4_codebook[256];
|
||||
cvid_codebook v1_codebook[256];
|
||||
};
|
||||
|
||||
struct CinepakContext
|
||||
{
|
||||
std::vector<cvid_strip> strips;
|
||||
|
||||
int avctx_width;
|
||||
int avctx_height;
|
||||
int width, height;
|
||||
|
||||
uint8_t* frame_data0;
|
||||
int frame_linesize0;
|
||||
|
||||
const uint8_t* data;
|
||||
int size;
|
||||
|
||||
public:
|
||||
CinepakContext(int avctx_width, int avctx_height);
|
||||
|
||||
~CinepakContext();
|
||||
|
||||
void DecodeFrame(const uint8_t* packet_data, const int packet_size);
|
||||
|
||||
void DumpFrameTGA(const char* outFN);
|
||||
};
|
||||
|
422
src/Video/moov.cpp
Normal file
422
src/Video/moov.cpp
Normal file
@ -0,0 +1,422 @@
|
||||
#include "Video/Cinepak.h"
|
||||
#include "PommeVideo.h"
|
||||
#include "PommeSound.h"
|
||||
#include "Utilities/BigEndianIStream.h"
|
||||
#include "PommeDebug.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
using namespace Pomme::Video;
|
||||
|
||||
class MoovException : public std::runtime_error
|
||||
{
|
||||
public:
|
||||
MoovException(const std::string m) : std::runtime_error(m)
|
||||
{}
|
||||
};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Utilities
|
||||
|
||||
struct ChunkInfo
|
||||
{
|
||||
UInt32 offset;
|
||||
UInt32 samplesPerChunk;
|
||||
};
|
||||
|
||||
static void MoovAssert(bool condition, const std::string msg)
|
||||
{
|
||||
if (!condition)
|
||||
{
|
||||
throw MoovException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static void Expect(Pomme::BigEndianIStream& f, const T value, const std::string msg)
|
||||
{
|
||||
T found = f.Read<T>();
|
||||
if (value != found)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "moov parser: " << msg << ": incorrect value: expected " << value << ", found " << found;
|
||||
throw MoovException(ss.str());
|
||||
}
|
||||
}
|
||||
|
||||
struct AtomGuard
|
||||
{
|
||||
Pomme::BigEndianIStream& f;
|
||||
FourCharCode fourCC;
|
||||
std::streampos end;
|
||||
|
||||
AtomGuard(Pomme::BigEndianIStream& inputStream, FourCharCode requiredAtomType)
|
||||
: f(inputStream)
|
||||
, fourCC(requiredAtomType)
|
||||
{
|
||||
auto start = f.Tell();
|
||||
auto atomSize = f.Read<UInt32>();
|
||||
Expect<FourCharCode>(f, requiredAtomType, "expected atom");
|
||||
end = start + (std::streampos) atomSize;
|
||||
}
|
||||
|
||||
~AtomGuard()
|
||||
{
|
||||
if (f.Tell() != end)
|
||||
{
|
||||
std::cerr << "WARNING: "
|
||||
<< (f.Tell() < end ? "didn't reach " : "read past ")
|
||||
<< "end of atom " << Pomme::FourCCString(fourCC) << "\n";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static void RequireAtomAndSkip(Pomme::BigEndianIStream& f, FourCharCode requiredAtomType)
|
||||
{
|
||||
AtomGuard atom(f, requiredAtomType);
|
||||
f.Goto(atom.end);
|
||||
}
|
||||
|
||||
static void SkipAtomIfPresent(Pomme::BigEndianIStream& f, const FourCharCode fourCCToSkip)
|
||||
{
|
||||
auto posGuard = f.GuardPos();
|
||||
|
||||
auto atomSize = f.Read<UInt32>();
|
||||
auto atomType = f.Read<FourCharCode>();
|
||||
|
||||
if (atomType == fourCCToSkip)
|
||||
{
|
||||
f.Skip(atomSize - 8);
|
||||
posGuard.Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Atom parsers
|
||||
|
||||
// Sample-to-Chunk
|
||||
static std::vector<ChunkInfo> Parse_stsc(Pomme::BigEndianIStream& f)
|
||||
{
|
||||
std::vector<ChunkInfo> chunkInfos;
|
||||
AtomGuard stsc(f, 'stsc');
|
||||
Expect<UInt32>(f, 0, "bad stsc version + flags");
|
||||
const auto numberOfEntries = f.Read<UInt32>();
|
||||
for (UInt32 i = 0; i < numberOfEntries; i++)
|
||||
{
|
||||
ChunkInfo ci = {};
|
||||
ci.offset = 0xFFFFFFFF;
|
||||
|
||||
const auto firstChunk = f.Read<UInt32>();
|
||||
ci.samplesPerChunk = f.Read<UInt32>();
|
||||
Expect<UInt32>(f, 1, "sample description ID");
|
||||
|
||||
// duplicate last chunk
|
||||
for (size_t j = chunkInfos.size(); j < firstChunk - 1; j++)
|
||||
{
|
||||
auto lastChunk = chunkInfos[chunkInfos.size() - 1];
|
||||
chunkInfos.push_back(lastChunk);
|
||||
}
|
||||
|
||||
chunkInfos.push_back(ci);
|
||||
}
|
||||
|
||||
return chunkInfos;
|
||||
}
|
||||
|
||||
// Sample Sizes
|
||||
static std::vector<UInt32> Parse_stsz(Pomme::BigEndianIStream& f)
|
||||
{
|
||||
std::vector<UInt32> sampleSizes;
|
||||
AtomGuard stsz(f, 'stsz');
|
||||
Expect<UInt32>(f, 0, "stsz version + flags");
|
||||
auto globalSampleSize = f.Read<UInt32>();
|
||||
auto numberOfEntries = f.Read<UInt32>();
|
||||
if (globalSampleSize == 0)
|
||||
{
|
||||
for (UInt32 i = 0; i < numberOfEntries; i++)
|
||||
{
|
||||
sampleSizes.push_back(f.Read<UInt32>());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sampleSizes.push_back(globalSampleSize);
|
||||
}
|
||||
return sampleSizes;
|
||||
}
|
||||
|
||||
// Chunk Offsets
|
||||
static void Parse_stco(Pomme::BigEndianIStream& f, std::vector<ChunkInfo>& chunkList)
|
||||
{
|
||||
AtomGuard stco(f, 'stco');
|
||||
Expect<UInt32>(f, 0, "stco version + flags");
|
||||
auto numberOfEntries = f.Read<UInt32>();
|
||||
for (UInt32 i = 0; i < numberOfEntries; i++)
|
||||
{
|
||||
auto chunkOffset = f.Read<UInt32>();
|
||||
chunkList.at(i).offset = chunkOffset;
|
||||
}
|
||||
}
|
||||
|
||||
static void Parse_mdia_vide(Pomme::BigEndianIStream& f, Movie& movie, UInt32 timeScale)
|
||||
{
|
||||
std::vector<ChunkInfo> chunkList;
|
||||
std::vector<UInt32> frameSizes;
|
||||
|
||||
{
|
||||
AtomGuard minf(f, 'minf');
|
||||
RequireAtomAndSkip(f, 'vmhd');
|
||||
RequireAtomAndSkip(f, 'hdlr');
|
||||
RequireAtomAndSkip(f, 'dinf');
|
||||
{
|
||||
AtomGuard stbl(f, 'stbl');
|
||||
{
|
||||
AtomGuard stsd(f, 'stsd');
|
||||
Expect<UInt32>(f, 0, "vide stsd version + flags");
|
||||
Expect<UInt32>(f, 1, "vide stsd number of entries");
|
||||
f.Skip(4); // UInt32 sampleDescriptionSize
|
||||
movie.videoFormat = f.Read<FourCharCode>();
|
||||
f.Skip(6); // reserved
|
||||
f.Skip(2); // data reference index
|
||||
Expect<UInt16>(f, 1, "vide stsd version");
|
||||
Expect<UInt16>(f, 1, "vide stsd revision level"); // docs say it should be 0, but in practice it's 1
|
||||
f.Skip(4); // vendor
|
||||
f.Skip(4); // temporal quality
|
||||
f.Skip(4); // spatial quality
|
||||
movie.width = f.Read<UInt16>();
|
||||
movie.height = f.Read<UInt16>();
|
||||
f.Skip(4); // horizontal resolution (ppi)
|
||||
f.Skip(4); // vertical resolution (ppi)
|
||||
Expect<UInt32>(f, 0, "vide stsd data size");
|
||||
Expect<UInt16>(f, 1, "vide stsd frame count per sample");
|
||||
f.Skip(32); // compressor name
|
||||
Expect<UInt16>(f, 24, "pixel depth");
|
||||
f.Skip(2); // color table ID
|
||||
}
|
||||
{
|
||||
AtomGuard stts(f, 'stts');
|
||||
Expect<UInt32>(f, 0, "stts version + flags");
|
||||
Expect<UInt32>(f, 1, "stts number of entries");
|
||||
f.Skip(4); // UInt32 sampleCount
|
||||
auto sampleDuration = f.Read<UInt32>();
|
||||
movie.videoFrameRate = (float) timeScale / sampleDuration;
|
||||
}
|
||||
SkipAtomIfPresent(f, 'stss');
|
||||
chunkList = Parse_stsc(f);
|
||||
frameSizes = Parse_stsz(f);
|
||||
Parse_stco(f, chunkList);
|
||||
SkipAtomIfPresent(f, 'stsh');
|
||||
}
|
||||
}
|
||||
|
||||
// std::cout << "vide: " << Pomme::FourCCString(movie.videoFormat) << ", " << movie.width << "x" << movie.height << ", " << movie.videoFrameRate << "fps\n";
|
||||
|
||||
// ------------------------------------
|
||||
// EXTRACT VIDEO FRAMES
|
||||
|
||||
// Set up guard
|
||||
auto guard = f.GuardPos();
|
||||
|
||||
size_t frameCounter = 0;
|
||||
for (const auto& chunk : chunkList)
|
||||
{
|
||||
f.Goto(chunk.offset);
|
||||
for (UInt32 s = 0; s < chunk.samplesPerChunk; s++, frameCounter++)
|
||||
{
|
||||
const size_t frameSize = frameSizes[frameCounter];
|
||||
movie.videoFrames.push(f.ReadBytes(frameSize));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void Parse_mdia_soun(Pomme::BigEndianIStream& f, Movie& movie)
|
||||
{
|
||||
std::vector<ChunkInfo> chunkList;
|
||||
|
||||
{
|
||||
AtomGuard minf(f, 'minf');
|
||||
SkipAtomIfPresent(f, 'smhd');
|
||||
SkipAtomIfPresent(f, 'hdlr');
|
||||
SkipAtomIfPresent(f, 'dinf');
|
||||
{
|
||||
AtomGuard stbl(f, 'stbl');
|
||||
{
|
||||
AtomGuard stsd(f, 'stsd');
|
||||
Expect<UInt32>(f, 0, "soun stsd version + flags");
|
||||
Expect<UInt32>(f, 1, "soun stsd number of entries");
|
||||
f.Skip(4); // UInt32 sampleDescriptionSize
|
||||
movie.audioFormat = f.Read<FourCharCode>();
|
||||
f.Skip(6); // reserved
|
||||
f.Skip(2); // data reference index
|
||||
Expect<UInt16>(f, 0, "soun stsd version");
|
||||
Expect<UInt16>(f, 0, "soun stsd revision level");
|
||||
f.Skip(4); // vendor
|
||||
movie.audioNChannels = f.Read<UInt16>();
|
||||
movie.audioBitDepth = f.Read<UInt16>();
|
||||
Expect<UInt16>(f, 0, "soun stsd compression ID");
|
||||
Expect<UInt16>(f, 0, "soun stsd packet size");
|
||||
Fixed fixedSampleRate = f.Read<Fixed>();
|
||||
movie.audioSampleRate = (static_cast<unsigned int>(fixedSampleRate) >> 16) & 0xFFFF;
|
||||
}
|
||||
{
|
||||
AtomGuard stts(f, 'stts');
|
||||
Expect<UInt32>(f, 0, "stts version + flags");
|
||||
Expect<UInt32>(f, 1, "stts number of entries");
|
||||
auto sampleCount = f.Read<UInt32>();
|
||||
Expect<UInt32>(f, 1, "soun stts: sample duration");
|
||||
movie.audioSampleCount = sampleCount;
|
||||
}
|
||||
SkipAtomIfPresent(f, 'stss');
|
||||
chunkList = Parse_stsc(f); //SkipAtomIfPresent(f, 'stsc');
|
||||
auto sampleSize = Parse_stsz(f); //SkipAtomIfPresent(f, 'stsz');
|
||||
MoovAssert(1 == sampleSize.size(), "in the sound track, all samples are expected to be of size 1");
|
||||
MoovAssert(1 == sampleSize[0], "in the sound track, all samples are expected to be of size 1");
|
||||
Parse_stco(f, chunkList);
|
||||
SkipAtomIfPresent(f, 'stsh');
|
||||
}
|
||||
}
|
||||
|
||||
// std::cout << "soun: " << Pomme::FourCCString(movie.audioFormat) << ", " << movie.audioNChannels << "ch, " << movie.audioBitDepth << "bit, " << movie.audioSampleRate << "Hz\n";
|
||||
|
||||
// ------------------------------------
|
||||
// EXTRACT AUDIO
|
||||
|
||||
bool isRawPCM = movie.audioFormat == 'twos' || movie.audioFormat == 'swot';
|
||||
std::unique_ptr<Pomme::Sound::Codec> codec = nullptr;
|
||||
if (!isRawPCM)
|
||||
{
|
||||
codec = Pomme::Sound::GetCodec(movie.audioFormat);
|
||||
}
|
||||
|
||||
// Set up position guard for rest of function
|
||||
auto guard = f.GuardPos();
|
||||
|
||||
// Unfortunately, Nanosaur's movies use version 0 of the 'stsd' atom for sound tracks.
|
||||
// This means that the "bytes per packet" count is not encoded into the file. (QTFF-2001, pp. 100-101)
|
||||
// We have to deduce it from the audio format.
|
||||
std::vector<int> chunkLengths;
|
||||
UInt32 compressedLength = 0;
|
||||
UInt32 totalSamples = 0;
|
||||
|
||||
for (const auto& chunk : chunkList)
|
||||
{
|
||||
totalSamples += chunk.samplesPerChunk;
|
||||
int chunkBytes = isRawPCM
|
||||
? movie.audioNChannels * chunk.samplesPerChunk * (movie.audioBitDepth / 8)
|
||||
: movie.audioNChannels * chunk.samplesPerChunk * codec->BytesPerPacket() / codec->SamplesPerPacket();
|
||||
chunkLengths.push_back(chunkBytes);
|
||||
compressedLength += chunkBytes;
|
||||
}
|
||||
|
||||
std::vector<char> compressedSoundData;
|
||||
compressedSoundData.reserve(compressedLength);
|
||||
char* out = compressedSoundData.data();
|
||||
|
||||
for (size_t i = 0; i < chunkList.size(); i++)
|
||||
{
|
||||
f.Goto(chunkList[i].offset);
|
||||
f.Read(out, chunkLengths[i]);
|
||||
out += chunkLengths[i];
|
||||
}
|
||||
|
||||
MoovAssert(out == compressedSoundData.data() + compressedLength, "csd length != total length");
|
||||
|
||||
if (isRawPCM)
|
||||
{
|
||||
movie.audioStream.SetBuffer(std::move(compressedSoundData));
|
||||
movie.audioStream.Init(
|
||||
movie.audioSampleRate,
|
||||
movie.audioBitDepth,
|
||||
movie.audioNChannels,
|
||||
movie.audioFormat == 'twos',
|
||||
movie.audioStream.GetBuffer(compressedLength));
|
||||
}
|
||||
else
|
||||
{
|
||||
auto outBytes = 2 * totalSamples * movie.audioNChannels;
|
||||
auto outSpan = movie.audioStream.GetBuffer(outBytes);
|
||||
auto inSpan = std::span(compressedSoundData.data(), compressedLength);
|
||||
codec->Decode(movie.audioNChannels, inSpan, outSpan);
|
||||
movie.audioStream.Init(movie.audioSampleRate, 16, movie.audioNChannels, false, outSpan);
|
||||
}
|
||||
}
|
||||
|
||||
static void Parse_mdia(Pomme::BigEndianIStream& f, Movie& movie)
|
||||
{
|
||||
AtomGuard mdia(f, 'mdia');
|
||||
|
||||
FourCharCode componentType;
|
||||
UInt32 timeScale;
|
||||
|
||||
{
|
||||
AtomGuard mdhd(f, 'mdhd');
|
||||
Expect<UInt32>(f, 0, "mdhd version + flags");
|
||||
f.Skip(4); // ctime
|
||||
f.Skip(4); // mtime
|
||||
timeScale = f.Read<UInt32>();
|
||||
f.Skip(4); // UInt32 duration
|
||||
f.Skip(2); // language
|
||||
f.Skip(2); // quality
|
||||
//std::cout << "mdhd: timeScale: " << timeScale << " units per second; duration: " << duration << " units\n";
|
||||
}
|
||||
|
||||
{
|
||||
AtomGuard hdlr(f, 'hdlr');
|
||||
f.Skip(4);
|
||||
Expect<FourCharCode>(f, 'mhlr', "mhlr required here");
|
||||
componentType = f.Read<FourCharCode>();
|
||||
f.Goto(hdlr.end);
|
||||
}
|
||||
|
||||
if ('vide' == componentType)
|
||||
{
|
||||
Parse_mdia_vide(f, movie, timeScale);
|
||||
}
|
||||
else if ('soun' == componentType)
|
||||
{
|
||||
Parse_mdia_soun(f, movie);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw MoovException("hdlr component type should be either vide or soun");
|
||||
}
|
||||
}
|
||||
|
||||
static void Parse_trak(Pomme::BigEndianIStream& f, Movie& movie)
|
||||
{
|
||||
AtomGuard trak(f, 'trak');
|
||||
|
||||
RequireAtomAndSkip(f, 'tkhd');
|
||||
|
||||
while (f.Tell() < trak.end)
|
||||
{
|
||||
auto start = f.Tell();
|
||||
auto atomSize = f.Read<UInt32>();
|
||||
auto atomType = f.Read<FourCharCode>();
|
||||
f.Goto(start);
|
||||
if (atomType != 'mdia')
|
||||
{
|
||||
f.Skip(atomSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
Parse_mdia(f, movie);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Moov parser
|
||||
|
||||
Movie Pomme::Video::ReadMoov(std::istream& theF)
|
||||
{
|
||||
Pomme::BigEndianIStream f(theF);
|
||||
Movie movie;
|
||||
AtomGuard moov(f, 'moov');
|
||||
RequireAtomAndSkip(f, 'mvhd');
|
||||
Parse_trak(f, movie); // Parse first track(video)
|
||||
Parse_trak(f, movie); // Parse second track (audio)
|
||||
SkipAtomIfPresent(f, 'udta');
|
||||
return movie;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user