Zero Tracker

This commit is contained in:
RogueMaster
2022-11-22 16:15:31 -05:00
parent 576ecb98a3
commit 58a532df3a
14 changed files with 1321 additions and 0 deletions
+2
View File
@@ -33,6 +33,7 @@ Thank you to all the supporters!
- Added: [Power: Show battery percentile while charging #178 (By TQMatvey)](https://github.com/DarkFlippers/unleashed-firmware/pull/178)
- Added: [Fix U2F HID vulnerability #2042 (By nminaylov)](https://github.com/flipperdevices/flipperzero-firmware/pull/2042)
- Updated: [WiFi (Marauder) (By 0xchocolate)](https://github.com/0xchocolate/flipperzero-firmware-with-wifi-marauder-companion)
- Added: [Zero Tracker (By DrZZlo13)](https://github.com/DrZlo13/flipper-zero-music-tracker)
## Install from Release
FLASH STOCK FIRST BEFORE UPDATING TO CUSTOM FIRMWARE!
@@ -247,6 +248,7 @@ $ ./fbt resources icons dolphin_ext
- [WiFi (Marauder) (By 0xchocolate)](https://github.com/0xchocolate/flipperzero-firmware-with-wifi-marauder-companion) `Req: ESP32 WITH MARAUDER FLASHED`
- [WiFi Scanner v.0.4 (By SequoiaSan)](https://github.com/SequoiaSan/FlipperZero-WiFi-Scanner_Module-ESP8266) `Req: ESP8266 or ESP32`
- [Wii EC Analyser (By csBlueChip)](https://github.com/csBlueChip/FlipperZero_WiiEC)
- [Zero Tracker (By DrZZlo13)](https://github.com/DrZlo13/flipper-zero-music-tracker)
## Open PRs Checked Out & Not Merged In Main
@@ -0,0 +1,19 @@
name: Build dev
on:
push:
branches:
- master
jobs:
build_dev:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Build
uses: oleksiikutuzov/flipperzero-ufbt-action@v1
with:
channel: dev
@@ -0,0 +1,4 @@
# Flipper Zero music tracker
-=-=- MVP Stage: minimum viable player -=-=-
[>Get latest build<](https://nightly.link/DrZlo13/flipper-zero-music-tracker/workflows/build_dev/master/zero_tracker.fap.zip)
@@ -0,0 +1,14 @@
App(
appid="zero_tracker",
name="Zero Tracker",
apptype=FlipperAppType.PLUGIN,
entry_point="zero_tracker_app",
requires=[
"gui",
],
stack_size=4 * 1024,
order=20,
fap_icon="zero_tracker.png",
fap_category="Music",
fap_icon_assets="icons",
)
@@ -0,0 +1,102 @@
#include "speaker_hal.h"
#define FURI_HAL_SPEAKER_TIMER TIM16
#define FURI_HAL_SPEAKER_CHANNEL LL_TIM_CHANNEL_CH1
#define FURI_HAL_SPEAKER_PRESCALER 500
void tracker_speaker_play(float frequency, float pwm) {
uint32_t autoreload = (SystemCoreClock / FURI_HAL_SPEAKER_PRESCALER / frequency) - 1;
if(autoreload < 2) {
autoreload = 2;
} else if(autoreload > UINT16_MAX) {
autoreload = UINT16_MAX;
}
if(pwm < 0) pwm = 0;
if(pwm > 1) pwm = 1;
uint32_t compare_value = pwm * autoreload;
if(compare_value == 0) {
compare_value = 1;
}
if(LL_TIM_OC_GetCompareCH1(FURI_HAL_SPEAKER_TIMER) != compare_value) {
LL_TIM_OC_SetCompareCH1(FURI_HAL_SPEAKER_TIMER, compare_value);
}
if(LL_TIM_GetAutoReload(FURI_HAL_SPEAKER_TIMER) != autoreload) {
LL_TIM_SetAutoReload(FURI_HAL_SPEAKER_TIMER, autoreload);
if(LL_TIM_GetCounter(FURI_HAL_SPEAKER_TIMER) > autoreload) {
LL_TIM_SetCounter(FURI_HAL_SPEAKER_TIMER, 0);
}
}
LL_TIM_EnableAllOutputs(FURI_HAL_SPEAKER_TIMER);
}
void tracker_speaker_stop() {
LL_TIM_DisableAllOutputs(FURI_HAL_SPEAKER_TIMER);
}
void tracker_speaker_init() {
furi_hal_speaker_start(200.0f, 0.01f);
tracker_speaker_stop();
}
void tracker_speaker_deinit() {
furi_hal_speaker_stop();
}
static FuriHalInterruptISR tracker_isr;
static void* tracker_isr_context;
static void tracker_interrupt_cb(void* context) {
UNUSED(context);
if(LL_TIM_IsActiveFlag_UPDATE(TIM2)) {
LL_TIM_ClearFlag_UPDATE(TIM2);
if(tracker_isr) {
tracker_isr(tracker_isr_context);
}
}
}
void tracker_interrupt_init(float freq, FuriHalInterruptISR isr, void* context) {
tracker_isr = isr;
tracker_isr_context = context;
furi_hal_interrupt_set_isr(FuriHalInterruptIdTIM2, tracker_interrupt_cb, NULL);
LL_TIM_InitTypeDef TIM_InitStruct = {0};
// Prescaler to get 1kHz clock
TIM_InitStruct.Prescaler = SystemCoreClock / 1000000 - 1;
TIM_InitStruct.CounterMode = LL_TIM_COUNTERMODE_UP;
// Auto reload to get freq Hz interrupt
TIM_InitStruct.Autoreload = (1000000 / freq) - 1;
TIM_InitStruct.ClockDivision = LL_TIM_CLOCKDIVISION_DIV1;
LL_TIM_Init(TIM2, &TIM_InitStruct);
LL_TIM_EnableIT_UPDATE(TIM2);
LL_TIM_EnableAllOutputs(TIM2);
LL_TIM_EnableCounter(TIM2);
}
void tracker_interrupt_deinit() {
FURI_CRITICAL_ENTER();
LL_TIM_DeInit(TIM2);
FURI_CRITICAL_EXIT();
furi_hal_interrupt_set_isr(FuriHalInterruptIdTIM2, NULL, NULL);
}
void tracker_debug_init() {
furi_hal_gpio_init(&gpio_ext_pc3, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow);
}
void tracker_debug_set(bool value) {
furi_hal_gpio_write(&gpio_ext_pc3, value);
}
void tracker_debug_deinit() {
furi_hal_gpio_init(&gpio_ext_pc3, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
}
@@ -0,0 +1,19 @@
#include <furi_hal.h>
void tracker_speaker_init();
void tracker_speaker_deinit();
void tracker_speaker_play(float frequency, float pwm);
void tracker_speaker_stop();
void tracker_interrupt_init(float freq, FuriHalInterruptISR isr, void* context);
void tracker_interrupt_deinit();
void tracker_debug_init();
void tracker_debug_set(bool value);
void tracker_debug_deinit();
@@ -0,0 +1,441 @@
#include "tracker.h"
#include <stdbool.h>
#include "speaker_hal.h"
// 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;
typedef struct {
ChannelState* channels;
uint8_t tick;
uint8_t tick_limit;
uint8_t pattern_index;
uint8_t row_index;
uint8_t order_list_index;
} SongState;
typedef struct {
uint8_t note;
uint8_t effect;
uint8_t data;
} UnpackedRow;
struct Tracker {
Song* song;
bool playing;
TrackerMessageCallback callback;
void* context;
SongState song_state;
};
static void channels_state_init(ChannelState* channel) {
channel->frequency = 0;
channel->frequency_target = FREQUENCY_UNSET;
channel->pwm = PWM_DEFAULT;
channel->play = false;
channel->vibrato.speed = 0;
channel->vibrato.depth = 0;
channel->vibrato.direction = 0;
channel->vibrato.value = 0;
}
static void tracker_song_state_init(Tracker* tracker) {
tracker->song_state.tick = 0;
tracker->song_state.tick_limit = 2;
tracker->song_state.row_index = 0;
tracker->song_state.order_list_index = 0;
tracker->song_state.pattern_index = tracker->song->order_list[0];
if(tracker->song_state.channels != NULL) {
free(tracker->song_state.channels);
}
tracker->song_state.channels = malloc(sizeof(ChannelState) * tracker->song->channels_count);
for(uint8_t i = 0; i < tracker->song->channels_count; i++) {
channels_state_init(&tracker->song_state.channels[i]);
}
}
static void tracker_song_state_clear(Tracker* tracker) {
if(tracker->song_state.channels != NULL) {
free(tracker->song_state.channels);
tracker->song_state.channels = NULL;
}
}
static uint8_t record_get_note(Row note) {
return note & ROW_NOTE_MASK;
}
static uint8_t record_get_effect(Row note) {
return (note >> 6) & ROW_EFFECT_MASK;
}
static uint8_t record_get_effect_data(Row note) {
return (note >> 10) & ROW_EFFECT_DATA_MASK;
}
#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,
};
static 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);
}
static float frequency_offset_semitones(float frequency, uint8_t semitones) {
return frequency * (1.0f + ((1.0f / 12.0f) * semitones));
}
static float frequency_get_seventh_of_a_semitone(float frequency) {
return frequency * ((1.0f / 12.0f) / 7.0f);
}
static UnpackedRow get_current_row(Song* song, SongState* song_state, uint8_t channel) {
Pattern* pattern = &song->patterns[song_state->pattern_index];
Row row = pattern->channels[channel].rows[song_state->row_index];
return (UnpackedRow){
.note = record_get_note(row),
.effect = record_get_effect(row),
.data = record_get_effect_data(row),
};
}
static int16_t advance_order_and_get_next_pattern_index(Song* song, SongState* song_state) {
song_state->order_list_index++;
if(song_state->order_list_index >= song->order_list_size) {
return -1;
} else {
return song->order_list[song_state->order_list_index];
}
}
typedef struct {
int16_t pattern;
int16_t row;
bool change_pattern;
bool change_row;
} Location;
static void tracker_send_position_message(Tracker* tracker) {
if(tracker->callback != NULL) {
tracker->callback(
(TrackerMessage){
.type = TrackerPositionChanged,
.data =
{
.position =
{
.order_list_index = tracker->song_state.order_list_index,
.row = tracker->song_state.row_index,
},
},
},
tracker->context);
}
}
static void tracker_send_end_message(Tracker* tracker) {
if(tracker->callback != NULL) {
tracker->callback((TrackerMessage){.type = TrackerEndOfSong}, tracker->context);
}
}
static void advance_to_pattern(Tracker* tracker, Location advance) {
if(advance.change_pattern) {
if(advance.pattern < 0 || advance.pattern >= tracker->song->patterns_count) {
tracker->playing = false;
tracker_send_end_message(tracker);
} else {
tracker->song_state.pattern_index = advance.pattern;
tracker->song_state.row_index = 0;
}
}
if(advance.change_row) {
if(advance.row < 0) advance.row = 0;
if(advance.row >= PATTERN_SIZE) advance.row = PATTERN_SIZE - 1;
tracker->song_state.row_index = advance.row;
}
tracker_send_position_message(tracker);
}
static void tracker_interrupt_body(Tracker* tracker) {
if(!tracker->playing) {
tracker_speaker_stop();
return;
}
const uint8_t channel_index = 0;
SongState* song_state = &tracker->song_state;
ChannelState* channel_state = &song_state->channels[channel_index];
Song* song = tracker->song;
UnpackedRow row = get_current_row(song, song_state, channel_index);
// load frequency from note at tick 0
if(song_state->tick == 0) {
bool invalidate_row = false;
// handle "on first tick" effects
if(row.effect == EffectBreakPattern) {
int16_t next_row_index = row.data;
int16_t next_pattern_index =
advance_order_and_get_next_pattern_index(song, song_state);
advance_to_pattern(
tracker,
(Location){
.pattern = next_pattern_index,
.row = next_row_index,
.change_pattern = true,
.change_row = true,
});
invalidate_row = true;
}
if(row.effect == EffectJumpToOrder) {
song_state->order_list_index = row.data;
int16_t next_pattern_index = song->order_list[song_state->order_list_index];
advance_to_pattern(
tracker,
(Location){
.pattern = next_pattern_index,
.change_pattern = true,
});
invalidate_row = true;
}
// tracker state can be affected by effects
if(!tracker->playing) {
tracker_speaker_stop();
return;
}
if(invalidate_row) {
row = get_current_row(song, song_state, channel_index);
if(row.effect == EffectSetSpeed) {
song_state->tick_limit = row.data;
}
}
// handle note effects
if(row.note == NOTE_OFF) {
channel_state->play = false;
} else if((row.note > NOTE_NONE) && (row.note < NOTE_OFF)) {
channel_state->play = true;
// reset vibrato
channel_state->vibrato.speed = 0;
channel_state->vibrato.depth = 0;
channel_state->vibrato.value = 0;
channel_state->vibrato.direction = 0;
// reset pwm
channel_state->pwm = PWM_DEFAULT;
if(row.effect == EffectSlideToNote) {
channel_state->frequency_target = note_to_freq(row.note);
} else {
channel_state->frequency = note_to_freq(row.note);
channel_state->frequency_target = FREQUENCY_UNSET;
}
}
}
if(channel_state->play) {
float frequency, pwm;
if((row.effect == EffectSlideUp || row.effect == EffectSlideDown) &&
row.data != EFFECT_DATA_NONE) {
// apply slide effect
channel_state->frequency += (row.effect == EffectSlideUp ? 1 : -1) * row.data;
} else if(row.effect == EffectSlideToNote) {
// apply slide to note effect, if target frequency is set
if(channel_state->frequency_target > 0) {
if(channel_state->frequency_target > channel_state->frequency) {
channel_state->frequency += row.data;
if(channel_state->frequency > channel_state->frequency_target) {
channel_state->frequency = channel_state->frequency_target;
channel_state->frequency_target = FREQUENCY_UNSET;
}
} else if(channel_state->frequency_target < channel_state->frequency) {
channel_state->frequency -= row.data;
if(channel_state->frequency < channel_state->frequency_target) {
channel_state->frequency = channel_state->frequency_target;
channel_state->frequency_target = FREQUENCY_UNSET;
}
}
}
}
frequency = channel_state->frequency;
pwm = channel_state->pwm;
// apply arpeggio effect
if(row.effect == EffectArpeggio) {
if(row.data != EFFECT_DATA_NONE) {
if((song_state->tick % 3) == 1) {
uint8_t note_offset = EFFECT_DATA_GET_X(row.data);
frequency = frequency_offset_semitones(frequency, note_offset);
} else if((song_state->tick % 3) == 2) {
uint8_t note_offset = EFFECT_DATA_GET_Y(row.data);
frequency = frequency_offset_semitones(frequency, note_offset);
}
}
} else if(row.effect == EffectVibrato) {
// apply vibrato effect, data = speed, depth
uint8_t vibrato_speed = EFFECT_DATA_GET_X(row.data);
uint8_t vibrato_depth = EFFECT_DATA_GET_Y(row.data);
// update vibrato parameters if speed or depth is non-zero
if(vibrato_speed != 0) channel_state->vibrato.speed = vibrato_speed;
if(vibrato_depth != 0) channel_state->vibrato.depth = vibrato_depth;
// update vibrato value
channel_state->vibrato.value +=
channel_state->vibrato.direction * channel_state->vibrato.speed;
// change direction if value is at the limit
if(channel_state->vibrato.value > channel_state->vibrato.depth) {
channel_state->vibrato.direction = -1;
} else if(channel_state->vibrato.value < -channel_state->vibrato.depth) {
channel_state->vibrato.direction = 1;
} else if(channel_state->vibrato.direction == 0) {
// set initial direction, if it is not set
channel_state->vibrato.direction = 1;
}
frequency +=
(frequency_get_seventh_of_a_semitone(frequency) * channel_state->vibrato.value);
} else if(row.effect == EffectPWM) {
pwm = (pwm - PWM_MIN) / EFFECT_DATA_1_MAX * row.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_index = (song_state->row_index + 1);
if(song_state->row_index >= PATTERN_SIZE) {
int16_t next_pattern_index =
advance_order_and_get_next_pattern_index(song, song_state);
advance_to_pattern(
tracker,
(Location){
.pattern = next_pattern_index,
.change_pattern = true,
});
} else {
tracker_send_position_message(tracker);
}
}
}
static void tracker_interrupt_cb(void* context) {
Tracker* tracker = (Tracker*)context;
tracker_debug_set(true);
tracker_interrupt_body(tracker);
tracker_debug_set(false);
}
/*********************************************************************
* Tracker Interface
*********************************************************************/
Tracker* tracker_alloc() {
Tracker* tracker = malloc(sizeof(Tracker));
return tracker;
}
void tracker_free(Tracker* tracker) {
tracker_song_state_clear(tracker);
free(tracker);
}
void tracker_set_message_callback(Tracker* tracker, TrackerMessageCallback callback, void* context) {
furi_check(tracker->playing == false);
tracker->callback = callback;
tracker->context = context;
}
void tracker_set_song(Tracker* tracker, Song* song) {
furi_check(tracker->playing == false);
tracker->song = song;
tracker_song_state_init(tracker);
}
void tracker_set_order_index(Tracker* tracker, uint8_t order_index) {
furi_check(tracker->playing == false);
furi_check(order_index < tracker->song->order_list_size);
tracker->song_state.order_list_index = order_index;
tracker->song_state.pattern_index = tracker->song->order_list[order_index];
}
void tracker_set_row(Tracker* tracker, uint8_t row) {
furi_check(tracker->playing == false);
furi_check(row < PATTERN_SIZE);
tracker->song_state.row_index = row;
}
void tracker_start(Tracker* tracker) {
furi_check(tracker->song != NULL);
tracker->playing = true;
tracker_send_position_message(tracker);
tracker_debug_init();
tracker_speaker_init();
tracker_interrupt_init(tracker->song->ticks_per_second, tracker_interrupt_cb, tracker);
}
void tracker_stop(Tracker* tracker) {
tracker_interrupt_deinit();
tracker_speaker_deinit();
tracker_debug_deinit();
tracker->playing = false;
}
@@ -0,0 +1,38 @@
#pragma once
#include "tracker_notes.h"
#include "tracker_song.h"
typedef enum {
TrackerPositionChanged,
TrackerEndOfSong,
} TrackerMessageType;
typedef struct {
TrackerMessageType type;
union tracker_message_data {
struct {
uint8_t order_list_index;
uint8_t row;
} position;
} data;
} TrackerMessage;
typedef void (*TrackerMessageCallback)(TrackerMessage message, void* context);
typedef struct Tracker Tracker;
Tracker* tracker_alloc();
void tracker_free(Tracker* tracker);
void tracker_set_message_callback(Tracker* tracker, TrackerMessageCallback callback, void* context);
void tracker_set_song(Tracker* tracker, Song* song);
void tracker_set_order_index(Tracker* tracker, uint8_t order_index);
void tracker_set_row(Tracker* tracker, uint8_t row);
void tracker_start(Tracker* tracker);
void tracker_stop(Tracker* tracker);
@@ -0,0 +1,64 @@
#pragma once
#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
@@ -0,0 +1,109 @@
#pragma once
#include <stdint.h>
/**
* @brief Row
*
* 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 Row;
#define ROW_NOTE_MASK 0x3F
#define ROW_EFFECT_MASK 0x0F
#define ROW_EFFECT_DATA_MASK 0x3F
typedef enum {
// 0xy, x - first semitones offset, y - second semitones offset. 0 - no offset .. 7 - +7 semitones...
// Play the arpeggio chord with three notes. The first note is the base note, the second and third are offset by x and y.
// Each note plays one tick.
EffectArpeggio = 0x00,
// 1xx, xx - effect speed, 0 - no effect, 1 - slowest, 0x3F - fastest.
// Slide the note pitch up by xx Hz every tick.
EffectSlideUp = 0x01,
// 2xx, xx - effect speed, 0 - no effect, 1 - slowest, 0x3F - fastest.
// Slide the note pitch down by xx Hz every tick.
EffectSlideDown = 0x02,
// 3xx, xx - effect speed, 0 - no effect, 1 - slowest, 0x3F - fastest.
// Slide the already playing note pitch towards another one by xx Hz every tick.
// The note value is saved until the note is playing, so you don't have to repeat the note value to continue sliding.
EffectSlideToNote = 0x03,
// 4xy, x - vibrato speed (0..7), y - vibrato depth (0..7).
// Vibrato effect. The pitch of the note increases by x Hz each tick to a positive vibrato depth, then decreases to a negative depth.
// Value 1 of depth means 1/7 of a semitone (about 14.28 ct), so value 7 means full semitone.
// Note will play without vibrato on the first tick at the beginning of the effect.
// Vibrato speed and depth are saved until the note is playing, and will be updated only if they are not zero, so you doesn't have to repeat them every tick.
EffectVibrato = 0x04,
// Effect05 = 0x05,
// Effect06 = 0x06,
// Effect07 = 0x07,
// Effect08 = 0x08,
// Effect09 = 0x09,
// Effect0A = 0x0A,
// Bxx, xx - pattern number
// Jump to the order xx in the pattern order table at first tick of current row.
// So if you want to jump to the pattern after note 4, you should put this effect on the 5th note.
EffectJumpToOrder = 0x0B,
// Cxx, xx - pwm value
// Set the PWM value to xx for current row.
EffectPWM = 0x0C,
// Bxx, xx - row number
// Jump to the row xx in next pattern at first tick of current row.
// So if you want to jump to the pattern after note 4, you should put this effect on the 5th note.
EffectBreakPattern = 0x0D,
// Effect0E = 0x0E,
// Fxx, xx - song speed, 0 - 1 tick per note, 1 - 2 ticks per note, 0x3F - 64 ticks per note.
// Set the speed of the song in terms of ticks per note.
// Will be applied at the first tick of current row.
EffectSetSpeed = 0x0F,
} Effect;
#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_NONE 0
#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 PWM_MAX
#define PATTERN_SIZE 64
#define ROW_MAKE(note, effect, data) \
((Row)(((note)&0x3F) | (((effect)&0xF) << 6) | (((data)&0x3F) << 10)))
typedef struct {
Row rows[PATTERN_SIZE];
} Channel;
typedef struct {
Channel* channels;
} Pattern;
typedef struct {
uint8_t channels_count;
uint8_t patterns_count;
Pattern* patterns;
uint8_t order_list_size;
uint8_t* order_list;
uint16_t ticks_per_second;
} Song;
@@ -0,0 +1,509 @@
#include <furi.h>
#include "zero_tracker.h"
#include "tracker_engine/tracker.h"
// Channel p_0_channels[] = {
// {
// .rows =
// {
// // 1/4
// ROW_MAKE(NOTE_C3, EffectArpeggio, EFFECT_DATA_2(4, 7)),
// ROW_MAKE(0, EffectArpeggio, EFFECT_DATA_2(4, 7)),
// ROW_MAKE(NOTE_C4, EffectSlideToNote, 0x20),
// ROW_MAKE(0, EffectSlideToNote, 0x20),
// //
// ROW_MAKE(0, EffectSlideToNote, 0x20),
// ROW_MAKE(0, EffectSlideToNote, 0x20),
// ROW_MAKE(0, EffectSlideToNote, 0x20),
// ROW_MAKE(0, EffectSlideToNote, 0x20),
// //
// ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(1, 1)),
// ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(1, 1)),
// ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(1, 1)),
// ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(1, 1)),
// //
// ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(2, 2)),
// ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(2, 2)),
// ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(2, 2)),
// ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(2, 2)),
// // 2/4
// ROW_MAKE(NOTE_C3, EffectSlideDown, 0x20),
// ROW_MAKE(0, EffectSlideDown, 0x20),
// ROW_MAKE(NOTE_C4, EffectVibrato, EFFECT_DATA_2(3, 3)),
// ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(3, 3)),
// //
// ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(3, 3)),
// ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(3, 3)),
// ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(3, 3)),
// ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(3, 3)),
// //
// ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(3, 3)),
// ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(3, 3)),
// ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(3, 3)),
// ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(3, 3)),
// //
// ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(3, 3)),
// ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(3, 3)),
// ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(3, 3)),
// ROW_MAKE(NOTE_OFF, EffectVibrato, EFFECT_DATA_2(3, 3)),
// // 3/4
// ROW_MAKE(NOTE_C3, EffectArpeggio, EFFECT_DATA_2(4, 7)),
// ROW_MAKE(0, EffectArpeggio, EFFECT_DATA_2(4, 7)),
// ROW_MAKE(NOTE_OFF, 0, 0),
// ROW_MAKE(0, 0, 0),
// //
// ROW_MAKE(0, 0, 0),
// ROW_MAKE(0, 0, 0),
// ROW_MAKE(0, 0, 0),
// ROW_MAKE(0, 0, 0),
// //
// ROW_MAKE(NOTE_C2, EffectPWM, 60),
// ROW_MAKE(0, EffectPWM, 32),
// ROW_MAKE(0, EffectPWM, 12),
// ROW_MAKE(NOTE_OFF, 0, 0),
// //
// ROW_MAKE(0, 0, 0),
// ROW_MAKE(0, 0, 0),
// ROW_MAKE(0, 0, 0),
// ROW_MAKE(0, 0, 0),
// // 4/4
// ROW_MAKE(NOTE_C3, EffectSlideDown, 0x20),
// ROW_MAKE(0, EffectSlideDown, 0x20),
// ROW_MAKE(0, EffectSlideDown, 0x20),
// ROW_MAKE(NOTE_OFF, 0, 0),
// //
// ROW_MAKE(0, 0, 0),
// ROW_MAKE(0, 0, 0),
// ROW_MAKE(0, 0, 0),
// ROW_MAKE(0, 0, 0),
// //
// ROW_MAKE(NOTE_C2, EffectPWM, 60),
// ROW_MAKE(0, EffectPWM, 32),
// ROW_MAKE(0, EffectPWM, 12),
// ROW_MAKE(NOTE_OFF, 0, 0),
// //
// ROW_MAKE(0, 0, 0),
// ROW_MAKE(0, 0, 0),
// ROW_MAKE(0, 0, 0),
// ROW_MAKE(0, 0, 0),
// },
// },
// };
Channel p_0_channels[] = {
{
.rows =
{
//
ROW_MAKE(NOTE_A4, EffectArpeggio, EFFECT_DATA_2(4, 7)),
ROW_MAKE(NOTE_C3, 0, 0),
ROW_MAKE(NOTE_F2, 0, 0),
ROW_MAKE(NOTE_C3, 0, 0),
//
ROW_MAKE(NOTE_E4, 0, 0),
ROW_MAKE(NOTE_C3, 0, 0),
ROW_MAKE(NOTE_E4, EffectPWM, 50),
ROW_MAKE(NOTE_OFF, 0, 0),
//
ROW_MAKE(NOTE_A4, 0, 0),
ROW_MAKE(0, EffectPWM, 55),
ROW_MAKE(0, EffectPWM, 45),
ROW_MAKE(NOTE_OFF, 0, 0),
//
ROW_MAKE(NOTE_E5, 0, 0),
ROW_MAKE(0, EffectPWM, 55),
ROW_MAKE(0, EffectPWM, 45),
ROW_MAKE(NOTE_OFF, 0, 0),
//
ROW_MAKE(NOTE_D5, 0, 0),
ROW_MAKE(NOTE_C3, EffectSlideDown, 0x30),
ROW_MAKE(NOTE_F2, 0, 0),
ROW_MAKE(NOTE_C3, 0, 0),
//
ROW_MAKE(NOTE_C5, 0, 0),
ROW_MAKE(NOTE_C3, 0, 0),
ROW_MAKE(NOTE_C5, 0, 0),
ROW_MAKE(NOTE_OFF, 0, 0),
//
ROW_MAKE(NOTE_A4, 0, 0),
ROW_MAKE(0, 0, 0),
ROW_MAKE(0, 0, 0),
ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(1, 1)),
//
ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(1, 1)),
ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(2, 2)),
ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(2, 2)),
ROW_MAKE(NOTE_OFF, 0, 0),
//
ROW_MAKE(NOTE_B4, EffectArpeggio, EFFECT_DATA_2(4, 7)),
ROW_MAKE(NOTE_D3, 0, 0),
ROW_MAKE(NOTE_G2, 0, 0),
ROW_MAKE(NOTE_D3, 0, 0),
//
ROW_MAKE(NOTE_E4, 0, 0),
ROW_MAKE(NOTE_D3, 0, 0),
ROW_MAKE(NOTE_E4, EffectPWM, 50),
ROW_MAKE(NOTE_OFF, 0, 0),
//
ROW_MAKE(NOTE_A4, 0, 0),
ROW_MAKE(0, EffectPWM, 55),
ROW_MAKE(0, EffectPWM, 45),
ROW_MAKE(NOTE_OFF, 0, 0),
//
ROW_MAKE(NOTE_E5, 0, 0),
ROW_MAKE(0, EffectPWM, 55),
ROW_MAKE(0, EffectPWM, 45),
ROW_MAKE(NOTE_OFF, 0, 0),
//
ROW_MAKE(NOTE_D5, 0, 0),
ROW_MAKE(NOTE_D3, EffectSlideDown, 0x3F),
ROW_MAKE(NOTE_G2, 0, 0),
ROW_MAKE(NOTE_D3, 0, 0),
//
ROW_MAKE(NOTE_C5, 0, 0),
ROW_MAKE(NOTE_D3, 0, 0),
ROW_MAKE(NOTE_C5, 0, 0),
ROW_MAKE(NOTE_OFF, 0, 0),
//
ROW_MAKE(NOTE_A4, 0, 0),
ROW_MAKE(0, 0, 0),
ROW_MAKE(0, 0, 0),
ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(1, 1)),
//
ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(1, 1)),
ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(2, 2)),
ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(2, 2)),
ROW_MAKE(NOTE_OFF, 0, 0),
},
},
};
Channel p_1_channels[] = {
{
.rows =
{
//
ROW_MAKE(NOTE_C5, EffectArpeggio, EFFECT_DATA_2(4, 7)),
ROW_MAKE(NOTE_E3, 0, 0),
ROW_MAKE(NOTE_A2, 0, 0),
ROW_MAKE(NOTE_E3, 0, 0),
//
ROW_MAKE(NOTE_B4, 0, 0),
ROW_MAKE(NOTE_E3, 0, 0),
ROW_MAKE(NOTE_B4, EffectPWM, 50),
ROW_MAKE(NOTE_OFF, 0, 0),
//
ROW_MAKE(NOTE_G4, 0, 0),
ROW_MAKE(0, EffectPWM, 55),
ROW_MAKE(0, EffectPWM, 45),
ROW_MAKE(NOTE_OFF, 0, 0),
//
ROW_MAKE(NOTE_C5, 0, 0),
ROW_MAKE(0, EffectPWM, 55),
ROW_MAKE(0, EffectPWM, 45),
ROW_MAKE(NOTE_OFF, 0, 0),
//
ROW_MAKE(NOTE_C6, 0, 0),
ROW_MAKE(NOTE_E3, EffectSlideDown, 0x30),
ROW_MAKE(NOTE_A2, 0, 0),
ROW_MAKE(NOTE_E3, 0, 0),
//
ROW_MAKE(NOTE_B4, 0, 0),
ROW_MAKE(NOTE_E3, 0, 0),
ROW_MAKE(NOTE_B4, EffectPWM, 50),
ROW_MAKE(NOTE_OFF, 0, 0),
//
ROW_MAKE(NOTE_G4, 0, 0),
ROW_MAKE(0, 0, 0),
ROW_MAKE(0, 0, 0),
ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(1, 1)),
//
ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(1, 1)),
ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(2, 2)),
ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(2, 2)),
ROW_MAKE(NOTE_OFF, 0, 0),
//
ROW_MAKE(NOTE_C5, EffectArpeggio, EFFECT_DATA_2(4, 7)),
ROW_MAKE(NOTE_E3, 0, 0),
ROW_MAKE(NOTE_A2, 0, 0),
ROW_MAKE(NOTE_E3, 0, 0),
//
ROW_MAKE(NOTE_B4, 0, 0),
ROW_MAKE(NOTE_E3, 0, 0),
ROW_MAKE(NOTE_B4, EffectPWM, 50),
ROW_MAKE(NOTE_OFF, 0, 0),
//
ROW_MAKE(NOTE_G4, 0, 0),
ROW_MAKE(0, EffectPWM, 55),
ROW_MAKE(0, EffectPWM, 45),
ROW_MAKE(NOTE_OFF, 0, 0),
//
ROW_MAKE(NOTE_D5, 0, 0),
ROW_MAKE(0, EffectPWM, 55),
ROW_MAKE(0, EffectPWM, 45),
ROW_MAKE(NOTE_OFF, 0, 0),
//
ROW_MAKE(NOTE_C6, 0, 0),
ROW_MAKE(NOTE_E3, EffectSlideDown, 0x30),
ROW_MAKE(NOTE_A2, 0, 0),
ROW_MAKE(NOTE_E3, 0, 0),
//
ROW_MAKE(NOTE_B4, 0, 0),
ROW_MAKE(NOTE_E3, 0, 0),
ROW_MAKE(NOTE_B4, EffectPWM, 50),
ROW_MAKE(NOTE_OFF, 0, 0),
//
ROW_MAKE(NOTE_G4, 0, 0),
ROW_MAKE(0, 0, 0),
ROW_MAKE(0, 0, 0),
ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(1, 1)),
//
ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(1, 1)),
ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(2, 2)),
ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(2, 2)),
ROW_MAKE(NOTE_OFF, 0, 0),
},
},
};
Channel p_2_channels[] = {
{
.rows =
{
//
ROW_MAKE(NOTE_C5, EffectArpeggio, EFFECT_DATA_2(4, 7)),
ROW_MAKE(NOTE_E3, 0, 0),
ROW_MAKE(NOTE_A2, 0, 0),
ROW_MAKE(NOTE_E3, 0, 0),
//
ROW_MAKE(NOTE_C5, 0, 0),
ROW_MAKE(NOTE_A4, 0, 0),
ROW_MAKE(NOTE_C5, 0, 0),
ROW_MAKE(NOTE_A4, 0, 0),
//
ROW_MAKE(NOTE_C5, EffectPWM, 55),
ROW_MAKE(NOTE_A4, EffectPWM, 45),
ROW_MAKE(NOTE_C5, EffectPWM, 35),
ROW_MAKE(NOTE_OFF, 0, 0),
//
ROW_MAKE(NOTE_C5, 0, 0),
ROW_MAKE(NOTE_A4, 0, 0),
ROW_MAKE(NOTE_C5, EffectPWM, 55),
ROW_MAKE(NOTE_OFF, 0, 0),
//
ROW_MAKE(NOTE_D5, 0, 0),
ROW_MAKE(NOTE_E3, EffectSlideDown, 0x30),
ROW_MAKE(NOTE_A2, 0, 0),
ROW_MAKE(NOTE_E3, 0, 0),
//
ROW_MAKE(NOTE_OFF, 0, 0),
ROW_MAKE(NOTE_E3, 0, 0),
ROW_MAKE(NOTE_B4, EffectPWM, 55),
ROW_MAKE(NOTE_OFF, 0, 0),
//
ROW_MAKE(NOTE_D5, 0, 0),
ROW_MAKE(NOTE_B4, 0, 0),
ROW_MAKE(NOTE_D5, EffectPWM, 55),
ROW_MAKE(NOTE_B4, EffectPWM, 55),
//
ROW_MAKE(NOTE_D5, EffectPWM, 45),
ROW_MAKE(NOTE_B4, EffectPWM, 45),
ROW_MAKE(NOTE_D5, EffectPWM, 35),
ROW_MAKE(NOTE_OFF, 0, 0),
//
ROW_MAKE(NOTE_D5, EffectArpeggio, EFFECT_DATA_2(4, 7)),
ROW_MAKE(NOTE_E3, 0, 0),
ROW_MAKE(NOTE_A2, 0, 0),
ROW_MAKE(NOTE_E3, 0, 0),
//
ROW_MAKE(NOTE_E5, 0, 0),
ROW_MAKE(NOTE_C5, 0, 0),
ROW_MAKE(NOTE_E5, 0, 0),
ROW_MAKE(NOTE_C5, 0, 0),
//
ROW_MAKE(NOTE_E5, EffectPWM, 55),
ROW_MAKE(NOTE_C5, EffectPWM, 45),
ROW_MAKE(NOTE_E5, EffectPWM, 35),
ROW_MAKE(NOTE_OFF, 0, 0),
//
ROW_MAKE(NOTE_E5, 0, 0),
ROW_MAKE(NOTE_C5, 0, 0),
ROW_MAKE(NOTE_E5, EffectPWM, 55),
ROW_MAKE(NOTE_OFF, 0, 0),
//
ROW_MAKE(NOTE_D5, 0, 0),
ROW_MAKE(NOTE_E3, EffectSlideDown, 0x30),
ROW_MAKE(NOTE_A2, 0, 0),
ROW_MAKE(NOTE_E3, 0, 0),
//
ROW_MAKE(NOTE_OFF, 0, 0),
ROW_MAKE(NOTE_E3, 0, 0),
ROW_MAKE(NOTE_B4, EffectPWM, 55),
ROW_MAKE(NOTE_OFF, 0, 0),
//
ROW_MAKE(NOTE_D5, 0, 0),
ROW_MAKE(NOTE_B4, 0, 0),
ROW_MAKE(NOTE_D5, EffectPWM, 55),
ROW_MAKE(NOTE_B4, EffectPWM, 55),
//
ROW_MAKE(NOTE_D5, EffectPWM, 45),
ROW_MAKE(NOTE_B4, EffectPWM, 45),
ROW_MAKE(NOTE_D5, EffectPWM, 35),
ROW_MAKE(NOTE_OFF, 0, 0),
},
},
};
Channel p_3_channels[] = {
{
.rows =
{
//
ROW_MAKE(NOTE_Ds5, EffectArpeggio, EFFECT_DATA_2(4, 6)),
ROW_MAKE(NOTE_C5, 0, 0),
ROW_MAKE(NOTE_Ds5, 0, 0),
ROW_MAKE(NOTE_C5, EffectPWM, 55),
//
ROW_MAKE(NOTE_Ds5, EffectPWM, 45),
ROW_MAKE(NOTE_C5, EffectPWM, 35),
ROW_MAKE(NOTE_Ds5, EffectPWM, 30),
ROW_MAKE(NOTE_OFF, 0, 0),
//
ROW_MAKE(NOTE_D5, 0, 0),
ROW_MAKE(NOTE_B4, 0, 0),
ROW_MAKE(NOTE_D5, 0, 0),
ROW_MAKE(NOTE_B4, EffectPWM, 55),
//
ROW_MAKE(NOTE_D5, EffectPWM, 45),
ROW_MAKE(NOTE_B4, EffectPWM, 35),
ROW_MAKE(NOTE_D5, EffectPWM, 30),
ROW_MAKE(NOTE_OFF, 0, 0),
//
ROW_MAKE(NOTE_Cs5, EffectArpeggio, EFFECT_DATA_2(4, 6)),
ROW_MAKE(NOTE_As4, 0, 0),
ROW_MAKE(NOTE_Cs5, 0, 0),
ROW_MAKE(NOTE_As4, EffectPWM, 55),
//
ROW_MAKE(NOTE_Cs5, EffectPWM, 45),
ROW_MAKE(NOTE_As4, EffectPWM, 35),
ROW_MAKE(NOTE_Cs5, EffectPWM, 30),
ROW_MAKE(NOTE_OFF, 0, 0),
//
ROW_MAKE(NOTE_C5, 0, 0),
ROW_MAKE(NOTE_A4, 0, 0),
ROW_MAKE(NOTE_C5, 0, 0),
ROW_MAKE(NOTE_A4, EffectPWM, 55),
//
ROW_MAKE(NOTE_C5, EffectPWM, 45),
ROW_MAKE(NOTE_A4, EffectPWM, 35),
ROW_MAKE(NOTE_C5, EffectPWM, 30),
ROW_MAKE(NOTE_OFF, 0, 0),
//
ROW_MAKE(NOTE_B4, EffectArpeggio, EFFECT_DATA_2(4, 6)),
ROW_MAKE(NOTE_Gs4, 0, 0),
ROW_MAKE(NOTE_B4, 0, 0),
ROW_MAKE(NOTE_Gs4, EffectPWM, 55),
//
ROW_MAKE(NOTE_B4, EffectPWM, 45),
ROW_MAKE(NOTE_Gs4, EffectPWM, 35),
ROW_MAKE(NOTE_B4, EffectPWM, 30),
ROW_MAKE(NOTE_OFF, 0, 0),
//
ROW_MAKE(NOTE_C5, 0, 0),
ROW_MAKE(NOTE_A4, 0, 0),
ROW_MAKE(NOTE_C5, 0, 0),
ROW_MAKE(NOTE_A4, EffectPWM, 55),
//
ROW_MAKE(NOTE_C5, EffectPWM, 45),
ROW_MAKE(NOTE_A4, EffectPWM, 35),
ROW_MAKE(NOTE_C5, EffectPWM, 30),
ROW_MAKE(NOTE_OFF, 0, 0),
//
ROW_MAKE(NOTE_Cs5, EffectArpeggio, EFFECT_DATA_2(4, 6)),
ROW_MAKE(NOTE_As4, 0, 0),
ROW_MAKE(NOTE_Cs5, 0, 0),
ROW_MAKE(NOTE_As4, EffectPWM, 55),
//
ROW_MAKE(NOTE_Cs5, EffectPWM, 45),
ROW_MAKE(NOTE_As4, EffectPWM, 35),
ROW_MAKE(NOTE_Cs5, EffectPWM, 30),
ROW_MAKE(NOTE_OFF, 0, 0),
//
ROW_MAKE(NOTE_D5, 0, 0),
ROW_MAKE(NOTE_B4, 0, 0),
ROW_MAKE(NOTE_D5, 0, 0),
ROW_MAKE(NOTE_B4, EffectPWM, 55),
//
ROW_MAKE(NOTE_D5, EffectPWM, 45),
ROW_MAKE(NOTE_B4, EffectPWM, 35),
ROW_MAKE(NOTE_D5, EffectPWM, 30),
ROW_MAKE(NOTE_OFF, 0, 0),
},
},
};
Pattern patterns[] = {
{.channels = p_0_channels},
{.channels = p_1_channels},
{.channels = p_2_channels},
{.channels = p_3_channels},
};
uint8_t order_list[] = {
0,
1,
0,
2,
0,
1,
0,
3,
};
Song song = {
.channels_count = 1,
.patterns_count = sizeof(patterns) / sizeof(patterns[0]),
.patterns = patterns,
.order_list_size = sizeof(order_list) / sizeof(order_list[0]),
.order_list = order_list,
.ticks_per_second = 60,
};
void tracker_message(TrackerMessage message, void* context) {
FuriMessageQueue* queue = context;
furi_assert(queue);
furi_message_queue_put(queue, &message, 0);
}
int32_t zero_tracker_app(void* p) {
UNUSED(p);
FuriMessageQueue* queue = furi_message_queue_alloc(8, sizeof(TrackerMessage));
Tracker* tracker = tracker_alloc();
tracker_set_message_callback(tracker, tracker_message, queue);
tracker_set_song(tracker, &song);
tracker_start(tracker);
while(1) {
TrackerMessage message;
FuriStatus status = furi_message_queue_get(queue, &message, portMAX_DELAY);
if(status == FuriStatusOk) {
if(message.type == TrackerPositionChanged) {
uint8_t order_list_index = message.data.position.order_list_index;
uint8_t row = message.data.position.row;
uint8_t pattern = song.order_list[order_list_index];
FURI_LOG_I("Tracker", "O:%d P:%d R:%d", order_list_index, pattern, row);
} else if(message.type == TrackerEndOfSong) {
FURI_LOG_I("Tracker", "End of song");
break;
}
}
}
tracker_stop(tracker);
tracker_free(tracker);
furi_message_queue_free(queue);
return 0;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 136 B