Wind Controller BLE

This is my third wind controller design, and this time it’s one that uses a Bluetooth Low Energy (BLE) wireless MIDI link. The body, mouthpiece, and mounting parts are all 3D printed, and it features capacitive touch sensors for the main keys. Another big difference between this and my previous MKI and MKII wind controllers is that this one uses an inexpensive Force Sensitive Resistor (FSR) to detect breath pressure to send out corresponding MIDI continuous controller (CC) messages – more on how this is done in the hardware discussion below.

The wind controller doesn’t make any sound in itself – it sends out MIDI messages via a Bluetooth LE link to a device that’s running a synth app. Most modern phones, tablets, and computers support BLE connections from their apps. There are a wide variety of compatible synthesizer software programs available from the Apple App Store, the Microsoft App Store, and elsewhere. I mainly use an iPhone with this wind controller, and my favorites synths are the Roli Seaboard 5D app, GarageBand, the Yamaha Synthbook AN2015 app, and the Korg Gadget LE Marseille app – all are available for free from the Apple App Store. For a Windows PC, MIDIberry lets the wind controller play the built-in Windows Microsoft GS Wavetable Synth, which contains a complete General MIDI sound set.  Here is more information about the Roli Seaboard 5D app.  Here is more information about the Yamaha Synthbook app.

Here is a video demonstration of  the wind controller.  The accompaniment music in the video was generated with Band-in-a-Box software on my PC and transferred into GarageBand on my iPad, and then the melody lines were all recorded using this wind controller and the synth voices built into GarageBand.

Here is a parts list for the project (Amazon links may earn me a small commission):

(*) If you don’t like the idea of using a 3D printed mouthpiece (a mouthpiece STL file is included in the files), note that the wind controller neck is designed to fit a standard alto saxophone mouthpiece and reed instead of the printed one if you wish. The mouthpiece makes no sound itself so any alto mouthpiece should work.

Files for the 3D printed parts are available on Thingiverse.

Adafruit has lots of great getting started guides and example programs. In fact, the software for the wind controller started with the example Bluetooth program provided by Adafruit but of course has become much more that that simple demo program. All editing, compiling, and loading the software into wind controller’s Feather processor is done using the Arduino Integrated Development Environment (IDE), but the Adafruit Feather nRF52 Board Support Package (BSP) first needs to be installed – see this page for more information. The bootloader on the Feather needed to be updated too – see this page for instructions.  Note it takes over 35 seconds to update the bootloader – so be patient when you do this – here’s a screen shot from my bootloader update on my Feather nRF52 (click on the picture to expand).

Operating Features Overview

Fingering on this wind controller is very similar to the saxophone, with a few minor tweaks. The main front touch pad keys are B to D, and the left pinky has a G# and a low C# hard key (LP1 and LP2). There right pinky has an Eb and low C hard key (RP1 and RP2). The right side hard keys (RS1 and RS2) are a bit different than normal saxophone fingerings – these two keys allow any fingered note to be shifted up by either a 1/2 step or a whole step.

On the bottom side there are three octave touch keys – the top ones shift everything up by either 2 octaves or 1 octave, and the bottom one shifts everything down by 1 octave. When your left thumb is on the raised bump and not touching any of the octave keys, no octave shift occurs.

On the bottom there are also three buttons used to navigate through the display menus and select options. There is an increment (INC) key, a decrement (DEC) key, and a combined mode/select/enter key (MODE).

Below the right hand thumb hook is a force sensitive resistor (FSR) that detects how hard you are pressing and causes the software to send out a MIDI continuous controller (CC) message that’s assigned to the thumb control function.

The power switch at the bottom turns on the wind controller.  The internal rechargeable battery can be recharged if a micro-USB type cord is plugged into the port at the end of the wind controller and connected to an appropriate charging source (computer, 5V wall wart power supply cube, etc.).

The small 128×64 pixel OLED LCD is used to display the various screens with menu, status, and selection options for the wind controller.

Displays and Menu Structure

Startup screen
The startup screen briefly displays on initial power up while the wind controller’s software is starting up. As noted in the picture above, the installed software version is displayed on this screen.  During the time this screen is displayed, variables and functions are initialized, the Bluetooth LE discovery and advertising begins, the touch sensors are initialized, and the breath pressure port is read several times to establish a ‘not-blowing’ baseline for the breath control sensor. It’s important to not blow into the wind controller for the first couple seconds after the power switch is turned on, and also try not to touch any of the copper touch pads during this time while the touch sensor calibrates.

Select Profile screen
The select profile screen comes up when power is applied right after the startup screen. Here the INC and DEC buttons can be used to cycle through the choices and the MODE button can be used to select the current profile displayed. The profiles are defined in the 00_Config.h file structures which set the default values for the configurable parameters desired at startup. Note that everything can be changed via selection on other pages, but this is how to make the changes persistent for favorite synth apps and/or setting combinations. As many synth profiles as desired can be added as long as they follow the coding structure/format of the CONFIGURATION_PROFILE[] elements in the 00_Config.h file.

Battery and Bluetooth Connection Status
For most of the display pages described below, a battery icon and percentage remaining value are shown. When charging, the battery icon changes to a charging symbol and the percent charged value is shown.

When the wind controller does not have a Bluetooth connection, a WAITING FOR CONNECT prompt is shown at the bottom of the display.

Once the Bluetooth connection is established, the bottom of the display changes to either a default “Wind Controller BLE” display if no patch names are to be displayed, or the current patch name that corresponds to the program value if a patch name set is enabled.

Program Selection
When PROG is highlighted, the MIDI program change command is sent every time the INC or DEC buttons are pressed. If the INC or DEC buttons are held, program changes are rapidly changed in the desired direction.

Note that not all synth apps honor MIDI program change commands. But for those that do accept them it is nice to display the patch name on the wind controller’s OLED display. Structures are defined in the 01_Patches.h file that include the patch names for several apps as well as the General MIDI patch set.

Pressing the MODE switch cycles forward to the next setting option. If one of the control switches – MODE, INC, or DEC – are not pressed again within 10 seconds, the display will revert back to first page with the PROG selected.

Octave Selection
When OCT is highlighted, the wind controller’s default octave can be adjusted.

Tuning Selection
When TUNING is highlighted, the wind controller’s default tuning can be adjusted. For example, to play in an alto saxophone tuning, select “+3 (Eb)” or to play in a trumpet, clarinet, or tenor saxophone tuning, select “-2 (Bb)”.

Bend Threshold Selection
The pitch bend is triggered if the controller is moved at an acceleration greater than the bend threshold set here. The pitch bend is down if the controller is moved to the left or down, and the pitch bend is up if the controller is moved right or up.

Bend Depth Selection
The amount of pitch bend (i.e. the maximum pitch bend value sent on the MIDI messages) is set here. Normally synths use the full range of 0 to 8192 for pitch bend to represent a whole step bend, or 0 to 4096 for a half step. Note that the Seaboard synth has an extremely large default bend range of two octaves for a value range of 0 to 8192. The default full step on the Seaboard is only a range of 0 to 683 and a half step is only 0 to 341. The Seaboard settings therefore give several options between the extremes.

Bend Period Selection
The bend period is the amount of time it takes for a bend to complete. Range is adjustable from 0.05 to 0.95 seconds.

Breath Assign Selection & Thumb Assign Selection
The breath and thumb controllers can each output a MIDI continuous controller (CC) message for one of many device types selectable here.

The filter resonance (CC71) can be ‘full range’ or constrained to one-third of the MIDI CC range over the full breath or thumb pressure ranges. The latter is useful if the MIDI synth being controlled has a very sensitive resonance response.

The filter cutoff (CC74) can be ‘normal’ where increasing breath or thumb pressure result in higher values, or ‘reverse’ where increasing breath or thumb pressure result in smaller values.

For most standard MIDI synths, breath control is routed to CC7 or CC11, and thumb control is routed to CC1.

For the Roli Seaboard 5D synth, breath control works well if it is routed to CC74 (Roli’s ‘slide’ parameter) and thumb control is routed to Aftertouch (Roli’s ‘press’ parameter).

Patch Names Selection
When this option is selected, one of the patch name sets defined in the 01_Patches.h file are selectable. This determined which patch name set will be displayed corresponding to the PROG setting on the first page of the display.

The 01_Patches.h file currently includes patch names for the Roli Seaboard 5D app, the Yamaha Synth Book AN2015 app, my favorite Synth Book user patch bank, and General MIDI patch names for use with MIDIberry and the Microsoft GS Wavetable Synth. For synths that don’t honor MIDI program change commands (examples include GarageBand and the Korg Gadget app), there’s a “None” option so no patch names are displayed on the first page of the display (just “Wind Controller BLE” will be displayed instead).

