<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://wiki.sandboxpedal.com/index.php?action=history&amp;feed=atom&amp;title=Writing_your_first_FV-1_program</id>
	<title>Writing your first FV-1 program - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://wiki.sandboxpedal.com/index.php?action=history&amp;feed=atom&amp;title=Writing_your_first_FV-1_program"/>
	<link rel="alternate" type="text/html" href="https://wiki.sandboxpedal.com/index.php?title=Writing_your_first_FV-1_program&amp;action=history"/>
	<updated>2026-08-24T05:02:48Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.43.9</generator>
	<entry>
		<id>https://wiki.sandboxpedal.com/index.php?title=Writing_your_first_FV-1_program&amp;diff=18&amp;oldid=prev</id>
		<title>Matthew: Add how-to guide</title>
		<link rel="alternate" type="text/html" href="https://wiki.sandboxpedal.com/index.php?title=Writing_your_first_FV-1_program&amp;diff=18&amp;oldid=prev"/>
		<updated>2026-08-23T23:53:27Z</updated>

		<summary type="html">&lt;p&gt;Add how-to guide&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;This guide builds up a working tremolo from nothing, one step at a time.  Every example here assembles cleanly and can be pasted straight into the [https://fv1.sandboxpedal.com Sandbox FV-1 Editor].&lt;br /&gt;
&lt;br /&gt;
You do not need a pedal to follow along — the [[Using the FV-1 simulator|simulator]] will play all of it.&lt;br /&gt;
&lt;br /&gt;
== How an FV-1 program works ==&lt;br /&gt;
&lt;br /&gt;
The FV-1 runs your program &amp;#039;&amp;#039;&amp;#039;once per audio sample&amp;#039;&amp;#039;&amp;#039;, 32768 times a second.  There is no main loop that you write; the chip loops for you.&lt;br /&gt;
&lt;br /&gt;
That gives you a hard budget of &amp;#039;&amp;#039;&amp;#039;128 instructions&amp;#039;&amp;#039;&amp;#039;.  Anything shorter is padded with NOPs automatically.  There is no way to spend more time on one sample than another.&lt;br /&gt;
&lt;br /&gt;
Almost everything flows through a single &amp;#039;&amp;#039;&amp;#039;accumulator&amp;#039;&amp;#039;&amp;#039; (ACC).  You read a value into it, do arithmetic on it, and write it somewhere.  Most instructions multiply by a coefficient on the way past.&lt;br /&gt;
&lt;br /&gt;
The registers you will use first:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Register !! Meaning&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;ADCL&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ADCR&amp;lt;/code&amp;gt; || Audio input, left and right&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;DACL&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;DACR&amp;lt;/code&amp;gt; || Audio output, left and right&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;POT0&amp;lt;/code&amp;gt;–&amp;lt;code&amp;gt;POT2&amp;lt;/code&amp;gt; || The three control knobs, 0.0 to 1.0&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;REG0&amp;lt;/code&amp;gt;–&amp;lt;code&amp;gt;REG31&amp;lt;/code&amp;gt; || General-purpose storage&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Coefficients are fixed-point fractions in the range −1.0 to just under 1.0.  You cannot write &amp;lt;code&amp;gt;1.0&amp;lt;/code&amp;gt; and expect exactly one in every field, which is why you will see &amp;lt;code&amp;gt;0.999&amp;lt;/code&amp;gt; in places where you might expect 1.&lt;br /&gt;
&lt;br /&gt;
== Step 1: pass-through ==&lt;br /&gt;
&lt;br /&gt;
The smallest useful program copies input to output.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
RDAX	ADCL, 1.0	; ACC = left input&lt;br /&gt;
WRAX	DACL, 0.0	; left output = ACC, then clear ACC&lt;br /&gt;
&lt;br /&gt;
RDAX	ADCR, 1.0	; ACC = right input&lt;br /&gt;
WRAX	DACR, 0.0	; right output = ACC, then clear ACC&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Two instructions per channel.  &amp;lt;code&amp;gt;RDAX&amp;lt;/code&amp;gt; reads a register into the accumulator, multiplying by the coefficient on the way in.  &amp;lt;code&amp;gt;WRAX&amp;lt;/code&amp;gt; writes the accumulator to a register and then multiplies what remains in ACC by &amp;#039;&amp;#039;its&amp;#039;&amp;#039; coefficient.&lt;br /&gt;
&lt;br /&gt;
That second coefficient is the part that catches people out.  &amp;lt;code&amp;gt;WRAX DACL, 0.0&amp;lt;/code&amp;gt; means &amp;quot;write ACC to the left DAC, then zero the accumulator&amp;quot; — which is what you want before starting work on the other channel.  Using &amp;lt;code&amp;gt;WRAX DACL, 1.0&amp;lt;/code&amp;gt; would leave the value in ACC and the right channel would come out as a mix of both.&lt;br /&gt;
&lt;br /&gt;
Assemble this and play it through the simulator.  It should sound like nothing at all, which is the correct result.&lt;br /&gt;
&lt;br /&gt;
== Step 2: a volume control ==&lt;br /&gt;
&lt;br /&gt;
Now put POT0 in charge of the level.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
RDAX	ADCL, 1.0&lt;br /&gt;
MULX	POT0		; ACC = ACC * POT0&lt;br /&gt;
WRAX	DACL, 0.0&lt;br /&gt;
&lt;br /&gt;
RDAX	ADCR, 1.0&lt;br /&gt;
MULX	POT0&lt;br /&gt;
WRAX	DACR, 0.0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;MULX&amp;lt;/code&amp;gt; multiplies the accumulator by the contents of a register.  Because &amp;lt;code&amp;gt;POT0&amp;lt;/code&amp;gt; reads 0.0 to 1.0, this is a straightforward volume knob.&lt;br /&gt;
&lt;br /&gt;
Open the SIM panel and move the POT0 slider while it plays.&lt;br /&gt;
&lt;br /&gt;
== Step 3: an LFO ==&lt;br /&gt;
&lt;br /&gt;
A tremolo is a volume control that moves on its own, so the next piece is a low-frequency oscillator.&lt;br /&gt;
&lt;br /&gt;
The FV-1 has two sine LFOs (&amp;lt;code&amp;gt;SIN0&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;SIN1&amp;lt;/code&amp;gt;) and two ramp LFOs (&amp;lt;code&amp;gt;RMP0&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;RMP1&amp;lt;/code&amp;gt;).  You start a sine LFO with &amp;lt;code&amp;gt;WLDS&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
WLDS	SIN0, 40, 32767		; LFO 0: rate 40, full amplitude&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This only needs to happen once, not on every one of the 32768 samples per second.  The idiom for that is a conditional skip on the &amp;lt;code&amp;gt;RUN&amp;lt;/code&amp;gt; flag, which is clear on the very first pass through the program and set on every pass after:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SKP	RUN, loop&lt;br /&gt;
WLDS	SIN0, 40, 32767&lt;br /&gt;
loop:&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On the first sample, &amp;lt;code&amp;gt;RUN&amp;lt;/code&amp;gt; is clear, so the skip does not happen and the LFO is set up.  On every subsequent sample the program jumps straight over &amp;lt;code&amp;gt;WLDS&amp;lt;/code&amp;gt; to the &amp;lt;code&amp;gt;loop&amp;lt;/code&amp;gt; label.&lt;br /&gt;
&lt;br /&gt;
To read the LFO&amp;#039;s current value, use &amp;lt;code&amp;gt;CHO RDAL&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
CHO	RDAL, SIN0	; ACC = the sine LFO, swinging -1.0 to +1.0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Step 4: shaping the LFO into a gain ==&lt;br /&gt;
&lt;br /&gt;
The LFO swings from −1.0 to +1.0, but a gain needs to be positive.  &amp;lt;code&amp;gt;SOF&amp;lt;/code&amp;gt; — scale and offset — fixes that in one instruction.  &amp;lt;code&amp;gt;SOF a, b&amp;lt;/code&amp;gt; computes &amp;lt;code&amp;gt;ACC = ACC × a + b&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
CHO	RDAL, SIN0	; -1.0 .. +1.0&lt;br /&gt;
SOF	0.5, 0.5	; halve it, shift it up: now 0.0 .. 1.0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Next, depth.  Multiplying by POT1 scales the swing down, and a second &amp;lt;code&amp;gt;SOF&amp;lt;/code&amp;gt; flips it so the gain sits just below unity at minimum depth:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
MULX	POT1		; 0.0 .. depth&lt;br /&gt;
SOF	-1.0, 0.999	; (1 - depth) .. 1.0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
With POT1 at zero the gain is a constant 0.999 — effectively off.  With POT1 fully up the gain swings across the full range.&lt;br /&gt;
&lt;br /&gt;
== The finished tremolo ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
; Simple tremolo&lt;br /&gt;
; POT0 = rate, POT1 = depth&lt;br /&gt;
&lt;br /&gt;
EQU	mod	REG0&lt;br /&gt;
&lt;br /&gt;
; Start the sine LFO once, on the first pass only&lt;br /&gt;
SKP	RUN, loop&lt;br /&gt;
WLDS	SIN0, 40, 32767&lt;br /&gt;
&lt;br /&gt;
loop:&lt;br /&gt;
; POT0 sets the LFO rate&lt;br /&gt;
RDAX	POT0, 0.5&lt;br /&gt;
SOF	1.0, 0.02&lt;br /&gt;
WRAX	SIN0_RATE, 0.0&lt;br /&gt;
&lt;br /&gt;
; Read the LFO and fold it into a 0..1 gain&lt;br /&gt;
CHO	RDAL, SIN0&lt;br /&gt;
SOF	0.5, 0.5&lt;br /&gt;
MULX	POT1&lt;br /&gt;
SOF	-1.0, 0.999&lt;br /&gt;
WRAX	mod, 0.0&lt;br /&gt;
&lt;br /&gt;
; Apply the gain to both channels&lt;br /&gt;
RDAX	ADCL, 1.0&lt;br /&gt;
MULX	mod&lt;br /&gt;
WRAX	DACL, 0.0&lt;br /&gt;
&lt;br /&gt;
RDAX	ADCR, 1.0&lt;br /&gt;
MULX	mod&lt;br /&gt;
WRAX	DACR, 0.0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sixteen instructions, out of the 128 available.&lt;br /&gt;
&lt;br /&gt;
Two new things appear here.  &amp;lt;code&amp;gt;EQU mod REG0&amp;lt;/code&amp;gt; gives &amp;lt;code&amp;gt;REG0&amp;lt;/code&amp;gt; a readable name — the assembler substitutes it, and it costs nothing at runtime.  Writing to &amp;lt;code&amp;gt;SIN0_RATE&amp;lt;/code&amp;gt; changes the LFO rate while the program runs, which is what turns POT0 into a rate knob; the &amp;lt;code&amp;gt;SOF 1.0, 0.02&amp;lt;/code&amp;gt; keeps the rate from reaching zero at the bottom of the sweep.&lt;br /&gt;
&lt;br /&gt;
Load it into the simulator, press Play, and move POT0 and POT1.&lt;br /&gt;
&lt;br /&gt;
== Where to go next ==&lt;br /&gt;
&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Delay memory.&amp;#039;&amp;#039;&amp;#039;  The &amp;lt;code&amp;gt;MEM&amp;lt;/code&amp;gt; directive allocates space in the FV-1&amp;#039;s 32768 words of delay memory: &amp;lt;code&amp;gt;MEM delay 16000&amp;lt;/code&amp;gt;.  It creates three symbols — &amp;lt;code&amp;gt;delay&amp;lt;/code&amp;gt; (start), &amp;lt;code&amp;gt;delay#&amp;lt;/code&amp;gt; (end) and &amp;lt;code&amp;gt;delay^&amp;lt;/code&amp;gt; (midpoint).  &amp;lt;code&amp;gt;RDA&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;WRA&amp;lt;/code&amp;gt; read and write it.&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;The built-in examples.&amp;#039;&amp;#039;&amp;#039;  &amp;#039;&amp;#039;&amp;#039;Load File…&amp;#039;&amp;#039;&amp;#039; in the editor offers Pass-through, Delay, Chorus and Tremolo.  Reading working code is the fastest way to pick up the idioms.&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;The instruction reference.&amp;#039;&amp;#039;&amp;#039;  The &amp;#039;&amp;#039;&amp;#039;HELP&amp;#039;&amp;#039;&amp;#039; tab lists the full instruction set with operand formats.&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Spin&amp;#039;s documentation.&amp;#039;&amp;#039;&amp;#039;  The FV-1 datasheet and application note AN-0001 are the authoritative source on LFO behaviour and the fixed-point formats.&lt;br /&gt;
&lt;br /&gt;
== A few things that bite beginners ==&lt;br /&gt;
&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Forgetting to clear ACC.&amp;#039;&amp;#039;&amp;#039;  If a channel sounds like it has the other channel bleeding into it, check your &amp;lt;code&amp;gt;WRAX&amp;lt;/code&amp;gt; coefficients.&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Coefficient range.&amp;#039;&amp;#039;&amp;#039;  Coefficients are fractions below 1.0.  If you need gain above unity, do it in stages or use a format that allows it.&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Setup code running every sample.&amp;#039;&amp;#039;&amp;#039;  &amp;lt;code&amp;gt;WLDS&amp;lt;/code&amp;gt; and friends belong behind a &amp;lt;code&amp;gt;SKP RUN&amp;lt;/code&amp;gt; guard.&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Running out of instructions.&amp;#039;&amp;#039;&amp;#039;  128 is a hard ceiling.  If you hit it, the answer is usually a cheaper filter, not a cleverer one.&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Trusting the simulator on chorus.&amp;#039;&amp;#039;&amp;#039;  &amp;lt;code&amp;gt;CHO&amp;lt;/code&amp;gt; interpolation is approximated.  Confirm modulation effects on hardware.&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
* [[Using the Sandbox FV-1 Editor]]&lt;br /&gt;
* [[Using the FV-1 simulator]]&lt;br /&gt;
* [[Programming an EEPROM with HEX files]]&lt;br /&gt;
&lt;br /&gt;
[[Category:How-to]]&lt;/div&gt;</summary>
		<author><name>Matthew</name></author>
	</entry>
</feed>