# ReaSet Changelog

---

# English

---

## v2.1.1 — Playback & Native Loop Fixes
*March 27, 2026*

### Bug fixes

- **Audio glitch at region end with auto-stop enabled** — When region A's endpoint matched region B's startpoint, the last ~200ms of A would play back 2–3 times before REAPER processed the stop. Root cause: the transport tick (~33ms) fired the end-of-region command multiple times before receiving a response. Fixed with three coordinated changes:
  - **ID-based deduplication** (`_lastRegionEndTrigger`): the trigger fires only once per region and re-arms when the playhead re-enters the region.
  - **Time-based lock** (`_endOfRegionLocked`, 400ms): blocks re-triggers while REAPER processes the async stop. Applied across all end-of-region paths: `autoStop`, `shouldPlayNext`, and `stopAfter`.
  - **Tighter detection window** (`timeRem > -0.1` instead of `> -1`): eliminates false positives when the cursor briefly lands just past the region endpoint.

- **`delayAfter` path missing lock protection** — The `delayAfter` override sent a STOP but did not activate `_endOfRegionLocked`, allowing re-triggers during the configured pause when the next region was adjacent. Fixed with the same lock logic as the other paths.

- **Native REAPER loop was incorrectly cancelled** — `_reaperNativeLoopOff()` was called unconditionally before checking `activeRegion.loop`, tearing down the native loop (Repeat + A/B points) and falling back to the internal JS loop instead. Fixed: if `ReaSet_NativeLoop.lua`'s native loop is active for the current region (`_nativeLoopSubId === activeRegion.id`), the Lua script already handles the jump and JS no longer intervenes.

- **Stutter + stop when clicking a song with auto-stop enabled** — Race condition between the user's click XHR and a stale pending transport response. When a song item was clicked, `playRegion` dispatched `SET/POS + PLAY` immediately. If a transport response arrived shortly after showing the old cursor position near the previous song's end (chain=false), the autoStop block dispatched a `STOP` that REAPER received *after* the `PLAY`, killing the new playback. Fixed with the `_playRegionLocked` flag (500ms): set by `playRegion` on every user-initiated play, checked by the autoStop block before sending its `STOP`. The 100ms repositioning `setTimeout` is also cancelled if the flag is still active.

- **Stutter + stop persistent: `triggerMidi()` internal STOP kills user playback** — The true root cause of the bug. When the previous song's autoStop fires, its 100ms setTimeout calls `triggerMidi()` to pre-arm MIDI for the next song. `triggerMidi()` sends PLAY and schedules a STOP 100ms later. If the user clicks a song during that 100ms window, `triggerMidi()`'s internal STOP arrives at REAPER *after* the user's PLAY, killing their playback. Fixed by adding the `_playbackInitiated` flag (500ms), set exclusively by `playRegion()`, which `triggerMidi()` checks before sending its internal STOP. A separate flag from `_playRegionLocked` is required because `cueRegion()` also sets `_playRegionLocked` and legitimately needs `triggerMidi()`'s STOP to fire (to stop REAPER after the brief MIDI-arm play).

- **`cueRegion` unprotected against autoStop** — The cue function (pre-position without playing) did not set `_playRegionLocked`, so if a stale transport response arrived while the cue was in progress, the autoStop block could send a spurious `STOP` or fire its 100ms repositioning `setTimeout`, overwriting the freshly cued position or triggering a duplicate `triggerMidi`. The same `_playRegionLocked` (500ms) guard applied to `playRegion` is now also applied to `cueRegion`.

- **Boundary marker assigned to both adjacent songs** — When a marker was placed exactly at the junction where one song's endpoint coincides with the next song's startpoint, the range check `[song.start - ε, song.end + ε]` would include it in both songs, generating duplicate nested sections. Implemented `_markerBelongsToSong()` with semantic disambiguation: (1) if the marker's display name matches a candidate song's display name (case-insensitive), it belongs to that song; (2) "end" keywords (`end`, `fin`, `outro`, `salida`, `cierre`, `coda`, `fade`, etc.) → belongs to the song ending there; (3) "start" keywords (`start`, `inicio`, `intro`, `entrada`, `apertura`, `begin`, etc.) → belongs to the song starting there; (4) no keyword match → defaults to the song starting there (a boundary marker without exit-keywords most likely names the incoming section).

