Skip to content

remap

Maps a value through a curve defined by a table of breakpoints.

The breakpoints are given as two arrays of the same length, one with the x coordinates and one with the y coordinates. For each input value, remap finds the two breakpoints that bracket it and returns the matching y, interpolated in one of five ways: linear, nearest, previous, next, or a shape-preserving cubic. A separate setting decides what happens when the input falls outside the table.

The input can be a k-rate value, a k-rate array, or an audio signal. With an audio input, every sample goes through the curve, so remap works as a waveshaper with a transfer function drawn by the breakpoints.

Syntax

y:k = remap(x:k, xdata[], ydata[], mode:i, bounds:i [, fill:i])
y:k[] = remap(x:k[], xdata[], ydata[], mode:i, bounds:i [, fill:i])
y:a = remap(x:a, xdata[], ydata[], mode:i, bounds:i [, fill:i])
ky remap kx, kxdata[], kydata[], imode, ibounds [, ifill]
ky[] remap kx[], kxdata[], kydata[], imode, ibounds [, ifill]
ay remap ax, kxdata[], kydata[], imode, ibounds [, ifill]

xdata and ydata may be i-rate or k-rate arrays, in any combination.

Initialization

mode -- how to interpolate between the two breakpoints that bracket the input:

  • 0 = linear.
  • 1 = nearest: the y of the closer breakpoint. When the input is exactly halfway, the left breakpoint wins.
  • 2 = previous: the y of the breakpoint at or to the left of the input. This is a step function that changes on each breakpoint.
  • 3 = next: the y of the first breakpoint to the right of the input. This is a step function, one breakpoint ahead of previous.
  • 4 = cubic: a shape-preserving piecewise cubic (PCHIP, Fritsch-Carlson). The curve is smooth and passes through every breakpoint. Unlike a natural cubic spline, it does not overshoot: between two breakpoints the curve stays within their y values, so a monotonic table gives a monotonic curve and a flat segment stays flat.

bounds -- what to do when the input is lower than the first x or higher than the last x:

  • 0 = error: stop the instrument with a performance error.
  • 1 = clamp: output the first or the last y.
  • 2 = fill: output fill.
  • 3 = extrapolate: continue the first or the last segment beyond the table. With mode 0 the segment is a straight line, and with mode 4 it is the cubic of the end segment. The step modes (1, 2 and 3) pick a y from the two breakpoints of the end segment: below the table, nearest and previous give the first y and next gives the second; above it, nearest and next give the last y and previous gives the one before.

fill -- the value output when bounds is 2. Defaults to 0. It is ignored for any other bounds setting.

Any other value of mode or bounds causes an initialization error.

Performance

x -- the input: a k-rate value, a k-rate array, or an audio signal.

xdata -- the x coordinates of the breakpoints. They must be strictly increasing.

ydata -- the y coordinates of the breakpoints. They can take any values.

y -- the output. For an array input, y is an array of the same length as x, and each element is computed on its own, so x does not need to be sorted. If x changes length during performance, y follows.

Both tables must hold at least two breakpoints and have the same length. A length mismatch causes a performance error. The lengths are checked at every call, since a global array can be resized by another instrument while the note runs.

When both tables are i-rate arrays, remap checks at initialization that xdata is strictly increasing and reports an initialization error otherwise. k-rate tables can be rewritten at any control period, and remap uses the new values at once. They are not checked: keeping a k-rate xdata strictly increasing is up to the orchestra, and an unsorted xdata gives meaningless results.

The output array may also be one of the tables, for example ydata = remap(x, xdata, ydata, 1, 1). Every element of the input is then mapped through the table as it was before the call.

remap has no i-rate version. Its output is first computed in the first control period, so an init-time read of a k-rate output gives 0.

📝 Breakpoints and step modes

On a breakpoint, linear, nearest and cubic return the y of that breakpoint. previous also returns it, except on the last breakpoint, where it returns the y of the one before. next returns the y of the following breakpoint, except on the last one, where it returns its own y. With x = [0, 1, 3, 4] and y = [0, 2, 2, 6], an input of 4 gives 2 with previous and 6 with next.

