Drone suffers from click - any suggestions for improvement?

259 views
Skip to first unread message

Gazzah

unread,
Feb 24, 2022, 2:26:41 AM2/24/22
to Mozzi-users
Hello.
(First post here in Mozzi-users!)

I'm working on a drone with eight oscillators based on the the 8knobs Drone, which in turn is based on the Control_Oscil_Wash sketch. I'm running it on an Arduino Nano (3rd party knock-off), with potentiometers on A0-A7 and toggle switches on D2-D3. The toggle switches change the oscillator type, and the pots adjust the pitch of the oscillators.

I have a problem with a faint but regular clicking noise. This isn't present in the original Control_Oscil_Wash but I do hear the click in the 8knobs Drone sketch. I think it's a performance problem due partly to the time taken to read the 8 pot values, and partly the time taken to update the 8 oscillators.

I've made the code a bit more efficient, which has reduce the frequency of the click from several times a second to about once every two seconds. I wonder if you could suggest any further improvements I could try.

What I have tried:
- Changed from reading all analog inputs on every Update to only read two per update
- Only set the oscillator type when the toggle switch values have changed
- Replaced 'if-else' with 'switch-case'
- Tried setupFastAnalogRead(FASTEST_ADC) but it made no difference (maybe I'm doing it wrong?)
- Use bit-shift instead of multiplication
- Moved code that sets Oscillators out of updateControl into separate voids
- Lower CONTROL_UPDATE values, eg 64, 32. This reduces click frequency, but doesn't eliminate it.

Reducing the number of oscillators from 8 to 6 definitely gets rid of the click, but it would be great to get it running smoothly with all 8 at a CONTROL_UPDATE of 128 if at all possible. (The pitch adjustments sound 'steppy' if I run at 64 or lower, and I would like to combine with another sketch that also sounds best at 128)

Questions:
- Are there other optimizations I could try?
- Am I using FastAnalogRead incorrectly?
- Could the problem be my cheap 3rd party hardware?
- How should I post my code here?

Any and all advice gratefully received.

staffa...@oscillator.se

unread,
Feb 24, 2022, 7:28:39 AM2/24/22
to mozzi...@googlegroups.com
I only quickly glanced at your code, but one thing I would try would be
to get rid of the 8 volume oscillators, and replace them with simple
variables that just inc/dec between two values (like a triangle wave). I
can see that you only call them in updateControl(), but it might still
be worth a try. You can start by commenting out the 8 calls in
updateControl() and see if the clicks lessen/disappear.

Can you simplify the audioControl() loop? Maybe let one v* control 2
oscillators, so you decrease the number of multiplications which I guess
are really time consuming? Something like this:
(aOsc1.next() + aOsc2.next()) *v1 +
etc

I am not very good at optimisation, somewhere you just have to
compromise with your idea, or use better/faster hardware.

I've used Mozzi on the M5Stack Core 2 with a lot of voices happening at
the same time:
https://www.oscillator.se/opensource/#m5stack

But now I use the Daisy Seed for my audio projects:
https://www.oscillator.se/opensource/#daisy
It is priced like an Arduino but is much more powerful.

/Staffan, SWEDEN


2022-02-24 08:26 skrev Gazzah:
> Hello.
> (First post here in Mozzi-users!)
>
> I'm working on a drone with eight oscillators based on the the 8knobs
> Drone, which in turn is based on the Control_Oscil_Wash sketch. I'm
> running it on an Arduino Nano (3rd party knock-off), with
> potentiometers on A0-A7 and toggle switches on D2-D3. The toggle
> switches change the oscillator type, and the pots adjust the pitch of
> the oscillators.
>
> I have a problem with a faint but regular clicking noise. This isn't
> present in the original Control_Oscil_Wash but I do hear the click in
> the 8knobs Drone [1] sketch. I think it's a performance problem due
> - How should I post my code [2] here?
>
> Any and all advice gratefully received.
>
> --
> You received this message because you are subscribed to the Google
> Groups "Mozzi-users" group.
> To unsubscribe from this group and stop receiving emails from it, send
> an email to mozzi-users...@googlegroups.com.
> To view this discussion on the web, visit
> https://groups.google.com/d/msgid/mozzi-users/08734ac4-ded0-41c8-9dee-3a8c99d2948cn%40googlegroups.com
> [3].
>
>
> Links:
> ------
> [1] https://github.com/SoundCodes/8knobs/tree/master/codes
> [2]
> https://github.com/CallPhysical/TweakedDrones/blob/main/drones_4_wave_5.ino
> [3]
> https://groups.google.com/d/msgid/mozzi-users/08734ac4-ded0-41c8-9dee-3a8c99d2948cn%40googlegroups.com?utm_medium=email&utm_source=footer

Andrew Row

unread,
Feb 24, 2022, 9:13:02 AM2/24/22
to Mozzi-users
Hi, 

I ran into same issues with my mozzi FM synth on an Arduino nano.

One thing you could definitely do to improve efficiency is to check whether the analog value has changed since the last update before calling setFreq(). These reads will probably be changing constantly every read by a small amount due to noise on the pins, so you can check to see if the value has changed to prevent unnecessary calls to setFreq().  Experiment with the movement threshold to avoid it sounding steppy - in my experience ~5 is ok but depends on the total range.

Example of what i mean is below (also if you are interested, updateAnalogControls() in my synth is how I ended up doing: https://github.com/Meebleeps/MeeBleeps-Freaq-FM-Synth).  I ended up updating all 8 analog controls every 5th time with an CONTROL_RATE of 128 but your mileage will vary.

#define MINIMUM_MOVEMENT_THRESHOLD 5

...

int lastAnalogRead[8];
int thisAnalogRead[8];
...

void  updateControl()
{
  ...
 
  thisAnalogRead[0] = mozziAnalogRead(A0);
 
  if( abs( thisAnalogRead[0] - lastAnalogRead[0]) > MINIMUM_MOVEMENT_THRESHOLD)
  {
    setFreq( thisAnalogRead[0] << 2);
  }
 
  // check the other inputs
  // ...
 

  // lastly record the value to check against next time
  lastAnalogRead[0]= thisAnalogRead[0];

Gazzah

unread,
Feb 24, 2022, 9:50:22 AM2/24/22
to Mozzi-users
Hi Staffan,
Thanks for all the great ideas. You've given me a lot of new possibilities to explore. Incidentally, after reading an older conversation here about performance issues, I tried alternating between reading  the sensors and writing the values on each cycle of updateControls to give the reads more time to complete, but if didn't help much.  I'll certainly give your suggestions a try next.  If all else fails I may have to settle for six oscillators in the current hardware. Thanks also for your suggestions on alternate boards.
Regards
Gary

Gazzah

unread,
Feb 24, 2022, 9:54:00 AM2/24/22
to Mozzi-users
Thanks, that sounds like a great suggestion. I understand your point about needing to allow for noise by using a movement threshold. I'll give it a shot.

Mr Sensorium

unread,
Feb 25, 2022, 1:31:19 AM2/25/22
to Mozzi-users

Hi Gazzah,

just a couple of other thoughts...

Unfortunately I expect the round robin for calling mozziAnalogRead() will give confusing results.

mozziAnalogRead(_pin) does 2 jobs.  It gets a result for _pin and requests a read to happen in the background which it will get next time around.  The results are on a stack so if you don't call mozziAnalogRead() for your pins in the same order each time, your stack gets mixed up and the results won't be the right ones for the pins you're reading.

Also, setupFastAnalogRead(FASTEST_ADC) only affects the background reading process, it won't save time when you call mozziAnalogRead() to get and request the reads.  FASTEST_ADC might give less accurate results than just leaving it at the default.

mozziAnalogRead() is fairly efficient, so calling all your pins each updateControl() wouldn't be a big hit, and you could do setFreq() in the round robin.  Or you could do a more complicated/confusing version calling mozziAnalogRead(_pin) once each updateControl(), updating and keeping track of which pin it's receiving and requesting as you cycle through the pins one per updateControl(). 

It will be hard to get much more out of the processor, so if you manage it without dropping any oscillators, you're doing well!

tomco...@live.fr

unread,
Feb 25, 2022, 5:10:45 AM2/25/22
to Mozzi-users
Hei!

To add a bit on this,

It is possible not to read all analogPins at each control round. Here is an example where the MIDI needed to be updated very frequently, but not the analogRead:


void updateControl() {


while (MIDI.read());

//set_freq(0);

//Serial.println(volume>>7);


toggle++;


switch (toggle)

{

case 1:

mix1 = mozziAnalogRead(PB0) >> 4;

break;

case 2:

mix2 = mozziAnalogRead(PA6) >> 4;

break;

case 3:

wet_dry_mix = mozziAnalogRead(PA3) >> 2; // goos to 1024

break;

case 4:

mix_oscil = mozziAnalogRead(PA5) >> 4 ;

break;

case 5:

chord_release = mozziAnalogRead(PA7) >> 0 ;

break;

case 6:

chord_attack = mozziAnalogRead(PB1) >> 0 ;

break;

case 7:

breath_on_cutoff = (mozziAnalogRead(PA4) >> 4);

//cutoff = cutoff_smooth.next(((breath_on_cutoff * volume) >> 13 ) + midi_cutoff); // >>8


break;

case 8:

resonance = mozziAnalogRead(PA2);

break;

case 9:

breath_sens = mozziAnalogRead(PA1) >> 4;

toggle = 0;

break;

}
}

That actually looks like what you are doing. Otherwise it looks fairly optimised already… There are still a few course of action you can try:
  - change the optimization level of the compiler: Arduino is by default compiling by optimizing the size of the program, not performances. You can try with level -O2 or -O3 (no guarantee on which one will be the best) if you have some room in memory. If not, you can always choose smaller tables
  - does the click happens even if you are not touching the switch?
  - your asig is a long type: 32 bites. Arduino is 8 bits, hence, it will be a lot of operation to add and multiply long number. You can make smaller (say 16 bits and shift your results along the way, not only at the end).
  - there is always the possibility to move to a more powerful hardware. There are a lot of different platforms supported by Mozzi now, Arduino being the slowest of them ;).

Hope it helps,
Thomas

Gazzah

unread,
Feb 25, 2022, 8:24:13 AM2/25/22
to Mozzi-users
Thanks, Mr Sensorium,
I've had some success splitting up the updateControls into 5 cycles in the pure Drone sketch : 1: read four analogs, set two volumes, 2: write four frequencies, set two volumes, 3: read next four analogs, set two volumes, 4: write four more frequencies, set remaining two volumes, 5: read digital pins. Repeat. That got the click rate down from once every 8 secs to about once every 24 secs.  That sounds quite acceptable to me. But when I try to merge that sketch into the other synth sketch, the click is back with a vengeance, so clearly more work to do.  I'll see how I can apply your suggestions. When (If?) I get good results I'll post a link to the code here. Cheers!

Gazzah

unread,
Feb 25, 2022, 8:28:09 AM2/25/22
to Mozzi-users
Thanks Thomas, (by the way, I'm clicking 'Reply All' here, not sure if I should be...)
Interesting point on the compiler option, I didn't know about that.  Yes, the click happens eve when I'm not touching anything, and it's regular. As noted above, I've managed to get the click rate down quite a lot thanks to everyone's suggestions. I'll study your code and try the compiler tweaks. Cheers!

Gazzah

unread,
Mar 2, 2022, 7:25:24 AM3/2/22
to Mozzi-users
Hi All,
Just reporting back on the problem of the clicky drone synth. Thanks to you guys, I fixed it!  It took a number of tweaks to eliminate the repetitive click I was hearing, but the main two were as follows: Firstly, I put a switch statement into the updateControl routine to alternately read the analog inputs and write the frequencies, in small batches. Secondly, I rewrote the final calculation that was using type Long so that it could use type Int.  Thank you so much for all your kind help.

Original calculation:
  long asigDRN = (long)
    aOsc1.next()*v1 +
    aOsc2.next()*v2 +
    aOsc3.next()*v3 +
    aOsc4.next()*v4 +
    aOsc5.next()*v5 +
    aOsc6.next()*v6 +
    aOsc7.next()*v7 +
    aOsc8.next()*v8;
  asigDRN >>= 9;  // divide by 512
  return (int) asigDRN;

Modified calculation:
    int asig1 = (aOsc1.next()*v1 + aOsc2.next()*v2)>>1; // sum two and divide by 2
    int asig2 = (aOsc3.next()*v3 + aOsc4.next()*v4)>>1;
    int asig3 = (aOsc5.next()*v5 + aOsc6.next()*v6)>>1;
    int asig4 = (aOsc7.next()*v7 + aOsc8.next()*v8)>>1;
    int asig5 = (asig1 + asig2)>>1; // sum two and divide by 2
    int asig6 = (asig3 + asig4)>>1;
    int asigDRN = (asig5 + asig6)>>7; // divide by 128
    return(asigDRN);


The final code is here: https://github.com/CallPhysical/TweakedDrones.  There are two versions of the sketch: 5.0a is a combined Helios and 6-voice drone that's designed to run on an unmodified BlogHoskins Helios Synth. And 5.0 (without the 'a') is an 8-voice version that assumes an expanded Helios with two extra analog pots and one more switch on D4.

Staffan Melin

unread,
Mar 2, 2022, 8:05:57 AM3/2/22
to mozzi...@googlegroups.com
I am happy that you solved it!

Thank you for sharing the knowledge and final code, i am sure other people will benefit from it.

Staffan
Reply all
Reply to author
Forward
0 new messages