### New file

- **`Requirements/ReaSet_NativeLoop.lua`** — Persistent background script for REAPER. Listens to the `ReaSet/nativeLoop` ExtState every frame and, on signal, creates a visual region in the timeline, sets native A/B loop points, and enables Repeat. Detects playhead crossings and, after the configured number of passes (`loopMax`, 0 = infinite), disables Repeat and removes the region. Announces itself to ReaSet automatically via `ReaSet/nativeLoopReady=1`.

---

## v2.1 — Display, Loop & Command System Overhaul
*March 2026*

### New features

- **Real-time display filters** — New "Display Filters" section in the sidebar with independent sliders to adjust Luminance (50–150%), Contrast (50–150%), and Saturation (0–200%) for the setlist body. Values are saved automatically and restored when the app restarts.
- **Fractional loop counter** — When a section has `+LOOP:N` active, a badge shows the current pass as a fraction (e.g. `2/4`) to the left of the song action buttons, in the corresponding nested section row, and in Live View next to the section name.
- **Context menu in nested sections** — Nested section rows now have their own 3-dot menu (to the left of the name) with access to Notes and Color selection, using the same ID-based override system as songs.
- **Next section indicator in Live** — Live View shows the next nested section to the right of the active name, separated by an arrow `→`. If no section is active but there is an upcoming one, it is also shown as a preview.
- **Secondary progress bar in Live** — A second progress bar, thinner and shorter than the main one, reflects progress inside the active nested section. The fill color matches the color assigned to that section in the setlist body.
- **Command system in region names** — Region and marker names in REAPER now support a full inline command system to control behavior, appearance, and duration directly from the project, without touching the app. See command reference below.

### UI improvements

- **Higher visual contrast** — All text, borders, badges, and card backgrounds in the setlist body increased luminance and opacity by 20%. Global variables `--text-secondary` and `--text-subdued` were updated accordingly. The transport bar and app background remain unchanged.
- **Live progress bar is now white** — The main progress bar fill in Live View changed from green to pure white for better on-stage readability.
- **Drag handle and 3-dot button redesign** — The handle has greater weight and contrast. The 3-dot button is now vertical (three SVG dots), with no circular background, and a vertical divider between both elements.
- **SVG icons in context menu** — Song options menu icons (Stop, Timer, Notes, Color) replaced emojis with SVG icons consistent with the dark theme.

### Bug fixes

- **Context menu did not close when clicking outside** — The `removeEventListener` listener was executed unconditionally on every click, preventing the menu from closing after the first external interaction. Fixed.
- **`+LOOP:N` played N+1 times** — Multiple `updatePlaybackUI` ticks within the 200ms window before REAPER responded to `SET/POS` caused duplicate loop-counter increments. Fixed with a dedupe guard `_lastLoopFireKey` keyed by `sectionId:endPos`.
- **Real-time loop changes were not applied** — The `_loopInit` flag permanently blocked re-applying default loop state when detecting changes in region name. Replaced with `_loopDefault`, which tracks the last parsed value and applies changes immediately.
- **Setlists were not isolated per project** — Override and setlist data in localStorage was shared across different REAPER projects. A per-project fingerprint (djb2 hash of sorted region IDs) was implemented as a suffix for all storage keys.
- **Keyboard shortcuts blocked typing in notes** — Letter keys mapped to app functions were intercepting input while focus was in a `<textarea>`. Fixed with a `tagName` guard. Additionally, `Enter` saves and removes focus, and `Escape` only removes focus.
- **Secondary Live progress color did not match setlist color** — The bar used the region raw color; it now goes through the same `getClosestPaletteColor()` pipeline as the body, ensuring both colors are identical.

---

## v2.1 Command Reference

Commands are written directly in the region or marker name in REAPER. They can be combined in any order. The clean name left after parsing all commands is what is displayed in the app.

**Combined example:**
```
Chorus {dense pre-chorus} +LOOP:4 [green] [.bold] [1:20]
```

---

### `+` commands — Playback behavior

