Skip to content

jsonunmarshalfile

Reads a JSON file into a declared user-defined type or a one-dimensional typed array.

The opcode opens a text file through Csound's file handling, reads one complete JSON document, and closes the file. It uses the same type mappings, options and validation as jsonunmarshal.

Syntax

value:Type = jsonunmarshalfile(Spath [, iflags [, imaxdepth]])
values:T[] = jsonunmarshalfile(Spath [, iflags [, imaxdepth]])
value:Type jsonunmarshalfile Spath [, iflags [, imaxdepth]]
values:T[] jsonunmarshalfile Spath [, iflags [, imaxdepth]]

Type stands for a declared UDT. T stands for i, k, b, B, S, or a supported UDT. The file's root must match the destination: an object for a UDT or an array for a typed array.

Initialization

Spath is the filename or path. Csound opens it read-only. It first tries a relative filename in the current directory, then uses its search paths from INCDIR, SSDIR and SFDIR. An absolute path selects a specific file. See Environment Variables for path setup. The JSON opcode does not add a search path of its own.

For example, keep settings.json in a directory named presets and add that directory to INCDIR:

csound --env:INCDIR+=presets piece.csd

Then read it as jsonunmarshalfile("settings.json") into a declared destination type. Csound can also add paths from the CSD location under its normal command-line rules; an explicit path or configured search directory avoids relying on a front end's working directory.

value or values receives the decoded value. Arrays take their length from the file. On a successful read, the opcode replaces the whole destination. On a failed read, it leaves the destination unchanged and reports an initialization error.

iflags defaults to 0 for strict JSON. Set 1 to allow comments, 2 to allow trailing commas, or 3 for both. The file extension does not change the parser: a .jsonc file still needs the appropriate flags.

imaxdepth defaults to 0, which selects a limit of 256 nested JSON containers. Set an integer from 1 to 256 for an explicit limit. The root object or array counts as one, and every nested object or array adds one. Supply iflags before imaxdepth, even when the flags are zero. All options must be finite integers in these ranges.

Errors and host filesystems

A missing or unreadable file causes a cannot open file initialization error. An empty file, malformed JSON or extra text after the document causes a parse error with a byte position. The file must hold a single JSON document; a stream of separate objects, such as JSON Lines, is not supported.

Every UDT member must occur exactly once with the right type. Unknown fields, missing fields, duplicates, null, invalid strings, unsupported types and excessive nesting cause an error. File input has no looser type rules than string input. See type mappings and fault handling for the full rules.

In a browser or an embedded host, the file must exist in the filesystem that the host exposes to Csound. The opcode does not fetch an HTTP URL or read an arbitrary file on the user's computer. Put the file in the host's virtual filesystem first, or pass text that the host already has to jsonunmarshal.

Performance

The opcode runs once at initialization. It reads and parses the whole file and allocates memory; it does not stream notes or watch the file for changes. File I/O and JSON conversion are not safe for a real-time audio callback. Load before live playback or use an offline render.

Examples

Read a score with a UDO

Download both jsonunmarshalfile.csd and jsonunmarshalfile-score.json into the same directory. Run Csound from that directory:

csound jsonunmarshalfile.csd

The example writes json-score.wav. It prints A short JSON score: 4 notes, 2.50 seconds and allows another 0.1 seconds before ending the performance.

jsonunmarshalfile-score.json
{
  "title": "A short JSON score",
  "tempo": 120,
  "notes": [
    {"start": 0, "duration": 0.8, "pitch": 60, "amplitude": 0.15, "pan": 0.2},
    {"start": 1, "duration": 0.8, "pitch": 64, "amplitude": 0.15, "pan": 0.4},
    {"start": 2, "duration": 0.8, "pitch": 67, "amplitude": 0.15, "pan": 0.6},
    {"start": 3, "duration": 2, "pitch": 72, "amplitude": 0.15, "pan": 0.8}
  ]
}
A UDO that reads and schedules a JSON score
<CsoundSynthesizer>
<CsOptions>
; Render to a file so loading never interrupts live audio.
-o json-score.wav -W -d -m0
</CsOptions>
<CsInstruments>
sr = 48000
ksmps = 32
nchnls = 2
0dbfs = 1

