// iigs/sound.h - convenience wrappers for the SoundManager toolset.
//
// What's here today: the simplest correct wrappers around the existing
// toolbox calls - SysBeep, FFStartSound on a pre-loaded DOC RAM region,
// FFStopSound, FFSoundDoneStatus polling, plus iigsLoadDocSample (a
// thin wrapper around WriteRamBlock that stages caller-RAM bytes into
// the Ensoniq DOC's 64 KB audio RAM) and iigsSoundProbeInit (a small
// MMStartUp + SoundStartUp helper so CLI-style sound demos don't have
// to pull in startdesk()'s 16-tool chain).  Lower-level than
// std::sound but a thin layer above iigs/toolbox.h.
//
// Phase 1.6 (2026-06-01) corrected the IigsSoundParmT layout to match
// ORCA's authoritative SoundParamBlock (18 bytes).  The previous 6-byte
// struct was silently wrong; any caller relying on the old layout MUST
// migrate.  The new layout matches the Apple SoundManager reference
// (Apple Tech Note #76) exactly.
//
// Phase 2.4 (2026-06-01) added iigsLoadDocSample so callers can stage
// in-RAM waveform bytes directly without going through the raw
// WriteRamBlock toolbox call.
//
// Caller must have started up the SoundManager before any of these
// functions are called.  startdesk() in iigs/desktop.h does that for
// you; for a CLI-style sound demo where a full desktop is overkill,
// call iigsSoundProbeInit() yourself first.
#ifndef IIGS_SOUND_H
#define IIGS_SOUND_H

#ifdef __cplusplus
extern "C" {
#endif

#include <stdint.h>


// SoundParamBlock consumed by FFStartSound.  Layout MUST match ORCA's
// authoritative SoundParamBlock (tools/orca-c/ORCACDefs/sound.h:69):
// 18 bytes total, field order load-bearing.  Do NOT reorder; the
// toolset reads by offset.
//
// waveStart is a 24/32-bit BYTE address into DOC RAM (NOT a 256-byte
// page index, as the previous incorrect layout assumed).  Pass the
// byte offset where the sample begins in DOC RAM.
// waveSize is in 256-byte pages.
// volSetting's high byte must be zero (DOC volume is u8).
// nextWavePtr chains additional waves; NULL terminates.
typedef struct IigsSoundParmT {
    void *                  waveStart;     // 4B: DOC RAM byte address of wave
    uint16_t                waveSize;      // 2B: wave length in 256-byte pages
    uint16_t                freqOffset;    // 2B: pitch offset
    uint16_t                docBuffer;     // 2B: DOC buffer start, low byte = 0
    uint16_t                bufferSize;    // 2B: DOC buffer size, low byte = 0
    struct IigsSoundParmT * nextWavePtr;   // 4B: next wave in chain, NULL = end
    uint16_t                volSetting;    // 2B: DOC volume (high byte = 0)
} IigsSoundParmT;

_Static_assert(sizeof(IigsSoundParmT) == 18, "IigsSoundParmT must be 18 bytes per ORCA SoundParamBlock");


// ---- one-call wrappers --------------------------------------------

// Lightweight startup helper for sound-only demos that don't want to
// drag in startdesk()'s full 16-tool chain.  Calls MMStartUp +
// SoundStartUp in the right order.  Safe to call after the Loader
// already started up Memory Manager (the toolset reference-counts).
// Returns the userId allocated by MMStartUp; the caller can pass it
// to NewHandle/similar if it needs to allocate from the same pool.
//
// Pair with iigsSoundProbeShutdown() at exit, or just exit straight to
// GS/OS - Finder will clean up the tool startup chain on app
// termination.
unsigned short iigsSoundProbeInit(void);


// Shut down the SoundManager started by iigsSoundProbeInit().  Optional
// - Finder will reclaim everything on app exit.
void iigsSoundProbeShutdown(void);


// Stage a waveform from caller RAM into the Ensoniq DOC's 64 KB audio
// RAM.  Wraps the WriteRamBlock toolbox call (tool 0x0908, set 0x08).
//
// SoundManager must already be started up (see iigsSoundProbeInit() or
// startdesk()).  Returns nothing; WriteRamBlock has no error result and
// silently truncates if docOffset + size overflows DOC RAM.  Use
// iigsPlayDocSample() afterwards to play the staged region.
//
//   wave       pointer to the raw sample bytes (signed 8-bit, DOC's
//              native format).  Reads `size` bytes starting here.
//   size       number of bytes to copy.  Must be a non-zero multiple
//              of 256 - DOC RAM addressing is page-aligned (256-byte
//              pages) and FFStartSound consumes lengths in pages.
//   docOffset  destination BYTE offset into DOC RAM (0..65535).  The
//              low byte should be zero (page-aligned).
void iigsLoadDocSample(const signed char *wave, unsigned short size, unsigned short docOffset);


// System beep.  Same as the toolbox SysBeep but named consistently.
void iigsBeep(void);


// Play a sample that has already been written into DOC RAM.  Returns
// immediately (asynchronous); use iigsSoundWait() to block until done.
//
// Phase 1.6 (2026-06-01) BREAKING CHANGE: the signature has been
// rewritten to match the corrected struct.  Old callers passed
// (docPage, pages, pitch, volume, channel) which silently produced
// wrong DOC RAM addresses (the old waveStart was 1 byte, not 4).
//
//   docAddr   DOC RAM BYTE address where the sample begins (NOT a
//             256-byte page index).  Multiply your old "docPage" by
//             256 to get the equivalent byte address.
//   pages     length in 256-byte pages.
//   freqOffset DOC pitch offset.
//   volume    0..255 (placed in volSetting, high byte zeroed).
//   genNum    generator number (0..15) in the low byte, priority
//             (0..255) in the high byte.  This is FFStartSound's
//             arg0 - the channel is NOT in the struct anymore.
void iigsPlayDocSample(void *docAddr, uint16_t pages,
                       uint16_t freqOffset, uint8_t volume,
                       uint16_t genNum);


// Stop playback on the given generator (0..15).  Pass 0xFF to stop
// all generators.
void iigsSoundStop(uint8_t generator);


// Block until generator 0 finishes playing.  Polls FFSoundDoneStatus.
void iigsSoundWait(void);


#ifdef __cplusplus
}
#endif

#endif  // IIGS_SOUND_H
