Import source from Nanosaur port

This commit is contained in:
Iliyas Jorio 2020-11-11 21:06:52 +01:00
parent 481ab70ef3
commit 5fc6598b47
56 changed files with 14488 additions and 0 deletions

87
CMakeLists.txt Normal file
View File

@ -0,0 +1,87 @@
cmake_minimum_required(VERSION 3.17)
set(CMAKE_CXX_STANDARD 20)
project(Pomme CXX)
set(POMME_SRCDIR src)
set(POMME_SOURCES
${POMME_SRCDIR}/Pomme.cpp
${POMME_SRCDIR}/Pomme.h
${POMME_SRCDIR}/PommeDebug.cpp
${POMME_SRCDIR}/PommeDebug.h
${POMME_SRCDIR}/PommeEnums.h
${POMME_SRCDIR}/PommeFiles.h
${POMME_SRCDIR}/PommeGraphics.h
${POMME_SRCDIR}/PommeInit.h
${POMME_SRCDIR}/PommeInput.h
${POMME_SRCDIR}/PommeSound.h
${POMME_SRCDIR}/PommeTime.h
${POMME_SRCDIR}/PommeTypes.h
${POMME_SRCDIR}/PommeVideo.h
${POMME_SRCDIR}/Files/Files.cpp
${POMME_SRCDIR}/Files/HostVolume.cpp
${POMME_SRCDIR}/Files/HostVolume.h
${POMME_SRCDIR}/Files/Resources.cpp
${POMME_SRCDIR}/Files/Volume.h
${POMME_SRCDIR}/Graphics/ARGBPixmap.cpp
${POMME_SRCDIR}/Graphics/Color.cpp
${POMME_SRCDIR}/Graphics/Graphics.cpp
${POMME_SRCDIR}/Graphics/PICT.cpp
${POMME_SRCDIR}/Graphics/SysFont.h
${POMME_SRCDIR}/Graphics/SystemPalettes.cpp
${POMME_SRCDIR}/Input/SDLInput.cpp
${POMME_SRCDIR}/Memory/Memory.cpp
${POMME_SRCDIR}/Sound/AIFF.cpp
${POMME_SRCDIR}/Sound/cmixer.cpp
${POMME_SRCDIR}/Sound/cmixer.h
${POMME_SRCDIR}/Sound/IMA4.cpp
${POMME_SRCDIR}/Sound/MACE.cpp
${POMME_SRCDIR}/Sound/SoundManager.cpp
${POMME_SRCDIR}/Sound/xlaw.cpp
${POMME_SRCDIR}/Time/TimeManager.cpp
${POMME_SRCDIR}/Utilities/BigEndianIStream.cpp
${POMME_SRCDIR}/Utilities/FixedPool.h
${POMME_SRCDIR}/Utilities/GrowablePool.h
${POMME_SRCDIR}/Utilities/IEEEExtended.cpp
${POMME_SRCDIR}/Utilities/IEEEExtended.h
${POMME_SRCDIR}/Utilities/memstream.cpp
${POMME_SRCDIR}/Utilities/memstream.h
${POMME_SRCDIR}/Utilities/StringUtils.cpp
${POMME_SRCDIR}/Utilities/StringUtils.h
${POMME_SRCDIR}/Utilities/structpack.cpp
${POMME_SRCDIR}/Utilities/structpack.h
${POMME_SRCDIR}/Video/Cinepak.cpp
${POMME_SRCDIR}/Video/Cinepak.h
${POMME_SRCDIR}/Video/moov.cpp
$<$<BOOL:${WIN32}>:${POMME_SRCDIR}/Platform/Windows/PommeWindows.cpp>
$<$<BOOL:${WIN32}>:${POMME_SRCDIR}/Platform/Windows/PommeWindows.h>
)
add_library(${PROJECT_NAME} ${POMME_SOURCES})
find_package(SDL2 REQUIRED)
target_include_directories(${PROJECT_NAME} PRIVATE
${SDL2_INCLUDE_DIRS}
${POMME_SRCDIR}
)
if(MSVC)
target_compile_options(${PROJECT_NAME} PRIVATE
/EHa
/W4
/wd4100 # unreferenced formal parameter
/wd4201 # nonstandard extension
/wd4244 # conversion from double to float
/wd4458 # declaration of variable hides class member
)
else()
target_compile_options(${PROJECT_NAME} PRIVATE
-Wall
-Wextra
-Wshadow
-Wno-multichar
-Wno-unused-parameter
-fexceptions
)
endif()

View File

@ -0,0 +1,9 @@
#pragma once
#if defined(__cplusplus) && __cplusplus >= 201703L && defined(__has_include) && __has_include(<filesystem>)
#include <filesystem>
namespace fs = std::filesystem;
#else
#include "CompilerSupport/filesystem_implementation.hpp"
namespace fs = ghc::filesystem;
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,8 @@
#pragma once
#if defined(__cplusplus) && __cplusplus >= 201703L && defined(__has_include) && __has_include(<span>)
#include <span>
#else
#include "CompilerSupport/span_implementation.hpp"
#endif

View File

@ -0,0 +1,611 @@
/*
This is an implementation of C++20's std::span
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/n4820.pdf
*/
// Copyright Tristan Brindle 2018.
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file ../../LICENSE_1_0.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
#ifndef TCB_SPAN_HPP_INCLUDED
#define TCB_SPAN_HPP_INCLUDED
#include <array>
#include <cstddef>
#include <cstdint>
#include <type_traits>
#ifndef TCB_SPAN_NO_EXCEPTIONS
// Attempt to discover whether we're being compiled with exception support
#if !(defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND))
#define TCB_SPAN_NO_EXCEPTIONS
#endif
#endif
#ifndef TCB_SPAN_NO_EXCEPTIONS
#include <cstdio>
#include <stdexcept>
#endif
// Various feature test macros
#ifndef TCB_SPAN_NAMESPACE_NAME
#define TCB_SPAN_NAMESPACE_NAME std
#endif
#if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)
#define TCB_SPAN_HAVE_CPP17
#endif
#if __cplusplus >= 201402L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201402L)
#define TCB_SPAN_HAVE_CPP14
#endif
namespace TCB_SPAN_NAMESPACE_NAME {
// Establish default contract checking behavior
#if !defined(TCB_SPAN_THROW_ON_CONTRACT_VIOLATION) && \
!defined(TCB_SPAN_TERMINATE_ON_CONTRACT_VIOLATION) && \
!defined(TCB_SPAN_NO_CONTRACT_CHECKING)
#if defined(NDEBUG) || !defined(TCB_SPAN_HAVE_CPP14)
#define TCB_SPAN_NO_CONTRACT_CHECKING
#else
#define TCB_SPAN_TERMINATE_ON_CONTRACT_VIOLATION
#endif
#endif
#if defined(TCB_SPAN_THROW_ON_CONTRACT_VIOLATION)
struct contract_violation_error : std::logic_error {
explicit contract_violation_error(const char* msg) : std::logic_error(msg)
{}
};
inline void contract_violation(const char* msg)
{
throw contract_violation_error(msg);
}
#elif defined(TCB_SPAN_TERMINATE_ON_CONTRACT_VIOLATION)
[[noreturn]] inline void contract_violation(const char* /*unused*/)
{
std::terminate();
}
#endif
#if !defined(TCB_SPAN_NO_CONTRACT_CHECKING)
#define TCB_SPAN_STRINGIFY(cond) #cond
#define TCB_SPAN_EXPECT(cond) \
cond ? (void) 0 : contract_violation("Expected " TCB_SPAN_STRINGIFY(cond))
#else
#define TCB_SPAN_EXPECT(cond)
#endif
#if defined(TCB_SPAN_HAVE_CPP17) || defined(__cpp_inline_variables)
#define TCB_SPAN_INLINE_VAR inline
#else
#define TCB_SPAN_INLINE_VAR
#endif
#if defined(TCB_SPAN_HAVE_CPP14) || \
(defined(__cpp_constexpr) && __cpp_constexpr >= 201304)
#define TCB_SPAN_HAVE_CPP14_CONSTEXPR
#endif
#if defined(TCB_SPAN_HAVE_CPP14_CONSTEXPR)
#define TCB_SPAN_CONSTEXPR14 constexpr
#else
#define TCB_SPAN_CONSTEXPR14
#endif
#if defined(TCB_SPAN_HAVE_CPP14_CONSTEXPR) && \
(!defined(_MSC_VER) || _MSC_VER > 1900)
#define TCB_SPAN_CONSTEXPR_ASSIGN constexpr
#else
#define TCB_SPAN_CONSTEXPR_ASSIGN
#endif
#if defined(TCB_SPAN_NO_CONTRACT_CHECKING)
#define TCB_SPAN_CONSTEXPR11 constexpr
#else
#define TCB_SPAN_CONSTEXPR11 TCB_SPAN_CONSTEXPR14
#endif
#if defined(TCB_SPAN_HAVE_CPP17) || defined(__cpp_deduction_guides)
#define TCB_SPAN_HAVE_DEDUCTION_GUIDES
#endif
#if defined(TCB_SPAN_HAVE_CPP17) || defined(__cpp_lib_byte)
#define TCB_SPAN_HAVE_STD_BYTE
#endif
#if defined(TCB_SPAN_HAVE_CPP17) || defined(__cpp_lib_array_constexpr)
#define TCB_SPAN_HAVE_CONSTEXPR_STD_ARRAY_ETC
#endif
#if defined(TCB_SPAN_HAVE_CONSTEXPR_STD_ARRAY_ETC)
#define TCB_SPAN_ARRAY_CONSTEXPR constexpr
#else
#define TCB_SPAN_ARRAY_CONSTEXPR
#endif
#ifdef TCB_SPAN_HAVE_STD_BYTE
using byte = std::byte;
#else
using byte = unsigned char;
#endif
#if defined(TCB_SPAN_HAVE_CPP17)
#define TCB_SPAN_NODISCARD [[nodiscard]]
#else
#define TCB_SPAN_NODISCARD
#endif
TCB_SPAN_INLINE_VAR constexpr std::size_t dynamic_extent = SIZE_MAX;
template <typename ElementType, std::size_t Extent = dynamic_extent>
class span;
namespace detail {
template <typename E, std::size_t S>
struct span_storage {
constexpr span_storage() noexcept = default;
constexpr span_storage(E* p_ptr, std::size_t /*unused*/) noexcept
: ptr(p_ptr)
{}
E* ptr = nullptr;
static constexpr std::size_t size = S;
};
template <typename E>
struct span_storage<E, dynamic_extent> {
constexpr span_storage() noexcept = default;
constexpr span_storage(E* p_ptr, std::size_t p_size) noexcept
: ptr(p_ptr), size(p_size)
{}
E* ptr = nullptr;
std::size_t size = 0;
};
// Reimplementation of C++17 std::size() and std::data()
#if defined(TCB_SPAN_HAVE_CPP17) || \
defined(__cpp_lib_nonmember_container_access)
using std::data;
using std::size;
#else
template <class C>
constexpr auto size(const C& c) -> decltype(c.size())
{
return c.size();
}
template <class T, std::size_t N>
constexpr std::size_t size(const T (&)[N]) noexcept
{
return N;
}
template <class C>
constexpr auto data(C& c) -> decltype(c.data())
{
return c.data();
}
template <class C>
constexpr auto data(const C& c) -> decltype(c.data())
{
return c.data();
}
template <class T, std::size_t N>
constexpr T* data(T (&array)[N]) noexcept
{
return array;
}
template <class E>
constexpr const E* data(std::initializer_list<E> il) noexcept
{
return il.begin();
}
#endif // TCB_SPAN_HAVE_CPP17
#if defined(TCB_SPAN_HAVE_CPP17) || defined(__cpp_lib_void_t)
using std::void_t;
#else
template <typename...>
using void_t = void;
#endif
template <typename T>
using uncvref_t =
typename std::remove_cv<typename std::remove_reference<T>::type>::type;
template <typename>
struct is_span : std::false_type {};
template <typename T, std::size_t S>
struct is_span<span<T, S>> : std::true_type {};
template <typename>
struct is_std_array : std::false_type {};
template <typename T, std::size_t N>
struct is_std_array<std::array<T, N>> : std::true_type {};
template <typename, typename = void>
struct has_size_and_data : std::false_type {};
template <typename T>
struct has_size_and_data<T, void_t<decltype(detail::size(std::declval<T>())),
decltype(detail::data(std::declval<T>()))>>
: std::true_type {};
template <typename C, typename U = uncvref_t<C>>
struct is_container {
static constexpr bool value =
!is_span<U>::value && !is_std_array<U>::value &&
!std::is_array<U>::value && has_size_and_data<C>::value;
};
template <typename T>
using remove_pointer_t = typename std::remove_pointer<T>::type;
template <typename, typename, typename = void>
struct is_container_element_type_compatible : std::false_type {};
template <typename T, typename E>
struct is_container_element_type_compatible<
T, E,
typename std::enable_if<
!std::is_same<typename std::remove_cv<decltype(
detail::data(std::declval<T>()))>::type,
void>::value>::type>
: std::is_convertible<
remove_pointer_t<decltype(detail::data(std::declval<T>()))> (*)[],
E (*)[]> {};
template <typename, typename = size_t>
struct is_complete : std::false_type {};
template <typename T>
struct is_complete<T, decltype(sizeof(T))> : std::true_type {};
} // namespace detail
template <typename ElementType, std::size_t Extent>
class span {
static_assert(std::is_object<ElementType>::value,
"A span's ElementType must be an object type (not a "
"reference type or void)");
static_assert(detail::is_complete<ElementType>::value,
"A span's ElementType must be a complete type (not a forward "
"declaration)");
static_assert(!std::is_abstract<ElementType>::value,
"A span's ElementType cannot be an abstract class type");
using storage_type = detail::span_storage<ElementType, Extent>;
public:
// constants and types
using element_type = ElementType;
using value_type = typename std::remove_cv<ElementType>::type;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
using pointer = element_type*;
using const_pointer = const element_type*;
using reference = element_type&;
using const_reference = const element_type&;
using iterator = pointer;
using reverse_iterator = std::reverse_iterator<iterator>;
static constexpr size_type extent = Extent;
// [span.cons], span constructors, copy, assignment, and destructor
template <
std::size_t E = Extent,
typename std::enable_if<(E == dynamic_extent || E <= 0), int>::type = 0>
constexpr span() noexcept
{}
TCB_SPAN_CONSTEXPR11 span(pointer ptr, size_type count)
: storage_(ptr, count)
{
TCB_SPAN_EXPECT(extent == dynamic_extent || count == extent);
}
TCB_SPAN_CONSTEXPR11 span(pointer first_elem, pointer last_elem)
: storage_(first_elem, last_elem - first_elem)
{
TCB_SPAN_EXPECT(extent == dynamic_extent ||
last_elem - first_elem ==
static_cast<std::ptrdiff_t>(extent));
}
template <std::size_t N, std::size_t E = Extent,
typename std::enable_if<
(E == dynamic_extent || N == E) &&
detail::is_container_element_type_compatible<
element_type (&)[N], ElementType>::value,
int>::type = 0>
constexpr span(element_type (&arr)[N]) noexcept : storage_(arr, N)
{}
template <std::size_t N, std::size_t E = Extent,
typename std::enable_if<
(E == dynamic_extent || N == E) &&
detail::is_container_element_type_compatible<
std::array<value_type, N>&, ElementType>::value,
int>::type = 0>
TCB_SPAN_ARRAY_CONSTEXPR span(std::array<value_type, N>& arr) noexcept
: storage_(arr.data(), N)
{}
template <std::size_t N, std::size_t E = Extent,
typename std::enable_if<
(E == dynamic_extent || N == E) &&
detail::is_container_element_type_compatible<
const std::array<value_type, N>&, ElementType>::value,
int>::type = 0>
TCB_SPAN_ARRAY_CONSTEXPR span(const std::array<value_type, N>& arr) noexcept
: storage_(arr.data(), N)
{}
template <
typename Container, std::size_t E = Extent,
typename std::enable_if<
E == dynamic_extent && detail::is_container<Container>::value &&
detail::is_container_element_type_compatible<
Container&, ElementType>::value,
int>::type = 0>
constexpr span(Container& cont)
: storage_(detail::data(cont), detail::size(cont))
{}
template <
typename Container, std::size_t E = Extent,
typename std::enable_if<
E == dynamic_extent && detail::is_container<Container>::value &&
detail::is_container_element_type_compatible<
const Container&, ElementType>::value,
int>::type = 0>
constexpr span(const Container& cont)
: storage_(detail::data(cont), detail::size(cont))
{}
constexpr span(const span& other) noexcept = default;
template <typename OtherElementType, std::size_t OtherExtent,
typename std::enable_if<
(Extent == OtherExtent || Extent == dynamic_extent) &&
std::is_convertible<OtherElementType (*)[],
ElementType (*)[]>::value,
int>::type = 0>
constexpr span(const span<OtherElementType, OtherExtent>& other) noexcept
: storage_(other.data(), other.size())
{}
~span() noexcept = default;
TCB_SPAN_CONSTEXPR_ASSIGN span&
operator=(const span& other) noexcept = default;
// [span.sub], span subviews
template <std::size_t Count>
TCB_SPAN_CONSTEXPR11 span<element_type, Count> first() const
{
TCB_SPAN_EXPECT(Count <= size());
return {data(), Count};
}
template <std::size_t Count>
TCB_SPAN_CONSTEXPR11 span<element_type, Count> last() const
{
TCB_SPAN_EXPECT(Count <= size());
return {data() + (size() - Count), Count};
}
template <std::size_t Offset, std::size_t Count = dynamic_extent>
using subspan_return_t =
span<ElementType, Count != dynamic_extent
? Count
: (Extent != dynamic_extent ? Extent - Offset
: dynamic_extent)>;
template <std::size_t Offset, std::size_t Count = dynamic_extent>
TCB_SPAN_CONSTEXPR11 subspan_return_t<Offset, Count> subspan() const
{
TCB_SPAN_EXPECT(Offset <= size() &&
(Count == dynamic_extent || Offset + Count <= size()));
return {data() + Offset,
Count != dynamic_extent ? Count : size() - Offset};
}
TCB_SPAN_CONSTEXPR11 span<element_type, dynamic_extent>
first(size_type count) const
{
TCB_SPAN_EXPECT(count <= size());
return {data(), count};
}
TCB_SPAN_CONSTEXPR11 span<element_type, dynamic_extent>
last(size_type count) const
{
TCB_SPAN_EXPECT(count <= size());
return {data() + (size() - count), count};
}
TCB_SPAN_CONSTEXPR11 span<element_type, dynamic_extent>
subspan(size_type offset, size_type count = dynamic_extent) const
{
TCB_SPAN_EXPECT(offset <= size() &&
(count == dynamic_extent || offset + count <= size()));
return {data() + offset,
count == dynamic_extent ? size() - offset : count};
}
// [span.obs], span observers
constexpr size_type size() const noexcept { return storage_.size; }
constexpr size_type size_bytes() const noexcept
{
return size() * sizeof(element_type);
}
TCB_SPAN_NODISCARD constexpr bool empty() const noexcept
{
return size() == 0;
}
// [span.elem], span element access
TCB_SPAN_CONSTEXPR11 reference operator[](size_type idx) const
{
TCB_SPAN_EXPECT(idx < size());
return *(data() + idx);
}
TCB_SPAN_CONSTEXPR11 reference front() const
{
TCB_SPAN_EXPECT(!empty());
return *data();
}
TCB_SPAN_CONSTEXPR11 reference back() const
{
TCB_SPAN_EXPECT(!empty());
return *(data() + (size() - 1));
}
constexpr pointer data() const noexcept { return storage_.ptr; }
// [span.iterators], span iterator support
constexpr iterator begin() const noexcept { return data(); }
constexpr iterator end() const noexcept { return data() + size(); }
TCB_SPAN_ARRAY_CONSTEXPR reverse_iterator rbegin() const noexcept
{
return reverse_iterator(end());
}
TCB_SPAN_ARRAY_CONSTEXPR reverse_iterator rend() const noexcept
{
return reverse_iterator(begin());
}
private:
storage_type storage_{};
};
#ifdef TCB_SPAN_HAVE_DEDUCTION_GUIDES
/* Deduction Guides */
template <class T, size_t N>
span(T (&)[N])->span<T, N>;
template <class T, size_t N>
span(std::array<T, N>&)->span<T, N>;
template <class T, size_t N>
span(const std::array<T, N>&)->span<const T, N>;
template <class Container>
span(Container&)->span<typename Container::value_type>;
template <class Container>
span(const Container&)->span<const typename Container::value_type>;
#endif // TCB_HAVE_DEDUCTION_GUIDES
template <typename ElementType, std::size_t Extent>
constexpr span<ElementType, Extent>
make_span(span<ElementType, Extent> s) noexcept
{
return s;
}
template <typename T, std::size_t N>
constexpr span<T, N> make_span(T (&arr)[N]) noexcept
{
return {arr};
}
template <typename T, std::size_t N>
TCB_SPAN_ARRAY_CONSTEXPR span<T, N> make_span(std::array<T, N>& arr) noexcept
{
return {arr};
}
template <typename T, std::size_t N>
TCB_SPAN_ARRAY_CONSTEXPR span<const T, N>
make_span(const std::array<T, N>& arr) noexcept
{
return {arr};
}
template <typename Container>
constexpr span<typename Container::value_type> make_span(Container& cont)
{
return {cont};
}
template <typename Container>
constexpr span<const typename Container::value_type>
make_span(const Container& cont)
{
return {cont};
}
template <typename ElementType, std::size_t Extent>
span<const byte, ((Extent == dynamic_extent) ? dynamic_extent
: sizeof(ElementType) * Extent)>
as_bytes(span<ElementType, Extent> s) noexcept
{
return {reinterpret_cast<const byte*>(s.data()), s.size_bytes()};
}
template <
class ElementType, size_t Extent,
typename std::enable_if<!std::is_const<ElementType>::value, int>::type = 0>
span<byte, ((Extent == dynamic_extent) ? dynamic_extent
: sizeof(ElementType) * Extent)>
as_writable_bytes(span<ElementType, Extent> s) noexcept
{
return {reinterpret_cast<byte*>(s.data()), s.size_bytes()};
}
template <std::size_t N, typename E, std::size_t S>
constexpr auto get(span<E, S> s) -> decltype(s[N])
{
return s[N];
}
} // namespace TCB_SPAN_NAMESPACE_NAME
namespace std {
template <typename ElementType, size_t Extent>
class tuple_size<TCB_SPAN_NAMESPACE_NAME::span<ElementType, Extent>>
: public integral_constant<size_t, Extent> {};
template <typename ElementType>
class tuple_size<TCB_SPAN_NAMESPACE_NAME::span<
ElementType, TCB_SPAN_NAMESPACE_NAME::dynamic_extent>>; // not defined
template <size_t I, typename ElementType, size_t Extent>
class tuple_element<I, TCB_SPAN_NAMESPACE_NAME::span<ElementType, Extent>> {
public:
static_assert(Extent != TCB_SPAN_NAMESPACE_NAME::dynamic_extent &&
I < Extent,
"");
using type = ElementType;
};
} // end namespace std
#endif // TCB_SPAN_HPP_INCLUDED

337
src/Files/Files.cpp Normal file
View File

@ -0,0 +1,337 @@
#include "Pomme.h"
#include "Utilities/BigEndianIStream.h"
#include "Utilities/GrowablePool.h"
#include "Utilities/memstream.h"
#include "PommeFiles.h"
#include "Files/Volume.h"
#include "Files/HostVolume.h"
#include <iostream>
#include <sstream>
#include "CompilerSupport/filesystem.h"
#if _WIN32
#include "Platform/Windows/PommeWindows.h"
#endif
using namespace Pomme;
using namespace Pomme::Files;
#define LOG POMME_GENLOG(POMME_DEBUG_FILES, "FILE")
constexpr int MAX_VOLUMES = 32767; // vRefNum is a signed short
//-----------------------------------------------------------------------------
// State
static Pomme::GrowablePool<std::unique_ptr<ForkHandle>, SInt16, 0x7FFF> openFiles;
static std::vector<std::unique_ptr<Volume>> volumes;
//-----------------------------------------------------------------------------
// Utilities
bool Pomme::Files::IsRefNumLegal(short refNum)
{
return openFiles.IsAllocated(refNum);
}
std::iostream& Pomme::Files::GetStream(short refNum)
{
if (!IsRefNumLegal(refNum))
{
throw std::runtime_error("illegal refNum");
}
return openFiles[refNum]->GetStream();
}
void Pomme::Files::CloseStream(short refNum)
{
if (!IsRefNumLegal(refNum))
{
throw std::runtime_error("illegal refNum");
}
openFiles[refNum].reset(nullptr);
openFiles.Dispose(refNum);
LOG << "Stream #" << refNum << " closed\n";
}
bool Pomme::Files::IsStreamOpen(short refNum)
{
if (!IsRefNumLegal(refNum))
{
throw std::runtime_error("illegal refNum");
}
return openFiles[refNum].get() != nullptr;
// return openFiles[refNum].stream.is_open();
}
bool Pomme::Files::IsStreamPermissionAllowed(short refNum, char perm)
{
if (!IsRefNumLegal(refNum))
{
throw std::runtime_error("illegal refNum");
}
return (perm & openFiles[refNum]->permission) == perm;
}
//-----------------------------------------------------------------------------
// Init
void Pomme::Files::Init()
{
auto hostVolume = std::make_unique<HostVolume>(0);
volumes.push_back(std::move(hostVolume));
short systemRefNum = openFiles.Alloc();
if (systemRefNum != 0)
{
throw std::logic_error("expecting 0 for system refnum");
}
}
//-----------------------------------------------------------------------------
// Implementation
bool IsVolumeLegal(short vRefNum)
{
return vRefNum >= 0 && (unsigned short) vRefNum < volumes.size();
}
OSErr FSMakeFSSpec(short vRefNum, long dirID, const char* cstrFileName, FSSpec* spec)
{
if (!IsVolumeLegal(vRefNum))
return nsvErr;
return volumes.at(vRefNum)->FSMakeFSSpec(dirID, cstrFileName, spec);
}
static OSErr OpenFork(const FSSpec* spec, ForkType forkType, char permission, short* refNum)
{
if (openFiles.IsFull())
return tmfoErr;
if (!IsVolumeLegal(spec->vRefNum))
return nsvErr;
short newRefNum = openFiles.Alloc();
auto& handlePtr = openFiles[newRefNum];
OSErr rc = volumes.at(spec->vRefNum)->OpenFork(spec, forkType, permission, handlePtr);
if (rc != noErr)
{
openFiles.Dispose(newRefNum);
newRefNum = -1;
LOG << "Failed to open " << spec->cName << "\n";
}
else
{
LOG << "Stream #" << newRefNum << " opened: " << spec->cName << ", " << (forkType == DataFork ? "data" : "rsrc") << "\n";
}
if (refNum)
{
*refNum = newRefNum;
}
return rc;
}
OSErr FSpOpenDF(const FSSpec* spec, char permission, short* refNum)
{
return OpenFork(spec, ForkType::DataFork, permission, refNum);
}
OSErr FSpOpenRF(const FSSpec* spec, char permission, short* refNum)
{
return OpenFork(spec, ForkType::ResourceFork, permission, refNum);
}
OSErr FindFolder(short vRefNum, OSType folderType, Boolean createFolder, short* foundVRefNum, long* foundDirID)
{
if (vRefNum != kOnSystemDisk)
{
throw std::runtime_error("FindFolder only supports kOnSystemDisk");
}
fs::path path;
switch (folderType)
{
case kPreferencesFolderType:
{
#ifdef _WIN32
path = Pomme::Platform::Windows::GetPreferencesFolder();
#elif defined(__APPLE__)
const char *home = getenv("HOME");
if (!home) {
return fnfErr;
}
path = fs::path(home) / "Library" / "Preferences";
#else
const char* home = getenv("XDG_CONFIG_HOME");
if (home)
{
path = std::filesystem::path(home);
}
else
{
home = getenv("HOME");
if (!home)
{
return fnfErr;
}
path = std::filesystem::path(home) / ".config";
}
#endif
break;
}
default:
TODO2("folder type '" << Pomme::FourCCString(folderType) << "' isn't supported yet");
return fnfErr;
}
path = path.lexically_normal();
bool exists = fs::exists(path);
if (exists && !fs::is_directory(path))
{
return dupFNErr;
}
if (!exists && createFolder)
{
fs::create_directories(path);
}
*foundVRefNum = 0;//GetVolumeID(path);
*foundDirID = dynamic_cast<HostVolume*>(volumes.at(0).get())->GetDirectoryID(path);
return noErr;
}
OSErr DirCreate(short vRefNum, long parentDirID, const char* cstrDirectoryName, long* createdDirID)
{
return IsVolumeLegal(vRefNum)
? volumes.at(vRefNum)->DirCreate(parentDirID, cstrDirectoryName, createdDirID)
: (OSErr)nsvErr;
}
OSErr FSpCreate(const FSSpec* spec, OSType creator, OSType fileType, ScriptCode scriptTag)
{
return IsVolumeLegal(spec->vRefNum)
? volumes.at(spec->vRefNum)->FSpCreate(spec, creator, fileType, scriptTag)
: (OSErr)nsvErr;
}
OSErr FSpDelete(const FSSpec* spec)
{
return IsVolumeLegal(spec->vRefNum)
? volumes.at(spec->vRefNum)->FSpDelete(spec)
: (OSErr)nsvErr;
}
OSErr ResolveAlias(const FSSpec* spec, AliasHandle alias, FSSpec* target, Boolean* wasChanged)
{
*wasChanged = false;
int aliasSize = GetHandleSize(alias);
// the target FN is at offset 50, and the target FN is a Str63, so 50+64
if (aliasSize < 50 + 64)
{
std::cerr << "unexpected size of alias: " << aliasSize << "\n";
return unimpErr;
}
memstream istr(*alias, GetHandleSize(alias));
Pomme::BigEndianIStream f(istr);
f.Skip(4); // application signature
if (f.Read<UInt16>() != aliasSize)
{
std::cerr << "unexpected size field in alias\n";
return unimpErr;
}
if (f.Read<UInt16>() != 2)
{
std::cerr << "unexpected alias version number\n";
return unimpErr;
}
auto kind = f.Read<UInt16>();
if (kind > 2)
{
std::cerr << "unsupported alias kind " << kind << "\n";
return unimpErr;
}
f.Skip(28); // volume name pascal string
f.Skip(4); // volume creation date
f.Skip(2); // volume signature (RW, BD, H+)
f.Skip(2); // drive type (0=HD, 1=network, 2=400K FD, 3=800K FD, 4=1.4M FD, 5=misc.ejectable)
f.Skip(4); // parent directory ID
auto targetFilename = f.ReadPascalString_FixedLengthRecord(63);
return FSMakeFSSpec(spec->vRefNum, spec->parID, targetFilename.c_str(), target);
}
OSErr FSRead(short refNum, long* count, Ptr buffPtr)
{
if (*count < 0) return paramErr;
if (!IsRefNumLegal(refNum)) return rfNumErr;
if (!IsStreamOpen(refNum)) return fnOpnErr;
if (!IsStreamPermissionAllowed(refNum, fsRdPerm)) return ioErr;
auto& f = GetStream(refNum);
f.read(buffPtr, *count);
*count = (long) f.gcount();
if (f.eof()) return eofErr;
return noErr;
}
OSErr FSWrite(short refNum, long* count, Ptr buffPtr)
{
if (*count < 0) return paramErr;
if (!IsRefNumLegal(refNum)) return rfNumErr;
if (!IsStreamOpen(refNum)) return fnOpnErr;
if (!IsStreamPermissionAllowed(refNum, fsWrPerm)) return wrPermErr;
auto& f = GetStream(refNum);
f.write(buffPtr, *count);
return noErr;
}
OSErr FSClose(short refNum)
{
if (!IsRefNumLegal(refNum))
return rfNumErr;
if (!IsStreamOpen(refNum))
return fnOpnErr;
CloseStream(refNum);
return noErr;
}
OSErr GetEOF(short refNum, long* logEOF)
{
if (!IsRefNumLegal(refNum)) return rfNumErr;
if (!IsStreamOpen(refNum)) return fnOpnErr;
auto& f = GetStream(refNum);
StreamPosGuard guard(f);
f.seekg(0, std::ios::end);
*logEOF = (long) f.tellg();
return noErr;
}
OSErr SetEOF(short refNum, long logEOF)
{
TODO();
return unimpErr;
}
FSSpec Pomme::Files::HostPathToFSSpec(const fs::path& fullPath)
{
return dynamic_cast<HostVolume*>(volumes[0].get())->ToFSSpec(fullPath);
}

343
src/Files/HostVolume.cpp Normal file
View File

