Merge pull request #113 from pimoroni/pico-audio-example

Add Pico Audio Pack example for #36
pull/115/head
Philip Howard 2021-03-31 18:08:32 +01:00 zatwierdzone przez GitHub
commit 9292e3f0e6
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
8 zmienionych plików z 540 dodań i 2 usunięć

Wyświetl plik

@ -17,7 +17,7 @@ jobs:
- os: ubuntu-20.04
name: Linux
cache-key: linux
cmake-args: '-DPICO_SDK_PATH=$GITHUB_WORKSPACE/pico-sdk'
cmake-args: '-DPICO_SDK_PATH=$GITHUB_WORKSPACE/pico-sdk -DPICO_SDK_POST_LIST_DIRS=$GITHUB_WORKSPACE/pico-extras'
apt-packages: clang-tidy gcc-arm-none-eabi libnewlib-arm-none-eabi libstdc++-arm-none-eabi-newlib
runs-on: ${{matrix.os}}
@ -36,6 +36,14 @@ jobs:
path: pico-sdk
submodules: true
# Check out the Pico Extras
- name: Checkout Pico Extras
uses: actions/checkout@v2
with:
repository: raspberrypi/pico-extras
path: pico-extras
submodules: false # lwip breaks audio submodule fetchin
# Linux deps
- name: Install deps
if: runner.os == 'Linux'

Wyświetl plik

@ -6,4 +6,5 @@ add_subdirectory(pico_scroll)
add_subdirectory(pico_explorer)
add_subdirectory(pico_rgb_keypad)
add_subdirectory(pico_rtc_display)
add_subdirectory(pico_tof_display)
add_subdirectory(pico_tof_display)
add_subdirectory(pico_audio)

Wyświetl plik

@ -0,0 +1,24 @@
if (TARGET pico_audio_i2s)
# You must supply the Pico Extras path to build this example
# -DPICO_SDK_POST_LIST_DIRS=/path/to/pico-extras
# You can use "cmake.configureSettings" in VSCode settings.json
# Grab the extra libraries here: https://github.com/raspberrypi/pico-extras
add_executable(
audio
demo.cpp
synth.cpp
)
# Pull in pico libraries that we need
target_link_libraries(audio pico_stdlib pico_audio_i2s)
target_compile_definitions(audio PRIVATE
# compile time configuration of I2S
PICO_AUDIO_I2S_MONO_INPUT=1
#define for our example code
USE_AUDIO_I2S=1
)
# create map/bin/hex file etc.
pico_add_extra_outputs(audio)
endif()

Wyświetl plik

@ -0,0 +1,24 @@
# Pico Audio Pack Demo
This demo requires the `pico-extras` repository to compile - https://github.com/raspberrypi/pico-extras.
You should clone it alongside `pico-sdk` and `pimoroni-pico`:
```
git clone https://github.com/raspberrypi/pico-extras
```
And adjust your `cmake` configure command to include the path:
```
cmake .. -DPICO_SDK_POST_LIST_DIRS=/path/to/pico-extras
```
If you're using Visual Studio Code you can add this to `settings.json`:
```json
{
"cmake.configureSettings": {"PICO_SDK_POST_LIST_DIRS": "/path/to/pico-extras"}
}
```

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

@ -0,0 +1,125 @@
#include <stdio.h>
#include <math.h>
#include "pico/stdlib.h"
#include "synth.hpp"
#include "audio.hpp"
#define PICO_AUDIO_PACK_I2S_DATA 9
#define PICO_AUDIO_PACK_I2S_BCLK 10
#define SONG_LENGTH 384
#define HAT 20000
#define BASS 500
#define SNARE 6000
#define SUB 50
using namespace synth;
synth::AudioChannel synth::channels[CHANNEL_COUNT];
// 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
},
};
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();
}
}
}
int main() {
stdio_init_all();
struct audio_buffer_pool *ap = init_audio(synth::sample_rate, PICO_AUDIO_PACK_I2S_DATA, PICO_AUDIO_PACK_I2S_BCLK);
// configure voices
// 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();
}