Skip to content

feat: export grain IR as JSON for visualization clients #73

@DMGiulioRomano

Description

@DMGiulioRomano

Context

The Grain IR (onset, duration, pointer_pos, pitch_ratio, volume, pan) is fully computed in stream.voices after generate_grains() but never persisted as a structured format — only as Csound .sco lines or accumulated into audio buffers. A visualization client (PGE-ui) needs per-grain data to draw grain rectangles inside each stream's timeline clip.

Proposed change

Add a --grain-json CLI flag (only active with --per-stream) that writes:

cache/<yaml_basename>__<stream_id>_grains.json

JSON schema:

{
  "stream_id": "stream1",
  "duration": 8.0,
  "num_voices": 4,
  "grains": [
    {"t": 0.000, "dur": 0.08, "vol": -6.0, "ptr": 0.34, "v": 0},
    ...
  ]
}
  • t = onset relative to stream start (grain.onset - stream.onset)
  • v = voice index
  • Flat array sorted by t, compact JSON (no whitespace)

Implementation

New file: src/export/grain_json_writer.py

  • Class GrainJsonWriter with write(stream, output_dir, yaml_basename) -> Path
  • Follows pattern of src/export/reaper_project_writer.py

Modified: src/main.py

  • Add --grain-json argparse flag
  • After create_elements(), before engine.render(): iterate generator.streams and call writer.write()

New test: tests/export/test_grain_json_writer.py

  • Follows pattern of tests/export/test_reaper_project_writer.py

Notes

  • Writing happens for all streams every run (grains are always recomputed anyway)
  • pointer_pos units may vary per stream — document in schema
  • Voice onset offsets can produce t < 0 grains — valid data, consumers must handle

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions