Performance-Mapping and Playability-Analysis Tool for Ableton Push 3
PushFlow helps you map sounds onto the Push 3's 8x8 pad grid and analyze whether the result is playable. It models real-world biomechanical constraints — finger spans, hand speed, fatigue — to evaluate layouts and generate alternatives that are physically comfortable and performable.
The product promise is: converge on a Layout plus Execution Plan that is playable, understandable, and worth keeping.
- Import
.midfiles — each unique MIDI pitch becomes a Sound with stable identity - Sounds persist across all layout operations: cloning, promotion, variant saving, and discard
- Color-coded sounds panel with hit count, grid position, hand/finger preference controls
- Drag-and-drop sound assignment to the 64-pad grid
- Left Hand / Right Hand zone visualization
- Timeline-linked mode: grid highlights active pads during playback
- Onion skin overlay for comparing transitions visually
- Placement lock icons on locked pads
- Finger assignment overlay per event
PushFlow uses a three-tier layout lifecycle:
| State | Purpose | Persistence |
|---|---|---|
| Active Layout | Committed baseline — the "real" layout | Durable (saved to project) |
| Working/Test Layout | Session-scoped draft for exploration | Ephemeral (discarded on close) |
| Saved Layout Variant | Named alternative preserved for later | Durable (named, timestamped) |
Workflow actions:
- Promote — Working layout becomes the new Active Layout; old Active auto-saved as variant
- Save Variant — Preserve current working state as a named variant without changing Active
- Discard — Abandon Working layout and revert to Active
- Undo/Redo — Full operation history within the working session
- Greedy Solver — Step-by-step local hill-climbing with human-readable move explanations
- Beam Solver — K-best beam search for finger assignment across events
- Annealing Solver — Global optimization using simulated annealing for deep layout exploration
- Cost Toggles — Selectively enable/disable cost families for diagnostic auditing
- Calculate Cost — Instantly evaluate any layout against the cost model
- Three-layer cost breakdown — Feasibility verdict, ergonomic factor bars, difficulty summary
- Five canonical factors — Transition, Grip Naturalness, Alternation, Hand Balance, Constraint Penalty
- Per-event difficulty chart — Cost contributions per event with difficulty classification
- Difficulty heatmap — Easy / Moderate / Hard / Extreme per event
- Optimization trace — Step-by-step history of moves made by the Greedy solver
- Actionable suggestions — Context-aware recommendations
- Staleness indicator — Warns when analysis is outdated relative to current layout
- Distinct Analyze/Generate phases — Visual distinction between auto-analysis and manual generation
- Horizontal event timeline showing all sounds as swim lanes
- Finger assignment annotations per event (L1-L5, R1-R5)
- Difficulty-colored event pills (amber for Hard, red for Unplayable)
- Playback with real-time cursor and pad highlighting
- All sound streams shown, including unplayable events
- Generated candidate list with tradeoff profile summaries
- Compare mode for side-by-side layout inspection (read-only)
- Promote or save-as-variant from any candidate
- Diversity enforcement — trivial duplicates rejected
- Generative pattern pipeline: motif sampling, phrase building, two-hand coordination
- Rudiment library with standard drumming patterns
- Pattern-based event generation for testing layouts against musical material
- Placement Locks — Pin a sound to a specific pad. Locks are preserved across cloning, generation, and promotion. Lock state is visually indicated on grid pads and in the sounds panel.
- Finger Constraints — Per-pad hand/finger preferences. Soft by default (optimizer biases toward them but may override).
- Grip Feasibility — Binary validation against biomechanical limits. Grips that violate hard constraints are rejected.
PushFlow evaluates layouts using a physics-informed cost model with five canonical diagnostic factors:
| Factor | Description |
|---|---|
| Transition | Fitts's Law movement cost between consecutive pads |
| Grip Naturalness | Combined finger preference + hand shape deviation cost |
| Alternation | Same-finger rapid repetition penalty |
| Hand Balance | Left/right hand distribution imbalance |
| Constraint Penalty | Hard penalty for constraint violations |
- Feasibility Verdict — Can this layout be played? (Feasible / Degraded / Infeasible)
- Ergonomic Cost Breakdown — Which factors contribute most to difficulty?
- Difficulty Summary — How many hard or unplayable events exist?
| Classification | Meaning |
|---|---|
| Easy | Comfortable, no strain |
| Moderate | Requires attention but playable |
| Hard | Challenging, may need practice |
| Extreme | Physically demanding or high-speed |
| Parameter | Value | Description |
|---|---|---|
MAX_HAND_SPAN |
5.5 units | Maximum comfortable multi-finger spread |
MAX_SPEED |
12.0 units/sec | Maximum hand movement speed |
TARGET_LR_SPLIT |
0.45 / 0.55 | Optimal left-hand share |
| Finger | Cost | Rationale |
|---|---|---|
| Index | 0 | Strongest, most dexterous |
| Middle | 0 | Strong, good reach |
| Ring | 1 | Reduced independence |
| Pinky | 3 | Weakest, limited reach |
| Thumb | 5 | Limited lateral movement on pads |
src/
engine/
solvers/ # Beam solver (finger assignment)
optimization/ # Annealing solver, greedy optimizer, multi-candidate generator
evaluation/ # Canonical evaluator, cost functions, difficulty scoring
analysis/ # Baseline compare, candidate comparison, constraint explainer
prior/ # Biomechanical model, feasibility checking
mapping/ # Pad-to-finger resolution
structure/ # Performance structure analysis, moment builder
rudiment/ # Drumming rudiment library
pattern/ # Pattern generation pipeline
diagnostics/ # Fatigue model
surface/ # Hand zone, pad grid
debug/ # Debug utilities, sanity checks
types/
layout.ts # Layout, LayoutRole, cloneLayout, hashLayout
voice.ts # Voice (Sound identity)
executionPlan.ts # ExecutionPlan, FingerAssignment
candidateSolution.ts # CandidateSolution, TradeoffProfile
diagnostics.ts # DiagnosticFactors, FeasibilityVerdict, DiagnosticsPayload
engineConfig.ts # EngineConfiguration, AnnealingPreset
performance.ts # Performance, PerformanceEvent
ui/
components/ # React components (Grid, Timeline, Panels, Candidates, etc.)
pages/ # ProjectLibraryPage, ProjectEditorPage
state/ # ProjectContext, reducer, actions, undo/redo
hooks/ # useAutoAnalysis, useKeyboardShortcuts, useLaneImport
persistence/ # localStorage with migration support
fixtures/ # Demo projects
import/ # MIDI file import
utils/ # ID generation, MIDI note names, seeded RNG
test/
types/ # Voice identity round-trip, layout role validation
engine/ # Solver, optimization, evaluation, feasibility tests
ui/state/ # Lanes reducer, streams conversion
golden/ # End-to-end golden scenario tests
For a code-level graph, see docs/dataflow-diagram.md. The runtime pipeline breaks down into these stages:
| Stage | Main entrypoints | What happens | Models / functions called | Artifacts created |
|---|---|---|---|---|
| 1. Source ingestion | src/import/midiImport.ts, Pattern Composer / loop editor surfaces |
MIDI notes or composed patterns are converted into canonical musical data | @tonejs/midi, parseMidiProject(), composer preset + loop editors |
Performance, PerformanceEvent[], PerformanceMoment[], Voice[], empty Layout |
| 2. Project state normalization | ProjectContext, projectReducer, serializer |
Imported or loaded data is normalized into the editor's single state model | projectSerializer.ts, projectState.ts, lanesReducer.ts |
ProjectState, SoundStream[], activeLayout, workingLayout, savedVariants |
| 3. Derived-performance build | getActivePerformance(), getDisplayedLayout() |
Muted streams are filtered out, current layout context is selected, and solver-facing performance data is rebuilt on demand | getActiveStreams(), getActivePerformance(), layout/display selectors in projectState.ts |
current Performance, displayed Layout, displayed ExecutionPlanResult context |
| 4. Constraint projection | useAutoAnalysis.ts |
User intent is projected from UI state onto solver inputs | buildSolverConstraints(), constraintsToManualAssignments(), computeInitialOwnership() |
SolverConstraints, legacy manual assignments, initial pad ownership |
| 5. Solver / optimizer execution | auto-analysis or manual Generate | PushFlow computes either a single fast analysis plan or a multi-candidate search result | createBeamSolver(), generateCandidates(), generateGreedyCandidates(), optimizer registry, BeamSolver, AnnealingSolver, GreedyOptimizer |
ExecutionPlanResult, OptimizerOutput, candidate Layouts, greedy move trace |
| 6. Canonical evaluation and ranking | post-solve engine pipeline | Layouts and assignments are rescored with the canonical evaluator, then ranked and filtered | evaluatePerformance(), difficultyScoring.ts, candidateRanker.ts, diversityMeasurement.ts, baselineCompare.ts |
PerformanceCostBreakdown, DiagnosticsPayload, DifficultyAnalysis, TradeoffProfile, filtered CandidateSolution[] |
| 7. Workflow materialization | reducer promotion / save actions | Results are committed into workflow artifacts the user can keep | PROMOTE_WORKING_LAYOUT, PROMOTE_CANDIDATE, SAVE_AS_VARIANT, LOAD_SAVED_VARIANT |
new activeLayout, workingLayout, durable savedVariants |
| 8. Persistence | autosave + explicit save | Durable state is serialized and written to browser storage | useAutoSave.ts, projectStorage.ts, indexedDbStore.ts, projectSerializer.ts |
persisted ProjectState snapshot |
This is the fast, always-available inspection path.
- UI changes mark the project
analysisStale. useAutoAnalysis()debounces a rerun.- The hook builds a displayed
Layout, activePerformance, and projected solver constraints. createBeamSolver()runs a single-candidate beam search.- The returned
ExecutionPlanResultis wrapped into aCandidateSolutionwithDifficultyAnalysisandTradeoffProfile. - The reducer stores it in
analysisResult, which drives the grid, timeline, costs panel, and summaries.
This is the exploration path for alternative layouts.
- The toolbar dispatches
generateFull(). - The selected optimizer method decides the branch:
greedy->generateGreedyCandidates()beam/annealing->generateCandidates()
- Candidate generation produces one or more
Layoutseeds. - A solver/optimizer computes assignments and normalized execution plans.
- PushFlow re-evaluates each result with
evaluatePerformance(). - Difficulty scoring, tradeoff scoring, baseline diffing, and duplicate filtering produce the final
CandidateSolution[]. - The reducer stores candidates and selects one for inspection.
This is the authoring/testing path for creating playable material, not just importing it.
- Composer and loop-editor surfaces create pattern/preset data.
- Rudiment and pattern utilities convert those structures into event timelines.
- Those events feed the exact same
Performance-> solver -> evaluation -> candidate pipeline as imported MIDI.
| Artifact | Type definition | Created by | Consumed by |
|---|---|---|---|
| Sound identity | SoundStream, Voice |
MIDI import, project load, composer preset placement | grid, sounds panel, layout models, solver mapping |
| Performance timeline | Performance, PerformanceEvent, PerformanceMoment |
import, composer generation, buildPerformanceMoments() |
solvers, evaluator, timeline UI |
| Static pad mapping | Layout |
import bootstrap, manual editing, seeding, optimization, workflow promotion | grid, candidate previews, solver mapping, persistence |
| Execution artifact | ExecutionPlanResult |
beam / annealing / greedy solver paths | timeline, costs panel, summaries, compare surfaces |
| Canonical scoring artifact | PerformanceCostBreakdown |
evaluatePerformance() |
diagnostics, ranking, manual cost calculation |
| User-facing candidate | CandidateSolution |
auto-analysis wrapper or candidate generation | layouts panel, compare mode, promotion flow |
| Workflow state | ProjectState |
serializer + reducer | the entire UI shell and persistence layer |
PushFlow does not call remote AI/LLM services. The "models" in this repo are local computational models:
| Model | Purpose | Primary code |
|---|---|---|
| MIDI parse model | turn .mid bytes into canonical note events |
@tonejs/midi, src/import/midiImport.ts |
| Biomechanical feasibility model | reject impossible grips and pad combinations | src/engine/prior/feasibility.ts, src/engine/prior/biomechanicalModel.ts |
| Hand-zone / surface model | reason about left/right zones and pad distances | src/engine/surface/handZone.ts, src/engine/surface/padGrid.ts |
| Beam search model | compute hand/finger assignments for a fixed layout | src/engine/solvers/beamSolver.ts |
| Annealing model | globally optimize layouts and assignments | src/engine/optimization/annealingSolver.ts |
| Greedy hill-climb model | interpretable local optimization with move traces | src/engine/optimization/greedyOptimizer.ts |
| Canonical evaluation model | compute cost dimensions for events, transitions, and full performances | src/engine/evaluation/canonicalEvaluator.ts, src/engine/evaluation/costFunction.ts |
| Difficulty / ranking model | turn raw plans into user-facing candidate scores and tradeoffs | src/engine/evaluation/difficultyScoring.ts, src/engine/optimization/candidateRanker.ts |
| Diversity / baseline compare model | keep generated candidates meaningfully different from the baseline | src/engine/analysis/diversityMeasurement.ts, src/engine/analysis/baselineCompare.ts |
The intended artifact progression is:
MIDI / Composer Input
-> SoundStream[] + Performance
-> Layout
-> ExecutionPlanResult
-> PerformanceCostBreakdown + DiagnosticsPayload
-> CandidateSolution
-> Active Layout / Working Layout / Saved Variant
That progression is the core contract of the product: PushFlow starts with musical material, creates a pad mapping, evaluates whether that mapping is physically playable, and then turns the best outcomes into durable workflow artifacts the user can inspect, promote, save, or discard.
- Go to your repo Settings > Pages
- Under Source, select GitHub Actions (not "Deploy from a branch")
- Merge the PWA branch to
main— the workflow will auto-build and deploy - Visit
https://tgalloway1.github.io/pushflow-modified/
Once deployed, open the site in Chrome or Edge and click the install icon (in the address bar or browser menu). PushFlow will appear as a standalone app in your dock/taskbar — no browser chrome, no terminal.
The current PWA icons (public/pwa-192x192.png, public/pwa-512x512.png) are auto-generated placeholders. Replace them with proper designed icons when ready. The favicon (public/favicon.svg) can also be updated.
- Node.js 18+
- npm
npm install # Install dependencies
npm run dev # Dev server at http://localhost:5173
npm run build # TypeScript check + Vite production build
npm run typecheck # TypeScript type checking only
npm test # Run Vitest in watch mode
npm run test:run # Run tests once (CI mode)
npm run test:coverage # Run tests with coverage reportThe test suite validates critical invariants:
- Sound Identity Round-Trip — IDs survive clone, promote, variant save, and discard
- Layout State Transitions — Active/Working/Variant lifecycle correctness
- Execution Plan Binding — Plans are layout-bound, staleness detection works
- Baseline Compare — Candidate comparison produces correct diffs
- Event Explainer — Per-event difficulty explanations are accurate
- Constraint Explanation — Constraint violations produce meaningful diagnostics
- Solver Determinism — Same input produces consistent output
- Feasibility Boundaries — Strict feasibility boundaries are correct
- Candidate Diversity — Generated candidates differ meaningfully from baseline
PushFlow's product canon lives in docs/canonical/:
PUSHFLOW_CANON.md— Product canon (workflow and state truths)PUSHFLOW_ENGINE_CONTRACT.md— Workflow-facing engine contractPUSHFLOW_SURFACE_FEATURES.md— Concrete feature specs per surfacePUSHFLOW_TERMINOLOGY.md— Stable term definitions
These four files are the only planning source of truth.
Private — All rights reserved.


