#Jamcorder app library export

This document specifies the library backup created by Export Library in the Jamcorder app. The exported file is a ZIP archive named jamcorder-app-export.zip. It contains the app's downloaded Jamcorder recordings, saved clips, and albums in their native on-disk representation.

Purpose: give people full control over their MIDI data, so they can access it outside the Jamcorder app and use it however they choose. This also enables custom export and restore workflows, such as creating custom libraries, excluding unwanted data, or otherwise controlling what is restored.

Scope: app settings are intentionally not specified here. Current app builds may include a settings/ subtree, except for settings/soundfonts/. Consumers of the library format should ignore that subtree.

#Contents

#Format overview

A representative archive has this layout:

jamcorder-app-export.zip
├── JAMC/
│   └── <year>/
│       └── <jamcorderUuid>/
│           └── Jmx-A<assetIdx>-<Mon>-<DD>-<YYYY>.mid/
│               ├── <stoneIdx>.stone.mid.gz
│               ├── <stoneIdx>.stone.mid.gz.completed.json
│               └── completed.json
├── clips/
│   └── <year>/
│       └── JClip-<unixtime>-<offset>-<clipUuid>-<Mon>-<DD>-<YYYY>/
│           └── clip.stone.mid.gz
├── albums/
│   └── <albumUuid>/
│       ├── info.json
│       └── clips/
│           └── JClip-<unixtime>-<offset>-<clipUuid>-<Mon>-<DD>-<YYYY>.json
└── settings/                 out of scope; may be present

All paths are relative to the ZIP root. There is no enclosing directory inside the archive. The .mid item below JAMC/ is a directory containing separately compressed stone files; it is not itself a MIDI file.

Top-level pathPurposeRequired
JAMC/Downloaded continuous recordings, divided into JMX stones.No; absent when no recordings have been downloaded.
clips/User-created and imported MIDI clips.No; absent when no clips have been saved.
albums/Album metadata and clip-membership markers.No; absent when no albums have been created.
settings/App state outside this specification.No. Readers of this specification should ignore it.

#ZIP container

ZIP CRC values protect individual entries against accidental corruption, but they are not an authenticity mechanism. Treat archive paths and decompressed sizes as untrusted input.

#Downloaded recordings

The JAMC/ tree mirrors Jamcorder recording identity while storing each downloaded stone as a separate gzip-compressed MIDI file:

JAMC/<year>/<jamcorderUuid>/Jmx-A<assetIdx>-<Mon>-<DD>-<YYYY>.mid/<stoneIdx>.stone.mid.gz

Example:

JAMC/2025/60b2d69c-333d-9b33-6c3b-9b38886c102d/
  Jmx-A00074-Apr-24-2025.mid/3.stone.mid.gz

#Recording path components

ComponentMeaning
<year>Four-digit year of the asset's local calendar date.
<jamcorderUuid>36-character Jamcorder UUID in five hyphen-separated groups.
<assetIdx>Device-local recording index. It is normally zero-padded to at least five decimal digits after A.
<Mon>-<DD>-<YYYY>English abbreviated month, two-digit day, and four-digit local year, such as Apr-24-2025.
<stoneIdx>Decimal stone index within the recording, normally beginning at 1.

The year directory and date in the asset directory name are derived from days since the Unix epoch at UTC midnight. The actual wall-clock anchors and local offsets are stored inside each JMX stone and should be authoritative for musical-event time.

#Stone payload

<stoneIdx>.stone.mid.gz is an RFC 1952 gzip stream. Decompression yields a complete format-0 Standard MIDI File:

The JMX header carries the asset, device, stone, time, position, tempo, sound, lifetime-statistics, firmware, fabrication, and optional clip fields. See the JMX MIDI file specification for its event framing and complete field catalog.

#Completion markers

Downloaded recordings can have two kinds of sidecar marker:

PathPayloadMeaning
<stoneIdx>.stone.mid.gz.completed.json{"completed":true}The app considers that stone fully downloaded and immutable.
completed.json{"completed":true}The app considers the entire asset fully downloaded.

These files are presence markers. Current app code checks whether they exist; it does not parse their JSON value. A missing marker means only that completeness is not established. It does not by itself prove that the associated MIDI bytes are invalid.

#Saved clips

Each saved clip occupies one directory and one payload file:

clips/<year>/JClip-<unixtime>-<offset>-<clipUuid>-<Mon>-<DD>-<YYYY>/clip.stone.mid.gz

Example:

clips/2024/
  JClip-1707346233-N600-cc9d75fc-50d3-4ea5-93b9-fd1459fb1363-Feb-07-2024/
    clip.stone.mid.gz
