Skip to content

Clips and Tracks ​

When you arrange music in a DAW, you are really filling in a small, predictable structure. Learn that structure once and every arrangement feature makes sense. This page describes the project model libsonare uses, in plain language, before you read the Project Editing guide.

The one-sentence summary

A project holds tracks, each track holds clips, and every clip sits at a position on a shared timeline measured in musical time (bars and beats).

The project model, top to bottom ​

A project is the whole song. It is the container that everything else lives inside, and it is the thing you save, load, and eventually bounce to a finished file.

Inside the project are tracks. A track is a single lane of content — think of one horizontal row in the arrangement view. Each track is an audio track, a MIDI track, or an aux (routing/return) track:

  • An audio track holds recorded or imported sound (a vocal take, a guitar, a drum loop).
  • A MIDI track holds notes, not sound. The notes are instructions ("play C4 now, hold it for one beat") that only become audible when an instrument turns them into sound.
  • An aux track is a routing lane, such as an effect return or a submix. It is not where recorded content belongs.

Inside each track are clips. A clip is one block of content placed at a specific spot on the timeline. An audio clip points at a piece of audio; a MIDI clip contains a list of note events. You can have many clips on one track, side by side or with gaps between them.

ENGINE · LANE MIXERIDLE
The engine lane mixer — faders and mutes inside the playback engine

Three MIDI clips loop through the realtime engine: each track occupies a lane with its own channel strip, and the lanes sum into a master strip carrying the engine’s true-peak limiter. The faders call the strip setters and the mutes call setSoloMute — every band below is the engine’s separate solo previews through the master chain, re-rendered through renderOffline as you move the controls.

Lead fader
0 dB
Bass fader
3 dB
Drums fader
-8 dB
Mute lead
Mute bass
Mute drums
Project → tracks → clips → instrument
routes torendersProjectAudio track: VocalsAudio track: DrumsMIDI track: BassClip: verse vocalClip: chorus vocalClip: drum loopClip: bass lineMIDI destinationInstrument: NativeSynth/ SoundFont
Audio tracks already hold sound and just play. A MIDI track's clips hold notes, so the track separately routes to a destination bound to an instrument, which renders them at bounce time.

Musical time, not seconds ​

Here is the idea that trips up most beginners: clip positions are stored in musical time, not in seconds.

A clip does not start "at 4.27 seconds". It starts "at bar 3, beat 1". The unit underneath is the quarter note: positions and lengths are floating-point numbers where 1.0 is exactly one quarter note. One bar of 4/4 is 4.0, an eighth note is 0.5, a dotted quarter is 1.5 — they are not integer ticks or a 960-based resolution, even though the parameter names use Ppq (short for pulses per quarter note; e.g. startPpq, lengthPpq). Every clip start and length you set is in these quarter-note units, so lengthPpq: 4 is four quarter notes long.

Why musical time matters

Because positions are musical, the arrangement stays correct when you change the tempo. Move the song from 120 BPM to 100 BPM and "bar 3, beat 1" simply happens later in seconds — but it is still bar 3, beat 1. MIDI notes never drift off the grid. Audio is different: a recording has its own fixed speed, so after a tempo change an audio clip still starts on its bar, but the recording inside it plays at its native rate unless the clip is warped to follow. The conversion from musical time to actual sample positions happens later, when the project is compiled and rendered; the next section shows the arithmetic.

Converting seconds, samples, and PPQ ​

Clips and events are placed in PPQ, but analysis results (onsets, chords, beats) come back in seconds or samples, and warp anchors are in samples. Project has no conversion call, so the conversion is yours. It follows from the tempo map.

Constant tempo. One quarter note lasts 60 / bpm seconds:

text
seconds = ppq × 60 / bpm          ppq = seconds × bpm / 60
samples = round(seconds × sampleRate)

At 120 BPM and 48 kHz, ppq: 1 is 0.5 s, or 24000 samples.

Ramped segment. In a ramp each successive quarter note is a little shorter than the last, so the time to a position is the area under 60 / bpm (seconds per quarter note) from the segment's start. Inside a tempo segment with an endBpm, the tempo moves in a straight line per quarter note, from bpm0 at the segment's startPpq (p0) to bpm1 at the next segment's start (p1). With k = (bpm1 − bpm0) / (p1 − p0), the time from p0 to a position p inside the segment is

text
t = (60 / k) × ln(1 + k × (p − p0) / bpm0)        and back:  p = p0 + bpm0 × (e^(k × t / 60) − 1) / k

