Skip to content
Open
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 sdk/drafts/threshold-slices/CHANGELOG.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[
{
"version": "0.1.0-experimental",
"date": "2026-11-07",
"changes": [
"AED SLICE gains CREATE THRESHOLD SLICES, under DELETE ALL SLICES. With Beat Slicer it sits under CREATE BEAT GRID.",
"Turn the threshold and the gap. A held note stays one slice, a short moment before the hit.",
"YES asks the usual CREATE SLICES or REPLACE SLICES question. NO leaves the slices alone.",
"Press LEVEL on the waveform to add one slice at the marker, or D to remove only the slice under it. The marker stays, and the other slices stay.",
"Known issue: needs a base that can read the waveform. A long sample still takes a while to scan."
]
}
]
22 changes: 22 additions & 0 deletions sdk/drafts/threshold-slices/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
MIT License

Copyright (c) 2026 jonathanwardle1992-beep
Copyright (c) 2026 twwwy and contributors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
57 changes: 57 additions & 0 deletions sdk/drafts/threshold-slices/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Threshold Slices

Version: `0.1.0-experimental` · author: @jonathanwardle1992-beep · draft.

## Overview

CREATE THRESHOLD SLICES chops the current trim where the level rises. It is on the audio editor SLICE menu, under DELETE ALL SLICES and above CREATE SLICE GRID. With Beat Slicer installed, this row sits under CREATE BEAT GRID.

A slice starts about 2 ms before a rise through the threshold and ends at the next start. A held note stays one slice. The last slice ends at the trim. The first start stays at the trim, so the audio before the first hit is still a slice.

## Controls

Select the sample, open AED, press AMP for SLICE, then YES, and choose CREATE THRESHOLD SLICES.

| Control | Behavior |
| --- | --- |
| Encoder A | Threshold, 10 to 100, as a percent of the loudest point in the trim. Higher keeps fewer hits. |
| Encoder B | Minimum gap: 10, 20, 50, 100, 200 or 400 ms. |
| Encoder C | L+R uses the louder side. LEFT and RIGHT use that side only. |
| LEVEL, pressed | With the waveform open on TRIM, SLICE or EDIT, adds one slice at the marker and leaves the marker on that point. The slices already there stay. The press does nothing while the editor is closed. |
| Encoder D, pressed | With the waveform open on TRIM, SLICE or EDIT, removes only the slice that contains the marker. The others keep their starts, ends and loops, and the marker stays on that point. Nothing happens when the marker is outside every slice, or when fewer than two slices exist. |
| YES | When the scan has finished, opens the stock CREATE SLICES? or REPLACE SLICES? question. Markers change only after that second YES. |
| NO | Close the popup. Nothing is written. |

The right side shows the scan as a percent, then CREATE or REPLACE, or EMPTY, SHORT, OVER64, CHECK, WAIT, REFUSED or NO READ. EMPTY means no rise cleared the threshold. OVER64 means the result would pass 64 slices; raise the threshold or the gap. NO READ means this base cannot hand the module the waveform yet.

## Usage

1. Stop recording into the buffer you are editing. Playback of the pattern can continue.
2. Open the popup and set the threshold and the gap. A drum loop often starts well at 40 and 50 ms.
3. Press YES, then YES at the stock question. Play the slices with slice mode on. DELETE SLICE and DELETE ALL SLICES clear them the usual way.

### Quick tutorial: chop a loop on its hits

1. Load a Flex or Static loop, open AED, press AMP for SLICE, then YES, and choose CREATE THRESHOLD SLICES.
2. Leave the threshold at 40 and the gap at 50 ms. Wait until the right side says CREATE or REPLACE and shows a slice count.
3. Press YES, then YES at the stock question. Trigger the slices. Press NO at the question if you want the old slices left alone.

## Compatibility and limitations

- OS 1.40C and a Modwerk base whose sample editor can copy peaks. The module installs beside Beat Slicer. Card firmware cannot include it.
- At most 64 slices. A result past that is refused.
- Replacing slices can change the slice numbers parameter locks point at, on every track that uses the sample.
- A start on a rise can still click. Nothing is saved on its own.
- Not yet tried on a unit, on the MKI, or after a project save and a power cycle.

## Tests and measurements

Host scan of synthetic peaks: `python -m unittest sdk.tests.test_threshold_slices`. The base host fixtures cover the peak read. No cycle measurement and no unit trial yet. See [TESTING.md](TESTING.md).

## Authorship and licences

Original module by @jonathanwardle1992-beep, MIT ([LICENSE](LICENSE)). The popup lettering is adapted from Beat Slicer's device font by @twwwy, MIT. No Elektron firmware, extracted routines or tables, factory samples, memory dumps or built firmware images are included.

## Screens and audio

No screen capture yet. The popup is drawn on the unit; the pictures belong with the first trial.
37 changes: 37 additions & 0 deletions sdk/drafts/threshold-slices/TESTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Threshold Slices testing

0.1.0-experimental. The host scan passed. A 12-second USB try was rolled back without opening the editor. The author reports that pressing D on the waveform removes only the slice under the marker.

## Commands and exact revision

