Files
tony-remake/game/tfmx_player/player_api.h
T
duffyduckandClaude Opus 4.8 85404c91f4 Projektstruktur: game/-Package angelegt, README ergaenzt
Laufzeit-Code vom Dev-Werkzeug getrennt:
- game/: das eigentliche Spiel, startbar per `python -m game [--once]`.
  - formats.py  (war tools/kellogg_formats.py) -- Asset-Parser, Kern-Bibliothek
  - audio.py    (war tools/tfmx_audio.py)      -- TFMX-Musik-Wrapper
  - intro.py    (war tools/intro_sequence.py)  -- Boot-/Intro-Sequenz
  - tfmx_player/(war tools/tfmx_player/)        -- MIT-C-Renderer
  - __init__.py / __main__.py fuer `python -m game`
- tools/: nur noch Dev-Werkzeuge (dat_extract, pcc_to_png, render_assets),
  importieren jetzt aus game.formats.
- README.md: Ueberblick, Start, Struktur, Voraussetzungen, Rechtliches.
- .gitignore: tfmx_player-Binary-Pfad auf game/ nachgezogen.

Intro laeuft unveraendert (headless-Smoke-Test `python -m game --once` exit 0).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 00:29:03 +02:00

62 lines
2.6 KiB
C

// Copyright (c) 2026 Peter Fors
// SPDX-License-Identifier: MIT
//
// Common interface every ported replayer exports. Each player's header declares
// a `struct player_api <name>_api` global that the test player (or any host)
// can iterate to auto-detect the right replayer for a file.
//
// Audio output is interleaved float stereo, nominal range [-1.0, 1.0]. Players
// ACCUMULATE into the caller's buffer (caller must pre-clear). They do NOT
// clip; the host is responsible for any final saturation, dithering, or
// conversion to the audio backend's native sample format.
#pragma once
#include <stdint.h>
// Optional file-loader callback used by players that read companion files
// (IFF SMUS instrument files, Face The Music external samples, etc.). Hosts
// that pass a loader implement `fetch` to resolve a logical name (e.g.
// "Instruments/Bass6.instr") to a heap-allocated byte buffer + length. The
// player calls free() on the returned pointer when done.
struct player_loader {
void *ctx;
uint8_t *(*fetch)(void *ctx, const char *name, uint32_t *out_len);
};
struct player_api {
const char *name;
const char **extensions; /* null-terminated list of lowercase extensions, no dot */
void *(*init)(void *data, uint32_t len, int32_t sample_rate);
void (*free)(void *state);
void (*get_audio)(void *state, float *output, int32_t frames);
// Optional: when non-null and the host has a loader for sibling files,
// the host should prefer this entry point. Players that don't need
// external files leave this null and the host falls back to init().
void *(*init_ex)(void *data, uint32_t len, int32_t sample_rate, struct player_loader *loader);
};
// [=]===^=[ player_get_audio_s16 ]===============================================================[=]
// Convenience wrapper for hosts that want signed-16 PCM out. Drives the
// player's float get_audio into the caller-supplied scratch buffer (must hold
// at least frames * 2 floats), clears it first, then converts with hard
// saturation into `output` (frames * 2 int16 stereo samples). The scratch is
// caller-owned so the hot path never allocates; reuse the same buffer across
// calls.
#include <string.h>
static void player_get_audio_s16(struct player_api *api, void *state, int16_t *output, float *scratch, int32_t frames) {
int32_t samples = frames * 2;
memset(scratch, 0, (size_t)samples * sizeof(float));
api->get_audio(state, scratch, frames);
for(int32_t i = 0; i < samples; ++i) {
float v = scratch[i] * 32767.0f;
if(v > 32767.0f) {
v = 32767.0f;
}
if(v < -32768.0f) {
v = -32768.0f;
}
output[i] = (int16_t)v;
}
}