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>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
3fe92d01b8
commit
85404c91f4
@@ -0,0 +1,61 @@
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user