| Command | Description |
|---|---|
| `+PAUSE` | Pauses playback when reaching the end of the section. |
| `+SKIP` | Marks the section as skipped by default. It appears crossed out and with reduced opacity. |
| `+LOOP` | Enables infinite looping for the section. |
| `+LOOP:N` | Repeats the section exactly **N** times, then continues. The `X/N` badge is shown in real time. |
| `+LOOPFULL` | Loop with absolute priority: if a song is queued, it waits for the loop to finish before jumping. |

---

### Brackets `[]` — Appearance and duration

| Command | Description |
|---|---|
| `[color]` | Assigns a palette color to the card. See available colors below. |
| `[mm:ss]` | Overrides the displayed section duration (minutes:seconds format). |
| `[nosong]` | Excludes the item from numbering and total song count. Appears with reduced opacity. |
| `[.class]` | Applies a custom CSS class to the name. See available classes below. |

**Available colors:**
`gray` · `red` · `orange` · `amber` · `yellow` · `lime` · `green` · `emerald` · `teal` · `cyan` · `sky` · `blue` · `indigo` · `violet` · `purple` · `fuchsia` · `pink` · `rose`

**Available CSS classes:**
| Class | Effect |
|---|---|
| `.bold` | Name in extra bold (900) with adjusted tracking. |
| `.dim` | Name at 42% opacity. |
| `.italic` | Name in italic. |
| `.loud` | Name in uppercase with expanded spacing and maximum weight. |

---

### Braces `{}` — Informational text

| Command | Description |
|---|---|
| `{text}` | Displays helper text in italic next to the section name. Not shown in Live View or Canvas. Useful for production notes visible in the setlist. |

---

### Special prefixes — Markers

| Command | Description |
|---|---|
| `>` | Converts the marker into a section marker (sub-region of the active song). |
| `*` | Completely ignores the marker; it does not appear in the app. |
| `>>> DestinationName` | When the section ends, automatically jumps to the region whose name matches `DestinationName`. |

---

### Reserved words

| Name | Description |
|---|---|
| `STOP` | Stop marker. Ends playback when reached. |
| `SONG END` | Alias of `STOP`. |

---

## v2.0 — Nested regions and override system
*December 2025*

- Full support for nested regions (sub-sections) with rendering in the setlist body and Live View
- ID-based override system (`g_songOverrides`) for custom notes and colors per song and section
- Live View redesigned with song name, active region, progress bar, and transport controls
- Canvas Mode for on-stage projection
- Themed color palette with automatic mapping (`getClosestPaletteColor`)
- Grid View mode for quick navigation
- Support for special markers: `+STOP`, `+PAUSE`, `+LOOP`, `+SKIP`, `[nosong]`
- Export/Import of all app data in JSON

---

## v1.0 — First release
*August 2025*

- Web setlist manager for REAPER with WebSocket connection
- Song play/stop from the UI
- Drag & drop reordering
- Real-time playback position sync
- Multi-setlist support with localStorage persistence

# Español

---

## v2.1.1 — Correcciones de reproducción y loop nativo
*27 de marzo de 2026*

### Corrección de bugs

- **Glitch de audio al final de región con auto-stop activado** — Cuando el endpoint de la región A coincidía con el startpoint de la región B, el último ~200ms de A se repetía 2–3 veces antes de que REAPER procesara el stop. Causa raíz: el tick de transporte (cada ~33ms) disparaba el comando de fin de región múltiples veces antes de recibir respuesta. Resuelto con tres cambios coordinados:
  - **Deduplicación por ID** (`_lastRegionEndTrigger`): el trigger solo se activa una vez por región y se re-arma al volver a entrar a la región.
  - **Lock temporal** (`_endOfRegionLocked`, 400ms): bloquea re-disparos mientras REAPER procesa el stop asíncrono. Aplicado en todos los paths de fin de región: `autoStop`, `shouldPlayNext` y `stopAfter`.
  - **Ventana de detección acotada** (`timeRem > -0.1` en vez de `> -1`): elimina falsos positivos cuando el cursor queda brevemente pasado el endpoint de la región.

- **Path `delayAfter` sin lock de protección** — El override `delayAfter` enviaba el STOP pero no activaba `_endOfRegionLocked`, lo que permitía re-disparos durante la pausa configurada cuando la región siguiente era adyacente. Corregido con la misma lógica de lock que los demás paths.