The host scan and the 12-second USB try below are commit `f96425206d5b209701cb0d4c05c7a03084612dee`. Pressing D to remove a slice came after that and was not part of those runs.

- `python -m unittest sdk.tests.test_threshold_slices` — PASS, 2026-11-06, host gcc. Spikes, silence, 64-slice refusal, a held rise and a short trim.
- The sample-editor host fixtures (service, board, menu, actions) were compiled with the same gcc and each printed PASS, including the new peak read.
- Host scan again on 2026-11-07, WSL gcc (`-std=c99 -Wall -Wextra -Werror`): PASS, including one rectified tone burst staying two starts (trim start plus one hit). Windows `python -m unittest` skipped because this machine has no `cc`.

Private ColdFire package `C:\Users\Roy Walker\modwerk-builds\threshold-slices.mwrm`, SHA-256 `a020a947cfdad3ad42b3e5566ec78e88a1e769942926f20fd897e6e9b145d3e7`, subscribed at order 60. Not committed.

On the unit, 2026-11-07, OCTATRACK 1.40C, base `671f0afd406290cb`: `device try` of that package for 12 seconds, then rolled back (not kept). While it ran, the slot was running, refusals stayed 0, and the tick value stayed `0x54530000` (the editor was not opened). About 60 ticks a second. After rollback the unit was normal, generation 2, the same base, and no slot running. The menu, LEVEL insert, scan time and CPU were not tried.

Reported by the author, 8 November 2026: with the waveform open, pressing D removed only the slice under the marker. The other slices stayed, and the result matched the description. No package hash was recorded for that load.

## Hardware and audio quality

Not tested. No MKI or MKII run. No audio capture.

## Stock flows

Not tested. The menu row is added in the creation group under DELETE ALL SLICES and above CREATE SLICE GRID. Stock rows, their callbacks and the remembered stock index are left to the base. DELETE SLICE and DELETE ALL SLICES are unchanged. Not compared on a unit.

## Performance

Not measured. The scan runs on the UI task while the popup is open, at most 8 reads of 32 frames a tick. Closed, the module does not read.

## Resources

Unmeasured.

## Hardware

The author's report of the D gesture is above. No audio capture. The menu, scan time and CPU were not tried.
105 changes: 105 additions & 0 deletions sdk/drafts/threshold-slices/device_font.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/* SPDX-License-Identifier: MIT */
/* Popup lettering adapted from Beat Slicer's device font (MIT, twwwy). */
#include "device_font.h"
static const unsigned char letters[26][7]={
{14,17,17,31,17,17,17},{30,17,17,30,17,17,30},{14,17,16,16,16,17,14},
{30,17,17,17,17,17,30},{31,16,16,30,16,16,31},{31,16,16,30,16,16,16},
{14,17,16,23,17,17,14},{17,17,17,31,17,17,17},{31,4,4,4,4,4,31},
{1,1,1,1,17,17,14},{17,18,20,24,20,18,17},{16,16,16,16,16,16,31},
{17,27,21,21,17,17,17},{17,25,21,19,17,17,17},{14,17,17,17,17,17,14},
{30,17,17,30,16,16,16},{14,17,17,17,21,18,13},{30,17,17,30,20,18,17},
{15,16,16,14,1,1,30},{31,4,4,4,4,4,4},{17,17,17,17,17,17,14},
{17,17,17,17,17,10,4},{17,17,17,21,21,21,10},{17,17,10,4,10,17,17},
{17,17,10,4,4,4,4},{31,1,2,4,8,16,31}
};
static const unsigned char digits[10][7]={
{14,17,19,21,25,17,14},{4,12,4,4,4,4,14},{14,17,1,2,4,8,31},
{30,1,1,14,1,1,30},{2,6,10,18,31,2,2},{31,16,16,30,1,1,30},
{14,16,16,30,17,17,14},{31,1,2,4,8,8,8},{14,17,17,14,17,17,14},
{14,17,17,15,1,1,14}
};
void ts_device_text_at(unsigned char *frame,uint32_t x,uint32_t y,const char *s){
if(!frame || !s || y>57)return;
for(;*s && x+5<=124;x+=6,++s){
const unsigned char *glyph=0;
if(*s>='A' && *s<='Z')glyph=letters[(unsigned)*s-'A'];
if(*s>='0' && *s<='9')glyph=digits[(unsigned)*s-'0'];
for(uint32_t row=0;row<7;++row){
uint32_t bits=glyph?glyph[row]:0;
if(*s==':' && (row==2 || row==5))bits=4;
if(*s=='.' && row==6)bits=4;
if(*s=='-' && row==3)bits=14;
if(*s=='/')bits=1u<<(row*4u/6u);
for(uint32_t column=0;column<5;++column)if(bits&(16u>>column))
frame[(x+column)*8u+7u-(y+row)/8u]|=(unsigned char)(1u<<((y+row)%8u));
}
}
}