The search for the bracketing breakpoints takes the same time whatever the input, so an unsorted array costs the same as a ramp. In cubic mode, the coefficients of a segment are computed only when needed, without any extra memory, even when the breakpoints change at k-rate.

Examples

Modes and bounds

This example prints what each mode and each bounds policy returns for a few inputs. The flat middle segment shows that the cubic mode does not overshoot. It uses the file remap.csd.

Interpolation modes and out-of-bounds policies.
<CsoundSynthesizer>
<CsOptions>
-n -d -m0
</CsOptions>
<CsInstruments>
sr = 48000
ksmps = 32
nchnls = 1
0dbfs = 1

; Prints how remap reads one table with each interpolation mode and with
; each out-of-bounds policy. Nothing is rendered.

instr 1
  ; breakpoints (0, 0), (1, 2), (3, 2), (4, 6): the middle segment is flat
  xs:i[] = fillarray(0, 1, 3, 4)
  ys:i[] = fillarray(0, 2, 2, 6)

  ; inputs inside the table, given as one k-rate array
  probe:k[] = fillarray(0.25, 1, 2, 3.75)

  ; mode: 0 linear, 1 nearest, 2 previous, 3 next, 4 cubic (PCHIP)
  lin:k[]  = remap(probe, xs, ys, 0, 1)
  near:k[] = remap(probe, xs, ys, 1, 1)
  prev:k[] = remap(probe, xs, ys, 2, 1)
  next:k[] = remap(probe, xs, ys, 3, 1)
  cub:k[]  = remap(probe, xs, ys, 4, 1)

  printf("x          %6.3f %6.3f %6.3f %6.3f\n", 1, probe[0], probe[1], probe[2], probe[3])
  printf("linear     %6.3f %6.3f %6.3f %6.3f\n", 1, lin[0], lin[1], lin[2], lin[3])
  printf("nearest    %6.3f %6.3f %6.3f %6.3f\n", 1, near[0], near[1], near[2], near[3])
  printf("previous   %6.3f %6.3f %6.3f %6.3f\n", 1, prev[0], prev[1], prev[2], prev[3])
  printf("next       %6.3f %6.3f %6.3f %6.3f\n", 1, next[0], next[1], next[2], next[3])
  printf("cubic      %6.3f %6.3f %6.3f %6.3f\n\n", 1, cub[0], cub[1], cub[2], cub[3])

  ; inputs outside the table: bounds 1 clamp, 2 fill, 3 extrapolate
  ; (bounds 0 would stop the instrument with a performance error)
  out:k[] = fillarray(-1, 5)
  clamp:k[] = remap(out, xs, ys, 0, 1)
  fill:k[]  = remap(out, xs, ys, 0, 2, -100)
  extl:k[]  = remap(out, xs, ys, 0, 3)
  extc:k[]  = remap(out, xs, ys, 4, 3)

  printf("x                 %8.3f %8.3f\n", 1, out[0], out[1])
  printf("clamp             %8.3f %8.3f\n", 1, clamp[0], clamp[1])
  printf("fill -100         %8.3f %8.3f\n", 1, fill[0], fill[1])
  printf("extrapolate lin.  %8.3f %8.3f\n", 1, extl[0], extl[1])
  printf("extrapolate cub.  %8.3f %8.3f\n", 1, extc[0], extc[1])

  ; one control period is enough
  turnoff
endin
</CsInstruments>
<CsScore>
i 1 0 1
e
</CsScore>
</CsoundSynthesizer>

Its output is:

x           0.250  1.000  2.000  3.750
linear      0.500  2.000  2.000  5.000
nearest     0.000  2.000  2.000  6.000
previous    0.000  2.000  2.000  2.000
next        2.000  2.000  2.000  6.000
cubic       0.688  2.000  2.000  4.625

x                   -1.000    5.000
clamp                0.000    6.000
fill -100         -100.000 -100.000
extrapolate lin.    -2.000   10.000
extrapolate cub.    -0.667    7.333

Scale quantizer

When the same array is used for xdata and ydata, the nearest mode snaps any input to the closest value in the table. Here the table holds a pentatonic scale, and a single call with an array input quantizes a three-note random chord. It uses the file remap-quantize.csd.