- **Loop nativo de REAPER se cancelaba incorrectamente** — La llamada a `_reaperNativeLoopOff()` ocurría incondicionalmente antes de verificar `activeRegion.loop`, eliminando el loop nativo (Repeat + A/B points) y cayendo al loop JS interno en su lugar. Corregido: si el native loop de `ReaSet_NativeLoop.lua` está activo para la región actual (`_nativeLoopSubId === activeRegion.id`), el script Lua ya gestiona el jump y JS no interviene.

- **Stutter + stop al hacer click en una canción con auto-stop activado** — Race condition entre el XHR del click del usuario y una respuesta de transport pendiente con posición antigua. Al hacer click en una canción, `playRegion` despachaba `SET/POS + PLAY` inmediatamente. Si en ese mismo momento llegaba una respuesta de transport mostrando la posición anterior cerca del final de la canción previa (chain=false), el bloque autoStop despachaba un `STOP` que REAPER recibía *después* del `PLAY`, deteniendo el playback. Resuelto con el flag `_playRegionLocked` (500ms): al hacer click se activa, y el bloque autoStop lo verifica antes de enviar su STOP. También se cancela el `setTimeout` de reposicionamiento de 100ms si el flag sigue activo.

- **Stutter + stop persistente: STOP interno de `triggerMidi()` mata el playback del usuario** — Causa raíz real del bug. Cuando el autoStop de la canción anterior dispara, su setTimeout de 100ms llama a `triggerMidi()` para pre-armar el MIDI de la siguiente canción. `triggerMidi()` envía PLAY y programa un STOP 100ms después. Si el usuario hace click en una canción durante esa ventana de 100ms, el STOP interno de `triggerMidi()` llega a REAPER *después* del PLAY del usuario, deteniendo el playback. Corregido añadiendo el flag `_playbackInitiated` (500ms) seteado exclusivamente por `playRegion()`, que `triggerMidi()` verifica antes de enviar su STOP interno. Se usa un flag separado de `_playRegionLocked` porque `cueRegion()` también setea ese flag y necesita que el STOP de `triggerMidi()` se ejecute normalmente (para detener el arm de MIDI tras el cue).

- **`cueRegion` sin protección contra autoStop** — La función de cue (preposicionar sin reproducir) no activaba `_playRegionLocked`, por lo que si llegaba una respuesta de transport estancada con posición antigua mientras el cue estaba en curso, el bloque autoStop podía enviar un `STOP` adicional o disparar su `setTimeout` de reposicionamiento, sobreescribiendo la posición de cue recién establecida o ejecutando un `triggerMidi` duplicado. Aplicado el mismo guard de `_playRegionLocked` (500ms) a `cueRegion`.

- **Marcador en límite entre canciones asignado a ambas** — Cuando un marcador estaba ubicado exactamente en el punto donde el endpoint de una canción coincide con el startpoint de la siguiente, el check de rango `[song.start - ε, song.end + ε]` lo incluía en las dos canciones a la vez, generando secciones duplicadas. Implementada la función `_markerBelongsToSong()` con asignación semántica: (1) si el nombre del marcador coincide con el nombre de display de una de las canciones candidatas, pertenece a esa canción; (2) palabras clave de fin (`end`, `fin`, `outro`, `salida`, `cierre`, `coda`, `fade`, etc.) → pertenece a la canción que termina ahí; (3) palabras clave de inicio (`start`, `inicio`, `intro`, `entrada`, `apertura`, `begin`, etc.) → pertenece a la canción que comienza ahí; (4) sin coincidencia de palabras clave → se asigna a la canción que comienza (por defecto, un marcador en un límite sin palabras clave de salida probablemente nombra la sección entrante).

### Nuevo archivo

- **`Requirements/ReaSet_NativeLoop.lua`** — Script de fondo persistente para REAPER. Escucha el ExtState `ReaSet/nativeLoop` cada frame y, al recibir la señal, crea una región visual en el timeline, configura los puntos A/B del loop nativo y activa Repeat. Detecta los cruces del playhead y, tras el número de repeticiones configurado (`loopMax`, 0 = infinito), desactiva Repeat y elimina la región. Se anuncia automáticamente a ReaSet via `ReaSet/nativeLoopReady=1`.