@ -0,0 +1,343 @@
#include "PommeEnums.h"
#include "PommeDebug.h"
#include "PommeFiles.h"
#include "Files/HostVolume.h"
#include "Utilities/BigEndianIStream.h"
#include "Utilities/StringUtils.h"
#include <fstream>
#include <iostream>
#define LOG POMME_GENLOG(POMME_DEBUG_FILES, "HOST")
using namespace Pomme;
using namespace Pomme::Files;
struct HostForkHandle : public ForkHandle
{
std::fstream backingStream;
public:
HostForkHandle(ForkType theForkType, char perm, fs::path& path)
: ForkHandle(theForkType, perm)
{
std::ios::openmode openmode = std::ios::binary;
if (permission & fsWrPerm) openmode |= std::ios::out;
if (permission & fsRdPerm) openmode |= std::ios::in;
backingStream = std::fstream(path, openmode);
}
virtual ~HostForkHandle() = default;
virtual std::iostream& GetStream() override
{
return backingStream;
}
};
HostVolume::HostVolume(short vRefNum)
: Volume(vRefNum)
{
// default directory (ID 0)
directories.push_back(fs::current_path());
}
//-----------------------------------------------------------------------------
// Public utilities
long HostVolume::GetDirectoryID(const fs::path& dirPath)
{
if (fs::exists(dirPath) && !fs::is_directory(dirPath))
{
std::cerr << "Warning: GetDirectoryID should only be used on directories! " << dirPath << "\n";
}
auto it = std::find(directories.begin(), directories.end(), dirPath);
if (it != directories.end())
{
return std::distance(directories.begin(), it);
}
directories.emplace_back(dirPath);
LOG << "directory " << directories.size() - 1 << ": " << dirPath << "\n";
return (long) directories.size() - 1;
}
//-----------------------------------------------------------------------------
// Internal utilities
fs::path HostVolume::ToPath(long parID, const std::string& name)
{
fs::path path = directories[parID] / AsU8(name);
return path.lexically_normal();
}
FSSpec HostVolume::ToFSSpec(const fs::path& fullPath)
{
auto parentPath = fullPath;
parentPath.remove_filename();
FSSpec spec;
spec.vRefNum = volumeID;
spec.parID = GetDirectoryID(parentPath);
snprintf(spec.cName, 256, "%s", (const char*) fullPath.filename().u8string().c_str());
return spec;
}
static void ADFJumpToResourceFork(std::istream& stream)
{
auto f = Pomme::BigEndianIStream(stream);
if (0x0005160700020000ULL != f.Read<UInt64>())
{
throw std::runtime_error("No ADF magic");
}
f.Skip(16);
auto numOfEntries = f.Read<UInt16>();
for (int i = 0; i < numOfEntries; i++)
{
auto entryID = f.Read<UInt32>();
auto offset = f.Read<UInt32>();
f.Skip(4); // length
if (entryID == 2)
{
// Found entry ID 2 (resource fork)
f.Goto(offset);
return;
}
}
throw std::runtime_error("Didn't find entry ID=2 in ADF");
}
OSErr HostVolume::OpenFork(const FSSpec* spec, ForkType forkType, char permission, std::unique_ptr<ForkHandle>& handle)
{
if (permission == fsCurPerm)
{
TODO2("fsCurPerm not implemented yet");
return unimpErr;
}
if ((permission & fsWrPerm) && forkType != ForkType::DataFork)
{
TODO2("opening resource fork for writing isn't implemented yet");
return unimpErr;
}
auto path = ToPath(spec->parID, spec->cName);
if (forkType == DataFork)
{
if (!fs::is_regular_file(path))
{
return fnfErr;
}
handle = std::make_unique<HostForkHandle>(DataFork, permission, path);
return noErr;
}
else
{
// We want to open a resource fork on the host volume.
// It is likely stored under one of the following names: ._NAME, NAME.rsrc, or NAME/..namedfork/rsrc
auto specName = path.filename().u8string();
struct
{
u8string filename;
bool isAppleDoubleFile;
} candidates[] =
{
// "._NAME": ADF contained in zips created by macOS's built-in archiver
{ u8"._" + specName, true },
// "NAME.rsrc": ADF extracted from StuffIt/CompactPro archives by unar
{ specName + u8".rsrc", true },
#if __APPLE__
// "NAME/..namedfork/rsrc": macOS-specific way to access true resource forks (not ADF)
{ specName + u8"/..namedfork/rsrc", false },
#endif
};
path.remove_filename();
for (auto c : candidates)
{
auto candidatePath = path / c.filename;
if (!fs::is_regular_file(candidatePath))
{
continue;
}
path = candidatePath;
handle = std::make_unique<HostForkHandle>(ResourceFork, permission, path);
if (c.isAppleDoubleFile)
{
ADFJumpToResourceFork(handle->GetStream());
}
return noErr;
}
}
return fnfErr;
}
static bool CaseInsensitiveAppendToPath(
fs::path& path,
const std::string& element,
bool skipFiles = false)
{
fs::path naiveConcat = path / AsU8(element);
if (!fs::exists(path))
{
path = naiveConcat;
return false;
}
if (fs::exists(naiveConcat))
{
path = naiveConcat;
return true;
}
const auto ELEMENT = UppercaseCopy(element);
for (const auto& candidate : fs::directory_iterator(path))
{
if (skipFiles && !candidate.is_directory())
{
continue;
}
auto f = FromU8(candidate.path().filename().u8string());
// It might be an AppleDouble resource fork ("._file" or "file.rsrc")
if (candidate.is_regular_file())
{
if (f.starts_with("._"))
{
f = f.substr(2);
}
else if (f.ends_with(".rsrc"))
{
f = f.substr(0, f.length() - 5);
}
}
if (ELEMENT == UppercaseCopy(f))
{
path /= AsU8(f);
return true;
}
}
path = naiveConcat;
return false;
}
//-----------------------------------------------------------------------------
// Implementation
OSErr HostVolume::FSMakeFSSpec(long dirID, const std::string& fileName, FSSpec* spec)
{
if (dirID < 0 || (unsigned long) dirID >= directories.size())
{
throw std::runtime_error("HostVolume::FSMakeFSSpec: directory ID not registered.");
}
auto path = directories[dirID];
auto suffix = fileName;
// Case-insensitive sanitization
bool exists = fs::exists(path);
std::string::size_type begin = (suffix.at(0) == ':') ? 1 : 0;
// Iterate on path elements between colons
while (begin < suffix.length())
{
auto end = suffix.find(":", begin);
bool isLeaf = end == std::string::npos; // no ':' found => end of path
if (isLeaf) end = suffix.length();
if (end == begin) // "::" => parent directory
{
path = path.parent_path();
}
else
{
auto element = suffix.substr(begin, end - begin);
exists = CaseInsensitiveAppendToPath(path, element, !isLeaf);
}
// +1: jump over current colon
begin = end + 1;
}
path = path.lexically_normal();
LOG << path << "\n";
*spec = ToFSSpec(path);
return exists ? noErr : fnfErr;
}
OSErr HostVolume::DirCreate(long parentDirID, const std::string& directoryName, long* createdDirID)
{
const auto path = ToPath(parentDirID, directoryName);
if (fs::exists(path))
{
if (fs::is_directory(path))
{
LOG << __func__ << ": directory already exists: " << path << "\n";
return noErr;
}
else
{
std::cerr << __func__ << ": a file with the same name already exists: " << path << "\n";
return bdNamErr;
}
}
try
{
fs::create_directory(path);
}
catch (const fs::filesystem_error& e)
{
std::cerr << __func__ << " threw " << e.what() << "\n";
return ioErr;
}
if (createdDirID)
{
*createdDirID = GetDirectoryID(path);
}
LOG << __func__ << ": created " << path << "\n";
return noErr;
}
OSErr HostVolume::FSpCreate(const FSSpec* spec, OSType creator, OSType fileType, ScriptCode scriptTag)
{
std::ofstream df(ToPath(spec->parID, spec->cName));
df.close();
// TODO: we could write an AppleDouble file to save the creator/filetype.
return noErr;
}
OSErr HostVolume::FSpDelete(const FSSpec* spec)
{
auto path = ToPath(spec->parID, spec->cName);
if (fs::remove(path))
return noErr;
else
return fnfErr;
}

44
src/Files/HostVolume.h Normal file
View File

@ -0,0 +1,44 @@
#pragma once
#include "Files/Volume.h"
#include "CompilerSupport/filesystem.h"
#include <vector>
namespace Pomme::Files
{
/**
* Volume implementation that lets the Mac app access files
* on the host system's filesystem.
*/
class HostVolume : public Volume
{
std::vector<fs::path> directories;
fs::path ToPath(long parID, const std::string& name);
public:
explicit HostVolume(short vRefNum);
virtual ~HostVolume() = default;
//-----------------------------------------------------------------------------
// Utilities
long GetDirectoryID(const fs::path& dirPath);
FSSpec ToFSSpec(const fs::path& fullPath);
//-----------------------------------------------------------------------------
// Toolbox API Implementation
OSErr FSMakeFSSpec(long dirID, const std::string& fileName, FSSpec* spec) override;
OSErr OpenFork(const FSSpec* spec, ForkType forkType, char permission, std::unique_ptr<ForkHandle>& stream) override;
OSErr FSpCreate(const FSSpec* spec, OSType creator, OSType fileType, ScriptCode scriptTag) override;
OSErr FSpDelete(const FSSpec* spec) override;
OSErr DirCreate(long parentDirID, const std::string& directoryName, long* createdDirID) override;
};
}

304
src/Files/Resources.cpp Normal file
View File

@ -0,0 +1,304 @@
#include "Pomme.h"
#include "PommeFiles.h"
#include "Utilities/BigEndianIStream.h"
#include <algorithm>
#include <fstream>
#include <iostream>
#define LOG POMME_GENLOG(POMME_DEBUG_RESOURCES, "RSRC")
using namespace Pomme;
using namespace Pomme::Files;
//-----------------------------------------------------------------------------
// State
static OSErr lastResError = noErr;
static std::vector<ResourceFork> rezSearchStack;
static int rezSearchStackIndex = 0;
//-----------------------------------------------------------------------------
// Internal
static void ResourceAssert(bool condition, const char* message)
{
if (!condition)
{
throw std::runtime_error(message);
}
}
static ResourceFork& GetCurRF()
{
return rezSearchStack[rezSearchStackIndex];
}
#if 0
static void PrintStack(const char* msg) {
LOG << "------ RESOURCE SEARCH STACK " << msg << " -------\n";
for (int i = int(rezSearchStack.size() - 1); i >= 0; i--) {
LOG << (rezSearchStackIndex == i? " =====> " : " ")
<< " StackPos=" << i << " "
<< " RefNum=" << rezSearchStack[i].fileRefNum << " "
// << Pomme::Files::GetHostFilename(rezSearchStack[i].fileRefNum)
<< "\n";
}
LOG << "------------------------------------\n";
}
static void DumpResource(ResourceMetadata& meta)
{
Handle handle = NewHandle(meta.size);
auto& fork = Pomme::Files::GetStream(meta.forkRefNum);
fork.seekg(meta.dataOffset, std::ios::beg);
fork.read(*handle, meta.size);
std::stringstream fn;
fn << "rezdump/" << meta.id << "_" << meta.name << "." << Pomme::FourCCString(meta.type, '_');
std::ofstream dump(fn.str(), std::ofstream::binary);
dump.write(*handle, meta.size);
dump.close();
std::cout << "wrote " << fn.str() << "\n";
DisposeHandle(handle);
}
#endif
//-----------------------------------------------------------------------------
// Resource file management
OSErr ResError(void)
{
return lastResError;
}
short FSpOpenResFile(const FSSpec* spec, char permission)
{
short slot;
lastResError = FSpOpenRF(spec, permission, &slot);
if (noErr != lastResError)
{
return -1;
}
auto f = Pomme::BigEndianIStream(Pomme::Files::GetStream(slot));
auto resForkOff = f.Tell();
// ----------------
// Load resource fork
rezSearchStack.emplace_back();
rezSearchStackIndex = int(rezSearchStack.size() - 1);
GetCurRF().fileRefNum = slot;
GetCurRF().resourceMap.clear();
// -------------------
// Resource Header
UInt32 dataSectionOff = f.Read<UInt32>() + resForkOff;
UInt32 mapSectionOff = f.Read<UInt32>() + resForkOff;
f.Skip(4); // UInt32 dataSectionLen
f.Skip(4); // UInt32 mapSectionLen
f.Skip(112 + 128); // system- (112) and app- (128) reserved data
ResourceAssert(f.Tell() == dataSectionOff, "FSpOpenResFile: Unexpected data offset");
f.Goto(mapSectionOff);
// map header
f.Skip(16 + 4 + 2); // junk
f.Skip(2); // UInt16 fileAttr
UInt32 typeListOff = f.Read<UInt16>() + mapSectionOff;
UInt32 resNameListOff = f.Read<UInt16>() + mapSectionOff;
// all resource types
int nResTypes = 1 + f.Read<UInt16>();
for (int i = 0; i < nResTypes; i++)
{
OSType resType = f.Read<OSType>();
int resCount = f.Read<UInt16>() + 1;
UInt32 resRefListOff = f.Read<UInt16>() + typeListOff;
// The guard will rewind the file cursor to the pos in the next iteration
auto guard1 = f.GuardPos();
f.Goto(resRefListOff);
for (int j = 0; j < resCount; j++)
{
SInt16 resID = f.Read<UInt16>();
UInt16 resNameRelativeOff = f.Read<UInt16>();
UInt32 resPackedAttr = f.Read<UInt32>();
f.Skip(4); // junk
// The guard will rewind the file cursor to the pos in the next iteration
auto guard2 = f.GuardPos();
// unpack attributes
Byte resFlags = (resPackedAttr & 0xFF000000) >> 24;
UInt32 resDataOff = (resPackedAttr & 0x00FFFFFF) + dataSectionOff;
// Check compressed flag
ResourceAssert(!(resFlags & 1), "FSpOpenResFile: Compressed resources not supported yet");
// Fetch name
std::string name;
if (resNameRelativeOff != 0xFFFF)
{
f.Goto(resNameListOff + resNameRelativeOff);
name = f.ReadPascalString();
}
// Fetch size
f.Goto(resDataOff);
SInt32 size = f.Read<SInt32>();
ResourceMetadata resMetadata;
resMetadata.forkRefNum = slot;
resMetadata.type = resType;
resMetadata.id = resID;
resMetadata.flags = resFlags;
resMetadata.dataOffset = resDataOff + 4;
resMetadata.size = size;
resMetadata.name = name;
GetCurRF().resourceMap[resType][resID] = resMetadata;
}
}
//PrintStack(__func__);
return slot;
}
void UseResFile(short refNum)
{
// See MoreMacintoshToolbox:1-69
lastResError = unimpErr;
ResourceAssert(refNum != 0, "UseResFile: Using the System file's resource fork is not implemented.");
ResourceAssert(refNum >= 0, "UseResFile: Illegal refNum");
ResourceAssert(IsStreamOpen(refNum), "UseResFile: Resource stream not open");
for (size_t i = 0; i < rezSearchStack.size(); i++)
{
if (rezSearchStack[i].fileRefNum == refNum)
{
lastResError = noErr;
rezSearchStackIndex = i;
return;
}
}
std::cerr << "no RF open with refNum " << rfNumErr << "\n";
lastResError = rfNumErr;
}
short CurResFile()
{
return GetCurRF().fileRefNum;
}
void CloseResFile(short refNum)
{
ResourceAssert(refNum != 0, "CloseResFile: Closing the System file's resource fork is not implemented.");
ResourceAssert(refNum >= 0, "CloseResFile: Illegal refNum");
ResourceAssert(IsStreamOpen(refNum), "CloseResFile: Resource stream not open");
//UpdateResFile(refNum); // MMT:1-110
Pomme::Files::CloseStream(refNum);
auto it = rezSearchStack.begin();
while (it != rezSearchStack.end())
{
if (it->fileRefNum == refNum)
it = rezSearchStack.erase(it);
else
it++;
}
rezSearchStackIndex = std::min(rezSearchStackIndex, (int) rezSearchStack.size() - 1);
}
short Count1Resources(ResType theType)
{
lastResError = noErr;
try
{
return (short) GetCurRF().resourceMap.at(theType).size();
}
catch (std::out_of_range&)
{
return 0;
}
}
Handle GetResource(ResType theType, short theID)
{
lastResError = noErr;
for (int i = rezSearchStackIndex; i >= 0; i--)
{
const auto& fork = rezSearchStack[i];
if (fork.resourceMap.end() == fork.resourceMap.find(theType))
continue;
auto& resourcesOfType = fork.resourceMap.at(theType);
if (resourcesOfType.end() == resourcesOfType.find(theID))
continue;
const auto& meta = fork.resourceMap.at(theType).at(theID);
auto& forkStream = Pomme::Files::GetStream(rezSearchStack[i].fileRefNum);
Handle handle = NewHandle(meta.size);
forkStream.seekg(meta.dataOffset, std::ios::beg);
forkStream.read(*handle, meta.size);
return handle;
}
lastResError = resNotFound;
return nil;
}
void ReleaseResource(Handle theResource)
{
DisposeHandle(theResource);
}
void RemoveResource(Handle theResource)
{
DisposeHandle(theResource);
TODO();
}
void AddResource(Handle theData, ResType theType, short theID, const char* name)
{
TODO();
}
void WriteResource(Handle theResource)
{
TODO();
}
void DetachResource(Handle theResource)
{
lastResError = noErr;
ONCE(TODOMINOR());
}
long GetResourceSizeOnDisk(Handle theResource)
{
TODO();
return -1;
}
long SizeResource(Handle theResource)
{
return GetResourceSizeOnDisk(theResource);
}

59
src/Files/Volume.h Normal file
View File

@ -0,0 +1,59 @@
#pragma once
#include <memory>
namespace Pomme::Files
{
enum ForkType
{
DataFork,
ResourceFork
};
struct ForkHandle
{
public:
ForkType forkType;
char permission;
protected:
ForkHandle(ForkType _forkType, char _permission)
: forkType(_forkType)
, permission(_permission)
{}
public:
virtual std::iostream& GetStream() = 0;
virtual ~ForkHandle() = default;
};
/**
* Base class for volumes through which the Mac app is given access to files.
*/
class Volume
{
protected:
short volumeID;
public:
Volume(short vid)
: volumeID(vid)
{}
virtual ~Volume() = default;
//-----------------------------------------------------------------------------
// Toolbox API Implementation
virtual OSErr FSMakeFSSpec(long dirID, const std::string& suffix, FSSpec* spec) = 0;
virtual OSErr OpenFork(const FSSpec* spec, ForkType forkType, char permission, std::unique_ptr<ForkHandle>& handle) = 0;
virtual OSErr FSpCreate(const FSSpec* spec, OSType creator, OSType fileType, ScriptCode scriptTag) = 0;
virtual OSErr FSpDelete(const FSSpec* spec) = 0;
virtual OSErr DirCreate(long parentDirID, const std::string& directoryName, long* createdDirID) = 0;
};
}

View File

@ -0,0 +1,73 @@
#include "PommeGraphics.h"
#include <iostream>
using namespace Pomme::Graphics;
//-----------------------------------------------------------------------------
// Pixmap
ARGBPixmap::ARGBPixmap()
: width(0)
, height(0)
, data(0)
{
}
ARGBPixmap::ARGBPixmap(int w, int h)
: width(w)
, height(h)
, data(w * h * 4, 0xAA)
{
Fill(255, 0, 255);
}
ARGBPixmap::ARGBPixmap(ARGBPixmap&& other) noexcept
: width(other.width)
, height(other.height)
, data(std::move(other.data))
{
other.width = -1;
other.height = -1;
}
ARGBPixmap& ARGBPixmap::operator=(ARGBPixmap&& other) noexcept
{
if (this != &other)
{
width = other.width;
height = other.height;
data = std::move(other.data);
other.width = -1;
other.height = -1;
}
return *this;
}
void ARGBPixmap::Fill(UInt8 red, UInt8 green, UInt8 blue, UInt8 alpha)
{
for (int i = 0; i < width * height * 4; i += 4)
{
data[i + 0] = alpha;
data[i + 1] = red;
data[i + 2] = green;
data[i + 3] = blue;
}
}
void ARGBPixmap::Plot(int x, int y, UInt32 color)
{
if (x < 0 || y < 0 || x >= width || y >= height)
{
throw std::out_of_range("ARGBPixmap::Plot: out of bounds");
}
else
{
*(UInt32*) &data[4 * (y * width + x)] = color;
}
}
void ARGBPixmap::WriteTGA(const char* path) const
{
DumpTGA(path, width, height, (const char*) data.data());
}

27
src/Graphics/Color.cpp Normal file
View File

@ -0,0 +1,27 @@
#include "PommeGraphics.h"
using namespace Pomme::Graphics;
//-----------------------------------------------------------------------------
// Color
Color::Color(uint8_t r_, uint8_t g_, uint8_t b_)
: a(0xFF)
, r(r_)
, g(g_)
, b(b_)
{}
Color::Color(uint8_t r_, uint8_t g_, uint8_t b_, uint8_t a_)
: a(a_)
, r(r_)
, g(g_)
, b(b_)
{}
Color::Color()
: a(0xFF)
, r(0xFF)
, g(0x00)
, b(0xFF)
{}

641
src/Graphics/Graphics.cpp Normal file
View File

@ -0,0 +1,641 @@
#include "Pomme.h"
#include "PommeGraphics.h"
#include "SysFont.h"
#include "Utilities/memstream.h"
#include <iostream>
#include <memory>
#include <SDL.h>
using namespace Pomme;
using namespace Pomme::Graphics;
static bool IntersectRects(const Rect* r1, Rect* r2)
{
r2->left = std::max(r1->left, r2->left);
r2->right = std::min(r1->right, r2->right);
r2->top = std::max(r1->top, r2->top);
r2->bottom = std::min(r1->bottom, r2->bottom);
return r2->left < r2->right && r2->top < r2->bottom;
}
// ---------------------------------------------------------------------------- -
// Types
struct GrafPortImpl
{
GrafPort port;
ARGBPixmap pixels;
bool dirty;
Rect dirtyRect;
PixMap macpm;
PixMap* macpmPtr;
GrafPortImpl(const Rect boundsRect)
: port({boundsRect, this})
, pixels(boundsRect.right - boundsRect.left, boundsRect.bottom - boundsRect.top)
, dirty(false)
{
macpm = {};
macpm.bounds = boundsRect;
macpm.pixelSize = 32;
macpm._impl = (Ptr) &pixels;
macpmPtr = &macpm;
}
void DamageRegion(const Rect& r)
{
if (!dirty)
{
dirtyRect = r;
}
else
{
// Already dirty, expand existing dirty rect
dirtyRect.top = std::min(dirtyRect.top, r.top);
dirtyRect.left = std::min(dirtyRect.left, r.left);
dirtyRect.bottom = std::max(dirtyRect.bottom, r.bottom);
dirtyRect.right = std::max(dirtyRect.right, r.right);
}
dirty = true;
}
void DamageRegion(SInt16 x, SInt16 y, SInt16 w, SInt16 h)
{
Rect r = {y, x, static_cast<SInt16>(y + h), static_cast<SInt16>(x + w)};
DamageRegion(r);
}
~GrafPortImpl()
{
macpm._impl = nullptr;
}
};
// ---------------------------------------------------------------------------- -
// Internal State
static std::unique_ptr<GrafPortImpl> screenPort = nullptr;
static GrafPortImpl* curPort = nullptr;
static UInt32 penFG = 0xFF'FF'00'FF;
static UInt32 penBG = 0xFF'00'00'FF;
static int penX = 0;
static int penY = 0;
// ---------------------------------------------------------------------------- -
// Globals
extern "C" {
SDL_Window* gSDLWindow = nullptr;
}
// ---------------------------------------------------------------------------- -
// Initialization
CGrafPtr Pomme::Graphics::GetScreenPort(void)
{
return &screenPort->port;
}
void Pomme::Graphics::Init(const char* windowTitle, int windowWidth, int windowHeight)
{
if (0 != SDL_Init(SDL_INIT_VIDEO))
{
throw std::runtime_error("Couldn't initialize SDL video subsystem.");
}
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
#ifndef _WIN32
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
#endif
gSDLWindow = SDL_CreateWindow(
windowTitle,
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
windowWidth,
windowHeight,
SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_SHOWN);
if (!gSDLWindow)
{
throw std::runtime_error("Couldn't create SDL window.");
}
Rect boundsRect = {0, 0, 480, 640};
screenPort = std::make_unique<GrafPortImpl>(boundsRect);
curPort = screenPort.get();
}
void Pomme::Graphics::Shutdown()
{
SDL_DestroyWindow(gSDLWindow);
gSDLWindow = nullptr;
}
// ---------------------------------------------------------------------------- -
// Internal utils
static UInt32 GetEightColorPaletteValue(long color)
{
switch (color) {
case whiteColor: return clut4[0];
case yellowColor: return clut4[1];
case redColor: return clut4[3];
case magentaColor: return clut4[4];
case blackColor: return clut4[15];
case cyanColor: return clut4[7];
case greenColor: return clut4[8]; // I'm assuming this is light green rather than dark
case blueColor: return clut4[6];
default: return 0xFF'FF'00'FF;
}
}
// ---------------------------------------------------------------------------- -
// PICT resources
PicHandle GetPicture(short PICTresourceID)
{
Handle rawResource = GetResource('PICT', PICTresourceID);
if (rawResource == nil)
return nil;
memstream substream(*rawResource, GetHandleSize(rawResource));
ARGBPixmap pm = ReadPICT(substream, false);
ReleaseResource(rawResource);
// Tack the data onto the end of the Picture struct,
// so that DisposeHandle frees both the Picture and the data.
PicHandle ph = (PicHandle) NewHandle(int(sizeof(Picture) + pm.data.size()));
Picture& pic = **ph;
Ptr pixels = (Ptr) *ph + sizeof(Picture);
pic.picFrame = Rect{0, 0, (SInt16) pm.height, (SInt16) pm.width};
pic.picSize = -1;
pic.__pomme_pixelsARGB32 = pixels;
memcpy(pic.__pomme_pixelsARGB32, pm.data.data(), pm.data.size());
return ph;
}
// ---------------------------------------------------------------------------- -
// Rect
void SetRect(Rect* r, short left, short top, short right, short bottom)
{
r->left = left;
r->top = top;
r->right = right;
r->bottom = bottom;
}
// ---------------------------------------------------------------------------- -
// GWorld
static inline GrafPortImpl& GetImpl(GWorldPtr offscreenGWorld)
{
return *(GrafPortImpl*) offscreenGWorld->_impl;
}
static inline ARGBPixmap& GetImpl(PixMapPtr pixMap)
{
return *(ARGBPixmap*) pixMap->_impl;
}
OSErr NewGWorld(GWorldPtr* offscreenGWorld, short pixelDepth, const Rect* boundsRect, void* junk1, void* junk2, long junk3)
{
GrafPortImpl* impl = new GrafPortImpl(*boundsRect);
*offscreenGWorld = &impl->port;
return noErr;
}
void DisposeGWorld(GWorldPtr offscreenGWorld)
{
delete &GetImpl(offscreenGWorld);
}
void GetGWorld(CGrafPtr* port, GDHandle* gdh)
{
*port = &curPort->port;
*gdh = nil;
}
void SetGWorld(CGrafPtr port, GDHandle gdh)
{
SetPort(port);
}
PixMapHandle GetGWorldPixMap(GWorldPtr offscreenGWorld)
{
return &GetImpl(offscreenGWorld).macpmPtr;
}
Ptr GetPixBaseAddr(PixMapHandle pm)
{
return (Ptr) GetImpl(*pm).data.data();
}
Boolean LockPixels(PixMapHandle pm)
{
return true;
}
void UnlockPixels(PixMapHandle pm)
{
// no-op
}
// ---------------------------------------------------------------------------- -
// Port
void SetPort(GrafPtr port)
{
curPort = &GetImpl(port);
}
void GetPort(GrafPtr* outPort)
{
*outPort = &curPort->port;
}
Boolean IsPortDamaged(void)
{
return curPort->dirty;
}
void GetPortDamageRegion(Rect* r)
{
*r = curPort->dirtyRect;
}
void ClearPortDamage(void)
{
curPort->dirty = false;
}
void DamagePortRegion(const Rect* r)
{
curPort->DamageRegion(*r);
}
void DumpPortTGA(const char* outPath)
{
curPort->pixels.WriteTGA(outPath);
}
// ---------------------------------------------------------------------------- -
// Pen state manipulation
void MoveTo(short h, short v)
{
penX = h;
penY = v;
}
void ForeColor(long color)
{
penFG = GetEightColorPaletteValue(color);
}
void BackColor(long color)
{
penBG = GetEightColorPaletteValue(color);
}
void GetForeColor(RGBColor* rgb)
{
rgb->red = (penFG >> 16 & 0xFF) << 8;
rgb->green = (penFG >> 8 & 0xFF) << 8;
rgb->blue = (penFG & 0xFF) << 8;
}
void RGBBackColor(const RGBColor* color)
{
penBG
= 0xFF'00'00'00
| ((color->red >> 8) << 16)
| ((color->green >> 8) << 8)
| (color->blue >> 8)
;
}
void RGBForeColor(const RGBColor* color)
{
penFG
= 0xFF'00'00'00
| ((color->red >> 8) << 16)
| ((color->green >> 8) << 8)
| (color->blue >> 8)
;
}
void RGBBackColor2(const UInt32 color)
{
penBG = 0xFF000000 | (color & 0x00FFFFFF);
}
void RGBForeColor2(const UInt32 color)
{
penFG = 0xFF000000 | (color & 0x00FFFFFF);
}
// ---------------------------------------------------------------------------- -
// Paint
static void _FillRect(const int left, const int top, const int right, const int bottom, UInt32 fillColor)
{
if (!curPort)
{
throw std::runtime_error("_FillRect: no port set");
}
Rect dstRect;
dstRect.left = left;
dstRect.top = top;
dstRect.right = right;
dstRect.bottom = bottom;
Rect clippedDstRect = dstRect;
if (!IntersectRects(&curPort->port.portRect, &clippedDstRect))
{
return;
}
curPort->DamageRegion(clippedDstRect);
fillColor = ByteswapScalar(fillColor); // convert to big-endian
UInt32* dst = curPort->pixels.GetPtr(clippedDstRect.left, clippedDstRect.top);
for (int y = clippedDstRect.top; y < clippedDstRect.bottom; y++)
{
for (int x = 0; x < clippedDstRect.right - clippedDstRect.left; x++)
{
dst[x] = fillColor;
}
dst += curPort->pixels.width;
}
}
void PaintRect(const struct Rect* r)
{
_FillRect(r->left, r->top, r->right, r->bottom, penFG);
}
void EraseRect(const struct Rect* r)
{
_FillRect(r->left, r->top, r->right, r->bottom, penBG);
}
void LineTo(short x1, short y1)
{
auto color = ByteswapScalar(penFG);
auto offx = curPort->port.portRect.left;
auto offy = curPort->port.portRect.top;
int x0 = penX;
int y0 = penY;
int dx = std::abs(x1 - x0);
int sx = x0 < x1 ? 1 : -1;
int dy = -std::abs(y1 - y0);
int sy = y0 < y1 ? 1 : -1;
int err = dx + dy;
curPort->DamageRegion(penX, penY, dx, dy);
while (1)
{
curPort->pixels.Plot(x0 - offx, y0 - offy, color);
if (x0 == x1 && y0 == y1) break;
int e2 = 2 * err;
if (e2 >= dy)
{
err += dy;
x0 += sx;
}
if (e2 <= dx)
{
err += dx;
y0 += sy;
}
}
penX = x0;
penY = y0;
}
void FrameRect(const Rect* r)
{
auto color = ByteswapScalar(penFG);
auto& pm = curPort->pixels;
auto offx = curPort->port.portRect.left;
auto offy = curPort->port.portRect.top;
for (int x = r->left; x < r->right; x++) pm.Plot(x - offx, r->top - offy, color);
for (int x = r->left; x < r->right; x++) pm.Plot(x - offx, r->bottom - 1 - offy, color);
for (int y = r->top; y < r->bottom; y++) pm.Plot(r->left - offx, y - offy, color);
for (int y = r->top; y < r->bottom; y++) pm.Plot(r->right - 1 - offx, y - offy, color);
curPort->DamageRegion(*r);
}
void Pomme::Graphics::DrawARGBPixmap(int left, int top, ARGBPixmap& pixmap)
{
if (!curPort)
{
throw std::runtime_error("DrawARGBPixmap: no port set");
}
Rect dstRect;
dstRect.left = left;
dstRect.top = top;
dstRect.right = left + pixmap.width;
dstRect.bottom = top + pixmap.height;
Rect clippedDstRect = dstRect;
if (!IntersectRects(&curPort->port.portRect, &clippedDstRect))
{
return; // wholly outside bounds
}
curPort->DamageRegion(clippedDstRect);
UInt32* src = pixmap.GetPtr(clippedDstRect.left - dstRect.left, clippedDstRect.top - dstRect.top);
UInt32* dst = curPort->pixels.GetPtr(clippedDstRect.left, clippedDstRect.top);
for (int y = clippedDstRect.top; y < clippedDstRect.bottom; y++)
{
memcpy(dst, src, Width(clippedDstRect) * sizeof(UInt32));
dst += curPort->pixels.width;
src += pixmap.width;
}
}
void DrawPicture(PicHandle myPicture, const Rect* dstRect)
{
auto& pic = **myPicture;
UInt32* srcPixels = (UInt32*) pic.__pomme_pixelsARGB32;
int dstWidth = Width(*dstRect);
int dstHeight = Height(*dstRect);
int srcWidth = Width(pic.picFrame);
int srcHeight = Height(pic.picFrame);
if (srcWidth != dstWidth || srcHeight != dstHeight)
TODOFATAL2("we only support dstRect with the same width/height as the source picture");
for (int y = 0; y < dstHeight; y++)
{
memcpy(
curPort->pixels.GetPtr(dstRect->left, dstRect->top + y),
srcPixels + y * srcWidth,
4 * dstWidth);
}
curPort->DamageRegion(*dstRect);
}
void CopyBits(
const PixMap* srcBits,
PixMap* dstBits,
const Rect* srcRect,
const Rect* dstRect,
short mode,
void* maskRgn
)
{
auto& srcPM = GetImpl((PixMapPtr) srcBits);
auto& dstPM = GetImpl(dstBits);
const auto& srcBounds = srcBits->bounds;
const auto& dstBounds = dstBits->bounds;
int srcRectWidth = Width(*srcRect);
int srcRectHeight = Height(*srcRect);
int dstRectWidth = Width(*dstRect);
int dstRectHeight = Height(*dstRect);
if (srcRectWidth != dstRectWidth || srcRectHeight != dstRectHeight)
TODOFATAL2("can only copy between rects of same dimensions");
for (int y = 0; y < srcRectHeight; y++)
{
memcpy(
dstPM.GetPtr(dstRect->left - dstBounds.left, dstRect->top - dstBounds.top + y),
srcPM.GetPtr(srcRect->left - srcBounds.left, srcRect->top - srcBounds.top + y),
4 * srcRectWidth
);
}
}
// ---------------------------------------------------------------------------- -
// Text rendering
short TextWidthC(const char* cstr)
{
if (!cstr) return 0;
int totalWidth = -SysFont::charSpacing;
for (; *cstr; cstr++)
{
totalWidth += SysFont::charSpacing;
totalWidth += SysFont::GetGlyph(*cstr).width;
}
return totalWidth;
}
void DrawStringC(const char* cstr)
{
if (!cstr) return;
_FillRect(
penX,
penY - SysFont::ascend,
penX + TextWidthC(cstr),
penY + SysFont::descend,
penBG
);
penX -= SysFont::charSpacing;
for (; *cstr; cstr++)
{
penX += SysFont::charSpacing;
DrawChar(*cstr);
}
}
void DrawChar(char c)
{
UInt32 fg = ByteswapScalar(penFG);
auto& glyph = SysFont::GetGlyph(c);
// Theoretical coordinates of top-left corner of glyph (may be outside port bounds!)
Rect dstRect;
dstRect.left = penX - SysFont::leftMargin;
dstRect.top = penY - SysFont::ascend;
dstRect.right = dstRect.left + SysFont::widthBits;
dstRect.bottom = dstRect.top + SysFont::rows;
Rect clippedDstRect = dstRect;
if (!IntersectRects(&curPort->port.portRect, &clippedDstRect))
{
return; // wholly outside bounds
}
curPort->DamageRegion(clippedDstRect);
// Glyph boundaries
int minCol = clippedDstRect.left - dstRect.left;
int minRow = clippedDstRect.top - dstRect.top;
auto* dst2 = curPort->pixels.GetPtr(clippedDstRect.left, clippedDstRect.top);
for (int glyphY = minRow; glyphY < minRow + Height(clippedDstRect); glyphY++)
{
auto rowBits = glyph.bits[glyphY];
rowBits >>= minCol;
auto* dstRow = dst2;
for (int glyphX = minCol; glyphX < minCol + Width(clippedDstRect); glyphX++)
{
if (rowBits & 1)
{
*dstRow = fg;
}
rowBits >>= 1;
dstRow++;
}
dst2 += curPort->pixels.width;
}
penX += glyph.width;
}
// ----------------------------------------------------------------------------
// Icons
void Pomme::Graphics::SetWindowIconFromIcl8Resource(short icl8ID)
{
Handle macIcon = GetResource('icl8', icl8ID);
if (1024 != GetHandleSize(macIcon))
{
throw std::invalid_argument("icl8 resource has incorrect size");
}
const int width = 32;
const int height = 32;
SDL_Surface* icon = SDL_CreateRGBSurface(0, width, height, 32, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000);
for (int y = 0; y < height; y++)
{
uint32_t* out = (uint32_t*) ((char*) icon->pixels + icon->pitch * y);
for (int x = 0; x < width; x++)
{
unsigned char pixel = (*macIcon)[y * width + x];
*out++ = pixel == 0 ? 0 : Pomme::Graphics::clut8[pixel];
}
}
SDL_SetWindowIcon(gSDLWindow, icon);
SDL_FreeSurface(icon);
DisposeHandle(macIcon);
}

