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

Posted: 11/8/2023 2:45:03 PM

From: Northern NJ, USA

Joined: 2/17/2012

Reactive Near Field

Theremins operate in the reactive near-field: https://en.wikipedia.org/wiki/Near_and_far_field:

Interestingly, the article says this portion of the field decreases by the inverse-cube, rather than inverse square of the distance.  The D-Lev linearizing gamma function uses a nominal -1/4 power, which is the 4th root of the inverse, and applied to the offset (the nulled difference in an analog Theremin) frequency.  So perhaps the very near field (i.e. right at the antenna) has an even higher power to it's diminishing?

I think because the magnetic component is so small (the antenna is physically much smaller than the wavelength) there is essentially no directionality to be found.  The only way to give it any directionality is via shielding, and that directionality would likely exist mainly in the near-field, because once you get far enough away from anything it's essentially a point source.

Fascinating article though, wish I'd seen it much sooner as it addresses the very basics of Theremin operation.  So much of web searching is a catch 22: you have to know the terminology to find anything, but you often don't know the terminology until you find it.

Posted: 11/8/2023 9:07:20 PM

From: Theremin Motherland

Joined: 11/13/2005

portion of the field decreases by the inverse-cube

As well as the electric field of a dipole!

Posted: 11/9/2023 4:33:45 PM

From: Northern NJ, USA

Joined: 2/17/2012

"As well as the electric field of a dipole!"  - ILYA

Ah, so it is!

Posted: 11/13/2023 10:33:41 AM

From: Porto, Portugal

Joined: 3/16/2017

Hmm... Does oscillator frequency play any role in theremin hand response? E.g. increasing of frequency might move working range to another region (radiative?)

Posted: 11/13/2023 12:41:56 PM

From: Northern NJ, USA

Joined: 2/17/2012

"Hmm... Does oscillator frequency play any role in theremin hand response? E.g. increasing of frequency might move working range to another region (radiative?)"  -- Buggins

Say the pitch field is operating at 300kHz (e.g. a typical analog Theremin).  Wavelength = velocity / frequency = 3e+8 / 3e+5 = 1000 meters.  The article says that if the antenna is equal to or less than 1/2 wavelength (certainly the case here!) then the reactive near field boundary is located 1 / 2*pi wavelengths away from it: 1000 / (2 * pi) = 159 meters.  To keep the operation within a 1 meter playing envelope, the frequency could then increase up to 159 * 300kHz = 47.7 MHz.  Interesting!

Posted: 11/15/2023 11:06:37 AM

From: Porto, Portugal

Joined: 3/16/2017

Tang Primer 25k is available on Aliexpress: Sipeed twitter
23x18mm module, 23k LUTs, 23k FFs, 8MB SPI FLASH, no on-module SDRAM.
50MHz xtal, 76 IOs, 27 dsp, 6 plls. 1Mbit BRAMs.

EUR 22 for module, EUR 34 for module + carrier board. Aliexpress link

Docs:

Module itself has 1 lane MIPI D-PHY, but it's not available on carrier board.
Can MIPI be used as high frequency (resolution) I/O for sensor - just as fast serializer/deserializer?
Electrical specification says it's differential signalling with 200mV common mode voltage and 200mV differential voltage - on AFE side can be received / transmitted with BJT cirquits.

Posted: 11/17/2023 1:10:20 PM

From: Northern NJ, USA

Joined: 2/17/2012

"Tang Primer 25k is available on Aliexpress: Sipeed twitter 23x18mm module, 23k LUTs, 23k FFs, 8MB SPI FLASH, no on-module SDRAM. 50MHz xtal, 76 IOs, 27 dsp, 6 plls. 1Mbit BRAMs."  - Buggins

1Mbit / 8 bits-per-byte = 131k bytes; / 4 bytes-per-sample = 32k; / 48kHz sample-rate = 0.68 seconds.  Not enough for long delays or reverb.

"EUR 22 for module, EUR 34 for module + carrier board."

Those are good prices!

What would be great would be a processor with high speed peripherals, perhaps those used for PWM motor control could be co-opted (as Evan pointed out)?

Posted: 11/18/2023 5:57:45 PM

From: Porto, Portugal

Joined: 3/16/2017

1Mbit / 8 bits-per-byte = 131k bytes; / 4 bytes-per-sample = 32k; / 48kHz sample-rate = 0.68 seconds.  Not enough for long delays or reverb.

