533 lines
21 KiB
C
533 lines
21 KiB
C
// Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
|
// See LICENSE.md for license information.
|
|
// SPDX-License-Identifier: MIT
|
|
//
|
|
// Audio conformance probe (AUDPAR, a gate's program, not an example): one
|
|
// fixed timeline of what every port's audio must do alike, each step logged
|
|
// as it starts, so scripts/verify-audio-conformance.sh can record what a
|
|
// port played and tools/gradeAudioConformance.py can measure it against the
|
|
// API's contract: tone and voice pitch, 2 dB attenuation steps, every voice,
|
|
// the noise generator's rate and steps, sound effects at their rates and
|
|
// pans, streamed ones (signed, and in the port's raw format), a module that
|
|
// does not loop ending (and jlAudioIsPlayingMod saying so), one that loops
|
|
// stopping when it is told, and tick callbacks at their rates.
|
|
//
|
|
// The log, one line per event, "AUDPAR <ms> <event> <values>", ms counted
|
|
// from the first sync tone's start:
|
|
// sync <hz> a sync tone: the timeline starts and ends
|
|
// with one, so a recording can be aligned
|
|
// tone <hz> jlAudioTone
|
|
// voice <v> <hz> <atten> jlAudioVoice
|
|
// noise <pitch> <atten> jlAudioNoise
|
|
// sfx <slot> <rate> <hz> <pan> <samples>
|
|
// a one-shot sine sounding <hz> at <rate>,
|
|
// <samples> long (44100 Hz: above the IIgs's
|
|
// and the Amiga's chips, which decimate)
|
|
// stream <slot> <rate> <hz> <pan> <raw> <samples>
|
|
// a sine streamed (raw 1: through
|
|
// jlAudioPlaySfxStreamRaw)
|
|
// mod start / mod end jlAudioPlayMod with loop false, and the
|
|
// first jlAudioIsPlayingMod false after it
|
|
// mod loop / mod stop jlAudioPlayMod with loop true, and the
|
|
// jlAudioStopMod after it
|
|
// tick <hz> <ok> <calls> <ms> jlAudioTickRegister's result, the calls
|
|
// that came, and the time they had
|
|
// silence everything stopped
|
|
// done
|
|
// Escape ends it early.
|
|
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <joey/joey.h>
|
|
#include <joey/file.h>
|
|
|
|
#if defined(JOEYLIB_PLATFORM_IIGS)
|
|
# define TEST_MOD_NAME "TEST.NTP"
|
|
#else
|
|
# define TEST_MOD_NAME "test.mod"
|
|
#endif
|
|
#define DATA_FILE_MAX (64UL * 1024UL)
|
|
|
|
#define SYNC_HZ 1000u
|
|
#define SYNC_MS 300u
|
|
#define GAP_MS 100u
|
|
#define PAUSE_MS 200u
|
|
#define ATTEN_STEPS 15u // 0..14 audible, 15 silent
|
|
#define ATTEN_SILENT 15u
|
|
#define TONE_MS 400u
|
|
#define STEP_MS 300u
|
|
#define NOISE_MS 400u
|
|
#define NOISE_STEP_MS 200u
|
|
#define NOISE_STEP_PITCH 8u
|
|
#define LEVEL_HZ 440u
|
|
#define VOICE_HZ 660u
|
|
#define TICK_MS 2000u
|
|
#define MOD_WAIT_MS 12000u
|
|
#define MOD_STOP_MS 1500u // a looping module plays this long, then stops
|
|
#define SFX_SLOT 0u
|
|
#define SFX_SAMPLES 4000u
|
|
#define SFX_PERIOD 8u // samples a cycle: the tone is rate / 8
|
|
// Faster than the IIgs's DOC and the Amiga's Paula play, so they decimate:
|
|
// the tone rate / 16, as the 22050 Hz step's, as long as that step.
|
|
#define SFX_FAST_RATE 44100u
|
|
#define SFX_FAST_PERIOD 16u
|
|
#define SFX_FAST_SAMPLES 8000u
|
|
#define SFX_AMPLITUDE 100
|
|
#define RAW_BIAS 0x80 // JL_SFX_RAW_UNSIGNED8_NOZERO's zero
|
|
#define MS_PER_SECOND 1000u
|
|
#define EVENTS_MAX 128u
|
|
|
|
typedef enum {
|
|
STEP_SYNC,
|
|
STEP_TONE,
|
|
STEP_VOICE,
|
|
STEP_NOISE,
|
|
STEP_SFX,
|
|
STEP_SFX_FAST,
|
|
STEP_STREAM,
|
|
STEP_STREAM_RAW,
|
|
STEP_STREAM_FAST,
|
|
STEP_MOD,
|
|
STEP_MOD_STOP,
|
|
STEP_TICK,
|
|
STEP_SILENCE
|
|
} StepKindE;
|
|
|
|
typedef struct {
|
|
StepKindE kind;
|
|
uint8_t channel; // voice, or the sfx pan
|
|
uint16_t value; // Hz, noise pitch, or sfx rate
|
|
uint8_t level; // attenuation
|
|
uint16_t ms; // how long it sounds (sfx: from its length)
|
|
} StepT;
|
|
|
|
// A sine a sound step plays: count samples, period of them a cycle.
|
|
typedef struct {
|
|
const int8_t *samples;
|
|
uint32_t count;
|
|
uint16_t period;
|
|
} SineT;
|
|
|
|
// A stream step's sine and its progress through it.
|
|
typedef struct {
|
|
const SineT *sine;
|
|
uint32_t pos;
|
|
} StreamT;
|
|
|
|
// What the log will say of one step, kept in memory until the timeline is
|
|
// over: a log line can take a disk write (a fifth of a second on the ST),
|
|
// and written while it plays it would stall the program's sound.
|
|
typedef struct {
|
|
uint32_t ms;
|
|
StepKindE kind;
|
|
uint16_t value;
|
|
uint8_t channel;
|
|
uint8_t level;
|
|
uint32_t count; // mod: its length, then playing; tick: calls
|
|
uint32_t span; // tick: the time the calls had
|
|
bool ok; // tick: registered; mod end: still playing
|
|
bool modEnd; // mod: its end; mod stop: the stop
|
|
} EventT;
|
|
|
|
static EventT *eventAdd(uint32_t ms, const StepT *step);
|
|
static void eventsLog(void);
|
|
static void loadMod(uint8_t **bytes, uint32_t *length);
|
|
static bool pump(void);
|
|
static void runStep(const StepT *step, const uint8_t *mod, uint32_t modLength);
|
|
static uint32_t since(void);
|
|
static void silenceAll(void);
|
|
static const SineT *stepSine(StepKindE kind);
|
|
static uint32_t streamFill(void *ctx, int8_t *dst, uint32_t count);
|
|
static void tickCount(void);
|
|
static bool waitMs(uint16_t ms);
|
|
|
|
static const StepT kTimeline[] = {
|
|
{ STEP_SYNC, 0u, SYNC_HZ, 0u, SYNC_MS },
|
|
{ STEP_SILENCE, 0u, 0u, 0u, PAUSE_MS },
|
|
// jlAudioTone across the octaves every port can play.
|
|
{ STEP_TONE, 0u, 110u, 0u, TONE_MS }, { STEP_SILENCE, 0u, 0u, 0u, GAP_MS },
|
|
{ STEP_TONE, 0u, 220u, 0u, TONE_MS }, { STEP_SILENCE, 0u, 0u, 0u, GAP_MS },
|
|
{ STEP_TONE, 0u, 440u, 0u, TONE_MS }, { STEP_SILENCE, 0u, 0u, 0u, GAP_MS },
|
|
{ STEP_TONE, 0u, 880u, 0u, TONE_MS }, { STEP_SILENCE, 0u, 0u, 0u, GAP_MS },
|
|
{ STEP_TONE, 0u, 1760u, 0u, TONE_MS }, { STEP_SILENCE, 0u, 0u, 0u, GAP_MS },
|
|
{ STEP_TONE, 0u, 3136u, 0u, TONE_MS }, { STEP_SILENCE, 0u, 0u, 0u, PAUSE_MS },
|
|
// Voice 0's attenuation, one step after another: 2 dB apart.
|
|
{ STEP_VOICE, 0u, LEVEL_HZ, 0u, STEP_MS }, { STEP_VOICE, 0u, LEVEL_HZ, 1u, STEP_MS },
|
|
{ STEP_VOICE, 0u, LEVEL_HZ, 2u, STEP_MS }, { STEP_VOICE, 0u, LEVEL_HZ, 3u, STEP_MS },
|
|
{ STEP_VOICE, 0u, LEVEL_HZ, 4u, STEP_MS }, { STEP_VOICE, 0u, LEVEL_HZ, 5u, STEP_MS },
|
|
{ STEP_VOICE, 0u, LEVEL_HZ, 6u, STEP_MS }, { STEP_VOICE, 0u, LEVEL_HZ, 7u, STEP_MS },
|
|
{ STEP_VOICE, 0u, LEVEL_HZ, 8u, STEP_MS }, { STEP_VOICE, 0u, LEVEL_HZ, 9u, STEP_MS },
|
|
{ STEP_VOICE, 0u, LEVEL_HZ, 10u, STEP_MS }, { STEP_VOICE, 0u, LEVEL_HZ, 11u, STEP_MS },
|
|
{ STEP_VOICE, 0u, LEVEL_HZ, 12u, STEP_MS }, { STEP_VOICE, 0u, LEVEL_HZ, 13u, STEP_MS },
|
|
{ STEP_VOICE, 0u, LEVEL_HZ, 14u, STEP_MS }, { STEP_SILENCE, 0u, 0u, 0u, PAUSE_MS },
|
|
// The other two voices.
|
|
{ STEP_VOICE, 1u, VOICE_HZ, 0u, STEP_MS }, { STEP_SILENCE, 0u, 0u, 0u, GAP_MS },
|
|
{ STEP_VOICE, 2u, VOICE_HZ, 0u, STEP_MS }, { STEP_SILENCE, 0u, 0u, 0u, PAUSE_MS },
|
|
// The noise generator's rate, bright to dark.
|
|
{ STEP_NOISE, 0u, 1u, 0u, NOISE_MS }, { STEP_SILENCE, 0u, 0u, 0u, GAP_MS },
|
|
{ STEP_NOISE, 0u, 2u, 0u, NOISE_MS }, { STEP_SILENCE, 0u, 0u, 0u, GAP_MS },
|
|
{ STEP_NOISE, 0u, 4u, 0u, NOISE_MS }, { STEP_SILENCE, 0u, 0u, 0u, GAP_MS },
|
|
{ STEP_NOISE, 0u, 8u, 0u, NOISE_MS }, { STEP_SILENCE, 0u, 0u, 0u, GAP_MS },
|
|
{ STEP_NOISE, 0u, 16u, 0u, NOISE_MS }, { STEP_SILENCE, 0u, 0u, 0u, GAP_MS },
|
|
{ STEP_NOISE, 0u, 31u, 0u, NOISE_MS }, { STEP_SILENCE, 0u, 0u, 0u, PAUSE_MS },
|
|
// The noise's attenuation steps.
|
|
{ STEP_NOISE, 0u, NOISE_STEP_PITCH, 0u, NOISE_STEP_MS }, { STEP_NOISE, 0u, NOISE_STEP_PITCH, 2u, NOISE_STEP_MS },
|
|
{ STEP_NOISE, 0u, NOISE_STEP_PITCH, 4u, NOISE_STEP_MS }, { STEP_NOISE, 0u, NOISE_STEP_PITCH, 6u, NOISE_STEP_MS },
|
|
{ STEP_NOISE, 0u, NOISE_STEP_PITCH, 8u, NOISE_STEP_MS }, { STEP_NOISE, 0u, NOISE_STEP_PITCH, 10u, NOISE_STEP_MS },
|
|
{ STEP_NOISE, 0u, NOISE_STEP_PITCH, 12u, NOISE_STEP_MS }, { STEP_NOISE, 0u, NOISE_STEP_PITCH, 14u, NOISE_STEP_MS },
|
|
{ STEP_SILENCE, 0u, 0u, 0u, PAUSE_MS },
|
|
// Sound effects at three rates, then panned each way.
|
|
{ STEP_SFX, JL_AUDIO_PAN_CENTER, 8000u, 0u, 0u }, { STEP_SILENCE, 0u, 0u, 0u, GAP_MS },
|
|
{ STEP_SFX, JL_AUDIO_PAN_CENTER, 16000u, 0u, 0u }, { STEP_SILENCE, 0u, 0u, 0u, GAP_MS },
|
|
{ STEP_SFX, JL_AUDIO_PAN_CENTER, 22050u, 0u, 0u }, { STEP_SILENCE, 0u, 0u, 0u, GAP_MS },
|
|
{ STEP_SFX_FAST, JL_AUDIO_PAN_CENTER, SFX_FAST_RATE, 0u, 0u }, { STEP_SILENCE, 0u, 0u, 0u, GAP_MS },
|
|
{ STEP_SFX, JL_AUDIO_PAN_LEFT, 8000u, 0u, 0u }, { STEP_SILENCE, 0u, 0u, 0u, GAP_MS },
|
|
{ STEP_SFX, JL_AUDIO_PAN_RIGHT, 8000u, 0u, 0u }, { STEP_SILENCE, 0u, 0u, 0u, PAUSE_MS },
|
|
// The same sound streamed, streamed raw, and streamed fast.
|
|
{ STEP_STREAM, JL_AUDIO_PAN_CENTER, 11025u, 0u, 0u }, { STEP_SILENCE, 0u, 0u, 0u, GAP_MS },
|
|
{ STEP_STREAM_RAW, JL_AUDIO_PAN_CENTER, 12000u, 0u, 0u }, { STEP_SILENCE, 0u, 0u, 0u, GAP_MS },
|
|
{ STEP_STREAM_FAST, JL_AUDIO_PAN_CENTER, SFX_FAST_RATE, 0u, 0u }, { STEP_SILENCE, 0u, 0u, 0u, PAUSE_MS },
|
|
// A module that does not loop, to its end.
|
|
{ STEP_MOD, 0u, 0u, 0u, MOD_WAIT_MS }, { STEP_SILENCE, 0u, 0u, 0u, PAUSE_MS },
|
|
// A module that loops, stopped while it plays.
|
|
{ STEP_MOD_STOP, 0u, 0u, 0u, MOD_STOP_MS }, { STEP_SILENCE, 0u, 0u, 0u, PAUSE_MS },
|
|
// Tick callbacks, slow and at a frame's rate.
|
|
{ STEP_TICK, 0u, 10u, 0u, TICK_MS },
|
|
{ STEP_TICK, 0u, 60u, 0u, TICK_MS },
|
|
{ STEP_SILENCE, 0u, 0u, 0u, PAUSE_MS },
|
|
{ STEP_SYNC, 0u, SYNC_HZ, 0u, SYNC_MS },
|
|
{ STEP_SILENCE, 0u, 0u, 0u, PAUSE_MS }
|
|
};
|
|
|
|
// One cycle of a sine, SFX_PERIOD samples, so a sound of it is rate / 8 Hz.
|
|
static const int8_t kSineCycle[SFX_PERIOD] = { 0, 71, SFX_AMPLITUDE, 71, 0, -71, -SFX_AMPLITUDE, -71 };
|
|
|
|
static EventT gEvents[EVENTS_MAX];
|
|
static uint16_t gEventCount = 0u;
|
|
static int8_t gSine[SFX_SAMPLES];
|
|
static int8_t gSineRaw[SFX_SAMPLES]; // gSine in the port's raw stream format
|
|
// The sines the steps play (stepSine); the fast one's samples are allocated
|
|
// at the start, as a static buffer that size would crowd the IIgs's bank.
|
|
static SineT gSineSlow = { gSine, SFX_SAMPLES, SFX_PERIOD };
|
|
static SineT gSineSlowRaw = { gSineRaw, SFX_SAMPLES, SFX_PERIOD };
|
|
static SineT gSineFast = { NULL, SFX_FAST_SAMPLES, SFX_FAST_PERIOD };
|
|
static StreamT gStream;
|
|
static uint8_t gRawFormat = JL_SFX_RAW_SIGNED8;
|
|
static uint32_t gStartMs = 0u;
|
|
static volatile uint32_t gTickCalls = 0u;
|
|
|
|
|
|
// A new event for step at ms; NULL once EVENTS_MAX are taken.
|
|
static EventT *eventAdd(uint32_t ms, const StepT *step) {
|
|
EventT *event;
|
|
|
|
if (gEventCount >= EVENTS_MAX) {
|
|
return NULL;
|
|
}
|
|
event = &gEvents[gEventCount++];
|
|
event->ms = ms;
|
|
event->kind = step->kind;
|
|
event->value = step->value;
|
|
event->channel = step->channel;
|
|
event->level = step->level;
|
|
event->count = 0u;
|
|
event->span = 0u;
|
|
event->ok = false;
|
|
event->modEnd = false;
|
|
return event;
|
|
}
|
|
|
|
|
|
// The log, once the timeline is over (the format in the header).
|
|
static void eventsLog(void) {
|
|
const EventT *event;
|
|
const SineT *sine;
|
|
uint16_t i;
|
|
|
|
for (i = 0u; i < gEventCount; i++) {
|
|
event = &gEvents[i];
|
|
switch (event->kind) {
|
|
case STEP_SYNC:
|
|
jlLogF("AUDPAR %lu sync %u", (unsigned long)event->ms, (unsigned)event->value);
|
|
break;
|
|
case STEP_TONE:
|
|
jlLogF("AUDPAR %lu tone %u", (unsigned long)event->ms, (unsigned)event->value);
|
|
break;
|
|
case STEP_VOICE:
|
|
jlLogF("AUDPAR %lu voice %u %u %u", (unsigned long)event->ms, (unsigned)event->channel, (unsigned)event->value, (unsigned)event->level);
|
|
break;
|
|
case STEP_NOISE:
|
|
jlLogF("AUDPAR %lu noise %u %u", (unsigned long)event->ms, (unsigned)event->value, (unsigned)event->level);
|
|
break;
|
|
case STEP_SFX:
|
|
case STEP_SFX_FAST:
|
|
sine = stepSine(event->kind);
|
|
jlLogF("AUDPAR %lu sfx %u %u %u %u %lu", (unsigned long)event->ms, (unsigned)SFX_SLOT, (unsigned)event->value, (unsigned)(event->value / sine->period), (unsigned)event->channel, (unsigned long)sine->count);
|
|
break;
|
|
case STEP_STREAM:
|
|
case STEP_STREAM_RAW:
|
|
case STEP_STREAM_FAST:
|
|
sine = stepSine(event->kind);
|
|
jlLogF("AUDPAR %lu stream %u %u %u %u %u %lu", (unsigned long)event->ms, (unsigned)SFX_SLOT, (unsigned)event->value, (unsigned)(event->value / sine->period), (unsigned)event->channel, (unsigned)(event->kind == STEP_STREAM_RAW), (unsigned long)sine->count);
|
|
break;
|
|
case STEP_MOD:
|
|
if (event->modEnd) {
|
|
jlLogF("AUDPAR %lu mod end %u", (unsigned long)event->ms, (unsigned)event->ok);
|
|
} else {
|
|
jlLogF("AUDPAR %lu mod start %lu", (unsigned long)event->ms, (unsigned long)event->count);
|
|
}
|
|
break;
|
|
case STEP_MOD_STOP:
|
|
jlLogF("AUDPAR %lu mod %s", (unsigned long)event->ms, event->modEnd ? "stop" : "loop");
|
|
break;
|
|
case STEP_TICK:
|
|
jlLogF("AUDPAR %lu tick %u %u %lu %lu", (unsigned long)event->ms, (unsigned)event->value, (unsigned)event->ok, (unsigned long)event->count, (unsigned long)event->span);
|
|
break;
|
|
case STEP_SILENCE:
|
|
jlLogF("AUDPAR %lu silence", (unsigned long)event->ms);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// DATA's module, whole; none (NULL, 0) when it is missing or too big.
|
|
static void loadMod(uint8_t **bytes, uint32_t *length) {
|
|
FILE *fp;
|
|
long size;
|
|
|
|
*bytes = NULL;
|
|
*length = 0u;
|
|
fp = jlDataOpen(TEST_MOD_NAME, "rb");
|
|
if (fp == NULL) {
|
|
return;
|
|
}
|
|
size = -1L;
|
|
if (fseek(fp, 0L, SEEK_END) == 0) {
|
|
size = ftell(fp);
|
|
}
|
|
if (size > 0L && (uint32_t)size <= DATA_FILE_MAX && fseek(fp, 0L, SEEK_SET) == 0) {
|
|
*bytes = (uint8_t *)malloc((size_t)size);
|
|
if (*bytes != NULL && fread(*bytes, 1, (size_t)size, fp) == (size_t)size) {
|
|
*length = (uint32_t)size;
|
|
}
|
|
}
|
|
fclose(fp);
|
|
}
|
|
|
|
|
|
// One frame: the engine's pump, the display's pace, and input. False when
|
|
// Escape asks to stop.
|
|
static bool pump(void) {
|
|
jlAudioFrameTick();
|
|
jlWaitVBL();
|
|
jlInputPoll();
|
|
return !jlKeyPressed(JL_KEY_ESCAPE);
|
|
}
|
|
|
|
|
|
// Each step's time is taken as it starts.
|
|
static void runStep(const StepT *step, const uint8_t *mod, uint32_t modLength) {
|
|
const SineT *sine;
|
|
uint32_t start;
|
|
EventT *event;
|
|
|
|
start = since();
|
|
event = eventAdd(start, step);
|
|
switch (step->kind) {
|
|
case STEP_SYNC:
|
|
case STEP_TONE:
|
|
jlAudioTone(step->value);
|
|
(void)waitMs(step->ms);
|
|
break;
|
|
case STEP_VOICE:
|
|
jlAudioVoice(step->channel, step->value, step->level);
|
|
(void)waitMs(step->ms);
|
|
break;
|
|
case STEP_NOISE:
|
|
jlAudioNoise((uint8_t)step->value, step->level);
|
|
(void)waitMs(step->ms);
|
|
break;
|
|
case STEP_SFX:
|
|
case STEP_SFX_FAST:
|
|
sine = stepSine(step->kind);
|
|
jlAudioSetPan(SFX_SLOT, (jlAudioPanE)step->channel);
|
|
jlAudioPlaySfx(SFX_SLOT, (const uint8_t *)sine->samples, sine->count, step->value);
|
|
(void)waitMs((uint16_t)(sine->count * MS_PER_SECOND / step->value));
|
|
break;
|
|
case STEP_STREAM:
|
|
case STEP_STREAM_RAW:
|
|
case STEP_STREAM_FAST:
|
|
jlAudioSetPan(SFX_SLOT, (jlAudioPanE)step->channel);
|
|
gStream.sine = stepSine(step->kind);
|
|
gStream.pos = 0u;
|
|
if (step->kind == STEP_STREAM_RAW) {
|
|
jlAudioPlaySfxStreamRaw(SFX_SLOT, streamFill, &gStream, step->value);
|
|
} else {
|
|
jlAudioPlaySfxStream(SFX_SLOT, streamFill, &gStream, step->value);
|
|
}
|
|
(void)waitMs((uint16_t)(gStream.sine->count * MS_PER_SECOND / step->value));
|
|
break;
|
|
case STEP_MOD:
|
|
if (mod != NULL) {
|
|
jlAudioPlayMod(mod, modLength, false);
|
|
}
|
|
if (event != NULL) {
|
|
event->count = modLength;
|
|
}
|
|
while (jlAudioIsPlayingMod() && since() - start < step->ms) {
|
|
if (!pump()) {
|
|
break;
|
|
}
|
|
}
|
|
event = eventAdd(since(), step);
|
|
if (event != NULL) {
|
|
event->modEnd = true;
|
|
event->ok = jlAudioIsPlayingMod();
|
|
}
|
|
jlAudioStopMod();
|
|
break;
|
|
case STEP_MOD_STOP:
|
|
if (mod != NULL) {
|
|
jlAudioPlayMod(mod, modLength, true);
|
|
}
|
|
(void)waitMs(step->ms);
|
|
event = eventAdd(since(), step);
|
|
if (event != NULL) {
|
|
event->modEnd = true;
|
|
}
|
|
jlAudioStopMod();
|
|
break;
|
|
case STEP_TICK:
|
|
gTickCalls = 0u;
|
|
if (event != NULL) {
|
|
event->ok = jlAudioTickRegister(tickCount, step->value);
|
|
}
|
|
(void)waitMs(step->ms);
|
|
(void)jlAudioTickRegister(NULL, 0u);
|
|
if (event != NULL) {
|
|
event->count = gTickCalls;
|
|
event->span = since() - start;
|
|
}
|
|
break;
|
|
case STEP_SILENCE:
|
|
silenceAll();
|
|
(void)waitMs(step->ms);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
static uint32_t since(void) {
|
|
return jlMillisElapsed() - gStartMs;
|
|
}
|
|
|
|
|
|
static void silenceAll(void) {
|
|
uint8_t voice;
|
|
|
|
for (voice = 0u; voice < JL_AUDIO_VOICES; voice++) {
|
|
jlAudioVoice(voice, 0u, ATTEN_SILENT);
|
|
}
|
|
jlAudioNoise(0u, ATTEN_SILENT);
|
|
jlAudioStopSfx(SFX_SLOT);
|
|
}
|
|
|
|
|
|
// The sine a sound step plays: the fast steps' (SFX_FAST_RATE), the raw
|
|
// stream's in the port's raw format, the rest the one-shots' signed bytes.
|
|
static const SineT *stepSine(StepKindE kind) {
|
|
if (kind == STEP_SFX_FAST || kind == STEP_STREAM_FAST) {
|
|
return &gSineFast;
|
|
}
|
|
return kind == STEP_STREAM_RAW ? &gSineSlowRaw : &gSineSlow;
|
|
}
|
|
|
|
|
|
// A stream step's fill: its sine, as stepSine chose it; 0 once it has all
|
|
// gone, which ends the stream. A copy, so the time the fill takes is the
|
|
// port's, not this program's.
|
|
static uint32_t streamFill(void *ctx, int8_t *dst, uint32_t count) {
|
|
StreamT *stream;
|
|
|
|
stream = (StreamT *)ctx;
|
|
if (count > stream->sine->count - stream->pos) {
|
|
count = stream->sine->count - stream->pos;
|
|
}
|
|
memcpy(dst, stream->sine->samples + stream->pos, count);
|
|
stream->pos += count;
|
|
return count;
|
|
}
|
|
|
|
|
|
static void tickCount(void) {
|
|
gTickCalls++;
|
|
}
|
|
|
|
|
|
static bool waitMs(uint16_t ms) {
|
|
uint32_t start;
|
|
|
|
start = since();
|
|
while (since() - start < ms) {
|
|
if (!pump()) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
int main(void) {
|
|
jlConfigT config;
|
|
uint8_t *mod;
|
|
int8_t *fast;
|
|
uint32_t modLength;
|
|
uint16_t i;
|
|
|
|
config.codegenBytes = 8 * 1024;
|
|
config.noAudio = false;
|
|
if (!jlInit(&config)) {
|
|
fprintf(stderr, "jlInit failed: %s\n", jlLastError());
|
|
return 1;
|
|
}
|
|
gRawFormat = jlAudioSfxRawFormat();
|
|
for (i = 0u; i < SFX_SAMPLES; i++) {
|
|
gSine[i] = kSineCycle[i % SFX_PERIOD];
|
|
gSineRaw[i] = gRawFormat == JL_SFX_RAW_UNSIGNED8_NOZERO ? (int8_t)(uint8_t)(gSine[i] + RAW_BIAS) : gSine[i];
|
|
}
|
|
// The fast sine: each cycle step of the slow one held for two samples.
|
|
fast = (int8_t *)malloc(SFX_FAST_SAMPLES);
|
|
if (fast == NULL) {
|
|
fprintf(stderr, "AUDPAR: no memory for the fast sine\n");
|
|
jlShutdown();
|
|
return 1;
|
|
}
|
|
for (i = 0u; i < SFX_FAST_SAMPLES; i++) {
|
|
fast[i] = kSineCycle[(i / (SFX_FAST_PERIOD / SFX_PERIOD)) % SFX_PERIOD];
|
|
}
|
|
gSineFast.samples = fast;
|
|
loadMod(&mod, &modLength);
|
|
jlSurfaceClear(jlStageGet(), 0u);
|
|
jlStagePresent();
|
|
|
|
gStartMs = jlMillisElapsed();
|
|
for (i = 0u; i < (uint16_t)(sizeof(kTimeline) / sizeof(kTimeline[0])); i++) {
|
|
runStep(&kTimeline[i], mod, modLength);
|
|
if (jlKeyDown(JL_KEY_ESCAPE)) {
|
|
break;
|
|
}
|
|
}
|
|
silenceAll();
|
|
eventsLog();
|
|
jlLogF("AUDPAR %lu done", (unsigned long)since());
|
|
jlLogFlush();
|
|
free(mod);
|
|
free(fast);
|
|
jlShutdown();
|
|
return 0;
|
|
}
|