Skip to content

Recording & Input

Resonon can capture audio from microphones, instruments, and other input devices. Route inputs to tracks, monitor in real time with effects, record multi-track takes, and save recordings to disk.

Use audio_devices() to list all audio devices on your system. Each entry is a dict with these fields:

FieldTypeDescription
nameStringDevice name
input_channelsNumberNumber of input channels
output_channelsNumberNumber of output channels
sample_rateNumberDefault sample rate (Hz)
is_defaultBooleanWhether this is the system default
let devices = audio_devices();
for d in devices {
if d.input_channels > 0 {
PRINT d.name + " (" + d.input_channels + " inputs)";
}
}

For projects that use a fixed recording setup, define named input channels in your configuration file (~/.resonon/config.toml):

[audio.input]
device = "Focusrite Scarlett 2i2"
[audio.input.channels]
mic = [0]
vocals = [0, 1]
guitar = [2]

Channel mapping rules:

  • Single-element array (e.g. [0]) — mono input from that channel
  • Two-element array (e.g. [0, 1]) — stereo input from those channels

Once configured, use the channel name with .input() instead of the device name:

let vox = AudioTrack("vocals");
vox = vox.input("vocals"); // Resolves to channels [0, 1] on the Scarlett

Use .input(name) on an AudioTrack to route an audio input:

let mic = AudioTrack("mic_track");
mic = mic.input("MacBook Pro Microphone");

The argument can be either a device name or a named channel from your [audio.input.channels] configuration. If no audio input stream is active yet, .input() starts one automatically for the named device — no configuration file is required for basic use. The method is chainable — it returns the AudioTrack.

Arm a track for recording with .arm(). The track must have an input source configured via .input() first — calling .arm() without one produces an error. Only armed tracks capture audio when RECORD is triggered. Disarm with .disarm().

mic = mic.arm();
// ... record ...
mic = mic.disarm();

Control whether the input signal is heard through the output with .monitor(mode):

ModeBehavior
"in"Always hear input through output
"auto"Hear input only when the track is armed or recording
"off"Input is not monitored (silent)
mic = mic.monitor("in"); // Always hear the microphone
mic = mic.monitor("auto"); // Hear only when armed/recording
mic = mic.monitor("off"); // Silence monitoring

Starts playback and begins capturing audio on all armed tracks simultaneously:

RECORD;

Pauses transport. Recording tracks stay in the recording state (arm state 2) — the RecordEngine thread continues accumulating input audio. Use .take() or STOP to finalize the recording:

PAUSE;

Stops recording and resets transport to the beginning. Recording tracks transition to armed (2→1) so you can RECORD again without re-arming. Mutes master output.

STOP;
CommandStops transportFinalizes recordingKeeps armedMutes master
PAUSEYesNoYesNo
STOPYes (+ reset)Yes (2→1)YesYes

A typical recording session:

mic = mic.arm();
mic = mic.monitor("auto");
RECORD;
// ... play or sing ...
PAUSE;

Arm multiple tracks to record from different inputs simultaneously. RECORD captures on all armed tracks at once:

let vocals = AudioTrack("vocals");
vocals = vocals.input("mic").arm().monitor("auto");
let guitar = AudioTrack("guitar");
guitar = guitar.input("guitar").arm().monitor("auto");
RECORD;
// ... perform ...
PAUSE;
// Each track has its own independent recording
let vocal_take = vocals.take();
let guitar_take = guitar.take();

Punch recording lets you define a cycle range where recording is active. This is useful for overdubbing specific sections or correcting part of a performance without re-recording everything.

Use .punch(in, out) to set both punch-in and punch-out points. Recording activates at the in cycle and deactivates at the out cycle:

let rec = AudioTrack("rec");
rec.input("mic").arm();
rec.punch(4, 12); // record only cycles 4–12
RECORD; // playback starts; recording activates at cycle 4

Set only one end with .punch_in(cycle) or .punch_out(cycle):

rec.punch_in(8); // open-ended: recording starts at cycle 8, no auto-stop
rec.punch_out(16); // recording starts immediately, stops at cycle 16
rec.clear_punch(); // remove all punch points
rec.is_punched(); // → true if punch points are set
  • Punch transitions happen at cycle boundaries, so they align with musical time.
  • Punch points persist across RECORD/STOP cycles — set once, record multiple takes.
  • STOP finalizes the current take but does not clear punch points.
  • PAUSE during a punch window finalizes a partial take.
  • .disarm() sets the track to disarmed; punch points remain for re-arming.
MethodReturnsDescription
.punch(in, out)AudioTrackSet punch-in and punch-out cycles
.punch_in(cycle)AudioTrackSet punch-in only (open-ended)
.punch_out(cycle)AudioTrackSet punch-out only (starts immediately)
.clear_punch()AudioTrackRemove all punch points
.is_punched()BooleanTrue if punch points are configured

