Sine waves are boring, MUSIC!

pull/113/head
Phil Howard 2021-03-31 14:52:32 +01:00
rodzic b5ba4ba913
commit 082d41a384
5 zmienionych plików z 464 dodań i 79 usunięć

Wyświetl plik

@ -6,6 +6,7 @@ if (TARGET pico_audio_i2s)
add_executable(
audio
demo.cpp
synth.cpp
)
# Pull in pico libraries that we need

Wyświetl plik

@ -0,0 +1,64 @@
/**
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#pragma once
#include "pico/audio_i2s.h"
#define SAMPLES_PER_BUFFER 256
typedef int16_t (*buffer_callback)(void);
struct audio_buffer_pool *init_audio(uint32_t sample_rate, uint8_t pin_data, uint8_t pin_bclk, uint8_t pio_sm=0, uint8_t dma_ch=0) {
static audio_format_t audio_format = {
.sample_freq = sample_rate,
.format = AUDIO_BUFFER_FORMAT_PCM_S16,
.channel_count = 1,
};
static struct audio_buffer_format producer_format = {
.format = &audio_format,
.sample_stride = 2
};
struct audio_buffer_pool *producer_pool = audio_new_producer_pool(
&producer_format,
3,
SAMPLES_PER_BUFFER
);
const struct audio_format *output_format;
struct audio_i2s_config config = {
.data_pin = pin_data,
.clock_pin_base = pin_bclk,
.dma_channel = dma_ch,
.pio_sm = pio_sm,
};
output_format = audio_i2s_setup(&audio_format, &config);
if (!output_format) {
panic("PicoAudio: Unable to open audio device.\n");
}
bool status = audio_i2s_connect(producer_pool);
if (!status) {
panic("PicoAudio: Unable to connect to audio device.\n");
}
audio_i2s_set_enabled(true);
return producer_pool;
}
void update_buffer(struct audio_buffer_pool *ap, buffer_callback cb) {
struct audio_buffer *buffer = take_audio_buffer(ap, true);
int16_t *samples = (int16_t *) buffer->buffer->bytes;
for (uint i = 0; i < buffer->max_sample_count; i++) {
samples[i] = cb();
}
buffer->sample_count = buffer->max_sample_count;
give_audio_buffer(ap, buffer);
}

Wyświetl plik

@ -1,97 +1,125 @@
/**
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <stdio.h>
#include <math.h>
#if PICO_ON_DEVICE
#include "hardware/clocks.h"
#include "hardware/structs/clocks.h"
#endif
#include "pico/stdlib.h"
#include "pico/audio_i2s.h"
#define PICO_AUDIO_PAC_I2S_DATA 9
#define PICO_AUDIO_PAC_I2S_BITCLOCK 10
#define SINE_WAVE_TABLE_LEN 2048
#define SAMPLES_PER_BUFFER 256
#include "synth.hpp"
#include "audio.hpp"
static int16_t sine_wave_table[SINE_WAVE_TABLE_LEN];
#define PICO_AUDIO_PACK_I2S_DATA 9
#define PICO_AUDIO_PACK_I2S_BCLK 10
struct audio_buffer_pool *init_audio() {
#define SONG_LENGTH 384
#define HAT 20000
#define BASS 500
#define SNARE 6000
#define SUB 50
static audio_format_t audio_format = {
.sample_freq = 24000,
.format = AUDIO_BUFFER_FORMAT_PCM_S16,
.channel_count = 1,
};
using namespace synth;
static struct audio_buffer_format producer_format = {
.format = &audio_format,
.sample_stride = 2
};
synth::AudioChannel synth::channels[CHANNEL_COUNT];
struct audio_buffer_pool *producer_pool = audio_new_producer_pool(&producer_format, 3,
SAMPLES_PER_BUFFER); // todo correct size
bool __unused ok;
const struct audio_format *output_format;
struct audio_i2s_config config = {
.data_pin = PICO_AUDIO_PAC_I2S_DATA,
.clock_pin_base = PICO_AUDIO_PAC_I2S_BITCLOCK,
.dma_channel = 0,
.pio_sm = 0,
};
// Gadgetoid's amazing masterpiece!
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
},
};
output_format = audio_i2s_setup(&audio_format, &config);
if (!output_format) {
panic("PicoAudio: Unable to open audio device.\n");
void update_playback(void) {
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;
beat = (tick_ms / 100) % SONG_LENGTH; // 100ms per beat
if (beat == prev_beat) return;
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();
}
ok = audio_i2s_connect(producer_pool);
assert(ok);
audio_i2s_set_enabled(true);
return producer_pool;
}
}
int main() {
stdio_init_all();
stdio_init_all();
struct audio_buffer_pool *ap = init_audio(synth::sample_rate, PICO_AUDIO_PACK_I2S_DATA, PICO_AUDIO_PACK_I2S_BCLK);
for (int i = 0; i < SINE_WAVE_TABLE_LEN; i++) {
sine_wave_table[i] = 32767 * cosf(i * 2 * (float) (M_PI / SINE_WAVE_TABLE_LEN));
}
// configure voices
struct audio_buffer_pool *ap = init_audio();
uint32_t step = 0x200000;
uint32_t pos = 0;
uint32_t pos_max = 0x10000 * SINE_WAVE_TABLE_LEN;
uint vol = 128;
while (true) {
int c = getchar_timeout_us(0);
if (c >= 0) {
if (c == '-' && vol) vol -= 4;
if ((c == '=' || c == '+') && vol < 255) vol += 4;
if (c == '[' && step > 0x10000) step -= 0x10000;
if (c == ']' && step < (SINE_WAVE_TABLE_LEN / 16) * 0x20000) step += 0x10000;
if (c == 'q') break;
printf("vol = %d, step = %d \r", vol, step >> 16);
}
struct audio_buffer *buffer = take_audio_buffer(ap, true);
int16_t *samples = (int16_t *) buffer->buffer->bytes;
for (uint i = 0; i < buffer->max_sample_count; i++) {
samples[i] = (vol * sine_wave_table[pos >> 16u]) >> 8u;
pos += step;
if (pos >= pos_max) pos -= pos_max;
}
buffer->sample_count = buffer->max_sample_count;
give_audio_buffer(ap, buffer);
}
puts("\n");
return 0;
// 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;
while (true) {
update_playback();
update_buffer(ap, get_audio_frame);
}
return 0;
}

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() {
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() {
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 = 44100;
extern uint16_t volume;
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();
bool is_audio_playing();
}