Select Function Display
This page is accessed by a long press (greater than 1 second) of the MODE switch. Here the touch sensors can be reset by pressing INC or the Select Profile screen can be reselected by pressing DEC.

Status LEDs
The blue LED flashes when a the wind controller is ready to make a Bluetooth connection, and it goes solid blue when a connection is established. The red LED illuminates when a USB cord is plugged into the Feather’s connector through the bottom plug hole and USB power is present. When the wind controller is charging, the orange LED lights (requires USB power applied and the power switch to be set to the ‘on’ position).


While my other wind controllers used relatively expensive pressure sensors for the breath sensor, I decided to experiment with other sensing methods to determine how much breath pressure was being exerted. The end result of this was a 3D printed ‘breath chamber’ in the neck part of the wind controller, which uses a piece of ordinary balloon material as a membrane that moves when breath pressure is applied. The membrane presses a small plunger onto a Force Sensitive Resistor (FSR) which in turn generates a voltage that’s read by an analog port on the Feather processor. This design shown has been working very well for me and seems very responsive and reliable. It’s possible that the balloon will eventually wear out (e.g. the rubber drys out or become brittle), but it would be super easy to replace if/when that ever happens.

The mechanical parts are all 3D printed in PLA using my CR-10S printer. This printer has a 300mm x 300mm print bed, and the three large parts (Body Top, Body Bottom, and Body Bottom Skin) were individually printed at a 45 degree angle on the print bed. Note that these parts will not fit on a printer with a print bed smaller than about 250mm x 250mm.

Files for the 3D printed parts are available on Thingiverse.

Gold PLA was used for the body top and bottom, breath chamber, and bottom plug, and black PLA was used for everything else. All of the parts except the plunger are printed at 0.2mm layer height with 30% infill. Most of the parts can also be printed without support material – the only parts that I printed with support turned on were the Breath Chamber Lower, Breath Chamber Upper, Plunger, and Neck. For strength, the Neck is printed sideways (as oriented in the STL file).  I also used a raft for the Plunger and Neck. Since it’s such a small part, the Plunger was printed at 0.1mm layer height.

Note that the bottom skin part has three small alignment indents along its center line. Short pieces of filament are glued into the holes in the skin, and then these ‘pins’ are used to align the skin to the body bottom when it is glued on. After the glue is dry, the excess pins are trimmed off on the inside.

The breath chamber and the main assembly screw in the bottom plug piece of the wind controller use M3 screws into M3 brass inserts. A good way to assemble these is to put the inserts on a long M3 screw and heat the insert using a small butane torch for about 5 seconds. Then carefully press the hot insert into the plastic parts so they melt themselves in place. An insert ‘practice piece’ is included in the 3D printer files to use several times to be sure to get the hang of heating and inserting these just right before committing doing it to the real parts. The larger holes in the insert practice piece is for the PC4-M10 connector which screws into the bottom of the breath chamber – it helps it to screw this in easier if it’s heated first as well.

A piece of the balloon is cut and stretched slightly before it is sandwiched between the retaining ring and the lower breath chamber. The retaining ring has a lip that’s raised above the inside plane of the lower chamber – perfect to guide a razor blade as it cuts off the excess balloon material. The breath FSR is adhered to the upper breath chamber piece exactly where the cutout in the middle breath chamber piece goes. The tip of the plunger may need to be sanded very slightly so it doesn’t press on the FSR when no breath is applied. Conversely, if the plunger does not touch the breath FSR, a small piece of electrical tape can be added to the top in one or two layers to shim the plunger tip up. The whole breath chamber assembly is held together with four 18mm long M3 screws. The neck piece fits into the breath chamber, and there is a ‘key’ in the design to ensure it’s aligned. A little sanding may be needed on the inside of the neck hole in the breath chamber to make it fit nicely. The neck is glued to the bottom of the breath chamber only (where the key is) – this way the top and middle pieces can still be removed if necessary for access. The breath chamber sandwich (lower, middle, and upper) has gaps which the tabs on the body top fit into. The gaps and/or the tabs might need to be sanded slightly so the body top slides in easily. Then the lower breath chamber is glued to the body bottom and the bottom plug is glued to the body top. This way, the single M3x8mm screw at the bottom of the wind controller can be removed and the body top slid down to disengage the tabs so the unit can be opened. The four screws holding the breath chamber together only need to be removed if the balloon, plunger, or breath FSR need to be adjusted in some way.

