Skip to content
Merged
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
3 changes: 2 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@ jobs:
fail-fast: false
matrix:
os: [windows-latest, macos-latest]
edition: [full, lite]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Configure
run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Release "-DCMAKE_OSX_ARCHITECTURES=arm64;x86_64"
run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Release "-DCMAKE_OSX_ARCHITECTURES=arm64;x86_64" "-DKSP_ENABLE_VST_HOST=${{ matrix.edition == 'full' && 'ON' || 'OFF' }}"
- name: Build
run: cmake --build build --config Release --parallel
- name: Tests
Expand Down
18 changes: 18 additions & 0 deletions App/AppFolders.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
AppFolders.h
Where the app keeps its own files: Documents/KeyStepPro Studio (the library, backups and the like).
*/

#pragma once

#include <juce_core/juce_core.h>

namespace ksp
{

inline juce::File dataFolder()
{
return juce::File::getSpecialLocation (juce::File::userDocumentsDirectory).getChildFile ("KeyStepPro Studio");
}

} // namespace ksp
5 changes: 5 additions & 0 deletions App/LibraryWindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ class LibraryPanel : public juce::Component, private juce::TableListBoxModel
h.addColumn ("Name", 1, 240, 100, 600);
h.addColumn ("Changed", 2, 140, 80, 220);
h.addColumn ("KeyStep project", 3, 270, 100, 500);
#if KSP_ENABLE_VST_HOST
h.addColumn ("Instruments", 4, 200, 80, 500);
#endif
h.setStretchToFitActive (true);

openButton.setButtonText ("Open");
Expand Down Expand Up @@ -140,17 +142,20 @@ class LibraryPanel : public juce::Component, private juce::TableListBoxModel
text = juce::String (e.modified).replaceCharacter ('T', ' ').upToFirstOccurrenceOf ("Z", false, false);
else if (column == 3)
text = e.summary;
#if KSP_ENABLE_VST_HOST
else
{
for (const auto& n : e.instruments)
text += (text.isEmpty() ? "" : ", ") + juce::String (n);
}
#endif
g.setColour (ksp::ui::palette::ink);
g.setFont (14.0f);
g.drawText (text, 4, 0, w - 8, h, juce::Justification::centredLeft, true);
}

