Transporte: al cargar un track el loop arranca desde cero
Some checks are pending
Build and Deploy to warm (strudel.cc) / build (push) Waiting to run
Strudel tests / build (20) (push) Waiting to run

Cargar un proyecto (pestaña Projects o anterior/siguiente de la barra) deja
pendiente un reinicio que se aplica en la siguiente evaluacion: el tema nuevo
empieza en el ciclo 0 aunque el reloj del scheduler siga corriendo. Editar y
actualizar en vivo no reinicia nada. Funciona con Cyclist y NeoCyclist (ambos
tienen setCycle).

README-HACKLAB mas breve y con enlaces: upstream, TidalCycles, algorave,
librerias de samples y video introductorio.
This commit is contained in:
hacklab 2026-07-17 02:00:08 +02:00
parent b6dbe97a62
commit 921647020c
4 changed files with 53 additions and 42 deletions

View file

@ -1,81 +1,81 @@
# Strudel · fork del hacklab # Strudel · fork del hacklab
Fork de [Strudel](https://codeberg.org/uzu/strudel) (live coding de música en Fork de [Strudel](https://codeberg.org/uzu/strudel) (live coding de música en
el navegador, port de TidalCycles a JavaScript) con mejoras de usabilidad el navegador, port de TidalCycles a JavaScript) con mejoras de usabilidad para
pensadas para pinchar en directo y para gente que empieza. pinchar en directo. Misma licencia que upstream: AGPL-3.0 (ver `LICENSE`).
Upstream: <https://codeberg.org/uzu/strudel> · Licencia: AGPL-3.0 (la misma, ## Qué añade
ver `LICENSE`). Gracias al equipo de Strudel/TidalCycles por la base ♥
## Qué añade este fork - Interfaz multiidioma (castellano, inglés, francés, alemán, italiano,
portugués y más). Selector en el header.
- **Interfaz multiidioma** (`website/src/i18n.mjs`): castellano, inglés, - Pestaña Projects: proyectos locales con carpetas, buscador y una librería
francés, alemán, italiano, portugués y más. Se cambia desde el selector del de tracks de fábrica para empezar a pinchar.
header y se guarda en los ajustes. - Barra de transporte bajo el editor: anterior / play-pausa / siguiente,
- **Pestaña Projects**: gestor de proyectos locales (localStorage) con tiempo y barra de progreso arrastrable (seek real sobre el reloj del
carpetas, buscador y una librería de tracks de fábrica lista para pinchar scheduler). Al cargar un track el loop arranca desde cero; editar y
(bases techno/acid originales, de la casa). actualizar en vivo no reinicia nada.
- **Barra de transporte** estilo reproductor bajo el editor: anterior / - Pestaña Guía: chuleta de síntesis y sonidos.
play-pausa / siguiente, tiempo transcurrido y **barra de progreso - Packs de temas del editor: neon-madrid y hacklab-pack.
arrastrable** (seek real: se añadió `Cyclist.setCycle()` en - Lanzador de escritorio: `strudel-launcher.sh`.
`packages/core`).
- **Pestaña Guía**: chuleta de síntesis y sonidos para tener a mano mientras
se pincha.
- **Packs de temas** para el editor (`neon-madrid` + `hacklab-pack`: 5 neón
sobre negro y 5 estándar).
- **Lanzador de escritorio** (`strudel-launcher.sh`): sirve la build estática
en local y abre el navegador. Ideal para un icono clicable.
## Instalación (Linux) ## Instalación (Linux)
Requisitos: `git`, `node` >= 20 y `pnpm` (`corepack enable` lo activa). Requisitos: `git`, `node` >= 20 y `pnpm` (`corepack enable`).
```bash ```bash
git clone https://gitea.laenre.net/hacklab/strudel.git git clone https://gitea.laenre.net/hacklab/strudel.git
cd strudel cd strudel
pnpm i # instala las dependencias del monorepo pnpm i
``` ```
### Modo desarrollo (recarga en vivo) Desarrollo (recarga en vivo):
```bash ```bash
pnpm dev # abre http://localhost:4321 pnpm dev # http://localhost:4321
``` ```
### Modo "aparato" (build estática + lanzador) Uso diario (build estática + lanzador):
```bash ```bash
pnpm build # genera la web estática pnpm build
./strudel-launcher.sh # la sirve en http://127.0.0.1:3009 y abre el navegador ./strudel-launcher.sh # sirve http://127.0.0.1:3009 y abre el navegador
``` ```
Para el icono de escritorio, crea `~/.local/share/applications/strudel.desktop`: Icono de escritorio: `~/.local/share/applications/strudel.desktop`
```ini ```ini
[Desktop Entry] [Desktop Entry]
Type=Application Type=Application
Name=Strudel Name=Strudel
Comment=Live coding de música Comment=Live coding de musica
Exec=/ruta/a/strudel/strudel-launcher.sh Exec=/ruta/a/strudel/strudel-launcher.sh
Icon=audio-x-generic Icon=audio-x-generic
Terminal=false Terminal=false
Categories=AudioVideo;Audio; Categories=AudioVideo;Audio;
``` ```
## Añadir tus propios packs de proyectos ## Packs de proyectos propios
La librería de fábrica vive en La librería de fábrica está en
`website/src/repl/components/panel/default-projects.mjs`. Para un pack `website/src/repl/components/panel/default-projects.mjs`. Para un pack
personal sin tocar el repo, crea un módulo local con el mismo formato personal crea un módulo con el mismo formato `{name, folder, code}` y
`{name, folder, code}` y concaténalo en `DEFAULT_PROJECTS` (los packs concaténalo en `DEFAULT_PROJECTS` (los packs personales están en
personales están en `.gitignore`). También puedes crear y organizar proyectos `.gitignore`). También se pueden crear proyectos desde la propia pestaña.
directamente desde la pestaña Projects de la interfaz.
## Mantenerse al día con upstream ## Actualizar desde upstream
El remoto `upstream` apunta al Strudel original:
```bash ```bash
git fetch upstream git fetch upstream
git merge upstream/main # y resolver conflictos si los hay git merge upstream/main
``` ```
## Enlaces
- Strudel (upstream): <https://codeberg.org/uzu/strudel>
- Documentación / tutorial: <https://strudel.cc/learn>
- TidalCycles, el original en Haskell: <https://tidalcycles.org>
- Algorave, la escena: <https://algorave.com>
- Samples para empezar a jugar:
- Dirt-Samples (la librería clásica de Tidal): <https://github.com/tidalcycles/Dirt-Samples>
- tidal-drum-machines (Roland TR-808/909 y muchas más): <https://github.com/ritchse/tidal-drum-machines>
- Para ver de qué va esto en directo: <https://www.youtube.com/watch?v=iu5rnQkfO6M>

