Your first semitone script
If you know Octave or MATLAB®, you already know most of semitone. The key things to understand upfront:
EXAMPLE 1 — SINE OSCILLATOR (runs in semitone and Octave)
% Simple sine oscillator fs = 44100; % sample rate f0 = 440.0; % A4 — concert pitch len = 0.01; % duration in seconds t = linspace(0, len, len * fs + 1); y = sin(2 * pi * f0 * t); disp(y);
EXAMPLE 2 — FOR LOOP & STRUCTS
% Group parameters in a struct osc = struct('freq', 440.0, 'amp', 0.8); osc.phase = 0.0; % 2^(1/12) per semitone step ratio = 2 ^ (1 / 12); for k = 0:11 f = osc.freq * ratio ^ k; disp(num2str(f, '%.2f')); end
Types & containers
The most significant deviation from Octave is the existence of a genuine scalar type. In Octave, everything is a matrix; in semitone a single number is genuinely scalar. This is invisible to your code but enables the chunked scalar execution mode.
| Type | Internal Type | Notes |
|---|---|---|
| double | Scalar<double> | Default numeric type — matches Octave |
| single | Scalar<float> | Single precision scalar |
| int8 … int64 | Scalar<int8_t> … | Signed integers |
| uint8 … uint64 | Scalar<uint8_t> … | Unsigned integers |
| logical | Scalar<bool> | Logical scalar |
| char | Scalar<char8> | Single ASCII character |
| matrix | NDArray<T> | 1 or 2-dimensional, column-major, 1-based indexing |
| struct | Struct | Named fields; internally, variable environment is a struct |
| cell array | CellArray | Heterogeneous container |
| float chunk | Chunk<float> | Audio buffer pointer — see chunked ccalar mode. Note that this type cannot be created inside a script. |
Built-in functions
All built-in functions follow Octave semantics unless noted.
TRIGONOMETRY
| sin(x), cos(x), tan(x) | Sine, cosine, tangent (radians) |
| asin(x), acos(x), atan(x) | Inverse trig |
| atan2(y, x), atan2d(y, x) | Four-quadrant arctangent; degree variant |
| sinh(x), cosh(x), tanh(x) | Hyperbolic |
| asinh(x), acosh(x), atanh(x) | Inverse hyperbolic |
| sind(x), cosd(x), tand(x) | Argument in degrees |
| sin1(x), cos1(x) | Argument in cycles (0–1) — not in Octave |
EXPONENTIAL & LOGARITHM
| exp(x) | e^x |
| log(x), log2(x), log10(x) | Natural, base-2, base-10 logarithm |
| sqrt(x) | Square root |
| abs(x) | Absolute value / complex modulus |
OTHER MATH OPERATIONS
| ceil(x) | Round toward +∞ |
| floor(x) | Round toward −∞ |
| round(x) | Round to nearest |
| mod(x, m) | Modulo — Octave sign convention |
| rem(x, m) | Remainder — C sign convention |
| min(x, y), max(x, y) | Minimum / maximum (element-by-element only in v0.1) |
MATRIX FACTORIES
| zeros(m, n), ones(m, n) | m×n zero / ones matrix |
| eye(n) | n×n identity matrix |
| linspace(a, b, n) | n evenly spaced points from a to b |
| hadamard(n) | Hadamard matrix |
| kron(A, B) | Kronecker tensor product |
REDUCTION & SHAPE
| sum(x), prod(x) | Sum / product of elements |
| size(x, dim) | Matrix dimensions |
| diff(x) | Differences between consecutive elements |
TYPE CONVERSION
| double(x), single(x) | Convert to floating-point type |
| int8(x) … int64(x) | Convert to signed integer |
| uint8(x) … uint64(x) | Convert to unsigned integer |
| logical(x) | Convert to boolean |
| char(x) | Convert to ASCII character |
| num2str(x, fmt) | Number to string, optional printf-style format |
CONSTANTS (work also as matrix factories, e.g. pi(m,n) )
| pi | π |
| e | Euler's number |
| Inf, inf | Positive infinity |
| NaN, nan | Not a number |
| true, false | Boolean constants; also matrix factories |
BITWISE & I/O
| bitand(a,b), bitor(a,b), bitxor(a,b) | Bitwise AND / OR / XOR |
| disp(x) | Display value |
| struct(...) | Create struct from name-value pairs |
| cell(m, n) | Create empty m×n cell array |
Chunked scalar mode
Chunked scalar mode is the feature that allows certain semitone scripts to run at near-native speed inside an audio plugin's process callback. It works best for scalar DSP algorithms that process one sample at a time with scalar intermediate values, without recursion. FM operators, filter coefficient computation, distortion algorithms and pixelwise operations are good examples.
Besides the overhead reduction achieved by processing entire chunks of data at a time, this mode also reduces friction for you as a developer. By treating data as individual samples, you avoid having to think of your algorithm in terms of vectors in places where this is unnecessary. For example a simple distortion function like the one below not only forces you to write many dot operators, but also to think about every operation: "is this a scalar-scalar, scalar-vector, or vector-vector operation?" and if it is scalar-vector, "does this constellation still require a dot operator?" (e.g. scalar ./ vector vs. vector / scalar).
y = (1 - d) .* x + d .* x ./ (1 + abs(x))This is a friction in the development process many of us have come to accept, but it is friction nonetheless.
In chunked scalar mode, you write your code like this:
y = (1 - d) * x + d * x / (1 + abs(x))
and the interpreter will apply the correct operations depending on whether d and x are chunks or scalars.
At this point, matrix operations are not allowed in chunked mode. If the need arises to do (small) matrix operations on samples from an audio signal, a "matrix of chunks" datatype may be added.
EXAMPLE — WHAT THE INTERPRETER SEES IN CHUNKED SCALAR MODE
% Your script — works on single samples in Octave: sin(x + sin(2 * x) / x) + sin(2 * x) % What chunked scalar mode evaluates: Buffer Prev. Comp. Node ID % block 1 N8 % │ ╭─ sin() 2 N5 % │ │ │ ╭─ (0.0, ..) (float chunk x) [input] % │ │ ╰─ + 2 N4 % │ │ │ ╭─ sin() 1 N2 % │ │ │ │ │ ╭─ 2.0 (double) [scalar] % │ │ │ │ ╰─ * 1 N1 % │ │ │ │ ╰─ (0.0, ..) (float chunk x) [input] % │ │ ╰─ / 2 N3 % │ │ ╰─ (0.0, ..) (float chunk x) [input] % ╰─ + 1 N7 % ╰─ sin() 1 N2 N6 % │ ╭─ 2.0 (double) % ╰─ * % ╰─ (0.0, ..) (float chunk x)