void selectedRowsChanged (int) override { updateButtons(); }
void returnKeyPressed (int) override { openSelected(); }
void cellDoubleClicked (int row, int, const juce::MouseEvent&) override
{
table.selectRow (row);
Expand Down
365 changes: 237 additions & 128 deletions App/Main.cpp

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ App JUCE 8 (Standalone, Windows y Mac) para el Arturia KeyStep Pro: editor de pr
- No mencionar otros proyectos de Leo en el código ni en los docs (Leo, 2026-10-30): el repo es público y trata solo del KSP.
- Testear lo justo (app personal): `protocol_test` y los tests sin JUCE de lo que se toca.

## Ediciones (Leo, 2026-11-04)
- Un solo repo, dos builds, las dos mantenidas: Lite (solo editor) es la edición PRINCIPAL y recomendada (Leo: el host de VST aleja la app de su propósito, editar proyectos visualmente); Full agrega VST3, audio y WAV. Opción CMake `KSP_ENABLE_VST_HOST` (OFF por defecto = Lite) y macro `KSP_ENABLE_VST_HOST` en App/Main.cpp, App/LibraryWindow.h. Lite no enlaza juce_audio_processors / audio_utils ni compila Source/Host. Todo lo que sea del editor, la sincronización y el MIDI va en las dos; el código de VST solo dentro de `#if KSP_ENABLE_VST_HOST`. Source/Mix/Transport.h (reloj del KSP) y App/AppFolders.h son comunes. Instaladores: `build-installer.ps1` (Lite, sin sufijo: KeyStepProStudio-Setup-x.y.z.exe) y `-Full` (KeyStepProStudio-Full-Setup-x.y.z.exe). CI compila las dos ediciones en Windows y Mac.

## Build
- `cmake -S . -B build -DCMAKE_BUILD_TYPE=Release` y `cmake --build build --config Release`. JUCE 8.0.4 se descarga solo (FetchContent) o se usa uno local con `-DJUCE_DIR=...`.
- Windows: Visual Studio 2022/2026 con CMake (o `cmake -G "Visual Studio 17 2022"`). Mac: Xcode o Ninja.
Expand All @@ -39,7 +42,6 @@ App JUCE 8 (Standalone, Windows y Mac) para el Arturia KeyStep Pro: editor de pr
- Tools/KspRestore/Main.cpp: fase 1b, `--file X --project N [--to M] [--write]`: backup previo del slot (ksp-pre-restore-P<M>-*.json), sin `--write` es un dry run; con `--write` abre sesión, manda, commit (3 s) y relee. Solo manda tramas al slot M (`ksp::isWriteFor`).
- App/KspStyle.h: look del KeyStep Pro (paleta chasis blanco / panel negro / colores de track verde-naranja-amarillo-rojo, botones grises con bisel, brillo en lo encendido, KspLookAndFeel, TrackTabs, SevenSegDisplay de 7 segmentos). Los colores van en ksp::ui::palette; no hardcodear colores en las ventanas.
- App/Main.cpp: KeyStepProStudio (fase 2): editor de patterns, abre un backup o lee del equipo, escribe a los 16 slots con backup previo y relectura. Source/Ksp/Device.h (JUCE): open, readSlot, backupSlot, writeSlot; lo usan la app y KspRestore.
- Source/Model/Overdub.h: grabación en tiempo real (overdub) sobre un Pattern desde MIDI crudo: clock desde Start, paso más cercano módulo el largo, solo suma notas y salta las que ya están en el paso (el KSP tocando lo guardado). Lo usa el botón Rec de la app con Device::setListener.
- Source/Model/Pattern.h: un pattern de secuenciador (largo, root, escala, pasos con notas) leído de y escrito en un ProjectData, sin tocar el equipo; después `writePlan` lo manda. Portado de edReadPattern / edWritePattern de KSCON. No toca drums ni arpegiador.
- Source/Model/NoteMove.h: mover / copiar un grupo de notas de un Pattern (arrastre del grid), todo o nada, y sameSteps para el Undo del grid; tests/notemove_test.cpp.
- Source/Model/PatternOps.h: nudge / reverse / invert de un Pattern entero (botón Ops de la app); tests/patternops_test.cpp.
Expand All @@ -57,7 +59,7 @@ App JUCE 8 (Standalone, Windows y Mac) para el Arturia KeyStep Pro: editor de pr
- App/GeneratorWindow.h: la ventana Generate... (estilo, mood, semilla, cadenas, velocidad) que escribe patterns y scenes en el proyecto abierto, con deshacer.
- Source/Gen/Theory.h (Chord, Params, escalas, RNG) + Roles.h (motor de roles: RoleSpec = drone/pad/melody/bells/bass/stab con densidad, registro, largo, pulsos y gate; euclid; makeDrumPattern con DrumSpec) + Ambient.h (el preset ambient = 4 RoleSpec, misma salida que antes: hash congelado en tests/gen_test.cpp) + Styles.h (presets techno/house/dnb: drums en el track 1 en drum mode, bajo, acordes, lead; `applyStyle`) + Arrangement.h (arreglo musical: 8 scenes con curva de energía; patterns 1-5 drums, 1-12 y 16 vacío en T2-T4; `applyArrangement`). En drum mode el instrumento del track 1 escucha el canal 10.
- Source/Gen/Ambient.h: generador ambient (4 tracks, largos 16/13/17/19, semilla, scenes, cadenas, moods ethereal/dark/meditative). Tools/KspGen/Main.cpp: lo escribe a un slot 10-16 con backup y relectura. tests/gen_test.cpp.
- tests/protocol_test.cpp: tramas contra las capturas de KSCON en hardware. tests/backup_test.cpp: plan, lectura con un equipo falso y JSON. tests/restore_test.cpp: JSON ida y vuelta, orden de envío, arreglo de drums, relectura. tests/pattern_test.cpp: leer / editar / escribir un pattern, límites. tests/overdub_test.cpp: cuantizado al paso, dedupe, canal, Start/Stop/Continue, límites.
- tests/protocol_test.cpp: tramas contra las capturas de KSCON en hardware. tests/backup_test.cpp: plan, lectura con un equipo falso y JSON. tests/restore_test.cpp: JSON ida y vuelta, orden de envío, arreglo de drums, relectura. tests/pattern_test.cpp: leer / editar / escribir un pattern, límites.

## Manual
- docs/manual/ (inglés): README con el estado por fase, protocolo y reglas de seguridad, herramientas, modelo, app y bitácora de hardware. Cada PR que cambie comportamiento, herramientas o resultados lo actualiza; es el contexto para tareas futuras. Conciso.
Expand Down
58 changes: 31 additions & 27 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,6 @@ add_test(NAME mix_test COMMAND mix_test)
add_executable(gen_test tests/gen_test.cpp)
target_link_libraries(gen_test PRIVATE ksp_core)
add_test(NAME gen_test COMMAND gen_test)
add_executable(overdub_test tests/overdub_test.cpp)
target_link_libraries(overdub_test PRIVATE ksp_core)
add_test(NAME overdub_test COMMAND overdub_test)
add_executable(notemove_test tests/notemove_test.cpp)
target_link_libraries(notemove_test PRIVATE ksp_core)
add_test(NAME notemove_test COMMAND notemove_test)
Expand All @@ -62,6 +59,9 @@ target_link_libraries(KspCompare PRIVATE ksp_core)
# JUCE: a local checkout with -DJUCE_DIR=..., otherwise downloaded once into the build folder.
# KSP_TESTS_ONLY=ON skips JUCE entirely (fast checks of the no-JUCE code).
option(KSP_TESTS_ONLY "Build only the tests without JUCE" OFF)
# Lite (OFF, the default and the recommended edition) is the editor alone, without the VST3 SDK.
# Full (ON) adds VST3 instruments and effects, audio output and WAV recording.
option(KSP_ENABLE_VST_HOST "Build the Full edition with VST3 hosting (OFF = Lite, the default)" OFF)
if(NOT KSP_TESTS_ONLY)
set(JUCE_DIR "" CACHE PATH "Local JUCE checkout (empty = download)")
if(JUCE_DIR)
Expand Down Expand Up @@ -115,42 +115,46 @@ if(NOT KSP_TESTS_ONLY)
juce::juce_recommended_config_flags
juce::juce_recommended_warning_flags)

