| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517 |
- #include <furi.h>
- #include "speaker_hal.h"
- #include "zero_tracker.h"
- /**
- * @brief Note record
- *
- * AH AL
- * FEDCBA98 76543210
- * nnnnnnee eedddddd
- * -------- --------
- * nnnnnn = [0] do nothing, [1..60] note number, [61] note off, [62..63] not used
- * ee ee = [0..F] effect
- * 111222 = [0..63] or [0..7, 0..7] effect data
- */
- typedef uint16_t NoteRecord;
- #define NOTE_NONE 0
- #define NOTE_C2 1
- #define NOTE_Cs2 2
- #define NOTE_D2 3
- #define NOTE_Ds2 4
- #define NOTE_E2 5
- #define NOTE_F2 6
- #define NOTE_Fs2 7
- #define NOTE_G2 8
- #define NOTE_Gs2 9
- #define NOTE_A2 10
- #define NOTE_As2 11
- #define NOTE_B2 12
- #define NOTE_C3 13
- #define NOTE_Cs3 14
- #define NOTE_D3 15
- #define NOTE_Ds3 16
- #define NOTE_E3 17
- #define NOTE_F3 18
- #define NOTE_Fs3 19
- #define NOTE_G3 20
- #define NOTE_Gs3 21
- #define NOTE_A3 22
- #define NOTE_As3 23
- #define NOTE_B3 24
- #define NOTE_C4 25
- #define NOTE_Cs4 26
- #define NOTE_D4 27
- #define NOTE_Ds4 28
- #define NOTE_E4 29
- #define NOTE_F4 30
- #define NOTE_Fs4 31
- #define NOTE_G4 32
- #define NOTE_Gs4 33
- #define NOTE_A4 34
- #define NOTE_As4 35
- #define NOTE_B4 36
- #define NOTE_C5 37
- #define NOTE_Cs5 38
- #define NOTE_D5 39
- #define NOTE_Ds5 40
- #define NOTE_E5 41
- #define NOTE_F5 42
- #define NOTE_Fs5 43
- #define NOTE_G5 44
- #define NOTE_Gs5 45
- #define NOTE_A5 46
- #define NOTE_As5 47
- #define NOTE_B5 48
- #define NOTE_C6 49
- #define NOTE_Cs6 50
- #define NOTE_D6 51
- #define NOTE_Ds6 52
- #define NOTE_E6 53
- #define NOTE_F6 54
- #define NOTE_Fs6 55
- #define NOTE_G6 56
- #define NOTE_Gs6 57
- #define NOTE_A6 58
- #define NOTE_As6 59
- #define NOTE_B6 60
- #define NOTE_OFF 63
- typedef enum {
- EffectArpeggio = 0x00,
- EffectSlideUp = 0x01,
- EffectSlideDown = 0x02,
- EffectSlideToNote = 0x03,
- EffectVibrato = 0x04,
- EffectPWM = 0x0C,
- EffectSetSpeed = 0x0F,
- } Effect;
- #define EFFECT_DATA_NONE 0
- #define EFFECT_DATA_2(x, y) ((x) | ((y) << 3))
- #define EFFECT_DATA_GET_X(data) ((data)&0x07)
- #define EFFECT_DATA_GET_Y(data) (((data) >> 3) & 0x07)
- #define EFFECT_DATA_1_MAX 0x3F
- #define EFFECT_DATA_2_MAX 0x07
- #define FREQUENCY_UNSET -1.0f
- #define PWM_MIN 0.01f
- #define PWM_MAX 0.5f
- #define PWM_DEFAULT 0.5f
- uint8_t record_get_note(NoteRecord note) {
- return note & 0x3F;
- }
- uint8_t record_get_effect(NoteRecord note) {
- return (note >> 6) & 0xF;
- }
- uint8_t record_get_effect_data(NoteRecord note) {
- return (note >> 10) & 0x3F;
- }
- #define RECORD_MAKE(note, effect, data) \
- ((NoteRecord)(((note)&0x3F) | (((effect)&0xF) << 6) | (((data)&0x3F) << 10)))
- #define NOTES_PER_OCT 12
- const float notes_oct[NOTES_PER_OCT] = {
- 130.813f,
- 138.591f,
- 146.832f,
- 155.563f,
- 164.814f,
- 174.614f,
- 184.997f,
- 195.998f,
- 207.652f,
- 220.00f,
- 233.082f,
- 246.942f,
- };
- float note_to_freq(uint8_t note) {
- if(note == NOTE_NONE) return 0.0f;
- note = note - NOTE_C2;
- uint8_t octave = note / NOTES_PER_OCT;
- uint8_t note_in_oct = note % NOTES_PER_OCT;
- return notes_oct[note_in_oct] * (1 << octave);
- }
- float frequency_offset_semitones(float frequency, uint8_t semitones) {
- return frequency * (1.0f + ((1.0f / 12.0f) * semitones));
- }
- float frequency_get_seventh_of_a_semitone(float frequency) {
- return frequency * ((1.0f / 12.0f) / 7.0f);
- }
- #define PATTERN_SIZE 64
- typedef struct {
- NoteRecord notes[PATTERN_SIZE];
- } NoteRow;
- typedef struct {
- uint8_t row_count;
- NoteRow* rows;
- } NotePattern;
- NoteRow _row = {
- .notes =
- {
- //
- RECORD_MAKE(NOTE_A4, 0, 0),
- RECORD_MAKE(NOTE_C3, 0, 0),
- RECORD_MAKE(NOTE_F2, 0, 0),
- RECORD_MAKE(NOTE_C3, 0, 0),
- //
- RECORD_MAKE(NOTE_E4, 0, 0),
- RECORD_MAKE(NOTE_C3, 0, 0),
- RECORD_MAKE(NOTE_E4, 0, 0),
- RECORD_MAKE(NOTE_OFF, 0, 0),
- //
- RECORD_MAKE(NOTE_A4, 0, 0),
- RECORD_MAKE(NOTE_A4, 0, 0),
- RECORD_MAKE(NOTE_A4, 0, 0),
- RECORD_MAKE(NOTE_OFF, 0, 0),
- //
- RECORD_MAKE(NOTE_E5, 0, 0),
- RECORD_MAKE(NOTE_E5, 0, 0),
- RECORD_MAKE(NOTE_E5, 0, 0),
- RECORD_MAKE(NOTE_OFF, 0, 0),
- //
- RECORD_MAKE(NOTE_D5, 0, 0),
- RECORD_MAKE(NOTE_C3, 0, 0),
- RECORD_MAKE(NOTE_F2, 0, 0),
- RECORD_MAKE(NOTE_C3, 0, 0),
- //
- RECORD_MAKE(NOTE_C5, 0, 0),
- RECORD_MAKE(NOTE_C3, 0, 0),
- RECORD_MAKE(NOTE_C5, 0, 0),
- RECORD_MAKE(NOTE_OFF, 0, 0),
- //
- RECORD_MAKE(NOTE_A4, 0, 0),
- RECORD_MAKE(0, 0, 0),
- RECORD_MAKE(0, 0, 0),
- RECORD_MAKE(0, 0, 0),
- //
- RECORD_MAKE(NOTE_A4, 0, 0),
- RECORD_MAKE(NOTE_A4, 0, 0),
- RECORD_MAKE(NOTE_A4, 0, 0),
- RECORD_MAKE(NOTE_OFF, 0, 0),
- //
- RECORD_MAKE(NOTE_B4, 0, 0),
- RECORD_MAKE(NOTE_D3, 0, 0),
- RECORD_MAKE(NOTE_G2, 0, 0),
- RECORD_MAKE(NOTE_D3, 0, 0),
- //
- RECORD_MAKE(NOTE_E4, 0, 0),
- RECORD_MAKE(NOTE_D3, 0, 0),
- RECORD_MAKE(NOTE_E4, 0, 0),
- RECORD_MAKE(NOTE_OFF, 0, 0),
- //
- RECORD_MAKE(NOTE_A4, 0, 0),
- RECORD_MAKE(NOTE_A4, 0, 0),
- RECORD_MAKE(NOTE_A4, 0, 0),
- RECORD_MAKE(NOTE_OFF, 0, 0),
- //
- RECORD_MAKE(NOTE_E5, 0, 0),
- RECORD_MAKE(NOTE_E5, 0, 0),
- RECORD_MAKE(NOTE_E5, 0, 0),
- RECORD_MAKE(NOTE_OFF, 0, 0),
- //
- RECORD_MAKE(NOTE_D5, 0, 0),
- RECORD_MAKE(NOTE_D3, 0, 0),
- RECORD_MAKE(NOTE_G2, 0, 0),
- RECORD_MAKE(NOTE_D3, 0, 0),
- //
- RECORD_MAKE(NOTE_C5, 0, 0),
- RECORD_MAKE(NOTE_D3, 0, 0),
- RECORD_MAKE(NOTE_C5, 0, 0),
- RECORD_MAKE(NOTE_OFF, 0, 0),
- //
- RECORD_MAKE(NOTE_A4, 0, 0),
- RECORD_MAKE(0, 0, 0),
- RECORD_MAKE(0, 0, 0),
- RECORD_MAKE(0, 0, 0),
- //
- RECORD_MAKE(NOTE_A4, 0, 0),
- RECORD_MAKE(NOTE_A4, 0, 0),
- RECORD_MAKE(NOTE_A4, 0, 0),
- RECORD_MAKE(NOTE_OFF, 0, 0),
- },
- };
- const uint8_t test = 0x20;
- NoteRow row = {
- .notes =
- {
- // 1/4
- RECORD_MAKE(NOTE_C3, EffectArpeggio, EFFECT_DATA_2(4, 7)),
- RECORD_MAKE(0, EffectArpeggio, EFFECT_DATA_2(4, 7)),
- RECORD_MAKE(NOTE_C4, EffectSlideToNote, test),
- RECORD_MAKE(0, EffectSlideToNote, test),
- //
- RECORD_MAKE(0, EffectSlideToNote, test),
- RECORD_MAKE(0, EffectSlideToNote, test),
- RECORD_MAKE(0, EffectSlideToNote, test),
- RECORD_MAKE(0, EffectSlideToNote, test),
- //
- RECORD_MAKE(0, EffectVibrato, EFFECT_DATA_2(1, 1)),
- RECORD_MAKE(0, EffectVibrato, EFFECT_DATA_2(1, 1)),
- RECORD_MAKE(0, EffectVibrato, EFFECT_DATA_2(1, 1)),
- RECORD_MAKE(0, EffectVibrato, EFFECT_DATA_2(1, 1)),
- //
- RECORD_MAKE(0, EffectVibrato, EFFECT_DATA_2(2, 2)),
- RECORD_MAKE(0, EffectVibrato, EFFECT_DATA_2(2, 2)),
- RECORD_MAKE(0, EffectVibrato, EFFECT_DATA_2(2, 2)),
- RECORD_MAKE(0, EffectVibrato, EFFECT_DATA_2(2, 2)),
- // 2/4
- RECORD_MAKE(NOTE_C3, EffectSlideDown, 0x20),
- RECORD_MAKE(0, EffectSlideDown, 0x20),
- RECORD_MAKE(NOTE_C4, EffectVibrato, EFFECT_DATA_2(3, 3)),
- RECORD_MAKE(0, EffectVibrato, EFFECT_DATA_2(3, 3)),
- //
- RECORD_MAKE(0, EffectVibrato, EFFECT_DATA_2(3, 3)),
- RECORD_MAKE(0, EffectVibrato, EFFECT_DATA_2(3, 3)),
- RECORD_MAKE(0, EffectVibrato, EFFECT_DATA_2(3, 3)),
- RECORD_MAKE(0, EffectVibrato, EFFECT_DATA_2(3, 3)),
- //
- RECORD_MAKE(0, EffectVibrato, EFFECT_DATA_2(3, 3)),
- RECORD_MAKE(0, EffectVibrato, EFFECT_DATA_2(3, 3)),
- RECORD_MAKE(0, EffectVibrato, EFFECT_DATA_2(3, 3)),
- RECORD_MAKE(0, EffectVibrato, EFFECT_DATA_2(3, 3)),
- //
- RECORD_MAKE(0, EffectVibrato, EFFECT_DATA_2(3, 3)),
- RECORD_MAKE(0, EffectVibrato, EFFECT_DATA_2(3, 3)),
- RECORD_MAKE(0, EffectVibrato, EFFECT_DATA_2(3, 3)),
- RECORD_MAKE(NOTE_OFF, EffectVibrato, EFFECT_DATA_2(3, 3)),
- // 3/4
- RECORD_MAKE(NOTE_C3, EffectArpeggio, EFFECT_DATA_2(4, 7)),
- RECORD_MAKE(0, EffectArpeggio, EFFECT_DATA_2(4, 7)),
- RECORD_MAKE(NOTE_OFF, 0, 0),
- RECORD_MAKE(0, 0, 0),
- //
- RECORD_MAKE(0, 0, 0),
- RECORD_MAKE(0, 0, 0),
- RECORD_MAKE(0, 0, 0),
- RECORD_MAKE(0, 0, 0),
- //
- RECORD_MAKE(NOTE_C2, EffectPWM, 60),
- RECORD_MAKE(0, EffectPWM, 32),
- RECORD_MAKE(0, EffectPWM, 12),
- RECORD_MAKE(NOTE_OFF, 0, 0),
- //
- RECORD_MAKE(0, 0, 0),
- RECORD_MAKE(0, 0, 0),
- RECORD_MAKE(0, 0, 0),
- RECORD_MAKE(0, 0, 0),
- // 4/4
- RECORD_MAKE(NOTE_C3, EffectSlideDown, 0x20),
- RECORD_MAKE(0, EffectSlideDown, 0x20),
- RECORD_MAKE(0, EffectSlideDown, 0x20),
- RECORD_MAKE(NOTE_OFF, 0, 0),
- //
- RECORD_MAKE(0, 0, 0),
- RECORD_MAKE(0, 0, 0),
- RECORD_MAKE(0, 0, 0),
- RECORD_MAKE(0, 0, 0),
- //
- RECORD_MAKE(NOTE_C2, EffectPWM, 60),
- RECORD_MAKE(0, EffectPWM, 32),
- RECORD_MAKE(0, EffectPWM, 12),
- RECORD_MAKE(NOTE_OFF, 0, 0),
- //
- RECORD_MAKE(0, 0, 0),
- RECORD_MAKE(0, 0, 0),
- RECORD_MAKE(0, 0, 0),
- RECORD_MAKE(0, 0, 0),
- },
- };
- typedef struct {
- uint8_t tick;
- uint8_t tick_limit;
- uint8_t row;
- } SongState;
- SongState song_state = {
- .tick = 0,
- .tick_limit = 2,
- .row = 0,
- };
- typedef struct {
- uint8_t speed;
- uint8_t depth;
- int8_t direction;
- int8_t value;
- } IntegerOscillator;
- typedef struct {
- float frequency;
- float frequency_target;
- float pwm;
- bool play;
- IntegerOscillator vibrato;
- } ChannelState;
- ChannelState ch_state = {
- .frequency = 0,
- .frequency_target = FREQUENCY_UNSET,
- .pwm = PWM_DEFAULT,
- .play = false,
- .vibrato =
- {
- .speed = 0,
- .depth = 0,
- .direction = 0,
- .value = 0,
- },
- };
- void tracker_interrupt_body() {
- uint8_t note = record_get_note(row.notes[song_state.row]);
- uint8_t effect = record_get_effect(row.notes[song_state.row]);
- uint8_t data = record_get_effect_data(row.notes[song_state.row]);
- // load frequency from note at tick 0
- if(song_state.tick == 0) {
- if(note == NOTE_OFF) {
- ch_state.play = false;
- } else if((note > NOTE_NONE) && (note < NOTE_OFF)) {
- ch_state.play = true;
- // reset vibrato
- ch_state.vibrato.speed = 0;
- ch_state.vibrato.depth = 0;
- ch_state.vibrato.value = 0;
- ch_state.vibrato.direction = 0;
- // reset pwm
- ch_state.pwm = PWM_DEFAULT;
- if(effect == EffectSlideToNote) {
- ch_state.frequency_target = note_to_freq(note);
- } else {
- ch_state.frequency = note_to_freq(note);
- ch_state.frequency_target = FREQUENCY_UNSET;
- }
- }
- // handle "on first tick" effects
- if(effect == EffectSetSpeed) {
- song_state.tick_limit = data;
- }
- }
- if(ch_state.play) {
- float frequency, pwm;
- if((effect == EffectSlideUp || effect == EffectSlideDown) && data != EFFECT_DATA_NONE) {
- // apply slide effect
- ch_state.frequency += (effect == EffectSlideUp ? 1 : -1) * data;
- } else if(effect == EffectSlideToNote) {
- // apply slide to note effect, if target frequency is set
- if(ch_state.frequency_target > 0) {
- if(ch_state.frequency_target > ch_state.frequency) {
- ch_state.frequency += data;
- if(ch_state.frequency > ch_state.frequency_target) {
- ch_state.frequency = ch_state.frequency_target;
- ch_state.frequency_target = FREQUENCY_UNSET;
- }
- } else if(ch_state.frequency_target < ch_state.frequency) {
- ch_state.frequency -= data;
- if(ch_state.frequency < ch_state.frequency_target) {
- ch_state.frequency = ch_state.frequency_target;
- ch_state.frequency_target = FREQUENCY_UNSET;
- }
- }
- }
- }
- frequency = ch_state.frequency;
- pwm = ch_state.pwm;
- // apply arpeggio effect
- if(effect == EffectArpeggio) {
- if(data != EFFECT_DATA_NONE) {
- if((song_state.tick % 3) == 1) {
- uint8_t note_offset = EFFECT_DATA_GET_X(data);
- frequency = frequency_offset_semitones(frequency, note_offset);
- } else if((song_state.tick % 3) == 2) {
- uint8_t note_offset = EFFECT_DATA_GET_Y(data);
- frequency = frequency_offset_semitones(frequency, note_offset);
- }
- }
- } else if(effect == EffectVibrato) {
- // apply vibrato effect, data = speed, depth
- uint8_t vibrato_speed = EFFECT_DATA_GET_X(data);
- uint8_t vibrato_depth = EFFECT_DATA_GET_Y(data);
- // update vibrato parameters if speed or depth is non-zero
- if(vibrato_speed != 0) ch_state.vibrato.speed = vibrato_speed;
- if(vibrato_depth != 0) ch_state.vibrato.depth = vibrato_depth;
- // update vibrato value
- ch_state.vibrato.value += ch_state.vibrato.direction * ch_state.vibrato.speed;
- // change direction if value is at the limit
- if(ch_state.vibrato.value > ch_state.vibrato.depth) {
- ch_state.vibrato.direction = -1;
- } else if(ch_state.vibrato.value < -ch_state.vibrato.depth) {
- ch_state.vibrato.direction = 1;
- } else if(ch_state.vibrato.direction == 0) {
- // set initial direction, if it is not set
- ch_state.vibrato.direction = 1;
- }
- frequency += (frequency_get_seventh_of_a_semitone(frequency) * ch_state.vibrato.value);
- } else if(effect == EffectPWM) {
- pwm = (pwm - PWM_MIN) / EFFECT_DATA_1_MAX * data + PWM_MIN;
- }
- tracker_speaker_play(frequency, pwm);
- } else {
- tracker_speaker_stop();
- }
- song_state.tick++;
- if(song_state.tick >= song_state.tick_limit) {
- song_state.tick = 0;
- // next note
- song_state.row = (song_state.row + 1) % PATTERN_SIZE;
- // handle "on last tick" effects
- }
- }
- void tracker_interrupt_cb() {
- tracker_debug_set(true);
- tracker_interrupt_body();
- tracker_debug_set(false);
- }
- int32_t zero_tracker_app(void* p) {
- UNUSED(p);
- tracker_debug_init();
- tracker_speaker_init();
- tracker_interrupt_init(60.0f, tracker_interrupt_cb, NULL);
- while(1) {
- furi_delay_ms(1000);
- }
- return 0;
- }
|