diff --git a/__init__.py b/__init__.py
index c6b6f61d7b1f79dd4ca55665284496e0a75db345..c91e43772e26483aa0c05db1e69b29ef6f2c6f9b 100644
--- a/__init__.py
+++ b/__init__.py
@@ -10,27 +10,92 @@ import random
 
 from st3m.ui import colours
 
-# bad hack to figure out which bl00mbox version is running
-fm_broken = False
-from st3m import processors
-if "ProcessorMidldeware" in dir(processors) and \
-    len(processors.ProcessorMidldeware.PROCESSORS):
-    fm_broken = True
-
-
-# stolen from future flow3r 1.4, import from utils instead when released
-def mkdir_recursive(path):
-    if path[0] != "/":
-        raise ValueError("path must be absolute")
-    path.rstrip("/")
-    splt = path.split("/")
-    for x in range(len(splt)):
-        subpath = "/".join(splt[: (x + 1)])
-        if not os.path.exists(subpath):
-            os.mkdir(subpath)
-        elif not os.path.isdir(subpath):
-            raise FileExistsError
+from st3m.utils import mkdir_recursive
 
+class Flute(bl00mbox.Patch):
+    def __init__(self, chan):
+        super().__init__(chan)
+        mp = self.new(bl00mbox.plugins.multipitch, 3)
+        mp.signals.max_pitch.value = 32767
+        mp.signals.min_pitch.value = -32767
+
+        # noise channel
+        noise_src = self.new(bl00mbox.plugins.noise)
+        noise_env = self.new(bl00mbox.plugins.env_adsr)
+        noise_comb = self.new(bl00mbox.plugins.flanger)
+        noise_env.signals.input << noise_src.signals.output
+        noise_env.signals.gain.dB = -34
+        noise_env.signals.attack.value = 70
+        noise_env.signals.release.value = 100
+        noise_env.signals.sustain.value = 20000
+        noise_comb.signals.input << noise_env.signals.output
+        noise_comb.signals.resonance.mult = 3
+        noise_comb.signals.mix.value = -16384
+        noise_env.signals.trigger << mp.signals.trigger_thru
+        noise_comb.signals.manual << mp.signals.output[0]
+        mp.signals.shift[0].tone = 0
+
+        # osc channel
+        osc = self.new(bl00mbox.plugins.osc)
+        osc_noise = self.new(bl00mbox.plugins.noise)
+        osc_noise_lp = self.new(bl00mbox.plugins.filter)
+        osc_env = self.new(bl00mbox.plugins.env_adsr)
+        osc_dist = self.new(bl00mbox.plugins.mixer, 2)
+        osc_noise_lp.signals.input << osc_noise.signals.output
+        osc_dist.signals.input[1] << osc_noise_lp.signals.output
+        osc_dist.signals.input[0] << noise_comb.signals.output
+        osc_dist.signals.gain.mult = 0.08
+        osc.signals.fm << osc_dist.signals.output
+        osc_noise_lp.signals.cutoff << mp.signals.output[2]
+        mp.signals.shift[2].tone = -24
+        mp.signals.min_pitch = -32767
+        osc_noise_lp.signals.reso.mult = 1.5
+        osc_noise_lp.signals.gain.dB = -12
+        osc_noise_lp.signals.mode.switch.LOWPASS = True
+        osc.signals.pitch << mp.signals.thru
+        osc.signals.waveform.switch.TRI = True
+        osc_env = self.new(bl00mbox.plugins.env_adsr)
+        osc_env.signals.trigger << mp.signals.trigger_thru
+        osc_env.signals.attack.value = 150
+        osc_env.signals.sustain.value = 32767
+        osc_env.signals.release.value = 200
+        osc_env.signals.decay.value = 0
+        osc_env.signals.input << osc.signals.output
+
+        # main mixer
+        main_mixer = self.new(bl00mbox.plugins.mixer, 2)
+        main_lp = self.new(bl00mbox.plugins.filter)
+        main_mixer.signals.input[0] << osc_env.signals.output
+        main_mixer.signals.input[1] << noise_comb.signals.output
+        main_mixer.signals.input_gain[1].dB = 0
+        main_mixer.signals.block_dc.switch.ON = True
+        main_lp.signals.mix.value = 32767
+        main_lp.signals.cutoff << mp.signals.output[1]
+        mp.signals.shift[1].tone = 48
+        main_lp.signals.input << main_mixer.signals.output
+        main_lp.signals.gain.dB = 6
+        main_lp.signals.mode.switch.LOWPASS = True
+
+        # eq
+        eq_bp = self.new(bl00mbox.plugins.filter)
+        eq_lp = self.new(bl00mbox.plugins.filter)
+        eq_bp.signals.input << main_lp.signals.output
+        eq_lp.signals.input << eq_bp.signals.output
+        eq_bp.signals.cutoff.value = 800
+        # bug: introduces DC, bypassing for now
+        eq_bp.signals.mix.value = 0
+        eq_bp.signals.mode.switch.BANDPASS = True
+        eq_lp.signals.cutoff.value = 4000
+        eq_lp.signals.mix.value = 12000
+        eq_lp.signals.mode.switch.LOWPASS = True
+        
+        self.signals.output = eq_lp.signals.output
+        self.signals.trigger = mp.signals.trigger_in
+        self.signals.pitch = mp.signals.input
+        self.signals.mod_in = mp.signals.mod_in
+        self.signals.gain = main_mixer.signals.gain
+        self.signals.fm_noise = osc_dist.signals.input_gain[0]
+        self.signals.cutoff = mp.signals.shift[1]
 
 class BinaryFluteApp(Application):
     def __init__(self, app_ctx) -> None:
