mirror of
https://github.com/arduino/Arduino.git
synced 2024-12-11 22:24:13 +01:00
265 lines
10 KiB
C
265 lines
10 KiB
C
|
// Squawk Soft-Synthesizer Library for Arduino
|
||
|
//
|
||
|
// Davey Taylor 2013
|
||
|
// d.taylor@arduino.cc
|
||
|
|
||
|
#ifndef _SQUAWK_H_
|
||
|
#define _SQUAWK_H_
|
||
|
#include <stddef.h>
|
||
|
#include <inttypes.h>
|
||
|
#include "Arduino.h"
|
||
|
|
||
|
#define Melody const uint8_t PROGMEM
|
||
|
|
||
|
class SquawkStream {
|
||
|
public:
|
||
|
virtual ~SquawkStream() = 0;
|
||
|
virtual uint8_t read() = 0;
|
||
|
virtual void seek(size_t offset) = 0;
|
||
|
};
|
||
|
inline SquawkStream::~SquawkStream() { }
|
||
|
|
||
|
class SquawkSynth {
|
||
|
|
||
|
protected:
|
||
|
// Load and play specified melody
|
||
|
void play(SquawkStream *melody);
|
||
|
|
||
|
public:
|
||
|
SquawkSynth() {};
|
||
|
|
||
|
// Initialize Squawk to generate samples at sample_rate Hz
|
||
|
void begin(uint16_t sample_rate);
|
||
|
|
||
|
// Load and play specified melody
|
||
|
// melody needs to point to PROGMEM data
|
||
|
void play(const uint8_t *melody);
|
||
|
|
||
|
// Resume currently loaded melody (or enable direct osc manipulation by sketch)
|
||
|
void play();
|
||
|
|
||
|
// Pause playback
|
||
|
void pause();
|
||
|
|
||
|
// Stop playback (unloads song)
|
||
|
void stop();
|
||
|
|
||
|
// Tune Squawk to a different frequency - default is 1.0
|
||
|
void tune(float tuning);
|
||
|
|
||
|
// Change the tempo - default is 50
|
||
|
void tempo(uint16_t tempo);
|
||
|
};
|
||
|
|
||
|
extern SquawkSynth Squawk;
|
||
|
|
||
|
// oscillator structure
|
||
|
typedef struct {
|
||
|
uint8_t vol;
|
||
|
uint16_t freq;
|
||
|
uint16_t phase;
|
||
|
} osc_t;
|
||
|
|
||
|
typedef osc_t Oscillator;
|
||
|
|
||
|
// oscillator memory
|
||
|
extern osc_t osc[4];
|
||
|
extern uint8_t pcm;
|
||
|
// channel 0 is pulse wave @ 25% duty
|
||
|
// channel 1 is square wave
|
||
|
// channel 2 is triangle wave
|
||
|
// channel 3 is noise
|
||
|
|
||
|
// For channel 3, freq is used as part of its LFSR and should not be changed.
|
||
|
// LFSR: Linear feedback shift register, a method of producing a
|
||
|
// pseudo-random bit sequence, used to generate nasty noise.
|
||
|
|
||
|
#ifdef __AVR_ATmega32U4__
|
||
|
// Supported configurations for ATmega32U4
|
||
|
#define SQUAWK_PWM_PIN5 OCR3AL
|
||
|
#define SQUAWK_PWM_PIN11 OCR0A
|
||
|
#define SQUAWK_PWM_PIN3 OCR0B
|
||
|
/*
|
||
|
// NOT SUPPORTED YET
|
||
|
#define SQUAWK_PWM_PIN6 OCR4D
|
||
|
#define SQUAWK_PWM_PIN9 OCR4B
|
||
|
#define SQUAWK_PWM_PIN10 OCR4B
|
||
|
*/
|
||
|
#endif
|
||
|
|
||
|
#ifdef __AVR_ATmega168__
|
||
|
// Supported configurations for ATmega168
|
||
|
#define SQUAWK_PWM_PIN6 OCR0A
|
||
|
#define SQUAWK_PWM_PIN5 OCR0B
|
||
|
#define SQUAWK_PWM_PIN11 OCR2A
|
||
|
#define SQUAWK_PWM_PIN3 OCR2B
|
||
|
#endif
|
||
|
|
||
|
#ifdef __AVR_ATmega328P__
|
||
|
// Supported configurations for ATmega328P
|
||
|
#define SQUAWK_PWM_PIN6 OCR0A
|
||
|
#define SQUAWK_PWM_PIN5 OCR0B
|
||
|
#define SQUAWK_PWM_PIN11 OCR2A
|
||
|
#define SQUAWK_PWM_PIN3 OCR2B
|
||
|
#endif
|
||
|
|
||
|
/*
|
||
|
// NOT SUPPORTED YET
|
||
|
#define SQUAWK_SPI SPDR
|
||
|
#define SQUAWK_RLD_PORTB PORTB
|
||
|
#define SQUAWK_RLD_PORTC PORTC
|
||
|
*/
|
||
|
|
||
|
extern void squawk_playroutine() asm("squawk_playroutine");
|
||
|
|
||
|
// SAMPLE GRINDER
|
||
|
// generates samples and updates oscillators
|
||
|
// uses 132 cycles (not counting playroutine)
|
||
|
// ~1/3 CPU @ 44kHz on 16MHz
|
||
|
#define SQUAWK_CONSTRUCT_ISR(TARGET_REGISTER) \
|
||
|
uint16_t cia, cia_count; \
|
||
|
intptr_t squawk_register = (intptr_t)&TARGET_REGISTER; \
|
||
|
ISR(TIMER1_COMPA_vect, ISR_NAKED) { \
|
||
|
asm volatile( \
|
||
|
"push r2 " "\n\t" \
|
||
|
"in r2, __SREG__ " "\n\t" \
|
||
|
"push r18 " "\n\t" \
|
||
|
"push r27 " "\n\t" \
|
||
|
"push r26 " "\n\t" \
|
||
|
"push r0 " "\n\t" \
|
||
|
"push r1 " "\n\t" \
|
||
|
\
|
||
|
"lds r18, osc+2*%[mul]+%[fre] " "\n\t" \
|
||
|
"lds r0, osc+2*%[mul]+%[pha] " "\n\t" \
|
||
|
"add r0, r18 " "\n\t" \
|
||
|
"sts osc+2*%[mul]+%[pha], r0 " "\n\t" \
|
||
|
"lds r18, osc+2*%[mul]+%[fre]+1" "\n\t" \
|
||
|
"lds r1, osc+2*%[mul]+%[pha]+1" "\n\t" \
|
||
|
"adc r1, r18 " "\n\t" \
|
||
|
"sts osc+2*%[mul]+%[pha]+1, r1 " "\n\t" \
|
||
|
\
|
||
|
"mov r27, r1 " "\n\t" \
|
||
|
"sbrc r27, 7 " "\n\t" \
|
||
|
"com r27 " "\n\t" \
|
||
|
"lsl r27 " "\n\t" \
|
||
|
"lds r26, osc+2*%[mul]+%[vol] " "\n\t" \
|
||
|
"subi r27, 128 " "\n\t" \
|
||
|
"muls r27, r26 " "\n\t" \
|
||
|
"lsl r1 " "\n\t" \
|
||
|
"mov r26, r1 " "\n\t" \
|
||
|
\
|
||
|
"lds r18, osc+0*%[mul]+%[fre] " "\n\t" \
|
||
|
"lds r0, osc+0*%[mul]+%[pha] " "\n\t" \
|
||
|
"add r0, r18 " "\n\t" \
|
||
|
"sts osc+0*%[mul]+%[pha], r0 " "\n\t" \
|
||
|
"lds r18, osc+0*%[mul]+%[fre]+1" "\n\t" \
|
||
|
"lds r1, osc+0*%[mul]+%[pha]+1" "\n\t" \
|
||
|
"adc r1, r18 " "\n\t" \
|
||
|
"sts osc+0*%[mul]+%[pha]+1, r1 " "\n\t" \
|
||
|
\
|
||
|
"mov r18, r1 " "\n\t" \
|
||
|
"lsl r18 " "\n\t" \
|
||
|
"and r18, r1 " "\n\t" \
|
||
|
"lds r27, osc+0*%[mul]+%[vol] " "\n\t" \
|
||
|
"sbrc r18, 7 " "\n\t" \
|
||
|
"neg r27 " "\n\t" \
|
||
|
"add r26, r27 " "\n\t" \
|
||
|
\
|
||
|
"lds r18, osc+1*%[mul]+%[fre] " "\n\t" \
|
||
|
"lds r0, osc+1*%[mul]+%[pha] " "\n\t" \
|
||
|
"add r0, r18 " "\n\t" \
|
||
|
"sts osc+1*%[mul]+%[pha], r0 " "\n\t" \
|
||
|
"lds r18, osc+1*%[mul]+%[fre]+1" "\n\t" \
|
||
|
"lds r1, osc+1*%[mul]+%[pha]+1" "\n\t" \
|
||
|
"adc r1, r18 " "\n\t" \
|
||
|
"sts osc+1*%[mul]+%[pha]+1, r1 " "\n\t" \
|
||
|
\
|
||
|
"lds r27, osc+1*%[mul]+%[vol] " "\n\t" \
|
||
|
"sbrc r1, 7 " "\n\t" \
|
||
|
"neg r27 " "\n\t" \
|
||
|
"add r26, r27 " "\n\t" \
|
||
|
\
|
||
|
"ldi r27, 1 " "\n\t" \
|
||
|
"lds r0, osc+3*%[mul]+%[fre] " "\n\t" \
|
||
|
"lds r1, osc+3*%[mul]+%[fre]+1" "\n\t" \
|
||
|
"add r0, r0 " "\n\t" \
|
||
|
"adc r1, r1 " "\n\t" \
|
||
|
"sbrc r1, 7 " "\n\t" \
|
||
|
"eor r0, r27 " "\n\t" \
|
||
|
"sbrc r1, 6 " "\n\t" \
|
||
|
"eor r0, r27 " "\n\t" \
|
||
|
"sts osc+3*%[mul]+%[fre], r0 " "\n\t" \
|
||
|
"sts osc+3*%[mul]+%[fre]+1, r1 " "\n\t" \
|
||
|
\
|
||
|
"lds r27, osc+3*%[mul]+%[vol] " "\n\t" \
|
||
|
"sbrc r1, 7 " "\n\t" \
|
||
|
"neg r27 " "\n\t" \
|
||
|
"add r26, r27 " "\n\t" \
|
||
|
\
|
||
|
"lds r27, pcm " "\n\t" \
|
||
|
"add r26, r27 " "\n\t" \
|
||
|
"sts %[reg], r26 " "\n\t" \
|
||
|
\
|
||
|
"lds r27, cia_count+1 " "\n\t" \
|
||
|
"lds r26, cia_count " "\n\t" \
|
||
|
"sbiw r26, 1 " "\n\t" \
|
||
|
"breq call_playroutine " "\n\t" \
|
||
|
"sts cia_count+1, r27 " "\n\t" \
|
||
|
"sts cia_count, r26 " "\n\t" \
|
||
|
"pop r1 " "\n\t" \
|
||
|
"pop r0 " "\n\t" \
|
||
|
"pop r26 " "\n\t" \
|
||
|
"pop r27 " "\n\t" \
|
||
|
"pop r18 " "\n\t" \
|
||
|
"out __SREG__, r2 " "\n\t" \
|
||
|
"pop r2 " "\n\t" \
|
||
|
"reti " "\n\t" \
|
||
|
"call_playroutine: " "\n\t" \
|
||
|
\
|
||
|
"lds r27, cia+1 " "\n\t" \
|
||
|
"lds r26, cia " "\n\t" \
|
||
|
"sts cia_count+1, r27 " "\n\t" \
|
||
|
"sts cia_count, r26 " "\n\t" \
|
||
|
\
|
||
|
"sei " "\n\t" \
|
||
|
"push r19 " "\n\t" \
|
||
|
"push r20 " "\n\t" \
|
||
|
"push r21 " "\n\t" \
|
||
|
"push r22 " "\n\t" \
|
||
|
"push r23 " "\n\t" \
|
||
|
"push r24 " "\n\t" \
|
||
|
"push r25 " "\n\t" \
|
||
|
"push r30 " "\n\t" \
|
||
|
"push r31 " "\n\t" \
|
||
|
\
|
||
|
"clr r1 " "\n\t" \
|
||
|
"call squawk_playroutine " "\n\t" \
|
||
|
\
|
||
|
"pop r31 " "\n\t" \
|
||
|
"pop r30 " "\n\t" \
|
||
|
"pop r25 " "\n\t" \
|
||
|
"pop r24 " "\n\t" \
|
||
|
"pop r23 " "\n\t" \
|
||
|
"pop r22 " "\n\t" \
|
||
|
"pop r21 " "\n\t" \
|
||
|
"pop r20 " "\n\t" \
|
||
|
"pop r19 " "\n\t" \
|
||
|
\
|
||
|
"pop r1 " "\n\t" \
|
||
|
"pop r0 " "\n\t" \
|
||
|
"pop r26 " "\n\t" \
|
||
|
"pop r27 " "\n\t" \
|
||
|
"pop r18 " "\n\t" \
|
||
|
"out __SREG__, r2 " "\n\t" \
|
||
|
"pop r2 " "\n\t" \
|
||
|
"reti " "\n\t" \
|
||
|
: \
|
||
|
: [reg] "M" _SFR_MEM_ADDR(TARGET_REGISTER), \
|
||
|
[mul] "M" (sizeof(Oscillator)), \
|
||
|
[pha] "M" (offsetof(Oscillator, phase)), \
|
||
|
[fre] "M" (offsetof(Oscillator, freq)), \
|
||
|
[vol] "M" (offsetof(Oscillator, vol)) \
|
||
|
); \
|
||
|
}
|
||
|
|
||
|
#endif
|