View file

@ -2,7 +2,7 @@ import { useStore } from '@nanostores/react';
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import cx from '@src/cx.mjs'; import cx from '@src/cx.mjs';
import { useT } from '@src/i18n.mjs'; import { useT } from '@src/i18n.mjs';
import { $projects, $playing, parseMeta, fmt } from './panel/ProjectsTab.jsx'; import { $projects, $playing, $resetPending, parseMeta, fmt } from './panel/ProjectsTab.jsx';
// Barra de transporte estilo Spotify, FIJA debajo del editor de código: // Barra de transporte estilo Spotify, FIJA debajo del editor de código:
// · fila superior: controles CENTRADOS (anterior / play-pausa / siguiente) // · fila superior: controles CENTRADOS (anterior / play-pausa / siguiente)
@ -98,6 +98,7 @@ export function TransportBar({ context }) {
const p = displayed[idx]; const p = displayed[idx];
e.setCode(p.code); e.setCode(p.code);
$playing.set(p.name); $playing.set(p.name);
$resetPending.set(true); // el track nuevo arranca desde el ciclo 0
e.evaluate(); e.evaluate();
}; };

View file

@ -64,6 +64,9 @@ const migrate = (list) => {
export const $projects = atom(migrate(load())); export const $projects = atom(migrate(load()));
// nombre del sample cargado en el editor (compartido con la barra de transporte) // nombre del sample cargado en el editor (compartido con la barra de transporte)
export const $playing = atom(null); export const $playing = atom(null);
// true = se acaba de cargar un track: la proxima evaluacion arranca el loop
// desde el ciclo 0 (editar y actualizar en vivo no pasa por aqui)
export const $resetPending = atom(false);
const persist = (list) => { const persist = (list) => {
try { try {
@ -261,6 +264,7 @@ export function ProjectsTab({ context }) {
ed.setCode(p.code); ed.setCode(p.code);
setPreviewed(p); setPreviewed(p);
$playing.set(p.name); $playing.set(p.name);
$resetPending.set(true);
}; };
const del = (p) => { const del = (p) => {

View file

@ -35,6 +35,7 @@ import { audioEngineTargets } from '../settings.mjs';
import { useStore } from '@nanostores/react'; import { useStore } from '@nanostores/react';
import { prebake } from './prebake.mjs'; import { prebake } from './prebake.mjs';
import { getRandomTune, initCode, loadModules, shareCode } from './util.mjs'; import { getRandomTune, initCode, loadModules, shareCode } from './util.mjs';
import { $resetPending } from './components/panel/ProjectsTab.jsx';
import './Repl.css'; import './Repl.css';
import { setInterval, clearInterval } from 'worker-timers'; import { setInterval, clearInterval } from 'worker-timers';
import { getMetadata } from '../metadata_parser'; import { getMetadata } from '../metadata_parser';
@ -104,6 +105,11 @@ export function useReplContext() {
beforeEval: () => audioReady, beforeEval: () => audioReady,
afterEval: (all) => { afterEval: (all) => {
const { code } = all; const { code } = all;
// track recien cargado desde Projects: el loop arranca desde cero
if ($resetPending.get()) {
$resetPending.set(false);
editorRef.current?.repl?.scheduler?.setCycle?.(0);
}
//post to iframe parent (like Udels) if it exists... //post to iframe parent (like Udels) if it exists...
window.parent?.postMessage(code); window.parent?.postMessage(code);