466
src/Graphics/PICT.cpp Normal file
View File

@ -0,0 +1,466 @@
#include "Pomme.h"
#include "PommeGraphics.h"
#include "Utilities/BigEndianIStream.h"
#include <list>
#include <fstream>
#include <iostream>
#include <vector>
using namespace Pomme;
using namespace Pomme::Graphics;
#define LOG POMME_GENLOG(POMME_DEBUG_PICT, "PICT")
#define LOG_NOPREFIX POMME_GENLOG_NOPREFIX(POMME_DEBUG_PICT)
class PICTException : public std::runtime_error
{
public:
PICTException(const char* m) : std::runtime_error(m)
{}
};
//-----------------------------------------------------------------------------
// Rect helpers
Rect ReadRect(BigEndianIStream& f)
{
Rect r;
r.top = f.Read<SInt16>();
r.left = f.Read<SInt16>();
r.bottom = f.Read<SInt16>();
r.right = f.Read<SInt16>();
return r;
}
bool operator==(const Rect& r1, const Rect& r2)
{
return
r1.top == r2.top &&
r1.left == r2.left &&
r1.bottom == r2.bottom &&
r1.right == r2.right;
}
bool operator!=(const Rect& r1, const Rect& r2)
{
return
r1.top != r2.top ||
r1.left != r2.left ||
r1.bottom != r2.bottom ||
r1.right != r2.right;
}
std::ostream& operator<<(std::ostream& s, const Rect& r)
{
return s << "T" << r.top << "L" << r.left << "B" << r.bottom << "R" << r.right;
}
//-----------------------------------------------------------------------------
// Dump Targa
void Pomme::Graphics::DumpTGA(const char* path, short width, short height, const char* argbData)
{
std::ofstream tga(path);
short tgaHdr[] = {0, 2, 0, 0, 0, 0, (short) width, (short) height, 0x2020};
tga.write((const char*) tgaHdr, sizeof(tgaHdr));
for (int i = 0; i < 4 * width * height; i += 4)
{
tga.put(argbData[i + 3]); //b
tga.put(argbData[i + 2]); //g
tga.put(argbData[i + 1]); //r
tga.put(argbData[i + 0]); //a
}
tga.close();
std::cout << "wrote " << path << "\n";
}
//-----------------------------------------------------------------------------
// PackBits
template<typename T>
static std::vector<T> UnpackBits(BigEndianIStream& f, UInt16 rowbytes, int packedLength)
{
//LOG << "UnpackBits rowbytes=" << rowbytes << " packedlength=" << packedLength << "\n";
std::vector<T> unpacked;
if (rowbytes < 8)
{
// Bits aren't compressed
LOG << "Bits aren't compressed\n";
for (int j = 0; j < packedLength; j += sizeof(T))
{
unpacked.push_back(f.Read<T>());
}
return unpacked;
}
for (int j = 0; j < packedLength;)
{
Byte FlagCounter = f.Read<Byte>();
if (FlagCounter == 0x80)
{
// special case: repeat value of 0. Apple says ignore.
j++;
}
else if (FlagCounter & 0x80)
{
// Packed data.
int len = ((FlagCounter ^ 0xFF) & 0xFF) + 2;
auto item = f.Read<T>();
for (int k = 0; k < len; k++)
{
unpacked.push_back(item);
}
j += 1 + sizeof(T);
}
else
{
// Unpacked data
int len = (FlagCounter & 0xFF) + 1;
for (int k = 0; k < len; k++)
{
unpacked.push_back(f.Read<T>());
}
j += 1 + len * sizeof(T);
}
}
return unpacked;
}
//-----------------------------------------------------------------------------
// Unpack PICT pixmap formats
template<typename T>
static std::vector<T> UnpackAllRows(BigEndianIStream& f, int w, int h, UInt16 rowbytes, std::size_t expectedItemCount)
{
LOG << "UnpackBits<" << typeid(T).name() << ">";
std::vector<T> data;
data.reserve(expectedItemCount);
for (int y = 0; y < h; y++)
{
int packedRowBytes = rowbytes > 250 ? f.Read<UInt16>() : f.Read<UInt8>();
std::vector<T> rowPixels = UnpackBits<T>(f, rowbytes, packedRowBytes);
data.insert(data.end(), rowPixels.begin(), rowPixels.end());
}
if (expectedItemCount != data.size())
{
throw PICTException("UnpackAllRows: unexpected item count");
}
LOG_NOPREFIX << "\n";
return data;
}
// Unpack pixel type 0 (8-bit indexed)
static ARGBPixmap Unpack0(BigEndianIStream& f, int w, int h, const std::vector<Color>& palette)
{
auto unpacked = UnpackAllRows<UInt8>(f, w, h, w, w * h);
ARGBPixmap dst(w, h);
dst.data.clear();
LOG << "indexed to RGBA";
for (uint8_t px : unpacked)
{
if (px >= palette.size())
{
throw PICTException("Unpack0: illegal color index in pixmap");
}
Color c = palette[px];
dst.data.push_back(c.a);
dst.data.push_back(c.r);
dst.data.push_back(c.g);
dst.data.push_back(c.b);
}
LOG_NOPREFIX << "\n";
return dst;
}
// Unpack pixel type 4 (16 bits, chunky)
static ARGBPixmap Unpack3(BigEndianIStream& f, int w, int h, UInt16 rowbytes)
{
auto unpacked = UnpackAllRows<UInt16>(f, w, h, rowbytes, w * h);
ARGBPixmap dst(w, h);
dst.data.clear();
dst.data.reserve(unpacked.size() * 4);
LOG << "Chunky16 to RGBA";
for (UInt16 px : unpacked)
{
dst.data.push_back(0xFF); // a
dst.data.push_back(((px >> 10) & 0x1F) << 3); // r
dst.data.push_back(((px >> 5) & 0x1F) << 3); // g
dst.data.push_back(((px >> 0) & 0x1F) << 3); // b
}
LOG_NOPREFIX << "\n";
return dst;
}
// Unpack pixel type 4 (24 or 32 bits, planar)
static ARGBPixmap Unpack4(BigEndianIStream& f, int w, int h, UInt16 rowbytes, int numPlanes)
{
auto unpacked = UnpackAllRows<Byte>(f, w, h, rowbytes, numPlanes * w * h);
ARGBPixmap dst(w, h);
dst.data.clear();
LOG << "Planar" << numPlanes*8 << " to RGBA";
for (int y = 0; y < h; y++)
{
if (numPlanes == 3)
{
for (int x = 0; x < w; x++)
{
dst.data.push_back(0xFF);
dst.data.push_back(unpacked[y*w*3 + x + w*0]); // red
dst.data.push_back(unpacked[y*w*3 + x + w*1]); // grn
dst.data.push_back(unpacked[y*w*3 + x + w*2]); // blu
}
}
else
{
for (int x = 0; x < w; x++)
{
dst.data.push_back(unpacked[y*w*3 + x + w*0]); // alpha
dst.data.push_back(unpacked[y*w*3 + x + w*1]); // red
dst.data.push_back(unpacked[y*w*3 + x + w*2]); // grn
dst.data.push_back(unpacked[y*w*3 + x + w*3]); // blu
}
}
}
LOG_NOPREFIX << "\n";
return dst;
}
//-----------------------------------------------------------------------------
// PICT header
static ARGBPixmap ReadPICTBits(BigEndianIStream& f, int opcode, const Rect& canvasRect)
{
bool directBitsOpcode = opcode == 0x009A || opcode == 0x009B;
//printf("@@@ ReadPictBits %04x ", opcode); LOG << "canvasRect: " << canvasRect << "\n";
if (directBitsOpcode) f.Skip(4); //skip junk
int rowbytes = f.Read<SInt16>();
//LOG << "**** rowbytes = " << rowbytes << "\n";
Rect frameRect = ReadRect(f);
LOG << "frameRect " << frameRect << "\n";
if (frameRect != canvasRect)
throw PICTException("frame dims != canvas dims 1");
int packType = -1;
int pixelSize = -1;
int componentCount = -1;
SInt16 pixmapVersion = f.Read<SInt16>();
packType = f.Read<SInt16>();
SInt32 packSize = f.Read<SInt32>();
Fixed hResolution = f.Read<Fixed >();
Fixed vResolution = f.Read<Fixed >();
SInt16 pixelType = f.Read<SInt16>();
pixelSize = f.Read<UInt16>();
componentCount = f.Read<UInt16>();
SInt16 componentSize = f.Read<SInt16>();
SInt32 planeBytes = f.Read<SInt32>();
SInt32 table = f.Read<SInt32>();
if (directBitsOpcode || (rowbytes & 0x8000) != 0)
{
f.Skip(4);
LOG << "----PICT PIXMAP----"
<< "\n\tpixmap version " << pixmapVersion
<< "\n\tpack type " << packType
<< "\n\tpack size " << packSize
<< "\n\tresolution " << (hResolution>>16) << "x" << (vResolution>>16)
<< "\n\tpixel type " << pixelType
<< "\n\tbpp " << pixelSize
<< "\n\tcomponent count " << componentCount
<< "\n\tcomponent size " << componentSize
<< "\n\tplane bytes " << planeBytes
<< "\n\ttable " << table
<< "\n";
if (pixelSize > 32) throw PICTException("pixmap invalid bpp");
if (componentCount > 4) throw PICTException("pixmap invalid component count");
if (componentSize <= 0) throw PICTException("pixmap invalid component size");
}
auto palette = std::vector<Color>();
if (!directBitsOpcode)
{
f.Skip(4);
UInt16 flags = f.Read<UInt16>();
int nColors = 1 + f.Read<UInt16>();
LOG << "Colormap: " << nColors << " colors\n";
if (nColors <= 0 || nColors > 256) throw PICTException("unsupported palette size");
palette.resize(nColors);
for (int i = 0; i < nColors; i++)
{
int index;
if (flags & 0x8000)
{
// ignore junk index (usually just set to 0)
f.Skip(2);
index = i;
}
else
{
index = f.Read<UInt16>();
if (index >= nColors)
throw PICTException("illegal color index in palette definition");
}
UInt8 r = (f.Read<UInt16>() >> 8) & 0xFF;
UInt8 g = (f.Read<UInt16>() >> 8) & 0xFF;
UInt8 b = (f.Read<UInt16>() >> 8) & 0xFF;
palette[index] = Color(r, g, b);
}
}
Rect srcRect = ReadRect(f);
Rect dstRect = ReadRect(f);
if (srcRect != dstRect) throw PICTException("unsupported src/dst rects");
if (srcRect != canvasRect) throw PICTException("unsupported src/dst rects that aren't the same as the canvas rect");
f.Skip(2);
if (opcode == 0x0091 || opcode == 0x0099 || opcode == 0x009b)
{
throw PICTException("unimplemented opcode");
}
if (!directBitsOpcode && (rowbytes & 0x8000) == 0)
{
throw PICTException("negative rowbytes");
}
else
{
int cw = Width(canvasRect);
int ch = Height(canvasRect);
switch (packType)
{
case 0: // 8-bit indexed color, packed bytewise
return Unpack0(f, cw, ch, palette);
case 3: // 16-bit color, stored chunky, packed pixelwise
return Unpack3(f, cw, ch, rowbytes);
case 4: // 24- or 32-bit color, stored planar, packed bytewise
return Unpack4(f, cw, ch, rowbytes, componentCount);
default:
throw PICTException("don't know how to unpack this pixel size");
}
}
}
ARGBPixmap Pomme::Graphics::ReadPICT(std::istream& theF, bool skip512)
{
BigEndianIStream f(theF);
LOG << "-----------------------------\n";
auto startOff = f.Tell();
if (skip512)
f.Skip(512); // junk
f.Skip(2); // Version 1 picture size. Meaningless for "modern" picts that can easily exceed 65,535 bytes.
Rect canvasRect = ReadRect(f);
if (Width(canvasRect) < 0 || Height(canvasRect) < 0)
{
LOG << "canvas rect: " << canvasRect << "\n";
throw PICTException("invalid width/height");
}
LOG << std::dec << "Pict canvas: " << canvasRect << "\n";
if (0x0011 != f.Read<SInt16>())
throw PICTException("didn't find version opcode in PICT header");
if (0x02 != f.Read<Byte>()) throw PICTException("unrecognized PICT version");
if (0xFF != f.Read<Byte>()) throw PICTException("bad PICT header");
ARGBPixmap pm(0, 0);
bool readPixmap = false;
while (true)
{
int opcode;
// Align position to short
f.Skip((f.Tell() - startOff) % 2);
opcode = f.Read<SInt16>();
//printf("~~~~~ OPCODE %04X ~~~~~\n", opcode);
// Skip reserved opcodes
if (opcode >= 0x0100 && opcode <= 0x7FFF)
{
f.Skip((opcode >> 7) & 0xFF);
continue;
}
switch (opcode)
{
case 0x0C00:
f.Skip(12);
break;
case 0x0000: // nop
case 0x001E: // DefHilite
case 0x0048: // frameSameRRect
LOG << __func__ << ": skipping opcode " << opcode << "\n";
break;
case 0x001F: // OpColor
LOG << __func__ << ": skipping opcode " << opcode << "\n";
f.Skip(6);
break;
case 0x0001: // clip
{
auto length = f.Read<UInt16>();
if (length != 0x0A) f.Skip(length - 2);
Rect frameRect = ReadRect(f);
//LOG << "CLIP:" << frameRect << "\n";
if (frameRect.left < 0 || frameRect.top < 0) throw PICTException("illegal frame rect");
if (frameRect != canvasRect)
{
std::cerr << "Warning: clip rect " << frameRect << " isn't the same as the canvas rect " << canvasRect << ", using clip rect\n";
canvasRect = frameRect;
}
break;
}
case 0x0098: // PackBitsRect
case 0x009A: // DirectBitsRect
if (readPixmap)
throw PICTException("already read one pixmap!");
pm = ReadPICTBits(f, opcode, canvasRect);
readPixmap = true;
break;
case 0x00A1: // Long comment
f.Skip(2);
f.Skip(f.Read<UInt16>());
break;
case 0x00FF: // done
case 0xFFFF:
return pm;
default:
std::cerr << "unsupported opcode " << opcode << " at offset " << f.Tell() << "\n";
throw PICTException("unsupported PICT opcode");
}
}
return pm;
}

122
src/Graphics/SysFont.h Normal file
View File

@ -0,0 +1,122 @@
namespace Pomme::SysFont
{
static const int firstCodepoint = 32;
static const int lastCodepoint = 126;
static const int charSpacing = 2;
static const int rows = 14;
static const int leftMargin = 2;
static const int ascend = 12;
static const int descend = 2;
static const int widthBits = 16;
static const struct Glyph
{
short width;
unsigned short bits[14];
} glyphs[95] = {
{ 2, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } }, //space
{ 3, { 0, 0, 0, 24, 24, 24, 24, 24, 24, 24, 0, 24, 0, 0 } }, //!
{ 4, { 0, 0, 0, 40, 40, 40, 0, 0, 0, 0, 0, 0, 0, 0 } }, //"
{ 8, { 0, 0, 288, 288,1016, 144, 144, 144, 508, 72, 72, 0, 0, 0 } }, //#
{ 7, { 0, 0, 32, 504, 44, 44, 44, 248, 416, 416, 416, 252, 32, 0 } }, //$
{ 9, { 0, 0, 0, 256, 280, 164, 164, 88, 832,1184,1184, 784, 16, 0 } }, //%
{ 7, { 0, 0, 0, 120, 76, 12, 408, 204, 204, 204, 204, 120, 0, 0 } }, //&
{ 2, { 0, 0, 0, 12, 12, 8, 4, 0, 0, 0, 0, 0, 0, 0 } }, //'
{ 4, { 0, 0, 48, 24, 24, 12, 12, 12, 12, 12, 24, 24, 48, 0 } }, //(
{ 4, { 0, 0, 12, 24, 24, 48, 48, 48, 48, 48, 24, 24, 12, 0 } }, //)
{ 7, { 0, 0, 0, 0, 0, 216, 112, 508, 112, 216, 0, 0, 0, 0 } }, //*
{ 6, { 0, 0, 0, 0, 0, 48, 48, 252, 252, 48, 48, 0, 0, 0 } }, //+
{ 2, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 8, 4 } }, //,
{ 4, { 0, 0, 0, 0, 0, 0, 0, 60, 60, 0, 0, 0, 0, 0 } }, //-
{ 3, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 24, 0, 0 } }, //.
{ 6, { 0, 0, 0, 192, 192, 96, 96, 48, 48, 24, 24, 12, 12, 0 } }, ///
{ 6, { 0, 0, 0, 120, 204, 204, 236, 220, 204, 204, 204, 120, 0, 0 } }, //0
{ 6, { 0, 0, 0, 56, 48, 48, 48, 48, 48, 48, 48, 252, 0, 0 } }, //1
{ 6, { 0, 0, 0, 124, 192, 192, 192, 96, 48, 24, 12, 252, 0, 0 } }, //2
{ 6, { 0, 0, 0, 124, 192, 192, 112, 192, 192, 192, 192, 124, 0, 0 } }, //3
{ 6, { 0, 0, 0, 192, 224, 208, 200, 204, 204, 252, 192, 192, 0, 0 } }, //4
{ 6, { 0, 0, 0, 252, 12, 12, 124, 192, 192, 192, 192, 124, 0, 0 } }, //5
{ 6, { 0, 0, 0, 248, 12, 12, 124, 204, 204, 204, 204, 120, 0, 0 } }, //6
{ 6, { 0, 0, 0, 252, 192, 224, 112, 56, 24, 24, 24, 24, 0, 0 } }, //7
{ 6, { 0, 0, 0, 120, 204, 204, 204, 120, 204, 204, 204, 120, 0, 0 } }, //8
{ 6, { 0, 0, 0, 120, 204, 204, 204, 248, 192, 192, 192, 120, 0, 0 } }, //9
{ 3, { 0, 0, 0, 0, 0, 0, 24, 24, 0, 0, 24, 24, 0, 0 } }, //:
{ 3, { 0, 0, 0, 0, 0, 0, 24, 24, 0, 0, 24, 24, 16, 8 } }, //;
{ 4, { 0, 0, 0, 0, 0, 32, 48, 24, 12, 24, 48, 32, 0, 0 } }, //<
{ 5, { 0, 0, 0, 0, 0, 0, 0, 124, 0, 124, 0, 0, 0, 0 } }, //=
{ 4, { 0, 0, 0, 0, 0, 4, 12, 24, 48, 24, 12, 4, 0, 0 } }, //>
{ 7, { 0, 0, 0, 240, 408, 408, 448, 224, 96, 96, 0, 96, 0, 0 } }, //?
{ 7, { 0, 0, 0, 0, 240, 264, 356, 340, 244, 4, 56, 0, 0, 0 } }, //@
{ 6, { 0, 0, 0, 120, 204, 204, 204, 252, 204, 204, 204, 204, 0, 0 } }, //A
{ 6, { 0, 0, 0, 124, 204, 204, 204, 124, 204, 204, 204, 124, 0, 0 } }, //B
{ 5, { 0, 0, 0, 120, 12, 12, 12, 12, 12, 12, 12, 120, 0, 0 } }, //C
{ 6, { 0, 0, 0, 124, 204, 204, 204, 204, 204, 204, 204, 124, 0, 0 } }, //D
{ 5, { 0, 0, 0, 124, 12, 12, 12, 60, 12, 12, 12, 124, 0, 0 } }, //E
{ 5, { 0, 0, 0, 124, 12, 12, 12, 60, 12, 12, 12, 12, 0, 0 } }, //F
{ 6, { 0, 0, 0, 248, 12, 12, 12, 204, 204, 204, 204, 184, 0, 0 } }, //G
{ 6, { 0, 0, 0, 204, 204, 204, 204, 252, 204, 204, 204, 204, 0, 0 } }, //H
{ 4, { 0, 0, 0, 60, 24, 24, 24, 24, 24, 24, 24, 60, 0, 0 } }, //I
{ 6, { 0, 0, 0, 192, 192, 192, 192, 192, 192, 204, 204, 120, 0, 0 } }, //J
{ 7, { 0, 0, 0, 396, 204, 108, 60, 28, 60, 108, 204, 396, 0, 0 } }, //K
{ 5, { 0, 0, 0, 12, 12, 12, 12, 12, 12, 12, 12, 124, 0, 0 } }, //L
{ 10, { 0, 0, 0,2044,3276,3276,3276,3276,3276,3276,3276,3276, 0, 0 } }, //M
{ 6, { 0, 0, 0, 124, 204, 204, 204, 204, 204, 204, 204, 204, 0, 0 } }, //N
{ 6, { 0, 0, 0, 120, 204, 204, 204, 204, 204, 204, 204, 120, 0, 0 } }, //O
{ 6, { 0, 0, 0, 124, 204, 204, 204, 124, 12, 12, 12, 12, 0, 0 } }, //P
{ 6, { 0, 0, 0, 120, 204, 204, 204, 204, 204, 204, 204, 120, 224, 0 } }, //Q
{ 6, { 0, 0, 0, 124, 204, 204, 204, 124, 204, 204, 204, 204, 0, 0 } }, //R
{ 6, { 0, 0, 0, 248, 12, 12, 28, 120, 224, 192, 192, 124, 0, 0 } }, //S
{ 6, { 0, 0, 0, 252, 48, 48, 48, 48, 48, 48, 48, 48, 0, 0 } }, //T
{ 6, { 0, 0, 0, 204, 204, 204, 204, 204, 204, 204, 204, 120, 0, 0 } }, //U
{ 6, { 0, 0, 0, 204, 204, 204, 204, 204, 204, 204, 76, 60, 0, 0 } }, //V
{ 10, { 0, 0, 0,3276,3276,3276,3276,3276,3276,3276,1228,1020, 0, 0 } }, //W
{ 6, { 0, 0, 0, 204, 204, 204, 204, 120, 204, 204, 204, 204, 0, 0 } }, //X
{ 6, { 0, 0, 0, 204, 204, 204, 204, 120, 48, 48, 48, 48, 0, 0 } }, //Y
{ 6, { 0, 0, 0, 252, 192, 192, 96, 48, 24, 12, 12, 252, 0, 0 } }, //Z
{ 3, { 0, 0, 28, 12, 12, 12, 12, 12, 12, 12, 12, 12, 28, 0 } }, //[
{ 6, { 0, 0, 0, 12, 12, 24, 24, 48, 48, 96, 96, 192, 192, 0 } }, //backslash
{ 3, { 0, 0, 28, 24, 24, 24, 24, 24, 24, 24, 24, 24, 28, 0 } }, //]
{ 7, { 0, 0, 0, 32, 112, 216, 396, 0, 0, 0, 0, 0, 0, 0 } }, //^
{ 5, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 124, 0 } }, //_
{ 2, { 0, 0, 0, 12, 12, 4, 8, 0, 0, 0, 0, 0, 0, 0 } }, //`
{ 6, { 0, 0, 0, 0, 0, 120, 192, 248, 204, 204, 204, 184, 0, 0 } }, //a
{ 6, { 0, 0, 0, 12, 12, 124, 204, 204, 204, 204, 204, 116, 0, 0 } }, //b
{ 5, { 0, 0, 0, 0, 0, 120, 12, 12, 12, 12, 12, 120, 0, 0 } }, //c
{ 6, { 0, 0, 0, 192, 192, 248, 204, 204, 204, 204, 204, 184, 0, 0 } }, //d
{ 6, { 0, 0, 0, 0, 0, 120, 204, 204, 252, 12, 12, 248, 0, 0 } }, //e
{ 5, { 0, 0, 0, 112, 24, 60, 24, 24, 24, 24, 24, 24, 0, 0 } }, //f
{ 6, { 0, 0, 0, 0, 0, 184, 204, 204, 204, 204, 204, 248, 192, 124 } }, //g
{ 6, { 0, 0, 0, 12, 12, 108, 220, 204, 204, 204, 204, 204, 0, 0 } }, //h
{ 2, { 0, 0, 0, 12, 0, 12, 12, 12, 12, 12, 12, 12, 0, 0 } }, //i
{ 2, { 0, 0, 0, 12, 0, 12, 12, 12, 12, 12, 12, 12, 12, 6 } }, //j
{ 6, { 0, 0, 0, 12, 12, 204, 108, 60, 28, 60, 108, 204, 0, 0 } }, //k
{ 3, { 0, 0, 0, 12, 12, 12, 12, 12, 12, 12, 12, 24, 0, 0 } }, //l
{ 10, { 0, 0, 0, 0, 0,1908,3276,3276,3276,3276,3276,3276, 0, 0 } }, //m
{ 6, { 0, 0, 0, 0, 0, 116, 204, 204, 204, 204, 204, 204, 0, 0 } }, //n
{ 6, { 0, 0, 0, 0, 0, 120, 204, 204, 204, 204, 204, 120, 0, 0 } }, //o
{ 6, { 0, 0, 0, 0, 0, 116, 204, 204, 204, 204, 204, 124, 12, 12 } }, //p
{ 6, { 0, 0, 0, 0, 0, 184, 204, 204, 204, 204, 204, 248, 192, 192 } }, //q
{ 5, { 0, 0, 0, 0, 0, 108, 28, 12, 12, 12, 12, 12, 0, 0 } }, //r
{ 6, { 0, 0, 0, 0, 0, 248, 12, 12, 120, 192, 192, 124, 0, 0 } }, //s
{ 4, { 0, 0, 0, 24, 24, 60, 24, 24, 24, 24, 24, 48, 0, 0 } }, //t
{ 6, { 0, 0, 0, 0, 0, 204, 204, 204, 204, 204, 204, 184, 0, 0 } }, //u
{ 6, { 0, 0, 0, 0, 0, 204, 204, 204, 204, 204, 76, 60, 0, 0 } }, //v
{ 10, { 0, 0, 0, 0, 0,3276,3276,3276,3276,3276,1228,1020, 0, 0 } }, //w
{ 6, { 0, 0, 0, 0, 0, 204, 204, 204, 120, 204, 204, 204, 0, 0 } }, //x
{ 6, { 0, 0, 0, 0, 0, 204, 204, 204, 204, 204, 204, 248, 192, 124 } }, //y
{ 6, { 0, 0, 0, 0, 0, 252, 192, 96, 48, 24, 12, 252, 0, 0 } }, //z
{ 4, { 0, 0, 48, 24, 24, 24, 24, 12, 24, 24, 24, 24, 48, 0 } }, //{
{ 2, { 0, 0, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12 } }, //|
{ 4, { 0, 0, 12, 24, 24, 24, 24, 48, 24, 24, 24, 24, 12, 0 } }, //}
};
static const Glyph& placeholder = glyphs['?' - firstCodepoint];
static inline const Glyph& GetGlyph(char codePoint)
{
if (codePoint < firstCodepoint || codePoint > lastCodepoint)
return placeholder;
else
return glyphs[codePoint - firstCodepoint];
}
}

View File

@ -0,0 +1,43 @@
#include "PommeGraphics.h"
const uint32_t Pomme::Graphics::clut8[256] =
{
0xFFFFFFFF, 0xFFFFFFCC, 0xFFFFFF99, 0xFFFFFF66, 0xFFFFFF33, 0xFFFFFF00, 0xFFFFCCFF, 0xFFFFCCCC,
0xFFFFCC99, 0xFFFFCC66, 0xFFFFCC33, 0xFFFFCC00, 0xFFFF99FF, 0xFFFF99CC, 0xFFFF9999, 0xFFFF9966,
0xFFFF9933, 0xFFFF9900, 0xFFFF66FF, 0xFFFF66CC, 0xFFFF6699, 0xFFFF6666, 0xFFFF6633, 0xFFFF6600,
0xFFFF33FF, 0xFFFF33CC, 0xFFFF3399, 0xFFFF3366, 0xFFFF3333, 0xFFFF3300, 0xFFFF00FF, 0xFFFF00CC,
0xFFFF0099, 0xFFFF0066, 0xFFFF0033, 0xFFFF0000, 0xFFCCFFFF, 0xFFCCFFCC, 0xFFCCFF99, 0xFFCCFF66,
0xFFCCFF33, 0xFFCCFF00, 0xFFCCCCFF, 0xFFCCCCCC, 0xFFCCCC99, 0xFFCCCC66, 0xFFCCCC33, 0xFFCCCC00,
0xFFCC99FF, 0xFFCC99CC, 0xFFCC9999, 0xFFCC9966, 0xFFCC9933, 0xFFCC9900, 0xFFCC66FF, 0xFFCC66CC,
0xFFCC6699, 0xFFCC6666, 0xFFCC6633, 0xFFCC6600, 0xFFCC33FF, 0xFFCC33CC, 0xFFCC3399, 0xFFCC3366,
0xFFCC3333, 0xFFCC3300, 0xFFCC00FF, 0xFFCC00CC, 0xFFCC0099, 0xFFCC0066, 0xFFCC0033, 0xFFCC0000,
0xFF99FFFF, 0xFF99FFCC, 0xFF99FF99, 0xFF99FF66, 0xFF99FF33, 0xFF99FF00, 0xFF99CCFF, 0xFF99CCCC,
0xFF99CC99, 0xFF99CC66, 0xFF99CC33, 0xFF99CC00, 0xFF9999FF, 0xFF9999CC, 0xFF999999, 0xFF999966,
0xFF999933, 0xFF999900, 0xFF9966FF, 0xFF9966CC, 0xFF996699, 0xFF996666, 0xFF996633, 0xFF996600,
0xFF9933FF, 0xFF9933CC, 0xFF993399, 0xFF993366, 0xFF993333, 0xFF993300, 0xFF9900FF, 0xFF9900CC,
0xFF990099, 0xFF990066, 0xFF990033, 0xFF990000, 0xFF66FFFF, 0xFF66FFCC, 0xFF66FF99, 0xFF66FF66,
0xFF66FF33, 0xFF66FF00, 0xFF66CCFF, 0xFF66CCCC, 0xFF66CC99, 0xFF66CC66, 0xFF66CC33, 0xFF66CC00,
0xFF6699FF, 0xFF6699CC, 0xFF669999, 0xFF669966, 0xFF669933, 0xFF669900, 0xFF6666FF, 0xFF6666CC,
0xFF666699, 0xFF666666, 0xFF666633, 0xFF666600, 0xFF6633FF, 0xFF6633CC, 0xFF663399, 0xFF663366,
0xFF663333, 0xFF663300, 0xFF6600FF, 0xFF6600CC, 0xFF660099, 0xFF660066, 0xFF660033, 0xFF660000,
0xFF33FFFF, 0xFF33FFCC, 0xFF33FF99, 0xFF33FF66, 0xFF33FF33, 0xFF33FF00, 0xFF33CCFF, 0xFF33CCCC,
0xFF33CC99, 0xFF33CC66, 0xFF33CC33, 0xFF33CC00, 0xFF3399FF, 0xFF3399CC, 0xFF339999, 0xFF339966,
0xFF339933, 0xFF339900, 0xFF3366FF, 0xFF3366CC, 0xFF336699, 0xFF336666, 0xFF336633, 0xFF336600,
0xFF3333FF, 0xFF3333CC, 0xFF333399, 0xFF333366, 0xFF333333, 0xFF333300, 0xFF3300FF, 0xFF3300CC,
0xFF330099, 0xFF330066, 0xFF330033, 0xFF330000, 0xFF00FFFF, 0xFF00FFCC, 0xFF00FF99, 0xFF00FF66,
0xFF00FF33, 0xFF00FF00, 0xFF00CCFF, 0xFF00CCCC, 0xFF00CC99, 0xFF00CC66, 0xFF00CC33, 0xFF00CC00,
0xFF0099FF, 0xFF0099CC, 0xFF009999, 0xFF009966, 0xFF009933, 0xFF009900, 0xFF0066FF, 0xFF0066CC,
0xFF006699, 0xFF006666, 0xFF006633, 0xFF006600, 0xFF0033FF, 0xFF0033CC, 0xFF003399, 0xFF003366,
0xFF003333, 0xFF003300, 0xFF0000FF, 0xFF0000CC, 0xFF000099, 0xFF000066, 0xFF000033, 0xFFEE0000,
0xFFDD0000, 0xFFBB0000, 0xFFAA0000, 0xFF880000, 0xFF770000, 0xFF550000, 0xFF440000, 0xFF220000,
0xFF110000, 0xFF00EE00, 0xFF00DD00, 0xFF00BB00, 0xFF00AA00, 0xFF008800, 0xFF007700, 0xFF005500,
0xFF004400, 0xFF002200, 0xFF001100, 0xFF0000EE, 0xFF0000DD, 0xFF0000BB, 0xFF0000AA, 0xFF000088,
0xFF000077, 0xFF000055, 0xFF000044, 0xFF000022, 0xFF000011, 0xFFEEEEEE, 0xFFDDDDDD, 0xFFBBBBBB,
0xFFAAAAAA, 0xFF888888, 0xFF777777, 0xFF555555, 0xFF444444, 0xFF222222, 0xFF111111, 0xFF000000,
};
const uint32_t Pomme::Graphics::clut4[16] =
{
0xffffffff, 0xfffcf305, 0xffff6402, 0xffdd0806, 0xfff20884, 0xff4600a5, 0xff0000d4, 0xff02abea,
0xff1fb714, 0xff006411, 0xff562c05, 0xff90713a, 0xffc0c0c0, 0xff808080, 0xff404040, 0xff000000,
};