---

## v2.1 — Overhaul de display, loop y sistema de comandos
*Marzo 2026*

### Nuevas funciones

- **Filtros de pantalla en tiempo real** — Nueva sección "Display Filters" en la sidebar con sliders independientes para ajustar Luminancia (50–150%), Contraste (50–150%) y Saturación (0–200%) del cuerpo del setlist. Los valores se guardan automáticamente y se recuperan al reiniciar la app.
- **Contador de loops fraccionario** — Cuando una sección tiene `+LOOP:N` activo, aparece un badge con la vuelta actual en formato fracción (ej. `2/4`) a la izquierda de los botones de acción de la canción, en la fila de la sección anidada correspondiente, y en la vista Live junto al nombre de la sección.
- **Menú contextual en secciones anidadas** — Las filas de secciones anidadas ahora tienen su propio menú de 3 puntos (a la izquierda del nombre) con acceso a Notas y selección de Color, usando el mismo sistema de overrides por ID que las canciones.
- **Indicador de sección siguiente en Live** — La vista Live muestra la próxima sección anidada a la derecha del nombre activo, separada por una flecha `→`. Si no hay ninguna sección activa pero hay una próxima, también se muestra como anticipo.
- **Barra de progreso secundaria en Live** — Una segunda barra de progreso, más delgada y corta que la principal, refleja el avance dentro de la sección anidada activa. El color de relleno coincide con el color asignado a esa sección en el cuerpo del setlist.
- **Sistema de comandos en nombres de región** — Los nombres de regiones y marcadores en REAPER ahora admiten un sistema completo de comandos inline para controlar comportamiento, apariencia y duración directamente desde el proyecto, sin tocar la app. Ver referencia de comandos más abajo.

### Mejoras de interfaz

- **Contraste visual elevado** — Todos los textos, bordes, badges y fondos de tarjetas del cuerpo del setlist aumentaron su luminancia y opacidad en un 20%. Variables globales `--text-secondary` y `--text-subdued` actualizadas en consecuencia. La barra de transporte y el fondo de la app permanecen sin cambios.
- **Barra de progreso en Live ahora blanca** — El relleno de la barra de progreso principal en Live View cambió de verde a blanco puro para mayor legibilidad en escena.
- **Rediseño del drag handle y botón 3-dot** — El handle tiene mayor peso y contraste. El botón 3-dot ahora es vertical (tres puntos SVG), sin fondo circular, con separador vertical entre ambos elementos.
- **Iconos SVG en el menú contextual** — Los iconos del menú de opciones de canción (Stop, Timer, Notas, Color) reemplazaron a los emojis por SVGs coherentes con el tema oscuro.

### Corrección de bugs

- **Menú contextual no se cerraba al hacer click fuera** — El listener `removeEventListener` se ejecutaba incondicionalmente en cada click, lo que impedía que el menú cerrara tras la primera interacción externa. Corregido.
- **+LOOP:N reproducía N+1 veces** — Múltiples ticks de `updatePlaybackUI` dentro de la ventana de 200ms antes de que REAPER respondiera al `SET/POS` causaban un incremento duplicado del contador de loops. Resuelto con un guard de deduplicación `_lastLoopFireKey` clave por `sectionId:endPos`.
- **Cambios de loop en tiempo real no se aplicaban** — El flag `_loopInit` bloqueaba permanentemente la re-aplicación del estado por defecto del loop al detectar cambios en el nombre de la región. Reemplazado por `_loopDefault` que rastrea el último valor parseado y aplica el cambio de inmediato.
- **Setlists no aisladas por proyecto** — Los datos de overrides y setlists en localStorage se compartían entre proyectos de REAPER distintos. Implementado fingerprint por proyecto (hash djb2 de IDs de región ordenados) como sufijo de todas las claves de almacenamiento.
- **Atajos de teclado bloqueaban la escritura en notas** — Las teclas de letras asociadas a funciones de la app interceptaban el input mientras el foco estaba en un `<textarea>`. Corregido con guard por `tagName`. Adicionalmente, `Enter` guarda y quita el foco, y `Escape` solo quita el foco.
- **Color del progress secundario en Live no coincidía con el setlist** — La barra usaba el color raw de la región; ahora pasa por el mismo pipeline `getClosestPaletteColor()` que el cuerpo, garantizando que ambos colores sean idénticos.