# Phase 3: out-of-process VST3 scanner (one plugin file per run; a crash kills only this process).
juce_add_console_app(KspVstHelper PRODUCT_NAME "KspVstHelper")
target_sources(KspVstHelper PRIVATE Tools/KspVstHelper/Main.cpp)
target_compile_definitions(KspVstHelper PRIVATE JUCE_WEB_BROWSER=0 JUCE_USE_CURL=0 JUCE_PLUGINHOST_VST3=1 JUCE_PLUGINHOST_AU=0)
target_link_libraries(KspVstHelper PRIVATE
juce::juce_audio_processors
juce::juce_recommended_config_flags
juce::juce_recommended_warning_flags)
if(KSP_ENABLE_VST_HOST)
# Phase 3: out-of-process VST3 scanner (one plugin file per run; a crash kills only this process).
juce_add_console_app(KspVstHelper PRODUCT_NAME "KspVstHelper")
target_sources(KspVstHelper PRIVATE Tools/KspVstHelper/Main.cpp)
target_compile_definitions(KspVstHelper PRIVATE JUCE_WEB_BROWSER=0 JUCE_USE_CURL=0 JUCE_PLUGINHOST_VST3=1 JUCE_PLUGINHOST_AU=0)
target_link_libraries(KspVstHelper PRIVATE
juce::juce_audio_processors
juce::juce_recommended_config_flags
juce::juce_recommended_warning_flags)

# Phase 3: checks that a scanned VST3 instrument loads, sounds and restores its state (no audio device, no window).
juce_add_console_app(KspHostCheck PRODUCT_NAME "KspHostCheck")
target_sources(KspHostCheck PRIVATE Tools/KspHostCheck/Main.cpp)
target_compile_definitions(KspHostCheck PRIVATE JUCE_WEB_BROWSER=0 JUCE_USE_CURL=0 JUCE_PLUGINHOST_VST3=1 JUCE_PLUGINHOST_AU=0)
target_link_libraries(KspHostCheck PRIVATE
ksp_core
juce::juce_audio_processors
juce::juce_audio_formats
juce::juce_recommended_config_flags
juce::juce_recommended_warning_flags)
# Phase 3: checks that a scanned VST3 instrument loads, sounds and restores its state (no audio device, no window).
juce_add_console_app(KspHostCheck PRODUCT_NAME "KspHostCheck")
target_sources(KspHostCheck PRIVATE Tools/KspHostCheck/Main.cpp)
target_compile_definitions(KspHostCheck PRIVATE JUCE_WEB_BROWSER=0 JUCE_USE_CURL=0 JUCE_PLUGINHOST_VST3=1 JUCE_PLUGINHOST_AU=0)
target_link_libraries(KspHostCheck PRIVATE
ksp_core
juce::juce_audio_processors
juce::juce_audio_formats
juce::juce_recommended_config_flags
juce::juce_recommended_warning_flags)
endif()