static void pixel(unsigned char *frame,uint32_t x,uint32_t y,unsigned invert)
{
if(x>=128 || y>=64)return;
unsigned char *p=&frame[x*8u+7u-y/8u],bit=(unsigned char)(1u<<(y%8u));
if(invert)*p^=bit;else *p|=bit;
}
void ts_device_clear(unsigned char *frame,uint32_t x,uint32_t y,uint32_t w,uint32_t h)
{
if(!frame || !w || !h || x>=128 || y>=64 || w>128-x || h>64-y)return;
/* Clear packed column bytes, rather than revisiting a byte for each pixel. */
uint32_t last=y+h-1u;
for(uint32_t band=y/8u;band<=last/8u;++band) {
uint32_t low=band==y/8u?y%8u:0u,high=band==last/8u?last%8u:7u;
unsigned char keep=(unsigned char)~((255u<<low)&(255u>>(7u-high)));
for(uint32_t column=x;column<x+w;++column)frame[column*8u+7u-band]&=keep;
}
}
/* Original compact lettering for the instrument-sized popup fields. */
void ts_device_small_text(unsigned char *frame,uint32_t x,uint32_t y,const char *s)
{
static const unsigned char alpha[26][5]={
{2,5,7,5,5},{6,5,6,5,6},{3,4,4,4,3},{6,5,5,5,6},
{7,4,6,4,7},{7,4,6,4,4},{3,4,5,5,3},{5,5,7,5,5},
{7,2,2,2,7},{1,1,1,5,2},{5,5,6,5,5},{4,4,4,4,7},
{5,7,7,5,5},{5,7,7,7,5},{2,5,5,5,2},{6,5,6,4,4},
{2,5,5,3,1},{6,5,6,5,5},{3,4,2,1,6},{7,2,2,2,2},
{5,5,5,5,7},{5,5,5,5,2},{5,5,7,7,5},{5,5,2,5,5},
{5,5,2,2,2},{7,1,2,4,7}
};
static const unsigned char digit[10][5]={
{7,5,5,5,7},{2,6,2,2,7},{6,1,7,4,7},{6,1,2,1,6},
{5,5,7,1,1},{7,4,6,1,6},{3,4,7,5,7},{7,1,2,2,2},
{7,5,7,5,7},{7,5,7,1,6}
};
if(!frame || !s || y>59)return;
for(;*s && x+3<=124;x+=4,++s) {
const unsigned char *glyph=0;
if(*s>='A' && *s<='Z')glyph=alpha[(unsigned)*s-'A'];
if(*s>='0' && *s<='9')glyph=digit[(unsigned)*s-'0'];
for(uint32_t row=0;row<5;++row) {
uint32_t bits=glyph?glyph[row]:0;
if(*s==':' && (row==1 || row==3))bits=2;
if(*s=='.' && row==4)bits=2;
if(*s=='-' && row==2)bits=7;
if(*s=='/')bits=1u<<(row/2u);
for(uint32_t column=0;column<3;++column)
if(bits&(4u>>column))pixel(frame,x+column,y+row,0);
}
}
}
void ts_device_box(unsigned char *frame,uint32_t x,uint32_t y,uint32_t w,uint32_t h)
{
if(!frame || !w || !h || x>=128 || y>=64 || w>128-x || h>64-y)return;
for(uint32_t i=x;i<x+w;++i){pixel(frame,i,y,0);pixel(frame,i,y+h-1,0);}
for(uint32_t i=y;i<y+h;++i){pixel(frame,x,i,0);pixel(frame,x+w-1,i,0);}
}
void ts_device_dashed_box(unsigned char *frame,uint32_t x,uint32_t y,uint32_t w,uint32_t h)
{
if(!frame || !w || !h || x>=128 || y>=64 || w>128-x || h>64-y)return;
for(uint32_t i=0;i<w;++i)if(i%4u<2u){pixel(frame,x+i,y,0);pixel(frame,x+i,y+h-1u,0);}
for(uint32_t i=0;i<h;++i)if(i%4u<2u){pixel(frame,x,y+i,0);pixel(frame,x+w-1u,y+i,0);}
}
void ts_device_invert(unsigned char *frame,uint32_t x,uint32_t y,uint32_t w,uint32_t h)
{
if(!frame || x>=128 || y>=64 || w>128-x || h>64-y)return;
for(uint32_t i=x;i<x+w;++i)for(uint32_t j=y;j<y+h;++j)pixel(frame,i,j,1);
}
13 changes: 13 additions & 0 deletions sdk/drafts/threshold-slices/device_font.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/* SPDX-License-Identifier: MIT
* Popup lettering adapted from Beat Slicer's device font (MIT, twwwy).
*/
#ifndef TS_DEVICE_FONT_H
#define TS_DEVICE_FONT_H
#include <stdint.h>
void ts_device_text_at(unsigned char *, uint32_t, uint32_t, const char *);
void ts_device_box(unsigned char *, uint32_t, uint32_t, uint32_t, uint32_t);
void ts_device_invert(unsigned char *, uint32_t, uint32_t, uint32_t, uint32_t);
void ts_device_small_text(unsigned char *, uint32_t, uint32_t, const char *);
void ts_device_clear(unsigned char *, uint32_t, uint32_t, uint32_t, uint32_t);
void ts_device_dashed_box(unsigned char *, uint32_t, uint32_t, uint32_t, uint32_t);
#endif
Loading
Loading