struct JsonNote start:i, duration:i, pitch:i, amplitude:i, pan:i
struct JsonScore title:S, tempo:i, notes:JsonNote[]

instr JsonTone
  envelope:a = linseg(0, p3 * 0.1, p5, p3 * 0.8, p5, p3 * 0.1, 0)
  tone:a = poscil(envelope, cpsmidinn(p4))
  left:a, right:a = pan2(tone, p6)
  out(left, right)
endin

; Return the score length in seconds. Starts and durations in JSON use beats.
opcode PlayJsonScore(path:S):(i)
  score:JsonScore = jsonunmarshalfile(path)
  if score.tempo <= 0 then
    prints("Score tempo must be greater than zero.\n")
    exitnow(1)
  endif
  secondsPerBeat:i = 60 / score.tempo
  count:i = lenarray(score.notes)
  endTime:i = 0

  if count > 0 then
    ; JSON checks types. This pass checks the musical limits before any scheduling.
    for note, index in score.notes do
      if note.start < 0 || note.duration <= 0 || \
         note.pitch < 0 || note.pitch > 127 || \
         note.amplitude < 0 || note.amplitude > 0.25 || \
         note.pan < 0 || note.pan > 1 then
        prints("Invalid score.notes[%d]: check time, pitch, amplitude and pan.\n", index)
        exitnow(1)
      endif
      endTime = max(endTime, (note.start + note.duration) * secondsPerBeat)
    od

    for note in score.notes do
      schedule(JsonTone, note.start * secondsPerBeat, \
               note.duration * secondsPerBeat, note.pitch, note.amplitude, note.pan)
    od
  endif
  prints("%s: %d notes, %.2f seconds\n", score.title, count, endTime)
  xout(endTime)
endop

instr LoadScore
  duration:i = PlayJsonScore("jsonunmarshalfile-score.json")
  ; Keep the performance running until the last scheduled note ends.
  eventi("e", 0, duration + 0.1)
endin
</CsInstruments>
<CsScore>
i "LoadScore" 0 0.01
f 0 z
</CsScore>
</CsoundSynthesizer>

PlayJsonScore reads the score, checks its musical values, then schedules the notes with schedule. It passes JsonTone without quotes, using the named instrument's InstrDef reference. JSON starts and durations use beats; the UDO converts them to seconds with 60 / tempo. Pitches use MIDI note numbers, amplitudes use the example's 0dbfs = 1, and pan runs from 0 (left) to 1 (right). The amplitude limit of 0.25 is a choice made by this UDO, not a JSON restriction; many overlapping notes can still sum above full scale.

Both passes use Csound 7 for loops over the note array. for note, index in score.notes do supplies each note and its zero-based index for validation messages. for note in score.notes do supplies each note for scheduling. These loops run at initialization for this array of structs.

The UDO checks all notes before scheduling any. It rejects non-positive tempo or duration, negative starts, out-of-range MIDI pitches, and amplitudes or pan positions outside its chosen limits. JSON type checks alone cannot enforce those musical rules. The UDO sends all notes to the fixed JsonTone instrument and returns the last note's end time in seconds. LoadScore uses that value to send an end-of-score event with eventi, so changing the score length does not require changing <CsScore>.

An empty notes array schedules no notes. For another score format, change the struct declarations and the UDO's checks together. To accept comments or trailing commas in a file, pass the chosen flags to the UDO's jsonunmarshalfile call.

See also

jsonunmarshal, jsonmarshal, JSON data, User Defined Opcodes, schedule, for, eventi, File Input and Output

Availability

New in Csound 7.