191
src/Input/SDLInput.cpp Normal file
View File

@ -0,0 +1,191 @@
#include <algorithm>
#include "Pomme.h"
#include "PommeInput.h"
#include <SDL_events.h>
#include <SDL_keyboard.h>
//-----------------------------------------------------------------------------
// Input
static char scancodeLookupTable[256];
static void InitScancodeLookupTable()
{
const unsigned char NO_MAC_VK = 0xFF;
memset(scancodeLookupTable, NO_MAC_VK, sizeof(scancodeLookupTable));
char* T = scancodeLookupTable;
T[0] = NO_MAC_VK; // 0
T[1] = NO_MAC_VK;
T[2] = NO_MAC_VK;
T[3] = NO_MAC_VK;
T[SDL_SCANCODE_A] = kVK_ANSI_A;
T[SDL_SCANCODE_B] = kVK_ANSI_B;
T[SDL_SCANCODE_C] = kVK_ANSI_C;
T[SDL_SCANCODE_D] = kVK_ANSI_D;
T[SDL_SCANCODE_E] = kVK_ANSI_E;
T[SDL_SCANCODE_F] = kVK_ANSI_F;
T[SDL_SCANCODE_G] = kVK_ANSI_G; // 10
T[SDL_SCANCODE_H] = kVK_ANSI_H;
T[SDL_SCANCODE_I] = kVK_ANSI_I;
T[SDL_SCANCODE_J] = kVK_ANSI_J;
T[SDL_SCANCODE_K] = kVK_ANSI_K;
T[SDL_SCANCODE_L] = kVK_ANSI_L;
T[SDL_SCANCODE_M] = kVK_ANSI_M;
T[SDL_SCANCODE_N] = kVK_ANSI_N;
T[SDL_SCANCODE_O] = kVK_ANSI_O;
T[SDL_SCANCODE_P] = kVK_ANSI_P;
T[SDL_SCANCODE_Q] = kVK_ANSI_Q; // 20
T[SDL_SCANCODE_R] = kVK_ANSI_R;
T[SDL_SCANCODE_S] = kVK_ANSI_S;
T[SDL_SCANCODE_T] = kVK_ANSI_T;
T[SDL_SCANCODE_U] = kVK_ANSI_U;
T[SDL_SCANCODE_V] = kVK_ANSI_V;
T[SDL_SCANCODE_W] = kVK_ANSI_W;
T[SDL_SCANCODE_X] = kVK_ANSI_X;
T[SDL_SCANCODE_Y] = kVK_ANSI_Y;
T[SDL_SCANCODE_Z] = kVK_ANSI_Z;
T[SDL_SCANCODE_1] = kVK_ANSI_1; // 30
T[SDL_SCANCODE_2] = kVK_ANSI_2;
T[SDL_SCANCODE_3] = kVK_ANSI_3;
T[SDL_SCANCODE_4] = kVK_ANSI_4;
T[SDL_SCANCODE_5] = kVK_ANSI_5;
T[SDL_SCANCODE_6] = kVK_ANSI_6;
T[SDL_SCANCODE_7] = kVK_ANSI_7;
T[SDL_SCANCODE_8] = kVK_ANSI_8;
T[SDL_SCANCODE_9] = kVK_ANSI_9;
T[SDL_SCANCODE_0] = kVK_ANSI_0;
T[SDL_SCANCODE_RETURN] = kVK_Return; // 40
T[SDL_SCANCODE_ESCAPE] = kVK_Escape;
T[SDL_SCANCODE_BACKSPACE] = kVK_Delete;
T[SDL_SCANCODE_TAB] = kVK_Tab;
T[SDL_SCANCODE_SPACE] = kVK_Space;
T[SDL_SCANCODE_MINUS] = kVK_ANSI_Minus;
T[SDL_SCANCODE_EQUALS] = kVK_ANSI_Equal;
T[SDL_SCANCODE_LEFTBRACKET] = kVK_ANSI_LeftBracket;
T[SDL_SCANCODE_RIGHTBRACKET]= kVK_ANSI_RightBracket;
T[SDL_SCANCODE_BACKSLASH] = kVK_ANSI_Backslash;
T[SDL_SCANCODE_NONUSHASH] = NO_MAC_VK; // 50
T[SDL_SCANCODE_SEMICOLON] = kVK_ANSI_Semicolon;
T[SDL_SCANCODE_APOSTROPHE] = kVK_ANSI_Quote;
T[SDL_SCANCODE_GRAVE] = kVK_ANSI_Grave;
T[SDL_SCANCODE_COMMA] = kVK_ANSI_Comma;
T[SDL_SCANCODE_PERIOD] = kVK_ANSI_Period;
T[SDL_SCANCODE_SLASH] = kVK_ANSI_Slash;
T[SDL_SCANCODE_CAPSLOCK] = kVK_CapsLock;
T[SDL_SCANCODE_F1] = kVK_F1;
T[SDL_SCANCODE_F2] = kVK_F2;
T[SDL_SCANCODE_F3] = kVK_F3; // 60
T[SDL_SCANCODE_F4] = kVK_F4;
T[SDL_SCANCODE_F5] = kVK_F5;
T[SDL_SCANCODE_F6] = kVK_F6;
T[SDL_SCANCODE_F7] = kVK_F7;
T[SDL_SCANCODE_F8] = kVK_F8;
T[SDL_SCANCODE_F9] = kVK_F9;
T[SDL_SCANCODE_F10] = kVK_F10;
T[SDL_SCANCODE_F11] = kVK_F11;
T[SDL_SCANCODE_F12] = kVK_F12;
T[SDL_SCANCODE_PRINTSCREEN] = NO_MAC_VK; // could be F13 // 70
T[SDL_SCANCODE_SCROLLLOCK] = NO_MAC_VK; // could be F14
T[SDL_SCANCODE_PAUSE] = NO_MAC_VK; // could be F15
T[SDL_SCANCODE_INSERT] = kVK_Help; // 'help' is there on an ext'd kbd
T[SDL_SCANCODE_HOME] = kVK_Home;
T[SDL_SCANCODE_PAGEUP] = kVK_PageUp;
T[SDL_SCANCODE_DELETE] = kVK_ForwardDelete;
T[SDL_SCANCODE_END] = kVK_End;
T[SDL_SCANCODE_PAGEDOWN] = kVK_PageDown;
T[SDL_SCANCODE_RIGHT] = kVK_RightArrow;
T[SDL_SCANCODE_LEFT] = kVK_LeftArrow; // 80
T[SDL_SCANCODE_DOWN] = kVK_DownArrow;
T[SDL_SCANCODE_UP] = kVK_UpArrow;
T[SDL_SCANCODE_NUMLOCKCLEAR]= kVK_ANSI_KeypadClear; // 83
T[SDL_SCANCODE_KP_DIVIDE] = kVK_ANSI_KeypadDivide; // 84
T[SDL_SCANCODE_KP_MULTIPLY] = kVK_ANSI_KeypadMultiply;
T[SDL_SCANCODE_KP_MINUS] = kVK_ANSI_KeypadMinus;
T[SDL_SCANCODE_KP_PLUS] = kVK_ANSI_KeypadPlus;
T[SDL_SCANCODE_KP_ENTER] = kVK_ANSI_KeypadEnter;
T[SDL_SCANCODE_KP_1] = kVK_ANSI_Keypad1;
T[SDL_SCANCODE_KP_2] = kVK_ANSI_Keypad2; // 90
T[SDL_SCANCODE_KP_3] = kVK_ANSI_Keypad3;
T[SDL_SCANCODE_KP_4] = kVK_ANSI_Keypad4;
T[SDL_SCANCODE_KP_5] = kVK_ANSI_Keypad5;
T[SDL_SCANCODE_KP_6] = kVK_ANSI_Keypad6;
T[SDL_SCANCODE_KP_7] = kVK_ANSI_Keypad7;
T[SDL_SCANCODE_KP_8] = kVK_ANSI_Keypad8;
T[SDL_SCANCODE_KP_9] = kVK_ANSI_Keypad9;
T[SDL_SCANCODE_KP_0] = kVK_ANSI_Keypad0;
T[SDL_SCANCODE_KP_PERIOD] = kVK_ANSI_KeypadDecimal;
T[SDL_SCANCODE_NONUSBACKSLASH] = NO_MAC_VK; // 100
T[SDL_SCANCODE_APPLICATION] = NO_MAC_VK;
T[SDL_SCANCODE_POWER] = NO_MAC_VK;
T[SDL_SCANCODE_KP_EQUALS] = kVK_ANSI_KeypadEquals; // 103
T[SDL_SCANCODE_F13] = kVK_F13; // 104
T[SDL_SCANCODE_F14] = kVK_F14;
T[SDL_SCANCODE_F15] = kVK_F15;
T[SDL_SCANCODE_F16] = kVK_F16;
T[SDL_SCANCODE_F17] = kVK_F17;
T[SDL_SCANCODE_F18] = kVK_F18;
T[SDL_SCANCODE_F19] = kVK_F19; // 110
T[SDL_SCANCODE_F20] = kVK_F20; // 111
T[SDL_SCANCODE_F21] = NO_MAC_VK;
T[SDL_SCANCODE_F22] = NO_MAC_VK;
T[SDL_SCANCODE_F23] = NO_MAC_VK;
T[SDL_SCANCODE_F24] = NO_MAC_VK; // 115
T[SDL_SCANCODE_HELP] = kVK_Help; // also INSERT
// --snip--
T[SDL_SCANCODE_LCTRL] = kVK_Control; // 224
T[SDL_SCANCODE_LSHIFT] = kVK_Shift;
T[SDL_SCANCODE_LALT] = kVK_Option;
T[SDL_SCANCODE_LGUI] = kVK_Command;
T[SDL_SCANCODE_RCTRL] = kVK_RightControl;
T[SDL_SCANCODE_RSHIFT] = kVK_RightShift;
T[SDL_SCANCODE_RALT] = kVK_RightOption;
T[SDL_SCANCODE_RGUI] = kVK_Command; // no right command I guess
}
void GetKeys(KeyMap km)
{
km[0] = 0;
km[1] = 0;
km[2] = 0;
km[3] = 0;
SDL_PumpEvents();
int numkeys = 0;
const UInt8* keystate = SDL_GetKeyboardState(&numkeys);
numkeys = std::min((int) sizeof(scancodeLookupTable), numkeys);
for (int sdlScancode = 0; sdlScancode < numkeys; sdlScancode++)
{
if (!keystate[sdlScancode])
continue;
int vk = scancodeLookupTable[sdlScancode];
if (vk == 0xFF)
continue;
int byteNo = vk >> 5; // =vk/32
int bitNo = vk & 31;
km[byteNo] |= 1 << bitNo;
}
#if POMME_DEBUG_INPUT
if (km[0] || km[1] || km[2] || km[3])
printf("GK %08x%08x%08x%08x\n", km[0], km[1], km[2], km[3]);
#endif
}
Boolean Button(void)
{
ONCE(TODOMINOR());
return false;
}
void Pomme::Input::Init()
{
InitScancodeLookupTable();
}

185
src/Memory/Memory.cpp Normal file
View File

@ -0,0 +1,185 @@
#include <iostream>
#include <vector>
#include <cstring>
#include "Pomme.h"
#include "Utilities/FixedPool.h"
#define LOG POMME_GENLOG(POMME_DEBUG_MEMORY, "MEMO")
//-----------------------------------------------------------------------------
// Implementation-specific stuff
static const int HANDLE_MAGIC_LENGTH = 8;
static const char HANDLE_MAGIC[HANDLE_MAGIC_LENGTH] = "LIVEhdl";
static const char HANDLE_MAGIC_DEAD[HANDLE_MAGIC_LENGTH] = "DEADhdl";
struct BlockDescriptor
{
Ptr buf;
char magic[HANDLE_MAGIC_LENGTH];
Size size;
};
// these must not move around
static Pomme::FixedPool<BlockDescriptor, UInt16, 1000> blocks;
static BlockDescriptor* HandleToBlock(Handle h)
{
auto bd = (BlockDescriptor*) h;
if (0 != memcmp(bd->magic, HANDLE_MAGIC, HANDLE_MAGIC_LENGTH))
throw std::runtime_error("corrupted handle");
return bd;
}
//-----------------------------------------------------------------------------
// Memory: Handle
Handle NewHandle(Size s)
{
if (s < 0) throw std::invalid_argument("trying to alloc negative size handle");
BlockDescriptor* block = blocks.Alloc();
block->buf = new char[s];
memcpy(block->magic, HANDLE_MAGIC, HANDLE_MAGIC_LENGTH);
block->size = s;
if ((Ptr) &block->buf != (Ptr) block)
throw std::runtime_error("buffer address mismatches block address");
LOG << (void*) block->buf << ", size " << s << "\n";
return &block->buf;
}
Handle NewHandleClear(Size s)
{
Handle h = NewHandle(s);
memset(*h, 0, s);
return h;
}
Handle TempNewHandle(Size s, OSErr* err)
{
Handle h = NewHandle(s);
*err = noErr;
return h;
}
Size GetHandleSize(Handle h)
{
return HandleToBlock(h)->size;
}
void SetHandleSize(Handle handle, Size byteCount)
{
TODOFATAL();
}
void DisposeHandle(Handle h)
{
LOG << (void*) *h << "\n";
BlockDescriptor* b = HandleToBlock(h);
delete[] b->buf;
b->buf = 0;
b->size = -1;
memcpy(b->magic, HANDLE_MAGIC_DEAD, HANDLE_MAGIC_LENGTH);
blocks.Dispose(b);
}
//-----------------------------------------------------------------------------
// Memory: Ptr
Ptr NewPtr(Size byteCount)
{
if (byteCount < 0) throw std::invalid_argument("trying to NewPtr negative size");
return new char[byteCount];
}
Ptr NewPtrSys(Size byteCount)
{
if (byteCount < 0) throw std::invalid_argument("trying to NewPtrSys negative size");
return new char[byteCount];
}
void DisposePtr(Ptr p)
{
delete[] p;
}
//-----------------------------------------------------------------------------
// Memory: BlockMove
void BlockMove(const void* srcPtr, void* destPtr, Size byteCount)
{
memcpy(destPtr, srcPtr, byteCount);
}
void BlockMoveData(const void* srcPtr, void* destPtr, Size byteCount)
{
TODOFATAL();
}
//-----------------------------------------------------------------------------
// No-op memory junk
void MaxApplZone(void)
{
// No-op
}
void MoreMasters(void)
{
// No-op
}
Size CompactMem(Size)
{
// No-op
// TODO: what should we actually return?
return 0;
}
Size CompactMemSys(Size)
{
// No-op
// TODO: what should we actually return?
return 0;
}
void PurgeMem(Size)
{
// No-op
}
void PurgeMemSys(Size)
{
// No-op
}
Size MaxMem(Size*)
{
// No-op
// TODO: what should we actually return?
return 0;
}
void HNoPurge(Handle)
{
// No-op
}
void HLock(Handle)
{
// No-op
}
void HLockHi(Handle)
{
// No-op
}
void NoPurgePixels(PixMapHandle)
{
// No-op
}

View File

@ -0,0 +1,17 @@
#include "Platform/Windows/PommeWindows.h"
#include <shlobj.h>
std::filesystem::path Pomme::Platform::Windows::GetPreferencesFolder()
{
wchar_t* wpath = nullptr;
SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, nullptr, &wpath);
auto path = std::filesystem::path(wpath);
CoTaskMemFree(static_cast<void*>(wpath));
return path;
}
void Pomme::Platform::Windows::SysBeep()
{
MessageBeep(0);
}

View File

@ -0,0 +1,10 @@
#pragma once
#include <filesystem>
namespace Pomme::Platform::Windows
{
std::filesystem::path GetPreferencesFolder();
void SysBeep();
}

82
src/Pomme.cpp Normal file
View File

@ -0,0 +1,82 @@
#include <iostream>
#include <cstring>
#include "Pomme.h"
#include "PommeInit.h"
#include "PommeTime.h"
#include "PommeFiles.h"
#include "PommeGraphics.h"
#include "PommeSound.h"
#include "PommeInput.h"
#include "SDL.h"
#if _WIN32
#include "Platform/Windows/PommeWindows.h"
#endif
//-----------------------------------------------------------------------------
// Our own utils
const char* Pomme::QuitRequest::what() const noexcept
{
return "the user has requested to quit the application";
}
//-----------------------------------------------------------------------------
// Misc
void ExitToShell()
{
throw Pomme::QuitRequest();
}
void SysBeep(short duration)
{
#ifdef _WIN32
Pomme::Platform::Windows::SysBeep();
#else
TODOMINOR();
#endif
}
void FlushEvents(short, short)
{
TODOMINOR();
}
void NumToStringC(long theNum, Str255 theString)
{
snprintf(theString, 256, "%ld", theNum);
}
//-----------------------------------------------------------------------------
// Mouse cursor
void InitCursor()
{
SDL_ShowCursor(1);
}
void HideCursor()
{
SDL_ShowCursor(0);
}
//-----------------------------------------------------------------------------
// Our own init
void Pomme::Init(const char* windowName)
{
Pomme::Time::Init();
Pomme::Files::Init();
Pomme::Graphics::Init(windowName, 640, 480);
Pomme::Sound::Init();
Pomme::Input::Init();
}
void Pomme::Shutdown()
{
Pomme::Sound::Shutdown();
Pomme::Graphics::Shutdown();
}

336
src/Pomme.h Normal file
View File

@ -0,0 +1,336 @@
#pragma once
#include "PommeTypes.h"
#include "PommeEnums.h"
#include "PommeDebug.h"
//-----------------------------------------------------------------------------
// Structure unpacking
#include "Utilities/structpack.h"
//-----------------------------------------------------------------------------
// PowerPC intrinsics
#define __fres(x) (1.0f/x)
#define __fabs(x) fabs(x)
#define __frsqrte(x) (1.0f/sqrtf(x))
//-----------------------------------------------------------------------------
// Source code compat
#define nil NULL
#ifdef __cplusplus
extern "C"
{
#endif
//-----------------------------------------------------------------------------
// FSSpec
OSErr FSMakeFSSpec(short vRefNum, long dirID, const char* cstrFileName, FSSpec* spec);
short FSpOpenResFile(const FSSpec* spec, char permission);
// Open a file's data fork
OSErr FSpOpenDF(const FSSpec* spec, char permission, short* refNum);
// Open a file's resource fork
OSErr FSpOpenRF(const FSSpec* spec, char permission, short* refNum);
OSErr FSpCreate(const FSSpec* spec, OSType creator, OSType fileType, ScriptCode scriptTag);
OSErr FSpDelete(const FSSpec* spec);
OSErr ResolveAlias(const FSSpec* spec, AliasHandle alias, FSSpec* target, Boolean* wasChanged);
OSErr FindFolder(short vRefNum, OSType folderType, Boolean createFolder, short* foundVRefNum, long* foundDirID);
OSErr DirCreate(short vRefNum, long parentDirID, const char* cstrDirectoryName, long* createdDirID);
//-----------------------------------------------------------------------------
// File I/O
OSErr FSRead(short refNum, long* count, Ptr buffPtr);
OSErr FSWrite(short refNum, long* count, Ptr buffPtr);
OSErr FSClose(short refNum);
OSErr GetEOF(short refNum, long* logEOF);
OSErr SetEOF(short refNum, long logEOF);
//-----------------------------------------------------------------------------
// Resource file management
// MoreMacintoshToolbox.pdf p174
OSErr ResError(void);
void UseResFile(short refNum);
// Gets the file reference number of the current resource file.
short CurResFile();
void CloseResFile(short refNum);
// Returns total number of resources of the given type
// in the current resource file only.
short Count1Resources(ResType);
Handle GetResource(ResType theType, short theID);
void ReleaseResource(Handle theResource);
void RemoveResource(Handle theResource);
void AddResource(Handle theData, ResType theType, short theID, const char* name);
void WriteResource(Handle theResource);
void DetachResource(Handle theResource);
long GetResourceSizeOnDisk(Handle);
long SizeResource(Handle);
//-----------------------------------------------------------------------------
// QuickDraw 2D
void SetRect(Rect* r, short left, short top, short right, short bottom);
PicHandle GetPicture(short PICTresourceID);
void DisposeGWorld(GWorldPtr offscreenGWorld);
// IM:QD:6-16
QDErr NewGWorld(
GWorldPtr* offscreenGWorld,
short pixelDepth,
const Rect* boundsRect,
void* junk1, // CTabHandle cTable
void* junk2, // GDHandle aGDevice
long junk3 // long flags
);
void GetGWorld(CGrafPtr* port, GDHandle* gdh);
void SetGWorld(CGrafPtr port, GDHandle gdh);
void SetPort(GrafPtr port);
void GetPort(GrafPtr* port);
void MoveTo(short h, short v);
void GetForeColor(RGBColor* rgb);
void ForeColor(long color);
void BackColor(long color);
void RGBBackColor(const RGBColor* color);
void RGBForeColor(const RGBColor* color);
// Pomme extension (not part of the original Toolbox API).
void RGBBackColor2(UInt32 color);
// Pomme extension (not part of the original Toolbox API).
void RGBForeColor2(UInt32 color);
void PaintRect(const Rect* r);
void EraseRect(const Rect* r);
void LineTo(short h, short v);
void FrameRect(const Rect*);
//short TextWidth(const char* textBuf, short firstByte, short byteCount);
short TextWidthC(const char* cstr);
void DrawChar(char c);
void DrawStringC(const char* cstr);
// IM:QD:7-44
void DrawPicture(PicHandle myPicture, const Rect* dstRect);
// IM:QD:6-31
PixMapHandle GetGWorldPixMap(GWorldPtr offscreenGWorld);
// IM:QD:6-38
Ptr GetPixBaseAddr(PixMapHandle pm);
void CopyBits(
const PixMap* srcBits,
PixMap* dstBits,
const Rect* srcRect,
const Rect* dstRect,
short mode,
void* maskRgn
);
//-----------------------------------------------------------------------------
// QuickDraw 2D extensions
// Returns true if the current port is "damaged".
// Pomme extension (not part of the original Toolbox API).
Boolean IsPortDamaged(void);
// Stores current port's damaged region into "r".
// You should only call this after having checked that IsPortDamaged() is true.
// Pomme extension (not part of the original Toolbox API).
void GetPortDamageRegion(Rect* r);
// Sets current port as undamaged.
// Pomme extension (not part of the original Toolbox API).
void ClearPortDamage(void);
// Extends the current port's damage region to include the given rectangle.
// Pomme extension (not part of the original Toolbox API).
void DamagePortRegion(const Rect*);
// Writes the current port to a Targa image.
// Pomme extension (not part of the original Toolbox API).
void DumpPortTGA(const char* path);
//-----------------------------------------------------------------------------
// Misc
void ExitToShell();
void SysBeep(short duration);
void FlushEvents(short, short);
void NumToStringC(long theNum, Str255 theString);
//-----------------------------------------------------------------------------
// Input
void GetKeys(KeyMap);
Boolean Button(void);
//-----------------------------------------------------------------------------
// Memory: No-op
void MaxApplZone(void);
void MoreMasters(void);
Size CompactMem(Size);
// Compact system heap zone manually.
Size CompactMemSys(Size);
void PurgeMem(Size);
void PurgeMemSys(Size);
Size MaxMem(Size* grow);
void HNoPurge(Handle);
void HLock(Handle);
void HLockHi(Handle);
void NoPurgePixels(PixMapHandle);
// To prevent the base address for an offscreen pixel image from being moved
// while you draw into or copy from its pixel map.
Boolean LockPixels(PixMapHandle); // shall return true always
//-----------------------------------------------------------------------------
// Memory: Handle
Handle NewHandle(Size);
// Allocate prezeroed memory
Handle NewHandleClear(Size);
// Allocate temp memory
Handle TempNewHandle(Size, OSErr*);
Size GetHandleSize(Handle);
// Change the logical size of the relocatable block corresponding to a handle
void SetHandleSize(Handle, Size);
void DisposeHandle(Handle);
//-----------------------------------------------------------------------------
// Memory: Ptr
Ptr NewPtr(Size);
Ptr NewPtrSys(Size);
void DisposePtr(Ptr p);
//-----------------------------------------------------------------------------
// Memory: BlockMove
// Copies a sequence of bytes from one location in memory to another
void BlockMove(const void* srcPtr, void* destPtr, Size byteCount);
void BlockMoveData(const void* srcPtr, void* destPtr, Size byteCount);
//-----------------------------------------------------------------------------
// Time Manager
// Number of seconds elapsed since 1904-01-01 00:00
void GetDateTime(unsigned long* secs);
// Number of usecs elapsed since system startup
void Microseconds(UnsignedWide* microTickCount);
// Number of ticks elapsed since system startup (1 tick = approx. 1/60 of a second)
UInt32 TickCount();
//-----------------------------------------------------------------------------
// Mouse cursor
void HideCursor();
void InitCursor();
//-----------------------------------------------------------------------------
// Sound Manager
OSErr GetDefaultOutputVolume(long*);
OSErr SetDefaultOutputVolume(long);
OSErr SndNewChannel(SndChannelPtr* chan, short synth, long init, SndCallBackProcPtr userRoutine);
OSErr SndDisposeChannel(SndChannelPtr chan, Boolean quietNow);
OSErr SndChannelStatus(SndChannelPtr chan, short theLength, SCStatusPtr theStatus);
OSErr SndDoImmediate(SndChannelPtr chan, const SndCommand* cmd);
OSErr GetSoundHeaderOffset(SndListHandle sndHandle, long* offset);
OSErr SndStartFilePlay(SndChannelPtr chan, short fRefNum, short resNum, long bufferSize, Ptr theBuffer, /*AudioSelectionPtr*/ void* theSelection, FilePlayCompletionUPP theCompletion, Boolean async);
OSErr SndPauseFilePlay(SndChannelPtr chan);
OSErr SndStopFilePlay(SndChannelPtr chan, Boolean quietNow);
NumVersion SndSoundManagerVersion();
// Pomme extension
Boolean Pomme_DecompressSoundResource(SndListHandle* sndHandlePtr, long* offsetToHeader);
// Pomme extension
void Pomme_PauseLoopingChannels(Boolean pause);
#ifdef __cplusplus
}
#endif

57
src/PommeDebug.cpp Normal file
View File

@ -0,0 +1,57 @@
#include "PommeDebug.h"
#include <SDL.h>
#include <sstream>
#include <iostream>
void ImplementMe(const char* fn, std::string msg, int severity)
{
if (severity >= 0)
{
std::stringstream ss;
ss << "[TODO] \x1b[1m" << fn << "\x1b[22m";
if (!msg.empty())
{
ss << ": " << msg;
}
auto str = ss.str();
std::cerr << (severity > 0 ? "\x1b[31m" : "\x1b[33m") << str << "\x1b[0m\n";
}
if (severity >= 2)
{
std::stringstream ss;
ss << fn << "()";
if (!msg.empty()) ss << "\n" << msg;
auto str = ss.str();
int mbflags = SDL_MESSAGEBOX_ERROR;
if (severity == 0) mbflags = SDL_MESSAGEBOX_INFORMATION;
if (severity == 1) mbflags = SDL_MESSAGEBOX_WARNING;
SDL_ShowSimpleMessageBox(mbflags, "Source port TODO", str.c_str(), nullptr);
}
if (severity >= 2)
{
abort();
}
}
std::string Pomme::FourCCString(uint32_t t, char filler)
{
char b[5];
*(uint32_t*) b = t;
#if !(TARGET_RT_BIGENDIAN)
std::reverse(b, b + 4);
#endif
// replace non-ascii with '?'
for (int i = 0; i < 4; i++)
{
char c = b[i];
if (c < ' ' || c > '~') b[i] = filler;
}
b[4] = '\0';
return b;
}

51
src/PommeDebug.h Normal file
View File

@ -0,0 +1,51 @@
#pragma once
#if __cplusplus
#include <string>
#include <sstream>
#define POMME_DEBUG_MEMORY false
#define POMME_DEBUG_SOUND false
#define POMME_DEBUG_PICT false
#define POMME_DEBUG_FILES false
#define POMME_DEBUG_RESOURCES false
#define POMME_DEBUG_INPUT false
#define POMME_GENLOG(define, prefix) if (!define) {} else std::cout << "[" << prefix << "] " << __func__ << ":\t"
#define POMME_GENLOG_NOPREFIX(define) if (!define) {} else std::cout
namespace Pomme
{
std::string FourCCString(uint32_t t, char filler = '?');
}
//-----------------------------------------------------------------------------
// My todos
void ImplementMe(const char* fn, std::string msg, int severity);
#define TODOCUSTOM(message, severity) { \
std::stringstream ss; \
ss << message; \
ImplementMe(__func__, ss.str(), severity); \
}
#define TODOMINOR() TODOCUSTOM("", 0)
#define TODOMINOR2(x) TODOCUSTOM(x, 0)
#define TODO() TODOCUSTOM("", 1)
#define TODO2(x) TODOCUSTOM(x, 1)
#define TODOFATAL() TODOCUSTOM("", 2)
#define TODOFATAL2(x) TODOCUSTOM(x, 2)
#define ONCE(x) { \
static bool once = false; \
if (!once) { \
once = true; \
{x} \
printf(" \x1b[90m\\__ this todo won't be shown again\x1b[0m\n"); \
} \
}
#endif

397
src/PommeEnums.h Normal file
View File

