Skip to content

GSND Files

github-actions[bot] edited this page Oct 15, 2026 · 3 revisions

GSND Files

Import into an open document

Choose Import definition… in the Definition toolbar and browse to an existing file of the same format. Legacy singleton files import directly. For a multi-definition file, check the members to import; Select all and Select none adjust the selection. Canceling or importing no members leaves the current document unchanged.

Imported members are independent copies appended to the current document. Existing names are retained when available; conflicts receive -2, -3, and subsequent suffixes. The first imported member becomes active and the destination becomes dirty. Save and Save As retain both existing and imported members; the source file is never changed.

Relative filesystem references, including GAF paths, are rebased to keep pointing at their original resources. Archive entry names and logical dependency selectors are retained. Resources are not copied or moved. Import does not rewrite logical references when a root name conflicts, or resolve collisions among inner sprite IDs, resource keys, or scene layer IDs. Review those relationships before runtime materialization. Incomplete authoring drafts can be imported and saved using the existing draft workflow; malformed document envelopes and duplicate source root identities are rejected.

Definition documents and logical members

One physical .gsnd file contains a document object with a Definitions array. Every member has a unique, non-empty, case-sensitive Key. The document location (loose path, or GAF path plus typed entry name) is independent of that logical identity.

{
  "Definitions": [
    { "Key": "first" },
    { "Key": "second" }
  ]
}

This illustrates the envelope and identities; supply the normal format-specific fields shown below for complete definitions. The same envelope is stored as the bytes of a single AssetTypes.AudioDefinition GAF entry. GAF remains a typed binary container; loading or saving a document does not create entries for individual members.

var selected = AudioDefinitionSerializer.Load("content.gsnd", "second");
var definitions = AudioDefinitionSerializer.LoadAll("content.gsnd");
var document = AudioDefinitionSerializer.LoadDocument("content.gsnd");
AudioDefinitionSerializer.SaveDocument("copy.gsnd", document);
AudioDefinitionSerializer.SaveAll("copy.gsnd", definitions);

var packed = AudioDefinitionSerializer.Load(assetsFile, "content.gsnd", "second");
var packedMembers = AudioDefinitionSerializer.LoadAll(assetsFile, "content.gsnd");

LoadDocument, LoadAll, and member-selecting Load also accept readable streams and leave them open. Anonymous streams do not claim physical-source provenance. ValidateDocument reports structural and per-member validation errors, qualified by logical identity. Parsing rejects null member lists, null members, duplicate identities, and mixed singleton/envelope objects. Authoring drafts may be structurally valid but incomplete; runtime materialization runs the format validator.

Existing singleton JSON without an envelope continues to load. Load(path) and other singleton convenience APIs require exactly one member and fail clearly on empty or multi-member documents. Save(path, definition) writes a canonical one-member envelope. ToJson(definition) retains the single-definition JSON projection for existing callers; use ToDocumentJson(document) for canonical document JSON. Singleton JSON examples below remain supported and can also be placed inside the envelope.

The editor's Definition toolbar selects, adds, duplicates, removes, and renames roots. Saving writes every member, and Save As rebases references for every member without modifying unrelated content. At least one member remains in an editable session.

Legacy roots without Key infer it from the filename or packed entry basename. Anonymous legacy JSON uses audio; canonical document members must explicitly provide a key.

Use AudioDefinitionSerializer.LoadAllIntoManager to materialize every member.

Load-all operations roll back newly created runtime content if a member fails. Physical/member separation supports future packaging work; content relocation is not implemented here.

.gsnd is Gondwana's standalone audio-definition format.

A GSND file describes which audio resources exist, where their media comes from, and their portable playback settings. It does not contain a backend playback handle, current play position, device state, or other runtime-only audio state.

GSND is to AudioResourceManager roughly what GTS is to tilesheets and GANI is to animation cycles: a clean authoring definition that can be stored on its own, embedded in EngineState, or loaded into the runtime registry.


What a GSND definition contains

Each logical member of AudioDefinitionDocument.Definitions has type:

Gondwana.Audio.GSND.AudioDefinition

It contains an ordered collection of:

AudioResourceDefinition

Each resource defines:

Property Purpose
Key Unique key used by AudioResourceManager
SourceKind LooseFile, PackedAsset, or Uri
FilePath Loose audio path
AssetsFilePath GAF path for packed audio
AssetEntryName Audio entry inside the GAF
SourceUri URI for URI-backed audio
SourceExtension Optional format hint such as .wav or .ogg
Volume Initial volume, from 0.0 through 1.0
Pan Initial stereo pan, from -1.0 through 1.0
PlaybackSpeed Initial playback rate, currently 0.25x through 4.0x
IsLooping Whether playback loops

A simple loose-file definition looks like:

{
  "Resources": [
    {
      "Key": "theme",
      "SourceKind": "LooseFile",
      "FilePath": "audio/theme.ogg",
      "SourceExtension": ".ogg",
      "Volume": 0.8,
      "Pan": 0.0,
      "PlaybackSpeed": 1.0,
      "IsLooping": true
    }
  ]
}

The exact JSON is produced by AudioDefinitionSerializer; applications should prefer the serializer rather than hand-assembling format-specific JSON.


Source kinds

Loose files

A loose resource points at an ordinary audio file:

