mirror of https://github.com/jorio/Pomme.git
Import source from Nanosaur port
This commit is contained in:
parent
481ab70ef3
commit
5fc6598b47
|
@ -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()
|
|
@ -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
|
File diff suppressed because it is too large
Load Diff
|
@ -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
|
||||||
|
|
|
@ -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
|
|
@ -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);
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
};
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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;
|
||||||
|
};
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
{}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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];
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,
|
||||||
|
};
|
|
@ -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();
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
namespace Pomme::Platform::Windows
|
||||||
|
{
|
||||||
|
std::filesystem::path GetPreferencesFolder();
|
||||||
|
|
||||||
|
void SysBeep();
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
|
@ -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
|
|
@ -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;
|
||||||
|
}
|
|
@ -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
|
|
@ -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,
|
||||||
|
};
|
|
@ -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);
|
||||||
|
}
|
|
@ -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];
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace Pomme::Input
|
||||||
|
{
|
||||||
|
void Init();
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace Pomme::Time
|
||||||
|
{
|
||||||
|
void Init();
|
||||||
|
}
|
|
@ -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
|
|
@ -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);
|
||||||
|
}
|
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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());
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
|
@ -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);
|
||||||
|
|
||||||
|
}
|
|
@ -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];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
|
@ -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];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
void ConvertToIeeeExtended(double num, char* bytes);
|
||||||
|
|
||||||
|
double ConvertFromIeeeExtended(const unsigned char* bytes);
|
|
@ -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());
|
||||||
|
}
|
|
@ -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);
|
|
@ -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)
|
||||||
|
{
|
||||||
|
}
|
|
@ -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;
|
||||||
|
};
|
|
@ -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;
|
||||||
|
}
|
|
@ -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
|
|
@ -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);
|
||||||
|
}
|
|
@ -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);
|
||||||
|
};
|
||||||
|
|
|
@ -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…
Reference in New Issue