Audio AI
The client half of Field Audio: a separate HTTP/WebSocket service that runs generative and analysis models over audio, and the typed vocabulary a box talks to it in. Nothing here loads a model or does inference in the JVM — a box builds a ModelOperation, submits it through FieldAudioClient, and gets back a job it can poll or watch; the results are immutable server-side assets referenced by id, which is why the protocol types carry representation ids rather than samples. Every call is asynchronous and completes off Field's runloop and off the audio callback, so nothing here may be .get()-ed from a box's frame. The rest of the category is what you do with a result inside a box: the candidate browser, the EnCodec reconstruction microscope, the beat/pitch analysis viewer and the job monitor, each of them one function returning a self-contained HTML fragment for display(…). Start from Protocol.kt for the vocabulary and FieldAudioClient.kt for the verbs; Field Audio service reference is the service-side contract the types mirror.
22 source file(s), 586 public symbol(s), 586 of them carrying KDoc. Everything below is the documentation that lives beside the code — the same text the completion documentation panel shows in the editor. A blank summary means the declaration has no KDoc yet, not that this page dropped it.
AnalysisOperations.kt
Typed constructors for model-free musical analysis and learned speech representations.
Each one is a ModelOperation factory and nothing more — it validates its arguments here in the
JVM, names the backend that serves it, and packs the parameters into the snake_case keys that
backend expects. Submitting it, waiting for it and drawing it are FieldAudioClient's job.
Every constructor asks for a single candidate, because an analysis of a fixed window with fixed parameters
has one answer; the seed family in styleCandidates has no counterpart here. All three also
produce an .npz of full-resolution arrays plus, for a drawing, a downsampled
AnalysisPreview — see analysisVisualizationHtml.
Which to reach for: multiScaleBeatAnalysis when you want when, and at several time scales at
once, because a tempo estimate is only meaningful relative to a window length;
monophonicPitchAnalysis when you know there is one voice and want a continuous F0 with a voiced
probability; polyphonicPitchAnalysis when there are chords and you want discrete note events
(and MIDI) rather than a curve.
src/jvmMain/kotlin/afield/audio/ai/AnalysisOperations.kt — read the source
Used in: Multi-instrument transcription with MuScriptor, Visualizers, Displays, and Little Instruments, Beat and pitch analysis
Top-level functions
MuscriptorInstrument
enum class MuscriptorInstrument(val id: String)
Instrument groups learned by MuScriptor 0.3.0 and accepted as hard transcription constraints.
Constants: ACOUSTIC_PIANO, ELECTRIC_PIANO, CHROMATIC_PERCUSSION, ORGAN, ACOUSTIC_GUITAR, CLEAN_ELECTRIC_GUITAR, DISTORTED_ELECTRIC_GUITAR, ACOUSTIC_BASS, ELECTRIC_BASS, VIOLIN, VIOLA, CELLO, CONTRABASS, ORCHESTRAL_HARP, TIMPANI, STRING_ENSEMBLE, SYNTH_STRINGS, VOICE, ORCHESTRA_HIT, TRUMPET, TROMBONE, TUBA, FRENCH_HORN, BRASS_SECTION, SOPRANO_AND_ALTO_SAX, TENOR_SAX, BARITONE_SAX, OBOE, ENGLISH_HORN, BASSOON, CLARINET, FLUTES, SYNTH_LEAD, SYNTH_PAD, DRUMS.
MuscriptorBackend
enum class MuscriptorBackend(val modelId: String)
Revision-pinned MuScriptor model exposed behind the model-neutral music-transcription operation.
Constants: SMALL, MEDIUM, LARGE.
MuscriptorTempoDetection
enum class MuscriptorTempoDetection
What MuScriptor should do when deriving MIDI tempo and meter from the selected audio.
Constants: REQUIRED, BEST_EFFORT, OFF.
SpeechEmbeddingBackend
enum class SpeechEmbeddingBackend(val modelId: String)
The two revision-pinned learned speech spaces exposed behind one model-neutral operation.
Constants: EMOTION2VEC, WAVLM_BASE_PLUS.
AnalysisVisualization.kt
Analysis viewers and dashboards for beat, pitch, and note results, with zoom, pan, and a shared time cursor.
analysisVisualizationHtml turns an AnalysisResult into a zoomable interactive display; the
browser decides which of the three layouts to draw from AnalysisPreview.operation: onset
strength and multi-scale tempo for a beat analysis, pYIN frequency and voicing probability for
a monophonic pitch analysis, or a Basic Pitch piano roll with activation curves for a
polyphonic one. analysisPreviewVisualizationHtml takes an already-fetched AnalysisPreview
and works without any service round trip.
Dashboard variants (analysisDashboardHtml, analysisPreviewDashboardHtml) stack panels with
a synchronized cursor, making beat/pYIN/Basic Pitch disagreements inspectable side by side.
All panels support wheel zoom, drag pan, hover inspection, and click-to-seek, synchronized
across panels that share a syncGroup.
src/jvmMain/kotlin/afield/audio/ai/AnalysisVisualization.kt — read the source
Used in: Visualizers, Displays, and Little Instruments, Multi-instrument transcription with MuScriptor
Top-level functions
AudioAssetVisualization.kt
The one-line bridge from a Field Audio AssetRepresentation to Field's general-purpose waveform
player.
It exists so that playing a service asset needs no URL arithmetic: the extension resolves the
binary path and the server-side waveform endpoint against the same-origin /field-audio prefix
and hands both to AudioVisualizer, which then fetches only the envelope of whatever window the
user is looking at.
src/jvmMain/kotlin/afield/audio/ai/AudioAssetVisualization.kt — read the source
Top-level functions
| Declaration | Summary |
|---|---|
fun AssetRepresentation.audioVisualizerHtml(serviceBaseUrl: String = "/field-audio", label: String? = role, options: AudioVisualizerOptions = AudioVisualizerOptions()): String |
Render any Field Audio WAV representation with range-aware server-side waveform summaries. |
CandidateBoard.kt
The candidate board: the instrument for choosing between the alternatives a generation produced.
One function, one HTML fragment, three things a listener actually needs — a player per candidate, the source-into-candidate splice preview beside it so a take can be judged in place rather than in isolation, and a rating, selection flag and note that are written straight back to the service. The provenance line under each player states the sample rates and the conversion, because whether you are hearing the model or a resampler is not a detail.
src/jvmMain/kotlin/afield/audio/ai/CandidateBoard.kt — read the source
Top-level functions
| Declaration | Summary |
|---|---|
fun candidateBoardHtml(candidateSet: CandidateSet, serviceBaseUrl: String = "/field-audio"): String |
A self-contained Field output-area candidate browser. |
CodecVisualization.kt
The EnCodec reconstruction microscope: what a neural codec kept, what it threw away, and what it wrote down in between.
A codec round trip is three things that have to be looked at together — the audio in, the discrete tokens, the audio out — and this file is the rendering half of that view. It is browser-free below the top layer: the spectrograms, the waveform and the token map are drawn into PNG bytes on the JVM, so the same code produces a live box output, a test assertion and the committed picture on the EnCodec microscope page.
Everything here needs the bytes locally, which is what FieldAudioClient.codecVisualizationHtml
is for — it downloads the three representations off Field's runloop and then renders.
src/jvmMain/kotlin/afield/audio/ai/CodecVisualization.kt — read the source
Used in: EnCodec round trip
Top-level functions
| Declaration | Summary |
|---|---|
fun FieldAudioClient.codecVisualizationHtml(result: CodecReconstructionResult, cacheDirectory: Path): CompletableFuture<String> |
Download the three binary assets off Field's runloop, then render one self-contained output view. |
CodecVisualizationData
Everything the EnCodec microscope draws, already decoded into the JVM.
| Declaration | Summary |
|---|---|
val source: FloatArray |
The model input: the windowed, resampled audio the codec was given, as mono float samples. |
val reconstruction: FloatArray |
The decoded output at the same rate and length, so the two can be compared sample for sample. |
val sampleRate: Int |
Sample rate shared by source and reconstruction; load refuses inputs that disagree. |
val tokens: NpyIntTensor |
The discrete codes the encoder produced, indexed codebook then frame. |
val metrics: ReconstructionMetrics |
How far reconstruction is from source, as the backend measured it. |
val frameRate: Double |
Token frames per second, from the codec metadata; 50.0 when the backend did not say. |
val cardinality: Int |
Codebook size, from the codec metadata; 2048 when the backend did not say. Sets the token colour scale. |
val codecLabel: String |
Model name for the heading, from the codec metadata; "EnCodec" when the backend did not say. |
val sourceWav: ByteArray? |
The original WAV bytes of source, kept for browser playback. |
val reconstructionWav: ByteArray? |
The original WAV bytes of reconstruction, with the same purpose as sourceWav. |
CodecVisualization
Rendering shared by live Field output, tests, and the public executable documentation.
| Declaration | Summary |
|---|---|
fun load(result: CodecReconstructionResult, modelInputWav: Path, reconstructionWav: Path, tokenNpy: Path): CodecVisualizationData |
Read the three downloaded files into one CodecVisualizationData. |
fun renderHtml(data: CodecVisualizationData, width: Int = 1200): String |
The whole microscope as one self-contained HTML fragment: two players, two log-frequency spectrograms, and the token map with a hover readout. |
fun renderWaveform(samples: FloatArray, width: Int, height: Int): ByteArray |
A min/max waveform as PNG bytes, one column per pixel. |
fun renderLogSpectrogram(samples: FloatArray, sampleRate: Int, width: Int, height: Int): ByteArray |
A log-frequency STFT as PNG bytes: 1024-point Hann windows, 256-sample hop, 35 Hz to Nyquist, 90 dB of range below the clip's own peak. |
fun renderTokens(tensor: NpyIntTensor, cardinality: Int, width: Int, height: Int): ByteArray |
The token tensor as PNG bytes: time across, codebooks down, coarse to fine, one hue per code. |
CorpusClip.kt
Turns a window of a recording in the Field audio corpus into a box that plays it.
corpusClip fetches a recording from the Field audio service, caches its WAV, and cuts the
requested window into a CorpusClip, whose CorpusClip.sampler plays it. The rest of the file
places a neighbour found by the corpus microscope (corpusMicroscopeHtml) as a box of its own:
corpusNeighbourName and corpusNeighbourCode give the box its name and its code, and
placeCorpusNeighbour creates it beside an existing box.
src/jvmMain/kotlin/afield/audio/ai/CorpusClip.kt — read the source
Top-level functions
| Declaration | Summary |
|---|---|
fun corpusClip(assetId: String, startSeconds: Double, endSeconds: Double, client: FieldAudioClient = FieldAudioClient()): CorpusClip |
The window startSeconds..endSeconds of the recording assetId, fetched from the Field audio service. The recording's WAV is cached under ~/.afield/cache/field-audio, so placing several windows of one recording downloads it once. This blocks until the bytes are here. |
fun corpusNeighbourName(neighbour: CorpusNeighbour): String |
The name a placed neighbour's box gets: its recording and window. |
fun corpusNeighbourCode(neighbour: CorpusNeighbour): String |
The code a placed neighbour's box gets: ordinary box code that plays the window. |
fun placeCorpusNeighbour(info: SessionInformation, beside: Box, neighbour: CorpusNeighbour, index: Int = 0): Box |
Make the box for neighbour beside beside: named after the window, its code corpusNeighbourCode. index stacks the boxes of several placed neighbours below one another. |
fun Box.placeCorpusNeighbour(neighbour: CorpusNeighbour, index: Int = 0): Box |
placeCorpusNeighbour beside this box, in its own document: what a card's place button does. |
CorpusClip
One window of a corpus recording, in memory: what a neighbour the corpusMicroscopeHtml found becomes when it is placed as a box of its own.
| Declaration | Summary |
|---|---|
val assetId: String |
The recording the window is cut from. |
val startSeconds: Double |
Where the window starts in that recording, in seconds. |
val endSeconds: Double |
Where it ends, in seconds. |
val sample: AudioSample |
The window's samples, the recording's left channel. |
fun sampler(): Sampler |
A Sampler over the window, already triggered, so it plays as soon as it is launched. |
override fun toString(): String |
The recording and the window, with both times to two decimal places: CorpusClip(sha256:…, 21.00–27.00 s). |
CorpusMicroscope.kt
The audition surface for a corpus nearest-neighbour query: the query window and every window the search returned, as players on one shared relative timeline.
corpusMicroscopeHtml turns the CorpusPreview an analysis.find_neighbours job leaves behind
into a single self-contained section of HTML, and it refuses a preview from any other operation.
Nothing in it reads audio: each AssetRepresentation in the preview becomes a media URL and a
waveform URL handed to AudioVisualizer, which does the fetching, the drawing and the
shared-cursor arithmetic.
A card's header prints two readings of the same number. One is the raw CorpusNeighbour
distance; the other is a percentage that maps distance 0 to 100% and saturates at 2, so a
neighbour further away than that reads as no similarity at all. The per-group bars apply the
same mapping to each entry of groupDistances and keep the raw distance in the tooltip, so a
bar and the header can be read against each other. The weights above the grid are taken from
the preview, which means they arrive with the result rather than from the
CorpusDistanceWeights the query was written with.
The footer carries what the players cannot. A retrieved window is a stretch of some other
recording, so the card keeps its absolute start and end, its scale in seconds, and whether
CorpusSegment records a previous and a next window. A neighbour that sits at the very
beginning or the very end of its recording shows up there.
src/jvmMain/kotlin/afield/audio/ai/CorpusMicroscope.kt — read the source
Used in: Corpus microscope: from recordings to related windows
Top-level functions
| Declaration | Summary |
|---|---|
fun corpusMicroscopeHtml(preview: CorpusPreview, queryAudio: AssetRepresentation, serviceBaseUrl: String = "/field-audio", width: Int = 1100, cardWidth: Int = 510, syncGroup: String = "corpus-${preview.query.assetId.takeLast(12)}-${preview.query.startSeconds}", placeControlIds: List<String>? = null): String |
A source-relative audition board for CorpusPreview.neighbours. |
fun Box.showCorpusMicroscope(preview: CorpusPreview, queryAudio: AssetRepresentation, channel: String = "microscope", width: Int = 1100, cardWidth: Int = 510) |
corpusMicroscopeHtml in channel, with a place button on every neighbour card: pressing it makes a box beside this one whose code plays that neighbour's window (placeCorpusNeighbour). |
CorpusOperations.kt
Multi-scale corpus operations: segment a source into overlapping windows, embed each window with grouped audio descriptors, and retrieve neighbours by weighted distance.
The three operations form a pipeline that runs server-side. segmentCorpus enumerates
deterministic time windows at several scales without computing any descriptors. embedCorpus
computes and persists the five grouped descriptor families (harmony, pitch, texture, rhythm,
dynamics) for every window. findCorpusNeighbours retrieves the closest embedded windows
to a query using CorpusDistanceWeights, which controls how much each group contributes to
the distance. All three produce a ModelOperation that FieldAudioClient.submit executes.
src/jvmMain/kotlin/afield/audio/ai/CorpusOperations.kt — read the source
Used in: Corpus microscope: from recordings to related windows
Top-level functions
CorpusDistanceWeights
Explicit weights for the five interpretable descriptor groups used by corpus retrieval. Each scales how much its own group counts towards the distance between two windows, a zero takes that group out of the comparison, and at least one of the five has to be positive.
FieldAudioClient.kt
The verbs of Field Audio: one client object whose every method is a request to the model service, plus the typed constructors for the two operations that ship with real controls.
The protocol types — AssetRegion, ModelOperation, JobRecord and the rest — are the
vocabulary; this is what you can do with it. The shape to hold on to is that nothing here
blocks and nothing here runs a model in the JVM: a call returns
a CompletableFuture that completes on the client's own executor, and the model work happens in
a Python worker that may take minutes. Composing futures (thenCompose, thenAccept) is
therefore the only correct way to use this from a box — a .get() on Field's runloop freezes
the editor for as long as the model takes.
Argument validation happens here, in the JVM, before anything is sent: require failures name
the constraint. Everything the service rejects instead arrives as a FieldAudioException
carrying the service's own StructuredError, and a job that fails after being accepted arrives
as a FieldAudioJobException from one of the await… helpers.
src/jvmMain/kotlin/afield/audio/ai/FieldAudioClient.kt — read the source
Used in: Field Audio AI, EnCodec round trip, Gesture board: drawing toward a sound
Top-level functions
| Declaration | Summary |
|---|---|
fun styleCandidates(source: AssetRegion, prompt: String = "", durationSeconds: Double = 12.0, seeds: LongRange = 0L until 8L, backend: String? = null, requestedBy: String? = null, controls: MusicGenStyleControls = MusicGenStyleControls(), forceRegenerate: Boolean = false): ModelOperation |
Ergonomic live-code constructor for the source-window style instrument. |
fun codecReconstruction(source: AssetRegion, numCodebooks: Int? = null, backend: String? = "audiocraft.encodec_musicgen_style_32khz", requestedBy: String? = null, forceRegenerate: Boolean = false): ModelOperation |
Exact EnCodec encode/decode round trip for one source selection. |
FieldAudioException
The service said no, and said why.
| Declaration | Summary |
|---|---|
val statusCode: Int |
HTTP status of the response that failed. |
val responseBody: String |
The response body verbatim, for the cases error could not be decoded from it. |
val error: StructuredError? |
The service's structured error, when the body contained one. See StructuredError.code. |
FieldAudioJobException
A job was accepted and then did not finish successfully.
| Declaration | Summary |
|---|---|
val jobId: String |
The job that ended badly; still fetchable with FieldAudioClient.inspect. |
val state: JobState |
The terminal state it ended in — anything but COMPLETE. |
val error: StructuredError? |
Why, when the job recorded a structured error. Null for a plain cancellation. |
FieldAudioTransportException
There was no conversation: the service could not be reached, or did not answer in the protocol.
FieldAudioClient
Non-blocking, backend-neutral client for Field's audio model service.
MusicGenStyleControls
Global controls exposed by the pinned MusicGen-Style checkpoint.
| Declaration | Summary |
|---|---|
val useSampling: Boolean |
Sample from the distribution rather than taking the argmax. Off makes output deterministic and dull. |
val topK: Int |
Keep only the k most likely tokens at each step; 0 disables the cut. Must be non-negative. |
val topP: Double |
Nucleus sampling threshold within 0..1; 0 disables it. Use one of topK and topP, not both. |
val temperature: Double |
Softmax temperature, strictly positive. Above 1 wanders, below 1 commits. |
val cfg: Double |
Classifier-free guidance strength, non-negative. Higher follows the conditioning harder and varies less. |
val cfgBeta: Double? |
Balance between the text prompt and the style excerpt, non-negative. |
val twoStepCfg: Boolean |
Run guidance as two forward passes instead of one batched pass: slower, occasionally different. |
val styleQuantization: Int |
Size of the style bottleneck, strictly positive. Larger keeps more of the source's identity; smaller abstracts it into gesture. |
val styleExcerptSeconds: Double? |
How much of the source window the style encoder actually reads, in seconds. |
val styleDownsampleFactor: Int? |
Temporal downsampling of the style representation, strictly positive. Null leaves the model's default. |
val styleEncodecCodebooks: Int? |
How many EnCodec codebooks the style path uses, strictly positive. Fewer is a coarser style. |
FieldPersistence.kt
How a Field document remembers audio work: ids and judgements in a box property, and not one sample of audio.
The rule this file enforces is that Python is a worker and Field is the authority. The service
owns immutable, content-addressed assets; the document owns which of them it refers to and what
you thought of them. That is why saved state is a FieldAudioProjectState of ids rather than
audio, and why reopening a document and re-fetching those ids gets the same bytes back.
src/jvmMain/kotlin/afield/audio/ai/FieldPersistence.kt — read the source
FieldAudioPersistence
Persist only protocol data and asset IDs; Python remains a worker, never project authority.
| Declaration | Summary |
|---|---|
const val DEFAULT_PROPERTY |
The box property the project state lives in unless you name another one. |
fun install(property: String = DEFAULT_PROPERTY) |
Register property as persistent and JSON-sidecar-separated on this document. |
fun load(box: Box, property: String = DEFAULT_PROPERTY): FieldAudioProjectState |
Read the saved state out of box, or an empty FieldAudioProjectState if there is none yet. |
fun save(box: Box, state: FieldAudioProjectState, property: String = DEFAULT_PROPERTY) |
Write state into box as pretty-printed JSON, replacing whatever was there. |
GestureBoard.kt
Temporal sketches for source-conditioned generation, with prompt regions, onset events, and parameter curves that together express a generation intent.
Build a GestureBoard with the gestureBoard{} DSL and pass it to gestureCandidates to
produce a multi-seed ModelOperation. Prompt regions are compiled into the model's global text
condition; onset events and parameter curves are evaluative: the service scores each generated
candidate against them rather than passing them as native model controls.
compileGestureBoard previews the protocol locally and returns a GestureCompilation that lists
how each board element is wired to the backend. Example:
val board = gestureBoard(8.0, "acoustic guitar, warm") {
prompt(0.0, 4.0, "sparse, arpeggiated")
event(3.5, "accent")
curve("energy", GestureCurveKind.ACTIVITY) {
point(0.0, 0.2); point(4.0, 0.8); point(8.0, 0.3)
}
}
val operation = gestureCandidates(source, board, seeds = 0L until 4L)
src/jvmMain/kotlin/afield/audio/ai/GestureBoard.kt — read the source
Used in: Gesture board: drawing toward a sound
Top-level functions
| Declaration | Summary |
|---|---|
fun gestureBoard(durationSeconds: Number, prompt: String = "", block: GestureBoardBuilder.() -> Unit = {}): GestureBoard |
A live-codeable temporal sketch for source-conditioned generation. |
fun validateGestureBoard(board: GestureBoard): GestureBoard |
Checks a board against the version-1 rules and returns it, so it can be used inline. It enforces timing (regions and points inside the duration, curve times strictly increasing), ranges (values and strengths in 0..1, weights in (0, 10]), unique curve names, and that each element's delivery is the one protocol version 1 defines for it. The gestureBoard{} builder validates on the way out and every function here that takes a board validates on the way in, so this is the entry point for a board assembled by hand or one that arrived over the wire. |
fun compileGestureBoard(board: GestureBoard, operation: String = "generate.style", parameters: JsonObject = JsonObject(emptyMap())): GestureCompilation |
Immediate local preview of the service's version-1 compilation contract. |
fun gestureCandidates(source: AssetRegion, board: GestureBoard, seeds: LongRange = 0L until 8L, backend: String? = null, requestedBy: String? = null, controls: MusicGenStyleControls = MusicGenStyleControls(), forceRegenerate: Boolean = false): ModelOperation |
Source-style generation plus an inspectable gesture sketch and candidate scoring. |
GestureBoardDsl
annotation class GestureBoardDsl
The DSL scope marker shared by the two gesture-board builders. It stops a nested curve{} block from reaching past its own receiver to call prompt or event on the board around it.
GestureCurveBuilder
Collects the breakpoints of one curve inside a curve{} block. Points are kept in the order they are written, and strictly increasing times, values in 0..1 and a minimum of two points are all checked when the enclosing board is built, not as each point arrives.
| Declaration | Summary |
|---|---|
fun point(timeSeconds: Number, value: Number) |
Adds one breakpoint to the curve: timeSeconds along the board, value in 0..1. |
GestureBoardBuilder
The receiver of a gestureBoard{} block: prompt regions, events and curves accumulate here in the order they are written, and the finished board is validated once, when the block returns.
| Declaration | Summary |
|---|---|
var metadata: JsonObject |
Free-form JSON that travels with the board to the service. Neither validation nor compilation reads it, so it is somewhere to record where a sketch came from. |
fun prompt(startSeconds: Number, durationSeconds: Number, text: String, weight: Number = 1.0) |
Adds a timed text region. Protocol version 1 has no timed text condition, so compileGestureBoard folds each region into the one global prompt, worded from As to Bs: text, in the order the regions were written; a region has to fit inside the board's duration and its weight has to lie in (0, 10]. |
fun event(timeSeconds: Number, label: String, strength: Number = 1.0, weight: Number = 1.0) |
Marks a labelled onset at timeSeconds. Events are evaluative: nothing is conditioned on them, and a generated candidate is rewarded for an accent near the marked time. strength is 0..1 and weight is in (0, 10]. |
fun curve(name: String, kind: GestureCurveKind, weight: Number = 1.0, block: GestureCurveBuilder.() -> Unit) |
Opens a named lane of one GestureCurveKind and collects its points. Names have to be unique within a board and a curve needs at least two points. Like an event, a curve is measured against the generated waveform rather than sent as a model control, and the pinned contract has four kinds to measure against: density, activity, register and brightness. |
GestureBoardVisualization.kt
A gesture board drawn as a picture of itself: prompt regions, onset events and parameter curves on one timeline, each mark labelled with how far into the model it reaches.
gestureBoardHtml renders a GestureBoard as a single self-contained section, with inline
styles, one SVG and a few lines of script for the hover cursor. There is no asset to fetch and
no library to install. Every rule and the script are scoped to an id derived from the board's
hash, so one document can show several boards at once. Prompt regions become blocks on the
prompt lane, events become dashed needles at their marked time, and each curve gets a lane of
its own coloured by its GestureCurveKind.
The chips above the timeline are the bindings of a GestureCompilation, and they separate a
mark the backend is conditioned on from a mark it is only scored against. By default the
compilation is the local compileGestureBoard preview of the service's version-1 contract
rather than a record of what some particular job sent, and the effective prompt printed beside
the chips is the time-worded text that preview produces.
Passing a CandidateSet adds a ranking strip under the chips: a row per candidate, ordered by
gesture score where the service returned one and by the candidate's own score otherwise, with a
bar for each evaluated lane. A board is validated before anything is drawn, so a sketch the
service would reject fails here rather than being rendered.
src/jvmMain/kotlin/afield/audio/ai/GestureBoardVisualization.kt — read the source
Top-level functions
| Declaration | Summary |
|---|---|
fun gestureBoardHtml(board: GestureBoard, compilation: GestureCompilation = compileGestureBoard(board), candidates: CandidateSet? = null, width: Int = 1080): String |
Draws board as a timeline section: the top-level name for GestureBoardVisualization's renderer, with the same defaults, and the one a box calls. |
GestureBoardVisualization
object GestureBoardVisualization
Interactive, dependency-free timeline renderer for a GestureBoard.
| Declaration | Summary |
|---|---|
fun renderHtml(board: GestureBoard, compilation: GestureCompilation = compileGestureBoard(board), candidates: CandidateSet? = null, width: Int = 1080): String |
Draws board over width pixels of timeline, inside a card 38 pixels wider than that. width must be at least 640 and the SVG keeps it, so a narrower output area scrolls the timeline horizontally instead of compressing it. compilation defaults to the local preview; passing the result of FieldAudioClient.compileGesture instead shows how the live service wires this same board. candidates adds the ranking strip and changes nothing else on the timeline. |
JobMonitor.kt
The job monitor: the service's own live view of every durable job, embedded in a box output.
It is an <iframe> rather than a Kotlin-rendered table, because the page is served by the
same service that owns the jobs, so it cannot disagree with them, and it needs no polling code on
the Field side. Same-origin through the /field-audio prefix, so nothing here reaches across
origins.
src/jvmMain/kotlin/afield/audio/ai/JobMonitor.kt — read the source
Used in: Beat and pitch analysis, Corpus microscope: from recordings to related windows, Short-time spectral spaces
Top-level functions
| Declaration | Summary |
|---|---|
fun fieldAudioJobMonitor(serviceBaseUrl: String = "/field-audio", heightPixels: Int = 420): String |
An embeddable, same-origin view of all durable Field Audio jobs. |
fun fieldAudioJobStatusHtml(accepted: JobAccepted, event: ProgressEvent? = null): String |
A small per-operation status card suitable for replacing one named Field output as events arrive. |
fun fieldAudioJobStatusHtml(job: JobRecord): String |
Render durable inspected state, including the structured error when a job failed. |
MusicTranscription.kt
Typed decoder for the versioned note document a multi-instrument transcription job leaves behind.
musicTranscriptionAnalysis in AnalysisOperations.kt builds the request, and three accessors
beside it locate what came back: performance MIDI, optional beat-quantized MIDI, and the
MessagePack event document. This file reads the last of those.
decodeMusicTranscriptionMessagePack turns its bytes into a MusicTranscription, and
FieldAudioClient.musicTranscription is the one-call path from a finished AnalysisResult.
MessagePack rather than JSON keeps the note times binary, so an onset reaches the JVM as the
float64 the worker wrote. The decoder rejects a payload whose format is not
field.audio.music-transcription.v1, rejects trailing data after the root map, and range-checks
every note: non-negative onset, offset at or after onset, MIDI pitch inside 0..127. A malformed
artifact fails at the decode rather than at the first drawing that reads it.
Most of the document is optional. MusicTranscription.beatGrid is null when the artifact carries
no beat_grid or a nil one, and inside a grid beatsPerBar, onsetDelaySeconds and
beatSubdivision are each independently absent. The four provenance fields come from the
artifact's metadata map and are null when the worker recorded nothing there. instrument is a
string naming one of the worker's own instrument groups, which is a model's hypothesis about a
mixed recording and not a reading of what played it. See
Multi-instrument transcription with MuScriptor for how far that has
been checked against a known source.
src/jvmMain/kotlin/afield/audio/ai/MusicTranscription.kt — read the source
Used in: Multi-instrument transcription with MuScriptor
Top-level functions
| Declaration | Summary |
|---|---|
fun decodeMusicTranscriptionMessagePack(bytes: ByteArray): MusicTranscription |
Decode the complete source-aligned note document without routing numerical data through JSON. |
fun FieldAudioClient.musicTranscription(result: AnalysisResult): CompletableFuture<MusicTranscription> |
Download and decode the complete typed note document retained by musicTranscriptionAnalysis. |
MusicTranscriptionNote
One source-aligned note recovered by a multi-instrument transcription worker.
| Declaration | Summary |
|---|---|
val startSeconds: Double |
Where the note begins, in seconds. The decoder rejects a negative onset. |
val endSeconds: Double |
Where the note ends, in seconds. Never earlier than startSeconds; the decoder rejects a pair that is. |
val midi: Int |
MIDI note number, 60 being middle C. The decoder rejects anything outside 0..127. |
val instrument: String |
The worker's own name for the instrument group it put this note in. It is a model's reading of a mixed recording rather than a record of what played the note. |
MusicTranscriptionBeatGrid
Tempo, meter, and the measured model-onset correction retained with a transcription.
MusicTranscription
Typed contents of the versioned MuScriptor MessagePack artifact.
NpyIntTensor.kt
A dependency-free reader for the integer NPY tensors the audio service emits — the codec token grids, in practice.
numpy.kt already reads .npy and .npz generally; this is the narrow, self-checking version
for the one shape that matters here (codebooks × frames of small integers), so the codec views
can index tokens by codebook and frame without a general tensor type in between. It refuses what
it cannot represent, rather than guessing: Fortran order, an unsupported dtype and a
multi-item batch are all errors with a message.
src/jvmMain/kotlin/afield/audio/ai/NpyIntTensor.kt — read the source
NpyIntTensor
data class NpyIntTensor(val shape: IntArray, val values: LongArray)
Small, dependency-free reader for the C-order integer NPY tensors emitted by the audio service.
| Declaration | Summary |
|---|---|
val shape: IntArray |
Array dimensions as the NPY header gave them; the last two are codebooks and time. |
val values: LongArray |
All codes, C-order (row-major), widened to Long whatever the on-disk integer width was. |
val codebooks: Int |
Size of the second-to-last axis: how many codebooks the encoder used. |
val frames: Int |
Size of the last axis: how many token frames, and so the time extent. |
operator fun get(codebook: Int, frame: Int): Long |
One code, by codebook and frame. Unchecked: an out-of-range index is an array error, not null. |
Protocol.kt
The wire vocabulary of Field Audio — one Kotlin type per JSON object in the pinned service contract, and nothing else.
Field Audio runs models out of process: a long-lived Python service owns inference, and a box
talks to it over HTTP and one WebSocket through FieldAudioClient. These are the values that
cross that boundary. Every SerialName here is the snake_case name the service uses, and the
committed, model-free snapshot of the same contract is published as
the Field Audio service reference — if the two disagree,
the service is right and this file is stale.
Three facts that explain the shapes
- Results are assets, not samples. Everything the service produces is written once, named by
the SHA-256 of its bytes (
sha256:followed by 64 hex digits) and referred to afterwards by id. That is why a result type carries anAssetRepresentationrather than aFloatArray: the audio is still on the service, andFieldAudioClient.downloadis how bytes reach the JVM. - Work is a job, not a call. Submitting an operation returns a
JobAcceptedimmediately; the outcome arrives later as aJobRecordyou poll or a stream ofProgressEvents you watch. - Identical requests are not repeated. The service hashes the request (ignoring
ModelOperation.forceRegenerate) intoJobRecord.requestHashand returns the stored result when it has seen that hash before, withcacheHit = trueand a job that is already complete.
Times, ids and timestamps
All times and durations are seconds as Double unless the property name says otherwise.
createdAt, updatedAt and ProgressEvent.at are ISO-8601 UTC timestamp strings, kept as
text because nothing in Field parses them and a string round-trips through project persistence
unchanged. Ids are opaque and prefixed by kind: sha256: for assets, rep_ for
representations, job_, op_, cset_, codec_ and analysis_ for the rest.
The client decodes with ignoreUnknownKeys, so a newer service that adds a field will not break
an older Field; the reverse is not true, because the service rejects unknown fields it is sent.
src/jvmMain/kotlin/afield/audio/ai/Protocol.kt — read the source
Used in: Field Audio service reference, Corpus microscope: from recordings to related windows, Visualizers, Displays, and Little Instruments
AssetRegion
A time window inside one asset — the unit of "what should the model listen to".
AssetRepresentation
One concrete encoding of one asset's bytes: what format they are in, and where to fetch them.
AudioAsset
An immutable blob of audio on the service, plus every representation of it.
| Declaration | Summary |
|---|---|
val id: String |
Content address of the bytes: sha256: followed by 64 hex digits. |
val byteSize: Long |
Size of the underlying bytes, in bytes. |
val createdAt: String |
ISO-8601 UTC timestamp of the first time these bytes were stored. |
val representations: List<AssetRepresentation> |
Every materialised view of this asset, oldest first. See AssetRepresentation.role. |
val metadata: JsonObject |
Free-form provenance recorded at import or generation time; not interpreted by Field. |
val durationSeconds: Double? |
Duration of the first representation that declares one, in seconds, or null if none does. |
val sampleRate: Int? |
Sample rate of the first representation that declares one, with the caveat on durationSeconds. |
val channels: Int? |
Channel count of the first representation that declares one, with the caveat on durationSeconds. |
WaveformSummary
A min/max envelope of one representation over one time window — what a waveform display draws.
| Declaration | Summary |
|---|---|
val representationId: String |
The representation that was summarised. |
val startSeconds: Double |
Start of the summarised window, in seconds from the start of the representation. |
val durationSeconds: Double |
Length of the summarised window, in seconds. |
val sampleRate: Int |
Sample rate of the underlying audio in Hz — the envelope itself is not sampled at this rate. |
val channels: Int |
Number of channels, and so the outer length of minimum and maximum. |
val pointCount: Int |
Points per channel. Never more than the window's frame count, so a short window returns fewer. |
val minimum: List<List<Float>> |
Per-channel minima, indexed [channel](simulation.md#model-kt--point), nominally in −1..1. |
val maximum: List<List<Float>> |
Per-channel maxima, indexed [channel](simulation.md#model-kt--point), nominally in −1..1. |
GestureDelivery
How one part of a gesture board reaches the sound, which the board always says out loud.
GestureCurveKind
What a GestureCurve traces over the length of a board. Each is scored on a normalized 0..1 scale.
GesturePoint
data class GesturePoint(@SerialName("time_seconds") val timeSeconds: Double, val value: Double)
One breakpoint of a GestureCurve.
GestureCurve
A shape asked of one quality of the sound over the length of a board.
GestureEvent
A moment the sound is asked to mark.
| Declaration | Summary |
|---|---|
val timeSeconds: Double |
When the accent is wanted, in seconds from the start of the board, and no later than its duration. |
val label: String |
What to call this event. Must not be blank. |
val strength: Double |
How pronounced the accent should be, between zero and one. |
val weight: Double |
How much this event counts against the others when candidates are ranked. In (0, 10]. |
val delivery: GestureDelivery |
Fixed at GestureDelivery.EVALUATIVE in protocol version 1: a candidate is rewarded for an onset near GestureEvent.timeSeconds. |
GesturePromptRegion
Words asked of one stretch of the board.
GestureBoard
Everything asked of one generated sound, with each part's route to the audio attached.
GestureBinding
One line of the account a compilation gives of how a board's element reaches the sound.
GestureCompileRequest
A board put forward for compilation, with the operation and parameters it is meant for.
GestureCompilation
What a board turns into, next to an account of how each part of it got there.
GestureEvaluation
How well one generated candidate answered the board it was asked for.
ModelOperation
A request for the service to do something — the one type you build by hand, or nearly.
| Declaration | Summary |
|---|---|
val operation: String |
The dotted operation name, e.g. generate.style; an unknown one is UNSUPPORTED_OPERATION. |
val inputs: List<AssetRegion> |
Source windows to work from. Every operation that ships today reads inputs[0] and no more. |
val parameters: JsonObject |
Operation-specific settings in the service's snake_case keys. The typed constructors fill this. |
val seeds: List<Long> |
One RNG seed per candidate, or empty to let the service choose. |
val candidateCount: Int |
How many alternatives to generate, 1..64. Analysis and codec operations use 1. |
val backendPreference: String? |
Which backend should serve this, e.g. analysis.librosa or audiocraft.encodec_musicgen_style_32khz. Null lets the service resolve one, and the choice it made comes back as JobAccepted.resolvedBackend. |
val requestedBy: String? |
Free-form attribution, carried through to JobSummary.requestedBy; useful once a job list gets busy. |
val gestureBoard: GestureBoard? |
The gesture board this request was compiled from, when it came from one. |
val forceRegenerate: Boolean |
Bypass the result cache and run the model again. |
JobState
Where a job has got to. Most of these are progress narration; four of them are the end.
| Declaration | Summary |
|---|---|
val terminal: Boolean |
True once the job will not change again: COMPLETE, CANCELLED, COMPLETED_BEFORE_CANCELLATION or FAILED. |
JobAccepted
The immediate answer to submitting a ModelOperation: a job exists, and here is how to follow it.
StructuredError
A failure the service could name, as opposed to an HTTP status.
JobRecord
The durable state of one job — the thing polling returns.
| Declaration | Summary |
|---|---|
val id: String |
The job id: job_ plus a UUID. |
val operationId: String |
The stored operation this job ran, op_ plus a UUID. Several jobs share one on a cache hit. |
val requestHash: String |
Hash of the request; the cache key. |
val state: JobState |
Where the job has got to. Check JobState.terminal before reading a result id. |
val resolvedBackend: String |
The backend that ran, or is running, this job. |
val progress: Double |
Fraction of the job done, 0..1. |
val createdAt: String |
ISO-8601 UTC timestamp of when the job was accepted. |
val updatedAt: String |
ISO-8601 UTC timestamp of the last state or progress change. |
val error: StructuredError? |
Why the job failed, when it did. Null in every other state. |
val candidateSetId: String? |
Set when a generate.* job completed: fetch it with FieldAudioClient.candidateSet. |
val codecResultId: String? |
Set when a codec.* job completed: fetch it with FieldAudioClient.codecResult. |
val analysisResultId: String? |
Set when an analysis.* job completed: fetch it with FieldAudioClient.analysisResult. |
val cacheHit: Boolean |
True when this job was answered from the result cache without running a model. |
JobSummary
A JobRecord as it appears in the job list, with the two fields that make a list readable: what was asked for, and by whom.
| Declaration | Summary |
|---|---|
val id: String |
The job id: job_ plus a UUID. |
val operationId: String |
The stored operation this job ran. |
val requestHash: String |
Hash of the request; the cache key. |
val state: JobState |
Where the job has got to. |
val resolvedBackend: String |
The backend that ran, or is running, this job. |
val progress: Double |
Fraction of the job done, 0..1; see JobRecord.progress. |
val createdAt: String |
ISO-8601 UTC timestamp of when the job was accepted. |
val updatedAt: String |
ISO-8601 UTC timestamp of the last state or progress change. |
val error: StructuredError? |
Why the job failed, when it did. |
val candidateSetId: String? |
Set when a generate.* job completed. |
val codecResultId: String? |
Set when a codec.* job completed. |
val analysisResultId: String? |
Set when an analysis.* job completed. |
val cacheHit: Boolean |
True when this job was answered from the result cache. |
val operation: String |
The dotted operation name that was requested — the column a job list is really scanned by. |
val requestedBy: String? |
Whatever ModelOperation.requestedBy was set to, or null. |
Candidate
One alternative in a generated family, with its provenance and your verdict on it.
| Declaration | Summary |
|---|---|
val asset: AudioAsset |
The model-native output asset, described in full so no second request is needed. |
val operationId: String |
The stored operation that produced the whole family. |
val index: Int |
Position in the family, from zero. The candidate board displays it one-based. |
val seed: Long |
The RNG seed this alternative was generated with — resubmit it to get this take back. |
val nativeRepresentationId: String |
Representation id of the model's own output, at the model's own sample rate. |
val proxyRepresentationId: String |
Representation id of the 48 kHz WAV to audition. This is the one to play. |
val splicePreviewRepresentationId: String? |
Representation id of the source crossfaded into this candidate, when the backend made one. |
val nativeRepresentation: AssetRepresentation? |
Inline metadata for nativeRepresentationId, when the service inlined it. |
val proxyRepresentation: AssetRepresentation? |
Inline metadata for proxyRepresentationId, when the service inlined it. |
val splicePreviewRepresentation: AssetRepresentation? |
Inline metadata for splicePreviewRepresentationId, when there is one. |
val score: Double? |
A backend-computed score, if that backend computes one. Not comparable across backends. |
val gestureEvaluation: GestureEvaluation? |
How the gesture board scored this candidate, when one produced it. |
val rating: Double? |
Your rating, as last written back with FieldAudioClient.judge. The board's slider spans −1..1. |
val selected: Boolean |
Your selection flag, as last written back. |
val annotation: String? |
Your note, as last written back. |
CandidateSet
The whole family a generate.* job produced, in one immutable, re-fetchable value.
| Declaration | Summary |
|---|---|
val id: String |
Id of the set: cset_ plus a UUID. |
val operationId: String |
The stored operation that produced it. |
val requestHash: String |
Hash of the request; two identical requests resolve to the same stored set. |
val candidates: List<Candidate> |
The alternatives, in Candidate.index order. |
val createdAt: String |
ISO-8601 UTC timestamp of when the set was written. |
ReconstructionMetrics
How far a codec round trip's output is from its input, by three different measures.
CodecReconstructionResult
Everything a codec.reconstruct job produced: what went in, the discrete tokens, what came back out, and how far apart the two ends are.
| Declaration | Summary |
|---|---|
val id: String |
Id of the result: codec_ plus a UUID. |
val operationId: String |
The stored operation that produced it. |
val requestHash: String |
Hash of the request. |
val source: AssetRegion |
The source window that was reconstructed. |
val modelInput: AssetRepresentation |
The audio the codec actually saw: the window, resampled to the model's rate. Compare against this. |
val tokens: AssetRepresentation |
The discrete codes, as a C-order integer .npy tensor — read it with NpyIntTensor. |
val reconstructionNative: AssetRepresentation |
The decoded reconstruction at the model's own sample rate. |
val reconstructionProxy: AssetRepresentation |
The same reconstruction resampled to 48 kHz for audition. |
val metrics: ReconstructionMetrics |
How close reconstructionNative is to modelInput. |
val codec: JsonObject |
The backend's description of the codec itself. CodecVisualization.load reads frame_rate, cardinality and model_name out of here, falling back to EnCodec's 50 Hz / 2048 defaults. |
val createdAt: String |
ISO-8601 UTC timestamp of when the result was written. |
AnalysisResult
Everything an analysis.* job produced — as stored assets, not as numbers.
AnalysisSeries
One named curve inside an AnalysisPreview: a time axis, a value axis, and what to call them.
| Declaration | Summary |
|---|---|
val name: String |
Machine name, e.g. onset_strength, frequency_hz, tempo_0_bpm. |
val label: String |
Human label for a legend, e.g. tempo · 4 s. Falls back to name. |
val unit: String? |
Unit of values where one applies, e.g. Hz or bpm; null for a dimensionless curve. |
val timesSeconds: List<Double?> |
Sample times in seconds from the start of the analysed window; null where non-finite. |
val values: List<Double?> |
The curve itself, parallel to timesSeconds; null where non-finite. |
val metadata: JsonObject |
Per-series extras, e.g. the scale_seconds a multi-scale tempo track was measured at. |
AnalysisNote
One transcribed note from polyphonic analysis: when, how high, how loud.
AnalysisPreview
A plot-sized view of an AnalysisResult, built server-side so the browser never sees the .npz.
| Declaration | Summary |
|---|---|
val operation: String |
The dotted operation this preview came from — the discriminator for everything below. |
val durationSeconds: Double |
Length of the analysed window in seconds; the natural x-axis extent. |
val series: List<AnalysisSeries> |
The curves, already downsampled. See AnalysisSeries for the null convention. |
val beatTimesSeconds: List<Double> |
Detected beat positions in seconds, for analysis.beats; empty otherwise. Not downsampled. |
val notes: List<AnalysisNote> |
Transcribed notes, for analysis.pitch_poly and analysis.transcribe_music; empty otherwise. |
val summary: JsonObject |
The same headline numbers as AnalysisResult.summary. |
val analysis: JsonObject |
The same resolved parameters as AnalysisResult.analysis. |
CorpusSegment
One immutable, source-adjacent window in the indexed audio corpus.
| Declaration | Summary |
|---|---|
val id: String |
The index's own identifier for this window, which previousSegmentId and nextSegmentId refer to. |
val assetId: String |
The recording the window was cut from. |
val startSeconds: Double |
Where the window starts in that recording, in seconds. |
val durationSeconds: Double |
How long the window is, in seconds. |
val scaleSeconds: Double |
The window length this part of the index was built at. One recording is usually indexed at several scales at once, and a window is only ever compared with others of its own. |
val previousSegmentId: String? |
The window before this one in the same recording at the same scale, or null at the start. |
val nextSegmentId: String? |
The window after it, or null at the end. Following these is how a splice keeps going. |
val descriptorRepresentationId: String? |
The representation holding this window's descriptor vector, where the index kept one. |
val metadata: JsonObject |
Anything else the indexer recorded about the window. |
CorpusNeighbour
A retrieved corpus window together with its grouped distance and playable representations.
CorpusPreview
Bounded metadata for a corpus index or nearest-neighbour result.
| Declaration | Summary |
|---|---|
val operation: String |
Which corpus operation this came from, and so which list below is filled: analysis.embed populates segments, analysis.find_neighbours populates neighbours. |
val query: AssetRegion |
The window that was indexed or searched with. |
val scaleSeconds: Double? |
The scale the operation worked at, where it worked at a single one. |
val indexedSegmentCount: Int |
How many windows the index holds, which is the pool a retrieval searched. |
val segments: List<CorpusSegment> |
The windows an indexing run produced. Empty for a retrieval. |
val neighbours: List<CorpusNeighbour> |
What a retrieval found, nearest first. Empty for an indexing run. |
val weights: Map<String, Double> |
The descriptor-group weights the distances were measured with, keyed as in CorpusNeighbour.groupDistances. |
val summary: JsonObject |
Headline numbers for the run, for showing above the results. |
ProgressEvent
One frame of a job's live narration, delivered over the WebSocket FieldAudioClient.watch opens.
| Declaration | Summary |
|---|---|
val sequence: Long |
Monotonically increasing within one job; use it to order or de-duplicate replayed events. |
val jobId: String |
The job this event belongs to. |
val state: JobState |
The state the job entered. |
val progress: Double |
Fraction done, 0..1; see JobRecord.progress. |
val message: String? |
A short human-readable note, e.g. "making 48 kHz audition proxy". Null when there is nothing to say. |
val candidateIndex: Int? |
Which candidate this event is about, for a family generated one at a time. |
val at: String |
ISO-8601 UTC timestamp of the event. |
ModelCapability
What one model can do, as reported by FieldAudioClient.models — the authoritative answer to "can I ask for this?" before submitting anything.
| Declaration | Summary |
|---|---|
val modelId: String |
Backend id: the same string ModelOperation.backendPreference takes. |
val revision: String |
Checkpoint revision, recorded into the provenance of everything this model produces. |
val operations: List<String> |
The dotted operations this backend serves. |
val nativeSampleRates: List<Int> |
Sample rates the model works at natively, in Hz. Output is resampled to 48 kHz for audition. |
val channels: List<String> |
Channel layouts supported, each mono or stereo. |
val maxDurationSeconds: Double? |
Longest output the model will produce, in seconds; null when unbounded or unknown. |
val supportsSeed: Boolean |
Whether ModelOperation.seeds does anything here. If false, results are not reproducible. |
val supportsCancellation: Boolean |
Whether a cancel request can actually stop it, or will end as CANCEL_UNSUPPORTED. |
val conditioning: List<String> |
The conditioning inputs this model accepts, e.g. a text prompt or a style excerpt. |
val available: Boolean |
False when the model is known but cannot run right now. |
val unavailableReason: String? |
Why available is false — a missing weight file, an unavailable worker environment. |
CandidateJudgement
Your verdict on one candidate, written back with FieldAudioClient.judge. Every field is optional.
SavedAudioOperation
One operation and everything that came of it, as saved into a Field document.
| Declaration | Summary |
|---|---|
val request: ModelOperation |
The request as submitted — enough to resubmit it exactly. |
val accepted: JobAccepted |
What the service said when it accepted it. |
val job: JobRecord? |
The last job state seen, if one was recorded before the document was saved. |
val candidateSet: CandidateSet? |
The candidate family, for a generate.* operation that finished. |
val codecResult: CodecReconstructionResult? |
The codec result, for a codec.* operation that finished. |
val analysisResult: AnalysisResult? |
The analysis result, for an analysis.* operation that finished. |
val judgements: Map<Int, CandidateJudgement> |
Ratings and notes by Candidate.index, as last saved. |
FieldAudioProjectState
The whole Field-side audio project: which assets it refers to, and what was done with them.
| Declaration | Summary |
|---|---|
val version: Int |
Format version of this saved state. Bump it when the shape changes incompatibly. |
val assets: Map<String, AudioAsset> |
Assets the project refers to, keyed by AudioAsset.id. |
val operations: List<SavedAudioOperation> |
Operations in the order they were performed. |
RenderedAudioAnalysis.kt
The render-to-analysis pipeline: render Field synthesis to a WAV, upload it once, and fan out durable analysis jobs.
RenderedAudioFile is a WAV produced by Field code, tagged with a generator name and a
provenance map that identify what produced the bytes. Six render functions cover the synthesis
systems: renderPatWav (Pat rhythmic patterns), renderDirtWav (Pat through the Dirt sample
library), renderDoughWav and renderDoughStereoWav (Dough signal graphs), renderFaustWav
and renderFaustWavAsync (Faust programs), and recordSonicPiWav (real-time Sonic Pi capture).
analyzeRenderedAudio uploads the WAV once, submits a fan-out of analysis jobs (beats,
monophonic pitch, and polyphonic pitch by default), watches progress over WebSocket, and
completes with a RenderedAudioAnalysis. The fan-out is all-or-nothing: a failed leg abandons
the others rather than returning a partial result. RenderedAudioAnalysis.visualizationHtml
renders the waveform and all analysis lanes on a shared, bidirectional time cursor.
val wav = renderPatWav(tmp, pattern, cycles = 4, cps = 1.5)
val analysis = client.analyzeRenderedAudio(wav).get()
display(analysis.visualizationHtml())
src/jvmMain/kotlin/afield/audio/ai/RenderedAudioAnalysis.kt — read the source
Used in: Render Field music, then analyse it, Visualizers, Displays, and Little Instruments
Top-level functions
RenderedAudioFile
A finite sound made by Field code and written to a real WAV file.
NamedAudioAnalysis
data class NamedAudioAnalysis(val label: String, val operation: ModelOperation)
One named analysis request, retained beside its accepted job and final result.
| Declaration | Summary |
|---|---|
val label: String |
Short name for this leg. standardAudioAnalyses uses beats, monophonic pitch and polyphonic pitch. It reaches CompletedAudioAnalysis and the progress callbacks so a caller can tell the legs apart; RenderedAudioAnalysis.visualizationHtml is handed only the results, and names its lanes from those. |
val operation: ModelOperation |
The request this leg submits. |
CompletedAudioAnalysis
A completed leg of a rendered-audio analysis fan-out.
| Declaration | Summary |
|---|---|
val label: String |
The NamedAudioAnalysis.label this leg was requested under. |
val accepted: JobAccepted |
What the service said when it took the job, including the job id progress was watched on. |
val result: AnalysisResult |
The durable result the job finished with. |
RenderedAudioAnalysis
The complete round trip: local render, immutable service asset, exact analysed window, jobs and durable results. This is suitable for storing on a Field node without making Python authoritative.
| Declaration | Summary |
|---|---|
val render: RenderedAudioFile |
The local WAV that was uploaded, with the generator name and provenance it was made with. |
val asset: AudioAsset |
The immutable service asset those bytes became. |
val sourceRepresentation: AssetRepresentation |
The asset's WAV representation the analyses ran against: its canonical_project WAV where there is one, otherwise the first WAV it has. |
val region: AssetRegion |
The window actually analysed. Its duration is clamped to what is left of the render after AssetRegion.startSeconds, so it can be shorter than the one asked for. |
val analyses: List<CompletedAudioAnalysis> |
One entry per leg of the fan-out, in the order the requests were listed. |
fun visualizationHtml(serviceBaseUrl: String = "/field-audio", width: Int = 1100, waveformHeight: Int = 150, panelHeight: Int = 250, points: Int = 1600, syncGroup: String = "render-analysis-${asset.id.removePrefix("sha256:").take(16)}"): String |
Source waveform and every analysis lane on one shared, bidirectional time cursor. |
SpectralEmbeddingProjection.kt
Spectral embedding: decode, pool, and project the short-time spectral frames stored in an analysis artifact.
A spectral analysis job writes a binary MessagePack artifact.
decodeSpectralEmbeddingMessagePack reads it into a SpectralEmbeddingFrames value containing
mel distributions, log-mel spectrograms, and MFCC vectors, optionally bounded to a time window,
temporally pooled, and sampled to a target frame count. Call .mds() or .tsne() on the
result to get a SpectralEmbeddingProjection with exact frame times, then pass it to
withSpectralProjection to attach the axes to an AnalysisPreview as named time series.
Five distance metrics are available via SpectralEmbeddingDistance. The default, mel Wasserstein,
treats each frame as a probability distribution over perceptual mel-bin positions and computes
earth-mover distance. The other four apply cosine or Euclidean distance to log-mel or MFCC
vectors directly. Both projection methods delegate to Smile via the smileMds and smileTsne
families.
Temporal pooling and hop length are independent: overlapping perceptual windows can still sample
at display rate. Sampling to maxFrames happens after pooling and retains time order. Example:
val frames = client.spectralEmbeddingFrames(result).get()
val projection = frames.mds()
val preview = preview.withSpectralProjection(projection)
src/jvmMain/kotlin/afield/audio/ai/SpectralEmbeddingProjection.kt — read the source
Used in: Short-time spectral spaces, Visualizers, Displays, and Little Instruments
Top-level functions
SpectralEmbeddingDistance
enum class SpectralEmbeddingDistance(val label: String)
The interchangeable short-time representations and metrics retained by one spectral job.
Constants: MEL_WASSERSTEIN, LOG_MEL_COSINE, LOG_MEL_EUCLIDEAN, MFCC_COSINE, MFCC_EUCLIDEAN.
SpectralEmbeddingFrames
A bounded and optionally pooled range decoded from a spectral MessagePack artifact.
| Declaration | Summary |
|---|---|
val timesSeconds: DoubleArray |
When each retained frame sits in the recording, in seconds, read from the artifact's own clock. |
val melDistributions: Array<FloatArray> |
One mel distribution per retained frame, each renormalized to sum to one so it can be read as a distribution over mel-bin position. Columns line up with melFrequenciesHz. |
val logMelDb: Array<FloatArray> |
One log-mel spectrum per retained frame, in decibels, pooled by mean. Columns line up with melFrequenciesHz. |
val mfcc: Array<FloatArray> |
One MFCC vector per retained frame, pooled by mean. |
val melFrequenciesHz: FloatArray |
The frequency in hertz the artifact gives for each mel bin, one per column of melDistributions. |
val sampledFrameIndices: IntArray |
For each retained frame, its index in the artifact's own frame numbering. |
val sourceFrameCount: Int |
Frames in the whole artifact, before any time window was applied. |
val pooledFrameCount: Int |
Pooled rows the chosen window produced, before sampling down to maxFrames. The retained frame count is this or maxFrames, whichever is smaller. |
val frameRateHz: Double |
The artifact's native frame rate, which sets the smallest pool and hop that can be asked for. |
val poolMillis: Double |
The pooling window actually used, in milliseconds. A request is rounded to a whole number of native frames and held to at least one, so this can differ from what was asked for. |
val hopMillis: Double |
The hop actually used, in milliseconds, rounded the same way as SpectralEmbeddingFrames.poolMillis. |
val backend: String |
Which analysis backend wrote the artifact. |
val libraryVersion: String |
The version of that backend's own library, as the artifact records it. |
SpectralEmbeddingProjection
A Field-owned spectral projection with exact frame times for synchronized audition.
| Declaration | Summary |
|---|---|
val timesSeconds: DoubleArray |
When each projected frame sits in the recording, in seconds, carried over from the frames it came from. |
val coordinates: Array<DoubleArray> |
One point per projected frame, each as long as the number of dimensions asked for. |
val sampledFrameIndices: IntArray |
For each projected frame, its index in the artifact's own frame numbering. |
val sourceFrameCount: Int |
SpectralEmbeddingFrames.sourceFrameCount of the frames this was projected from. |
val pooledFrameCount: Int |
SpectralEmbeddingFrames.pooledFrameCount of the frames this was projected from. |
val poolMillis: Double |
The pooling window those frames were built with, in milliseconds. |
val hopMillis: Double |
The hop those frames were built with, in milliseconds. |
val backend: String |
Which analysis backend wrote the artifact underneath. |
val libraryVersion: String |
The version of that backend's own library. |
val distance: SpectralEmbeddingDistance |
Which of the five metrics the distances were measured with. |
val method: String |
MDS or t-SNE. withSpectralProjection names its series from this. |
val scores: DoubleArray |
The MDS eigenvalue scores. Empty for a t-SNE projection. |
val proportion: DoubleArray |
The share of the total each MDS dimension retains. Empty for a t-SNE projection. |
val perplexity: Double? |
The perplexity t-SNE ran with. Null for an MDS projection. |
val seed: Long? |
The seed t-SNE ran with, which is fixed by default so a projection repeats. Null for MDS. |
val cost: Double? |
The divergence t-SNE finished at, lower being a closer fit. Null for an MDS projection. |
SpeechEmbeddingMds.kt
Speech embedding: decode, pool, and project the learned speech frames stored in an analysis artifact.
speechEmbeddingAnalysis submits the analysis.embed_speech job, and the worker keeps two
things: the full float16 frame tensor, and its own four-axis MDS preview over cosine distance.
This file is the second route to a picture. decodeSpeechEmbeddingMessagePack reads the tensor
into a SpeechEmbeddingFrames bounded to a time window, then .mds() or .tsne() fits the
projection in Field through Smile's smileMds and smileTsne. Changing the range, the pooling,
the distance, the dimension count, the perplexity or the seed re-fits locally and never re-runs
the speech model.
The payload stores float64 times and float16 vectors as little-endian MessagePack binary fields,
and a private half-float expansion reads them, so nothing here needs JVM float16 support. The
decoder rejects any format other than field.audio.speech-embedding.v1, and any dtype pair
other than those two.
Pooling and hop are independent. poolMillis is the width of a centred mean window and
hopMillis is the spacing between the vectors that come out of it. Both round to the frame rate
recorded in the payload, and omitting them preserves the model's own frames one for one. Pooling
runs as a rolling sum, so a window covering many source rows costs one pass over the range
instead of one pass per output row, and maxFrames sampling happens after pooling and keeps time
order.
client.speechEmbeddingSmileTsnePreview(result).thenAccept { preview ->
display("speech space", speechEmbeddingPreviewVisualizationHtml(preview))
}
withSpeechMds and withSpeechTsne replace a preview's projection axes with the locally fitted
ones and stamp the owner, library and method into both the series metadata and the preview's
analysis object, which is how a Field-fitted view is told apart from the worker's cached one on
the page. Both require at least four dimensions, because
speechEmbeddingPreviewVisualizationHtml draws axes 0/1 and 2/3 as two phase portraits.
Every projection result carries the frame times and source frame indices it was fitted from alongside the backend, model id, model revision and WavLM layer, so a trajectory on screen can be traced back to the rows and the checkpoint behind it. The coordinates themselves are navigational: axis numbering is specific to one fit, and comparing coordinate 0 between two backends or two independently fitted recordings has no meaning. Use the stored vectors for that. See Speech-reactive learned spaces.
src/jvmMain/kotlin/afield/audio/ai/SpeechEmbeddingMds.kt — read the source
Top-level functions
SpeechEmbeddingFrames
A bounded, time-preserving frame range decoded from a speech embedding MessagePack artifact.
| Declaration | Summary |
|---|---|
val timesSeconds: DoubleArray |
When each retained frame sits in the recording, in seconds, read from the payload's own clock. |
val vectors: Array<FloatArray> |
One model vector per retained frame, each sourceDimensions long, pooled by centred mean. |
val sampledFrameIndices: IntArray |
For each retained frame, its index in the payload's own frame numbering. |
val sourceFrameCount: Int |
Frames in the whole payload, before any time range was applied. |
val pooledFrameCount: Int |
Pooled rows the chosen range produced, before sampling down to maxFrames. The retained frame count is this or maxFrames, whichever is smaller. |
val sourceDimensions: Int |
The width of the model's own vectors, and so of every row of vectors. |
val frameRateHz: Double |
The model's native frame rate, which sets the smallest pool and hop that can be asked for. |
val poolMillis: Double |
The pooling window actually used, in milliseconds. A request is rounded to a whole number of native frames and held to at least one, so this can differ from what was asked for. |
val hopMillis: Double |
The hop actually used, in milliseconds, rounded the same way as SpeechEmbeddingFrames.poolMillis. |
val backend: String |
Which speech worker produced the payload. |
val modelId: String |
The model it ran. |
val modelRevision: String |
The revision of that model, so a trajectory can be traced back to one checkpoint. |
val wavlmLayer: Int? |
Which WavLM layer the vectors were read from, or null where the backend recorded none. |
SpeechEmbeddingMds
Smile MDS coordinates attached to the exact speech-frame times that were projected.
| Declaration | Summary |
|---|---|
val timesSeconds: DoubleArray |
When each projected frame sits in the recording, in seconds, carried over from the frames it was fitted from. |
val coordinates: Array<DoubleArray> |
One point per projected frame, as long as the number of dimensions asked for. These axes are for navigating a single fit: the numbering belongs to this fit alone, so coordinate 0 of two separately fitted recordings are not comparable. Compare SpeechEmbeddingFrames.vectors instead. |
val sampledFrameIndices: IntArray |
For each projected frame, its index in the payload's own frame numbering. |
val sourceFrameCount: Int |
SpeechEmbeddingFrames.sourceFrameCount of the frames this was fitted from. |
val pooledFrameCount: Int |
SpeechEmbeddingFrames.pooledFrameCount of the frames this was fitted from. |
val poolMillis: Double |
The pooling window those frames were built with, in milliseconds. |
val hopMillis: Double |
The hop those frames were built with, in milliseconds. |
val backend: String |
Which speech worker produced the payload underneath. |
val modelId: String |
The model it ran. |
val modelRevision: String |
The revision of that model. |
val wavlmLayer: Int? |
Which WavLM layer the vectors were read from, or null where the backend recorded none. |
val distance: SmileMdsDistance |
The metric the distances were measured with. |
val scores: DoubleArray |
The eigenvalue scores of the fit, one per dimension. |
val proportion: DoubleArray |
The share of the total each dimension retains, one per dimension. |
SpeechEmbeddingTsne
Smile t-SNE coordinates attached to the exact speech-frame times that were projected.
| Declaration | Summary |
|---|---|
val timesSeconds: DoubleArray |
When each projected frame sits in the recording, in seconds, carried over from the frames it was fitted from. |
val coordinates: Array<DoubleArray> |
One point per projected frame, as long as the number of dimensions asked for. As with MDS these axes are for navigating a single fit, and t-SNE adds its own caution: the distances between well-separated clusters carry much less than the grouping does. |
val sampledFrameIndices: IntArray |
For each projected frame, its index in the payload's own frame numbering. |
val sourceFrameCount: Int |
SpeechEmbeddingFrames.sourceFrameCount of the frames this was fitted from. |
val pooledFrameCount: Int |
SpeechEmbeddingFrames.pooledFrameCount of the frames this was fitted from. |
val poolMillis: Double |
The pooling window those frames were built with, in milliseconds. |
val hopMillis: Double |
The hop those frames were built with, in milliseconds. |
val backend: String |
Which speech worker produced the payload underneath. |
val modelId: String |
The model it ran. |
val modelRevision: String |
The revision of that model. |
val wavlmLayer: Int? |
Which WavLM layer the vectors were read from, or null where the backend recorded none. |
val distance: SmileMdsDistance |
The metric the distances were measured with. |
val perplexity: Double |
The perplexity the fit ran with, which sets how large a neighbourhood each frame is placed against. |
val seed: Long |
The seed the fit ran with. It is fixed by default, so the same frames give the same picture. |
val cost: Double |
The divergence the fit finished at, lower being a closer one. |
SpeechEmbeddingVisualization.kt
Phase portrait renderers for speech and spectral embedding spaces, synchronized with a shared audio cursor.
Two kinds of space are supported. Speech embeddings come from learned models such as WavLM and
use voice activity as the opacity modulator. Spectral embeddings come from
SpectralEmbeddingProjection and use spectral flux instead. Both draw two phase portraits
(axes 0/1 and 2/3) above an activity strip, with hover audition and click-to-seek wired into
the same field-time-* event bus as the audio player.
Each kind has three entry points: a single-panel live variant that fetches from an
AnalysisResult, a single-panel preview variant that works from a cached AnalysisPreview,
and a multi-panel dashboard that stacks several panels with a shared cursor. The spectral
variants accept the MDS or t-SNE preview that withSpectralProjection attaches to an
AnalysisPreview.
src/jvmMain/kotlin/afield/audio/ai/SpeechEmbeddingVisualization.kt — read the source
Used in: Visualizers, Displays, and Little Instruments
Top-level functions
TrackedAnalysis.kt
One explicit analysis request, followed from submission to durable result.
FieldAudioClient has four separate verbs for the stages of that, each returning its own future:
submit, watch, awaitAnalysis and inspect. trackAnalysis composes them into a single
future owned by the calling run, with a callback at each stage, which is what a box asking one
question of the service usually wants. Ownership divides along the way: the subscription and the
polling belong to the run and are released when it stops, while the job belongs to the service and
goes on, since another client may be waiting on the same result.
src/jvmMain/kotlin/afield/audio/ai/TrackedAnalysis.kt — read the source
Top-level functions
| Declaration | Summary |
|---|---|
fun FieldAudioClient.trackAnalysis(request: ModelOperation, onAccepted: (JobAccepted) -> Unit = {}, onProgress: (JobAccepted, ProgressEvent) -> Unit = { _, _ -> }, onCompleted: (JobRecord) -> Unit = {}, onResult: (AnalysisResult) -> Unit = {}, onError: ((Throwable) -> Unit)? = null): CompletableFuture<AnalysisResult> |
Submit one explicit analysis request, watch progress, await its result and inspect final job state. Publication and resources belong to the calling run through runAsync. Stop/rerun cancels local polling and closes the watch, including a handshake that finishes late. Durable service jobs and shared clients are retained: abandoning a subscription does not cancel work another client may use. Progress transport failures fail this operation; final job state comes from FieldAudioClient.inspect. |
UnderfitOperations.kt
Request builder for source-conditioned Stable Audio 3 generation, with LoRAs named by digest.
underfitCandidates is a ModelOperation factory and nothing more: it validates its arguments
here in the JVM, names underfit.stable_audio_3 as the backend, and packs everything into the
snake_case keys that backend expects. Submitting it, waiting on it and auditioning the candidate
board are FieldAudioClient's job. Stable Audio 3 and Underfit themselves stay in an optional
Python worker, so the Kotlin here knows a wire operation name and a parameter vocabulary rather
than a model.
The source AssetRegion is sent as init audio, and initNoiseLevel on
UnderfitGenerationControls sets how far generation may travel from it. One candidate is
requested per seed, so the default 0L until 8L asks for eight. UnderfitOperation chooses
between the generate.style and generate.variations wire operations, and UnderfitModel
carries the duration ceiling of each checkpoint pack, which underfitCandidates checks
durationSeconds against before building anything.
A LoRA is identified by a path relative to the worker's configured LoRA root plus the lowercase
SHA-256 of its .safetensors bytes, and both travel with the request. So a request names one
exact set of weights rather than a filename that may have been retrained since.
UnderfitLoraCheckpoint.fromFile computes that digest from a local file, which assumes Field and
the worker share a filesystem. UnderfitLoraCheckpoint's own init block checks what it can
before a request exists: the path stays inside the root and ends in .safetensors, the digest is
exactly 64 lowercase hex digits, strength is finite and non-negative, and the LoRA's denoising
interval satisfies
0 <= intervalMin <= intervalMax <= 1. UnderfitGenerationControls validates its sampling knobs
the same way, independently of any one request.
src/jvmMain/kotlin/afield/audio/ai/UnderfitOperations.kt — read the source
Used in: Field Audio AI
Top-level functions
UnderfitModel
enum class UnderfitModel(val id: String, val maximumDurationSeconds: Double)
Stable Audio 3 checkpoint packs supported by the pinned Underfit worker.
Constants: SMALL_MUSIC, SMALL_MUSIC_BASE, SMALL_SFX, SMALL_SFX_BASE, MEDIUM, MEDIUM_BASE.
UnderfitOperation
enum class UnderfitOperation(val wireName: String)
Source-conditioned operation exposed by the Underfit worker.
Constants: STYLE, VARIATIONS.
UnderfitLoraCheckpoint
One immutable Underfit LoRA as the worker sees it.
UnderfitGenerationControls
Stable Audio 3 sampling controls honored by the pinned Underfit worker.
| Declaration | Summary |
|---|---|
val steps: Int |
How many denoising steps the sampler takes. Between 1 and 1000. Sent as steps. |
val cfg: Double |
Classifier-free guidance strength. Non-negative and finite. Sent as cfg. |
val initNoiseLevel: Double |
How far generation may travel from the source audio, between zero and one: zero preserves the source and one approaches a fresh text-conditioned sample. Sent as init_noise_level. |
val samplerType: String? |
Names one of the worker's samplers. Null leaves the key out of the request, so the worker picks; a blank string is rejected here. Sent as sampler_type. |
val cfgIntervalMin: Double |
Start of the denoising range over which guidance applies. The pair must satisfy 0 <= cfgIntervalMin <= cfgIntervalMax <= 1. Sent as cfg_interval_min. |
val cfgIntervalMax: Double |
End of that range, at or after cfgIntervalMin and at most one. Sent as cfg_interval_max. |
val cfgRescale: Double |
Guidance rescaling. Non-negative and finite. Sent as cfg_rescale. |
val cfgNormThreshold: Double |
Guidance norm threshold. Non-negative and finite. Sent as cfg_norm_threshold. |
val durationPaddingSeconds: Double |
Duration padding, in seconds. Non-negative and finite. Sent as duration_padding_seconds. |
val apgScale: Double |
APG guidance scale. Non-negative and finite. Sent as apg_scale. |
val chunkedDecode: Boolean? |
Whether the worker decodes in chunks. Null leaves the key out of the request, so the worker picks. Sent as chunked_decode. |