ComponentMeaning
<unixtime>Clip creation time as whole seconds since 1970-01-01 00:00:00 UTC.
<offset>Local offset from UTC in minutes, encoded as P<minutes> for zero or positive and N<minutes> for negative. Examples: P0, P330, N600.
<clipUuid>36-character clip UUID. App-created clips currently begin with cc; imported clips currently begin with im.
<Mon>-<DD>-<YYYY>Local calendar date computed from unixtime plus offset.
<year>The same local year printed in the clip directory name.

#Clip payload

clip.stone.mid.gz has the same gzip-to-format-0-SMF envelope as a recording stone. Its first event is jmxStoneHdr, whose clip object provides the clip-specific metadata:

{
  "clip": {
    "clipUuid": "cc9d75fc-50d3-4ea5-93b9-fd1459fb1363",
    "unixtime": 1707346233,
    "localOffset": -600,
    "title": "Late-night idea"
  }
}

The example shows only the clip member of the much larger jmxStoneHdr object. The directory identity and embedded values should agree. The title exists only in the embedded header; there is no separate clip metadata JSON file.

#Albums

An album is stored under its UUID:

albums/<albumUuid>/info.json
albums/<albumUuid>/clips/<clipName>.json

albumUuid is a 36-character hexadecimal UUID; UUIDs generated by current app builds begin with aa. The built-in Recents view is virtual and is not exported as an album.

#info.json

{
  "albumUuid": "aa1077cb-7f94-f663-49dd-c2337374abfb",
  "title": "Rehearsal ideas",
  "creationTime": {
    "unixtime": 1745510400,
    "localOffset": -300
  },
  "lastModified": {
    "unixtime": 1745596800,
    "localOffset": -300
  },
  "clipCount": 2
}
FieldTypeMeaning
albumUuidstringAlbum identity. It should equal the enclosing directory name.
titlestringUser-visible album title.
creationTimeobjectAlbum creation time as unixtime seconds and localOffset minutes.
lastModifiedobjectMost recent album edit in the same time representation.
clipCountintegerCached membership count. Readers should verify it against valid marker files.

#Clip membership

Every file in albums/<albumUuid>/clips/ is an empty marker. Its basename is a complete clip directory name plus .json:

JClip-1707346233-N600-cc9d75fc-50d3-4ea5-93b9-fd1459fb1363-Feb-07-2024.json

Despite the extension, the marker has no JSON payload; current writers create a zero-byte file. Membership is encoded entirely by the filename. The corresponding clip payload lives under clips/. Albums do not contain copies or links to MIDI data, and the same clip can appear in any number of albums.

Readers should ignore malformed marker names and should handle dangling markers whose referenced clip is absent. Album order is not stored. Clip membership is presented newest first by sorting the creation timestamps parsed from marker filenames.

#Time and identifiers

The app represents a wall-clock value with two integers:

{
  "unixtime": 1745510400,
  "localOffset": -300
}

Asset indexes and stone indexes provide device-local ordering. Stable identity comes from UUID fields embedded in JMX data and from the Jamcorder, clip, and album UUIDs. Do not assume that an asset index or filename is globally unique without its Jamcorder UUID.

#Import and merge behavior

The app normalizes ZIP entry separators to /, rejects empty and parent-traversing paths, and recognizes four top-level names case-insensitively: JAMC, clips, albums, and settings. Unknown top-level entries are ignored.

TreeCurrent merge rule
JAMC/Imported files do not overwrite files already present at the same path.
clips/Imported files do not overwrite files already present at the same path.
albums/An archive album is intended to be skipped when the same album UUID directory already exists, preventing partial merges with an existing album.
settings/Out of scope. Current imports consider it only when the destination has no settings directory.

After extraction, the app rescans recordings, albums, and clips from disk. A library archive is therefore a merge source rather than a transactional image of the destination documents directory. Import does not remove destination content that is absent from the archive.

#Reader requirements

A robust reader or importer should:

  1. Enforce limits on compressed size, decompressed size, entry count, path length, JSON size, and MIDI event lengths before allocating or extracting.
  2. Normalize both slash styles, reject absolute paths, drive prefixes, parent traversal, symlinks, and any output path that escapes the chosen destination.
  3. Compare top-level path names case-insensitively, but preserve and validate the documented case of names below them.
  4. Ignore unknown top-level trees, unknown sidecars, unknown JSON fields, and the out-of-scope settings/ subtree.
  5. Gunzip every .stone.mid.gz independently and validate the resulting SMF and JMX structure before trusting embedded offsets, lengths, times, or identifiers.
  6. Cross-check path identity against embedded JMX fields and resolve conflicts explicitly. Do not silently use a path's UUID with another payload's metadata.
  7. Treat completion JSON as a presence marker, not proof that an entry is complete, authentic, or uncorrupted.
  8. Parse album JSON by field name, not property order, and tolerate additional fields for forward compatibility.
  9. Derive album membership from valid marker filenames, verify referenced clips exist, and treat clipCount as cached advisory data.
  10. Preserve unknown valid files when performing a lossless round trip, because the archive has no manifest declaring an exhaustive schema.