Add the segment's own start time, which is the sum of every earlier segment's duration.

Take the map from Tempo map and tempo segments: 120 BPM from PPQ 0, a ramp from 120 to 140 BPM starting at PPQ 16, and 140 BPM from PPQ 32. The first segment is 16 quarter notes at 120 BPM, so PPQ 16 is at 8 s. The ramp has k = (140 − 120) / 16 = 1.25, so PPQ 24 is 8 + 48 × ln(130 / 120) ≈ 11.842 s, and PPQ 32 is 8 + 48 × ln(140 / 120) ≈ 15.399 s (739163 samples at 48 kHz). A realtime engine given the same segments reports the same sample positions from sampleAtPpq(ppq).

A warp anchor. A warp anchor's warpSample counts samples from the clip's own start. To pin a downbeat the recording has at source sample 95000 onto PPQ 8, in a clip starting at PPQ 4 at a constant 120 BPM and 48 kHz: PPQ 8 is (8 − 4) × 0.5 s = 2 s after the clip start, so the anchor is { warpSample: 96000, sourceSample: 95000 }. Under a tempo map, take the difference of the two converted times instead.

How a MIDI track makes sound ​

An audio track is self-contained: it already holds sound, so it just plays. A MIDI track is different. Its clips only hold note events, so something has to perform those notes.

That something is reached through the track's MIDI destination. A destination is a numbered routing slot. When you bind an instrument — such as NativeSynth or the SoundFont player — to a destination and route the track to it, the track's notes are dispatched to that instrument, which synthesizes the actual audio.

StepWhat you doResult
1Add a MIDI track and a clipA lane with notes, but silent
2Put note events in the clipThe performance is written down
3Route the track to a destinationNotes have somewhere to go
4Bind an instrument to that destinationNotes become sound at render time

This separation is powerful: the same MIDI clip can be played by a synth today and a sampled instrument tomorrow, just by changing the destination's instrument.

Step 2, the performance is written down, is literally a score: a clip's note events are standard musical notation. Here is one such clip engraved, then performed; switch the instrument and the same written notes are rendered by a different voice.

MIDI · SCOREIDLE
The same MIDI, written as a score

The very same three-voice phrase as the piano roll above — melody, broken chords, and bass — engraved as standard notation on a grand staff. The notes, durations, beams, and clefs are read straight from the MIDI the engine plays; switch the instrument and the identical notes sound through a different built-in voice, drag the tempo and the reading speeds up. Press play to hear it: every sounding note lights up the instant it plays.

Instrument
Tempo
96 BPM

What you can do to clips and tracks ​

Once the structure exists, arranging is mostly a handful of operations.

Clip operations, the everyday moves:

  • Move a clip to a new position (and optionally to another track).
  • Set gain to make one clip louder or quieter than its neighbors.
  • Fade in / fade out so a clip enters and leaves smoothly instead of clicking.
  • Loop a clip so a short block repeats across a longer region.
  • Duplicate a clip to copy a part somewhere else.
  • Re-source a clip to point it at different underlying material.
  • Remove a clip you no longer want.

Track operations manage the lanes themselves: add a track, remove one, rename it, route it (bind it to a mixer strip / output, or set its MIDI destination), and change its kind between audio, MIDI, and aux.

Clips reference, they do not copy

Several clips can point at the same source material. Duplicating an audio clip shares the source rather than cloning the audio, which keeps projects small. Re-sourcing a clip changes only that clip; the others keep pointing at the shared source. Keep this in mind when you copy parts around.

Fade curves and crossfades ​

A fade multiplies the clip by a gain that runs from 0 to 1 (fade-in) or from 1 to 0 (fade-out) over the fade length. With x the fraction of the fade completed, the four curves setClipFade accepts are:

CurveGain at xGain halfwayAs a fade-inAs a fade-out
'linear'x0.5 (−6 dB)Even ramp in amplitudeEven ramp in amplitude
'equal-power'sin(x · π/2)0.707 (−3 dB)Rises early, then levels offHolds, then drops late
'exponential'x²0.25 (−12 dB)Stays quiet, then rises lateDrops early, then trails off quietly
'logarithmic'√x0.707 (−3 dB)Rises very earlyHolds longest, then drops at the end

A fade-out applies the same curve mirrored in time, with x counting the fraction of the fade still to go. The names describe the shape only: 'exponential' is a square law and 'logarithmic' a square-root law, not curves that are straight in decibels.

