Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,18 @@ All notable changes to EigenScript are documented here.

## [Unreleased]

### Fixed
- **gfx audio: `audio_play` / `audio_play_loop` accept `VAL_BUFFER`
samples (#578).** `audio_convert_samples` was list-only, so handing a
buffer — the type the language recommends for bulk samples and what a
DAW render produces — silently returned channel `0`; consumers had to
convert buffer→list per play (a 3-minute stereo render is ~16M
appends + VAL_NUM allocations) just for the C side to re-walk it into
int16. Buffers now convert directly off their C double array (same
clamping and 64 MB cap as lists); empty buffers are rejected like
empty lists. Gfx-suite checks cover play, play_loop, clamping, the
queued length, and the empty-buffer rejection.

### Added
- **`read_line` builtin (#558)** — blocking line read from stdin
(`getline(3)`): returns the next line without its trailing newline
Expand Down
25 changes: 22 additions & 3 deletions src/ext_gfx.c
Original file line number Diff line number Diff line change
Expand Up @@ -730,10 +730,29 @@ static void audio_mix_callback(void *ud, uint8_t *stream, int len) {
}
}

/* Convert a sample list (floats -1..1) to an owned int16 buffer.
* Returns NULL on bad shape or an over-64MB clip. */
/* Convert samples (floats -1..1) to an owned int16 buffer. Accepts a
* VAL_LIST or a VAL_BUFFER (#578 — the buffer is the type the language
* recommends for bulk samples and what a DAW render produces; forcing a
* buffer→list conversion per play meant millions of appends + VAL_NUM
* allocations just to feed the mixer). Returns NULL on bad shape or an
* over-64MB clip. */
static int16_t* audio_convert_samples(Value *samples, int *out_n) {
if (!samples || samples->type != VAL_LIST) return NULL;
if (!samples) return NULL;
if (samples->type == VAL_BUFFER) {
int n = samples->data.buffer.count;
if (n <= 0 || (double)n * sizeof(int16_t) > 64.0 * 1024.0 * 1024.0) return NULL;
int16_t *buf = xmalloc_array(n, sizeof(int16_t));
const double *src = samples->data.buffer.data;
for (int i = 0; i < n; i++) {
double s = src[i];
if (s > 1.0) s = 1.0;
if (s < -1.0) s = -1.0;
buf[i] = (int16_t)(s * 32767);
}
*out_n = n;
return buf;
}
if (samples->type != VAL_LIST) return NULL;
int n = samples->data.list.count;
if (n <= 0 || (double)n * sizeof(int16_t) > 64.0 * 1024.0 * 1024.0) return NULL;
int16_t *buf = xmalloc_array(n, sizeof(int16_t));
Expand Down
29 changes: 29 additions & 0 deletions tests/test_audio.eigs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,35 @@ if d != 0:
assert_eq of [audio_stop of chinf, 0, "stop on an inactive channel returns 0"]
audio_clear of null
assert_eq of [audio_queue_size of null, 0, "clear empties every channel"]

# ---- #578: VAL_BUFFER samples accepted (the bulk fast path) ----
# A DAW render produces C-backed double buffers; audio_play used to
# silently return channel 0 for them, forcing a buffer->list convert
# per play. Buffer and list of the same samples must both play.
nb is 1000
bclip is buffer of nb
bi is 0
loop while bi < nb:
bclip[bi] is 0.1
bi += 1
chb is audio_play of bclip
assert_eq of [(chb > 0), 1, "audio_play accepts a buffer (#578)"]
assert_eq of [audio_queue_size of null, nb * 2, "buffer clip queued at full length"]
chbl is audio_play_loop of [bclip, 3]
assert_eq of [(chbl > 0), 1, "audio_play_loop accepts a buffer (#578)"]
assert_eq of [audio_stop of chbl, 1, "buffer loop channel stoppable"]
# Buffer values are clamped like list values (out-of-range -> +/-1).
hot is buffer of 100
hi is 0
loop while hi < 100:
hot[hi] is 9.5
hi += 1
chh is audio_play of hot
assert_eq of [(chh > 0), 1, "out-of-range buffer samples clamp, still play"]
# Empty buffer is still rejected (0), matching the empty-list rule.
assert_eq of [audio_play of (buffer of 0), 0, "empty buffer rejected"]

audio_clear of null
audio_close of null
print of "mixer channel tests: ok"
else:
Expand Down
Loading