Quantizing an array of random pitches to a scale.
<CsoundSynthesizer>
<CsOptions>
; Select audio/midi flags here according to platform
-odac     ;;;realtime audio out
;-iadc    ;;;uncomment -iadc if realtime audio input is needed too
; For Non-realtime ouput leave only the line below:
; -o remap-quantize.wav -W ;;; for file output any platform
</CsOptions>
<CsInstruments>
sr = 48000
ksmps = 32
nchnls = 2
0dbfs = 1

; remap as a scale quantizer. With the same array for x and y, the nearest
; mode snaps any input to the closest value of the table. Here the table is
; C minor pentatonic over two octaves, and the input is an array of three
; random pitches, so one call quantizes a whole chord.

instr 1
  scale:i[] = fillarray(48, 51, 53, 55, 58, 60, 63, 65, 67, 70, 72)

  raw:k[] init 3
  chord:k[] init 3
  count:k init 0
  if metro(2) == 1 then
    raw[0] = random:k(46, 60)
    raw[1] = random:k(55, 68)
    raw[2] = random:k(62, 74)
    ; mode 1 nearest, bounds 1 clamp: pitches beyond the scale stick to its ends
    chord = remap(raw, scale, scale, 1, 1)
    count += 1
    printf("raw %5.2f %5.2f %5.2f -> %2d %2d %2d\n", count, \
           raw[0], raw[1], raw[2], chord[0], chord[1], chord[2])
  endif

  env:k = madsr(0.02, 0.1, 0.8, 0.3)
  voices:a = vco2(0.1, cpsmidinn(chord[0]), 12) + \
             vco2(0.1, cpsmidinn(chord[1]), 12) + \
             vco2(0.1, cpsmidinn(chord[2]), 12)
  voices = moogladder(voices * env, 1800, 0.3)
  out(voices, voices)
endin
</CsInstruments>
<CsScore>
i 1 0 8
e
</CsScore>
</CsoundSynthesizer>

Waveshaper

With an audio input, the breakpoints draw a transfer curve. This one is a soft saturation that never goes above 0.8. The cubic mode rounds off the corners that the linear mode leaves at each breakpoint, which gives a smoother tone. It uses the file remap-waveshaper.csd.

Waveshaping a sine with a breakpoint transfer curve.
<CsoundSynthesizer>
<CsOptions>
; Select audio/midi flags here according to platform
-odac     ;;;realtime audio out
;-iadc    ;;;uncomment -iadc if realtime audio input is needed too
; For Non-realtime ouput leave only the line below:
; -o remap-waveshaper.wav -W ;;; for file output any platform
</CsOptions>
<CsInstruments>
sr = 48000
ksmps = 32
nchnls = 2
0dbfs = 1

; remap with an audio input is a waveshaper: every sample goes through the
; transfer curve drawn by the breakpoints. The curve below is a soft
; saturation that never exceeds 0.8. The drive rises during the note, so the
; sine is pushed further and further into the knee.

instr 1
  mode:i = p4
  prints(mode == 0 ? "linear transfer curve\n" : "cubic transfer curve\n")

  ; input level -> output level
  curvex:i[] = fillarray(-1, -0.5, 0, 0.5, 1)
  curvey:i[] = fillarray(-0.8, -0.6, 0, 0.6, 0.8)

  drive:k = expseg(0.1, p3, 4)
  sig:a = poscil(drive, 110)

  ; bounds 1 clamp: past +-1 the output holds at +-0.8
  shaped:a = remap(sig, curvex, curvey, mode, 1)

  env:k = linseg(0, 0.05, 1, p3 - 0.15, 1, 0.1, 0)
  shaped *= 0.3 * env
  out(shaped, shaped)
endin
</CsInstruments>
<CsScore>
; p4 = interpolation mode
i 1 0   4 0  ; linear: corners at every breakpoint, brighter
i 1 4.5 4 4  ; cubic: the same curve with rounded corners, smoother
e
</CsScore>
</CsoundSynthesizer>

See also

Linear and Exponential Generators

bpf, linlin, tablei

Credits

Author: Pasquale Mainolfi
Italy
New in Csound version 7.0
October 2026