The air and any moisture that collects from your breath exits the breath chamber through a PTFE tube that runs down to the bottom of the wind controller. The PTFE tube is a leftover piece of standard 3D printer bowden tube, and it’s connected to the bottom of the breath chamber using a PC4-M10 connector, also a spare from my 3D printer.

The key touch sensors are copper foil tape applied to the 3D printed bottom and top switch mount parts before they are mounted to the body. This tape comes with a paper backing, so it’s fairly easy to pre-cut the necessary sizes. A gap is left where the ridge is on the switch mounts to provide isolation between the B and bis keys on the top and between the +2 and +1 octave keys on the bottom. Soldering the wires to the back of the copper foil touch pads to connect them to the MPR121 capacitive touch sensor board was very easy and did not melt the plastic parts due to the way the soldering heat was dissipated in the foil.

The right and left switch mounts and the bottom switch mount have recesses for standard 12mm tact switches with posts for caps (the caps are attached from the outside after everything is assembled). The legs of these switches fit neatly into the holes in the mounts, and there’s a taper on the bottom to provide a little clearance for soldering the wires after the parts are assembled. The right switch mount parts 2A and 2B are glued together before attaching them. The switch mounts are all attached to the body with 1/4″ long #2 round head screws.

A piece of 2mm thick cork sheeting (sold for saxophone neck cork replacement) is used on the neck. There are many resources that show how to cork a saxophone neck on the web, but basically the first thing to do is sand one edge of the piece of cork down to a bevel as shown. Then put contact cement on the bevel, the bottom of the cork, and the neck itself and allow the glue to dry. After the contact cement is dry to the touch, the cork is wrapped tightly around the neck and trimmed with a razor blade. Finally, the cork is sanded and greased until it fits into the mouthpiece. The cork sheeting typically comes in packs of 10 pieces so you can try again if it doesn’t work out well the first time. I have done this several times and I get better at it with each one I do.  If you prefer, wrapping the neck part with electrical tape to build up a nice fit into the mouthpiece instead of using cork would probably work too.

The Feather nRF52 processor mounts at the bottom of the wind controller with its LEDs facing down so that the light will shine through the small holes in the body bottom and skin. The Feather has a connector on the side where a matched LiPo battery can connect – it should be noted that not all batteries with similar connectors available from Amazon or other sellers are polarized the same way. You need to be very careful to make sure that the polarity of the battery matches what the processor connector expects! A power switch was put in line with the positive battery lead so that the unit draws absolutely zero current when it’s turned off.  The terminals of the rocker switch were trimmed down a bit so they wouldn’t touch the processor board. The two FSR pull down resistors (R1 and R2) are mounted to the side of the Feather processor. The two USB voltage sense resistors (R3 and R4) are mounted on the bottom of the Feather before the rest of the wires are added.

The 128X64 OLED LCD display used in the wind controller has a glass size of 25 x 17mm and this fits perfectly in the hole in the body. If you build this and use a different display, you may need to slightly sand or carve the opening in the lower body to fit the display. The glass is very fragile on these tiny displays, so it needs to fit into the opening without force. The bottom switch mount and the bottom skin hold the display in place, sandwiched between these parts with the lower body in between. No screws are put through the holes of the display board itself.

The thumb FSR-402 fits in the opening in the bottom skin, and the tail is curved over on the inside so that its pins face the mouthpiece and it is held in place with the FSR support piece.

The MPR121 Touch Sensor is mounted in its own dedicated area and is held in place with two screws at the bottom and the MPU-6050 Inertial Measurement Unit (IMU) mounting plate at the top.

Once all the parts are mounted, separated ribbon cable was used to make the wiring connections shown on the electrical schematic. Click on the picture above for a larger view – it’s a surprisingly simple schematic and can be wired up point-to-point pretty easily if you take your time.  Use several zip ties to keep things neat and tidy.


Wind Controller BLE Software (link downloads a zip file)

The wind controller operating software is broken up into several “.h” and “.ino” files, contained in the zip file linked above. After the files are unzipped to a folder on your PC, double-click on the Wind_Controller_BLE_V1_0.ino file and all the others open up in tabs in the Arduino IDE. This main file has the usual loop() main program which has sections that execute at 10 Hz for the battery voltage monitoring and display routines, and at 100 Hz for everything else.

