Skip to content

Instantly share code, notes, and snippets.

@joefutrelle
Last active July 26, 2022 16:10
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save joefutrelle/e127f9dcd8e66a21808d to your computer and use it in GitHub Desktop.
Save joefutrelle/e127f9dcd8e66a21808d to your computer and use it in GitHub Desktop.
Direct digital synthesis (Karplus-Strong plucked string algorithm) on the Atmel ATtiny85 AVR microcontroller using the MCP4725 12-bit DAC over I2C using USI-based TWI as described in Application Note AVR310
/**
* Author: Joe Futrelle
* License: CC0 - Free as in freedom
*/
/**
* ATtiny85 direct digital synthesis
* using MCP4725 12-bit I2C DAC
* TWI is handled using Atmel's USI TWI implementation
* which is provided as part of Application Note AVR310
* http://www.atmel.com/Images/Atmel-2561-Using-the-USI-Module-as-a-I2C-Master_AP-Note_AVR310.pdf
*/
/**
* For details of the MCP4725 TWI protocol see its datasheet at
* http://ww1.microchip.com/downloads/en/DeviceDoc/22039d.pdf
* Breakout board from Adafruit recommended for prototyping
*/
#include <avr/io.h>
#include <stdlib.h>
#include "USI_TWI_Master.h"
// DAC address (may vary, see datasheet)
#define DAC_ADDR 0x62
// DAC "write but don't write to EEPROM" command
#define DAC_WRITE 0x40
// Use unsigned 16-bit values for DAC 12-bit values
#define DAC_T uint16_t
// TWI message buffer; only need 4 bytes
uint8_t buf[4];
// call before using dac_out
void dac_init() {
// prepare to send a bunch of write commands
buf[0] = DAC_ADDR << 1;
buf[1] = DAC_WRITE;
}
void dac_out(DAC_T v) {
// must call dac_init first to populate first two bytes
// of message buffer
// construct 2-byte representation of 12-bit value
buf[2] = v >> 4;
buf[3] = ((v & 0xF) << 4);
// now send the write command over USI TWI
USI_TWI_Start_Transceiver_With_Data(buf, 4);
}
/**
* Random note player using Karplus-Strong plucked string
* algorithm
* http://en.wikipedia.org/wiki/Karplus%E2%80%93Strong_string_synthesis
*/
// using signed sample type
#define SAMPLE_T int16_t
// zero is 0xFFF >> 1 in unsigned representation
#define ZERO 2047
// sample buffer
#define BUF_SIZE 64
SAMPLE_T wav[BUF_SIZE];
// minimum wavelength
#define MIN_WL 5
// noise burst length
#define NOISE_L 24
size_t ks_wptr; // position in wavetable
SAMPLE_T ks_prev; // previous sample
void ks_init(size_t wl) {
// initialize wavetable to short noise burst
size_t i;
for(i = 0; i < wl; i++) {
if(i < NOISE_L) { // short burst of noise
wav[i] = (SAMPLE_T)(rand() & 0x0FFF) - ZERO;
} else {
wav[i] = 0;
}
}
// initialize wavetable pointer
ks_wptr = 0;
// initialize filter
SAMPLE_T ks_prev = wav[wl-1];
}
SAMPLE_T ks_iter(size_t wl) {
// retrieve sample value from buffer
SAMPLE_T v = wav[ks_wptr];
// now filter the sample
wav[ks_wptr] = (ks_prev / 2) + (v / 2);
ks_prev = v;
// recur
ks_wptr = (ks_wptr + 1) % wl;
// return output sample
return v;
}
int main(void)
{
// initialize
USI_TWI_Master_Initialise();
dac_init();
for(;;) {
// silence
dac_out(ZERO);
// choose wavelength
size_t wl = (rand() % (BUF_SIZE - MIN_WL)) + MIN_WL;
// initialize Karplus-Strong algorithm with this wavelength
ks_init(wl);
// choose number of iterations
uint16_t iter = rand() & 0x0FFF;
size_t i = 0; // position in note
for(i = 0; i < iter; i++) {
dac_out(ks_iter(wl) + ZERO);
}
}
// never reached
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment