Years ago, and on MSP430 not AVR, I found that gcc produced tighter code
using `if` instead of `switch`. Thus, I found myself always using `if`
and never `switch` if I needed branching, since smaller code is faster
code, and we needed all the speed we could get.
Turns out, AVR does not have this problem, and we get better performance
using `switch`.
Rather than hunting for a free channel, just hard-code the button to the
channel. This gets polyphonics sort-of working: both notes are
out-of-tune which suggests the CPU isn't keeping up.
It seems when the NJM2113 is first powered up, it emits a pop noise.
Not much I can do about that. The thought was I should power down the
amplifier when it is not in use to save power.
The outcome is on each note, it would power the amp up, and it would
pop.
Solution: create a 1msec timer for general timing events, and use that
to count down one minute before powering off the amplifier. The toy
will pop on first power on, then if left alone for a minute, will pop
again on the first note, but will not pop so long as there's only short
gaps between notes.
Since the number of notes possible with this device is small… in fact,
it seems to only want to do monophonics, not sure why, we can bump the
amplitude up a bit more without risk of saturation.
Seems `~value` doesn't quite have the intended effect. The intent was
that, being the 1s compliment of `value`, the expression would return
zero if `value` was all ones, and non-zero.
This doesn't quite work the way I'd like it to. A comparison should be
the same number of CPU cycles, and is more readable, so we'll do that.
This implements detection of the pressed buttons, with software
debouncing, and plays a note indefinitely until the button is released.
The corresponding LED's amplitude is modulated by the ADSR envelope.
This is useful if you don't know how long a given note will be held for…
you can just trigger the note upon pressing the button and have it
progress to the "sustain" state, at which point it'll hold the note.
You can then call `adsr_continue` to "release" that note when you detect
the button release and have it decay.
The ATTiny85, as good as it is, has too few pins to really do a lot.
You can buy I²C GPIO expanders, but many cost more than programming an
ATTiny24A (which has more brains).
The nearest equivalent is the ATTiny861, it is basically the same core
as the ATTiny85, features the same PLL for high-speed PWM, but comes in
a 20-pin package. They cost ~AU$3 in individual quantities. (An
ATTiny85 costs AU$1.74, an ATTiny24A costs AU$1.57; thus you save about
20c and a lot of interfacing effort in going to the '861.)
Thus it can interface to 8 individual switches with ease.
The circuit here uses a 74374 D-latch to drive up to 8 LEDs with PWM and
two 4066s to isolate the 8 inputs. Idea being, when the GPIO_EN signal
is high, the 4066s are turned on and port A sees the 8 GPIO lines on the
other side of the 4066s.
4066s were used because I have 4066s up the wazoo… bought a box of
random ICs off eBay many years ago and it came with 5 (!) tubes of
MM74HC4066s with 25 ICs each (amongst other parts, some hard-to-find).
The bonus being these can be ADC inputs too if desired, allowing sensing
of piezo sensors.
When we want to switch which LEDs are turned on, we bring GPIO_EN low,
switch port A's pins to outputs, assert the desired LEDs, then bring
GPIO_EN high again. The 74374 latches those pins, and we are free to
put port A back to being inputs.
This happens with each sample from the synthesizer, alternating between
input and output. Thus the effective rate seen on the LEDs and inputs
is half the sample rate.
A spare GPIO is available for turning on and off an amplifier (I use the
NJR NJM2113D) to save power.
The 74374's nOE pin is connected to the PWM output for the lights, thus
using pull resistors, one is able to use the one PWM channel for all 8
lights. The lights are turned on in round-robin fashion, so effective
duty cycle is ⅛ and the refresh rate is ~500Hz.
This works by relying on a resistor ladder that is part-shorted-out by a
button to Vcc. The ADC detects the button pressed from the voltage seen
on the ADC.
There's some work to be done on this yet.
I left this out because I thought the idea of modulus and division on a
MCU that lacks a hardware multiplier would be too much for it… and
indeed it was too much with my other project.
Thinking about it this afternoon, I had an idea. If I have 2^N samples,
then the modulus can be optimised to:
```
mod = sample & ((2**n)-1)
```
and the segment can be figured out by:
```
segment = sample >> n
```
The segment is two bits. A function that returns the scaled sine value
for a given scaled angle can be given as:
```
int8_t fp_sine(uint8_t angle) {
uint8_t segment = (angle >> POLY_SINE_SZ_BITS) & 3;
uint8_t offset = angle & ((1 << POLY_SINE_SZ_BITS)-1);
switch (segment) {
case 0:
return _poly_sine[offset];
case 1:
return _poly_sine[
POLY_SINE_SZ - offset];
case 2:
return -_poly_sine[offset];
case 3:
return -_poly_sine[
POLY_SINE_SZ - offset];
}
}
```
… or something like that. If `POLY_SINE_SZ_BITS=6`, then `angle=255`
represents 360°.