kopia lustrzana https://github.com/pimoroni/pimoroni-pico
Sine waves are boring, MUSIC!
rodzic
b5ba4ba913
commit
082d41a384
|
@ -6,6 +6,7 @@ if (TARGET pico_audio_i2s)
|
||||||
add_executable(
|
add_executable(
|
||||||
audio
|
audio
|
||||||
demo.cpp
|
demo.cpp
|
||||||
|
synth.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
# Pull in pico libraries that we need
|
# Pull in pico libraries that we need
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
|
@ -1,97 +1,125 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: BSD-3-Clause
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
||||||
#if PICO_ON_DEVICE
|
|
||||||
|
|
||||||
#include "hardware/clocks.h"
|
|
||||||
#include "hardware/structs/clocks.h"
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "pico/stdlib.h"
|
#include "pico/stdlib.h"
|
||||||
#include "pico/audio_i2s.h"
|
|
||||||
|
|
||||||
#define PICO_AUDIO_PAC_I2S_DATA 9
|
#include "synth.hpp"
|
||||||
#define PICO_AUDIO_PAC_I2S_BITCLOCK 10
|
#include "audio.hpp"
|
||||||
#define SINE_WAVE_TABLE_LEN 2048
|
|
||||||
#define SAMPLES_PER_BUFFER 256
|
|
||||||
|
|
||||||
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 = {
|
using namespace synth;
|
||||||
.sample_freq = 24000,
|
|
||||||
.format = AUDIO_BUFFER_FORMAT_PCM_S16,
|
|
||||||
.channel_count = 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
static struct audio_buffer_format producer_format = {
|
synth::AudioChannel synth::channels[CHANNEL_COUNT];
|
||||||
.format = &audio_format,
|
|
||||||
.sample_stride = 2
|
|
||||||
};
|
|
||||||
|
|
||||||
struct audio_buffer_pool *producer_pool = audio_new_producer_pool(&producer_format, 3,
|
// Gadgetoid's amazing masterpiece!
|
||||||
SAMPLES_PER_BUFFER); // todo correct size
|
const int16_t notes[5][SONG_LENGTH] = {
|
||||||
bool __unused ok;
|
{ // melody notes
|
||||||
const struct audio_format *output_format;
|
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,
|
||||||
struct audio_i2s_config config = {
|
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,
|
||||||
.data_pin = PICO_AUDIO_PAC_I2S_DATA,
|
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
|
||||||
.clock_pin_base = PICO_AUDIO_PAC_I2S_BITCLOCK,
|
},
|
||||||
.dma_channel = 0,
|
{ // rhythm notes
|
||||||
.pio_sm = 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,
|
||||||
|
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);
|
void update_playback(void) {
|
||||||
if (!output_format) {
|
static uint16_t prev_beat = 1;
|
||||||
panic("PicoAudio: Unable to open audio device.\n");
|
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() {
|
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++) {
|
// configure voices
|
||||||
sine_wave_table[i] = 32767 * cosf(i * 2 * (float) (M_PI / SINE_WAVE_TABLE_LEN));
|
|
||||||
}
|
|
||||||
|
|
||||||
struct audio_buffer_pool *ap = init_audio();
|
// melody track
|
||||||
uint32_t step = 0x200000;
|
channels[0].waveforms = Waveform::TRIANGLE | Waveform::SQUARE;
|
||||||
uint32_t pos = 0;
|
channels[0].attack_ms = 16;
|
||||||
uint32_t pos_max = 0x10000 * SINE_WAVE_TABLE_LEN;
|
channels[0].decay_ms = 168;
|
||||||
uint vol = 128;
|
channels[0].sustain = 0xafff;
|
||||||
while (true) {
|
channels[0].release_ms = 168;
|
||||||
int c = getchar_timeout_us(0);
|
channels[0].volume = 10000;
|
||||||
if (c >= 0) {
|
|
||||||
if (c == '-' && vol) vol -= 4;
|
// rhythm track
|
||||||
if ((c == '=' || c == '+') && vol < 255) vol += 4;
|
channels[1].waveforms = Waveform::SINE | Waveform::SQUARE;
|
||||||
if (c == '[' && step > 0x10000) step -= 0x10000;
|
channels[1].attack_ms = 38;
|
||||||
if (c == ']' && step < (SINE_WAVE_TABLE_LEN / 16) * 0x20000) step += 0x10000;
|
channels[1].decay_ms = 300;
|
||||||
if (c == 'q') break;
|
channels[1].sustain = 0;
|
||||||
printf("vol = %d, step = %d \r", vol, step >> 16);
|
channels[1].release_ms = 0;
|
||||||
}
|
channels[1].volume = 12000;
|
||||||
struct audio_buffer *buffer = take_audio_buffer(ap, true);
|
|
||||||
int16_t *samples = (int16_t *) buffer->buffer->bytes;
|
// drum track
|
||||||
for (uint i = 0; i < buffer->max_sample_count; i++) {
|
channels[2].waveforms = Waveform::NOISE;
|
||||||
samples[i] = (vol * sine_wave_table[pos >> 16u]) >> 8u;
|
channels[2].attack_ms = 5;
|
||||||
pos += step;
|
channels[2].decay_ms = 10;
|
||||||
if (pos >= pos_max) pos -= pos_max;
|
channels[2].sustain = 16000;
|
||||||
}
|
channels[2].release_ms = 100;
|
||||||
buffer->sample_count = buffer->max_sample_count;
|
channels[2].volume = 18000;
|
||||||
give_audio_buffer(ap, buffer);
|
|
||||||
}
|
// hi-hat track
|
||||||
puts("\n");
|
channels[3].waveforms = Waveform::NOISE;
|
||||||
return 0;
|
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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
|
||||||
|
}
|
Ładowanie…
Reference in New Issue