Last active
July 26, 2022 16:10
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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