The 100 Hz section has these main parts:
1 – Bluetooth connection logic
2 – Breath pressure input analog read
3 – Switch read and pitch computation
4 – Note on/off message computation
5 – Breath controller message computation
6 – Thumb controller message computation
7 – Pitch bend message computation
8 – Handle mode changes
9 – Timing test

The 10 Hz section has only two parts:
10 – Battery voltage monitoring
11 – Display control

1 – Bluetooth connection logic
The Bluetooth connection logic monitors the state of the Bluetooth connection and changes the display appropriately if the wind controller is waiting for a connection or if one is established.

2 – Breath pressure input analog read
The breath pressure analog is then read, averaged over two frames, and adjusted to provide a minimum value dead band. Then the value is squared and rescaled to create a more natural feeling exponential response over the MIDI velocity range of 0 to 127.

3 – Switch read and pitch computation
The switch read and pitch computation section first reads the state of the touch sensors from the MPR121 Touch Sensor board and uses this data to lookup a base pitch value from the FINGERING_ARRAY_1 table. The pinky hard switches (left pinky 1&2 and right pinky 1&2) states are used to index into the FINGERING_ARRAY_2 table to determine an adjustment to the base pitch value. Finally, the octave touch switch states are used to index into the FINGERING_ARRAY_3 table to determine an octave adjustment.

4 – Note on/off message computation
Once the pitch computation and breath input are computed, it’s time to play a note. There are three parts to the note on/off computation section. First, if no note is playing and a new note is started, a MIDI note-on message is sent using the pitch and velocity computed earlier. The breath average array is initialized to this new velocity value and an initial breath control message is sent correspondingly. Second, if a note is already playing but the pitch changes, that means the old note should simply be stopped (MIDI note-off) and the new note started (MIDI note-on) while keeping the breath control message unchanged. Third, if the breath input is stopped, a MIDI note-off message is sent.

5 – Breath controller message computation
The breath controller message is computed by keeping a 50 element rolling average of the breath pressure analog. This averaging gives a filtered response for the control message sent for this controller. The control messages can be one of several MIDI controllers (more later).

6 – Thumb controller message computation
The thumb controller message computation is similar to the breath controller logic – first the analog is read and adjusted to the MIDI range. Then the value is averaged using a 50 element rolling average. Finally, the control message is sent on the selected MIDI control message (more later).

7 – Pitch bend message computation
The pitch bend message computation is based on motion and gestures detected by the MPU-6050 IMU. If the controller is moved down (towards the player’s body) or to the left at an acceleration greater than the set threshold, a pitch bend down and back up is triggered. If the controller is moved up (away from the player) or to the right a pitch bend up and back down is triggered. The values of the threshold, the bend amount, and the bend period can be adjusted.

8 – Handle mode changes
Mode changes allow the user to adjust several aspects of the behavior of the wind controller:

  • profile selection
  • program changes
  • octave changes
  • tuning changes
  • pitch bend threshold changes
  • pitch bend depth changes
  • pitch bend period changes
  • breath pressure MIDI controller assignment
  • thumb pressure MIDI controller assignment
  • patch naming changes
  • reset the touch sensors

9 – Timing test
There’s a constant TIMING_TEST_ENABLED defined in the 02_Const.h file that can be set to ‘true’ to enable print statements in the timing test section. These print statements are used with the Arduino IDE Serial Plotter tool to display a measurement of the time taken by all the processing in a 100Hz frame. A reference line is shown at 10,000 microseconds which is equal to 10 milliseconds (red line). Over a 100 frame average, the software easily runs within the 10ms frame time (yellow line), although blown frames occur in individual frames (blue line) as the outgoing MIDI Messages are sent over Bluetooth. This is unavoidable due to latency in the Bluetooth LE protocol, although it is not noticeable when playing the wind controller.

10 – Battery voltage monitoring
The battery voltage routine reads the current voltage and computes a percent level, which is then averaged over 50 readings. The routine also detects if the USB power is present and handles the red LED control associated with this.

11 – Display control
The display control routine handles all the aspects of the display of information on the SSD1306 OLED display (see above). Page 0 is the select profile choices. Page 1 is the program, octave, and tuning choices. Page 2 is the pitch bend threshold, depth, and period choices. Page 3 is the breath pressure and thumb pressure MIDI assignment choices and the patch names display setup. Page 4 is the ‘select function’ where touch can be reset or the profile can be changed (re-selected).


This was a fun project and hopefully there’s enough information here if you want to build one for yourself.  If you do build one, let me know how it works out!

Comments are closed.