The choice matters most when two signals cross, as in a crossfade between overlapping clips, a loop seam, or a comp seam. What decides it is how alike the two signals are:

  • Unrelated material (two different takes, two different sounds): their powers add, so the gains should satisfy gain_out² + gain_in² = 1. An 'equal-power' pair (or a 'logarithmic' pair) does this, and the level stays flat. A 'linear' pair dips about 3 dB in the middle.
  • Near-identical material (the same recording on both sides, or a loop point chosen on matching audio): the waveforms add in phase, so the gains should satisfy gain_out + gain_in = 1. A 'linear' pair does this; an equal-power pair rises about 3 dB in the middle, because 0.707 + 0.707 = 1.41.

In general the gains that hold the level satisfy g_in² + g_out² + 2ρ·g_in·g_out = 1, where ρ is the correlation of the two sides (0 for unrelated, 1 for identical). noteStretch and renderNotes solve this for every seam.

Fade curves and what a crossfade sums to
fade-in gain00.250.50.75100.51fade progress xcrossfade, summed level−30+3dB00.51fade progress x+3.0 dB−3.0 dBnear-identicalunrelated
  • 'linear' x
  • 'equal-power' sin(x·π/2)
  • 'exponential' x²
  • 'logarithmic' √x
Left: the gain through a fade-in for each curve; the dots mark the halfway gains from the table. Right: a fade-out and a fade-in of the same curve, summed. Solid lines are near-identical material, which adds in amplitude; dashed lines are unrelated material, which adds in power. Linear stays flat for the first and dips 3 dB for the second, and equal-power does the reverse.

The same comparison by ear: two overlapping clips crossfaded by the engine, with the level of their sum measured through the seam.

CROSSFADE · FADE CURVESIDLE
Crossfade curves — what the seam sums to

Clip A fades out while clip B fades in over the shaded span; the top lanes are the gains the engine applied, and the lower trace is the measured level of their sum. With the same recording on both sides the two add in amplitude: linear stays flat, equal-power and logarithmic swell about 3 dB at the seam, and exponential dips about 6 dB. With two different chords they add in power: linear dips about 3 dB, equal-power and logarithmic stay flat, and exponential dips about 9 dB. A longer fade makes the dip or swell easier to hear.

Curve
Material
Fade length
2 s

The loop-seam crossfade (loopCrossfadePpq) and the comp-seam crossfade (crossfadePpq on a comp segment) are always equal-power, which suits unrelated material. A single fade-in or fade-out has nothing to cross with, so there the curve is a matter of how the fade should sound.

Source metadata and undo history ​

Two more edits sit above individual clips.

Audio-source metadata. Since several clips can share one source (see the warning above), it is often the source, not the clip, that needs to carry information. setAudioSourceMetadata(sourceId, contentHash, externalStemRole) attaches two informational strings to a source as one undoable edit: a contentHash a host can use to recognize the same underlying audio reappearing under a different source id (skip a redundant import, flag a stale reference), and an externalStemRole that records what an externally separated stem represents (for example "vocals" or "drums"), so a project built around stem separation can still tell its stems apart after a save and reload.

Bounded undo/redo memory. Every edit in this model — moveClip, setClipGain, addAutomationLane, and the rest — is retained on an undo stack so undo() / redo() can step back and forward through a session. Each edit command reports its own retained byte footprint, and Project.setMaxHistoryBytes(bytes) caps the combined memory the undo and redo stacks are allowed to hold; once the cap is reached, the oldest entries are evicted to make room. Setting the cap to zero disables retention entirely. setMaxUndoDepth caps the same history a different way, by entry count rather than bytes, and the two limits apply together.

How libsonare implements this

The model lives on the Project class in the editing engine. addTrack creates a track (audio or MIDI), and addMidiClip(startPpq, lengthPpq) returns { trackId, clipId } for a fresh MIDI track + clip; addClip adds an audio or MIDI clip to an existing track. All positions are floating-point quarter-note units (1.0 = one quarter note), so moveClip(clipId, newStartPpq, newTrackId?), setClipGain(clipId, gain), setClipFade(clipId, fadeIn, fadeOut), setClipLoop, duplicateClip, setClipSource, and removeClip work in musical time. Track lifecycle is removeTrack, renameTrack, setTrackKind, and setTrackRoute. A MIDI track reaches its instrument through setTrackMidiDestination(trackId, destinationId): the compiler stamps each MIDI clip with that id so the engine dispatches its events to the instrument bound at that destination. The whole project round-trips through JSON via toJson() / Project.fromJson(json), and musical time is converted to samples only at compile() / bounce().

Related: Project Editing, Takes and Comping, Mixing Basics