Let's Design and Build a (mostly) Digital Theremin!

Posted: 5/1/2021 11:07:54 AM

From: Occitanie

Joined: 3/4/2012

Don't ask too much to MIDI... Even the standards committees of the 1980's have to obey the second law of thermodynamics and create some unavoidable antropy. Focus on the thereminist.

The two states of the Theremin are Play and Mute.

During Play:
The Theremin makes some sound.
Under a certain volume threshold the theremin doesn't send any Note or Pitch bend Message.
If volume increases above the Threshold, a single Note On is sent and then Pitch Bend will take care of exact pitch
If volume decreases under the Threshold, a single Note Off is sent.

During Mute:
The Theremin doesn't make any sound.
The theremin doesn't send any Note or Pitch bend Message

From Play to Mute:
The only thing you want to avoid is that distant synths plays a constant "beep" because the thereminist forgot to approach his hand to the volume antenna before pushing Mute button. Any solution should do : Known Note Off, All Note Off or All sound Off.

Posted: 5/1/2021 12:29:01 PM

From: Northern NJ, USA

Joined: 2/17/2012

"The two states of the Theremin are Play and Mute."  - Mr_Dham

Yes, following your excellent Open.Theremin MIDI code that's pretty much exactly what my D-Lev assembly does.  In order to meet my real-time budget I've have to break the states down into sub-states, and am thinking of moving the code to the encoder thread where it would run at 4.8kHz rather than 48kHz interrupt rate (the MIDI command rate is really low bandwidth, unfortunately).

Posted: 5/1/2021 2:15:20 PM

From: Occitanie

Joined: 3/4/2012

Yes, it should be safe to put MIDI processing in a task running at 4.8KHz.
Knowing that a 3 bytes messages usualy take 1ms and that you are probably sending severall of them at once,  you even have enough time to overload the MIDI stack.
It is not as clean as with regular interrupt but MIDI OTV3 calls MIDI processing every 3ms (300 Hz). The average player that I am don't suffer from that.

the MIDI command rate is really low bandwidth

Yes indeed !

Posted: 5/3/2021 2:17:43 PM

From: Northern NJ, USA

Joined: 2/17/2012

Pretty MIDI

Moved the MIDI code over to thread 7 where it now co-exists with the encoder and pushbutton sampling, debouncing, and conditioning code.  These are invoked via the 48kHz IRQ, and a 0-9 counter time-slices them (0-7:encoder conditioning; 8:sampling; 9:MIDI) so it's running at 4.8kHz.  Each slice gets the full 180MHz / 48kHz / 8 threads = 468 cycles, which is way more than enough (I count 87 cycles max for MIDI).  Given this extra contiguous real-time, I was able to reduce the state machine states to 5:

State 0 is inactive, and if the volume hand crosses the trigger point we calculate velocity and go to state 1.  In state 1 we transmit CC, pitch bend, and note-on.  Then we go to state 2 where we check for activity and transmit MIDI CC data.  Then to state 3 where we transmit pitchbend data and check to see if the pitchbend range has been exceeded.  The loop from state 2 to 3 and back again is the main path for pitch bending an active note.  In state 4 we transmit note-off, and go to state 1 to play a new note (at the same velocity) if still active (which means the pitchbend range was exceeded), otherwise we go inactive.

There is code at the entrance of the state machine which can prevent it from running at all, such as when chan=0 or Mute=1, and transmitting MIDI mute data is handled there.


More Saferer Encoder Velocity

I was taking one last swing at calculating encoder velocity, this time with a bi-modal low-pass filter instead of the funky slew limiter.  That was a bust so the slew limiter remains, but in the process I came up with a safer way to apply velocity.

When sampled, the two bit encoder detent delta takes on one of 4 values: {-2, -1, 0, +1}.  Since -2 is ambiguous (it could represent -2 or 2) we set it to zero, which leaves: {-1, 0, +1}.   Velocity is just accumulated deltas over time, with a leaky bucket type arrangement for when delta = 0.  So say a bunch of +1 deltas come in quickly and they peg the velocity at +3.  For each +1 delta we issue the encoder delta added to the velocity, for a total of +4 per detent, which speeds up the knob action by 4.  What should we do if in the middle of all of this we suddenly get a -1 delta due to an encoder glitch?  I think the best action is to always take the delta at face value, but not use the velocity when it differs in sign with the delta.  In this case then we accumulate the -1 which drives the velocity downward to +2, but since the velocity is positive we don't use it and only issue the -1 delta, which limits the damage.

How might one implement this?
1. Save the velocity sign (-1 or +1).
2. Multiply velocity and delta.
3. Limit the result of step 2 (set negative values to 0).
4. Multiply the result of step 3 by the velocity sign of step 1.
5. Add the result of step 4 and delta together, issue this value to the software.

To see how this works, plug in some values:
Velocity=+3 and delta=+1: the result is +4.
Velocity=+3 and delta=-1: the result is -1.
Velocity=-3 and delta=+1: the result is +1.
Velocity=-3 and delta=-1: the result is -4.

Viola Voila!

[EDIT] Put more simply: multiplying two things with the same sign will always be positive; multiplying dissimilar signs will always be negative.  So just lop off any negative results and restore the sign.