I suppose that BRAM should be mostly used for softcore program and data memory.
For reverb, and other memory consuming purposes (e.g. LCD framebuffer), QSPI PSRAM may be used.
8MB soic-8 QSPI PSRAM chips are cheap (\$2-\$3).
When you need to read or write sequence of bytes, throughput 50-100Mbytes/s may be achieved (100-200MHz QSPI clock).
But if you only need to read only one byte from random location, it will take many cycles (send command, address, then data) and the speed will drop down to 5-10 MB per second.

Posted: 11/19/2023 5:27:07 PM

From: Northern NJ, USA

Joined: 2/17/2012

"For reverb, and other memory consuming purposes (e.g. LCD framebuffer), QSPI PSRAM may be used."  - Buggins

I can't say that RAM with a serial interfaced would be the first thing that pops into my head for reverb, but there are likely a fixed number of memory reads and writes for each I/O sample.  Since reverbs consist of multiple delays, most of the reads would be sequential over samples, but not per sample.  Probably doable though.

Posted: 11/19/2023 6:15:38 PM

From: Northern NJ, USA

Joined: 2/17/2012

Fuzzy Wuzzy

Spent the last week or so investigating and experimenting with fuzzy matching via recursion.  There are two applications of it in the D-Lev librarian: for command hints if an unknown command is entered, and for the knob command page name resolution.  Previously I was relying on prefix matching plus the minimum calculated Levenshtein distance between the pattern and candidates.  Prefix works well, but despite the hype Levenshtein is a really poor fit - it's more of a crypto tool really.

I ran across this excellent explanation of fuzzy matching, with a functional example that's really fun to play with:
https://www.forrestthewoods.com/blog/reverse_engineering_sublime_texts_fuzzy_match/#.d05n81yjy

The method entails comparing each letter in the pattern (in sequence) to each letter in the candidate (in sequence) and scoring/weighting certain special conditions associated with the individual character matches.  A first letter match is obviously important, as are successive matches, camelCase matches, and matches that occur after a separator.  Doing it in a best effort way requires an exhaustive exploration of the solution space and picking the highest score.

Fuzzy matching isn't built-in to the Go standard library, and the examples I found out there were kinda messy and hard to follow (and therefore trust).  It may be possible to implement an exhaustive approach via 2 or more loops (?) but I decided to walk the solution space via recursion, another thing I have no real experience with.  A loop is used to compare a given pattern char to the remaining candidate string, and with each match recurse to the next char in each, returning the score and a flag indicating whether the entire pattern was traversed or not.  Once that was working I added the scoring weights.

Another function which deals with a list of candidates calls this and sorts the results based on score and match flag.  There's a >100k word list in most Linux installs (/usr/share/dict/words) that I used for experimenting with the various weights.  While it works absolutely fine for the purposes of the librarian, I'm not 1000% happy with the general results, but that's probably inevitable with almost any heuristic approach.

These sorts of exercises can be criticized as "reinventing the wheel" but they're fun and give me a deeper understanding of the concepts - and of the programming language itself.  Designing my Hive processor, assembly language, math libraries, and simulator gave me an understanding computing that I could get nowhere else.  To me it's somewhat akin to a musician practicing a piece, is that wasted time?  Much school learning is working problems.  Also 90% of the code out there is crap (which for all I know includes mine) so it's hard to want to pull in anything that isn't from an anointed, polished, and heavily banged on standard library.

[EDIT] A few small repairs to the fuzzy mechanics and scoring:
1. Making the best score update only at full pattern match eliminates any chance that a higher unmatched score gets reported as the final matched score.
2. Feeding the score forward rather than having it returned makes the best score updating and recursion somewhat cleaner.
3. Best score and match flag are now global within the function, with the recursion happening via a nested sub function.
4. Unmatched leading chars penalty is now -1 per char.
5. So the minimum unmatched score is then -len(target).
6. No need for match char bonus.

#1 precludes non-fully matched scoring, but whatever.  I'm finally very happy with the results!

[EDIT2] Even more tinkering with the scoring:
7. Matched chars get a penalty of the distance from the start (-t_idx).
8. Matched chars get a bonus of 60 / (t_idx + 1).
9. A penalty of the length of the target is applied at the end of scoring (-t_len).
10. So the minimum unmatched score is then -2 * len(t_len).

#7 lightly biases everything towards the beginning chars of the target.
#8 is very non-linear and heavily biases the first chars of the target, and this bias continues inward for some distance in a milder fashion.

Integers are used for scoring weights, and integer resolution is kind of a problem, though the weights could be scaled up to mitigate this somewhat.  I assume ints are used to maximize the speed.