First addition of synth to GU

feature/galactic_unicorn
ZodiusInfuser 2022-08-19 17:25:44 +01:00 zatwierdzone przez Phil Howard
rodzic 12ea527f44
commit deec835692
9 zmienionych plików z 484 dodań i 15 usunięć

Wyświetl plik

@ -6,6 +6,7 @@ pico_generate_pio_header(galactic_unicorn ${CMAKE_CURRENT_LIST_DIR}/audio_i2s.pi
target_sources(galactic_unicorn INTERFACE
${CMAKE_CURRENT_LIST_DIR}/galactic_unicorn.cpp
${CMAKE_CURRENT_LIST_DIR}/synth.cpp
)
target_include_directories(galactic_unicorn INTERFACE ${CMAKE_CURRENT_LIST_DIR})

Wyświetl plik

@ -47,6 +47,40 @@ static uint32_t audio_dma_channel;
static const int16_t sine_waveform[256] = {-32768,-32758,-32729,-32679,-32610,-32522,-32413,-32286,-32138,-31972,-31786,-31581,-31357,-31114,-30853,-30572,-30274,-29957,-29622,-29269,-28899,-28511,-28106,-27684,-27246,-26791,-26320,-25833,-25330,-24812,-24279,-23732,-23170,-22595,-22006,-21403,-20788,-20160,-19520,-18868,-18205,-17531,-16846,-16151,-15447,-14733,-14010,-13279,-12540,-11793,-11039,-10279,-9512,-8740,-7962,-7180,-6393,-5602,-4808,-4011,-3212,-2411,-1608,-804,0,804,1608,2411,3212,4011,4808,5602,6393,7180,7962,8740,9512,10279,11039,11793,12540,13279,14010,14733,15447,16151,16846,17531,18205,18868,19520,20160,20788,21403,22006,22595,23170,23732,24279,24812,25330,25833,26320,26791,27246,27684,28106,28511,28899,29269,29622,29957,30274,30572,30853,31114,31357,31581,31786,31972,32138,32286,32413,32522,32610,32679,32729,32758,32767,32758,32729,32679,32610,32522,32413,32286,32138,31972,31786,31581,31357,31114,30853,30572,30274,29957,29622,29269,28899,28511,28106,27684,27246,26791,26320,25833,25330,24812,24279,23732,23170,22595,22006,21403,20788,20160,19520,18868,18205,17531,16846,16151,15447,14733,14010,13279,12540,11793,11039,10279,9512,8740,7962,7180,6393,5602,4808,4011,3212,2411,1608,804,0,-804,-1608,-2411,-3212,-4011,-4808,-5602,-6393,-7180,-7962,-8740,-9512,-10279,-11039,-11793,-12540,-13279,-14010,-14733,-15447,-16151,-16846,-17531,-18205,-18868,-19520,-20160,-20788,-21403,-22006,-22595,-23170,-23732,-24279,-24812,-25330,-25833,-26320,-26791,-27246,-27684,-28106,-28511,-28899,-29269,-29622,-29957,-30274,-30572,-30853,-31114,-31357,-31581,-31786,-31972,-32138,-32286,-32413,-32522,-32610,-32679,-32729,-32758};
#define SONG_LENGTH 384
#define HAT 20000
#define BASS 500
#define SNARE 6000
#define SUB 50
static const int16_t notes[5][SONG_LENGTH] = {
{ // melody notes
147, 0, 0, 0, 0, 0, 0, 0, 175, 0, 196, 0, 220, 0, 262, 0, 247, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 175, 0, 0, 0, 0, 0, 0, 0, 175, 0, 196, 0, 220, 0, 262, 0, 330, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 349, 0, 0, 0, 0, 0, 0, 0, 349, 0, 330, 0, 294, 0, 220, 0, 262, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 247, 0, 0, 0, 0, 0, 0, 0, 247, 0, 220, 0, 196, 0, 147, 0, 175, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0,
147, 0, 0, 0, 0, 0, 0, 0, 175, 0, 196, 0, 220, 0, 262, 0, 247, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 175, 0, 0, 0, 0, 0, 0, 0, 175, 0, 196, 0, 220, 0, 262, 0, 330, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 349, 0, 0, 0, 0, 0, 0, 0, 349, 0, 330, 0, 294, 0, 220, 0, 262, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 247, 0, 0, 0, 0, 0, 0, 0, 247, 0, 220, 0, 196, 0, 147, 0, 175, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0,
147, 0, 0, 0, 0, 0, 0, 0, 175, 0, 196, 0, 220, 0, 262, 0, 247, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 175, 0, 0, 0, 0, 0, 0, 0, 175, 0, 196, 0, 220, 0, 262, 0, 330, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 349, 0, 0, 0, 0, 0, 0, 0, 349, 0, 330, 0, 294, 0, 220, 0, 262, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 247, 0, 0, 0, 0, 0, 0, 0, 247, 0, 262, 0, 294, 0, 392, 0, 440, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
},
{ // rhythm notes
294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 392, 0, 523, 0, 659, 0, 523, 0, 392, 0, 523, 0, 659, 0, 523, 0, 698, 0, 587, 0, 440, 0, 587, 0, 698, 0, 587, 0, 440, 0, 587, 0, 523, 0, 440, 0, 330, 0, 440, 0, 523, 0, 440, 0, 330, 0, 440, 0, 349, 0, 294, 0, 220, 0, 294, 0, 349, 0, 294, 0, 220, 0, 294, 0, 262, 0, 247, 0, 220, 0, 175, 0, 165, 0, 147, 0, 131, 0, 98, 0,
294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 392, 0, 523, 0, 659, 0, 523, 0, 392, 0, 523, 0, 659, 0, 523, 0, 698, 0, 587, 0, 440, 0, 587, 0, 698, 0, 587, 0, 440, 0, 587, 0, 523, 0, 440, 0, 330, 0, 440, 0, 523, 0, 440, 0, 330, 0, 440, 0, 349, 0, 294, 0, 220, 0, 294, 0, 349, 0, 294, 0, 220, 0, 294, 0, 262, 0, 247, 0, 220, 0, 175, 0, 165, 0, 147, 0, 131, 0, 98, 0,
294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 392, 0, 523, 0, 659, 0, 523, 0, 392, 0, 523, 0, 659, 0, 523, 0, 698, 0, 587, 0, 440, 0, 587, 0, 698, 0, 587, 0, 440, 0, 587, 0, 523, 0, 440, 0, 330, 0, 440, 0, 523, 0, 440, 0, 330, 0, 440, 0, 349, 0, 294, 0, 220, 0, 294, 0, 349, 0, 294, 0, 220, 0, 294, 0, 262, 0, 247, 0, 220, 0, 175, 0, 165, 0, 147, 0, 131, 0, 98, 0,
},
{ // drum beats
BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, BASS, -1, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, 0, 0, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, BASS, -1, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, 0, 0, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, BASS, -1, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, 0, 0, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, BASS, -1, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, 0, 0,
BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, BASS, -1, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, 0, 0, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, BASS, -1, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, 0, 0, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, BASS, -1, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, 0, 0, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, BASS, -1, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, 0, 0,
BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, BASS, -1, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, 0, 0, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, BASS, -1, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, 0, 0, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, BASS, -1, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, 0, 0, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, BASS, -1, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, 0, 0
},
{ // hi-hat
HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1,
HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1,
HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1
},
{ // bass notes under bass drum
SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, SUB, -1, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, SUB, -1, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, SUB, -1, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, SUB, -1, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0,
SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, SUB, -1, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, SUB, -1, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, SUB, -1, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, SUB, -1, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0,
SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, SUB, -1, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, SUB, -1, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, SUB, -1, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, SUB, -1, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0
},
};
namespace pimoroni {
GalacticUnicorn* GalacticUnicorn::unicorn = nullptr;
@ -350,6 +384,57 @@ namespace pimoroni {
irq_set_enabled(DMA_IRQ_0, true);
}
// configure voices
/*
channels[0].waveforms = Waveform::SINE;
channels[0].attack_ms = 1;
channels[0].decay_ms = 1;
channels[0].sustain = 0xafff;
channels[0].release_ms = 1;
channels[0].volume = 10000;
channels[0].frequency = 1000;
*/
// melody track
channels[0].waveforms = Waveform::TRIANGLE | Waveform::SQUARE;
channels[0].attack_ms = 16;
channels[0].decay_ms = 168;
channels[0].sustain = 0xafff;
channels[0].release_ms = 168;
channels[0].volume = 10000;
// rhythm track
channels[1].waveforms = Waveform::SINE | Waveform::SQUARE;
channels[1].attack_ms = 38;
channels[1].decay_ms = 300;
channels[1].sustain = 0;
channels[1].release_ms = 0;
channels[1].volume = 12000;
// drum track
channels[2].waveforms = Waveform::NOISE;
channels[2].attack_ms = 5;
channels[2].decay_ms = 10;
channels[2].sustain = 16000;
channels[2].release_ms = 100;
channels[2].volume = 18000;
// hi-hat track
channels[3].waveforms = Waveform::NOISE;
channels[3].attack_ms = 5;
channels[3].decay_ms = 5;
channels[3].sustain = 8000;
channels[3].release_ms = 40;
channels[3].volume = 8000;
// bass track
channels[4].waveforms = Waveform::SQUARE;
channels[4].attack_ms = 10;
channels[4].decay_ms = 100;
channels[4].sustain = 0;
channels[4].release_ms = 500;
channels[4].volume = 12000;
unicorn = this;
next_dma_sequence();
@ -411,7 +496,7 @@ namespace pimoroni {
// Nothing is playing, so we can set up the first buffer straight away
current_buffer = 0;
populate_next_buffer();
populate_next_tone();
// Restart the audio SM and start a new DMA transfer
pio_sm_set_enabled(audio_pio, audio_sm, true);
@ -423,6 +508,32 @@ namespace pimoroni {
}
}
void GalacticUnicorn::play_synth() {
if(play_mode != PLAYING_SYNTH) {
stop_playing();
}
if(unicorn == this) {
if(play_mode == NOT_PLAYING) {
// Nothing is playing, so we can set up the first buffer straight away
current_buffer = 0;
absolute_time_t at = get_absolute_time();
synth_start = to_us_since_boot(at) / 1000;
populate_next_synth();
// Restart the audio SM and start a new DMA transfer
pio_sm_set_enabled(audio_pio, audio_sm, true);
play_mode = PLAYING_SYNTH;
loop_tone();
}
}
}
void GalacticUnicorn::loop_tone() {
// Clear any interrupt request caused by our channel
//dma_channel_acknowledge_irq0(audio_dma_channel);
@ -430,19 +541,28 @@ namespace pimoroni {
// https://github.com/raspberrypi/pico-sdk/issues/974
dma_hw->ints0 = 1u << audio_dma_channel;
if(play_mode == PLAYING_TONE) {
if(play_mode == PLAYING_TONE || play_mode == PLAYING_SYNTH) {
dma_channel_transfer_from_buffer_now(audio_dma_channel, tone_buffers[current_buffer], TONE_BUFFER_SIZE);
current_buffer = (current_buffer + 1) % NUM_TONE_BUFFERS;
populate_next_buffer();
switch(play_mode) {
case PLAYING_TONE:
populate_next_tone();
break;
case PLAYING_SYNTH:
populate_next_synth();
break;
default:
break;
}
}
else {
play_mode = NOT_PLAYING;
}
}
void GalacticUnicorn::populate_next_buffer() {
void GalacticUnicorn::populate_next_tone() {
float percent_along_a, percent_along_b;
int16_t val;
int16_t* buffer = tone_buffers[current_buffer];
@ -464,6 +584,34 @@ namespace pimoroni {
wave_start_b = fmodf((((float)TONE_BUFFER_SIZE * tone_frequency_b) / SYSTEM_FREQ) + wave_start_b, 1.0f);
}
void GalacticUnicorn::populate_next_synth() {
static uint16_t prev_beat = 1;
static uint16_t beat = 0;
absolute_time_t at = get_absolute_time();
uint64_t tick_ms = (to_us_since_boot(at) / 1000) - synth_start;
beat = (tick_ms / 100) % SONG_LENGTH; // 100ms per beat
if (beat != prev_beat) {
prev_beat = beat;
for(uint8_t i = 0; i < 5; i++) {
if(notes[i][beat] > 0) {
channels[i].frequency = notes[i][beat];
channels[i].trigger_attack();
} else if (notes[i][beat] == -1) {
channels[i].trigger_release();
}
}
}
int16_t *samples = tone_buffers[current_buffer];
for(uint i = 0; i < TONE_BUFFER_SIZE; i++) {
samples[i] = get_audio_frame(channels);
}
}
void GalacticUnicorn::stop_playing() {
if(unicorn == this) {
// Stop the audio SM

Wyświetl plik

@ -2,6 +2,9 @@
#include "hardware/pio.h"
#include "pico_graphics.hpp"
#include "synth.hpp"
using namespace synth;
namespace pimoroni {
@ -70,7 +73,7 @@ namespace pimoroni {
static void dma_complete();
static const uint NUM_TONE_BUFFERS = 2;
static const uint TONE_BUFFER_SIZE = 16;
static const uint TONE_BUFFER_SIZE = 4;
int16_t tone_buffers[NUM_TONE_BUFFERS][TONE_BUFFER_SIZE] = {0};
uint current_buffer = 0;
@ -79,9 +82,12 @@ namespace pimoroni {
float wave_start_a = 0.0f;
float wave_start_b = 0.0f;
synth::AudioChannel channels[CHANNEL_COUNT];
enum PlayMode {
PLAYING_BUFFER,
PLAYING_TONE,
PLAYING_SYNTH,
NOT_PLAYING
};
PlayMode play_mode = NOT_PLAYING;
@ -115,14 +121,18 @@ namespace pimoroni {
void play_sample(uint8_t *data, uint32_t length);
void play_tone(float frequency);
void play_dual_tone(float freq_a, float freq_b);
void play_synth();
void stop_playing();
uint64_t synth_start = 0;
private:
void next_dma_sequence();
void partial_teardown();
void dma_safe_abort(uint channel);
void loop_tone();
void populate_next_buffer();
void populate_next_tone();
void populate_next_synth();
};
}

Wyświetl plik

@ -0,0 +1,160 @@
#include "synth.hpp"
namespace synth {
uint32_t prng_xorshift_state = 0x32B71700;
uint32_t prng_xorshift_next() {
uint32_t x = prng_xorshift_state;
x ^= x << 13;
x ^= x >> 17;
x ^= x << 5;
prng_xorshift_state = x;
return x;
}
int32_t prng_normal() {
// rough approximation of a normal distribution
uint32_t r0 = prng_xorshift_next();
uint32_t r1 = prng_xorshift_next();
uint32_t n = ((r0 & 0xffff) + (r1 & 0xffff) + (r0 >> 16) + (r1 >> 16)) / 2;
return n - 0xffff;
}
//uint16_t volume = 0xffff;
const int16_t sine_waveform[256] = {-32768,-32758,-32729,-32679,-32610,-32522,-32413,-32286,-32138,-31972,-31786,-31581,-31357,-31114,-30853,-30572,-30274,-29957,-29622,-29269,-28899,-28511,-28106,-27684,-27246,-26791,-26320,-25833,-25330,-24812,-24279,-23732,-23170,-22595,-22006,-21403,-20788,-20160,-19520,-18868,-18205,-17531,-16846,-16151,-15447,-14733,-14010,-13279,-12540,-11793,-11039,-10279,-9512,-8740,-7962,-7180,-6393,-5602,-4808,-4011,-3212,-2411,-1608,-804,0,804,1608,2411,3212,4011,4808,5602,6393,7180,7962,8740,9512,10279,11039,11793,12540,13279,14010,14733,15447,16151,16846,17531,18205,18868,19520,20160,20788,21403,22006,22595,23170,23732,24279,24812,25330,25833,26320,26791,27246,27684,28106,28511,28899,29269,29622,29957,30274,30572,30853,31114,31357,31581,31786,31972,32138,32286,32413,32522,32610,32679,32729,32758,32767,32758,32729,32679,32610,32522,32413,32286,32138,31972,31786,31581,31357,31114,30853,30572,30274,29957,29622,29269,28899,28511,28106,27684,27246,26791,26320,25833,25330,24812,24279,23732,23170,22595,22006,21403,20788,20160,19520,18868,18205,17531,16846,16151,15447,14733,14010,13279,12540,11793,11039,10279,9512,8740,7962,7180,6393,5602,4808,4011,3212,2411,1608,804,0,-804,-1608,-2411,-3212,-4011,-4808,-5602,-6393,-7180,-7962,-8740,-9512,-10279,-11039,-11793,-12540,-13279,-14010,-14733,-15447,-16151,-16846,-17531,-18205,-18868,-19520,-20160,-20788,-21403,-22006,-22595,-23170,-23732,-24279,-24812,-25330,-25833,-26320,-26791,-27246,-27684,-28106,-28511,-28899,-29269,-29622,-29957,-30274,-30572,-30853,-31114,-31357,-31581,-31786,-31972,-32138,-32286,-32413,-32522,-32610,-32679,-32729,-32758};
bool is_audio_playing(AudioChannel *channels) {
if(volume == 0) {
return false;
}
bool any_channel_playing = false;
for(int c = 0; c < CHANNEL_COUNT; c++) {
if(channels[c].volume > 0 && channels[c].adsr_phase != ADSRPhase::OFF) {
any_channel_playing = true;
}
}
return any_channel_playing;
}
int16_t get_audio_frame(AudioChannel *channels) {
int32_t sample = 0; // used to combine channel output
for(int c = 0; c < CHANNEL_COUNT; c++) {
auto &channel = channels[c];
// increment the waveform position counter. this provides an
// Q16 fixed point value representing how far through
// the current waveform we are
channel.waveform_offset += ((channel.frequency * 256) << 8) / sample_rate;
if(channel.adsr_phase == ADSRPhase::OFF) {
continue;
}
if ((channel.adsr_frame >= channel.adsr_end_frame) && (channel.adsr_phase != ADSRPhase::SUSTAIN)) {
switch (channel.adsr_phase) {
case ADSRPhase::ATTACK:
channel.trigger_decay();
break;
case ADSRPhase::DECAY:
channel.trigger_sustain();
break;
case ADSRPhase::RELEASE:
channel.off();
break;
default:
break;
}
}
channel.adsr += channel.adsr_step;
channel.adsr_frame++;
if(channel.waveform_offset & 0x10000) {
// if the waveform offset overflows then generate a new
// random noise sample
channel.noise = prng_normal();
}
channel.waveform_offset &= 0xffff;
// check if any waveforms are active for this channel
if(channel.waveforms) {
uint8_t waveform_count = 0;
int32_t channel_sample = 0;
if(channel.waveforms & Waveform::NOISE) {
channel_sample += channel.noise;
waveform_count++;
}
if(channel.waveforms & Waveform::SAW) {
channel_sample += (int32_t)channel.waveform_offset - 0x7fff;
waveform_count++;
}
// creates a triangle wave of ^
if (channel.waveforms & Waveform::TRIANGLE) {
if (channel.waveform_offset < 0x7fff) { // initial quarter up slope
channel_sample += int32_t(channel.waveform_offset * 2) - int32_t(0x7fff);
}
else { // final quarter up slope
channel_sample += int32_t(0x7fff) - ((int32_t(channel.waveform_offset) - int32_t(0x7fff)) * 2);
}
waveform_count++;
}
if (channel.waveforms & Waveform::SQUARE) {
channel_sample += (channel.waveform_offset < channel.pulse_width) ? 0x7fff : -0x7fff;
waveform_count++;
}
if(channel.waveforms & Waveform::SINE) {
// the sine_waveform sample contains 256 samples in
// total so we'll just use the most significant bits
// of the current waveform position to index into it
channel_sample += sine_waveform[channel.waveform_offset >> 8];
waveform_count++;
}
if(channel.waveforms & Waveform::WAVE) {
channel_sample += channel.wave_buffer[channel.wave_buf_pos];
if (++channel.wave_buf_pos == 64) {
channel.wave_buf_pos = 0;
if(channel.wave_buffer_callback)
channel.wave_buffer_callback(channel);
}
waveform_count++;
}
channel_sample = channel_sample / waveform_count;
channel_sample = (int64_t(channel_sample) * int32_t(channel.adsr >> 8)) >> 16;
// apply channel volume
channel_sample = (int64_t(channel_sample) * int32_t(channel.volume)) >> 16;
// apply channel filter
//if (channel.filter_enable) {
//float filter_epow = 1 - expf(-(1.0f / 22050.0f) * 2.0f * pi * int32_t(channel.filter_cutoff_frequency));
//channel_sample += (channel_sample - channel.filter_last_sample) * filter_epow;
//}
//channel.filter_last_sample = channel_sample;
// combine channel sample into the final sample
sample += channel_sample;
}
}
sample = (int64_t(sample) * int32_t(volume)) >> 16;
// clip result to 16-bit
sample = sample <= -0x8000 ? -0x8000 : (sample > 0x7fff ? 0x7fff : sample);
return sample;
}
}

Wyświetl plik

@ -0,0 +1,132 @@
#pragma once
#include <cstdint>
namespace synth {
// The duration a note is played is determined by the amount of attack,
// decay, and release, combined with the length of the note as defined by
// the user.
//
// - Attack: number of milliseconds it takes for a note to hit full volume
// - Decay: number of milliseconds it takes for a note to settle to sustain volume
// - Sustain: percentage of full volume that the note sustains at (duration implied by other factors)
// - Release: number of milliseconds it takes for a note to reduce to zero volume after it has ended
//
// Attack (750ms) - Decay (500ms) -------- Sustain ----- Release (250ms)
//
// + + + +
// | | | |
// | | | |
// | | | |
// v v v v
// 0ms 1000ms 2000ms 3000ms 4000ms
//
// | XXXX | | | |
// | X X|XX | | |
// | X | XXX | | |
// | X | XXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXXX| |
// | X | | |X |
// | X | | |X |
// | X | | | X |
// | X | | | X |
// | X | | | X |
// | X | | | X |
// | X | | | X |
// | X | | | X |
// | X + + + | + + + | + + + | + + + | +
// | X | | | | | | | | | | | | | | | | |
// |X | | | | | | | | | | | | | | | | |
// +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+--->
#define CHANNEL_COUNT 8
constexpr float pi = 3.14159265358979323846f;
const uint32_t sample_rate = 22050;
const uint16_t volume = 0x2fff;
enum Waveform {
NOISE = 128,
SQUARE = 64,
SAW = 32,
TRIANGLE = 16,
SINE = 8,
WAVE = 1
};
enum class ADSRPhase : uint8_t {
ATTACK,
DECAY,
SUSTAIN,
RELEASE,
OFF
};
struct AudioChannel {
uint8_t waveforms = 0; // bitmask for enabled waveforms (see AudioWaveform enum for values)
uint16_t frequency = 660; // frequency of the voice (Hz)
uint16_t volume = 0xffff; // channel volume (default 50%)
uint16_t attack_ms = 2; // attack period
uint16_t decay_ms = 6; // decay period
uint16_t sustain = 0xffff; // sustain volume
uint16_t release_ms = 1; // release period
uint16_t pulse_width = 0x7fff; // duty cycle of square wave (default 50%)
int16_t noise = 0; // current noise value
uint32_t waveform_offset = 0; // voice offset (Q8)
int32_t filter_last_sample = 0;
bool filter_enable = false;
uint16_t filter_cutoff_frequency = 0;
uint32_t adsr_frame = 0; // number of frames into the current ADSR phase
uint32_t adsr_end_frame = 0; // frame target at which the ADSR changes to the next phase
uint32_t adsr = 0;
int32_t adsr_step = 0;
ADSRPhase adsr_phase = ADSRPhase::OFF;
uint8_t wave_buf_pos = 0; //
int16_t wave_buffer[64]; // buffer for arbitrary waveforms. small as it's filled by user callback
void *user_data = nullptr;
void (*wave_buffer_callback)(AudioChannel &channel);
void trigger_attack() {
adsr_frame = 0;
adsr_phase = ADSRPhase::ATTACK;
adsr_end_frame = (attack_ms * sample_rate) / 1000;
adsr_step = (int32_t(0xffffff) - int32_t(adsr)) / int32_t(adsr_end_frame);
}
void trigger_decay() {
adsr_frame = 0;
adsr_phase = ADSRPhase::DECAY;
adsr_end_frame = (decay_ms * sample_rate) / 1000;
adsr_step = (int32_t(sustain << 8) - int32_t(adsr)) / int32_t(adsr_end_frame);
}
void trigger_sustain() {
adsr_frame = 0;
adsr_phase = ADSRPhase::SUSTAIN;
adsr_end_frame = 0;
adsr_step = 0;
}
void trigger_release() {
adsr_frame = 0;
adsr_phase = ADSRPhase::RELEASE;
adsr_end_frame = (release_ms * sample_rate) / 1000;
adsr_step = (int32_t(0) - int32_t(adsr)) / int32_t(adsr_end_frame);
}
void off() {
adsr_frame = 0;
adsr_phase = ADSRPhase::OFF;
adsr_step = 0;
}
};
//extern AudioChannel channels[CHANNEL_COUNT];
int16_t get_audio_frame(AudioChannel *channels);
bool is_audio_playing(AudioChannel *channels);
}

Wyświetl plik

@ -658,6 +658,7 @@ was_a_pressed = False
was_b_pressed = False
was_c_pressed = False
was_d_pressed = False
was_z_pressed = False
bool_playing = False
freq_a = 0
@ -724,6 +725,13 @@ while True:
if bool_playing:
freq_a -= 10
gu.play_dual_tone(freq_a, freq_b)
if gu.is_pressed(GalacticUnicorn.SWITCH_SLEEP):
if not was_z_pressed:
gu.play_synth()
was_z_pressed = True
else:
was_z_pressed = False
graphics.set_pen(graphics.create_pen(0, 0, 0))
graphics.clear()
@ -747,31 +755,31 @@ while True:
text = ""
if gu.is_pressed(GalacticUnicorn.SWITCH_A):
text = "Button A"
text = "Play Sample"
if gu.is_pressed(GalacticUnicorn.SWITCH_B):
text = "Button B"
text = "Tone A"
if gu.is_pressed(GalacticUnicorn.SWITCH_C):
text = "Button C"
text = "Tone B"
if gu.is_pressed(GalacticUnicorn.SWITCH_D):
text = "Button D"
text = "Stop"
if gu.is_pressed(GalacticUnicorn.SWITCH_VOLUME_UP):
text = "Louder!"
text = "Raise A"
if gu.is_pressed(GalacticUnicorn.SWITCH_VOLUME_DOWN):
text = "Quieter"
text = "Lower A"
if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_UP):
text = "Brighter!"
text = "Raise B"
if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_DOWN):
text = "Darker"
text = "Power B"
if gu.is_pressed(GalacticUnicorn.SWITCH_SLEEP):
text = "Zzz... zzz..."
text = "Play Synth"
outline_text(text)

Wyświetl plik

@ -16,6 +16,7 @@ MP_DEFINE_CONST_FUN_OBJ_2(GalacticUnicorn_is_pressed_obj, GalacticUnicorn_is_pre
MP_DEFINE_CONST_FUN_OBJ_2(GalacticUnicorn_play_sample_obj, GalacticUnicorn_play_sample);
MP_DEFINE_CONST_FUN_OBJ_2(GalacticUnicorn_play_tone_obj, GalacticUnicorn_play_tone);
MP_DEFINE_CONST_FUN_OBJ_3(GalacticUnicorn_play_dual_tone_obj, GalacticUnicorn_play_dual_tone);
MP_DEFINE_CONST_FUN_OBJ_1(GalacticUnicorn_play_synth_obj, GalacticUnicorn_play_synth);
MP_DEFINE_CONST_FUN_OBJ_1(GalacticUnicorn_stop_playing_obj, GalacticUnicorn_stop_playing);
/***** Binding of Methods *****/
@ -34,6 +35,7 @@ STATIC const mp_rom_map_elem_t GalacticUnicorn_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR_play_sample), MP_ROM_PTR(&GalacticUnicorn_play_sample_obj) },
{ MP_ROM_QSTR(MP_QSTR_play_tone), MP_ROM_PTR(&GalacticUnicorn_play_tone_obj) },
{ MP_ROM_QSTR(MP_QSTR_play_dual_tone), MP_ROM_PTR(&GalacticUnicorn_play_dual_tone_obj) },
{ MP_ROM_QSTR(MP_QSTR_play_synth), MP_ROM_PTR(&GalacticUnicorn_play_synth_obj) },
{ MP_ROM_QSTR(MP_QSTR_stop_playing), MP_ROM_PTR(&GalacticUnicorn_stop_playing_obj) },
{ MP_ROM_QSTR(MP_QSTR_WIDTH), MP_ROM_INT(53) },

Wyświetl plik

@ -173,6 +173,13 @@ extern mp_obj_t GalacticUnicorn_play_dual_tone(mp_obj_t self_in, mp_obj_t freq_a
return mp_const_none;
}
extern mp_obj_t GalacticUnicorn_play_synth(mp_obj_t self_in) {
_GalacticUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _GalacticUnicorn_obj_t);
self->galactic->play_synth();
return mp_const_none;
}
extern mp_obj_t GalacticUnicorn_stop_playing(mp_obj_t self_in) {
_GalacticUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _GalacticUnicorn_obj_t);
self->galactic->stop_playing();

Wyświetl plik

@ -27,4 +27,5 @@ extern mp_obj_t GalacticUnicorn_is_pressed(mp_obj_t self_in, mp_obj_t button);
extern mp_obj_t GalacticUnicorn_play_sample(mp_obj_t self_in, mp_obj_t data);
extern mp_obj_t GalacticUnicorn_play_tone(mp_obj_t self_in, mp_obj_t freq);
extern mp_obj_t GalacticUnicorn_play_dual_tone(mp_obj_t self_in, mp_obj_t freq_a, mp_obj_t freq_b);
extern mp_obj_t GalacticUnicorn_play_synth(mp_obj_t self_in);
extern mp_obj_t GalacticUnicorn_stop_playing(mp_obj_t self_in);