---

## Referencia de comandos v2.1

Los comandos se escriben directamente en el nombre de la región o marcador en REAPER. Se pueden combinar en cualquier orden. El nombre limpio que queda después de parsear todos los comandos es el que se muestra en la app.

**Ejemplo combinado:**
```
Chorus {pre-coro denso} +LOOP:4 [green] [.bold] [1:20]
```

---

### Comandos `+` — Comportamiento de reproducción

| Comando | Descripción |
|---|---|
| `+PAUSE` | Pausa la reproducción al llegar al final de la sección. |
| `+SKIP` | Marca la sección como omitida por defecto. Aparece tachada y en opacidad reducida. |
| `+LOOP` | Activa el loop infinito de la sección. |
| `+LOOP:N` | Repite la sección exactamente **N** veces y luego continúa. El badge `X/N` se muestra en tiempo real. |
| `+LOOPFULL` | Loop con prioridad absoluta: si hay una canción en cola, espera a que el loop termine antes de saltar. |

---

### Corchetes `[]` — Apariencia y duración

| Comando | Descripción |
|---|---|
| `[color]` | Asigna un color de la paleta a la tarjeta. Ver colores disponibles abajo. |
| `[mm:ss]` | Sobreescribe la duración mostrada de la sección (formato minutos:segundos). |
| `[nosong]` | Excluye el elemento de la numeración y el conteo total de canciones. Aparece en opacidad reducida. |
| `[.clase]` | Aplica una clase CSS personalizada al nombre. Ver clases disponibles abajo. |

**Colores disponibles:**
`gray` · `red` · `orange` · `amber` · `yellow` · `lime` · `green` · `emerald` · `teal` · `cyan` · `sky` · `blue` · `indigo` · `violet` · `purple` · `fuchsia` · `pink` · `rose`

**Clases CSS disponibles:**
| Clase | Efecto |
|---|---|
| `.bold` | Nombre en negrita extra (900) con tracking ajustado. |
| `.dim` | Nombre al 42% de opacidad. |
| `.italic` | Nombre en cursiva. |
| `.loud` | Nombre en mayúsculas con espaciado ampliado y peso máximo. |

---

### Llaves `{}` — Texto informativo

| Comando | Descripción |
|---|---|
| `{texto}` | Muestra texto auxiliar en cursiva junto al nombre de la sección. No se muestra en Live View ni en Canvas. Útil para notas de producción visibles en el setlist. |

---

### Prefijos especiales — Marcadores

| Comando | Descripción |
|---|---|
| `>` | Convierte el marcador en un marcador de sección (sub-región de la canción activa). |
| `*` | Ignora completamente el marcador; no aparece en la app. |
| `>>> NombreDestino` | Al finalizar la sección, salta automáticamente a la región cuyo nombre coincida con `NombreDestino`. |

---

### Palabras reservadas

| Nombre | Descripción |
|---|---|
| `STOP` | Marcador de parada. Finaliza la reproducción al alcanzarlo. |
| `SONG END` | Alias de `STOP`. |

---

## v2.0 — Regiones anidadas y sistema de overrides
*Diciembre 2025*

- Soporte completo de regiones anidadas (sub-sections) con visualización en el cuerpo del setlist y la vista Live
- Sistema de overrides por ID (`g_songOverrides`) para notas y colores personalizados por canción y sección
- Vista Live rediseñada con nombre de canción, región activa, barra de progreso y controles de transporte
- Canvas Mode para proyección en escena
- Paleta de colores temática con mapeo automático (`getClosestPaletteColor`)
- Modo Grid View para navegación rápida
- Soporte de marcadores especiales: `+STOP`, `+PAUSE`, `+LOOP`, `+SKIP`, `[nosong]`
- Export/Import de todos los datos de la app en JSON

---

## v1.0 — Primera versión
*Agosto 2025*

- Setlist manager web para REAPER con conexión WebSocket
- Reproducción/parada de canciones desde la interfaz
- Reordenamiento por drag & drop
- Sincronización de posición de reproducción en tiempo real
- Soporte multi-setlist con persistencia en localStorage

---
---