There are two ways to get recordings from a track:

Returns a RecordedAudio object for inspection and saving, or NUL if nothing was captured. Consumes the buffer — calling .take() again returns NUL until a new recording is made.

let take1 = mic.take();
if take1 != NUL {
PRINT take1.duration();
take1.save("take1.wav");
}

Saves the recording directly to a WAV file and returns the AudioTrack (chainable). Also consumes the buffer.

mic.save("takes/vocals_01.wav");

Both approaches consume the recording buffer, so use one or the other — not both for the same take.

Monitored input flows through the track’s effect chain before reaching the output. This means you can apply effects to a live input signal:

let vox = AudioTrack("vocals");
vox = vox.input("mic");
vox = vox.load_effect(Reverb(0.3, 0.5));
vox = vox.load_effect(Compressor(-18, 4, 0.01, 0.1));
vox = vox.monitor("in");
// Now hear your voice with reverb and compression in real time

Live effects also work with routing. Use .send_to() to route processed input to other tracks:

let reverb_bus = AudioTrack("reverb_bus");
reverb_bus = reverb_bus.load_effect(Reverb(0.5, 0.7));
let vox = AudioTrack("vocals");
vox = vox.input("mic").monitor("in");
vox = vox.send_to(reverb_bus, 0.4); // Send 40% to reverb bus
MethodReturnsDescription
.input(name)AudioTrackRoute an input device or named channel
.arm()AudioTrackArm track for recording
.disarm()AudioTrackDisarm track
.monitor(mode)AudioTrackSet monitor mode: "in", "auto", or "off"
.take()RecordedAudio / NULRetrieve recording (consumes buffer)
.save(path)AudioTrackSave recording to WAV file (consumes buffer)
.is_armed()BooleanTrue if the track is armed or recording
.is_recording()BooleanTrue if the track is actively recording
MethodReturnsDescription
.duration()NumberLength of the recording in seconds
.samples()NumberTotal sample count
.sample_rate()NumberSample rate in Hz
.save(path)RecordedAudioSave to a WAV file (chainable)
let take1 = mic.take();
if take1 != NUL {
PRINT take1.duration();
PRINT take1.samples();
PRINT take1.sample_rate();
take1.save("recording.wav");
}

Chaining example — save and continue using the recording:

let take1 = mic.take();
if take1 != NUL {
take1.save("backup.wav").save("archive/backup_copy.wav");
}
// 1. Discover input devices
let devices = audio_devices();
for d in devices {
if d.input_channels > 0 {
PRINT d.name;
}
}
// 2. Create tracks with different inputs
let vocals = AudioTrack("vocals");
vocals = vocals.input("mic");
vocals = vocals.load_effect(Compressor(-18, 4, 0.01, 0.1));
let guitar = AudioTrack("guitar");
guitar = guitar.input("guitar");
guitar = guitar.load_effect(Reverb(0.2, 0.4));
// 3. Arm and enable auto monitoring
vocals = vocals.arm().monitor("auto");
guitar = guitar.arm().monitor("auto");
// 4. Record
RECORD;
// ... perform ...
PAUSE;
// 5. Retrieve and save recordings
let vocal_take = vocals.take();
if vocal_take != NUL {
PRINT "Vocals: " + vocal_take.duration() + "s";
vocal_take.save("takes/vocals_01.wav");
}
guitar.save("takes/guitar_01.wav");
// 6. Disarm and turn off monitoring
vocals = vocals.disarm().monitor("off");
guitar = guitar.disarm().monitor("off");

When recording while playing back, the recorded audio is shifted forward in time by the round-trip I/O latency (input + output device latency). Use recording_offset() to compensate:

// Set the global recording offset (trims the front of all recordings)
recording_offset(10); // 10ms — adjust to match your audio interface
// Read the current offset
let offset = recording_offset();

The offset value depends on your audio interface and driver settings. Start with your interface’s advertised round-trip latency and fine-tune by recording a click track.

Use audio_latency() to query the current system latency:

let lat = audio_latency();
show(lat["buffer_ms"]); // Audio buffer latency in ms
show(lat["pdc_ms"]); // PDC path latency in ms
show(lat["sample_rate"]); // Current sample rate

You can also set this in your project config file for persistence:

[audio]
recording_offset_ms = 10.0

For external hardware (outboard effects, reamping), use .delay() to compensate for the hardware’s round-trip latency:

// The signal leaves Resonon, goes through a guitar amp, and comes back
// with ~8ms of additional latency
guitar.delay(8);
// Read the current delay
let d = guitar.delay();

Track delay shifts the track’s output later in time. The PDC system automatically compensates other tracks so everything stays aligned. Recordings on a delayed track are also shifted to match.