Posted: 5/3/2021 5:21:14 PM

From: Northern NJ, USA

Joined: 2/17/2012

"Boing" Goes The D-Lev

Also made the resonator shut up at preset change.  I found two presets that would (sometimes, randomly) "boing" when switching from A=>B (but not B=>A).  Ran through all the B knobs, setting them one-by-one to the A settings, starting with the usual suspects.  Surprisingly, setting osc and nois to 0 didn't stop it.  Narrowed it down to the single RESON freq knob, and changing this abruptly (smaller) caused the "boing".  I tried zeroing out the resonator sample memory and again surprisingly that didn't stop it. 

What did finally stop it was setting the resonator sample memory pointers (all-pass and simple delay) to zero.  Clearing the memory is also a good thing to do, as otherwise there are instances of A=>B then B=>A where the original A decay sound resumes upon return to A, which is understandable but obviously rather weird. 

So audio drops out for 1024/48kHz = 0.0213 seconds at preset change during resonator sample memory clearing, which isn't anything really (1/47 Hz) and is certainly much less objectionable than a loud-ass "boing".  (This time could be dramatically reduced, but I am again running low on code space and would like to keep a tiny reserve for real emergencies.)  And extraneous decays upon return to a previously played preset have been quashed.

Posted: 5/10/2021 2:12:07 PM

From: Northern NJ, USA

Joined: 2/17/2012

Manual Labor

Been toiling away on the D-Lev manual these past few weeks.  Most modules, screens, and controls have been covered, and the field setup instructions are clearer - even in my own mind - than ever before, which is nice.  Still need to add a quick setup section, some conventions, and maybe a section on errors.

I found the free ASL font "GallaudetRegular.ttf" and converted the characters 'S' (closed fist) and '5' (open hand) in Inkscape for use in the field setup diagrams.  Between drawio, LibreOffice Writer, and Inkscape, anyone can produce a decent looking manual.


Human Watchdog

One thing the very latest SW does is show the boot version on the screen in case of a corrupt SW load in EEPROM.  I'm thinking also of somehow displaying the processor error register if it is non-zero.  The user could note the error code, and then do a power cycle to clear it.  I should have had something like this in place long ago if only for development, as it could have quickly revealed any problems without me having to drop to the terminal in the librarian to read the error register (which I've done thousands of times over the years).

Posted: 5/12/2021 1:44:04 PM

From: Northern NJ, USA

Joined: 2/17/2012

Fly Me To The Moon

Sitting around evenings listening to random CDs donated to our local Historical Society jumble shop is acting like a stool softener on my brain.  Lots of old tunes oozing out of the leaky memory banks.  So I'm playing "Fly Me To The Moon" but the version with the waltz tempo, rather than the coo-coo jazzy Sinatra Vegas act tempo.  Not sure why the former is more ensconced in my head - elevator music? - but that's more the way it was written.  With the latter there's the usual "singer or song stylist?" shifting of notes and timing, but what's really weird is the waltz version is in 3/4 and the "Old Blue Eyes" version is in 4/4 - not sure I've ever encountered such wholesale mucking about with a tempo before.

Compare and contrast:

Posted: 5/14/2021 9:21:49 PM

From: Occitanie

Joined: 3/4/2012


I looked at the state machine. It should work fine.

Just one question: Why do you send CCs only when a note is active ?

Some users may want to use the theremin as a contoler only (e.g. A guitar player controling wha-wha effect  by entering in pitch antenna's field with his guitar neck).

Posted: 5/15/2021 12:53:55 PM

From: Northern NJ, USA

Joined: 2/17/2012

"Just one question: Why do you send CCs only when a note is active ?

Some users may want to use the theremin as a contoler only (e.g. A guitar player controling wha-wha effect  by entering in pitch antenna's field with his guitar neck). "  - Mr_Dham

Good question!  I guess I've been thinking of CC as a volume type thing - not that it strictly is of course, but that's what it mainly gets used for on a Theremin controller.  Your software IIRC sends auxiliary CC for each axis; mine is more limited and only sends a single CC for the volume axis.  So a guitarist "playing" the pitch antenna with his guitar wouldn't get any CC action anyway.  If he instead played the volume antenna, then the CC would kick in.  But you have a point, the CC could be a largely independent process.  I'll need to think about this a little more.  I do appreciate your input!

[EDIT] I was able to combine states 2 & 3, and now do a CC transmit at each exit of the state machine subroutine (i.e. at each state transition, including staying in the current state):

It seems to be working fine, thanks for the suggestion Vincent!  I'm all for reduced complexity...

Posted: 5/22/2021 1:37:48 PM

From: Northern NJ, USA

Joined: 2/17/2012

In Other Words...

That song is a total earworm, been stuck in ma haid for days: [MP3] (I'm playing it in 3/4).

As I noted earlier over here, pitch correction covers up all kinds of sloppiness in my playing.  And I've noticed (for better or worse) that I use it to "form" the melody by quickly centering the notes, rather than via volume dips, which is something I never anticipated.

You must be logged in to post a reply. Please log in or register for a new account.