@ -0,0 +1,397 @@
#pragma once
//-----------------------------------------------------------------------------
// Filesystem permission char
enum EFSPermissions
{
fsCurPerm = 0,
fsRdPerm = 1,
fsWrPerm = 2,
fsRdWrPerm = 3,
};
//-----------------------------------------------------------------------------
// Folder types
enum
{
kSystemFolderType = 'macs',
kDesktopFolderType = 'desk',
kSystemDesktopFolderType = 'sdsk',
kTrashFolderType = 'trsh',
kSystemTrashFolderType = 'strs',
kWhereToEmptyTrashFolderType = 'empt',
kPrintMonitorDocsFolderType = 'prnt',
kStartupFolderType = 'strt',
kShutdownFolderType = 'shdf',
kAppleMenuFolderType = 'amnu',
kControlPanelFolderType = 'ctrl',
kSystemControlPanelFolderType = 'sctl',
kExtensionFolderType = 'extn',
kFontsFolderType = 'font',
kPreferencesFolderType = 'pref',
kSystemPreferencesFolderType = 'sprf',
kTemporaryFolderType = 'temp'
};
enum {
kOnSystemDisk = -32768L,
kOnAppropriateDisk = -32767,
kSystemDomain = -32766,
kLocalDomain = -32765,
kNetworkDomain = -32764,
kUserDomain = -32763,
kClassicDomain = -32762
};
#define kCreateFolder true
#define kDontCreateFolder false
//-----------------------------------------------------------------------------
// Error codes (OSErr)
enum EErrors
{
noErr = 0,
unimpErr = -4, // unimplemented core routine
controlErr = -17, // I/O System Errors
statusErr = -18, // I/O System Errors
readErr = -19, // I/O System Errors
writErr = -20, // I/O System Errors
badUnitErr = -21, // I/O System Errors
unitEmptyErr = -22, // I/O System Errors
openErr = -23, // I/O System Errors
closErr = -24, // I/O System Errors
abortErr = -27, // IO call aborted by KillIO
notOpenErr = -28, // Couldn't rd/wr/ctl/sts cause driver not opened
dirFulErr = -33, // File directory full
dskFulErr = -34, // Disk or volume full
nsvErr = -35, // Volume doesn't exist
ioErr = -36, // I/O error
bdNamErr = -37, // Bad file or volume name
fnOpnErr = -38, // File not open
eofErr = -39, // End-of-file reached
posErr = -40, // Attempt to position mark before start of file
mFulErr = -41, // Memory full (open) or file won't fit (load)
tmfoErr = -42, // Too many files open
fnfErr = -43, // File not found (FSSpec is still valid)
wPrErr = -44, // Volume is hardware-locked
fLckdErr = -45, // File is locked
vLckdErr = -46, // Volume is software-locked
fBsyErr = -47, // File is busy
dupFNErr = -48, // Duplicate filename
opWrErr = -49, // File already open for writing
paramErr = -50, // Invalid value passed in parameter
rfNumErr = -51, // Invalid reference number
gfpErr = -52, // Error during a GetFPos family function
volOffLinErr = -53, // Volume is offline
permErr = -54, // Permission error
volOnLinErr = -55, // Volume already online
nsDrvErr = -56, // No such Drive
noMacDskErr = -57, // Foreign disk
extFSErr = -58, // Volume belongs to external file system
fsRnErr = -59, // Couldn't rename
badMDBErr = -60, // Bad master directory block
wrPermErr = -61, // Read/write permission doesn't allow writing
memROZWarn = -99, // soft error in ROZ
memROZError = -99, // hard error in ROZ
memROZErr = -99, // hard error in ROZ
memFullErr = -108, // Not enough room in heap zone
nilHandleErr = -109, // Master Pointer was NIL in HandleZone or other
memWZErr = -111, // WhichZone failed (applied to free block)
memPurErr = -112, // trying to purge a locked or non-purgeable block
memAdrErr = -110, // address was odd; or out of range
memAZErr = -113, // Address in zone check failed
memPCErr = -114, // Pointer Check failed
memBCErr = -115, // Block Check failed
memSCErr = -116, // Size Check failed
memLockedErr = -117, // trying to move a locked block (MoveHHi)
dirNFErr = -120, // Directory not found
tmwdoErr = -121, // Too many working directories open
badMovErr = -122, // Couldn't move
wrgVolTypErr = -123, // Unrecognized volume (not HFS)
volGoneErr = -124, // Server volume disconnected
fsDSIntErr = -127, // Non-hardware internal file system error
userCanceledErr = -128,
badExtResource = -185, // extended resource has a bad format
CantDecompress = -186, // resource bent ("the bends") - can't decompress a compressed resource
resourceInMemory= -188, // Resource already in memory
writingPastEnd = -189, // Writing past end of file
inputOutOfBounds= -190, // Offset of Count out of bounds
resNotFound = -192, // Resource not found
resFNotFound = -193, // Resource file not found
addResFailed = -194, // AddResource failed
addRefFailed = -195, // AddReference failed
rmvResFailed = -196, // RmveResource failed
rmvRefFailed = -197, // RmveReference failed
resAttrErr = -198, // attribute inconsistent with operation
mapReadErr = -199, // map inconsistent with operation
// Sound Manager error codes
notEnoughHardwareErr = -201,
queueFull = -203,
resProblem = -204,
badChannel = -205, // channel is corrupt or unusable
badFormat = -206, // resource is corrupt or unusable
notEnoughBufferSpace = -207, // insufficient memory available
badFileFormat = -208, // file is corrupt or unusable, or not AIFF or AIFF-C
channelBusy = -209,
buffersTooSmall = -210,
siInvalidCompression = -223,
};
//-----------------------------------------------------------------------------
// Script Manager enums
enum EScriptManager
{
smSystemScript = -1,
smCurrentScript = -2,
smAllScripts = -3
};
//-----------------------------------------------------------------------------
enum EEvents
{
everyEvent = ~0
};
//-----------------------------------------------------------------------------
// Memory enums
enum EMemory
{
maxSize = 0x7FFFFFF0 // the largest block possible
};
//-----------------------------------------------------------------------------
// Resource types
enum EResTypes
{
rAliasType = 'alis',
};
//-----------------------------------------------------------------------------
// Sound Manager enums
enum ESndPitch
{
kMiddleC = 60L,
};
enum ESndSynth
{
squareWaveSynth = 1,
waveTableSynth = 3,
sampledSynth = 5
};
enum ESndInit
{
initChanLeft = 0x0002, // left stereo channel
initChanRight = 0x0003, // right stereo channel
initMono = 0x0080, // monophonic channel
initStereo = 0x00C0, // stereo channel
initMACE3 = 0x0300, // 3:1 compression
initMACE6 = 0x0400, // 6:1 compression
initNoInterp = 0x0004, // no linear interpolation
initNoDrop = 0x0008, // no drop-sample conversion
};
// Sound commands
enum ESndCmds
{
nullCmd = 0,
initCmd = 1,
freeCmd = 2,
quietCmd = 3,
flushCmd = 4,
reInitCmd = 5,
waitCmd = 10,
pauseCmd = 11,
resumeCmd = 12,
callBackCmd = 13,
syncCmd = 14,
availableCmd = 24,
versionCmd = 25,
totalLoadCmd = 26,
loadCmd = 27,
freqDurationCmd = 40,
restCmd = 41,
freqCmd = 42,
ampCmd = 43,
timbreCmd = 44,
getAmpCmd = 45,
volumeCmd = 46, // sound manager 3.0 or later only
getVolumeCmd = 47, // sound manager 3.0 or later only
clockComponentCmd = 50, // sound manager 3.2.1 or later only
getClockComponentCmd = 51, // sound manager 3.2.1 or later only
scheduledSoundCmd = 52, // sound manager 3.3 or later only
linkSoundComponentsCmd = 53, // sound manager 3.3 or later only
waveTableCmd = 60,
phaseCmd = 61,
soundCmd = 80,
bufferCmd = 81,
rateCmd = 82,
continueCmd = 83,
doubleBufferCmd = 84,
getRateCmd = 85,
rateMultiplierCmd = 86,
getRateMultiplierCmd = 87,
sizeCmd = 90, // obsolete command
convertCmd = 91, // obsolete MACE command
pommeSetLoopCmd = 0x7001,
// Do not define commands above 0x7FFF -- the high bit means a 'snd ' resource has associated sound data
};
//-----------------------------------------------------------------------------
// Keyboard enums
enum
{
// key positions on US keyboard
kVK_ANSI_A = 0x00,
kVK_ANSI_S = 0x01,
kVK_ANSI_D = 0x02,
kVK_ANSI_F = 0x03,
kVK_ANSI_H = 0x04,
kVK_ANSI_G = 0x05,
kVK_ANSI_Z = 0x06,
kVK_ANSI_X = 0x07,
kVK_ANSI_C = 0x08,
kVK_ANSI_V = 0x09,
kVK_ANSI_B = 0x0B,
kVK_ANSI_Q = 0x0C,
kVK_ANSI_W = 0x0D,
kVK_ANSI_E = 0x0E,
kVK_ANSI_R = 0x0F,
kVK_ANSI_Y = 0x10,
kVK_ANSI_T = 0x11,
kVK_ANSI_1 = 0x12,
kVK_ANSI_2 = 0x13,
kVK_ANSI_3 = 0x14,
kVK_ANSI_4 = 0x15,
kVK_ANSI_6 = 0x16,
kVK_ANSI_5 = 0x17,
kVK_ANSI_Equal = 0x18,
kVK_ANSI_9 = 0x19,
kVK_ANSI_7 = 0x1A,
kVK_ANSI_Minus = 0x1B,
kVK_ANSI_8 = 0x1C,
kVK_ANSI_0 = 0x1D,
kVK_ANSI_RightBracket = 0x1E,
kVK_ANSI_O = 0x1F,
kVK_ANSI_U = 0x20,
kVK_ANSI_LeftBracket = 0x21,
kVK_ANSI_I = 0x22,
kVK_ANSI_P = 0x23,
kVK_ANSI_L = 0x25,
kVK_ANSI_J = 0x26,
kVK_ANSI_Quote = 0x27,
kVK_ANSI_K = 0x28,
kVK_ANSI_Semicolon = 0x29,
kVK_ANSI_Backslash = 0x2A,
kVK_ANSI_Comma = 0x2B,
kVK_ANSI_Slash = 0x2C,
kVK_ANSI_N = 0x2D,
kVK_ANSI_M = 0x2E,
kVK_ANSI_Period = 0x2F,
kVK_ANSI_Grave = 0x32,
kVK_ANSI_KeypadDecimal = 0x41,
kVK_ANSI_KeypadMultiply = 0x43,
kVK_ANSI_KeypadPlus = 0x45,
kVK_ANSI_KeypadClear = 0x47,
kVK_ANSI_KeypadDivide = 0x4B,
kVK_ANSI_KeypadEnter = 0x4C,
kVK_ANSI_KeypadMinus = 0x4E,
kVK_ANSI_KeypadEquals = 0x51,
kVK_ANSI_Keypad0 = 0x52,
kVK_ANSI_Keypad1 = 0x53,
kVK_ANSI_Keypad2 = 0x54,
kVK_ANSI_Keypad3 = 0x55,
kVK_ANSI_Keypad4 = 0x56,
kVK_ANSI_Keypad5 = 0x57,
kVK_ANSI_Keypad6 = 0x58,
kVK_ANSI_Keypad7 = 0x59,
kVK_ANSI_Keypad8 = 0x5B,
kVK_ANSI_Keypad9 = 0x5C
};
// keycodes for keys that are independent of keyboard layout
enum
{
kVK_Return = 0x24,
kVK_Tab = 0x30,
kVK_Space = 0x31,
kVK_Delete = 0x33,
kVK_Escape = 0x35,
kVK_Command = 0x37,
kVK_Shift = 0x38,
kVK_CapsLock = 0x39,
kVK_Option = 0x3A,
kVK_Control = 0x3B,
kVK_RightShift = 0x3C,
kVK_RightOption = 0x3D,
kVK_RightControl = 0x3E,
kVK_Function = 0x3F,
kVK_F17 = 0x40,
kVK_VolumeUp = 0x48,
kVK_VolumeDown = 0x49,
kVK_Mute = 0x4A,
kVK_F18 = 0x4F,
kVK_F19 = 0x50,
kVK_F20 = 0x5A,
kVK_F5 = 0x60,
kVK_F6 = 0x61,
kVK_F7 = 0x62,
kVK_F3 = 0x63,
kVK_F8 = 0x64,
kVK_F9 = 0x65,
kVK_F11 = 0x67,
kVK_F13 = 0x69,
kVK_F16 = 0x6A,
kVK_F14 = 0x6B,
kVK_F10 = 0x6D,
kVK_F12 = 0x6F,
kVK_F15 = 0x71,
kVK_Help = 0x72,
kVK_Home = 0x73,
kVK_PageUp = 0x74,
kVK_ForwardDelete = 0x75,
kVK_F4 = 0x76,
kVK_End = 0x77,
kVK_F2 = 0x78,
kVK_PageDown = 0x79,
kVK_F1 = 0x7A,
kVK_LeftArrow = 0x7B,
kVK_RightArrow = 0x7C,
kVK_DownArrow = 0x7D,
kVK_UpArrow = 0x7E
};
//-----------------------------------------------------------------------------
// QD2D
enum
{
whiteColor = 30,
blackColor = 33,
yellowColor = 69,
magentaColor = 137,
redColor = 205,
cyanColor = 273,
greenColor = 341,
blueColor = 409,
};

41
src/PommeFiles.h Normal file
View File

@ -0,0 +1,41 @@
#pragma once
#include "PommeTypes.h"
#include <iostream>
#include <map>
#include "CompilerSupport/filesystem.h"
namespace Pomme::Files
{
struct ResourceMetadata
{
short forkRefNum;
OSType type;
SInt16 id;
Byte flags;
SInt32 size;
UInt32 dataOffset;
std::string name;
};
struct ResourceFork
{
SInt16 fileRefNum;
std::map<ResType, std::map<SInt16, ResourceMetadata> > resourceMap;
};
void Init();
bool IsRefNumLegal(short refNum);
bool IsStreamOpen(short refNum);
bool IsStreamPermissionAllowed(short refNum, char perm);
std::iostream& GetStream(short refNum);
void CloseStream(short refNum);
FSSpec HostPathToFSSpec(const fs::path& fullPath);
}

66
src/PommeGraphics.h Normal file
View File

@ -0,0 +1,66 @@
#pragma once
#include "PommeTypes.h"
#include <istream>
#include <vector>
namespace Pomme::Graphics
{
struct Color
{
UInt8 a, r, g, b;
Color(UInt8 red, UInt8 green, UInt8 blue);
Color(UInt8 red, UInt8 green, UInt8 blue, UInt8 alpha);
Color();
};
struct ARGBPixmap
{
int width;
int height;
std::vector<Byte> data;
ARGBPixmap();
ARGBPixmap(int w, int h);
ARGBPixmap(ARGBPixmap&& other) noexcept;
ARGBPixmap& operator=(ARGBPixmap&& other) noexcept;
void Fill(UInt8 red, UInt8 green, UInt8 blue, UInt8 alpha = 0xFF);
void Plot(int x, int y, UInt32 color);
void WriteTGA(const char* path) const;
inline UInt32* GetPtr(int x, int y)
{ return (UInt32*) &data.data()[4 * (y * width + x)]; }
};
void Init(const char* windowTitle, int windowWidth, int windowHeight);
void Shutdown();
ARGBPixmap ReadPICT(std::istream& f, bool skip512 = true);
void DumpTGA(const char* path, short width, short height, const char* argbData);
void DrawARGBPixmap(int left, int top, ARGBPixmap& p);
CGrafPtr GetScreenPort(void);
void SetWindowIconFromIcl8Resource(short i);
inline int Width(const Rect& r)
{ return r.right - r.left; }
inline int Height(const Rect& r)
{ return r.bottom - r.top; }
extern const uint32_t clut8[256];
extern const uint32_t clut4[16];
}

16
src/PommeInit.h Normal file
View File

@ -0,0 +1,16 @@
#pragma once
namespace Pomme
{
// Throw this exception to interrupt the game's main loop
class QuitRequest : public std::exception
{
public:
virtual const char* what() const noexcept;
};
void Init(const char* applName);
void Shutdown();
}

6
src/PommeInput.h Normal file
View File

@ -0,0 +1,6 @@
#pragma once
namespace Pomme::Input
{
void Init();
}

70
src/PommeSound.h Normal file
View File

@ -0,0 +1,70 @@
#pragma once
#include "CompilerSupport/span.h"
#include <vector>
#include <istream>
#include <memory>
#include "Sound/cmixer.h"
namespace Pomme::Sound
{
void Init();
void Shutdown();
void ReadAIFF(std::istream& input, cmixer::WavStream& output);
class Codec
{
public:
virtual ~Codec()
{}
virtual int SamplesPerPacket() = 0;
virtual int BytesPerPacket() = 0;
virtual void Decode(const int nChannels, const std::span<const char> input, const std::span<char> output) = 0;
};
class MACE : public Codec
{
public:
int SamplesPerPacket() override
{ return 6; }
int BytesPerPacket() override
{ return 2; }
void Decode(const int nChannels, const std::span<const char> input, const std::span<char> output) override;
};
class IMA4 : public Codec
{
public:
int SamplesPerPacket() override
{ return 64; }
int BytesPerPacket() override
{ return 34; }
void Decode(const int nChannels, const std::span<const char> input, const std::span<char> output) override;
};
class xlaw : public Codec
{
const int16_t* xlawToPCM;
public:
xlaw(uint32_t codecFourCC);
int SamplesPerPacket() override
{ return 1; }
int BytesPerPacket() override
{ return 1; }
void Decode(const int nChannels, const std::span<const char> input, const std::span<char> output) override;
};
std::unique_ptr<Pomme::Sound::Codec> GetCodec(uint32_t fourCC);
}

6
src/PommeTime.h Normal file
View File

@ -0,0 +1,6 @@
#pragma once
namespace Pomme::Time
{
void Init();
}

258
src/PommeTypes.h Normal file
View File

@ -0,0 +1,258 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
//-----------------------------------------------------------------------------
// Integer types
typedef char SignedByte;
typedef char SInt8;
typedef short SInt16;
typedef int SInt32;
typedef long long SInt64;
typedef unsigned char Byte;
typedef unsigned char UInt8;
typedef unsigned char Boolean;
typedef unsigned short UInt16;
typedef unsigned int UInt32;
typedef unsigned long long UInt64;
#if TARGET_RT_BIGENDIAN
typedef struct { UInt32 hi, lo; } UnsignedWide;
#else
typedef struct { UInt32 lo, hi; } UnsignedWide;
#endif
//-----------------------------------------------------------------------------
// Fixed/fract types
typedef SInt32 Fixed;
typedef SInt32 Fract;
typedef UInt32 UnsignedFixed;
typedef SInt16 ShortFixed;
typedef Fixed* FixedPtr;
typedef Fract* FractPtr;
typedef UnsignedFixed* UnsignedFixedPtr;
typedef ShortFixed* ShortFixedPtr;
//-----------------------------------------------------------------------------
// Basic system types
typedef SInt16 OSErr;
typedef SInt32 OSStatus;
typedef void* LogicalAddress;
typedef const void* ConstLogicalAddress;
typedef void* PhysicalAddress;
typedef UInt8* BytePtr;
typedef unsigned long ByteCount;
typedef unsigned long ByteOffset;
typedef SInt32 Duration;
typedef UnsignedWide AbsoluteTime;
typedef UInt32 OptionBits;
typedef unsigned long ItemCount;
typedef UInt32 PBVersion;
typedef SInt16 ScriptCode;
typedef SInt16 LangCode;
typedef SInt16 RegionCode;
typedef UInt32 FourCharCode;
typedef FourCharCode OSType;
typedef FourCharCode ResType;
typedef OSType* OSTypePtr;
typedef ResType* ResTypePtr;
typedef char* Ptr; // Pointer to a non-relocatable block
typedef Ptr* Handle; // Pointer to a master pointer to a relocatable block
typedef long Size; // Number of bytes in a block (signed for historical reasons)
typedef void (*ProcPtr);
//-----------------------------------------------------------------------------
// (Pascal) String types
typedef char Str32[33];
typedef char Str255[256];
typedef char* StringPtr;
typedef const char* ConstStr255Param;
//-----------------------------------------------------------------------------
// Point & Rect types
typedef struct Point { SInt16 v, h; } Point;
typedef struct Rect { SInt16 top, left, bottom, right; } Rect;
typedef Point* PointPtr;
typedef Rect* RectPtr;
typedef struct FixedPoint { Fixed x, y; } FixedPoint;
typedef struct FixedRect { Fixed left, top, right, bottom; } FixedRect;
//-----------------------------------------------------------------------------
// FSSpec types
typedef struct FSSpec
{
// Volume reference number of the volume containing the specified file or directory.
short vRefNum;
// Parent directory ID of the specified file or directory (the directory ID of the directory containing the given file or directory).
long parID;
// The name of the specified file or directory. In Carbon, this name must be a leaf name; the name cannot contain a semicolon.
// WARNING: this is a C string encoded as UTF-8, NOT a pascal string encoded as MacRoman!
// Mac application code expecting "name" (the pascal string) must be adjusted.
Str255 cName;
} FSSpec;
typedef Handle AliasHandle;
//-----------------------------------------------------------------------------
// QuickDraw types
typedef SInt16 QDErr;
typedef struct RGBColor
{
UInt16 red;
UInt16 green;
UInt16 blue;
} RGBColor;
typedef struct Picture
{
// Version 1 size.
// Not used for version 2 PICTs, as the size may easily exceed 16 bits.
SInt16 picSize;
Rect picFrame;
// Raw raster image decoded from PICT opcodes. Shouldn't be accessed
// directly by the Mac application as it is stored in a format internal
// to the Pomme implementation for rendering (typically ARGB32).
Ptr __pomme_pixelsARGB32;
} Picture;
typedef Picture* PicPtr;
typedef PicPtr* PicHandle;
typedef Handle GDHandle; // GDevice handle. Game code doesn't care about GDevice internals, so we just alias a generic Handle
// http://mirror.informatimago.com/next/developer.apple.com/documentation/Carbon/Reference/QuickDraw_Ref/qdref_main/data_type_41.html
typedef struct PixMap
{
Rect bounds;
short pixelSize;
Ptr _impl; // Points to ARGBPixmap
} PixMap;
typedef PixMap* PixMapPtr;
typedef PixMapPtr* PixMapHandle;
typedef struct GrafPort
{
Rect portRect;
void* _impl; // Points to GrafPortImpl
} GrafPort;
typedef GrafPort* GrafPtr;
typedef GrafPtr WindowPtr;
typedef GrafPort CGrafPort;
typedef GrafPtr CGrafPtr;
typedef CGrafPtr GWorldPtr;
//-----------------------------------------------------------------------------
// Sound Manager types
typedef struct SndCommand
{
unsigned short cmd;
short param1;
union {
long param2;
Ptr ptr; // pomme addition to pass 64-bit clean pointers
};
} SndCommand;
typedef struct SCStatus
{
UnsignedFixed scStartTime; // starting time for play from disk (based on audio selection record)
UnsignedFixed scEndTime; // ending time for play from disk (based on audio selection record)
UnsignedFixed scCurrentTime; // current time for play from disk
Boolean scChannelBusy; // true if channel is processing commands
Boolean scChannelDisposed; // reserved
Boolean scChannelPaused; // true if channel is paused
Boolean scUnused; // reserved
unsigned long scChannelAttributes; // attributes of this channel
long scCPULoad; // cpu load for this channel ("obsolete")
} SCStatus;
typedef struct SndChannel* SndChannelPtr;
typedef void (*SndCallBackProcPtr)(SndChannelPtr chan, SndCommand* cmd);
// for pomme implementation purposes we don't care about the 68000/ppc specifics of universal procedure pointers
typedef SndCallBackProcPtr SndCallbackUPP;
typedef struct SndChannel
{
SndChannelPtr nextChan;
Ptr firstMod; // reserved for the Sound Manager (Pomme: used as internal ptr)
SndCallBackProcPtr callBack;
long long userInfo; // free for application's use (Pomme: made it 64 bit so app can store ptrs)
#if 0
long wait; // The following is for internal Sound Manager use only.
SndCommand cmdInProgress;
short flags;
short qLength;
short qHead;
short qTail;
SndCommand queue[128];
#endif
} SndChannel;
typedef struct ModRef
{
unsigned short modNumber;
long modInit;
} ModRef;
typedef struct SndListResource
{
short format;
short numModifiers;
// flexible array hack
ModRef modifierPart[1];
short numCommands;
// flexible array hack
SndCommand commandPart[1];
UInt8 dataPart[1];
} SndListResource;
typedef SCStatus* SCStatusPtr;
typedef SndListResource* SndListPtr;
typedef SndListPtr* SndListHandle;
typedef SndListHandle SndListHndl;
typedef void(*FilePlayCompletionProcPtr)(SndChannelPtr chan);
typedef FilePlayCompletionProcPtr FilePlayCompletionUPP;
#define NewFilePlayCompletionProc(userRoutine) (userRoutine)
//-----------------------------------------------------------------------------
// Keyboard input types
typedef UInt32 KeyMap[4];
//-----------------------------------------------------------------------------
// 'vers' resource
#if TARGET_RT_BIG_ENDIAN
//BCD encoded, e.g. "4.2.1a3" is 0x04214003
typedef struct NumVersion
{
UInt8 majorRev; // 1st part of version number in BCD
UInt8 minorAndBugRev; // 2nd & 3rd part of version number share a byte
UInt8 stage; // stage code: dev, alpha, beta, final
UInt8 nonRelRev; // revision level of non-released version
} NumVersion;
#else
typedef struct NumVersion
{
UInt8 nonRelRev; // revision level of non-released version
UInt8 stage; // stage code: dev, alpha, beta, final
UInt8 minorAndBugRev; // 2nd & 3rd part of version number share a byte
UInt8 majorRev; // 1st part of version number in BCD
} NumVersion;
#endif

29
src/PommeVideo.h Normal file
View File

@ -0,0 +1,29 @@
#pragma once
#include "PommeTypes.h"
#include "Sound/cmixer.h"
#include <istream>
#include <queue>
#include <vector>
namespace Pomme::Video
{
struct Movie
{
int width;
int height;
FourCharCode videoFormat;
float videoFrameRate;
std::queue<std::vector<unsigned char>> videoFrames;
FourCharCode audioFormat;
int audioSampleRate;
int audioBitDepth;
int audioNChannels;
cmixer::WavStream audioStream;
unsigned audioSampleCount;
};
Movie ReadMoov(std::istream& f);
}

88
src/Sound/AIFF.cpp Normal file
View File

@ -0,0 +1,88 @@
#include "PommeSound.h"
#include "Utilities/BigEndianIStream.h"
#include <cstdint>
static void AIFFAssert(bool condition, const char* message)
{
if (!condition)
{
throw std::runtime_error(message);
}
}
void Pomme::Sound::ReadAIFF(std::istream& input, cmixer::WavStream& output)
{
BigEndianIStream f(input);
AIFFAssert('FORM' == f.Read<uint32_t>(), "AIFF: invalid FORM");
auto formSize = f.Read<uint32_t>();
auto endOfForm = f.Tell() + std::streampos(formSize);
auto formType = f.Read<uint32_t>();
AIFFAssert(formType == 'AIFF' || formType == 'AIFC', "AIFF: not an AIFF or AIFC file");
// COMM chunk contents
int nChannels = 0;
int nPackets = 0;
int sampleRate = 0;
uint32_t compressionType = 'NONE';
bool gotCOMM = false;
while (f.Tell() != endOfForm)
{
auto ckID = f.Read<uint32_t>();
auto ckSize = f.Read<uint32_t>();
std::streampos endOfChunk = f.Tell() + std::streampos(ckSize);
switch (ckID)
{
case 'FVER':
{
auto timestamp = f.Read<uint32_t>();
AIFFAssert(timestamp == 0xA2805140u, "AIFF: unrecognized FVER");
break;
}
case 'COMM': // common chunk, 2-85
{
nChannels = f.Read<uint16_t>();
nPackets = f.Read<uint32_t>();
f.Skip(2); // sample bit depth (UInt16)
sampleRate = (int)f.Read80BitFloat();
if (formType == 'AIFC')
{
compressionType = f.Read<uint32_t>();
f.ReadPascalString(); // This is a human-friendly compression name. Skip it.
}
gotCOMM = true;
break;
}
case 'SSND':
{
AIFFAssert(gotCOMM, "AIFF: reached SSND before COMM");
AIFFAssert(0 == f.Read<uint64_t>(), "AIFF: unexpected offset/blockSize in SSND");
// sampled sound data is here
const int ssndSize = ckSize - 8;
auto ssnd = std::vector<char>(ssndSize);
f.Read(ssnd.data(), ssndSize);
// TODO: if the compression type is 'NONE' (raw PCM), just init the WavStream without decoding (not needed for Nanosaur though)
auto codec = Pomme::Sound::GetCodec(compressionType);
auto spanIn = std::span(ssnd);
auto spanOut = output.GetBuffer(nChannels * nPackets * codec->SamplesPerPacket() * 2);
codec->Decode(nChannels, spanIn, spanOut);
output.Init(sampleRate, 16, nChannels, false, spanOut);
break;
}
default:
f.Goto(int(endOfChunk));
break;
}
AIFFAssert(f.Tell() == endOfChunk, "AIFF: incorrect end-of-chunk position");
}
}

161
src/Sound/IMA4.cpp Normal file
View File

