Transporte: al cargar un track el loop arranca desde cero
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:
parent
b6dbe97a62
commit
921647020c
4 changed files with 53 additions and 42 deletions
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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) => {
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue