133 lines
5.2 KiB
C++
133 lines
5.2 KiB
C++
#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();
|
|
|
|
}
|