@ -0,0 +1,161 @@
// Adapted from ffmpeg. Look at libavcodec/adpcm{,_data}.{c,h}
/*
* Portions Copyright (c) 2001-2003 The FFmpeg project
*
* FFmpeg is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* FFmpeg is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with FFmpeg; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "PommeSound.h"
#include <vector>
#include <cassert>
#include <algorithm>
const int8_t ff_adpcm_index_table[16] = {
-1, -1, -1, -1, 2, 4, 6, 8,
-1, -1, -1, -1, 2, 4, 6, 8,
};
const int16_t ff_adpcm_step_table[89] = {
7, 8, 9, 10, 11, 12, 13, 14, 16, 17,
19, 21, 23, 25, 28, 31, 34, 37, 41, 45,
50, 55, 60, 66, 73, 80, 88, 97, 107, 118,
130, 143, 157, 173, 190, 209, 230, 253, 279, 307,
337, 371, 408, 449, 494, 544, 598, 658, 724, 796,
876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066,
2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358,
5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899,
15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767
};
struct ADPCMChannelStatus
{
int predictor;
int16_t step_index;
int step;
};
static inline int sign_extend(int val, unsigned bits)
{
unsigned shift = 8 * sizeof(int) - bits;
union { unsigned u; int s; } v = { (unsigned)val << shift };
return v.s >> shift;
}
static inline int adpcm_ima_qt_expand_nibble(ADPCMChannelStatus* c, int nibble, int shift)
{
int step_index;
int predictor;
int diff, step;
step = ff_adpcm_step_table[c->step_index];
step_index = c->step_index + ff_adpcm_index_table[nibble];
step_index = std::clamp(step_index, 0, 88);
diff = step >> 3;
if (nibble & 4) diff += step;
if (nibble & 2) diff += step >> 1;
if (nibble & 1) diff += step >> 2;
if (nibble & 8)
predictor = c->predictor - diff;
else
predictor = c->predictor + diff;
c->predictor = std::clamp(predictor, -32768, 32767);
c->step_index = step_index;
return c->predictor;
}
// In QuickTime, IMA is encoded by chunks of 34 bytes (=64 samples). Channel data is interleaved per-chunk.
static void DecodeIMA4Chunk(
const uint8_t** input,
int16_t** output,
std::vector<ADPCMChannelStatus>& ctx)
{
const size_t nChannels = ctx.size();
const unsigned char* in = *input;
int16_t* out = *output;
for (size_t chan = 0; chan < nChannels; chan++)
{
ADPCMChannelStatus& cs = ctx[chan];
// Bits 15-7 are the _top_ 9 bits of the 16-bit initial predictor value
int predictor = sign_extend((in[0] << 8) | in[1], 16);
int step_index = predictor & 0x7F;
predictor &= ~0x7F;
in += 2;
if (cs.step_index == step_index)
{
int diff = predictor - cs.predictor;
if (diff < 0x00) diff = -diff;
if (diff > 0x7f) goto update;
}
else
{
update:
cs.step_index = step_index;
cs.predictor = predictor;
}
if (cs.step_index > 88)
throw std::invalid_argument("step_index[chan]>88!");
size_t pos = chan;
for (int m = 0; m < 32; m++)
{
int byte = (uint8_t) (*in++);
out[pos] = adpcm_ima_qt_expand_nibble(&cs, byte & 0x0F, 3);
pos += nChannels;
out[pos] = adpcm_ima_qt_expand_nibble(&cs, byte >> 4, 3);
pos += nChannels;
}
}
*input = in;
*output += 64 * nChannels;
}
void Pomme::Sound::IMA4::Decode(
const int nChannels,
const std::span<const char> input,
const std::span<char> output)
{
if (input.size() % 34 != 0)
throw std::invalid_argument("odd input buffer size");
const size_t nChunks = input.size() / (34 * nChannels);
const size_t nSamples = 64 * nChunks;
if (output.size() != nSamples * nChannels * 2)
throw std::invalid_argument("incorrect output size");
const uint8_t* in = reinterpret_cast<const unsigned char*>(input.data());
int16_t* out = reinterpret_cast<int16_t*>(output.data());
std::vector<ADPCMChannelStatus> ctx(nChannels);
for (size_t chunk = 0; chunk < nChunks; chunk++)
{
DecodeIMA4Chunk(&in, &out, ctx);
}
assert(reinterpret_cast<const char*>(in) == input.data() + input.size());
assert(reinterpret_cast<char*>(out) == output.data() + output.size());
}

229
src/Sound/MACE.cpp Normal file
View File

@ -0,0 +1,229 @@
// Adapted from ffmpeg
// ---- Begin ffmpeg copyright notices ----
/*
* MACE decoder
* Copyright (c) 2002 Laszlo Torok <torokl@alpha.dfmk.hu>
*
* This file is part of FFmpeg.
*
* FFmpeg is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* FFmpeg is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with FFmpeg; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
/*
* Adapted to libavcodec by Francois Revol <revol@free.fr>
* (removed 68k REG stuff, changed types, added some statics and consts,
* libavcodec api, context stuff, interlaced stereo out).
*/
// ---- End ffmpeg copyright notices ----
#include "PommeSound.h"
#include <cassert>
static const int16_t MACEtab1[] = {-13, 8, 76, 222, 222, 76, 8, -13};
static const int16_t MACEtab3[] = {-18, 140, 140, -18};
static const int16_t MACEtab2[][4] = {
{ 37, 116, 206, 330}, { 39, 121, 216, 346},
{ 41, 127, 225, 361}, { 42, 132, 235, 377},
{ 44, 137, 245, 392}, { 46, 144, 256, 410},
{ 48, 150, 267, 428}, { 51, 157, 280, 449},
{ 53, 165, 293, 470}, { 55, 172, 306, 490},
{ 58, 179, 319, 511}, { 60, 187, 333, 534},
{ 63, 195, 348, 557}, { 66, 205, 364, 583},
{ 69, 214, 380, 609}, { 72, 223, 396, 635},
{ 75, 233, 414, 663}, { 79, 244, 433, 694},
{ 82, 254, 453, 725}, { 86, 265, 472, 756},
{ 90, 278, 495, 792}, { 94, 290, 516, 826},
{ 98, 303, 538, 862}, { 102, 316, 562, 901},
{ 107, 331, 588, 942}, { 112, 345, 614, 983},
{ 117, 361, 641, 1027}, { 122, 377, 670, 1074},
{ 127, 394, 701, 1123}, { 133, 411, 732, 1172},
{ 139, 430, 764, 1224}, { 145, 449, 799, 1280},
{ 152, 469, 835, 1337}, { 159, 490, 872, 1397},
{ 166, 512, 911, 1459}, { 173, 535, 951, 1523},
{ 181, 558, 993, 1590}, { 189, 584, 1038, 1663},
{ 197, 610, 1085, 1738}, { 206, 637, 1133, 1815},
{ 215, 665, 1183, 1895}, { 225, 695, 1237, 1980},
{ 235, 726, 1291, 2068}, { 246, 759, 1349, 2161},
{ 257, 792, 1409, 2257}, { 268, 828, 1472, 2357},
{ 280, 865, 1538, 2463}, { 293, 903, 1606, 2572},
{ 306, 944, 1678, 2688}, { 319, 986, 1753, 2807},
{ 334, 1030, 1832, 2933}, { 349, 1076, 1914, 3065},
{ 364, 1124, 1999, 3202}, { 380, 1174, 2088, 3344},
{ 398, 1227, 2182, 3494}, { 415, 1281, 2278, 3649},
{ 434, 1339, 2380, 3811}, { 453, 1398, 2486, 3982},
{ 473, 1461, 2598, 4160}, { 495, 1526, 2714, 4346},
{ 517, 1594, 2835, 4540}, { 540, 1665, 2961, 4741},
{ 564, 1740, 3093, 4953}, { 589, 1818, 3232, 5175},
{ 615, 1898, 3375, 5405}, { 643, 1984, 3527, 5647},
{ 671, 2072, 3683, 5898}, { 701, 2164, 3848, 6161},
{ 733, 2261, 4020, 6438}, { 766, 2362, 4199, 6724},
{ 800, 2467, 4386, 7024}, { 836, 2578, 4583, 7339},
{ 873, 2692, 4786, 7664}, { 912, 2813, 5001, 8008},
{ 952, 2938, 5223, 8364}, { 995, 3070, 5457, 8739},
{ 1039, 3207, 5701, 9129}, { 1086, 3350, 5956, 9537},
{ 1134, 3499, 6220, 9960}, { 1185, 3655, 6497, 10404},
{ 1238, 3818, 6788, 10869}, { 1293, 3989, 7091, 11355},
{ 1351, 4166, 7407, 11861}, { 1411, 4352, 7738, 12390},
{ 1474, 4547, 8084, 12946}, { 1540, 4750, 8444, 13522},
{ 1609, 4962, 8821, 14126}, { 1680, 5183, 9215, 14756},
{ 1756, 5415, 9626, 15415}, { 1834, 5657, 10057, 16104},
{ 1916, 5909, 10505, 16822}, { 2001, 6173, 10975, 17574},
{ 2091, 6448, 11463, 18356}, { 2184, 6736, 11974, 19175},
{ 2282, 7037, 12510, 20032}, { 2383, 7351, 13068, 20926},
{ 2490, 7679, 13652, 21861}, { 2601, 8021, 14260, 22834},
{ 2717, 8380, 14897, 23854}, { 2838, 8753, 15561, 24918},
{ 2965, 9144, 16256, 26031}, { 3097, 9553, 16982, 27193},
{ 3236, 9979, 17740, 28407}, { 3380, 10424, 18532, 29675},
{ 3531, 10890, 19359, 31000}, { 3688, 11375, 20222, 32382},
{ 3853, 11883, 21125, 32767}, { 4025, 12414, 22069, 32767},
{ 4205, 12967, 23053, 32767}, { 4392, 13546, 24082, 32767},
{ 4589, 14151, 25157, 32767}, { 4793, 14783, 26280, 32767},
{ 5007, 15442, 27452, 32767}, { 5231, 16132, 28678, 32767},
{ 5464, 16851, 29957, 32767}, { 5708, 17603, 31294, 32767},
{ 5963, 18389, 32691, 32767}, { 6229, 19210, 32767, 32767},
{ 6507, 20067, 32767, 32767}, { 6797, 20963, 32767, 32767},
{ 7101, 21899, 32767, 32767}, { 7418, 22876, 32767, 32767},
{ 7749, 23897, 32767, 32767}, { 8095, 24964, 32767, 32767},
{ 8456, 26078, 32767, 32767}, { 8833, 27242, 32767, 32767},
{ 9228, 28457, 32767, 32767}, { 9639, 29727, 32767, 32767}
};
static const int16_t MACEtab4[][2] = {
{ 64, 216}, { 67, 226}, { 70, 236}, { 74, 246},
{ 77, 257}, { 80, 268}, { 84, 280}, { 88, 294},
{ 92, 307}, { 96, 321}, { 100, 334}, { 104, 350},
{ 109, 365}, { 114, 382}, { 119, 399}, { 124, 416},
{ 130, 434}, { 136, 454}, { 142, 475}, { 148, 495},
{ 155, 519}, { 162, 541}, { 169, 564}, { 176, 590},
{ 185, 617}, { 193, 644}, { 201, 673}, { 210, 703},
{ 220, 735}, { 230, 767}, { 240, 801}, { 251, 838},
{ 262, 876}, { 274, 914}, { 286, 955}, { 299, 997},
{ 312, 1041}, { 326, 1089}, { 341, 1138}, { 356, 1188},
{ 372, 1241}, { 388, 1297}, { 406, 1354}, { 424, 1415},
{ 443, 1478}, { 462, 1544}, { 483, 1613}, { 505, 1684},
{ 527, 1760}, { 551, 1838}, { 576, 1921}, { 601, 2007},
{ 628, 2097}, { 656, 2190}, { 686, 2288}, { 716, 2389},
{ 748, 2496}, { 781, 2607}, { 816, 2724}, { 853, 2846},
{ 891, 2973}, { 930, 3104}, { 972, 3243}, { 1016, 3389},
{ 1061, 3539}, { 1108, 3698}, { 1158, 3862}, { 1209, 4035},
{ 1264, 4216}, { 1320, 4403}, { 1379, 4599}, { 1441, 4806},
{ 1505, 5019}, { 1572, 5244}, { 1642, 5477}, { 1715, 5722},
{ 1792, 5978}, { 1872, 6245}, { 1955, 6522}, { 2043, 6813},
{ 2134, 7118}, { 2229, 7436}, { 2329, 7767}, { 2432, 8114},
{ 2541, 8477}, { 2655, 8854}, { 2773, 9250}, { 2897, 9663},
{ 3026, 10094}, { 3162, 10546}, { 3303, 11016}, { 3450, 11508},
{ 3604, 12020}, { 3765, 12556}, { 3933, 13118}, { 4108, 13703},
{ 4292, 14315}, { 4483, 14953}, { 4683, 15621}, { 4892, 16318},
{ 5111, 17046}, { 5339, 17807}, { 5577, 18602}, { 5826, 19433},
{ 6086, 20300}, { 6358, 21205}, { 6642, 22152}, { 6938, 23141},
{ 7248, 24173}, { 7571, 25252}, { 7909, 26380}, { 8262, 27557},
{ 8631, 28786}, { 9016, 30072}, { 9419, 31413}, { 9839, 32767},
{ 10278, 32767}, { 10737, 32767}, { 11216, 32767}, { 11717, 32767},
{ 12240, 32767}, { 12786, 32767}, { 13356, 32767}, { 13953, 32767},
{ 14576, 32767}, { 15226, 32767}, { 15906, 32767}, { 16615, 32767}
};
static const struct
{
const int16_t *tab1;
const int16_t *tab2;
int stride;
} tabs[] = {
{MACEtab1, &MACEtab2[0][0], 4},
{MACEtab3, &MACEtab4[0][0], 2},
{MACEtab1, &MACEtab2[0][0], 4}
};
#define QT_8S_2_16S(x) (((x) & 0xFF00) | (((x) >> 8) & 0xFF))
struct ChannelData
{
int16_t index, factor, prev2, previous, level;
};
struct MACEContext
{
ChannelData chd[2];
};
static inline int16_t mace_broken_clip_int16(int n)
{
if (n > 32767)
return 32767;
else if (n < -32768)
return -32767;
else
return n;
}
static int16_t read_table(ChannelData* chd, uint8_t val, int tab_idx)
{
int16_t current;
if (val < tabs[tab_idx].stride)
current = tabs[tab_idx].tab2[((chd->index & 0x7f0) >> 4) * tabs[tab_idx].stride + val];
else
current = - 1 - tabs[tab_idx].tab2[((chd->index & 0x7f0) >> 4)*tabs[tab_idx].stride + 2*tabs[tab_idx].stride-val-1];
if (( chd->index += tabs[tab_idx].tab1[val]-(chd->index >> 5) ) < 0)
chd->index = 0;
return current;
}
void Pomme::Sound::MACE::Decode(
const int nChannels,
const std::span<const char> input,
const std::span<char> output)
{
if (input.size() % (nChannels * 2) != 0)
throw std::invalid_argument("odd input buffer size");
size_t nSamples = 3 * int(input.size()) / nChannels;
if (output.size() != nSamples * nChannels * 2)
throw std::invalid_argument("incorrect output size");
int16_t* out = reinterpret_cast<int16_t*>(output.data());
MACEContext ctx = {};
for (int chan = 0; chan < nChannels; chan++)
for (size_t j = 0; j < input.size() / (nChannels * 2); j++)
for (int k = 0; k < 2; k++)
{
uint8_t pkt = (uint8_t)input[(chan * 2) + (j * nChannels * 2) + k];
int val2[3] = { pkt & 7, (pkt >> 3) & 3, pkt >> 5 };
for (int l = 0; l < 3; l++)
{
int16_t current = read_table(&ctx.chd[chan], (uint8_t)val2[l], l);
current = mace_broken_clip_int16(current + ctx.chd[chan].level);
ctx.chd[chan].level = current - (current >> 3);
current = QT_8S_2_16S(current);
*out = current;
out++;
}
}
assert(reinterpret_cast<char*>(out) == output.data() + output.size());
}

782
src/Sound/SoundManager.cpp Normal file
View File

@ -0,0 +1,782 @@
#include "Pomme.h"
#include "PommeFiles.h"
#include "Sound/cmixer.h"
#include "PommeSound.h"
#include "Utilities/BigEndianIStream.h"
#include "Utilities/memstream.h"
#include <thread>
#include <chrono>
#include <iostream>
#include <cassert>
#include <cstring>
#define LOG POMME_GENLOG(POMME_DEBUG_SOUND, "SOUN")
#define LOG_NOPREFIX POMME_GENLOG_NOPREFIX(POMME_DEBUG_SOUND)
static struct ChannelImpl* headChan = nullptr;
static int nManagedChans = 0;
static double midiNoteFrequencies[128];
//-----------------------------------------------------------------------------
// Cookie-cutter sound command list.
// Used to generate 'snd ' resources.
static const uint8_t kSampledSoundCommandList[20] = {
0,1, // format
0,1, // modifier count
0,5, // modifier "sampled synth"
0,0,0,0, // init bits
0,1, // command count
0x80,soundCmd, // command soundCmd (high bit set)
0,0, // param1
0,0,0,20, // param2 (offset)
// Sample data follows
};
constexpr int kSampledSoundCommandListLength = sizeof(kSampledSoundCommandList);
//-----------------------------------------------------------------------------
// 'snd ' resource header
struct SampledSoundHeader
{
UInt32 zero;
union // the meaning of union this is decided by the encoding type
{
SInt32 stdSH_nBytes;
SInt32 cmpSH_nChannels;
SInt32 extSH_nChannels;
SInt32 nativeSH_nBytes;
};
Fixed fixedSampleRate;
UInt32 loopStart;
UInt32 loopEnd;
Byte encoding;
Byte baseFrequency; // 0-127, see Table 2-2, IM:S:2-43
unsigned int sampleRate() const
{
return (static_cast<unsigned int>(fixedSampleRate) >> 16) & 0xFFFF;
}
};
static_assert(sizeof(SampledSoundHeader) >= 22 && sizeof(SampledSoundHeader) <= 24,
"unexpected SampledSoundHeader size");
constexpr int kSampledSoundHeaderLength = 22;
constexpr const char* kSampledSoundHeaderPackFormat = "IiIIIbb";
enum SampledSoundEncoding
{
stdSH = 0x00,
nativeSH_mono16 = 0x10, // pomme extension
nativeSH_stereo16 = 0x11, // pomme extension
cmpSH = 0xFE,
extSH = 0xFF,
};
//-----------------------------------------------------------------------------
// Internal channel info
struct ChannelImpl
{
private:
ChannelImpl* prev;
ChannelImpl* next;
public:
SndChannelPtr macChannel;
bool macChannelStructAllocatedByPomme;
cmixer::WavStream source;
FilePlayCompletionProcPtr onComplete;
Byte baseNote = kMiddleC;
Byte playbackNote = kMiddleC;
double pitchMult = 1;
bool temporaryPause = false;
ChannelImpl(SndChannelPtr _macChannel, bool transferMacChannelOwnership)
: macChannel(_macChannel)
, macChannelStructAllocatedByPomme(transferMacChannelOwnership)
, source()
, onComplete(nullptr)
, baseNote(kMiddleC)
, playbackNote(kMiddleC)
, pitchMult(1.0)
{
macChannel->firstMod = (Ptr) this;
Link(); // Link chan into our list of managed chans
}
~ChannelImpl()
{
Unlink();
macChannel->firstMod = nullptr;
if (macChannelStructAllocatedByPomme)
{
delete macChannel;
}
}
void Recycle()
{
source.Clear();
baseNote = kMiddleC;
playbackNote = kMiddleC;
pitchMult = 1;
temporaryPause = false;
}
void ApplyPitch()
{
if (!source.active)
{
return;
}
double baseFreq = midiNoteFrequencies[baseNote];
double playbackFreq = midiNoteFrequencies[playbackNote];
source.SetPitch(pitchMult * playbackFreq / baseFreq);
}
ChannelImpl* GetPrev() const
{
return prev;
}
ChannelImpl* GetNext() const
{
return next;
}
void SetPrev(ChannelImpl* newPrev)
{
prev = newPrev;
}
void SetNext(ChannelImpl* newNext)
{
next = newNext;
macChannel->nextChan = newNext ? newNext->macChannel : nullptr;
}
void Link()
{
if (!headChan)
{
SetNext(nullptr);
}
else
{
assert(nullptr == headChan->GetPrev());
headChan->SetPrev(this);
SetNext(headChan);
}
headChan = this;
SetPrev(nullptr);
nManagedChans++;
}
void Unlink()
{
if (headChan == this)
{
headChan = GetNext();
}
if (nullptr != GetPrev())
{
GetPrev()->SetNext(GetNext());
}
if (nullptr != GetNext())
{
GetNext()->SetPrev(GetPrev());
}
SetPrev(nullptr);
SetNext(nullptr);
nManagedChans--;
}
};
//-----------------------------------------------------------------------------
// Internal utilities
static inline ChannelImpl& GetImpl(SndChannelPtr chan)
{
return *(ChannelImpl*) chan->firstMod;
}
//-----------------------------------------------------------------------------
// MIDI note utilities
// Note: these names are according to IM:S:2-43.
// These names won't match real-world names.
// E.g. for note 67 (A 440Hz), this will return "A6", whereas the real-world
// convention for that note is "A4".
static std::string GetMidiNoteName(int i)
{
static const char* gamme[12] = {"A", "A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#"};
int octave = 1 + (i + 3) / 12;
int semitonesFromA = (i + 3) % 12;
std::stringstream ss;
ss << gamme[semitonesFromA] << octave;
return ss.str();
}
static void InitMidiFrequencyTable()
{
// powers of twelfth root of two
double gamme[12];
gamme[0] = 1.0;
for (int i = 1; i < 12; i++)
{
gamme[i] = gamme[i - 1] * 1.059630943592952646;
}
for (int i = 0; i < 128; i++)
{
int octave = 1 + (i + 3) / 12; // A440 and middle C are in octave 7
int semitone = (i + 3) % 12; // halfsteps up from A in current octave
if (octave < 7)
midiNoteFrequencies[i] = gamme[semitone] * 440.0 / (1 << (7 - octave)); // 440/(2**octaveDiff)
else
midiNoteFrequencies[i] = gamme[semitone] * 440.0 * (1 << (octave - 7)); // 440*(2**octaveDiff)
//LOG << i << "\t" << GetMidiNoteName(i) << "\t" << midiNoteFrequencies[i] << "\n";
}
}
//-----------------------------------------------------------------------------
// Sound Manager
OSErr GetDefaultOutputVolume(long* stereoLevel)
{
unsigned short g = (unsigned short) (cmixer::GetMasterGain() * 256.0);
*stereoLevel = (g << 16) | g;
return noErr;
}
// See IM:S:2-139, "Controlling Volume Levels".
OSErr SetDefaultOutputVolume(long stereoLevel)
{
unsigned short left = 0xFFFF & stereoLevel;
unsigned short right = 0xFFFF & (stereoLevel >> 16);
if (right != left)
TODOMINOR2("setting different volumes for left & right is not implemented");
LOG << left / 256.0 << "\n";
cmixer::SetMasterGain(left / 256.0);
return noErr;
}
// IM:S:2-127
OSErr SndNewChannel(SndChannelPtr* macChanPtr, short synth, long init, SndCallBackProcPtr userRoutine)
{
if (synth != sampledSynth)
{
TODO2("unimplemented synth type " << sampledSynth);
return unimpErr;
}
//---------------------------
// Allocate Mac channel record if needed
bool transferMacChannelOwnership = false;
if (!*macChanPtr)
{
*macChanPtr = new SndChannel;
(**macChanPtr) = {};
transferMacChannelOwnership = true;
}
//---------------------------
// Set up
(**macChanPtr).callBack = userRoutine;
new ChannelImpl(*macChanPtr, transferMacChannelOwnership);
//---------------------------
// Done
LOG << "New channel created, init = $" << std::hex << init << std::dec << ", total managed channels = " << nManagedChans << "\n";
return noErr;
}
// IM:S:2-129
OSErr SndDisposeChannel(SndChannelPtr macChanPtr, Boolean quietNow)
{
if (!quietNow)
{
TODO2("SndDisposeChannel: quietNow == false is not implemented");
}
delete &GetImpl(macChanPtr);
return noErr;
}
OSErr SndChannelStatus(SndChannelPtr chan, short theLength, SCStatusPtr theStatus)
{
*theStatus = {};
auto& source = GetImpl(chan).source;
theStatus->scChannelPaused = source.GetState() == cmixer::CM_STATE_PAUSED;
theStatus->scChannelBusy = source.GetState() == cmixer::CM_STATE_PLAYING;
return noErr;
}
static void ProcessSoundCmd(SndChannelPtr chan, const Ptr sndhdr)
{
// Install a sampled sound as a voice in a channel. If the high bit of the
// command is set, param2 is interpreted as an offset from the beginning of
// the 'snd ' resource containing the command to the sound header. If the
// high bit is not set, param2 is interpreted as a pointer to the sound
// header.
auto& impl = GetImpl(chan);
impl.Recycle();
// PACKED RECORD
memstream headerInput(sndhdr, kSampledSoundHeaderLength + 42);
Pomme::BigEndianIStream f(headerInput);
SampledSoundHeader sh;
f.Read(reinterpret_cast<char*>(&sh), kSampledSoundHeaderLength);
ByteswapStructs(kSampledSoundHeaderPackFormat, kSampledSoundHeaderLength, 1, reinterpret_cast<char*>(&sh));
if (sh.zero != 0)
{
// The first field can be a pointer to the sampled-sound data.
// In practice it's always gonna be 0.
TODOFATAL2("expected 0 at the beginning of an snd");
}
int sampleRate = sh.sampleRate();
impl.baseNote = sh.baseFrequency;
LOG << sampleRate << "Hz, " << GetMidiNoteName(sh.baseFrequency) << ", loop " << sh.loopStart << "->" << sh.loopEnd << ", ";
switch (sh.encoding)
{
case 0x00: // stdSH - standard sound header - IM:S:2-104
{
// noncompressed sample data (8-bit mono) from this point on
char* here = sndhdr + f.Tell();
impl.source.Init(sampleRate, 8, 1, false, std::span<char>(here, sh.stdSH_nBytes));
LOG_NOPREFIX << "stdSH: 8-bit mono, " << sh.stdSH_nBytes << " frames\n";
break;
}
case nativeSH_mono16:
case nativeSH_stereo16:
{
int nChannels = sh.encoding == nativeSH_mono16 ? 1 : 2;
char* here = sndhdr + f.Tell();
auto span = std::span(here, sh.nativeSH_nBytes);
impl.source.Init(sampleRate, 16, nChannels, false, span);
LOG_NOPREFIX << "nativeSH\n";
break;
}
case 0xFF: // extSH - extended sound header - IM:S:2-106
{
// fields that follow baseFrequency
SInt32 nFrames = f.Read<SInt32>();
f.Skip(22);
SInt16 bitDepth = f.Read<SInt16>();
f.Skip(14);
int nBytes = sh.extSH_nChannels * nFrames * bitDepth / 8;
// noncompressed sample data (big endian) from this point on
char* here = sndhdr + f.Tell();
LOG_NOPREFIX << "extSH: " << bitDepth << "-bit " << (sh.extSH_nChannels == 1? "mono": "stereo") << ", " << nFrames << " frames\n";
impl.source.Init(sampleRate, bitDepth, sh.extSH_nChannels, true, std::span<char>(here, nBytes));
break;
}
case 0xFE: // cmpSH - compressed sound header - IM:S:2-108
{
// fields that follow baseFrequency
SInt32 nCompressedChunks = f.Read<SInt32>();
f.Skip(14);
OSType format = f.Read<OSType>();
f.Skip(20);
if (format == 0)
{
// Assume MACE-3. It should've been set in the init options in the snd pre-header,
// but Nanosaur doesn't actually init the sound channels for MACE-3. So I guess the Mac
// assumes by default that any unspecified compression is MACE-3.
// If it wasn't MACE-3, it would've been caught by GetSoundHeaderOffset.
format = 'MAC3';
}
// compressed sample data from this point on
char* here = sndhdr + f.Tell();
std::cout << "cmpSH: " << Pomme::FourCCString(format) << " " << (sh.cmpSH_nChannels == 1 ? "mono" : "stereo") << ", " << nCompressedChunks << " ck\n";
std::unique_ptr<Pomme::Sound::Codec> codec = Pomme::Sound::GetCodec(format);
// Decompress
int nBytesIn = sh.cmpSH_nChannels * nCompressedChunks * codec->BytesPerPacket();
int nBytesOut = sh.cmpSH_nChannels * nCompressedChunks * codec->SamplesPerPacket() * 2;
auto spanIn = std::span(here, nBytesIn);
auto spanOut = impl.source.GetBuffer(nBytesOut);
codec->Decode(sh.cmpSH_nChannels, spanIn, spanOut);
impl.source.Init(sampleRate, 16, sh.cmpSH_nChannels, false, spanOut);
break;
}
default:
TODOFATAL2("unsupported snd header encoding " << (int)sh.encoding);
}
if (sh.loopEnd - sh.loopStart <= 1)
{
// don't loop
}
else if (sh.loopStart == 0)
{
impl.source.SetLoop(true);
}
else
{
TODO2("looping on a portion of the snd isn't supported yet");
impl.source.SetLoop(true);
}
impl.ApplyPitch();
impl.temporaryPause = false;
impl.source.Play();
}
OSErr SndDoImmediate(SndChannelPtr chan, const SndCommand* cmd)
{
auto& impl = GetImpl(chan);
// Discard the high bit of the command (it indicates whether an 'snd ' resource has associated data).
switch (cmd->cmd & 0x7FFF)
{
case nullCmd:
break;
case flushCmd:
// flushCmd is a no-op for now because we don't support queuing commands--
// all commands are executed immediately in the current implementation.
break;
case quietCmd:
impl.source.Stop();
break;
case soundCmd:
ProcessSoundCmd(chan, cmd->ptr);
break;
case ampCmd:
impl.source.SetGain(cmd->param1 / 255.0);
LOG << "ampCmd " << impl.source.gain << "\n";
break;
case freqCmd:
LOG << "freqCmd " << cmd->param2 << " " << GetMidiNoteName(cmd->param2) << " " << midiNoteFrequencies[cmd->param2] << "\n";
impl.playbackNote = Byte(cmd->param2);
impl.ApplyPitch();
break;
case rateCmd:
// IM:S says it's a fixed-point multiplier of 22KHz, but Nanosaur uses rate "1" everywhere,
// even for sounds sampled at 44Khz, so I'm treating it as just a pitch multiplier.
impl.pitchMult = cmd->param2 / 65536.0;
impl.ApplyPitch();
break;
case pommeSetLoopCmd:
impl.source.SetLoop(cmd->param1);
break;
default:
TODOMINOR2(cmd->cmd << "(" << cmd->param1 << "," << cmd->param2 << ")");
}
return noErr;
}
template<typename T>
static void Expect(const T a, const T b, const char* msg)
{
if (a != b)
throw std::runtime_error(msg);
}
// IM:S:2-58 "MyGetSoundHeaderOffset"
OSErr GetSoundHeaderOffset(SndListHandle sndHandle, long* offset)
{
memstream sndStream((Ptr) *sndHandle, GetHandleSize((Handle) sndHandle));
Pomme::BigEndianIStream f(sndStream);
// Skip everything before sound commands
Expect<SInt16>(1, f.Read<SInt16>(), "'snd ' format");
Expect<SInt16>(1, f.Read<SInt16>(), "'snd ' modifier count");
Expect<SInt16>(5, f.Read<SInt16>(), "'snd ' sampledSynth");
UInt32 initBits = f.Read<UInt32>();
if (initBits & initMACE6)
TODOFATAL2("MACE-6 not supported yet");
SInt16 nCmds = f.Read<SInt16>();
//LOG << nCmds << " commands\n";
for (; nCmds >= 1; nCmds--)
{
UInt16 cmd = f.Read<UInt16>();
f.Skip(2); // SInt16 param1
SInt32 param2 = f.Read<SInt32>();
cmd &= 0x7FFF; // See IM:S:2-75
// When a sound command contained in an 'snd ' resource has associated sound data,
// the high bit of the command is set. This changes the meaning of the param2 field of the
// command from a pointer to a location in RAM to an offset value that specifies the offset
// in bytes from the resource's beginning to the location of the associated sound data (such
// as a sampled sound header).
if (cmd == bufferCmd || cmd == soundCmd)
{
*offset = param2;
return noErr;
}
}
LOG << "didn't find offset in snd resource\n";
return badFormat;
}
OSErr SndStartFilePlay(
SndChannelPtr chan,
short fRefNum,
short resNum,
long bufferSize,
Ptr theBuffer,
/*AudioSelectionPtr*/ void* theSelection,
FilePlayCompletionUPP theCompletion,
Boolean async)
{
if (resNum != 0)
{
TODO2("playing snd resource not implemented yet, resource " << resNum);
return unimpErr;
}
if (!chan)
{
if (async) // async requires passing in a channel
return badChannel;
TODO2("nullptr chan for sync play, check IM:S:1-37");
return unimpErr;
}
if (theSelection)
{
TODO2("audio selection record not implemented");
return unimpErr;
}
auto& impl = GetImpl(chan);
impl.Recycle();
auto& stream = Pomme::Files::GetStream(fRefNum);
// Rewind -- the file might've been fully played already and we might just be trying to loop it
stream.seekg(0, std::ios::beg);
Pomme::Sound::ReadAIFF(stream, impl.source);
if (theCompletion)
{
impl.source.onComplete = [=]() { theCompletion(chan); };
}
impl.temporaryPause = false;
impl.source.Play();
if (!async)
{
while (impl.source.GetState() != cmixer::CM_STATE_STOPPED)
{
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
theCompletion(chan);
impl.Recycle();
return noErr;
}
return noErr;
}
OSErr SndPauseFilePlay(SndChannelPtr chan)
{
// TODO: check that chan is being used for play from disk
GetImpl(chan).source.TogglePause();
return noErr;
}
OSErr SndStopFilePlay(SndChannelPtr chan, Boolean quietNow)
{
// TODO: check that chan is being used for play from disk
if (!quietNow)
TODO2("quietNow==false not supported yet, sound will be cut off immediately instead");
GetImpl(chan).source.Stop();
return noErr;
}
NumVersion SndSoundManagerVersion()
{
NumVersion v = {};
v.majorRev = 3;
v.minorAndBugRev = 9;
v.stage = 0x80;
v.nonRelRev = 0;
return v;
}
//-----------------------------------------------------------------------------
// Extension: decompress
Boolean Pomme_DecompressSoundResource(SndListHandle* sndHandlePtr, long* offsetToHeader)
{
// Prep the BE reader on the header.
Ptr sndhdr = (Ptr) (**sndHandlePtr) + *offsetToHeader;
memstream headerInput(sndhdr, kSampledSoundHeaderLength + 42);
Pomme::BigEndianIStream f(headerInput);
// Read in SampledSoundHeader and unpack it.
SampledSoundHeader sh;
f.Read(reinterpret_cast<char*>(&sh), kSampledSoundHeaderLength);
ByteswapStructs(kSampledSoundHeaderPackFormat, kSampledSoundHeaderLength, 1, reinterpret_cast<char*>(&sh));
// We only handle cmpSH (compressed) 'snd ' resources.
if (sh.encoding != cmpSH)
{
return false;
}
// Fields that follow SampledSoundHeader when the encoding is cmpSH.
const auto nCompressedChunks = f.Read<SInt32>();
f.Skip(14);
const auto format = f.Read<OSType>();
f.Skip(20);
// Compressed sample data in the input stream from this point on.
const char* here = sndhdr + f.Tell();
int outInitialSize = kSampledSoundCommandListLength + kSampledSoundHeaderLength;
std::unique_ptr<Pomme::Sound::Codec> codec = Pomme::Sound::GetCodec(format);
// Decompress
const int nBytesIn = sh.cmpSH_nChannels * nCompressedChunks * codec->BytesPerPacket();
const int nBytesOut = sh.cmpSH_nChannels * nCompressedChunks * codec->SamplesPerPacket() * 2;
SndListHandle outHandle = (SndListHandle) NewHandle(outInitialSize + nBytesOut);
auto spanIn = std::span(here, nBytesIn);
auto spanOut = std::span((char*) *outHandle + outInitialSize, nBytesOut);
codec->Decode(sh.cmpSH_nChannels, spanIn, spanOut);
// ------------------------------------------------------
// Now we have the PCM data.
// Put the output 'snd ' resource together.
SampledSoundHeader shOut = sh;
shOut.zero = 0;
shOut.encoding = sh.cmpSH_nChannels == 2 ? nativeSH_stereo16 : nativeSH_mono16;
shOut.nativeSH_nBytes = nBytesOut;
ByteswapStructs(kSampledSoundHeaderPackFormat, kSampledSoundHeaderLength, 1, reinterpret_cast<char*>(&shOut));
memcpy(*outHandle, kSampledSoundCommandList, kSampledSoundCommandListLength);
memcpy((char*) *outHandle + kSampledSoundCommandListLength, &shOut, kSampledSoundHeaderLength);
// Nuke compressed sound handle, replace it with the decopmressed one we've just created
DisposeHandle((Handle) *sndHandlePtr);
*sndHandlePtr = outHandle;
*offsetToHeader = kSampledSoundCommandListLength;
long offsetCheck = 0;
OSErr err = GetSoundHeaderOffset(outHandle, &offsetCheck);
if (err != noErr || offsetCheck != kSampledSoundCommandListLength)
{
throw std::runtime_error("Incorrect decompressed sound header offset");
}
return true;
}
//-----------------------------------------------------------------------------
// Extension: pause/unpause looping channels
void Pomme_PauseLoopingChannels(Boolean pause)
{
for (auto* chan = headChan; chan; chan = chan->GetNext())
{
auto& source = chan->source;
if (pause && source.state == cmixer::CM_STATE_PLAYING && !chan->temporaryPause)
{
source.Pause();
chan->temporaryPause = true;
}
else if (!pause && source.state == cmixer::CM_STATE_PAUSED && chan->temporaryPause)
{
source.Play();
chan->temporaryPause = false;
}
}
}
//-----------------------------------------------------------------------------
// Init Sound Manager
void Pomme::Sound::Init()
{
InitMidiFrequencyTable();
cmixer::InitWithSDL();
}
void Pomme::Sound::Shutdown()
{
cmixer::ShutdownWithSDL();
while (headChan)
{
SndDisposeChannel(headChan->macChannel, true);
}
}
std::unique_ptr<Pomme::Sound::Codec> Pomme::Sound::GetCodec(uint32_t fourCC)
{
switch (fourCC)
{
case 0: // Assume MACE-3 by default.
case 'MAC3':
return std::make_unique<Pomme::Sound::MACE>();
case 'ima4':
return std::make_unique<Pomme::Sound::IMA4>();
case 'alaw':
case 'ulaw':
return std::make_unique<Pomme::Sound::xlaw>(fourCC);
default:
throw std::runtime_error("Unknown audio codec: " + Pomme::FourCCString(fourCC));
}
}

649
src/Sound/cmixer.cpp Normal file
View File

@ -0,0 +1,649 @@
// Adapted from cmixer by rxi (https://github.com/rxi/cmixer)
/*
** Copyright (c) 2017 rxi
**
** Permission is hereby granted, free of charge, to any person obtaining a copy
** of this software and associated documentation files (the "Software"), to
** deal in the Software without restriction, including without limitation the
** rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
** sell copies of the Software, and to permit persons to whom the Software is
** furnished to do so, subject to the following conditions:
**
** The above copyright notice and this permission notice shall be included in
** all copies or substantial portions of the Software.
**
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
** FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
** IN THE SOFTWARE.
**/
#include "Sound/cmixer.h"
#include "Utilities/structpack.h"
#include <SDL.h>
#include <vector>
#include <fstream>
#include <list>
using namespace cmixer;
#define CLAMP(x, a, b) ((x) < (a) ? (a) : (x) > (b) ? (b) : (x))
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define FX_BITS (12)
#define FX_UNIT (1 << FX_BITS)
#define FX_MASK (FX_UNIT - 1)
#define FX_FROM_FLOAT(f) ((long)((f) * FX_UNIT))
#define DOUBLE_FROM_FX(f) ((double)f / FX_UNIT)
#define FX_LERP(a, b, p) ((a) + ((((b) - (a)) * (p)) >> FX_BITS))
#define BUFFER_MASK (BUFFER_SIZE - 1)
static constexpr bool INTERPOLATED_RESAMPLING = false;
//-----------------------------------------------------------------------------
// Global mixer
static struct Mixer
{
SDL_mutex* sdlAudioMutex;
std::list<Source*> sources; // Linked list of active (playing) sources
int32_t pcmmixbuf[BUFFER_SIZE]; // Internal master buffer
int samplerate; // Master samplerate
int gain; // Master gain (fixed point)
void Init(int samplerate);
void Process(int16_t* dst, int len);
void Lock();
void Unlock();
void SetMasterGain(double newGain);
} gMixer = {};
//-----------------------------------------------------------------------------
// Global init/shutdown
static bool sdlAudioSubSystemInited = false;
static SDL_AudioDeviceID sdlDeviceID = 0;
void cmixer::InitWithSDL()
{
if (sdlAudioSubSystemInited)
throw std::runtime_error("SDL audio subsystem already inited");
if (0 != SDL_InitSubSystem(SDL_INIT_AUDIO))
throw std::runtime_error("couldn't init SDL audio subsystem");
sdlAudioSubSystemInited = true;
// Init SDL audio
SDL_AudioSpec fmt = {};
fmt.freq = 44100;
fmt.format = AUDIO_S16;
fmt.channels = 2;
fmt.samples = 1024;
fmt.callback = [](void* udata, Uint8* stream, int size)
{
gMixer.Process((int16_t*) stream, size / 2);
};
SDL_AudioSpec got;
sdlDeviceID = SDL_OpenAudioDevice(NULL, 0, &fmt, &got, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE);
if (!sdlDeviceID)
throw std::runtime_error(SDL_GetError());
// Init library
gMixer.Init(got.freq);
gMixer.SetMasterGain(0.5);
// Start audio
SDL_PauseAudioDevice(sdlDeviceID, 0);
}
void cmixer::ShutdownWithSDL()
{
if (sdlDeviceID)
{
SDL_CloseAudioDevice(sdlDeviceID);
sdlDeviceID = 0;
}
if (gMixer.sdlAudioMutex)
{
SDL_DestroyMutex(gMixer.sdlAudioMutex);
gMixer.sdlAudioMutex = nullptr;
}
if (sdlAudioSubSystemInited)
{
SDL_QuitSubSystem(SDL_INIT_AUDIO);
sdlAudioSubSystemInited = false;
}
}
double cmixer::GetMasterGain()
{
return DOUBLE_FROM_FX(gMixer.gain);
}
void cmixer::SetMasterGain(double newGain)
{
gMixer.SetMasterGain(newGain);
}
//-----------------------------------------------------------------------------
// Global mixer impl
void Mixer::Lock()
{
SDL_LockMutex(sdlAudioMutex);
}
void Mixer::Unlock()
{
SDL_UnlockMutex(sdlAudioMutex);
}
void Mixer::Init(int newSamplerate)
{
sdlAudioMutex = SDL_CreateMutex();
samplerate = newSamplerate;
gain = FX_UNIT;
}
void Mixer::SetMasterGain(double newGain)
{
if (newGain < 0)
newGain = 0;
gain = FX_FROM_FLOAT(newGain);
}
void Mixer::Process(int16_t* dst, int len)
{
// Process in chunks of BUFFER_SIZE if `len` is larger than BUFFER_SIZE
while (len > BUFFER_SIZE)
{
Process(dst, BUFFER_SIZE);
dst += BUFFER_SIZE;
len -= BUFFER_SIZE;
}
// Zeroset internal buffer
memset(pcmmixbuf, 0, len * sizeof(pcmmixbuf[0]));
// Process active sources
Lock();
for (auto si = sources.begin(); si != sources.end();)
{
auto& s = **si;
s.Process(len);
// Remove source from list if it is no longer playing
if (s.state != CM_STATE_PLAYING)
{
s.active = false;
si = sources.erase(si);
}
else
{
++si;
}
}
Unlock();
// Copy internal buffer to destination and clip
for (int i = 0; i < len; i++)
{
int x = (pcmmixbuf[i] * gain) >> FX_BITS;
dst[i] = CLAMP(x, -32768, 32767);
}
}
//-----------------------------------------------------------------------------
// Source implementation
Source::Source()
{
active = false;
Clear();
}
void Source::Clear()
{
samplerate = 0;
length = 0;
end = 0;
state = CM_STATE_STOPPED;
position = 0;
lgain = 0;
rgain = 0;
rate = 0;
nextfill = 0;
loop = false;
rewind = true;
// DON'T touch active. The source may still be in gMixer!
gain = 0;
pan = 0;
onComplete = nullptr;
}
void Source::Init(int theSampleRate, int theLength)
{
this->samplerate = theSampleRate;
this->length = theLength;
SetGain(1);
SetPan(0);
SetPitch(1);
SetLoop(false);
Stop();
}
Source::~Source()
{
gMixer.Lock();
if (active)
{
gMixer.sources.remove(this);
}
gMixer.Unlock();
}
void Source::Rewind()
{
Rewind2();
position = 0;
rewind = false;
end = length;
nextfill = 0;
}
void Source::FillBuffer(int offset, int fillLength)
{
FillBuffer(pcmbuf + offset, fillLength);
}
void Source::Process(int len)
{
int32_t* dst = gMixer.pcmmixbuf;
// Do rewind if flag is set
if (rewind)
{
Rewind();
}
// Don't process if not playing
if (state != CM_STATE_PLAYING)
{
return;
}
// Process audio
while (len > 0)
{
// Get current position frame
int frame = int(position >> FX_BITS);
// Fill buffer if required
if (frame + 3 >= nextfill)
{
FillBuffer((nextfill * 2) & BUFFER_MASK, BUFFER_SIZE / 2);
nextfill += BUFFER_SIZE / 4;
}
// Handle reaching the end of the playthrough
if (frame >= end)
{
// As streams continiously fill the raw buffer in a loop we simply
// increment the end idx by one length and continue reading from it for
// another play-through
end = frame + this->length;
// Set state and stop processing if we're not set to loop
if (!loop)
{
state = CM_STATE_STOPPED;
if (onComplete != nullptr)
onComplete();
break;
}
}
// Work out how many frames we should process in the loop
int n = MIN(nextfill - 2, end) - frame;
int count = (n << FX_BITS) / rate;
count = MAX(count, 1);
count = MIN(count, len / 2);
len -= count * 2;
// Add audio to master buffer
if (rate == FX_UNIT)
{
// Add audio to buffer -- basic
n = frame * 2;
for (int i = 0; i < count; i++)
{
dst[0] += (pcmbuf[(n ) & BUFFER_MASK] * lgain) >> FX_BITS;
dst[1] += (pcmbuf[(n + 1) & BUFFER_MASK] * rgain) >> FX_BITS;
n += 2;
dst += 2;
}
this->position += count * FX_UNIT;
}
else if (INTERPOLATED_RESAMPLING)
{
// Resample audio (with linear interpolation) and add to buffer
for (int i = 0; i < count; i++)
{
n = int(position >> FX_BITS) * 2;
int p = position & FX_MASK;
int a = pcmbuf[(n ) & BUFFER_MASK];
int b = pcmbuf[(n + 2) & BUFFER_MASK];
dst[0] += (FX_LERP(a, b, p) * lgain) >> FX_BITS;
n++;
a = pcmbuf[(n ) & BUFFER_MASK];
b = pcmbuf[(n + 2) & BUFFER_MASK];
dst[1] += (FX_LERP(a, b, p) * rgain) >> FX_BITS;
position += rate;
dst += 2;
}
}
else
{
// Resample audio (without interpolation) and add to buffer
for (int i = 0; i < count; i++)
{
n = int(position >> FX_BITS) * 2;
dst[0] += (pcmbuf[(n ) & BUFFER_MASK] * lgain) >> FX_BITS;
dst[1] += (pcmbuf[(n + 1) & BUFFER_MASK] * rgain) >> FX_BITS;
position += rate;
dst += 2;
}
}
}
}
double Source::GetLength() const
{
return length / (double) samplerate;
}
double Source::GetPosition() const
{
return ((position >> FX_BITS) % length) / (double) samplerate;
}
int Source::GetState() const
{
return state;
}
void Source::RecalcGains()
{
double l = this->gain * (pan <= 0. ? 1. : 1. - pan);
double r = this->gain * (pan >= 0. ? 1. : 1. + pan);
this->lgain = FX_FROM_FLOAT(l);
this->rgain = FX_FROM_FLOAT(r);
}
void Source::SetGain(double newGain)
{
gain = newGain;
RecalcGains();
}
void Source::SetPan(double newPan)
{
pan = CLAMP(newPan, -1.0, 1.0);
RecalcGains();
}
void Source::SetPitch(double newPitch)
{
double newRate;
if (newPitch > 0.)
{
newRate = samplerate / (double) gMixer.samplerate * newPitch;
}
else
{
newRate = 0.001;
}
rate = FX_FROM_FLOAT(newRate);
}
void Source::SetLoop(bool newLoop)
{
loop = newLoop;
}
void Source::Play()
{
gMixer.Lock();
state = CM_STATE_PLAYING;
if (!active)
{
active = true;
gMixer.sources.push_front(this);
}
gMixer.Unlock();
}
void Source::Pause()
{
state = CM_STATE_PAUSED;
}
void Source::TogglePause()
{
if (state == CM_STATE_PAUSED)
Play();
else if (state == CM_STATE_PLAYING)
Pause();
}
void Source::Stop()
{
state = CM_STATE_STOPPED;
rewind = true;
}
//-----------------------------------------------------------------------------
// WavStream implementation
#define WAV_PROCESS_LOOP(X) \
while (n--) \
{ \
X \
dst += 2; \
idx++; \
}
WavStream::WavStream()
: Source()
, bitdepth(0)
, channels(0)
, bigEndian(false)
, idx(0)
, userBuffer()
{}
void WavStream::Clear()
{
Source::Clear();
bitdepth = 0;
channels = 0;
bigEndian = false;
idx = 0;
userBuffer.clear();
}
void WavStream::Init(
int theSampleRate,
int theBitDepth,
int theNChannels,
bool theBigEndian,
std::span<char> theSpan)
{
Clear();
Source::Init(theSampleRate, int((theSpan.size() / (theBitDepth / 8)) / theNChannels));
this->bitdepth = theBitDepth;
this->channels = theNChannels;
this->idx = 0;
this->span = theSpan;
this->bigEndian = theBigEndian;
}
std::span<char> WavStream::GetBuffer(int nBytesOut)
{
userBuffer.clear();
userBuffer.reserve(nBytesOut);
return std::span(userBuffer.data(), nBytesOut);
}
std::span<char> WavStream::SetBuffer(std::vector<char>&& data)
{
userBuffer = std::move(data);
return std::span(userBuffer.data(), userBuffer.size());
}
void WavStream::Rewind2()
{
idx = 0;
}
void WavStream::FillBuffer(int16_t* dst, int len)
{
int x, n;
len /= 2;
while (len > 0)
{
n = MIN(len, length - idx);
len -= n;
if (bigEndian && bitdepth == 16 && channels == 1)
{
WAV_PROCESS_LOOP({
dst[0] = dst[1] = ByteswapScalar(data16()[idx]);
});
}
else if (bigEndian && bitdepth == 16 && channels == 2)
{
WAV_PROCESS_LOOP({
x = idx * 2;
dst[0] = ByteswapScalar(data16()[x]);
dst[1] = ByteswapScalar(data16()[x + 1]);
});
}
else if (bitdepth == 16 && channels == 1)
{
WAV_PROCESS_LOOP({
dst[0] = dst[1] = data16()[idx];
});
}
else if (bitdepth == 16 && channels == 2)
{
WAV_PROCESS_LOOP({
x = idx * 2;
dst[0] = data16()[x];
dst[1] = data16()[x + 1];
});
}
else if (bitdepth == 8 && channels == 1)
{
WAV_PROCESS_LOOP({
dst[0] = dst[1] = (data8()[idx] - 128) << 8;
});
}
else if (bitdepth == 8 && channels == 2)
{
WAV_PROCESS_LOOP({
x = idx * 2;
dst[0] = (data8()[x] - 128) << 8;
dst[1] = (data8()[x + 1] - 128) << 8;
});
}
// Loop back and continue filling buffer if we didn't fill the buffer
if (len > 0)
{
idx = 0;
}
}
}
#if 0
//-----------------------------------------------------------------------------
// LoadWAVFromFile for testing
static std::vector<char> LoadFile(char const* filename)
{
std::ifstream ifs(filename, std::ios::binary | std::ios::ate);
auto pos = ifs.tellg();
std::vector<char> bytes(pos);
ifs.seekg(0, std::ios::beg);
ifs.read(&bytes[0], pos);
return bytes;
}
static const char* FindChunk(const char* data, int len, const char* id, int* size)
{
// TODO : Error handling on malformed wav file
int idlen = strlen(id);
const char* p = data + 12;
next:
*size = *((cm_UInt32*)(p + 4));
if (memcmp(p, id, idlen)) {
p += 8 + *size;
if (p > data + len) return NULL;
goto next;
}
return p + 8;
}
WavStream cmixer::LoadWAVFromFile(const char* path)
{
int sz;
auto filebuf = LoadFile(path);
auto len = filebuf.size();
const char* data = filebuf.data();
const char* p = (char*)data;
// Check header
if (memcmp(p, "RIFF", 4) || memcmp(p + 8, "WAVE", 4))
throw std::invalid_argument("bad wav header");
// Find fmt subchunk
p = FindChunk(data, len, "fmt ", &sz);
if (!p)
throw std::invalid_argument("no fmt subchunk");
// Load fmt info
int format = *((cm_UInt16*)(p));
int channels = *((cm_UInt16*)(p + 2));
int samplerate = *((cm_UInt32*)(p + 4));
int bitdepth = *((cm_UInt16*)(p + 14));
if (format != 1)
throw std::invalid_argument("unsupported format");
if (channels == 0 || samplerate == 0 || bitdepth == 0)
throw std::invalid_argument("bad format");
// Find data subchunk
p = FindChunk(data, len, "data", &sz);
if (!p)
throw std::invalid_argument("no data subchunk");
return WavStream(
samplerate,
bitdepth,
channels,
std::vector<char>(p, p + sz));
}
#endif

155
src/Sound/cmixer.h Normal file
View File

@ -0,0 +1,155 @@
// Adapted from cmixer by rxi (https://github.com/rxi/cmixer)
/*
** Copyright (c) 2017 rxi
**
** Permission is hereby granted, free of charge, to any person obtaining a copy
** of this software and associated documentation files (the "Software"), to
** deal in the Software without restriction, including without limitation the
** rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
** sell copies of the Software, and to permit persons to whom the Software is
** furnished to do so, subject to the following conditions:
**
** The above copyright notice and this permission notice shall be included in
** all copies or substantial portions of the Software.
**
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
** FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
** IN THE SOFTWARE.
**/
#pragma once
#include <vector>
#include <functional>
#include <cstdint>
#include "CompilerSupport/span.h"
#define BUFFER_SIZE (512)
namespace cmixer
{
enum
{
CM_STATE_STOPPED,
CM_STATE_PLAYING,
CM_STATE_PAUSED
};
struct Source
{
int16_t pcmbuf[BUFFER_SIZE]; // Internal buffer with raw stereo PCM
int samplerate; // Stream's native samplerate
int length; // Stream's length in frames
int end; // End index for the current play-through
int state; // Current state (playing|paused|stopped)
int64_t position; // Current playhead position (fixed point)
int lgain, rgain; // Left and right gain (fixed point)
int rate; // Playback rate (fixed point)
int nextfill; // Next frame idx where the buffer needs to be filled
bool loop; // Whether the source will loop when `end` is reached
bool rewind; // Whether the source will rewind before playing
bool active; // Whether the source is part of `sources` list
double gain; // Gain set by `cm_set_gain()`
double pan; // Pan set by `cm_set_pan()`
std::function<void()> onComplete; // Callback
protected:
Source();
void Init(int samplerate, int length);
virtual void Rewind2() = 0;
virtual void FillBuffer(int16_t* buffer, int length) = 0;
public:
virtual ~Source();
virtual void Clear();
void Rewind();
void RecalcGains();
void FillBuffer(int offset, int length);
void Process(int len);
double GetLength() const;
double GetPosition() const;
int GetState() const;
void SetGain(double gain);
void SetPan(double pan);
void SetPitch(double pitch);
void SetLoop(bool loop);
void Play();
void Pause();
void TogglePause();
void Stop();
};
class WavStream : public Source
{
int bitdepth;
int channels;
bool bigEndian;
int idx;
std::span<char> span;
std::vector<char> userBuffer;
void Rewind2() override;
void FillBuffer(int16_t* buffer, int length) override;
inline uint8_t* data8() const
{ return reinterpret_cast<uint8_t*>(span.data()); }
inline int16_t* data16() const
{ return reinterpret_cast<int16_t*>(span.data()); }
public:
WavStream();
virtual void Clear() override;
void Init(
int theSampleRate,
int theBitDepth,
int nChannels,
bool bigEndian,
std::span<char> data
);
std::span<char> GetBuffer(int nBytesOut);
std::span<char> SetBuffer(std::vector<char>&& data);
};
void InitWithSDL();
void ShutdownWithSDL();
double GetMasterGain();
void SetMasterGain(double);
WavStream LoadWAVFromFile(const char* path);
}

91
src/Sound/xlaw.cpp Normal file
View File

@ -0,0 +1,91 @@
#include "PommeSound.h"
// Conversion tables to obtain 16-bit PCM from 8-bit a-law/mu-law.
// These tables are valid for *-law input bytes [0...127].
// For inputs [-127...-1], mirror the table and negate the output.
//
// These tables were generated using alaw2linear() and ulaw2linear()
// from ffmpeg/libavcodec/pcm_tablegen.h.
static const int16_t alawToPCM[128] = {
-5504, -5248, -6016, -5760, -4480, -4224, -4992, -4736,
-7552, -7296, -8064, -7808, -6528, -6272, -7040, -6784,
-2752, -2624, -3008, -2880, -2240, -2112, -2496, -2368,
-3776, -3648, -4032, -3904, -3264, -3136, -3520, -3392,
-22016, -20992, -24064, -23040, -17920, -16896, -19968, -18944,
-30208, -29184, -32256, -31232, -26112, -25088, -28160, -27136,
-11008, -10496, -12032, -11520, -8960, -8448, -9984, -9472,
-15104, -14592, -16128, -15616, -13056, -12544, -14080, -13568,
-344, -328, -376, -360, -280, -264, -312, -296,
-472, -456, -504, -488, -408, -392, -440, -424,
-88, -72, -120, -104, -24, -8, -56, -40,
-216, -200, -248, -232, -152, -136, -184, -168,
-1376, -1312, -1504, -1440, -1120, -1056, -1248, -1184,
-1888, -1824, -2016, -1952, -1632, -1568, -1760, -1696,
-688, -656, -752, -720, -560, -528, -624, -592,
-944, -912, -1008, -976, -816, -784, -880, -848,
};
static const int16_t ulawToPCM[128] = {
-32124, -31100, -30076, -29052, -28028, -27004, -25980, -24956,
-23932, -22908, -21884, -20860, -19836, -18812, -17788, -16764,
-15996, -15484, -14972, -14460, -13948, -13436, -12924, -12412,
-11900, -11388, -10876, -10364, -9852, -9340, -8828, -8316,
-7932, -7676, -7420, -7164, -6908, -6652, -6396, -6140,
-5884, -5628, -5372, -5116, -4860, -4604, -4348, -4092,
-3900, -3772, -3644, -3516, -3388, -3260, -3132, -3004,
-2876, -2748, -2620, -2492, -2364, -2236, -2108, -1980,
-1884, -1820, -1756, -1692, -1628, -1564, -1500, -1436,
-1372, -1308, -1244, -1180, -1116, -1052, -988, -924,
-876, -844, -812, -780, -748, -716, -684, -652,
-620, -588, -556, -524, -492, -460, -428, -396,
-372, -356, -340, -324, -308, -292, -276, -260,
-244, -228, -212, -196, -180, -164, -148, -132,
-120, -112, -104, -96, -88, -80, -72, -64,
-56, -48, -40, -32, -24, -16, -8, -0,
};
Pomme::Sound::xlaw::xlaw(uint32_t codecFourCC)
{
switch (codecFourCC)
{
case 'ulaw':
xlawToPCM = ulawToPCM;
break;
case 'alaw':
xlawToPCM = alawToPCM;
break;
default:
throw std::runtime_error("unknown xlaw fourCC");
}
}
void Pomme::Sound::xlaw::Decode(
const int nChannels,
const std::span<const char> input,
const std::span<char> output)
{
if (2 * input.size() != output.size())
{
throw std::runtime_error("ulaw: incorrect input/output buffer sizes");
}
const std::span<int16_t> out16(
reinterpret_cast<int16_t*>(output.data()),
input.size());
for (size_t i = 0; i < input.size(); i++)
{
int8_t b = input[i]; // SIGNED!
if (b < 0)
{
// Mirror table and negate output
out16[i] = -xlawToPCM[128 + b];
}
else
{
out16[i] = xlawToPCM[b];
}
}
}

42
src/Time/TimeManager.cpp Normal file
View File

@ -0,0 +1,42 @@
#include "Pomme.h"
#include "PommeTime.h"
#include "PommeTypes.h"
#include "PommeDebug.h"
#include <chrono>
#include <iostream>
#include <ctime>
#include <iomanip>
std::chrono::time_point<std::chrono::high_resolution_clock> bootTP;
// timestamp (from unix epoch) of the mac epoch, Jan 1, 1904, 00:00:00
constexpr int JANUARY_1_1904 = -2'082'844'800;
//-----------------------------------------------------------------------------
// Time Manager
void Pomme::Time::Init()
{
bootTP = std::chrono::high_resolution_clock::now();
}
void GetDateTime(unsigned long* secs)
{
*secs = (unsigned long) (std::time(nullptr) + JANUARY_1_1904);
}
void Microseconds(UnsignedWide* usecs)
{
auto now = std::chrono::high_resolution_clock::now();
auto usecs1 = std::chrono::duration_cast<std::chrono::microseconds>(now - bootTP);
auto usecs2 = usecs1.count();
usecs->lo = usecs2 & 0xFFFFFFFFL;
usecs->hi = (usecs2 >> 32) & 0xFFFFFFFFL;
}
UInt32 TickCount()
{
TODO();
return 0;
}

View File

@ -0,0 +1,85 @@
#include "Utilities/BigEndianIStream.h"
#include "Utilities/IEEEExtended.h"
Pomme::StreamPosGuard::StreamPosGuard(std::istream& theStream) :
stream(theStream)
, backup(theStream.tellg())
, active(true)
{
}
Pomme::StreamPosGuard::~StreamPosGuard()
{
if (active)
{
stream.seekg(backup, std::ios_base::beg);
}
}
void Pomme::StreamPosGuard::Cancel()
{
active = false;
}
Pomme::BigEndianIStream::BigEndianIStream(std::istream& theStream) :
stream(theStream)
{
}
void Pomme::BigEndianIStream::Read(char* dst, size_t n)
{
stream.read(dst, n);
if (stream.eof())
{
throw std::out_of_range("Read past end of stream!");
}
}
std::vector<unsigned char> Pomme::BigEndianIStream::ReadBytes(size_t n)
{
std::vector<unsigned char> buf(n);
Read(reinterpret_cast<char*>(buf.data()), n);
return buf;
}
std::string Pomme::BigEndianIStream::ReadPascalString()
{
int length = Read<uint8_t>();
auto bytes = ReadBytes(length);
bytes.push_back('\0');
return std::string((const char*) &bytes.data()[0]);
}
std::string Pomme::BigEndianIStream::ReadPascalString_FixedLengthRecord(const int maxChars)
{
int length = Read<uint8_t>();
char buf[256];
stream.read(buf, maxChars);
return std::string(buf, length);
}
double Pomme::BigEndianIStream::Read80BitFloat()
{
auto bytes = ReadBytes(10);
return ConvertFromIeeeExtended((unsigned char*) bytes.data());
}
void Pomme::BigEndianIStream::Goto(std::streamoff absoluteOffset)
{
stream.seekg(absoluteOffset, std::ios_base::beg);
}
void Pomme::BigEndianIStream::Skip(size_t n)
{
stream.seekg(n, std::ios_base::cur);
}
std::streampos Pomme::BigEndianIStream::Tell() const
{
return stream.tellg();
}
Pomme::StreamPosGuard Pomme::BigEndianIStream::GuardPos()
{
return Pomme::StreamPosGuard(stream);
}

View File

@ -0,0 +1,62 @@
#pragma once
#include <algorithm>
#include <istream>
#include <vector>
namespace Pomme
{
class StreamPosGuard
{
std::istream& stream;
const std::streampos backup;
bool active;
public:
StreamPosGuard(std::istream& f);
~StreamPosGuard();
void Cancel();
};
class BigEndianIStream
{
std::istream& stream;
public:
BigEndianIStream(std::istream& theStream);
void Read(char* dst, size_t n);
void Skip(size_t n);
void Goto(std::streamoff absoluteOffset);
std::streampos Tell() const;
StreamPosGuard GuardPos();
std::vector<unsigned char> ReadBytes(size_t n);
std::string ReadPascalString();
std::string ReadPascalString_FixedLengthRecord(const int maxChars);
double Read80BitFloat();
template<typename T>
T Read()
{
char b[sizeof(T)];
Read(b, sizeof(T));
#if !(TARGET_RT_BIGENDIAN)
if constexpr (sizeof(T) > 1)
{
std::reverse(b, b + sizeof(T));
}
#endif
return *(T*) b;
}
};
}

46
src/Utilities/FixedPool.h Normal file
View File

@ -0,0 +1,46 @@
#pragma once
namespace Pomme {
template<typename TObj, typename TId, int MAX>
class FixedPool
{
// fixed size array so that pointers to the elements don't move around in memory
// (unlike how a vector might move around elements on resize)
TObj pool[MAX];
std::vector<TId> freeIDs;
int inUse, inUsePeak;
public:
FixedPool()
{
inUse = 0;
inUsePeak = 0;
freeIDs.reserve(MAX);
for (int i = MAX - 1; i >= 0; i--)
freeIDs.push_back(i);
}
TObj* Alloc()
{
if (freeIDs.empty())
throw std::length_error("pool exhausted");
TId id = freeIDs.back();
freeIDs.pop_back();
inUse++;
if (inUse > inUsePeak)
inUsePeak = inUse;
return &pool[id];
}
void Dispose(TObj* obj)
{
intptr_t id = obj - &pool[0];
if (id < 0 || id >= MAX)
throw std::invalid_argument("obj isn't stored in pool");
inUse--;
freeIDs.push_back((TId)id);
}
};
}

View File

@ -0,0 +1,90 @@
#pragma once
#include <vector>
namespace Pomme
{
template<typename TObj, typename TId, int MAX>
class GrowablePool
{
std::vector<TObj> pool;
std::vector<bool> isInUse;
std::vector<TId> freeIDs;
TId inUse, inUsePeak;
public:
GrowablePool()
{
inUse = 0;
inUsePeak = 0;
}
TId Alloc()
{
if (IsFull()) throw std::length_error("too many items allocated");
inUse++;
if (inUse > inUsePeak)
{
inUsePeak = inUse;
}
if (!freeIDs.empty())
{
auto id = freeIDs.back();
freeIDs.pop_back();
isInUse[id] = true;
return id;
}
else
{
pool.emplace_back();
isInUse.push_back(true);
return TId(pool.size() - 1);
}
}
void Dispose(TId id)
{
if (!IsAllocated(id)) throw std::invalid_argument("id isn't allocated");
inUse--;
freeIDs.push_back(id);
isInUse[id] = false;
Compact();
}
void Compact()
{
while (!freeIDs.empty() && freeIDs.back() == (TId) pool.size() - 1)
{
freeIDs.pop_back();
pool.pop_back();
isInUse.pop_back();
}
}
TObj& operator[](TId id)
{
if (!IsAllocated(id)) throw std::invalid_argument("id isn't allocated");
return pool[id];
}
const TObj& operator[](TId id) const
{
if (!IsAllocated(id)) throw std::invalid_argument("id isn't allocated");
return pool[id];
}
bool IsFull() const
{
return inUse >= MAX;
}
bool IsAllocated(TId id) const
{
return id >= 0 && (unsigned) id < pool.size() && isInUse[id];
}
};
}

View File

@ -0,0 +1,126 @@
/* Copyright (C) 1988-1991 Apple Computer, Inc.
* All rights reserved.
*
* Machine-independent I/O routines for IEEE floating-point numbers.
*
* NaN's and infinities are converted to HUGE_VAL or HUGE, which
* happens to be infinity on IEEE machines. Unfortunately, it is
* impossible to preserve NaN's in a machine-independent way.
* Infinities are, however, preserved on IEEE machines.
*
* These routines have been tested on the following machines:
* Apple Macintosh, MPW 3.1 C compiler
* Apple Macintosh, THINK C compiler
* Silicon Graphics IRIS, MIPS compiler
* Cray X/MP and Y/MP
* Digital Equipment VAX
*
*
* Implemented by Malcolm Slaney and Ken Turkowski.
*
* Malcolm Slaney contributions during 1988-1990 include big- and little-
* endian file I/O, conversion to and from Motorola's extended 80-bit
* floating-point format, and conversions to and from IEEE single-
* precision floating-point format.
*
* In 1991, Ken Turkowski implemented the conversions to and from
* IEEE double-precision format, added more precision to the extended
* conversions, and accommodated conversions involving +/- infinity,
* NaN's, and denormalized numbers.
*/
#include <math.h>
#include "IEEEExtended.h"
#ifndef HUGE_VAL
# define HUGE_VAL HUGE
#endif
#define FloatToUnsigned(f) ((unsigned long)(((long)(f - 2147483648.0)) + 2147483647L) + 1)
#define UnsignedToFloat(u) (((double)((long)(u - 2147483647L - 1))) + 2147483648.0)
void ConvertToIeeeExtended(double num, char* bytes)
{
int sign;
int expon;
double fMant, fsMant;
unsigned long hiMant, loMant;
if (num < 0) {
sign = 0x8000;
num *= -1;
} else {
sign = 0;
}
if (num == 0) {
expon = 0; hiMant = 0; loMant = 0;
}
else {
fMant = frexp(num, &expon);
if ((expon > 16384) || !(fMant < 1)) { /* Infinity or NaN */
expon = sign|0x7FFF; hiMant = 0; loMant = 0; /* infinity */
}
else { /* Finite */
expon += 16382;
if (expon < 0) { /* denormalized */
fMant = ldexp(fMant, expon);
expon = 0;
}
expon |= sign;
fMant = ldexp(fMant, 32);
fsMant = floor(fMant);
hiMant = FloatToUnsigned(fsMant);
fMant = ldexp(fMant - fsMant, 32);
fsMant = floor(fMant);
loMant = FloatToUnsigned(fsMant);
}
}
bytes[0] = char(expon >> 8);
bytes[1] = char(expon);
bytes[2] = char(hiMant >> 24);
bytes[3] = char(hiMant >> 16);
bytes[4] = char(hiMant >> 8);
bytes[5] = char(hiMant);
bytes[6] = char(loMant >> 24);
bytes[7] = char(loMant >> 16);
bytes[8] = char(loMant >> 8);
bytes[9] = char(loMant);
}
double ConvertFromIeeeExtended(const unsigned char* bytes /* LCN */)
{
double f;
int expon;
unsigned long hiMant, loMant;
expon = ((bytes[0] & 0x7F) << 8) | (bytes[1] & 0xFF);
hiMant = ((unsigned long)(bytes[2] & 0xFF) << 24)
| ((unsigned long)(bytes[3] & 0xFF) << 16)
| ((unsigned long)(bytes[4] & 0xFF) << 8)
| ((unsigned long)(bytes[5] & 0xFF));
loMant = ((unsigned long)(bytes[6] & 0xFF) << 24)
| ((unsigned long)(bytes[7] & 0xFF) << 16)
| ((unsigned long)(bytes[8] & 0xFF) << 8)
| ((unsigned long)(bytes[9] & 0xFF));
if (expon == 0 && hiMant == 0 && loMant == 0) {
f = 0;
}
else {
if (expon == 0x7FFF) { /* Infinity or NaN */
f = HUGE_VAL;
}
else {
expon -= 16383;
f = ldexp(UnsignedToFloat(hiMant), expon-=31);
f += ldexp(UnsignedToFloat(loMant), expon-=32);
}
}
if (bytes[0] & 0x80)
return -f;
else
return f;
}

View File

@ -0,0 +1,5 @@
#pragma once
void ConvertToIeeeExtended(double num, char* bytes);
double ConvertFromIeeeExtended(const unsigned char* bytes);

View File

@ -0,0 +1,28 @@
#include "Utilities/StringUtils.h"
#include <algorithm>
#include <iterator>
std::string UppercaseCopy(const std::string& in)
{
std::string out;
std::transform(
in.begin(),
in.end(),
std::back_inserter(out),
[](unsigned char c) -> unsigned char
{
return (c >= 'a' && c <= 'z') ? ('A' + c - 'a') : c;
});
return out;
}
u8string AsU8(const std::string s)
{
return u8string(s.begin(), s.end());
}
std::string FromU8(const u8string u8s)
{
return std::string(u8s.begin(), u8s.end());
}

View File

@ -0,0 +1,15 @@
#pragma once
#include <string>
#if defined(__cplusplus) && __cplusplus > 201703L && defined(__cpp_lib_char8_t) && __cpp_lib_char8_t >= 201907L
typedef std::u8string u8string;
#else
typedef std::string u8string;
#endif
std::string UppercaseCopy(const std::string&);
u8string AsU8(const std::string);
std::string FromU8(const u8string);

View File

@ -0,0 +1,80 @@
#include "memstream.h"
//-----------------------------------------------------------------------------
// membuf
membuf::membuf(char* p, size_t n)
{
begin = p;
end = p + n;
setg(p, p, p + n);
setp(p, p + n);
}
membuf::membuf(std::vector<char>& vector)
: membuf(vector.data(), vector.size())
{
}
std::streambuf::pos_type membuf::seekoff(off_type off, std::ios_base::seekdir dir, std::ios_base::openmode which)
{
pos_type ret = 0;
if ((which & std::ios_base::in) > 0)
{
if (dir == std::ios_base::cur)
{
gbump((int32_t) off);
}
else if (dir == std::ios_base::end)
{
setg(begin, end + off, end);
}
else if (dir == std::ios_base::beg)
{
setg(begin, begin + off, end);
}
ret = gptr() - eback();
}
if ((which & std::ios_base::out) > 0)
{
if (dir == std::ios_base::cur)
{
pbump((int32_t) off);
}
else if (dir == std::ios_base::end)
{
setp(begin, end + off);
}
else if (dir == std::ios_base::beg)
{
setp(begin, begin + off);
}
ret = pptr() - pbase();
}
return ret;
}
std::streambuf::pos_type membuf::seekpos(std::streampos pos, std::ios_base::openmode mode)
{
return seekoff(pos - pos_type(off_type(0)), std::ios_base::beg, mode);
}
//-----------------------------------------------------------------------------
// memstream
memstream::memstream(char* p, size_t n)
: membuf(p, n)
, std::iostream(this)
{
}
memstream::memstream(std::vector<char>& data)
: membuf(data)
, std::iostream(this)
{
}

34
src/Utilities/memstream.h Normal file
View File

@ -0,0 +1,34 @@
#pragma once
#include <iostream>
#include <streambuf>
#include <vector>
class membuf : public std::basic_streambuf<char>
{
char* begin;
char* end;
public:
membuf(char* p, size_t n);
explicit membuf(std::vector<char>&);
virtual ~membuf() override = default;
virtual pos_type seekoff(off_type off, std::ios_base::seekdir dir, std::ios_base::openmode which = std::ios_base::in) override;
virtual pos_type seekpos(std::streampos pos, std::ios_base::openmode mode) override;
};
class memstream
: membuf
, public std::iostream
{
public:
memstream(char* p, size_t n);
explicit memstream(std::vector<char>& data);
virtual ~memstream() = default;
};

View File

@ -0,0 +1,122 @@
#include "structpack.h"
#include <algorithm>
#include <stdexcept>
static int Unpack(const char* format, char* buffer)
{
int totalBytes = 0;
int repeat = 0;
for (const char* c2 = format; *c2; c2++)
{
char c = *c2;
int fieldLength = -1;
switch (c)
{
case '>': // big endian indicator (for compat with python's struct) - OK just ignore
continue;
case ' ':
case '\r':
case '\n':
case '\t':
continue;
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
if (repeat) repeat *= 10;
repeat += c - '0';
continue;
case 'x': // pad byte
case 'c': // char
case 'b': // signed char
case 'B': // unsigned char
case '?': // bool
fieldLength = 1;
break;
case 'h': // short
case 'H': // unsigned short
fieldLength = 2;
break;
case 'i': // int
case 'I': // unsigned int
case 'l': // long
case 'L': // unsigned long
case 'f': // float
fieldLength = 4;
break;
case 'q': // long long
case 'Q': // unsigned long long
case 'd': // double
fieldLength = 8;
break;
default:
throw std::invalid_argument("unknown format char in structpack format");
}
if (totalBytes % fieldLength != 0)
{
throw std::invalid_argument("illegal word alignment in structpack format");
}
if (!repeat)
repeat = 1;
bool doSwap = fieldLength > 1;
if (buffer)
{
if (doSwap)
{
for (int i = 0; i < repeat; i++)
{
std::reverse(buffer, buffer + fieldLength);
buffer += fieldLength;
}
}
else
{
buffer += repeat * fieldLength;
}
}
totalBytes += repeat * fieldLength;
repeat = 0;
}
return totalBytes;
}
int ByteswapStructs(const char* format, int structSize, int structCount, void* buffer)
{
char* byteBuffer = (char*) buffer;
int totalBytes = 0;
for (int i = 0; i < structCount; i++)
{
int newSize = Unpack(format, byteBuffer);
byteBuffer += newSize;
totalBytes += newSize;
}
if (totalBytes != structSize * structCount)
{
throw std::invalid_argument("unexpected length after byteswap");
}
return totalBytes;
}
int ByteswapInts(int intSize, int intCount, void* buffer)
{
char* byteBuffer = (char*) buffer;
for (int i = 0; i < intCount; i++)
{
std::reverse(byteBuffer, byteBuffer + intSize);
byteBuffer += intSize;
}
return intCount * intSize;
}

View File

@ -0,0 +1,53 @@
#pragma once
#include <stdint.h>
#ifdef __cplusplus
#include <algorithm>
template<typename T> T ByteswapScalar(T x)
{
#if TARGET_RT_BIGENDIAN
return x;
#else
char* b = (char*)&x;
std::reverse(b, b + sizeof(T));
return x;
#endif
}
#endif
#ifdef __cplusplus
extern "C" {
#endif
int ByteswapStructs(const char* format, int structSize, int structCount, void* buffer);
int ByteswapInts(int intSize, int intCount, void* buffer);
static inline uint16_t Byteswap16(const void* p)
{
uint16_t v;
v =
(*(const uint8_t*) p)
| ((*(const uint8_t*) p + 1) << 8);
return v;
}
static inline uint32_t Byteswap32(const void* p)
{
uint32_t v;
v =
(*(const uint8_t*) p)
| ((*(const uint8_t*) p + 1) << 8)
| ((*(const uint8_t*) p + 2) << 16)
| ((*(const uint8_t*) p + 3) << 24);
return v;
}
#ifdef __cplusplus
}
#endif

400
src/Video/Cinepak.cpp Normal file
View File

@ -0,0 +1,400 @@
// Adapted from ffmpeg
// ---- Begin ffmpeg copyright notices ----
/*
* Cinepak Video Decoder
* Copyright (C) 2003 The FFmpeg project
*
* This file is part of FFmpeg.
*
* FFmpeg is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* FFmpeg is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with FFmpeg; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
/**
* @file
* Cinepak video decoder
* @author Ewald Snel <ewald@rambo.its.tudelft.nl>
*
* Cinepak colorspace support (c) 2013 Rl, Aetey Global Technologies AB
* @author Cinepak colorspace, Rl, Aetey Global Technologies AB
*/
// ---- End ffmpeg copyright notices ----
#include "Video/Cinepak.h"
#include <cstring>
#include <fstream>
class CinepakException : public std::runtime_error
{
public:
CinepakException(const char* m) : std::runtime_error(m)
{}
};
static uint8_t av_clip_uint8(int x)
{
return x > 255 ? 255 : (x < 0 ? 0 : x);
}
static uint16_t AV_RB16(const uint8_t* in)
{
return ((uint16_t)in[0] << 8)
| ((uint16_t)in[1]);
}
static uint32_t AV_RB24(const uint8_t* in)
{
return ((uint32_t)in[0] << 16)
| ((uint32_t)in[1] << 8)
| (uint32_t)in[2];
}
static uint32_t AV_RB32(const uint8_t* in)
{
return ((uint32_t)in[0] << 24)
| ((uint32_t)in[1] << 16)
| ((uint32_t)in[2] << 8)
| (uint32_t)in[3];
}
static void cinepak_decode_codebook (cvid_codebook *codebook,
int chunk_id, int size, const uint8_t *data)
{
const uint8_t *eod = (data + size);
uint32_t flag, mask;
int i, n;
uint8_t *p;
/* check if this chunk contains 4- or 6-element vectors */
n = (chunk_id & 0x04) ? 4 : 6;
flag = 0;
mask = 0;
p = codebook[0];
for (i=0; i < 256; i++) {
if ((chunk_id & 0x01) && !(mask >>= 1)) {
if ((data + 4) > eod)
break;
flag = AV_RB32 (data);
data += 4;
mask = 0x80000000;
}
if (!(chunk_id & 0x01) || (flag & mask)) {
int k, kk;
if ((data + n) > eod)
break;
for (k = 0; k < 4; ++k) {
int r = *data++;
for (kk = 0; kk < 3; ++kk)
*p++ = r;
}
if (n == 6) {
int r, g, b, u, v;
u = *(int8_t *)data++;
v = *(int8_t *)data++;
p -= 12;
for(k=0; k<4; ++k) {
r = *p++ + v*2;
g = *p++ - (u/2) - v;
b = *p + u*2;
p -= 2;
*p++ = av_clip_uint8(r);
*p++ = av_clip_uint8(g);
*p++ = av_clip_uint8(b);
}
}
} else {
p += 12;
}
}
}
void cinepak_decode_vectors (
CinepakContext *s,
cvid_strip *strip,
int chunk_id,
int size,
const uint8_t *data)
{
const uint8_t *eod = (data + size);
uint32_t flag, mask;
uint8_t *cb0, *cb1, *cb2, *cb3;
int x, y;
uint8_t *ip0, *ip1, *ip2, *ip3;
flag = 0;
mask = 0;
for (y=strip->y1; y < strip->y2; y+=4) {
/* take care of y dimension not being multiple of 4, such streams exist */
ip0 = ip1 = ip2 = ip3 = s->frame_data0 +
(strip->x1*3) + (y * s->frame_linesize0);
if(s->avctx_height - y > 1) {
ip1 = ip0 + s->frame_linesize0;
if(s->avctx_height - y > 2) {
ip2 = ip1 + s->frame_linesize0;
if(s->avctx_height - y > 3) {
ip3 = ip2 + s->frame_linesize0;
}
}
}
/* to get the correct picture for not-multiple-of-4 cases let us fill each
* block from the bottom up, thus possibly overwriting the bottommost line
* more than once but ending with the correct data in place
* (instead of in-loop checking) */
for (x=strip->x1; x < strip->x2; x+=4) {
if ((chunk_id & 0x01) && !(mask >>= 1)) {
if ((data + 4) > eod)
throw CinepakException("invalid data");
flag = AV_RB32 (data);
data += 4;
mask = 0x80000000;
}
if (!(chunk_id & 0x01) || (flag & mask)) {
if (!(chunk_id & 0x02) && !(mask >>= 1)) {
if ((data + 4) > eod)
throw CinepakException("invalid data");
flag = AV_RB32 (data);
data += 4;
mask = 0x80000000;
}
if ((chunk_id & 0x02) || (~flag & mask)) {
uint8_t *p;
if (data >= eod)
throw CinepakException("invalid data");
p = strip->v1_codebook[*data++];
p += 6;
memcpy(ip3 + 0, p, 3); memcpy(ip3 + 3, p, 3);
memcpy(ip2 + 0, p, 3); memcpy(ip2 + 3, p, 3);
p += 3; /* ... + 9 */
memcpy(ip3 + 6, p, 3); memcpy(ip3 + 9, p, 3);
memcpy(ip2 + 6, p, 3); memcpy(ip2 + 9, p, 3);
p -= 9; /* ... + 0 */
memcpy(ip1 + 0, p, 3); memcpy(ip1 + 3, p, 3);
memcpy(ip0 + 0, p, 3); memcpy(ip0 + 3, p, 3);
p += 3; /* ... + 3 */
memcpy(ip1 + 6, p, 3); memcpy(ip1 + 9, p, 3);
memcpy(ip0 + 6, p, 3); memcpy(ip0 + 9, p, 3);
} else if (flag & mask) {
if ((data + 4) > eod)
throw CinepakException("invalid data");
cb0 = strip->v4_codebook[*data++];
cb1 = strip->v4_codebook[*data++];
cb2 = strip->v4_codebook[*data++];
cb3 = strip->v4_codebook[*data++];
memcpy(ip3 + 0, cb2 + 6, 6);
memcpy(ip3 + 6, cb3 + 6, 6);
memcpy(ip2 + 0, cb2 + 0, 6);
memcpy(ip2 + 6, cb3 + 0, 6);
memcpy(ip1 + 0, cb0 + 6, 6);
memcpy(ip1 + 6, cb1 + 6, 6);
memcpy(ip0 + 0, cb0 + 0, 6);
memcpy(ip0 + 6, cb1 + 0, 6);
}
}
ip0 += 12; ip1 += 12;
ip2 += 12; ip3 += 12;
}
}
}
void cinepak_decode_strip (
CinepakContext *s,
cvid_strip *strip,
const uint8_t *data,
int size)
{
const uint8_t *eod = (data + size);
int chunk_id, chunk_size;
/* coordinate sanity checks */
if (strip->x2 > s->width ||
strip->y2 > s->height ||
strip->x1 >= strip->x2 || strip->y1 >= strip->y2)
throw CinepakException("invalid data");
while ((data + 4) <= eod) {
chunk_id = data[0];
chunk_size = AV_RB24 (&data[1]) - 4;
if(chunk_size < 0)
throw CinepakException("invalid data");
data += 4;
chunk_size = ((data + chunk_size) > eod) ? (eod - data) : chunk_size;
switch (chunk_id) {
case 0x20:
case 0x21:
case 0x24:
case 0x25:
cinepak_decode_codebook (strip->v4_codebook, chunk_id, chunk_size, data);
break;
case 0x22:
case 0x23:
case 0x26:
case 0x27:
cinepak_decode_codebook (strip->v1_codebook, chunk_id, chunk_size, data);
break;
case 0x30:
case 0x31:
case 0x32:
cinepak_decode_vectors (s, strip, chunk_id, chunk_size, data);
return;
}
data += chunk_size;
}
throw CinepakException("invalid data");
}
void cinepak_predecode_check (CinepakContext *s)
{
int num_strips;
int encoded_buf_size;
num_strips = AV_RB16 (&s->data[8]);
encoded_buf_size = AV_RB24(&s->data[1]);
if (s->size < encoded_buf_size)
throw CinepakException("invalid data");
if (s->size < 10 /*+ s->sega_film_skip_bytes*/ + num_strips * 12)
throw CinepakException("invalid data");
if (num_strips) {
const uint8_t* data = s->data + 10; //+ s->sega_film_skip_bytes;
int strip_size = AV_RB24 (data + 1);
if (strip_size < 12 || strip_size > encoded_buf_size)
throw CinepakException("invalid data");
}
}
static void cinepak_decode (CinepakContext *s)
{
const uint8_t *eod = (s->data + s->size);
int i, strip_size, frame_flags, num_strips;
int y0 = 0;
frame_flags = s->data[0];
num_strips = AV_RB16 (&s->data[8]);
s->data += 10;
num_strips = std::min(num_strips, CINEPAK_MAX_STRIPS);
// s->frame->key_frame = 0;
for (i=0; i < num_strips; i++) {
if ((s->data + 12) > eod)
throw CinepakException("invalid data");
s->strips[i].id = s->data[0];
/* zero y1 means "relative to the previous stripe" */
if (!(s->strips[i].y1 = AV_RB16 (&s->data[4])))
s->strips[i].y2 = (s->strips[i].y1 = y0) + AV_RB16 (&s->data[8]);
else
s->strips[i].y2 = AV_RB16 (&s->data[8]);
s->strips[i].x1 = AV_RB16 (&s->data[6]);
s->strips[i].x2 = AV_RB16 (&s->data[10]);
// if (s->strips[i].id == 0x10)
// s->frame->key_frame = 1;
strip_size = AV_RB24 (&s->data[1]) - 12;
if (strip_size < 0)
throw CinepakException("invalid data");
s->data += 12;
strip_size = ((s->data + strip_size) > eod) ? (eod - s->data) : strip_size;
if ((i > 0) && !(frame_flags & 0x01)) {
memcpy (s->strips[i].v4_codebook, s->strips[i-1].v4_codebook,
sizeof(s->strips[i].v4_codebook));
memcpy (s->strips[i].v1_codebook, s->strips[i-1].v1_codebook,
sizeof(s->strips[i].v1_codebook));
}
cinepak_decode_strip (s, &s->strips[i], s->data, strip_size);
s->data += strip_size;
y0 = s->strips[i].y2;
}
}
CinepakContext::CinepakContext(int _width, int _height)
: strips(CINEPAK_MAX_STRIPS)
{
avctx_width = _width;
avctx_height = _height;
width = (avctx_width + 3) & ~3;
height = (avctx_height + 3) & ~3;
frame_data0 = new uint8_t[width * height * 3];
frame_linesize0 = width*3;
if (!frame_data0)
throw CinepakException("couldn't allocate frame");
}
void CinepakContext::DecodeFrame(const uint8_t* packet_data, const int packet_size)
{
if (packet_size < 10)
throw CinepakException("invalid data -- input buffer too small?");
this->data = packet_data;
this->size = packet_size;
int num_strips = AV_RB16 (&this->data[8]);
//Empty frame, do not waste time
if (!num_strips)
return;
cinepak_predecode_check(this);
cinepak_decode(this);
}
CinepakContext::~CinepakContext()
{
delete[] frame_data0;
}
void CinepakContext::DumpFrameTGA(const char* outFN)
{
std::ofstream out(outFN, std::ios::out | std::ios::binary);
uint16_t TGAhead[] = { 0, 2, 0, 0, 0, 0, (uint16_t)width, (uint16_t)height, 24 };
out.write(reinterpret_cast<char*>(&TGAhead), sizeof(TGAhead));
out.write(reinterpret_cast<char*>(frame_data0), width*height*3);
}

42
src/Video/Cinepak.h Normal file
View File

@ -0,0 +1,42 @@
#pragma once
#include <cstdint>
#include <vector>
#define CINEPAK_MAX_STRIPS 32
typedef uint8_t cvid_codebook[12];
struct cvid_strip
{
uint16_t id;
uint16_t x1, y1;
uint16_t x2, y2;
cvid_codebook v4_codebook[256];
cvid_codebook v1_codebook[256];
};
struct CinepakContext
{
std::vector<cvid_strip> strips;
int avctx_width;
int avctx_height;
int width, height;
uint8_t* frame_data0;
int frame_linesize0;
const uint8_t* data;
int size;
public:
CinepakContext(int avctx_width, int avctx_height);
~CinepakContext();
void DecodeFrame(const uint8_t* packet_data, const int packet_size);
void DumpFrameTGA(const char* outFN);
};

422
src/Video/moov.cpp Normal file
View File

@ -0,0 +1,422 @@
#include "Video/Cinepak.h"
#include "PommeVideo.h"
#include "PommeSound.h"
#include "Utilities/BigEndianIStream.h"
#include "PommeDebug.h"
#include <iostream>
#include <sstream>
using namespace Pomme::Video;
class MoovException : public std::runtime_error
{
public:
MoovException(const std::string m) : std::runtime_error(m)
{}
};
//-----------------------------------------------------------------------------
// Utilities
struct ChunkInfo
{
UInt32 offset;
UInt32 samplesPerChunk;
};
static void MoovAssert(bool condition, const std::string msg)
{
if (!condition)
{
throw MoovException(msg);
}
}
template<typename T>
static void Expect(Pomme::BigEndianIStream& f, const T value, const std::string msg)
{
T found = f.Read<T>();
if (value != found)
{
std::stringstream ss;
ss << "moov parser: " << msg << ": incorrect value: expected " << value << ", found " << found;
throw MoovException(ss.str());
}
}
struct AtomGuard
{
Pomme::BigEndianIStream& f;
FourCharCode fourCC;
std::streampos end;
AtomGuard(Pomme::BigEndianIStream& inputStream, FourCharCode requiredAtomType)
: f(inputStream)
, fourCC(requiredAtomType)
{
auto start = f.Tell();
auto atomSize = f.Read<UInt32>();
Expect<FourCharCode>(f, requiredAtomType, "expected atom");
end = start + (std::streampos) atomSize;
}
~AtomGuard()
{
if (f.Tell() != end)
{
std::cerr << "WARNING: "
<< (f.Tell() < end ? "didn't reach " : "read past ")
<< "end of atom " << Pomme::FourCCString(fourCC) << "\n";
}
}
};
static void RequireAtomAndSkip(Pomme::BigEndianIStream& f, FourCharCode requiredAtomType)
{
AtomGuard atom(f, requiredAtomType);
f.Goto(atom.end);
}
static void SkipAtomIfPresent(Pomme::BigEndianIStream& f, const FourCharCode fourCCToSkip)
{
auto posGuard = f.GuardPos();
auto atomSize = f.Read<UInt32>();
auto atomType = f.Read<FourCharCode>();
if (atomType == fourCCToSkip)
{
f.Skip(atomSize - 8);
posGuard.Cancel();
}
}
//-----------------------------------------------------------------------------
// Atom parsers
// Sample-to-Chunk
static std::vector<ChunkInfo> Parse_stsc(Pomme::BigEndianIStream& f)
{
std::vector<ChunkInfo> chunkInfos;
AtomGuard stsc(f, 'stsc');
Expect<UInt32>(f, 0, "bad stsc version + flags");
const auto numberOfEntries = f.Read<UInt32>();
for (UInt32 i = 0; i < numberOfEntries; i++)
{
ChunkInfo ci = {};
ci.offset = 0xFFFFFFFF;
const auto firstChunk = f.Read<UInt32>();
ci.samplesPerChunk = f.Read<UInt32>();
Expect<UInt32>(f, 1, "sample description ID");
// duplicate last chunk
for (size_t j = chunkInfos.size(); j < firstChunk - 1; j++)
{
auto lastChunk = chunkInfos[chunkInfos.size() - 1];
chunkInfos.push_back(lastChunk);
}
chunkInfos.push_back(ci);
}
return chunkInfos;
}
// Sample Sizes
static std::vector<UInt32> Parse_stsz(Pomme::BigEndianIStream& f)
{
std::vector<UInt32> sampleSizes;
AtomGuard stsz(f, 'stsz');
Expect<UInt32>(f, 0, "stsz version + flags");
auto globalSampleSize = f.Read<UInt32>();
auto numberOfEntries = f.Read<UInt32>();
if (globalSampleSize == 0)
{
for (UInt32 i = 0; i < numberOfEntries; i++)
{
sampleSizes.push_back(f.Read<UInt32>());
}
}
else
{
sampleSizes.push_back(globalSampleSize);
}
return sampleSizes;
}
// Chunk Offsets
static void Parse_stco(Pomme::BigEndianIStream& f, std::vector<ChunkInfo>& chunkList)
{
AtomGuard stco(f, 'stco');
Expect<UInt32>(f, 0, "stco version + flags");
auto numberOfEntries = f.Read<UInt32>();
for (UInt32 i = 0; i < numberOfEntries; i++)
{
auto chunkOffset = f.Read<UInt32>();
chunkList.at(i).offset = chunkOffset;
}
}
static void Parse_mdia_vide(Pomme::BigEndianIStream& f, Movie& movie, UInt32 timeScale)
{
std::vector<ChunkInfo> chunkList;
std::vector<UInt32> frameSizes;
{
AtomGuard minf(f, 'minf');
RequireAtomAndSkip(f, 'vmhd');
RequireAtomAndSkip(f, 'hdlr');
RequireAtomAndSkip(f, 'dinf');
{
AtomGuard stbl(f, 'stbl');
{
AtomGuard stsd(f, 'stsd');
Expect<UInt32>(f, 0, "vide stsd version + flags");
Expect<UInt32>(f, 1, "vide stsd number of entries");
f.Skip(4); // UInt32 sampleDescriptionSize
movie.videoFormat = f.Read<FourCharCode>();
f.Skip(6); // reserved
f.Skip(2); // data reference index
Expect<UInt16>(f, 1, "vide stsd version");
Expect<UInt16>(f, 1, "vide stsd revision level"); // docs say it should be 0, but in practice it's 1
f.Skip(4); // vendor
f.Skip(4); // temporal quality
f.Skip(4); // spatial quality
movie.width = f.Read<UInt16>();
movie.height = f.Read<UInt16>();
f.Skip(4); // horizontal resolution (ppi)
f.Skip(4); // vertical resolution (ppi)
Expect<UInt32>(f, 0, "vide stsd data size");
Expect<UInt16>(f, 1, "vide stsd frame count per sample");
f.Skip(32); // compressor name
Expect<UInt16>(f, 24, "pixel depth");
f.Skip(2); // color table ID
}
{
AtomGuard stts(f, 'stts');
Expect<UInt32>(f, 0, "stts version + flags");
Expect<UInt32>(f, 1, "stts number of entries");
f.Skip(4); // UInt32 sampleCount
auto sampleDuration = f.Read<UInt32>();
movie.videoFrameRate = (float) timeScale / sampleDuration;
}
SkipAtomIfPresent(f, 'stss');
chunkList = Parse_stsc(f);
frameSizes = Parse_stsz(f);
Parse_stco(f, chunkList);
SkipAtomIfPresent(f, 'stsh');
}
}
// std::cout << "vide: " << Pomme::FourCCString(movie.videoFormat) << ", " << movie.width << "x" << movie.height << ", " << movie.videoFrameRate << "fps\n";
// ------------------------------------
// EXTRACT VIDEO FRAMES
// Set up guard
auto guard = f.GuardPos();
size_t frameCounter = 0;
for (const auto& chunk : chunkList)
{
f.Goto(chunk.offset);
for (UInt32 s = 0; s < chunk.samplesPerChunk; s++, frameCounter++)
{
const size_t frameSize = frameSizes[frameCounter];
movie.videoFrames.push(f.ReadBytes(frameSize));
}
}
}
static void Parse_mdia_soun(Pomme::BigEndianIStream& f, Movie& movie)
{
std::vector<ChunkInfo> chunkList;
{
AtomGuard minf(f, 'minf');
SkipAtomIfPresent(f, 'smhd');
SkipAtomIfPresent(f, 'hdlr');
SkipAtomIfPresent(f, 'dinf');
{
AtomGuard stbl(f, 'stbl');
{
AtomGuard stsd(f, 'stsd');
Expect<UInt32>(f, 0, "soun stsd version + flags");
Expect<UInt32>(f, 1, "soun stsd number of entries");
f.Skip(4); // UInt32 sampleDescriptionSize
movie.audioFormat = f.Read<FourCharCode>();
f.Skip(6); // reserved
f.Skip(2); // data reference index
Expect<UInt16>(f, 0, "soun stsd version");
Expect<UInt16>(f, 0, "soun stsd revision level");
f.Skip(4); // vendor
movie.audioNChannels = f.Read<UInt16>();
movie.audioBitDepth = f.Read<UInt16>();
Expect<UInt16>(f, 0, "soun stsd compression ID");
Expect<UInt16>(f, 0, "soun stsd packet size");
Fixed fixedSampleRate = f.Read<Fixed>();
movie.audioSampleRate = (static_cast<unsigned int>(fixedSampleRate) >> 16) & 0xFFFF;
}
{
AtomGuard stts(f, 'stts');
Expect<UInt32>(f, 0, "stts version + flags");
Expect<UInt32>(f, 1, "stts number of entries");
auto sampleCount = f.Read<UInt32>();
Expect<UInt32>(f, 1, "soun stts: sample duration");
movie.audioSampleCount = sampleCount;
}
SkipAtomIfPresent(f, 'stss');
chunkList = Parse_stsc(f); //SkipAtomIfPresent(f, 'stsc');
auto sampleSize = Parse_stsz(f); //SkipAtomIfPresent(f, 'stsz');
MoovAssert(1 == sampleSize.size(), "in the sound track, all samples are expected to be of size 1");
MoovAssert(1 == sampleSize[0], "in the sound track, all samples are expected to be of size 1");
Parse_stco(f, chunkList);
SkipAtomIfPresent(f, 'stsh');
}
}
// std::cout << "soun: " << Pomme::FourCCString(movie.audioFormat) << ", " << movie.audioNChannels << "ch, " << movie.audioBitDepth << "bit, " << movie.audioSampleRate << "Hz\n";
// ------------------------------------
// EXTRACT AUDIO
bool isRawPCM = movie.audioFormat == 'twos' || movie.audioFormat == 'swot';
std::unique_ptr<Pomme::Sound::Codec> codec = nullptr;
if (!isRawPCM)
{
codec = Pomme::Sound::GetCodec(movie.audioFormat);
}
// Set up position guard for rest of function
auto guard = f.GuardPos();
// Unfortunately, Nanosaur's movies use version 0 of the 'stsd' atom for sound tracks.
// This means that the "bytes per packet" count is not encoded into the file. (QTFF-2001, pp. 100-101)
// We have to deduce it from the audio format.
std::vector<int> chunkLengths;
UInt32 compressedLength = 0;
UInt32 totalSamples = 0;
for (const auto& chunk : chunkList)
{
totalSamples += chunk.samplesPerChunk;
int chunkBytes = isRawPCM
? movie.audioNChannels * chunk.samplesPerChunk * (movie.audioBitDepth / 8)
: movie.audioNChannels * chunk.samplesPerChunk * codec->BytesPerPacket() / codec->SamplesPerPacket();
chunkLengths.push_back(chunkBytes);
compressedLength += chunkBytes;
}
std::vector<char> compressedSoundData;
compressedSoundData.reserve(compressedLength);
char* out = compressedSoundData.data();
for (size_t i = 0; i < chunkList.size(); i++)
{
f.Goto(chunkList[i].offset);
f.Read(out, chunkLengths[i]);
out += chunkLengths[i];
}
MoovAssert(out == compressedSoundData.data() + compressedLength, "csd length != total length");
if (isRawPCM)
{
movie.audioStream.SetBuffer(std::move(compressedSoundData));
movie.audioStream.Init(
movie.audioSampleRate,
movie.audioBitDepth,
movie.audioNChannels,
movie.audioFormat == 'twos',
movie.audioStream.GetBuffer(compressedLength));
}
else
{
auto outBytes = 2 * totalSamples * movie.audioNChannels;
auto outSpan = movie.audioStream.GetBuffer(outBytes);
auto inSpan = std::span(compressedSoundData.data(), compressedLength);
codec->Decode(movie.audioNChannels, inSpan, outSpan);
movie.audioStream.Init(movie.audioSampleRate, 16, movie.audioNChannels, false, outSpan);
}
}
static void Parse_mdia(Pomme::BigEndianIStream& f, Movie& movie)
{
AtomGuard mdia(f, 'mdia');
FourCharCode componentType;
UInt32 timeScale;
{
AtomGuard mdhd(f, 'mdhd');
Expect<UInt32>(f, 0, "mdhd version + flags");
f.Skip(4); // ctime
f.Skip(4); // mtime
timeScale = f.Read<UInt32>();
f.Skip(4); // UInt32 duration
f.Skip(2); // language
f.Skip(2); // quality
//std::cout << "mdhd: timeScale: " << timeScale << " units per second; duration: " << duration << " units\n";
}
{
AtomGuard hdlr(f, 'hdlr');
f.Skip(4);
Expect<FourCharCode>(f, 'mhlr', "mhlr required here");
componentType = f.Read<FourCharCode>();
f.Goto(hdlr.end);
}
if ('vide' == componentType)
{
Parse_mdia_vide(f, movie, timeScale);
}
else if ('soun' == componentType)
{
Parse_mdia_soun(f, movie);
}
else
{
throw MoovException("hdlr component type should be either vide or soun");
}
}
static void Parse_trak(Pomme::BigEndianIStream& f, Movie& movie)
{
AtomGuard trak(f, 'trak');
RequireAtomAndSkip(f, 'tkhd');
while (f.Tell() < trak.end)
{
auto start = f.Tell();
auto atomSize = f.Read<UInt32>();
auto atomType = f.Read<FourCharCode>();
f.Goto(start);
if (atomType != 'mdia')
{
f.Skip(atomSize);
}
else
{
Parse_mdia(f, movie);
}
}
}
//-----------------------------------------------------------------------------
// Moov parser
Movie Pomme::Video::ReadMoov(std::istream& theF)
{
Pomme::BigEndianIStream f(theF);
Movie movie;
AtomGuard moov(f, 'moov');
RequireAtomAndSkip(f, 'mvhd');
Parse_trak(f, movie); // Parse first track(video)
Parse_trak(f, movie); // Parse second track (audio)
SkipAtomIfPresent(f, 'udta');
return movie;
}