@@ -111,91 +176,15 @@ class BinaryFluteApp(Application):
         settings["code"] = "gray" if self.gray else "bin"
         utils.save_file_if_changed(self.dirpath + self.settings_file, json.dumps(settings))
 
-    def _build_synth(self):
-        self.blm = bl00mbox.Channel("flute")
-        self.blm.volume = 32767
-        self.multipitch = self.blm.new(bl00mbox.plugins.multipitch, 10)
-        self.multipitch.signals.max_pitch = 32767
-        self.multipitch.signals.min_pitch = -32767
-
-        # noise channel
-        self.noise_src = self.blm.new(bl00mbox.plugins.noise)
-        self.noise_env = self.blm.new(bl00mbox.plugins.env_adsr)
-        self.noise_comb = self.blm.new(bl00mbox.plugins.flanger)
-        self.noise_env.signals.input = self.noise_src.signals.output
-        self.noise_env.signals.gain.dB = -34
-        self.noise_env.signals.attack = 70
-        self.noise_env.signals.release = 100
-        self.noise_env.signals.sustain = 20000
-        self.noise_comb.signals.input = self.noise_env.signals.output
-        self.noise_comb.signals.resonance = 24000
-        self.noise_comb.signals.mix = -16384
-        self.noise_env.signals.trigger = self.multipitch.signals.trigger_thru
-        self.noise_comb.signals.manual = self.multipitch.signals.output[0]
-        self.multipitch.signals.shift[0].tone = 0
-
-        # osc channel
-        self.osc = self.blm.new(bl00mbox.plugins.osc)
-        self.osc_noise = self.blm.new(bl00mbox.plugins.noise)
-        self.osc_noise_lp = self.blm.new(bl00mbox.plugins.filter)
-        self.osc_env = self.blm.new(bl00mbox.plugins.env_adsr)
-        self.osc_dist = self.blm.new(bl00mbox.plugins.mixer, 2)
-        self.osc_noise_lp.signals.input = self.osc_noise.signals.output
-        self.osc_dist.signals.input[1] = self.osc_noise_lp.signals.output
-        self.osc_dist.signals.input[0] = self.noise_comb.signals.output
-        self.osc_dist.signals.gain.mult = 0.08
-        if fm_broken:
-            self.osc_dist.signals.gain.mult = 0
-            print("skipping fm noise, please update to dev firmware for nicer sound")
-        self.osc.signals.fm = self.osc_dist.signals.output
-        self.osc_noise_lp.signals.cutoff = self.multipitch.signals.output[2]
-        self.multipitch.signals.shift[2].tone = -24
-        self.multipitch.signals.min_pitch = -32767
-        self.osc_noise_lp.signals.reso = 12000
-        self.osc_noise_lp.signals.gain.dB = -12
-        self.osc_noise_lp.signals.mode.switch.LOWPASS = True
-        self.osc.signals.pitch = self.multipitch.signals.thru
-        self.osc.signals.waveform.switch.TRI = True
-        self.osc_env = self.blm.new(bl00mbox.plugins.env_adsr)
-        self.osc_env.signals.trigger = self.multipitch.signals.trigger_thru
-        self.osc_env.signals.attack = 150
-        self.osc_env.signals.sustain = 32767
-        self.osc_env.signals.release = 200
-        self.osc_env.signals.decay = 0
-        self.osc_env.signals.input = self.osc.signals.output
-
-        # main mixer
-        self.main_mixer = self.blm.new(bl00mbox.plugins.mixer, 2)
-        self.main_lp = self.blm.new(bl00mbox.plugins.filter)
-        self.main_mixer.signals.input[0] = self.osc_env.signals.output
-        self.main_mixer.signals.input[1] = self.noise_comb.signals.output
-        self.main_mixer.signals.input_gain[1].dB = 0
-        self.main_mixer.signals.block_dc.switch.ON = True
-        self.main_lp.signals.mix = 32767
-        self.main_lp.signals.cutoff = self.multipitch.signals.output[1]
-        self.multipitch.signals.shift[1].tone = 48
-        self.main_lp.signals.input = self.main_mixer.signals.output
-        self.main_lp.signals.gain = 16000
-        self.main_lp.signals.mode.switch.LOWPASS = True
-
-        # eq
-        self.eq_bp = self.blm.new(bl00mbox.plugins.filter)
-        self.eq_lp = self.blm.new(bl00mbox.plugins.filter)
-        self.eq_bp.signals.input = self.main_lp.signals.output
-        self.eq_lp.signals.input = self.eq_bp.signals.output
-        self.eq_bp.signals.cutoff = 800
-        # bug: introduces DC, bypassing for now
-        self.eq_bp.signals.mix = 0
-        self.eq_bp.signals.mode.switch.BANDPASS = True
-        self.eq_lp.signals.cutoff = 4000
-        self.eq_lp.signals.mix = 12000
-        self.eq_lp.signals.mode.switch.LOWPASS = True
-        
-        self.blm.mixer = self.eq_lp.signals.output
+    def build_synth(self):
+        self.blm = bl00mbox.Channel("binary flute")
+        self.flute = self.blm.new(Flute)
+        self.blm.gain_dB = 0
+        self.blm.signals.line_out << self.flute.signals.output
 
     def on_enter(self, vm):
         super().on_enter(vm)