# Phase 2: KeyStepPro Studio, the editor app.
# Icon: App/Resources/*.png from Tools/make_icon.py (the four track colours); JUCE makes the .ico / .icns.
juce_add_gui_app(KeyStepProStudio PRODUCT_NAME "KeyStepPro Studio" COMPANY_NAME "KeyStepPro Studio"
BUNDLE_ID io.github.lgarraza.keystepprostudio
ICON_BIG App/Resources/icon.png ICON_SMALL App/Resources/icon_small.png)
target_sources(KeyStepProStudio PRIVATE App/Main.cpp)
target_compile_definitions(KeyStepProStudio PRIVATE JUCE_WEB_BROWSER=0 JUCE_USE_CURL=0 JUCE_PLUGINHOST_VST3=1 JUCE_PLUGINHOST_AU=0 JUCE_APPLICATION_NAME_STRING="KeyStepPro Studio" JUCE_APPLICATION_VERSION_STRING="${PROJECT_VERSION}")
target_compile_definitions(KeyStepProStudio PRIVATE JUCE_WEB_BROWSER=0 JUCE_USE_CURL=0 KSP_ENABLE_VST_HOST=$<BOOL:${KSP_ENABLE_VST_HOST}> JUCE_APPLICATION_NAME_STRING="KeyStepPro Studio" JUCE_APPLICATION_VERSION_STRING="${PROJECT_VERSION}")
target_link_libraries(KeyStepProStudio PRIVATE
ksp_core
juce::juce_gui_basics
juce::juce_audio_devices
juce::juce_audio_processors
juce::juce_audio_utils
juce::juce_recommended_config_flags
juce::juce_recommended_warning_flags)
add_dependencies(KeyStepProStudio KspVstHelper)
add_custom_command(TARGET KeyStepProStudio POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:KspVstHelper> $<TARGET_FILE_DIR:KeyStepProStudio>)
if(KSP_ENABLE_VST_HOST)
target_compile_definitions(KeyStepProStudio PRIVATE JUCE_PLUGINHOST_VST3=1 JUCE_PLUGINHOST_AU=0)
target_link_libraries(KeyStepProStudio PRIVATE juce::juce_audio_processors juce::juce_audio_utils)
add_dependencies(KeyStepProStudio KspVstHelper)
add_custom_command(TARGET KeyStepProStudio POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:KspVstHelper> $<TARGET_FILE_DIR:KeyStepProStudio>)
endif()
endif()
30 changes: 23 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,40 @@
# KeyStepPro Studio

A free desktop companion for the **Arturia KeyStep Pro**: edit its sequencer patterns on a big screen, keep a library of
projects on your computer, and play the KeyStep through your own VST3 instruments without opening a DAW.
projects on your computer, and generate new music for it. An optional Full edition also plays the KeyStep through your own
VST3 instruments.

![KeyStepPro Studio main window](docs/images/main-window.png)

Windows (tested) and macOS (builds from the same code, not tested lately). Not affiliated with Arturia.

## Editions

**We recommend Lite.** The app was made to edit KeyStep Pro projects visually, and Lite does exactly that. Full adds
VST3 hosting, which takes the app a bit away from that purpose. Both editions are maintained from one source tree
(CMake option `KSP_ENABLE_VST_HOST`, OFF by default = Lite):

| | **Lite** (recommended) | Full |
| --- | --- | --- |
| Pattern editor, library, Project Map, generator, backups, sync, MIDI monitor | yes | yes |
| KeyStep clock readout (PLAY / STOP, position, BPM) | yes | yes |
| VST3 instruments, effects, audio output, WAV recording | no | yes |

Lite has no VST3 SDK and no audio engine, so it is smaller and builds faster. Each release has both installers
(`KeyStepProStudio-Setup-x.y.z.exe` for Lite and `KeyStepProStudio-Full-Setup-x.y.z.exe`); installing one replaces the other.
Build Full with `cmake -S . -B build-full -DKSP_ENABLE_VST_HOST=ON`.

## What it does