{
  "Key": "laser",
  "SourceKind": "LooseFile",
  "FilePath": "audio/laser.wav"
}

When a GSND file is saved, loose file paths are made relative to the GSND file whenever the source and destination share a filesystem root. Save As rebases the reference against the new GSND location.

When the definition is loaded, the path is resolved relative to the GSND file.

Packed GAF audio

A resource can point at an Audio entry inside a GAF:

{
  "Key": "explosion",
  "SourceKind": "PackedAsset",
  "AssetsFilePath": "content/audio.gaf",
  "AssetEntryName": "explosion.wav"
}

The referenced GAF must already be loaded when the GSND definition is materialized directly. EngineState handles this dependency by restoring AssetsFiles before Audio.

If an asset entry name has no useful extension, SourceExtension supplies the format hint needed by byte/stream audio backends.

GSND definition files themselves can also be packed into a GAF using:

AssetTypes.AudioDefinition

URIs

URI-backed audio uses:

{
  "Key": "browser-theme",
  "SourceKind": "Uri",
  "SourceUri": "assets/theme.mp3"
}

Whether a URI is actually playable depends on the configured audio backend. For example, browser audio is naturally suited to URI sources.


Validation

Use:

var errors =
    AudioDefinitionValidator.Validate(definition);

Validation checks authoring data without creating playback handles.

It currently verifies:

  • non-empty, unique resource keys
  • valid source kinds
  • required source metadata
  • volume and pan ranges
  • finite, supported playback-speed values
  • a usable extension hint for packed audio whose entry name has no extension

Validation deliberately does not prove that a referenced file, GAF entry, URI, decoder, output device, or backend will be available at runtime.


Loading and saving

Load a loose GSND file:

var definition =
    AudioDefinitionSerializer.Load("content/audio.gsnd");

Save one:

AudioDefinitionSerializer.Save(
    "content/audio.gsnd",
    definition);

Capture the currently registered audio resources:

var definition =
    AudioDefinitionSerializer.FromManager(
        AudioResourceManager.Instance);

Or save the manager directly:

AudioDefinitionSerializer.Save(
    "content/audio.gsnd",
    AudioResourceManager.Instance);

Materializing runtime audio

Editing or deserializing GSND does not require an audio backend.

Creating live AudioResource objects does.

After configuring the appropriate backend:

AudioDefinitionSerializer.LoadIntoManager(
    "content/audio.gsnd");

The serializer recreates each resource in AudioResourceManager, applies its volume, pan, playback speed, and looping settings, and preserves packed-asset identity when applicable.

The default behavior replaces an existing resource with the same key. Pass:

overwriteExisting: false

to preserve an existing resource while applying the incoming portable settings.

GSND does not persist current playback position or whether a sound happened to be playing or paused at the instant the definition was captured.


EngineState integration

New EngineState saves persist audio through the GSND definition layer instead of serializing runtime AudioResource objects directly.

The default is inline:

state.SaveToFile(
    "game.state",
    parts: EngineStateParts.Audio);

To externalize audio:

state.SaveToFile(
    "game.state",
    parts: EngineStateParts.Audio,
    separateGsndFile: true);

For game.state, the external layout is:

game.state
game.audio/
    audio.gsnd

EngineState restores asset files before GSND audio, so packed GAF references can resolve correctly.

Older EngineState files that contain the pre-GSND SoundResources representation remain readable. New saves use GSND.

See Serialization and EngineState for the full snapshot and dependency model.


WinForms tooling

The standalone editor is:

Tooling/Gondwana.Tooling.Audio.WinForms

Run it from the repository root:

dotnet run --project Tooling/Gondwana.Tooling.Audio.WinForms -c Release

The editor is definition-driven and does not start a GameHost or require an audio backend just to edit a file.

Its hostable AudioEditorControl contains editor-local dockable panes for:

  • Audio resources
  • Properties
  • Validation

The standalone application's Working directory browser and each complete GSND editor remain outer DockPanelSuite windows/documents. This is the same composition pattern used by the current GTS, GAF, and GANI tooling and is intended for later reuse by Gondwana Studio.

Layout persistence is not currently implemented.


Runtime versus definition data

A useful distinction is:

GSND definition
    |
    +-- source identity
    +-- resource key
    +-- volume / pan
    +-- playback speed
    +-- looping

runtime AudioResource
    |
    +-- all of the above
    +-- backend playback handle
    +-- current position
    +-- duration
    +-- playing / paused / stopped state
    +-- playback-completion events

GSND deliberately describes reproducible content rather than a live audio-device snapshot.


Where to read next

Preserving definition origins in EngineState

EngineStateSaveOptions supports DefinitionPersistence.PreserveSource for this format. A definition loaded from a named GAF can retain AssetsFilePath and AssetEntryName; a loose definition can retain its path. EngineState compares current serializable state with its loaded baseline and falls back when it changed. Packed loads use AssetsFile.SourcePath, including an explicitly supplied stream origin; anonymous archives cannot produce path-based provenance. See Definition persistence options for collection ownership, generated identities, relative paths, and safe fallback.

Gondwana Wiki

🔎 Search the Gondwana Wiki

Introduction

Core Concepts
Systems
Rendering
Widgets
Studio & Desktop Tools
Advanced Topics

Reference

Clone this wiki locally