-        self._build_synth()
+        self.build_synth()
         self.enter_done = False
         self.full_redraw = True
         self.sound_active = False
@@ -222,10 +211,7 @@ class BinaryFluteApp(Application):
 
     def on_exit(self):
         super().on_exit()
-        if self.blm is not None:
-            self.blm.clear()
-            self.blm.free = True
-            self.blm = None
+        self.blm.delete()
 
     def draw(self, ctx):
         if not self.enter_done:
@@ -383,10 +369,10 @@ class BinaryFluteApp(Application):
 
         if self.sound_active != sound_active:
             if sound_active:
-                self.multipitch.signals.trigger_in.start()
+                self.flute.signals.trigger.start()
                 self.play_note(self.valves, glide = False)
             else:
-                self.multipitch.signals.trigger_in.stop()
+                self.flute.signals.trigger.stop()
             self.sound_active = sound_active
 
         new_captouch_data = False
@@ -408,7 +394,7 @@ class BinaryFluteApp(Application):
                 if valves == self.valves_candidate:
                     self.valves = valves
                     if self.sound_active:
-                        self.multipitch.signals.trigger_in.start()
+                        self.flute.signals.trigger.start()
                         self.play_note(self.valves)
                 else:
                     self.valves_candidate = valves
@@ -422,21 +408,21 @@ class BinaryFluteApp(Application):
             vol = 0.5 - vol
             self.vol += (vol - self.vol) * (1-decay_slow)
             self.vol = max(0, min(1, self.vol))
-            self.main_mixer.signals.gain.dB = -32 + 12 * self.vol
-            self.multipitch.signals.shift[1].tone = 12 + 12 * self.vol
-            self.osc_dist.signals.input_gain[0].mult = 1 + 2 * max(self.vol-0.5, 0)
+            self.flute.signals.gain.dB = -32 + 12 * self.vol
+            self.flute.signals.cutoff.tone = 12 + 12 * self.vol
+            self.flute.signals.fm_noise.mult = 1 + 2 * max(self.vol-0.5, 0)
         else:
             self.vol = 0
 
         note_delta = self.target_note - self.current_note
         self.current_note += note_delta * (1 - (0.9**delta_ms))
-        self.multipitch.signals.input.tone = self.current_note
+        self.flute.signals.pitch.tone = self.current_note
 
         pitch_mod = ins.imu.acc[2] * 60 
         if self.pitch_mod_bias is None:
             self.pitch_mod_bias = pitch_mod
         self.pitch_mod_bias += (pitch_mod - self.pitch_mod_bias) * (1 - decay_slow)
-        self.multipitch.signals.mod_in = pitch_mod - self.pitch_mod_bias
+        self.flute.signals.mod_in = pitch_mod - self.pitch_mod_bias
 
     def play_note(self, valves, glide = True):
         def gray_to_binary(gy):
diff --git a/flow3r.toml b/flow3r.toml
index b98fc87a8abc9e3dd9427e350b3bb2a7ee62ae67..e8dd244c62eab1c5bb058c6f29cccadae8ee3c8f 100644
--- a/flow3r.toml
+++ b/flow3r.toml
@@ -11,15 +11,8 @@ author = "moon2"
 license = "CC0-1.0"
 description = "flute emulator"
 url = "https://git.flow3r.garden/moon2/binary-flute"
-version = 8
+version = 9
 
 [style]
 background = "rgb(0.5, 0.5, 1.0)"
-
-[[style.floaties]]
-symbol = "flow3r"
-color = "rgba(1.0, 1.0, 1.0, 0.5)"
-
-[[style.floaties]]
-symbol = "cat"
 color = "rgba(1.0, 1.0, 1.0, 0.5)"