- **Pattern editor.** Read a project from the KeyStep over USB (or open a backup), see all 64 steps of every pattern,
add, delete and edit notes (pitch, gate, velocity, time shift, randomness), change length, root and scale, copy patterns,
shift a track by an octave, and write the result back to the KeyStep.
- **Overdub.** Press Rec, hit Play on the KeyStep and play over the loop; notes land on the nearest step.
- **Library.** Your projects live on the computer (`Documents/KeyStepPro Studio/Library`), with names, notes and the
instruments of each track. The KeyStep's 16 slots are treated as a working area: open and edit projects without the
KeyStep connected, load one into a slot when you want to play it.
- **VST3 host.** One VST3 instrument per KeyStep track (T1 to T4 on MIDI channels 1 to 4), up to four effect inserts per
- **VST3 host (Full edition).** One VST3 instrument per KeyStep track (T1 to T4 on MIDI channels 1 to 4), up to four effect inserts per
track and a master effect chain with a limiter. The KeyStep is the clock: its Play and Stop drive the app.
- **WAV recording** of the mix and/or one stem per track, started and stopped by the KeyStep's transport.
- **WAV recording (Full edition)** of the mix and/or one stem per track, started and stopped by the KeyStep's transport.
- **Generator** (Generate...): writes patterns and scenes into the open project in a style (Ambient with four moods,
Techno, House deep / chill / melodic, Drum & bass) with a seed, pattern chains and undo. The styles add drums on track 1
and set the project tempo.
Expand Down Expand Up @@ -52,7 +68,7 @@ start writing to your KeyStep.

## Install (Windows)

1. Download `KeyStepProStudio-Setup-x.y.z.exe` from [Releases](../../releases) and run it. It installs the app, the
1. Download `KeyStepProStudio-Setup-x.y.z.exe` (Lite, recommended; `KeyStepProStudio-Full-Setup-x.y.z.exe` adds VST3 hosting) from [Releases](../../releases) and run it. It installs the app, the
VST3 helper and the command-line tools, and adds a Start menu entry. Prefer no installer? Download
`KeyStepProStudio-x.y.z-portable.zip` instead, unzip it anywhere and run `KeyStepPro Studio.exe`.
2. The installer and the app are **not code-signed** (see [docs/signing.md](docs/signing.md)). Windows SmartScreen will
Expand All @@ -69,7 +85,7 @@ start writing to your KeyStep.
selected note. Switch track and pattern at the top.
3. **Save** keeps the project in your library. **Write to slot** sends it to a slot from 10 to 16 (backed up and
verified as described above).
4. To play it through VST3 instruments: **Scan plugins** once, pick an instrument per track in the rows under the grid,
4. Full edition only: to play it through VST3 instruments: **Scan plugins** once, pick an instrument per track in the rows under the grid,
choose your audio device with **Audio...**, and press Play on the KeyStep.

**New to the app?** Start with the [User guide](docs/manual/user/README.md). Developer notes: [docs/manual](docs/manual/README.md).
Expand All @@ -84,7 +100,7 @@ Installed next to the app; useful for backups and troubleshooting.
| `KspBackup` | no | Backup of all projects and the global settings to JSON (about 10 minutes). |
| `KspRestore` | only with `--write` | Restore a project from a backup to a slot (backup first, read back after). |
| `KspGen` | only with `--write` | Generate an ambient project, or a style with `--style`, into a slot. |
| `KspHostCheck` | no | Check that a VST3 instrument or effect loads, sounds and restores its state. |
| `KspHostCheck` (Full only) | no | Check that a VST3 instrument or effect loads, sounds and restores its state. |

Details in [docs/manual/02-tools.md](docs/manual/02-tools.md).

Expand Down
4 changes: 4 additions & 0 deletions Source/Mix/Transport.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ class Transport
long ticks() const noexcept { return tickCount.load(); }
/** The tempo in BPM from the clock (120 until a clock arrives). */
double bpm() const noexcept { return tempo.load(); }
/** Has a tempo been measured from the clock yet? */
bool hasTempo() const noexcept { return measured.load(); }
/** How many Start / Continue and Stop messages arrived: a watcher compares them with what it saw last. */
std::uint32_t starts() const noexcept { return startCounter.load(); }
std::uint32_t stops() const noexcept { return stopCounter.load(); }
Expand Down Expand Up @@ -84,6 +86,7 @@ class Transport
{
average = average == 0.0 ? dt : 0.95 * average + 0.05 * dt;
tempo.store (60.0 / (kClocksPerQuarter * average));
measured.store (true);
}
}
lastClock = now;
Expand All @@ -92,6 +95,7 @@ class Transport
std::atomic<bool> playingNow { false };
std::atomic<long> tickCount { 0 };
std::atomic<double> tempo { 120.0 };
std::atomic<bool> measured { false };
std::atomic<std::uint32_t> startCounter { 0 }, stopCounter { 0 };
double lastClock = 0.0, average = 0.0; // MIDI thread only
};
Expand Down
Loading