Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
# Changelog

## Vinx Scorza fork

Starting from `v0.3.2-vinx.1`, this changelog includes changes specific to the Vinx Scorza fork.

All entries below `v0.3.2-vinx.1` are inherited from the Mebitek fork history and are kept here as upstream reference.

# v0.3.2-vinx.1 (16 March 2026)
- Fix crash/reboot when shifting 64-step note sequences with `shift + <` / `shift + >`
- Fix step shifting range handling across note, logic, curve, stochastic and arp sequences
- Fix selected-step shifting inside non-zero subranges

## Mebitek fork history

# v0.3.2 ()
- issue #123 - request - launchpad X step page responsive

Expand Down
9 changes: 7 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.0)
cmake_minimum_required(VERSION 3.5)

set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake")

Expand All @@ -17,4 +17,9 @@ endif()

project(sequencer)
enable_testing()
add_subdirectory(src)

# Fix per pybind11
set(PYBIND11_FINDPYTHON ON)
find_package(Python REQUIRED COMPONENTS Interpreter Development)

add_subdirectory(src)
21 changes: 16 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
![Build Status](https://github.com/westlicht/performer/actions/workflows/ci.yml/badge.svg?branch=master)

# Improvements
# Vinx Scorza Fork

This is a fork of the [original repository](https://github.com/westlicht/performer), with some improvements that I personally find essential.
This is a personal fork of the [Mebitek fork](https://github.com/mebitek/performer) of the original [Westlicht Performer firmware](https://westlicht.github.io/performer).
Current fork version: `0.3.2-vinx.1`.

[Full updated documentation](https://mebitek.github.io/performer/manual/)
The Vinx Scorza fork starts from `v0.3.2-vinx.1`.
Everything before that version in this repository history and changelog comes from the Mebitek fork and is preserved as upstream reference.

To find out more about improvements changes, check [changelog](https://github.com/mebitek/performer/blob/master/CHANGELOG.md)
I’m very grateful to Mebitek for the work done on his fork.
This fork would not exist without the fundamental help of AI agents during development and debugging.

--- original documentation below ---
There will be no documentation for the moment, as there will be no public release. I created this fork to experiment with this awesome project.

You can find the full updated Mebitek documentation here:
https://mebitek.github.io/performer/manual/

You can also find the changelog for the Mebitek fork here:
https://github.com/mebitek/performer/blob/master/CHANGELOG.md

--- ORIGINAL DOCUMENTATION BELOW (Westlicht Performer) ---

# PER|FORMER

Expand Down
7 changes: 6 additions & 1 deletion src/apps/bootloader/Bootloader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <libopencm3/cm3/systick.h>
#include <libopencm3/stm32/flash.h>

#include <cctype>
#include <cstring>

// version tag of firmware in flash
Expand Down Expand Up @@ -75,7 +76,11 @@ static void deinit() {

static void formatVersion(const VersionTag &version, char *str, size_t len) {
if (version.isValid()) {
snprintf(str, len, "%s (%d.%d.%d)", version.name, version.major, version.minor, version.revision);
if (version.name[0] != '\0' && (std::isdigit(version.name[0]) || std::strchr(version.name, '-'))) {
snprintf(str, len, "%s", version.name);
} else {
snprintf(str, len, "%s (%d.%d.%d)", version.name, version.major, version.minor, version.revision);
}
} else {
snprintf(str, len, "invalid image");
}
Expand Down
2 changes: 1 addition & 1 deletion src/apps/sequencer/Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

// Version tag
#define CONFIG_VERSION_MAGIC 0xfadebabe
#define CONFIG_VERSION_NAME "PER|FORMER SEQUENCER"
#define CONFIG_VERSION_NAME "0.3.2-vinx.1"
#define CONFIG_VERSION_MAJOR 0
#define CONFIG_VERSION_MINOR 3
#define CONFIG_VERSION_REVISION 2
Expand Down
4 changes: 2 additions & 2 deletions src/apps/sequencer/model/ArpSequence.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -318,9 +318,9 @@ void ArpSequence::setNotes(std::initializer_list<int> notes) {

void ArpSequence::shiftSteps(const std::bitset<CONFIG_STEP_COUNT> &selected, int direction) {
if (selected.any()) {
ModelUtils::shiftSteps(_steps, selected, firstStep(), lastStep(), direction);
ModelUtils::shiftSteps(_steps, selected, firstStep(), lastStep() + 1, direction);
} else {
ModelUtils::shiftSteps(_steps, firstStep(), lastStep(), direction);
ModelUtils::shiftSteps(_steps, firstStep(), lastStep() + 1, direction);
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/apps/sequencer/model/CurveSequence.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -210,9 +210,9 @@ void CurveSequence::setShapes(std::initializer_list<int> shapes) {

void CurveSequence::shiftSteps(const std::bitset<CONFIG_STEP_COUNT> &selected, int direction) {
if (selected.any()) {
ModelUtils::shiftSteps(_steps, selected, firstStep(), lastStep(), direction);
ModelUtils::shiftSteps(_steps, selected, firstStep(), lastStep() + 1, direction);
} else {
ModelUtils::shiftSteps(_steps, firstStep(), lastStep(), direction);
ModelUtils::shiftSteps(_steps, firstStep(), lastStep() + 1, direction);
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/apps/sequencer/model/LogicSequence.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -294,9 +294,9 @@ void LogicSequence::setNotes(std::initializer_list<int> notes) {

void LogicSequence::shiftSteps(const std::bitset<CONFIG_STEP_COUNT> &selected, int direction) {
if (selected.any()) {
ModelUtils::shiftSteps(_steps, selected, firstStep(), lastStep(), direction);
ModelUtils::shiftSteps(_steps, selected, firstStep(), lastStep() + 1, direction);
} else {
ModelUtils::shiftSteps(_steps, firstStep(), lastStep(), direction);
ModelUtils::shiftSteps(_steps, firstStep(), lastStep() + 1, direction);
}
}

Expand Down
34 changes: 26 additions & 8 deletions src/apps/sequencer/model/ModelUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,16 @@ static void shiftSteps(std::array<Step, N> &steps, int direction) {
template<typename Step, size_t N>
static void shiftSteps(std::array<Step, N> &steps, int first, int last, int direction)
{
if (last - first <= 1) {
return;
}

if (direction == 1) {
for (int i = last - 1; i >= first; --i) {
for (int i = last - 2; i >= first; --i) {
std::swap(steps[i], steps[i + 1]);
}
} else if (direction == -1) {
for (int i = first; i < last; ++i) {
for (int i = first; i < last - 1; ++i) {
std::swap(steps[i], steps[i + 1]);
}
}
Expand All @@ -75,8 +79,22 @@ static void shiftSteps(std::array<Step, N> &steps, const std::bitset<N> &selecte
// direction L = -1 ; R = 1

int distance = last - first;
if (distance <= 1) {
return;
}

int starting_point = -1; // from where we'll swap. -1 means everything is selected

auto wrap = [first, last, distance] (int index) {
while (index < first) {
index += distance;
}
while (index >= last) {
index -= distance;
}
return index;
};

if (direction == -1) {
// Find a starting point (the closest step to the left that we don't shift)

Expand All @@ -93,9 +111,9 @@ static void shiftSteps(std::array<Step, N> &steps, const std::bitset<N> &selecte
}

// scan and swap the whole range, from left to right
for(int i=starting_point; i<distance; i++) {
int idx = (i + starting_point + last) % last;
int previous_idx = ((idx - 1) + last) % last;
for (int i = 1; i < distance; ++i) {
int idx = wrap(starting_point + i);
int previous_idx = wrap(idx - 1);

if (selected[idx])
std::swap(steps[previous_idx], steps[idx]);
Expand All @@ -116,9 +134,9 @@ static void shiftSteps(std::array<Step, N> &steps, const std::bitset<N> &selecte
}

// scan and swap the whole range, from right to left
for(int i=distance-1; i>=0; i--) {
int idx = (i + starting_point + last) % last;
int next_idx = ((idx + 1) + last) % last;
for (int i = 1; i < distance; ++i) {
int idx = wrap(starting_point - i);
int next_idx = wrap(idx + 1);

if (selected[idx])
std::swap(steps[next_idx], steps[idx]);
Expand Down
2 changes: 2 additions & 0 deletions src/apps/sequencer/model/ProjectVersion.h
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#pragma once

enum ProjectVersion {
// added NoteTrack::cvUpdateMode
Version4 = 4,
Expand Down
4 changes: 2 additions & 2 deletions src/apps/sequencer/model/StochasticSequence.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -335,9 +335,9 @@ void StochasticSequence::setNotes(std::initializer_list<int> notes) {

void StochasticSequence::shiftSteps(const std::bitset<CONFIG_STEP_COUNT> &selected, int direction) {
if (selected.any()) {
ModelUtils::shiftSteps(_steps, selected, firstStep(), lastStep(), direction);
ModelUtils::shiftSteps(_steps, selected, firstStep(), lastStep() + 1, direction);
} else {
ModelUtils::shiftSteps(_steps, firstStep(), lastStep(), direction);
ModelUtils::shiftSteps(_steps, firstStep(), lastStep() + 1, direction);
}
}

Expand Down
6 changes: 3 additions & 3 deletions src/apps/sequencer/ui/pages/LogicSequenceEditPage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -531,7 +531,7 @@ void LogicSequenceEditPage::keyPress(KeyPressEvent &event) {
if (key.shiftModifier()) {
_inMemorySequence = _project.selectedLogicSequence();
sequence.shiftSteps(_stepSelection.selected(), -1);
_stepSelection.shiftLeft(sequence.firstStep(), sequence.lastStep()-1);
_stepSelection.shiftLeft(sequence.firstStep(), sequence.lastStep() + 1);
} else {
track.setPatternFollowDisplay(false);
sequence.setSecion(std::max(0, sequence.section() - 1));
Expand All @@ -542,7 +542,7 @@ void LogicSequenceEditPage::keyPress(KeyPressEvent &event) {
if (key.shiftModifier()) {
_inMemorySequence = _project.selectedLogicSequence();
sequence.shiftSteps(_stepSelection.selected(), 1);
_stepSelection.shiftRight(sequence.firstStep(), sequence.lastStep()-1);
_stepSelection.shiftRight(sequence.firstStep(), sequence.lastStep() + 1);
} else {
track.setPatternFollowDisplay(false);
sequence.setSecion(std::min(3, sequence.section() + 1));
Expand Down Expand Up @@ -1222,4 +1222,4 @@ void LogicSequenceEditPage::setSelectedStepsGate(bool gate) {
sequence.step(stepIndex).setGate(gate);
}
}
}
}
2 changes: 1 addition & 1 deletion src/apps/sequencer/ui/pages/StartupPage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ void StartupPage::draw(Canvas &canvas) {
canvas.setFont(Font::Small);
canvas.drawTextCentered(0, 0, Width, 32, "PERFORMER");
canvas.setFont(Font::Tiny);
canvas.drawTextCentered(0, 20, Width, 8, "mebitek custom firmware");
canvas.drawTextCentered(0, 20, Width, 8, "Vinx Custom Firmware");

canvas.setFont(Font::Tiny);
canvas.drawTextCentered(0, 32, Width, 32, "LOADING ...");
Expand Down
1 change: 1 addition & 0 deletions src/tests/unit/sequencer/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
include_directories(../../../apps/sequencer)

register_test(TestCurve TestCurve.cpp)
register_test(TestNoteSequence TestNoteSequence.cpp)
register_test(TestScale TestScale.cpp)
82 changes: 82 additions & 0 deletions src/tests/unit/sequencer/TestNoteSequence.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#include "UnitTest.h"

#include "apps/sequencer/model/ModelUtils.h"

#include <array>
#include <bitset>

UNIT_TEST("NoteSequence") {

CASE("shiftSteps rotates full 64-step range right") {
std::array<int, 64> steps = {};
steps[0] = 10;
steps[63] = 99;

ModelUtils::shiftSteps(steps, 0, 64, 1);

expectEqual(99, steps[0]);
expectEqual(10, steps[1]);
expectEqual(0, steps[63]);
}

CASE("shiftSteps rotates full 64-step range left") {
std::array<int, 64> steps = {};
steps[0] = 10;
steps[63] = 99;

ModelUtils::shiftSteps(steps, 0, 64, -1);

expectEqual(0, steps[0]);
expectEqual(99, steps[62]);
expectEqual(10, steps[63]);
}

CASE("shiftSteps rotates subrange ending at step 64 right") {
std::array<int, 64> steps = {};
steps[60] = 1;
steps[63] = 2;

ModelUtils::shiftSteps(steps, 60, 64, 1);

expectEqual(2, steps[60]);
expectEqual(1, steps[61]);
expectEqual(0, steps[63]);
}

CASE("shiftSteps moves selected steps right inside non-zero subrange") {
std::array<int, 64> steps = {};
for (int i = 16; i < 20; ++i) {
steps[i] = i - 15;
}

std::bitset<64> selected;
selected.set(17);
selected.set(18);

ModelUtils::shiftSteps(steps, selected, 16, 20, 1);

expectEqual(1, steps[16]);
expectEqual(4, steps[17]);
expectEqual(2, steps[18]);
expectEqual(3, steps[19]);
}

CASE("shiftSteps moves selected steps left inside non-zero subrange") {
std::array<int, 64> steps = {};
for (int i = 16; i < 20; ++i) {
steps[i] = i - 15;
}

std::bitset<64> selected;
selected.set(17);
selected.set(18);

ModelUtils::shiftSteps(steps, selected, 16, 20, -1);

expectEqual(2, steps[16]);
expectEqual(3, steps[17]);
expectEqual(1, steps[18]);
expectEqual(4, steps[19]);
}

}