mandioca-tabs-mcp 0.2.17 → 0.2.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/index.mjs +2162 -1125
  2. package/package.json +1 -1
package/index.mjs CHANGED
@@ -12336,12 +12336,13 @@ function migrarMarcasPorGrado(guardadas, pintarGradosViejo) {
12336
12336
  }
12337
12337
  return salida;
12338
12338
  }
12339
- var DocumentoTabs;
12339
+ var CLAVE_DE_LA_SESION, DocumentoTabs;
12340
12340
  var init_documento = __esm({
12341
12341
  "packages/model/src/documento.ts"() {
12342
12342
  init_yjs();
12343
12343
  init_src();
12344
12344
  init_tipos();
12345
+ CLAVE_DE_LA_SESION = "sesion";
12345
12346
  DocumentoTabs = class {
12346
12347
  doc;
12347
12348
  cabecera;
@@ -13730,7 +13731,7 @@ var init_kit = __esm({
13730
13731
  KIT = [
13731
13732
  { indice: 0, nombre: "Bombo", midi: 36, linea: 9, cabeza: "normal", grupo: "Parches" },
13732
13733
  { indice: 1, nombre: "Caja", midi: 38, linea: 5, cabeza: "normal", grupo: "Parches" },
13733
- { indice: 2, nombre: "Aro (rimshot)", midi: 37, linea: 5, cabeza: "x", grupo: "Parches" },
13734
+ { indice: 2, nombre: "Aro (side stick)", midi: 37, linea: 5, cabeza: "x", grupo: "Parches" },
13734
13735
  { indice: 3, nombre: "Tom agudo", midi: 48, linea: 3, cabeza: "normal", grupo: "Parches" },
13735
13736
  { indice: 4, nombre: "Tom medio", midi: 45, linea: 4, cabeza: "normal", grupo: "Parches" },
13736
13737
  { indice: 5, nombre: "Tom de piso", midi: 41, linea: 7, cabeza: "normal", grupo: "Parches" },
@@ -14163,11 +14164,11 @@ var init_enBlanco = __esm({
14163
14164
 
14164
14165
  // packages/model/src/armadura.ts
14165
14166
  function armaduraEscrita(concierto, transposicion) {
14166
- if (!transposicion) return concierto;
14167
+ if (!transposicion || transposicion % 12 === 0) return concierto;
14167
14168
  return normalizar(concierto - 7 * transposicion);
14168
14169
  }
14169
14170
  function armaduraDeConcierto(escrita, transposicion) {
14170
- if (!transposicion) return escrita;
14171
+ if (!transposicion || transposicion % 12 === 0) return escrita;
14171
14172
  return normalizar(escrita + 7 * transposicion);
14172
14173
  }
14173
14174
  var normalizar;
@@ -14553,6 +14554,7 @@ function borrarNotaDeCuerda(documento, indice, cursor, cuerda) {
14553
14554
  documento.doc.transact(() => {
14554
14555
  documento.notas.delete(nota.id);
14555
14556
  if (quedan === 0) documento.figuras.get(figura.id)?.set("esSilencio", true);
14557
+ repararLigaduras(documento, [cursor.pistaId], "soltar");
14556
14558
  });
14557
14559
  return true;
14558
14560
  }
@@ -14569,6 +14571,7 @@ function escribirSilencio(documento, indice, cursor, duracion = DURACION_INICIAL
14569
14571
  id2 = figuraExistente.id;
14570
14572
  for (const nota of notas) documento.notas.delete(nota.id);
14571
14573
  documento.figuras.get(id2)?.set("esSilencio", true);
14574
+ if (notas.length > 0) repararLigaduras(documento, [cursor.pistaId], "soltar");
14572
14575
  } else {
14573
14576
  id2 = documento.agregarFigura({ ...figuraNueva(compasId, cursor, duracion), esSilencio: true });
14574
14577
  }
@@ -14749,32 +14752,61 @@ function copiar(indice, seleccion) {
14749
14752
  }
14750
14753
  const ultima = copiadas[copiadas.length - 1];
14751
14754
  const ultimaFigura = figuras[figuras.length - 1];
14755
+ const tipo = indice.pista(seleccion.pistaId)?.datos.tipo;
14752
14756
  return {
14753
14757
  figuras: copiadas,
14754
- tics: ultima && ultimaFigura ? ultima.desplazamiento + ultimaFigura.tics : 0
14758
+ tics: ultima && ultimaFigura ? ultima.desplazamiento + ultimaFigura.tics : 0,
14759
+ ...tipo ? { tipo } : {}
14755
14760
  };
14756
14761
  }
14762
+ function tipoDeNota(nota) {
14763
+ if (nota.articulacion !== null && nota.articulacion !== void 0) return "percusion";
14764
+ if (nota.altura !== null && nota.altura !== void 0) return "altura";
14765
+ return "trastes";
14766
+ }
14767
+ function porQueNoSePuedePegar(indice, cursor, portapapeles2) {
14768
+ const destino = indice.pista(cursor.pistaId)?.datos;
14769
+ if (!destino) return "Esa pista no existe.";
14770
+ const notas = portapapeles2.figuras.flatMap((f) => f.notas);
14771
+ if (notas.length === 0) return null;
14772
+ const origen = portapapeles2.tipo;
14773
+ if (origen ? origen !== destino.tipo : notas.some((n) => tipoDeNota(n) !== destino.tipo)) {
14774
+ return "Lo copiado es de otro tipo de instrumento.";
14775
+ }
14776
+ if (destino.tipo === "trastes") {
14777
+ const cuerdas = destino.afinacion.length;
14778
+ const fuera = notas.find((n) => n.cuerda < 1 || n.cuerda > cuerdas);
14779
+ if (fuera) return `Esta pista no tiene cuerda ${fuera.cuerda}.`;
14780
+ }
14781
+ return null;
14782
+ }
14757
14783
  function pegar(documento, indice, cursor, portapapeles2) {
14758
14784
  if (portapapeles2.figuras.length === 0) return false;
14759
14785
  if (!indice.pista(cursor.pistaId)) return false;
14786
+ if (porQueNoSePuedePegar(indice, cursor, portapapeles2)) return false;
14760
14787
  const destinos = [];
14761
14788
  let compas = cursor.compas;
14762
- let tiempo = cursor.tiempo;
14789
+ let exacto = cursor.tiempo;
14763
14790
  for (const figura of portapapeles2.figuras) {
14764
14791
  let maestro = indice.compasMaestro(compas);
14765
14792
  if (!maestro) return false;
14766
- while (tiempo >= ticsDeCompas(maestro.datos)) {
14767
- tiempo -= ticsDeCompas(maestro.datos);
14793
+ while (Math.round(exacto) >= ticsDeCompas(maestro.datos)) {
14794
+ exacto -= ticsDeCompas(maestro.datos);
14768
14795
  compas += 1;
14769
14796
  maestro = indice.compasMaestro(compas);
14770
14797
  if (!maestro) return false;
14771
14798
  }
14772
- destinos.push({ compas, tiempo, figura });
14773
- tiempo += ticsDeFigura({ ...figura, tupletNumerador: figura.tupletNumerador, tupletDenominador: figura.tupletDenominador });
14799
+ const tiempo = Math.round(exacto);
14800
+ exacto += ticsExactosDeFigura(figura);
14801
+ destinos.push({ compas, tiempo, fin: Math.round(exacto), figura });
14802
+ if (Math.round(exacto) > ticsDeCompas(maestro.datos)) {
14803
+ compas += 1;
14804
+ exacto = 0;
14805
+ }
14774
14806
  }
14775
14807
  const tramos = /* @__PURE__ */ new Map();
14776
14808
  for (const d of destinos) {
14777
- const fin = d.tiempo + ticsDeFigura(d.figura);
14809
+ const fin = d.fin;
14778
14810
  const tramo = tramos.get(d.compas);
14779
14811
  if (tramo) {
14780
14812
  tramo.desde = Math.min(tramo.desde, d.tiempo);
@@ -14865,7 +14897,9 @@ function duplicarUltima(documento, indice, cursor, opciones = {}) {
14865
14897
  } = anterior.datos;
14866
14898
  if (!opciones.sinAcorde && acordeSegundaTipografia !== void 0)
14867
14899
  Object.assign(golpe, { acordeSegundaTipografia });
14900
+ const tipo = indice.pista(cursor.pistaId)?.datos.tipo;
14868
14901
  const portapapeles2 = {
14902
+ ...tipo ? { tipo } : {},
14869
14903
  tics: ticsDeFigura(anterior.datos),
14870
14904
  figuras: [
14871
14905
  {
@@ -15161,14 +15195,17 @@ function ponerSilabaEnElGolpe(documento, indice, cursor, texto4, marcas) {
15161
15195
  });
15162
15196
  return true;
15163
15197
  }
15164
- function escribirLetraEnLosGolpes(documento, indice, pistaId, voz, desdeCompas, silabas2) {
15198
+ function escribirLetraEnLosGolpes(documento, indice, pistaId, voz, desdeCompas, silabas2, pentagrama = 0) {
15165
15199
  let escritas = 0;
15166
15200
  documento.doc.transact(() => {
15167
15201
  for (let compas = desdeCompas; compas < indice.cantidadDeCompases; compas++) {
15168
15202
  if (escritas >= silabas2.length) break;
15169
- const compasId = indice.compasId(pistaId, compas);
15203
+ const compasId = indice.compasId(pistaId, compas, pentagrama);
15170
15204
  for (const figura of indice.figuras(compasId, voz)) {
15171
15205
  if (escritas >= silabas2.length) break;
15206
+ const notas = indice.notas(figura.id);
15207
+ const seAtaca = !figura.datos.esSilencio && notas.length > 0 && notas.some((n) => !n.datos.ligadaAAnterior);
15208
+ if (!seAtaca) continue;
15172
15209
  const palabra = silabas2[escritas].trim();
15173
15210
  escritas += 1;
15174
15211
  if (palabra === "") continue;
@@ -16448,16 +16485,25 @@ function moverNotasDeCuerda(documento, indice, cursor, seleccion, hacia) {
16448
16485
  }
16449
16486
  documento.doc.transact(() => {
16450
16487
  for (const { id: id2, cuerda } of destinos) documento.notas.get(id2)?.set("cuerda", cuerda);
16488
+ repararLigaduras(documento, [cursor.pistaId], "soltar");
16451
16489
  });
16452
16490
  return { hecho: true, movidas: destinos.length };
16453
16491
  }
16454
16492
  var init_moverNotas = __esm({
16455
16493
  "packages/editor/src/moverNotas.ts"() {
16456
16494
  init_efectos();
16495
+ init_ligaduras();
16457
16496
  }
16458
16497
  });
16459
16498
 
16460
16499
  // packages/editor/src/atajos.ts
16500
+ function comoSeLee(combinacion) {
16501
+ const partes = combinacion.split("+");
16502
+ if (combinacion === "+") return "+";
16503
+ const ultima = partes.pop() ?? "";
16504
+ const legible = COMO_SE_LEE[ultima] ?? (ultima.length === 1 ? ultima.toUpperCase() : ultima);
16505
+ return [...partes.map((m) => MODIFICADORAS[m] ?? m), legible].join(" + ");
16506
+ }
16461
16507
  function combinacionesDeLasTablas() {
16462
16508
  return [
16463
16509
  ...Object.keys(MOVIMIENTOS_POR_TECLA),
@@ -16465,7 +16511,7 @@ function combinacionesDeLasTablas() {
16465
16511
  ...Object.keys(ACCIONES_CON_MAYUSCULAS).map(conMayusculas)
16466
16512
  ];
16467
16513
  }
16468
- var conMayusculas, COMANDOS_DE_MENU, COMANDOS_DE_AUDIO, NOMBRE_DE_LA_FIGURA, ATAJOS;
16514
+ var conMayusculas, COMANDOS_DE_MENU, COMANDOS_DE_AUDIO, NOMBRE_DE_LA_FIGURA, ATAJOS, COMO_SE_LEE, MODIFICADORAS;
16469
16515
  var init_atajos = __esm({
16470
16516
  "packages/editor/src/atajos.ts"() {
16471
16517
  init_efectos();
@@ -16473,7 +16519,7 @@ var init_atajos = __esm({
16473
16519
  init_teclasDeFabrica();
16474
16520
  init_comandos();
16475
16521
  init_teclado();
16476
- conMayusculas = (tecla) => `Shift+${tecla}`;
16522
+ conMayusculas = (tecla) => `Ctrl+Shift+${tecla}`;
16477
16523
  COMANDOS_DE_MENU = [
16478
16524
  { id: "nuevo", nombre: "Nuevo archivo\u2026", grupo: "Archivo y salida" },
16479
16525
  { id: "abrir", nombre: "Abrir\u2026", grupo: "Archivo y salida" },
@@ -16496,6 +16542,16 @@ var init_atajos = __esm({
16496
16542
  { id: "voz-3", nombre: "Escribir en la voz 3", grupo: "Escribir notas" },
16497
16543
  { id: "voz-4", nombre: "Escribir en la voz 4", grupo: "Escribir notas" },
16498
16544
  { id: "voz-siguiente", nombre: "Pasar a la voz siguiente", grupo: "Escribir notas" },
16545
+ // ★ Las dos puertas a las paletas que NO piden sostener nada (informe «Navegación de paletas
16546
+ // con teclado», 2026-11-11). Sin tecla de fábrica: el informe pide un comando reasignable,
16547
+ // y cualquier tecla libre que se eligiera acá le quitaría una a quien ya la usa para otra cosa.
16548
+ // · `paleta-con-teclado` deja ENGANCHADO el modo del Tab sostenido: para quien no puede
16549
+ // sostener teclas (temblor, una mano, teclado en pantalla). Sticky Keys de Windows sólo
16550
+ // engancha modificadores, y Tab no lo es. Se sale con Esc o con el mismo comando.
16551
+ // · `buscar-en-paletas` es la búsqueda de las siete a la vez: un combobox estándar, que es
16552
+ // además el camino principal para un lector de pantalla.
16553
+ { id: "paleta-con-teclado", nombre: "Elegir en la paleta con el teclado, sin sostener Tab", grupo: "Escribir notas" },
16554
+ { id: "buscar-en-paletas", nombre: "Buscar en las paletas\u2026", grupo: "Escribir notas" },
16499
16555
  // ── ⛔⛔ EL MENÚ DE LA LÍNEA DIVISORIA, que faltaba (2026-09-01) ───────────────
16500
16556
  //
16501
16557
  // Estas cuatro se construyeron el 2026-09-01 con su menú, su modelo y sus cuatro
@@ -16519,6 +16575,12 @@ var init_atajos = __esm({
16519
16575
  // ★ La sección (2026-09-13): el menú Compás la abre desde el cursor, y entra al catálogo el
16520
16576
  // mismo día —asignable, sin tecla de fábrica—, que es la regla de toda acción nueva.
16521
16577
  { id: "compas-seccion", nombre: "Secci\xF3n en un comp\xE1s\u2026", grupo: "Compases" },
16578
+ // ★ Repeticiones, casillas y saltos a mano (2026-11-11, función 3 de la competencia). Entran
16579
+ // el mismo día que el menú, sin tecla de fábrica y asignables. Los nombres llevan las
16580
+ // palabras que se buscan («casilla», «coda», «segno», «fine»): el buscador lee el nombre.
16581
+ { id: "compas-repeticion", nombre: "Barras de repetici\xF3n en un comp\xE1s o pasaje\u2026", grupo: "Compases" },
16582
+ { id: "compas-casillas", nombre: "Casillas de repetici\xF3n (1.\xAA, 2.\xAA\u2026)\u2026", grupo: "Compases" },
16583
+ { id: "compas-saltos", nombre: "Saltos y destinos: D.C., D.S., Segno, Coda, Fine\u2026", grupo: "Compases" },
16522
16584
  { id: "borrar-compases-desde", nombre: "Borrar de un comp\xE1s en adelante", grupo: "Compases" },
16523
16585
  { id: "borrar-compases-hasta", nombre: "Borrar de un comp\xE1s para atr\xE1s", grupo: "Compases" },
16524
16586
  // ── EL ASPECTO DE LA OBRA (2026-09-01) ────────────────────────────
@@ -16657,11 +16719,29 @@ var init_atajos = __esm({
16657
16719
  // teclado (ver `accionDe`).
16658
16720
  { id: "paleta-siguiente", nombre: "Pasar a la siguiente paleta (abre el panel de edici\xF3n si est\xE1 cerrado)", grupo: "Escribir notas", teclas: ["Tab"], reasignable: true },
16659
16721
  // ★ 2026-11-03. Tab tocado pasa de paleta; Tab SOSTENIDO en la de acordes le da las flechas.
16722
+ {
16723
+ // ★ Desde el 2026-11-11, en las siete paletas y con la misma gramática. Ver
16724
+ // `paletasConTeclado.ts`.
16725
+ id: "paleta-con-tab",
16726
+ nombre: "Con Tab sostenido en cualquier paleta: \u2190 \u2192 elegir, \u2193 o Intro escribir, \u2191 quitar, letras para buscar, Esc para salir",
16727
+ grupo: "Escribir notas",
16728
+ teclas: ["Tab + \u2190 \u2192", "Tab + \u2193", "Tab + \u2191", "Tab + letras", "Tab + Esc"],
16729
+ reasignable: false,
16730
+ porQueNo: "Va con las flechas, que marcan la direcci\xF3n; Tab se reasigna en \xABPasar a la siguiente paleta\xBB."
16731
+ },
16732
+ {
16733
+ id: "paleta-con-tab-posicion",
16734
+ nombre: "Con Tab sostenido en Figura o Matiz: 1 a 9 eligen la ficha por su lugar",
16735
+ grupo: "Escribir notas",
16736
+ teclas: ["Tab + 1 \u2026 9"],
16737
+ reasignable: false,
16738
+ porQueNo: "Los d\xEDgitos son la posici\xF3n de la ficha, como sin Tab son el traste."
16739
+ },
16660
16740
  {
16661
16741
  id: "acorde-con-tab",
16662
- nombre: "Con Tab sostenido en la paleta de acordes: \u2190 \u2192 elegir, \u2193 o Intro escribirlo (Ctrl: s\xF3lo las notas), \u2191 quitarlo",
16742
+ nombre: "Con Tab sostenido en la paleta de acordes: Ctrl + \u2193 escribe s\xF3lo las notas",
16663
16743
  grupo: "Escribir notas",
16664
- teclas: ["Tab + \u2190 \u2192", "Tab + \u2193", "Tab + Ctrl + \u2193", "Tab + \u2191"],
16744
+ teclas: ["Tab + Ctrl + \u2193"],
16665
16745
  reasignable: false,
16666
16746
  porQueNo: "Va con las flechas, que marcan la direcci\xF3n; Tab se reasigna en \xABPasar a la siguiente paleta\xBB."
16667
16747
  },
@@ -16773,7 +16853,10 @@ var init_atajos = __esm({
16773
16853
  })),
16774
16854
  // ── Compases ──────────────────────────────────────────────────────────────
16775
16855
  { id: "insertar-compas", nombre: "Insertar un comp\xE1s", grupo: "Compases", teclas: ["Ctrl+Enter"], reasignable: true },
16776
- { id: "borrar-compas", nombre: "Borrar el comp\xE1s", grupo: "Compases", teclas: ["Shift+Delete", "Shift+Backspace"], reasignable: true },
16856
+ // ⛔ (2026-11-11, C7) Decía «Shift+Delete»: la pantalla mostraba «May + Supr», y May + Supr
16857
+ // borra la NOTA (la tabla general no mira las Mayúsculas). La tecla que de verdad borra el
16858
+ // compás es Ctrl + May + Supr. Si se cambia de combinación es decisión de Daniel.
16859
+ { id: "borrar-compas", nombre: "Borrar el comp\xE1s", grupo: "Compases", teclas: ["Ctrl+Shift+Delete", "Ctrl+Shift+Backspace"], reasignable: true },
16777
16860
  // ── Reproducción ──────────────────────────────────────────────────────────
16778
16861
  { id: "reproducir", nombre: "Reproducir o pausar", grupo: "Reproducci\xF3n", teclas: [" "], reasignable: true },
16779
16862
  // ★ «Desde el principio» (2026-10-15): Mayús + Espacio, como el «Play from Start» de Guitar Pro.
@@ -16790,6 +16873,20 @@ var init_atajos = __esm({
16790
16873
  // toda la ventana— y por eso todavía no se reasigna.
16791
16874
  { id: "info", nombre: "Mostrar u ocultar la ventana de info", grupo: "Vista y pistas", teclas: ["?"], reasignable: false, porQueNo: "Responde en toda la ventana, tambi\xE9n fuera del pliego: cambiarlo es otro trabajo." }
16792
16875
  ];
16876
+ COMO_SE_LEE = {
16877
+ " ": "Espacio",
16878
+ ArrowUp: "\u2191",
16879
+ ArrowDown: "\u2193",
16880
+ ArrowLeft: "\u2190",
16881
+ ArrowRight: "\u2192",
16882
+ Delete: "Supr",
16883
+ Backspace: "Retroceso",
16884
+ Home: "Inicio",
16885
+ End: "Fin",
16886
+ Enter: "Entrar",
16887
+ Escape: "Esc"
16888
+ };
16889
+ MODIFICADORAS = { Shift: "May" };
16793
16890
  }
16794
16891
  });
16795
16892
 
@@ -16837,7 +16934,8 @@ function haceDiptongo(a, b) {
16837
16934
  if (esDebilAtona(a) && esDebilAtona(b)) return a !== b;
16838
16935
  return true;
16839
16936
  }
16840
- function silabas(palabra, idioma = "es") {
16937
+ function silabas(palabraTalCual, idioma = "es") {
16938
+ const palabra = palabraTalCual.normalize("NFC");
16841
16939
  if (!palabra) return [];
16842
16940
  if (idioma !== "es") return [palabra];
16843
16941
  const unidades = enUnidades(palabra);
@@ -16850,8 +16948,16 @@ function silabas(palabra, idioma = "es") {
16850
16948
  continue;
16851
16949
  }
16852
16950
  let fin = i2;
16853
- while (fin + 1 < unidades.length && unidades[fin + 1].vocal && haceDiptongo(unidades[fin].texto.toLowerCase(), unidades[fin + 1].texto.toLowerCase())) {
16854
- fin++;
16951
+ for (; ; ) {
16952
+ if (fin + 1 < unidades.length && unidades[fin + 1].vocal && haceDiptongo(unidades[fin].texto.toLowerCase(), unidades[fin + 1].texto.toLowerCase())) {
16953
+ fin++;
16954
+ continue;
16955
+ }
16956
+ if (fin + 2 < unidades.length && unidades[fin + 1].texto.toLowerCase() === "h" && unidades[fin + 2].vocal && haceDiptongo(unidades[fin].texto.toLowerCase(), unidades[fin + 2].texto.toLowerCase())) {
16957
+ fin += 2;
16958
+ continue;
16959
+ }
16960
+ break;
16855
16961
  }
16856
16962
  nucleos.push({ desde: i2, hasta: fin });
16857
16963
  i2 = fin + 1;
@@ -16881,7 +16987,7 @@ function silabas(palabra, idioma = "es") {
16881
16987
  }
16882
16988
  function leerLetra(texto4, idioma = "es") {
16883
16989
  const salida = [];
16884
- for (const cruda of texto4.split(/\r?\n/)) {
16990
+ for (const cruda of texto4.normalize("NFC").split(/\r?\n/)) {
16885
16991
  const linea = cruda.trim();
16886
16992
  if (!linea) continue;
16887
16993
  const palabras = [];
@@ -20438,6 +20544,11 @@ function nombreLegible(deteccion) {
20438
20544
  const limpio = acorde.type === "major" && acorde.tonic ? acorde.tonic : cuerpo;
20439
20545
  return bajo ? `${limpio}/${bajo}` : limpio;
20440
20546
  }
20547
+ function afinacionQueSuena(pista) {
20548
+ if (!pista) return [];
20549
+ const capo = Number.isInteger(pista.capo) && pista.capo > 0 ? pista.capo : 0;
20550
+ return pista.afinacion.map((n) => n + capo);
20551
+ }
20441
20552
  function notaMidi(cuerda, traste, afinacion) {
20442
20553
  const alAire = afinacion[cuerda - 1];
20443
20554
  return alAire === void 0 ? null : alAire + traste;
@@ -20468,10 +20579,10 @@ function acordeEnElCursor(indice, cursor) {
20468
20579
  const compasId = indice.compasId(cursor.pistaId, cursor.compas, cursor.pentagrama);
20469
20580
  const figura = compasId ? indice.figuraEn(compasId, cursor.voz, cursor.tiempo) : void 0;
20470
20581
  if (!figura) return null;
20471
- const afinacion = indice.pista(cursor.pistaId)?.datos.afinacion;
20472
- if (!afinacion) return null;
20582
+ const pista = indice.pista(cursor.pistaId)?.datos;
20583
+ if (!pista) return null;
20473
20584
  const notas = indice.notas(figura.id).map((n) => n.datos);
20474
- return acordeDeNotas(notas, afinacion);
20585
+ return acordeDeNotas(notas, afinacionQueSuena(pista));
20475
20586
  }
20476
20587
  function claveDeForma(forma) {
20477
20588
  return [...forma].sort((a, b) => a.cuerda - b.cuerda).map((n) => `${n.cuerda}:${n.traste}`).join(" ");
@@ -20565,15 +20676,17 @@ function ponerAcorde(documento, indice, cursor, nombre, duracion = DURACION_INIC
20565
20676
  return null;
20566
20677
  }
20567
20678
  const yaTieneNotas = figura ? indice.notas(figura.id).length > 0 : false;
20568
- if (sinNombre && (yaTieneNotas || forma.length === 0)) return null;
20679
+ const pista = indice.pista(cursor.pistaId)?.datos;
20680
+ const formaQueEntra = pista?.tipo === "trastes" && forma.every((n) => n.cuerda >= 1 && n.cuerda <= pista.afinacion.length) ? forma : [];
20681
+ if (sinNombre && (yaTieneNotas || formaQueEntra.length === 0)) return null;
20569
20682
  documento.doc.transact(() => {
20570
20683
  const compasId = compasExistente ?? documento.agregarCompas(normalizarCompas({ pistaId: cursor.pistaId, compasMaestroId: maestro.id, pentagrama: cursor.pentagrama }));
20571
20684
  const id2 = figura?.id ?? documento.agregarFigura({ ...figuraNueva(compasId, cursor, duracion), esSilencio: true });
20572
20685
  const mapa = documento.figuras.get(id2);
20573
20686
  if (mapa && !sinNombre) escribirConMarcas(mapa, "acorde", nombre ?? "", marcas, { recortar: true });
20574
- if (!yaTieneNotas && forma.length > 0) {
20687
+ if (!yaTieneNotas && formaQueEntra.length > 0) {
20575
20688
  documento.figuras.get(id2)?.set("esSilencio", false);
20576
- for (const { cuerda, traste } of forma) {
20689
+ for (const { cuerda, traste } of formaQueEntra) {
20577
20690
  documento.agregarNota(notaNueva(id2, cuerda, traste));
20578
20691
  }
20579
20692
  }
@@ -20612,9 +20725,207 @@ var init_acordes = __esm({
20612
20725
  });
20613
20726
 
20614
20727
  // packages/editor/src/miraDeAcordes.ts
20728
+ var MIRA_DEDUCIR;
20615
20729
  var init_miraDeAcordes = __esm({
20616
20730
  "packages/editor/src/miraDeAcordes.ts"() {
20617
20731
  init_acordes();
20732
+ MIRA_DEDUCIR = "+deducir";
20733
+ }
20734
+ });
20735
+
20736
+ // packages/editor/src/paletasConTeclado.ts
20737
+ function fichasFijas(familia) {
20738
+ switch (familia) {
20739
+ case "figura":
20740
+ return [
20741
+ ...FIGURAS.map((f) => ({ id: `figura-${f.valor}`, nombre: f.nombre, atajo: `figura-${f.valor}` })),
20742
+ { id: "puntillo", nombre: "puntillo", atajo: "puntillo" },
20743
+ { id: "silencio", nombre: "silencio", atajo: "silencio" }
20744
+ ].map(conSinonimos);
20745
+ case "tecnica":
20746
+ return [
20747
+ ...EFECTOS_EN_ORDEN.map((e) => ({ id: `efecto-${e}`, nombre: EFECTOS[e].nombre, atajo: `efecto-${e}` })),
20748
+ ...FORMAS_EN_ORDEN.map((f) => ({
20749
+ id: `bend-${f}`,
20750
+ nombre: FORMAS_DE_BEND[f].nombre,
20751
+ ...f === "bend" ? { atajo: "bend" } : {}
20752
+ })),
20753
+ ...ALTURAS_DE_BEND.map((a) => ({ id: `sube-${a.cuartos}`, nombre: `sube ${a.nombre.toLowerCase()}` })),
20754
+ { id: "curva", nombre: "curva propia" }
20755
+ ].map(conSinonimos);
20756
+ case "signo":
20757
+ return GRUPOS_EN_ORDEN.flatMap(
20758
+ (g) => signosDelGrupo(g).map((s) => ({ id: `signo-${s.clave}`, nombre: s.nombre, atajo: `signo-${s.clave}` }))
20759
+ ).map(conSinonimos);
20760
+ case "matiz":
20761
+ return MATICES.map((m) => {
20762
+ const [italiano = "", resto = ""] = m.detalle.split("\u2014");
20763
+ const castellano = resto.split(".")[0] ?? "";
20764
+ return {
20765
+ id: `matiz-${m.nombre}`,
20766
+ nombre: m.nombre,
20767
+ sinonimos: [italiano.trim(), castellano.trim()].filter(Boolean),
20768
+ atajo: `comando-matiz-${m.nombre}`
20769
+ };
20770
+ });
20771
+ case "kit":
20772
+ return KIT.map((p) => conSinonimos({ id: `kit-${p.indice}`, nombre: p.nombre }));
20773
+ case "acorde":
20774
+ case "seccion":
20775
+ return [];
20776
+ }
20777
+ }
20778
+ function fichasDeAcordes(usados) {
20779
+ return [
20780
+ { id: MIRA_DEDUCIR, nombre: "+ acorde", sinonimos: ["a\xF1adir", "deducir", "el del golpe"] },
20781
+ ...usados.map((a) => ({ id: a.clave, nombre: a.letra ? `${a.nombre} ${a.letra}` : a.nombre }))
20782
+ ];
20783
+ }
20784
+ function fichasDeSecciones(nombres) {
20785
+ const vistos = /* @__PURE__ */ new Set();
20786
+ const unicos = [];
20787
+ for (const n of nombres) {
20788
+ const limpio = n.trim();
20789
+ if (limpio && !vistos.has(limpio)) {
20790
+ vistos.add(limpio);
20791
+ unicos.push(limpio);
20792
+ }
20793
+ }
20794
+ return [
20795
+ { id: SECCION_NUEVA, nombre: "nombre nuevo", sinonimos: ["escribir", "otra"] },
20796
+ ...unicos.map((n) => ({ id: `seccion:${n}`, nombre: n }))
20797
+ ];
20798
+ }
20799
+ function normalizarBusqueda(texto4) {
20800
+ return texto4.toLowerCase().normalize("NFD").replace(/[̀-ͯ]/g, "").replace(/\s+/g, " ").trim();
20801
+ }
20802
+ function puntajeDeBusqueda(ficha, texto4) {
20803
+ const q = normalizarBusqueda(texto4);
20804
+ if (!q) return null;
20805
+ const nombre = normalizarBusqueda(ficha.nombre);
20806
+ const otros = (ficha.sinonimos ?? []).map(normalizarBusqueda);
20807
+ if (nombre === q) return -1;
20808
+ if (nombre.startsWith(q)) return 0;
20809
+ if (otros.some((s) => s.startsWith(q))) return 1;
20810
+ const palabras = [nombre, ...otros].flatMap((t) => t.split(/[\s(),/-]+/));
20811
+ if (palabras.some((p) => p.startsWith(q))) return 2;
20812
+ if (nombre.includes(q) || otros.some((s) => s.includes(q))) return 3;
20813
+ return null;
20814
+ }
20815
+ function buscarEnLasPaletas(paletas, texto4, limite = 12) {
20816
+ if (!normalizarBusqueda(texto4)) return [];
20817
+ const todos = [];
20818
+ let orden = 0;
20819
+ for (const { familia, fichas } of paletas) {
20820
+ for (const ficha of fichas) {
20821
+ const p = puntajeDeBusqueda(ficha, texto4);
20822
+ if (p !== null) todos.push({ familia, ficha, puntaje: p, orden });
20823
+ orden++;
20824
+ }
20825
+ }
20826
+ return todos.sort((a, b) => a.puntaje - b.puntaje || a.orden - b.orden).slice(0, limite).map(({ familia, ficha, puntaje: p }) => ({ familia, ficha, puntaje: p }));
20827
+ }
20828
+ var NOMBRE_DE_LA_FAMILIA, SINONIMOS, conSinonimos, FIGURAS, SECCION_NUEVA;
20829
+ var init_paletasConTeclado = __esm({
20830
+ "packages/editor/src/paletasConTeclado.ts"() {
20831
+ init_efectos();
20832
+ init_bend();
20833
+ init_variantes();
20834
+ init_signos();
20835
+ init_dinamicas();
20836
+ init_miraDeAcordes();
20837
+ init_src2();
20838
+ NOMBRE_DE_LA_FAMILIA = {
20839
+ figura: "Figura",
20840
+ tecnica: "T\xE9cnica",
20841
+ signo: "Signo",
20842
+ acorde: "Acorde",
20843
+ kit: "Bater\xEDa",
20844
+ matiz: "Matiz",
20845
+ seccion: "Secci\xF3n"
20846
+ };
20847
+ SINONIMOS = {
20848
+ // Figuras
20849
+ "figura-1": ["entera", "whole"],
20850
+ "figura-2": ["mitad", "half"],
20851
+ "figura-4": ["cuarto", "quarter"],
20852
+ "figura-8": ["octavo", "eighth"],
20853
+ "figura-16": ["dieciseisavo", "sixteenth"],
20854
+ "figura-32": ["treintaidosavo", "thirty-second"],
20855
+ "figura-64": ["sesentaicuatroavo", "sixty-fourth"],
20856
+ puntillo: ["punto", "dot"],
20857
+ silencio: ["pausa", "rest"],
20858
+ // Técnicas
20859
+ "efecto-martillo": ["ligado", "hammer", "hammer-on", "pull-off"],
20860
+ "efecto-deslizamiento": ["slide", "glissando", "arrastre"],
20861
+ "efecto-muerta": ["apagada", "dead note", "muted"],
20862
+ "efecto-palmMute": ["muteado", "apagado con la palma", "pm"],
20863
+ "efecto-letRing": ["dejar sonar", "que suene"],
20864
+ "efecto-vibrato": ["temblor"],
20865
+ "efecto-fantasma": ["ghost", "nota fantasma"],
20866
+ "efecto-armonico": ["harmonic", "flageolet"],
20867
+ "efecto-staccato": ["staccato", "corto"],
20868
+ "efecto-ligadaAAnterior": ["ligadura de prolongaci\xF3n", "tie", "prolongar"],
20869
+ "bend-bend": ["estirar", "estirada", "bending"],
20870
+ "bend-bendVuelta": ["release", "estirar y soltar"],
20871
+ "bend-prebend": ["pre bend", "ya estirada"],
20872
+ "bend-prebendVuelta": ["pre bend release"],
20873
+ curva: ["curva de bend", "dibujar"],
20874
+ // Signos
20875
+ "signo-acento": ["accent", "marcato"],
20876
+ "signo-ligaduraDeExpresion": ["ligado", "slur", "legato"],
20877
+ "signo-calderon": ["fermata", "corona"],
20878
+ "signo-pedal": ["sustain", "pedal"],
20879
+ "signo-anchoDeVibrato": ["vibrato"],
20880
+ "signo-vibratoDePalanca": ["whammy", "tremolo bar", "palanca"],
20881
+ "signo-tappingIzquierda": ["tap"],
20882
+ "signo-dedoIzquierda": ["digitacion", "dedo", "fingering"],
20883
+ "signo-cejilla": ["barre", "cejuela", "capo"],
20884
+ "signo-puaDireccion": ["pick", "plectro", "downstroke", "upstroke", "direccion"],
20885
+ "signo-rasgueo": ["arpegio", "strum", "brush"],
20886
+ "signo-rasgueado": ["flamenco"],
20887
+ "signo-slap": ["bajo"],
20888
+ "signo-pop": ["bajo"],
20889
+ "signo-tapping": ["tap"],
20890
+ "signo-slapMuerto": ["dead slap"],
20891
+ "signo-golpeEnLaTapa": ["percusion", "tapa"],
20892
+ "signo-dedoDerecha": ["pima", "digitacion", "dedo"],
20893
+ "signo-adorno": ["grace", "appoggiatura", "acciaccatura", "mordente"],
20894
+ "signo-tremolo": ["tremolo picking", "tremolo"],
20895
+ "signo-trino": ["trill"],
20896
+ "signo-regulador": ["crescendo", "decrescendo", "hairpin"],
20897
+ "signo-fundido": ["fade", "volume swell"],
20898
+ "signo-wah": ["wah"],
20899
+ "signo-alteracion": ["sostenido", "bemol", "becuadro", "accidental"],
20900
+ "signo-muestraCuerda": ["cuerda", "string"],
20901
+ "signo-barraOblicua": ["slash", "ritmo"],
20902
+ "signo-plicaInvertida": ["plica", "stem"],
20903
+ "signo-barrado": ["beam", "corchete"],
20904
+ // Batería
20905
+ "kit-0": ["kick", "bass drum"],
20906
+ "kit-1": ["snare", "redoblante", "tarola"],
20907
+ "kit-2": ["rimshot", "aro"],
20908
+ "kit-3": ["tom", "high tom"],
20909
+ "kit-4": ["tom", "mid tom"],
20910
+ "kit-5": ["floor tom", "tom de pie"],
20911
+ "kit-6": ["hi-hat", "hihat", "contratiempo", "hh"],
20912
+ "kit-7": ["hi-hat abierto", "hihat abierto"],
20913
+ "kit-8": ["hi-hat pedal", "pie"],
20914
+ "kit-9": ["platillo", "crash"],
20915
+ "kit-10": ["platillo", "ride"],
20916
+ "kit-11": ["bell", "campana"]
20917
+ };
20918
+ conSinonimos = (f) => SINONIMOS[f.id] ? { ...f, sinonimos: SINONIMOS[f.id] } : f;
20919
+ FIGURAS = [
20920
+ { valor: 1, nombre: "redonda" },
20921
+ { valor: 2, nombre: "blanca" },
20922
+ { valor: 4, nombre: "negra" },
20923
+ { valor: 8, nombre: "corchea" },
20924
+ { valor: 16, nombre: "semicorchea" },
20925
+ { valor: 32, nombre: "fusa" },
20926
+ { valor: 64, nombre: "semifusa" }
20927
+ ];
20928
+ SECCION_NUEVA = "+nueva";
20618
20929
  }
20619
20930
  });
20620
20931
 
@@ -20823,9 +21134,11 @@ var init_instrumentos = __esm({
20823
21134
  programaMidi: PROGRAMAS.guitarraNylon
20824
21135
  },
20825
21136
  // ── Teclados ──
21137
+ // ★ (2026-11-11) Los tres en DOS pentagramas bajo una llave, como se escriben: el órgano también
21138
+ // (antes salía en uno, sin llave). Ver `verificar-llave-del-piano`.
20826
21139
  { clave: "piano", nombre: "Piano", nombreCorto: "pno", grupo: "Teclados", tipo: "altura", afinacion: [], programaMidi: PROGRAMAS.piano, pentagramas: 2 },
20827
21140
  { clave: "piano-electrico", nombre: "Piano el\xE9ctrico", nombreCorto: "e.pno", grupo: "Teclados", tipo: "altura", afinacion: [], programaMidi: PROGRAMAS.pianoElectrico, pentagramas: 2 },
20828
- { clave: "organo", nombre: "\xD3rgano", nombreCorto: "org", grupo: "Teclados", tipo: "altura", afinacion: [], programaMidi: PROGRAMAS.organo },
21141
+ { clave: "organo", nombre: "\xD3rgano", nombreCorto: "org", grupo: "Teclados", tipo: "altura", afinacion: [], programaMidi: PROGRAMAS.organo, pentagramas: 2 },
20829
21142
  // ── Vientos ──
20830
21143
  { clave: "trompeta", nombre: "Trompeta", nombreCorto: "tpt", grupo: "Vientos", tipo: "altura", afinacion: [], programaMidi: PROGRAMAS.trompeta },
20831
21144
  { clave: "trombon", nombre: "Tromb\xF3n", nombreCorto: "tbn", grupo: "Vientos", tipo: "altura", afinacion: [], programaMidi: PROGRAMAS.trombon },
@@ -21164,7 +21477,7 @@ var init_estado = __esm({
21164
21477
  "estado.info": {
21165
21478
  nombre: "Panel de info",
21166
21479
  accion: "info",
21167
- principiante: "Muestra u oculta el panel de arriba a la derecha, que explica lo que est\xE1 bajo el puntero. Cuando el c\xEDrculo est\xE1 amarillo, el panel est\xE1 a la vista; gris, est\xE1 oculto. Tambi\xE9n se prende y se apaga con la tecla de signo de pregunta.",
21480
+ principiante: "Muestra u oculta la franja de debajo de las herramientas, que explica lo que est\xE1 bajo el puntero. Cuando el c\xEDrculo est\xE1 amarillo, el panel est\xE1 a la vista; gris, est\xE1 oculto. Tambi\xE9n se prende y se apaga con la tecla de signo de pregunta.",
21168
21481
  profesional: "Muestra u oculta el panel de info. Tecla: ?"
21169
21482
  }
21170
21483
  };
@@ -21199,8 +21512,8 @@ var init_menus = __esm({
21199
21512
  },
21200
21513
  "menu-compas": {
21201
21514
  nombre: "Men\xFA Comp\xE1s",
21202
- principiante: "Lo que se hace con el comp\xE1s donde est\xE1 el cursor: insertar uno nuevo, borrarlo, cambiarle el tempo, la m\xE9trica o la tonalidad desde ah\xED, marcarlo como anacruza o ponerle el nombre de una secci\xF3n.",
21203
- profesional: "Estructura del comp\xE1s del cursor: insertar, borrar, tempo, m\xE9trica, tonalidad, anacruza y secci\xF3n."
21515
+ principiante: "Lo que se hace con el comp\xE1s donde est\xE1 el cursor: insertar uno nuevo, borrarlo, cambiarle el tempo, la m\xE9trica o la tonalidad desde ah\xED, marcarlo como anacruza o ponerle el nombre de una secci\xF3n. Tambi\xE9n pone repeticiones, casillas, D.C., D.S. y Coda.",
21516
+ profesional: "Comp\xE1s del cursor: insertar, borrar, tempo, m\xE9trica, tonalidad, anacruza, secci\xF3n, repetici\xF3n, casillas y saltos."
21204
21517
  },
21205
21518
  "menu-nota": {
21206
21519
  nombre: "Men\xFA Nota",
@@ -21214,8 +21527,8 @@ var init_menus = __esm({
21214
21527
  },
21215
21528
  "menu-configuracion": {
21216
21529
  nombre: "Men\xFA Configuraci\xF3n",
21217
- principiante: "Los ajustes del programa que son tuyos y no de la obra: las teclas de cada acci\xF3n y c\xF3mo se sincroniza la letra.",
21218
- profesional: "Atajos de teclado y sincronizaci\xF3n de letra."
21530
+ principiante: "Los ajustes del programa que son tuyos y no de la obra: las teclas de cada acci\xF3n, c\xF3mo se sincroniza la letra, c\xF3mo se escribe y tu privacidad.",
21531
+ profesional: "Atajos, sincronizaci\xF3n de letra, escritura y privacidad."
21219
21532
  },
21220
21533
  // ── Archivo ─────────────────────────────────────────────────────────────────
21221
21534
  nuevo: {
@@ -21225,8 +21538,8 @@ var init_menus = __esm({
21225
21538
  },
21226
21539
  abrir: {
21227
21540
  nombre: "Abrir\u2026",
21228
- principiante: "Abre una obra que est\xE1 en tu computadora: un proyecto de Mandioca Tabs (.mtabs) o un archivo de Guitar Pro, MusicXML y otros formatos de tablatura. Si hay cambios sin guardar, te pregunta antes.",
21229
- profesional: "Abre .mtabs, .gp y MusicXML."
21541
+ principiante: "Abre una obra que est\xE1 en tu computadora: un proyecto de Mandioca Tabs (.mtabs) o un archivo de Guitar Pro, MusicXML, MIDI y otros formatos de tablatura. Si hay cambios sin guardar, te pregunta antes.",
21542
+ profesional: "Abre .mtabs, .gp, MusicXML y MIDI."
21230
21543
  },
21231
21544
  "menu-recientes": {
21232
21545
  nombre: "Abrir reciente",
@@ -21301,6 +21614,18 @@ var init_menus = __esm({
21301
21614
  principiante: "Borra la nota que est\xE1 bajo el cursor. El tiempo que ocupaba queda en su lugar, como silencio, as\xED que el comp\xE1s no se corre.",
21302
21615
  profesional: "Borra la nota; el golpe queda."
21303
21616
  },
21617
+ "menu-buscar-en-paletas": {
21618
+ nombre: "Buscar en las paletas\u2026",
21619
+ accion: "comando-buscar-en-paletas",
21620
+ principiante: "Abre un buscador que mira las siete paletas a la vez. Escribe el nombre de un signo, una t\xE9cnica, una figura o un matiz, y elige con las flechas. Sirve cuando no sabes en qu\xE9 paleta est\xE1 algo.",
21621
+ profesional: "B\xFAsqueda global de paletas."
21622
+ },
21623
+ "menu-paleta-con-teclado": {
21624
+ nombre: "Paleta con el teclado, sin sostener Tab",
21625
+ accion: "comando-paleta-con-teclado",
21626
+ principiante: "Hace lo mismo que mantener Tab apretado, pero sin tener que sostenerlo: las flechas eligen en la paleta, \u2193 escribe, \u2191 quita y las letras buscan. Sirve si sostener una tecla te cuesta. Para salir, Esc o este mismo comando.",
21627
+ profesional: "Modo fijo del Tab sostenido; Esc o el mismo comando salen."
21628
+ },
21304
21629
  "menu-borrar-figura": {
21305
21630
  nombre: "Borrar la figura entera",
21306
21631
  accion: "borrar-figura",
@@ -21333,7 +21658,7 @@ var init_menus = __esm({
21333
21658
  nombre: "Insertar un comp\xE1s",
21334
21659
  accion: "insertar-compas",
21335
21660
  principiante: "Agrega un comp\xE1s vac\xEDo justo despu\xE9s del comp\xE1s donde est\xE1 el cursor. Para meterlo antes de uno en particular, haz clic derecho sobre su l\xEDnea divisoria.",
21336
- profesional: "Inserta un comp\xE1s despu\xE9s del del cursor."
21661
+ profesional: "Inserta un comp\xE1s despu\xE9s del comp\xE1s del cursor."
21337
21662
  },
21338
21663
  "menu-borrar-compas": {
21339
21664
  nombre: "Borrar el comp\xE1s",
@@ -21348,7 +21673,7 @@ var init_menus = __esm({
21348
21673
  profesional: "Borra del comp\xE1s del cursor al final."
21349
21674
  },
21350
21675
  "menu-borrar-hasta": {
21351
- nombre: "Borrar para atr\xE1s",
21676
+ nombre: "Borrar los compases antes",
21352
21677
  accion: "comando-borrar-compases-hasta",
21353
21678
  principiante: "Borra todos los compases que est\xE1n antes del cursor, desde el principio de la obra. El comp\xE1s del cursor pasa a ser el primero.",
21354
21679
  profesional: "Borra del principio al comp\xE1s anterior al cursor."
@@ -21383,6 +21708,24 @@ var init_menus = __esm({
21383
21708
  principiante: "Le pone un nombre a la parte que empieza en el comp\xE1s del cursor: Intro, Estrofa, Coro, el que quieras. Si ah\xED ya empieza una secci\xF3n, puedes cambiarle el nombre o borrarlo.",
21384
21709
  profesional: "Marca de secci\xF3n en el comp\xE1s del cursor."
21385
21710
  },
21711
+ "menu-repeticion": {
21712
+ nombre: "Repetici\xF3n\u2026",
21713
+ accion: "comando-compas-repeticion",
21714
+ principiante: "Pone las barras de repetici\xF3n. Con un pasaje marcado, abre en su primer comp\xE1s y cierra en el \xFAltimo. Sin pasaje, va en el comp\xE1s del cursor. Eliges cu\xE1ntas veces se toca. Desmarca las barras para quitarlas.",
21715
+ profesional: "Barras de inicio y fin de repetici\xF3n, con n\xFAmero de veces."
21716
+ },
21717
+ "menu-casillas": {
21718
+ nombre: "Casillas\u2026",
21719
+ accion: "comando-compas-casillas",
21720
+ principiante: "Pone una casilla de repetici\xF3n, la 1.\xAA o la 2.\xAA: el comp\xE1s se toca s\xF3lo en esas vueltas. Con un pasaje marcado, la casilla abarca todo el pasaje. Necesita una barra de cierre para hacer algo.",
21721
+ profesional: "Casillas 1.\xAA, 2.\xAA\u2026 del comp\xE1s o del pasaje."
21722
+ },
21723
+ "menu-saltos": {
21724
+ nombre: "D.C., D.S. y Coda\u2026",
21725
+ accion: "comando-compas-saltos",
21726
+ principiante: "Pone en el comp\xE1s del cursor un salto (D.C., D.S., To Coda) o un destino (Segno, Coda, Fine). Un D.S. al Coda necesita tres marcas: el Segno, el To Coda y la Coda. La ventana te avisa si falta alguna.",
21727
+ profesional: "Segno, Coda, Fine y los saltos del comp\xE1s del cursor."
21728
+ },
21386
21729
  // ── Nota ────────────────────────────────────────────────────────────────────
21387
21730
  "menu-duplicar": {
21388
21731
  nombre: "Repetir la nota anterior",
@@ -21492,12 +21835,12 @@ var init_menus = __esm({
21492
21835
  },
21493
21836
  "menu-compases-por-linea": {
21494
21837
  nombre: "Compases por l\xEDnea",
21495
- principiante: "Cu\xE1ntos compases entran en cada rengl\xF3n. En vista horizontal, cu\xE1ntos se ven a la vez en la ventana. Con \xABLos que entren\xBB el programa los acomoda seg\xFAn su contenido.",
21838
+ principiante: "Cu\xE1ntos compases entran en cada rengl\xF3n. En vista horizontal, cu\xE1ntos se ven a la vez en la ventana. Con \xABLos que entren\xBB el programa pone los que quepan en el ancho, seg\xFAn lo que lleve cada uno.",
21496
21839
  profesional: "Compases por sistema."
21497
21840
  },
21498
21841
  "menu-cpl": {
21499
21842
  nombre: "Compases por l\xEDnea",
21500
- principiante: "Pone esta cantidad de compases en cada rengl\xF3n. \xABLos que entren\xBB deja que el programa decida seg\xFAn el ancho de la pantalla.",
21843
+ principiante: "Pone esta cantidad de compases en cada rengl\xF3n. \xABLos que entren\xBB pone los que quepan en el ancho, seg\xFAn lo que lleve cada uno.",
21501
21844
  profesional: "Fija los compases por sistema."
21502
21845
  },
21503
21846
  "menu-ritmo": {
@@ -21651,6 +21994,12 @@ var init_barra = __esm({
21651
21994
  principiante: "Ajustes para practicar: un comp\xE1s de cuenta antes de que entre la m\xFAsica, y que la velocidad suba sola cada tantas vueltas del bucle hasta la que elijas. De f\xE1brica todo apagado.",
21652
21995
  profesional: "Count-in y speed trainer del bucle."
21653
21996
  },
21997
+ // ★ Sólo existe con el teléfono parado (2026-11-11): la de escritorio vive en el panel de audio.
21998
+ "barra.velocidad": {
21999
+ nombre: "Velocidad",
22000
+ principiante: "La obra suena m\xE1s lenta o m\xE1s r\xE1pida, de \xD70,25 a \xD71,5. La afinaci\xF3n no cambia. Sirve para sacar un pasaje dif\xEDcil de a poco.",
22001
+ profesional: "Velocidad del transporte, \xD70,25 a \xD71,5, con el tono intacto."
22002
+ },
21654
22003
  "barra.voz": {
21655
22004
  nombre: "Voz",
21656
22005
  principiante: "En qu\xE9 voz escribes. Una voz es una l\xEDnea mel\xF3dica dentro del mismo pentagrama: por ejemplo, el bajo en la voz 1 y la melod\xEDa en la voz 2, cada una con sus propias figuras. Lo que teclees va a la voz marcada.",
@@ -21711,7 +22060,7 @@ function fichaDeSigno(s) {
21711
22060
  profesional: s.ayuda.length <= 120 && (s.ayuda.match(/[.!?](\s|$)/g) ?? []).length <= 2 ? s.ayuda : `${s.nombre}.`
21712
22061
  };
21713
22062
  }
21714
- var FICHAS_DE_SIGNOS, FIGURAS, FICHAS_DE_FIGURAS, FICHAS_DE_MATICES, FICHAS_DE_LA_PALETA;
22063
+ var FICHAS_DE_SIGNOS, FIGURAS2, FICHAS_DE_FIGURAS, FICHAS_DE_MATICES, FICHAS_DE_LA_PALETA;
21715
22064
  var init_paleta = __esm({
21716
22065
  "packages/editor/src/info/fichas/paleta.ts"() {
21717
22066
  init_signos();
@@ -21719,7 +22068,7 @@ var init_paleta = __esm({
21719
22068
  FICHAS_DE_SIGNOS = Object.fromEntries(
21720
22069
  SIGNOS.map((s) => [`signo.${s.clave}`, fichaDeSigno(s)])
21721
22070
  );
21722
- FIGURAS = [
22071
+ FIGURAS2 = [
21723
22072
  { valor: 1, nombre: "Redonda", dura: "cuatro tiempos en un comp\xE1s de 4/4" },
21724
22073
  { valor: 2, nombre: "Blanca", dura: "dos tiempos" },
21725
22074
  { valor: 4, nombre: "Negra", dura: "un tiempo" },
@@ -21729,7 +22078,7 @@ var init_paleta = __esm({
21729
22078
  { valor: 64, nombre: "Semifusa", dura: "un dieciseisavo de tiempo" }
21730
22079
  ];
21731
22080
  FICHAS_DE_FIGURAS = Object.fromEntries(
21732
- FIGURAS.map((f) => [
22081
+ FIGURAS2.map((f) => [
21733
22082
  `figura.${f.valor}`,
21734
22083
  {
21735
22084
  nombre: f.nombre,
@@ -21898,6 +22247,19 @@ var init_paleta = __esm({
21898
22247
  principiante: "Borra el nombre del acorde del golpe del cursor. Las notas no se tocan.",
21899
22248
  profesional: "Quita el cifrado del golpe."
21900
22249
  },
22250
+ // ── Sección ─────────────────────────────────────────────────────────────────
22251
+ "seccion.usada": {
22252
+ nombre: "Secci\xF3n de la obra",
22253
+ principiante: "Un nombre de secci\xF3n que ya usaste en esta obra, como Coro o Verso. Un clic lo pone en el comp\xE1s del cursor. Para uno nuevo, escribe en el campo.",
22254
+ profesional: "Reutiliza un r\xF3tulo de secci\xF3n de la obra."
22255
+ },
22256
+ // ── El teclado en las paletas (2026-11-11) ──────────────────────────────────
22257
+ "paleta.busqueda": {
22258
+ nombre: "Buscar en las paletas",
22259
+ accion: "comando-buscar-en-paletas",
22260
+ principiante: "Escribe el nombre de lo que quieres poner \u2014acento, martillo, corchea, ff\u2014 y busca en las siete paletas a la vez. Acepta otros nombres, como hammer o fermata. Cada resultado dice en qu\xE9 paleta est\xE1 y su tecla, para que la pr\xF3xima vez vayas directo. Las flechas eligen, Intro lo pone y Esc cierra.",
22261
+ profesional: "B\xFAsqueda en todas las paletas, con sin\xF3nimos y la tecla de cada resultado."
22262
+ },
21901
22263
  // ── Batería ─────────────────────────────────────────────────────────────────
21902
22264
  "kit.pieza": {
21903
22265
  nombre: "Pieza de la bater\xEDa",
@@ -21948,7 +22310,7 @@ var init_paneles = __esm({
21948
22310
  },
21949
22311
  "mezcla.volumen": {
21950
22312
  nombre: "Volumen de la pista",
21951
- principiante: "Cu\xE1nto suena esta pista, en decibeles. 0 dB es el m\xE1ximo; los n\xFAmeros negativos bajan. Se cambia arrastrando, con las flechas o escribiendo.",
22313
+ principiante: "Cu\xE1nto suena esta pista, en decibeles. Llega hasta +6 dB; los n\xFAmeros negativos bajan. Se cambia arrastrando, con las flechas o escribiendo.",
21952
22314
  profesional: "Volumen de la pista, en dB."
21953
22315
  },
21954
22316
  "mezcla.paneo": {
@@ -22044,6 +22406,11 @@ var init_paneles = __esm({
22044
22406
  principiante: "Empieza la separaci\xF3n con la cantidad elegida. Las partes aparecen como grabaciones nuevas de la obra.",
22045
22407
  profesional: "Lanza la separaci\xF3n."
22046
22408
  },
22409
+ "audio.stems-cancelar": {
22410
+ nombre: "Cancelar la separaci\xF3n",
22411
+ principiante: "Detiene la separaci\xF3n. No se agrega ninguna pista a la obra.",
22412
+ profesional: "Aborta la separaci\xF3n sin escribir."
22413
+ },
22047
22414
  "audio.letra-cerrar": {
22048
22415
  nombre: "Cerrar la letra",
22049
22416
  principiante: "Cierra el panel de la letra y vuelve a la partitura. Lo escrito se queda.",
@@ -22170,7 +22537,7 @@ var init_paneles = __esm({
22170
22537
  },
22171
22538
  "inicio.abrir": {
22172
22539
  nombre: "Abrir un archivo",
22173
- principiante: "Abre un proyecto o un archivo de Guitar Pro o MusicXML que est\xE9 en tu computadora.",
22540
+ principiante: "Abre un proyecto o un archivo de Guitar Pro, MusicXML o MIDI que est\xE9 en tu computadora.",
22174
22541
  profesional: "Abre un archivo."
22175
22542
  },
22176
22543
  "inicio.reciente": {
@@ -22265,6 +22632,21 @@ var init_paneles = __esm({
22265
22632
  principiante: "Pone una armadura nueva desde el comp\xE1s que empieza en esta l\xEDnea, hasta el pr\xF3ximo cambio.",
22266
22633
  profesional: "Cambio de armadura desde este comp\xE1s."
22267
22634
  },
22635
+ "linea.repeticion": {
22636
+ nombre: "Repetici\xF3n",
22637
+ principiante: "Pone las barras de repetici\xF3n en el comp\xE1s que empieza en esta l\xEDnea. Si ese comp\xE1s est\xE1 dentro de un pasaje marcado, las pone en todo el pasaje.",
22638
+ profesional: "Barras de repetici\xF3n aqu\xED o en el pasaje."
22639
+ },
22640
+ "linea.casillas": {
22641
+ nombre: "Casillas",
22642
+ principiante: "Pone una casilla 1.\xAA, 2.\xAA\u2026 en el comp\xE1s que empieza en esta l\xEDnea, o en todo el pasaje marcado si est\xE1 adentro.",
22643
+ profesional: "Casillas aqu\xED o en el pasaje."
22644
+ },
22645
+ "linea.saltos": {
22646
+ nombre: "D.C., D.S. y Coda",
22647
+ principiante: "Pone un salto o un destino \u2014Segno, Coda, Fine, D.S. al Coda\u2014 en el comp\xE1s que empieza en esta l\xEDnea.",
22648
+ profesional: "Saltos y destinos de este comp\xE1s."
22649
+ },
22268
22650
  "linea.insertar-izquierda": {
22269
22651
  nombre: "Insertar comp\xE1s a la izquierda",
22270
22652
  principiante: "Mete un comp\xE1s vac\xEDo ANTES del comp\xE1s que empieza en esta l\xEDnea. Sirve tambi\xE9n para agregar uno al principio de la obra.",
@@ -22489,6 +22871,37 @@ var init_dialogos = __esm({
22489
22871
  principiante: "Escribe c\xF3mo se llama la parte \u2014Intro, Coro, Puente\u2014. Vac\xEDo, saca la secci\xF3n que hab\xEDa.",
22490
22872
  profesional: "Texto de la secci\xF3n; vac\xEDo la quita."
22491
22873
  },
22874
+ // ── Repeticiones, casillas y saltos (2026-11-11) ────────────────────────────
22875
+ "repeticion.abre": {
22876
+ nombre: "Barra de inicio",
22877
+ principiante: "La barra con dos puntos que marca d\xF3nde empieza lo que se repite. Desm\xE1rcala para quitarla.",
22878
+ profesional: "Barra de inicio de repetici\xF3n."
22879
+ },
22880
+ "repeticion.cierra": {
22881
+ nombre: "Barra de fin",
22882
+ principiante: "La barra con dos puntos donde la m\xFAsica vuelve al inicio de la repetici\xF3n. Desm\xE1rcala para quitarla.",
22883
+ profesional: "Barra de fin de repetici\xF3n."
22884
+ },
22885
+ "repeticion.veces": {
22886
+ nombre: "Veces",
22887
+ principiante: "Cu\xE1ntas veces se toca el pasaje en total, contando la primera. 2 es lo normal y no se escribe en el pliego; desde 3 aparece \xABx3\xBB.",
22888
+ profesional: "Pasadas totales (2\u201364)."
22889
+ },
22890
+ "casillas.vuelta": {
22891
+ nombre: "Vuelta",
22892
+ principiante: "En qu\xE9 vuelta de la repetici\xF3n se toca este comp\xE1s. Puedes marcar m\xE1s de una, como \xAB1. 2.\xBB. Sin ninguna marcada, se quita la casilla.",
22893
+ profesional: "Vueltas en que suena; ninguna la quita."
22894
+ },
22895
+ "saltos.destino": {
22896
+ nombre: "Destino",
22897
+ principiante: "Un lugar al que otro comp\xE1s salta: el Segno para un D.S., la Coda para un \xABal Coda\xBB, el Fine para un \xABal Fine\xBB.",
22898
+ profesional: "Marca de destino (Segno, Coda, Fine)."
22899
+ },
22900
+ "saltos.salto": {
22901
+ nombre: "Salto",
22902
+ principiante: "Manda la m\xFAsica a otro lugar al terminar el comp\xE1s. D.C. vuelve al principio y D.S. al Segno. To Coda marca d\xF3nde se salta a la Coda en la segunda pasada.",
22903
+ profesional: "Salto (D.C., D.S., To Coda\u2026)."
22904
+ },
22492
22905
  "golpe.letra": {
22493
22906
  nombre: "S\xEDlaba",
22494
22907
  principiante: "La s\xEDlaba de la letra que se canta en este golpe. Vac\xEDa, saca la que hab\xEDa.",
@@ -23029,6 +23442,7 @@ var init_src3 = __esm({
23029
23442
  init_silabas();
23030
23443
  init_acordes();
23031
23444
  init_miraDeAcordes();
23445
+ init_paletasConTeclado();
23032
23446
  init_alinearLetra();
23033
23447
  init_diapason();
23034
23448
  init_video();
@@ -87649,6 +88063,156 @@ var init_alphaTab = __esm({
87649
88063
  }
87650
88064
  });
87651
88065
 
88066
+ // packages/io/src/manosDelPiano.ts
88067
+ function repartirManos(notas, P = PARAMETROS_DE_MANOS) {
88068
+ const derecha = [];
88069
+ const izquierda = [];
88070
+ const orden = notas.map((_, i3) => i3).sort((a, b) => notas[a].ini - notas[b].ini || notas[a].altura - notas[b].altura);
88071
+ let centroD = P.centroDerecha;
88072
+ let centroI = P.centroIzquierda;
88073
+ let sostieneD = [];
88074
+ let sostieneI = [];
88075
+ let ultimaD = null;
88076
+ let ultimaI = null;
88077
+ let i2 = 0;
88078
+ while (i2 < orden.length) {
88079
+ const ini = notas[orden[i2]].ini;
88080
+ const golpe = [];
88081
+ while (i2 < orden.length && notas[orden[i2]].ini === ini) golpe.push(orden[i2++]);
88082
+ sostieneD = sostieneD.filter((j) => notas[j].fin > ini);
88083
+ sostieneI = sostieneI.filter((j) => notas[j].fin > ini);
88084
+ const techoI = sostieneI.length ? Math.max(...sostieneI.map((j) => notas[j].altura)) : -Infinity;
88085
+ const pisoD = sostieneD.length ? Math.min(...sostieneD.map((j) => notas[j].altura)) : Infinity;
88086
+ let mejor = { corte: 0, costo: Infinity, distanciaAlDo: Infinity };
88087
+ for (let corte = 0; corte <= golpe.length; corte++) {
88088
+ const abajo = golpe.slice(0, corte).map((j) => notas[j].altura);
88089
+ const arriba = golpe.slice(corte).map((j) => notas[j].altura);
88090
+ let costo = 0;
88091
+ for (const a of abajo) costo += Math.abs(a - centroI) + (a >= DO_CENTRAL ? P.costoLadoDelDo : 0);
88092
+ for (const a of arriba) costo += Math.abs(a - centroD) + (a < DO_CENTRAL ? P.costoLadoDelDo : 0);
88093
+ const manoI = [...abajo, ...abajo.length ? sostieneI.map((j) => notas[j].altura) : []];
88094
+ const manoD = [...arriba, ...arriba.length ? sostieneD.map((j) => notas[j].altura) : []];
88095
+ for (const mano of [manoI, manoD]) {
88096
+ if (mano.length < 2) continue;
88097
+ const abre = Math.max(...mano) - Math.min(...mano);
88098
+ if (abre > P.abertura) costo += P.costoAbertura + (abre - P.abertura) * 4;
88099
+ }
88100
+ for (const a of arriba) if (a < techoI) costo += P.costoCruce;
88101
+ for (const a of abajo) if (a > pisoD) costo += P.costoCruce;
88102
+ if (golpe.length === 1) {
88103
+ const a = notas[golpe[0]].altura;
88104
+ if (abajo.length && ultimaI !== null && Math.abs(a - ultimaI) <= P.continuidadCerca) costo -= P.premioContinuidad;
88105
+ if (arriba.length && ultimaD !== null && Math.abs(a - ultimaD) <= P.continuidadCerca) costo -= P.premioContinuidad;
88106
+ }
88107
+ const delDo = Math.abs(corte - golpe.filter((j) => notas[j].altura < DO_CENTRAL).length);
88108
+ if (costo < mejor.costo - 1e-9 || Math.abs(costo - mejor.costo) <= 1e-9 && delDo < mejor.distanciaAlDo)
88109
+ mejor = { corte, costo, distanciaAlDo: delDo };
88110
+ }
88111
+ const deI = golpe.slice(0, mejor.corte);
88112
+ const deD = golpe.slice(mejor.corte);
88113
+ izquierda.push(...deI);
88114
+ derecha.push(...deD);
88115
+ sostieneI.push(...deI);
88116
+ sostieneD.push(...deD);
88117
+ const media = (js) => js.reduce((s, j) => s + notas[j].altura, 0) / js.length;
88118
+ if (deI.length) {
88119
+ centroI = centroI * (1 - P.seguimiento) + media(deI) * P.seguimiento;
88120
+ ultimaI = notas[deI[deI.length - 1]].altura;
88121
+ } else centroI += (P.centroIzquierda - centroI) * P.regreso;
88122
+ if (deD.length) {
88123
+ centroD = centroD * (1 - P.seguimiento) + media(deD) * P.seguimiento;
88124
+ ultimaD = notas[deD[0]].altura;
88125
+ } else centroD += (P.centroDerecha - centroD) * P.regreso;
88126
+ }
88127
+ return { derecha, izquierda };
88128
+ }
88129
+ function baseDeLasManos(nombreDerecha, nombreIzquierda) {
88130
+ const d = DERECHA.exec(nombreDerecha.trim());
88131
+ const i2 = IZQUIERDA.exec(nombreIzquierda.trim());
88132
+ if (!d || !i2) return null;
88133
+ const base = (s) => s.trim().replace(/[\s\-_.:,(]+$/, "").trim();
88134
+ const bd = base(d[1]);
88135
+ const bi = base(i2[1]);
88136
+ if (bd.toLocaleLowerCase("es") !== bi.toLocaleLowerCase("es")) return null;
88137
+ return bd || "Piano";
88138
+ }
88139
+ function juntarManosDelPiano(documento) {
88140
+ const enOrden = documento.pistasEnOrden().map((p) => p.id);
88141
+ const parejas = [];
88142
+ for (let i2 = 0; i2 + 1 < enOrden.length; i2++) {
88143
+ const d = documento.pistas.get(enOrden[i2]);
88144
+ const iz = documento.pistas.get(enOrden[i2 + 1]);
88145
+ const mismoInstrumento = d.get("tipo") === "altura" && iz.get("tipo") === "altura" && (d.get("pentagramas") ?? 1) === 1 && (iz.get("pentagramas") ?? 1) === 1 && d.get("programaMidi") === iz.get("programaMidi");
88146
+ if (!mismoInstrumento) continue;
88147
+ const porNombre = baseDeLasManos(String(d.get("nombre") ?? ""), String(iz.get("nombre") ?? ""));
88148
+ const porCorto = baseDeLasManos(String(d.get("nombreCorto") ?? ""), String(iz.get("nombreCorto") ?? ""));
88149
+ const base = porNombre ?? (porCorto !== null ? String(d.get("nombre") ?? "").trim() || porCorto : null);
88150
+ if (!base) continue;
88151
+ const clave = porNombre !== null ? "nombre" : "nombreCorto";
88152
+ const explicitas = MANO_EXPLICITA.test(String(d.get(clave) ?? "").trim()) && MANO_EXPLICITA.test(String(iz.get(clave) ?? "").trim());
88153
+ if (!esTeclado(d.get("programaMidi")) && !explicitas) continue;
88154
+ parejas.push({ derecha: enOrden[i2], izquierda: enOrden[i2 + 1], indiceIzquierda: i2 + 1, base });
88155
+ i2++;
88156
+ }
88157
+ if (parejas.length === 0) return 0;
88158
+ documento.doc.transact(() => {
88159
+ for (const { derecha, izquierda, base } of parejas) {
88160
+ const d = documento.pistas.get(derecha);
88161
+ const corto = String(d.get("nombreCorto") ?? "");
88162
+ d.set("pentagramas", 2);
88163
+ d.set("nombre", base);
88164
+ d.set("nombreCorto", corto.replace(/[\s\-_.:,(]*(mano\s+derecha|right\s+hand|r\.?\s?h\.?|m\.?\s?d\.?|rh|md)\)?\s*$/i, "").trim() || base);
88165
+ for (const compas of documento.compases.values()) {
88166
+ if (compas.get("pistaId") !== izquierda) continue;
88167
+ compas.set("pistaId", derecha);
88168
+ compas.set("pentagrama", 1);
88169
+ }
88170
+ documento.pistas.delete(izquierda);
88171
+ }
88172
+ const sesion = documento.ajustes.get(CLAVE_DE_LA_SESION);
88173
+ if (sesion && typeof sesion === "object" && !Array.isArray(sesion)) {
88174
+ const quitadas = new Set(parejas.map((p) => p.izquierda));
88175
+ const nueva = { ...sesion };
88176
+ for (const clave of ["mezcla", "volumenEnSolo", "notacionPorPista"]) {
88177
+ const v = nueva[clave];
88178
+ if (!v || typeof v !== "object" || Array.isArray(v)) continue;
88179
+ nueva[clave] = Object.fromEntries(Object.entries(v).filter(([id2]) => !quitadas.has(id2)));
88180
+ }
88181
+ if (Array.isArray(nueva.pistasConOjo)) {
88182
+ let lista2 = nueva.pistasConOjo.filter((n) => typeof n === "number");
88183
+ for (const quitado of parejas.map((p) => p.indiceIzquierda).sort((x2, y) => y - x2))
88184
+ lista2 = lista2.filter((n) => n !== quitado).map((n) => n > quitado ? n - 1 : n);
88185
+ nueva.pistasConOjo = lista2;
88186
+ }
88187
+ documento.ajustes.set(CLAVE_DE_LA_SESION, nueva);
88188
+ }
88189
+ });
88190
+ return parejas.length;
88191
+ }
88192
+ var DO_CENTRAL, PARAMETROS_DE_MANOS, DERECHA, IZQUIERDA, MANO_EXPLICITA, esTeclado;
88193
+ var init_manosDelPiano = __esm({
88194
+ "packages/io/src/manosDelPiano.ts"() {
88195
+ init_src2();
88196
+ DO_CENTRAL = 60;
88197
+ PARAMETROS_DE_MANOS = {
88198
+ centroDerecha: 72,
88199
+ centroIzquierda: 52,
88200
+ abertura: 12,
88201
+ costoAbertura: 20,
88202
+ costoCruce: 25,
88203
+ continuidadCerca: 7,
88204
+ premioContinuidad: 12,
88205
+ costoLadoDelDo: 1.5,
88206
+ seguimiento: 0.3,
88207
+ regreso: 0.1
88208
+ };
88209
+ DERECHA = /^(.*?)[\s\-_.:,(]*\b(mano\s+derecha|m\.?\s?d\.?|right\s+hand|r\.?\s?h\.?|rh|md|derecha|right|treble)\)?\s*$/i;
88210
+ IZQUIERDA = /^(.*?)[\s\-_.:,(]*\b(mano\s+izquierda|m\.?\s?i\.?|left\s+hand|l\.?\s?h\.?|lh|mi|izquierda|left|bass)\)?\s*$/i;
88211
+ MANO_EXPLICITA = /\b(mano\s+(derecha|izquierda)|(right|left)\s+hand|m\.?\s?[di]\.?|[rl]\.?\s?h\.?|md|mi|rh|lh)\)?\s*$/i;
88212
+ esTeclado = (programa) => typeof programa === "number" && (programa <= 7 || programa >= 16 && programa <= 20);
88213
+ }
88214
+ });
88215
+
87652
88216
  // node_modules/fflate/esm/index.mjs
87653
88217
  import { createRequire } from "module";
87654
88218
  function deflateSync(data, opts) {
@@ -88630,272 +89194,865 @@ var init_esm = __esm({
88630
89194
  }
88631
89195
  });
88632
89196
 
88633
- // packages/io/src/musicxmlEntrada.ts
88634
- function textoMusicXml(bytes) {
88635
- if (bytes.length > 4 && bytes[0] === 80 && bytes[1] === 75) {
88636
- let archivos;
88637
- try {
88638
- archivos = unzipSync(bytes);
88639
- } catch {
88640
- return null;
88641
- }
88642
- const contenedor = archivos["META-INF/container.xml"];
88643
- if (!contenedor) return null;
88644
- const ruta = /<rootfile\b[^>]*full-path="([^"]+)"/.exec(strFromU8(contenedor))?.[1];
88645
- const raiz = ruta ? archivos[ruta] : void 0;
88646
- return raiz ? decodificar(raiz) : null;
88647
- }
88648
- const texto4 = decodificar(bytes.subarray(0, Math.min(bytes.length, 4096)));
88649
- if (!/<score-(partwise|timewise)\b/.test(texto4) && !/<!DOCTYPE\s+score-/.test(texto4)) return null;
88650
- return decodificar(bytes);
89197
+ // packages/io/src/musicxml.ts
89198
+ function escapar(texto4) {
89199
+ return texto4.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
88651
89200
  }
88652
- function decodificar(bytes) {
88653
- if (bytes[0] === 255 && bytes[1] === 254) return new TextDecoder("utf-16le").decode(bytes);
88654
- if (bytes[0] === 254 && bytes[1] === 255) return new TextDecoder("utf-16be").decode(bytes);
88655
- return new TextDecoder("utf-8").decode(bytes);
89201
+ function elemento(nombre, texto4) {
89202
+ if (texto4 === null || texto4 === void 0 || texto4 === "") return [];
89203
+ return [`<${nombre}>${escapar(String(texto4))}</${nombre}>`];
88656
89204
  }
88657
- function prepararMusicXml(xml) {
88658
- const correcciones = [];
88659
- let atributosMovidos = 0;
88660
- let acordesSeparados = 0;
88661
- let saltosMovidos = 0;
88662
- let salida = xml.replace(/<part\b[^>]*>[\s\S]*?<\/part>/g, (parteOriginal) => {
88663
- const movidos = moverAtributosDeFinDeCompas(parteOriginal);
88664
- atributosMovidos += movidos.cuantos;
88665
- const separados = separarAcordesEntrePentagramas(movidos.texto);
88666
- acordesSeparados += separados.cuantos;
88667
- const saltos = sacarSaltosDeLaIndicacion(separados.texto);
88668
- saltosMovidos += saltos.cuantos;
88669
- const { texto: texto4, cambio } = compactarVoces(saltos.texto);
88670
- if (cambio) correcciones.push(cambio);
88671
- return texto4;
88672
- });
88673
- if (atributosMovidos) {
88674
- correcciones.push(
88675
- `${atributosMovidos} cambios de clave, armadura o comp\xE1s escritos entre las notas se pusieron al principio del comp\xE1s donde rigen.`
88676
- );
89205
+ function alturaMusicXml(midi, fifths, modo = 0) {
89206
+ const octavaAlphaTab = Math.floor(midi / 12);
89207
+ const clase = midi - octavaAlphaTab * 12;
89208
+ const octava = octavaAlphaTab - 1;
89209
+ const pedida = ALTERACION_POR_MODO[modo];
89210
+ if (pedida !== void 0) {
89211
+ const sinAlterar = clase - pedida;
89212
+ const vueltas = Math.floor(sinAlterar / 12);
89213
+ const paso = PASOS.find((p) => p.semitonos === sinAlterar - vueltas * 12);
89214
+ if (paso) return { paso: paso.paso, alteracion: pedida, octava: octava + vueltas };
88677
89215
  }
88678
- if (acordesSeparados) {
88679
- correcciones.push(`${acordesSeparados} notas de acordes repartidos entre los dos pentagramas se pusieron en el suyo.`);
89216
+ const natural = PASOS.find((p) => p.semitonos === clase);
89217
+ if (natural) return { paso: natural.paso, alteracion: 0, octava };
89218
+ if (fifths < 0) {
89219
+ const arriba = PASOS.find((p) => p.semitonos === clase + 1);
89220
+ if (arriba) return { paso: arriba.paso, alteracion: -1, octava };
88680
89221
  }
88681
- if (saltosMovidos) correcciones.push(`${saltosMovidos} saltos (D.C., D.S., Coda, Fine, Segno) se leyeron de su indicaci\xF3n.`);
88682
- let juntados = 0;
88683
- salida = salida.replace(/<direction\b[^>]*>[\s\S]*?<\/direction>/g, (dir) => {
88684
- const palabras = [...dir.matchAll(/<words\b[^>]*>([\s\S]*?)<\/words>/g)];
88685
- if (palabras.length < 2) return dir;
88686
- juntados++;
88687
- const todo = palabras.map((p) => p[1].trim()).filter(Boolean).join("\n");
88688
- let primero = true;
88689
- return dir.replace(/<words\b([^>]*)>[\s\S]*?<\/words>/g, (_m, attrs) => {
88690
- if (!primero) return "";
88691
- primero = false;
88692
- return `<words${attrs}>${todo}</words>`;
88693
- });
88694
- });
88695
- if (juntados) correcciones.push(`${juntados} indicaciones de texto en varios renglones se juntaron en una.`);
88696
- let silabas2 = 0;
88697
- salida = salida.replace(/<lyric\b[^>]*>[\s\S]*?<\/lyric>/g, (lyric) => {
88698
- const tipo = /<syllabic>\s*(\w+)\s*<\/syllabic>/.exec(lyric)?.[1];
88699
- if (tipo !== "begin" && tipo !== "middle") return lyric;
88700
- return lyric.replace(/(<text\b[^>]*>)([\s\S]*?)(<\/text>)/, (m, a, texto4, b) => {
88701
- if (texto4.trimEnd().endsWith("-")) return m;
88702
- silabas2++;
88703
- return `${a}${texto4.trimEnd()}-${b}`;
88704
- });
88705
- });
88706
- if (silabas2) correcciones.push(`${silabas2} s\xEDlabas unidas de la letra se marcaron con su guion.`);
88707
- let tempos = 0;
88708
- salida = salida.replace(/<metronome\b[^>]*>[\s\S]*?<\/metronome>/g, (met) => {
88709
- const unidad = /<beat-unit>\s*([\w-]+)\s*<\/beat-unit>/.exec(met)?.[1];
88710
- const porMinuto = /<per-minute>\s*([\d.]+)\s*<\/per-minute>/.exec(met)?.[1];
88711
- const negras = unidad ? NEGRAS_POR_FIGURA[unidad] : void 0;
88712
- if (!unidad || !porMinuto || negras === void 0 || /<beat-unit>[\s\S]*<beat-unit>/.test(met)) return met;
88713
- const puntillos = (met.match(/<beat-unit-dot\s*\/>/g) ?? []).length;
88714
- const factor = negras * (puntillos === 0 ? 1 : puntillos === 1 ? 1.5 : 1.75);
88715
- if (factor === 1) return met;
88716
- tempos++;
88717
- const bpm = Math.round(Number.parseFloat(porMinuto) * factor * 100) / 100;
88718
- return met.replace(/<beat-unit>[\s\S]*?<\/beat-unit>/, "<beat-unit>quarter</beat-unit>").replace(/<beat-unit-dot\s*\/>/g, "").replace(/<per-minute>[\s\S]*?<\/per-minute>/, `<per-minute>${bpm}</per-minute>`);
88719
- });
88720
- if (tempos) correcciones.push(`${tempos} indicaciones de tempo se pasaron a negras (mismo pulso).`);
88721
- return { xml: salida, bytes: new TextEncoder().encode(salida), correcciones };
88722
- }
88723
- function compasesDe(parte) {
88724
- return [...parte.matchAll(/(<measure\b[^>]*>)([\s\S]*?)<\/measure>/g)].map((m) => ({
88725
- inicio: m.index,
88726
- fin: m.index + m[0].length,
88727
- apertura: m[1],
88728
- contenido: m[2]
88729
- }));
89222
+ const abajo = PASOS.find((p) => p.semitonos === clase - 1);
89223
+ if (abajo) return { paso: abajo.paso, alteracion: 1, octava };
89224
+ throw new Error(`altura MIDI no representable: ${midi}`);
88730
89225
  }
88731
- function rearmar(parte, compases, nuevos) {
88732
- let salida = "";
88733
- let cursor = 0;
88734
- compases.forEach((c, i2) => {
88735
- salida += parte.slice(cursor, c.inicio) + c.apertura + nuevos[i2] + "</measure>";
88736
- cursor = c.fin;
88737
- });
88738
- return salida + parte.slice(cursor);
89226
+ function elementoTranspose(semitonos, numero2) {
89227
+ if (!semitonos) return [];
89228
+ const octavas = Math.trunc(semitonos / 12);
89229
+ const resto = semitonos - octavas * 12;
89230
+ const diatonico = Math.sign(resto) * PASOS_POR_SEMITONOS[Math.abs(resto)];
89231
+ return [
89232
+ numero2 === null ? "<transpose>" : `<transpose number="${numero2}">`,
89233
+ `<diatonic>${diatonico}</diatonic>`,
89234
+ `<chromatic>${resto}</chromatic>`,
89235
+ ...octavas ? [`<octave-change>${octavas}</octave-change>`] : [],
89236
+ "</transpose>"
89237
+ ];
88739
89238
  }
88740
- function tiempoAl(pedazo) {
88741
- let t = 0;
88742
- for (const m of pedazo.matchAll(/<(note|backup|forward)\b[\s\S]*?<\/\1>/g)) {
88743
- const dur = entero(m[0], /<duration>\s*(\d+)\s*<\/duration>/) ?? 0;
88744
- if (m[1] === "backup") t -= dur;
88745
- else if (m[1] === "forward") t += dur;
88746
- else if (!/<chord\s*\/>/.test(m[0]) && !/<grace\b/.test(m[0])) t += dur;
89239
+ function claveDelPentagrama(staff, bar) {
89240
+ if (staff.isPercussion) return { signo: "percussion", linea: 2, lineas: 5, conAfinacion: false };
89241
+ if (staff.showTablature && staff.tuning.length > 0)
89242
+ return { signo: "TAB", linea: 5, lineas: staff.tuning.length, conAfinacion: true };
89243
+ switch (bar.clef) {
89244
+ case _barrel_exports$4.Clef.C3:
89245
+ return { signo: "C", linea: 3, lineas: 5, conAfinacion: false };
89246
+ case _barrel_exports$4.Clef.C4:
89247
+ return { signo: "C", linea: 4, lineas: 5, conAfinacion: false };
89248
+ case _barrel_exports$4.Clef.F4:
89249
+ return { signo: "F", linea: 4, lineas: 5, conAfinacion: false };
89250
+ case _barrel_exports$4.Clef.Neutral:
89251
+ return { signo: "percussion", linea: 2, lineas: 5, conAfinacion: false };
89252
+ default:
89253
+ return { signo: "G", linea: 2, lineas: 5, conAfinacion: false };
88747
89254
  }
88748
- return t;
88749
89255
  }
88750
- function moverAtributosDeFinDeCompas(parte) {
88751
- const compases = compasesDe(parte);
88752
- let cuantos = 0;
88753
- let arrastre = "";
88754
- const nuevos = compases.map((c) => {
88755
- let contenido = c.contenido;
88756
- const primeraNota = contenido.search(/<note\b/);
88757
- if (primeraNota >= 0) {
88758
- let llevar = "";
88759
- let alPrincipio = "";
88760
- const despues = contenido.slice(primeraNota);
88761
- contenido = contenido.slice(0, primeraNota) + despues.replace(/<attributes\b[^>]*>([\s\S]*?)<\/attributes>/g, (bloque, adentro, donde) => {
88762
- const hijos = adentro.match(HIJO_QUE_SE_MUEVE);
88763
- if (!hijos) return bloque;
88764
- cuantos += hijos.length;
88765
- if (tiempoAl(despues.slice(0, donde)) <= 0) alPrincipio += hijos.join("");
88766
- else llevar += hijos.join("");
88767
- const resto = adentro.replace(HIJO_QUE_SE_MUEVE, "");
88768
- return resto.trim() ? `<attributes>${resto}</attributes>` : "";
88769
- });
88770
- const entra2 = arrastre + alPrincipio;
88771
- arrastre = llevar;
88772
- return (entra2 ? `<attributes>${entra2}</attributes>` : "") + contenido;
89256
+ function gruposDeTresillo(beats) {
89257
+ const abren = /* @__PURE__ */ new Set();
89258
+ const cierran = /* @__PURE__ */ new Set();
89259
+ let i2 = 0;
89260
+ while (i2 < beats.length) {
89261
+ const b = beats[i2];
89262
+ if (b.tupletNumerator <= 0 || b.tupletDenominator <= 0) {
89263
+ i2++;
89264
+ continue;
88773
89265
  }
88774
- const entra = arrastre;
88775
- arrastre = "";
88776
- return (entra ? `<attributes>${entra}</attributes>` : "") + contenido;
88777
- });
88778
- return cuantos ? { texto: rearmar(parte, compases, nuevos), cuantos } : { texto: parte, cuantos: 0 };
89266
+ let j = i2;
89267
+ while (j + 1 < beats.length && beats[j + 1].tupletNumerator === b.tupletNumerator && beats[j + 1].tupletDenominator === b.tupletDenominator && (b.tupletGroup === null || beats[j + 1].tupletGroup === b.tupletGroup))
89268
+ j++;
89269
+ abren.add(i2);
89270
+ cierran.add(j);
89271
+ i2 = j + 1;
89272
+ }
89273
+ return { abren, cierran };
88779
89274
  }
88780
- function separarAcordesEntrePentagramas(parte) {
88781
- const compases = compasesDe(parte);
88782
- let cuantos = 0;
88783
- const nuevos = compases.map((c) => {
88784
- const notas = [...c.contenido.matchAll(/<note\b[\s\S]*?<\/note>/g)];
88785
- if (notas.length < 2) return c.contenido;
88786
- let salida = "";
88787
- let cursor = 0;
88788
- let i2 = 0;
88789
- while (i2 < notas.length) {
88790
- const grupo = [notas[i2]];
88791
- let j = i2 + 1;
88792
- while (j < notas.length) {
88793
- const anterior = notas[j - 1];
88794
- const entre = c.contenido.slice(anterior.index + anterior[0].length, notas[j].index);
88795
- if (entre.trim() || !/<chord\s*\/>/.test(notas[j][0])) break;
88796
- grupo.push(notas[j]);
88797
- j++;
88798
- }
88799
- i2 = j;
88800
- const pent = (n) => entero(n[0], /<staff>\s*(\d+)\s*<\/staff>/) ?? 1;
88801
- const cabeza = grupo[0];
88802
- const pentDeCabeza = pent(cabeza);
88803
- const ajenas = grupo.filter((n) => pent(n) !== pentDeCabeza);
88804
- const durCabeza = entero(cabeza[0], /<duration>\s*(\d+)\s*<\/duration>/);
88805
- if (ajenas.length === 0 || /<chord\s*\/>/.test(cabeza[0]) || grupo.some((n) => /<grace\b|<cue\s*\/>/.test(n[0])) || durCabeza === null) {
88806
- continue;
88807
- }
88808
- cuantos += ajenas.length;
88809
- const propias = grupo.filter((n) => pent(n) === pentDeCabeza).map((n) => n[0]);
88810
- let texto4 = propias.join("");
88811
- const porPentagrama = /* @__PURE__ */ new Map();
88812
- for (const n of ajenas) porPentagrama.set(pent(n), [...porPentagrama.get(pent(n)) ?? [], n[0]]);
88813
- for (const notasDelPent of porPentagrama.values()) {
88814
- const primera = notasDelPent[0].replace(/<chord\s*\/>\s*/, "");
88815
- const durPrimera = entero(primera, /<duration>\s*(\d+)\s*<\/duration>/) ?? durCabeza;
88816
- texto4 += `<backup><duration>${durCabeza}</duration></backup>` + primera + notasDelPent.slice(1).join("");
88817
- const delta = durCabeza - durPrimera;
88818
- if (delta > 0) texto4 += `<forward><duration>${delta}</duration></forward>`;
88819
- else if (delta < 0) texto4 += `<backup><duration>${-delta}</duration></backup>`;
88820
- }
88821
- const ultimo = grupo[grupo.length - 1];
88822
- salida += c.contenido.slice(cursor, cabeza.index) + texto4;
88823
- cursor = ultimo.index + ultimo[0].length;
88824
- }
88825
- return salida + c.contenido.slice(cursor);
88826
- });
88827
- return cuantos ? { texto: rearmar(parte, compases, nuevos), cuantos } : { texto: parte, cuantos: 0 };
89275
+ function direccionDeLasPalabras(texto4) {
89276
+ return SALTO_POR_CLAVE.get(claveDeSalto(texto4)) ?? null;
88828
89277
  }
88829
- function sacarSaltosDeLaIndicacion(parte) {
88830
- let cuantos = 0;
88831
- const texto4 = parte.replace(/<direction\b[^>]*>[\s\S]*?<\/direction>/g, (dir, desde, todo) => {
88832
- const sonido = /<sound\b([^>]*?)\/?>/.exec(dir)?.[1];
88833
- if (!sonido) return dir;
88834
- const atributos = ATRIBUTOS_DE_SALTO.map((a) => new RegExp(`\\b${a}="([^"]*)"`).exec(sonido)?.[0]).filter((a) => a !== void 0);
88835
- if (atributos.length === 0) return dir;
88836
- const copia = `<sound ${atributos.join(" ")}/>`;
88837
- if (todo.slice(desde + dir.length).trimStart().startsWith(copia)) return dir;
88838
- cuantos++;
88839
- return dir + copia;
88840
- });
88841
- return { texto: texto4, cuantos };
89278
+ function saltosDelCompas(master) {
89279
+ const s = [];
89280
+ for (const [direccion, salto] of SALTOS) {
89281
+ if (!master?.directions?.has(direccion)) continue;
89282
+ s.push('<direction placement="above">', "<direction-type>");
89283
+ s.push(salto.simbolo ? `<${salto.simbolo}/>` : `<words>${escapar(salto.texto ?? "")}</words>`);
89284
+ s.push("</direction-type>", `<sound ${salto.sonido}/>`, "</direction>");
89285
+ }
89286
+ return s;
88842
89287
  }
88843
- function compactarVoces(parte) {
88844
- const porPentagrama = /* @__PURE__ */ new Map();
88845
- const pentagramaDeVoz = /* @__PURE__ */ new Map();
88846
- for (const m of parte.matchAll(/<note\b[\s\S]*?<\/note>/g)) {
88847
- const voz = entero(m[0], /<voice>\s*(\d+)\s*<\/voice>/);
88848
- if (voz === null) continue;
88849
- const pent = entero(m[0], /<staff>\s*(\d+)\s*<\/staff>/) ?? 1;
88850
- const lista2 = porPentagrama.get(pent) ?? [];
88851
- if (!lista2.includes(voz)) lista2.push(voz);
88852
- porPentagrama.set(pent, lista2);
88853
- if (!pentagramaDeVoz.has(voz)) pentagramaDeVoz.set(voz, pent);
89288
+ function nombreDeFigura(duracion) {
89289
+ return NOMBRE_DE_LA_FIGURA2.get(duracion) ?? "quarter";
89290
+ }
89291
+ function instrumentosDePercusion(track) {
89292
+ const usadas = /* @__PURE__ */ new Set();
89293
+ for (const staff of track.staves)
89294
+ for (const bar of staff.bars)
89295
+ for (const voice of bar.voices)
89296
+ for (const beat of voice.beats)
89297
+ for (const note of beat.notes)
89298
+ if (note.percussionArticulation >= 0) usadas.add(note.percussionArticulation);
89299
+ const ids = /* @__PURE__ */ new Map();
89300
+ for (const art of [...usadas].sort((a, b) => a - b))
89301
+ ids.set(art, `P${track.index + 1}-I${art + 1}`);
89302
+ return ids;
89303
+ }
89304
+ function posicionDePercusion(staffLine) {
89305
+ const indiceDeB = 6;
89306
+ const bruto = indiceDeB + (5 - staffLine);
89307
+ const paso = PASOS[(bruto % 7 + 7) % 7].paso;
89308
+ return { paso, octava: 4 + Math.floor(bruto / 7) };
89309
+ }
89310
+ function escribirNota(note, beat, staff, opciones) {
89311
+ const s = ["<note>"];
89312
+ const esAdorno = beat.graceType !== _barrel_exports$4.GraceType.None;
89313
+ if (esAdorno) s.push(beat.graceType === _barrel_exports$4.GraceType.BeforeBeat ? "<grace/>" : '<grace slash="yes"/>');
89314
+ if (opciones.esAcorde) s.push("<chord/>");
89315
+ const esGolpeDeKit = staff.isPercussion && note.percussionArticulation >= 0;
89316
+ if (esGolpeDeKit) {
89317
+ const art = staff.track.percussionArticulations[note.percussionArticulation];
89318
+ const { paso, octava } = posicionDePercusion(art?.staffLine ?? 5);
89319
+ s.push("<unpitched>");
89320
+ s.push(`<display-step>${paso}</display-step>`);
89321
+ s.push(`<display-octave>${octava}</display-octave>`);
89322
+ s.push("</unpitched>");
89323
+ } else {
89324
+ const { paso, alteracion, octava } = alturaMusicXml(
89325
+ note.calculateRealValue(false, false) - staff.displayTranspositionPitch,
89326
+ opciones.fifths,
89327
+ note.accidentalMode
89328
+ );
89329
+ s.push("<pitch>");
89330
+ s.push(`<step>${paso}</step>`);
89331
+ if (alteracion !== 0) s.push(`<alter>${alteracion}</alter>`);
89332
+ s.push(`<octave>${octava}</octave>`);
89333
+ s.push("</pitch>");
88854
89334
  }
88855
- const nueva = /* @__PURE__ */ new Map();
88856
- let hayCambio = false;
88857
- for (const [pent, voces] of porPentagrama) {
88858
- ;
88859
- [...voces].sort((a, b) => a - b).forEach((voz, i2) => {
88860
- nueva.set(`${pent}|${voz}`, i2 + 1);
88861
- if (voz !== i2 + 1) hayCambio = true;
88862
- });
89335
+ if (!esAdorno) s.push(`<duration>${opciones.duracion}</duration>`);
89336
+ if (note.isTieDestination) s.push('<tie type="stop"/>');
89337
+ if (note.isTieOrigin) s.push('<tie type="start"/>');
89338
+ const idDeInstrumento = opciones.instrumentos.get(note.percussionArticulation);
89339
+ if (esGolpeDeKit && idDeInstrumento) s.push(`<instrument id="${idDeInstrumento}"/>`);
89340
+ s.push(`<voice>${opciones.numeroDeVoz}</voice>`);
89341
+ s.push(...elemento("type", nombreDeFigura(beat.duration)));
89342
+ for (let d = 0; d < beat.dots; d++) s.push("<dot/>");
89343
+ if (beat.tupletNumerator > 0 && beat.tupletDenominator > 0) {
89344
+ s.push("<time-modification>");
89345
+ s.push(`<actual-notes>${beat.tupletNumerator}</actual-notes>`);
89346
+ s.push(`<normal-notes>${beat.tupletDenominator}</normal-notes>`);
89347
+ s.push("</time-modification>");
88863
89348
  }
88864
- if (!hayCambio) return { texto: parte, cambio: null };
88865
- const renumerar = (bloque, pentPorOmision) => {
88866
- const voz = entero(bloque, /<voice>\s*(\d+)\s*<\/voice>/);
88867
- if (voz === null) return bloque;
88868
- const pent = entero(bloque, /<staff>\s*(\d+)\s*<\/staff>/) ?? pentPorOmision(voz);
88869
- const n = nueva.get(`${pent}|${voz}`);
88870
- return n === void 0 ? bloque : bloque.replace(/<voice>\s*\d+\s*<\/voice>/, `<voice>${n}</voice>`);
88871
- };
88872
- const texto4 = parte.replace(/<note\b[\s\S]*?<\/note>/g, (b) => renumerar(b, () => 1)).replace(/<forward\b[\s\S]*?<\/forward>/g, (b) => renumerar(b, (v) => pentagramaDeVoz.get(v) ?? 1));
88873
- const id2 = /<part\b[^>]*\bid="([^"]*)"/.exec(parte)?.[1] ?? "?";
88874
- const detalle = [...porPentagrama].map(([p, vs]) => `pentagrama ${p}: voces ${[...vs].sort((a, b) => a - b).join(", ")}`).join("; ");
88875
- return { texto: texto4, cambio: `Parte ${id2}: las voces se renumeraron por pentagrama (${detalle}).` };
89349
+ if (note.isDead) s.push("<notehead>x</notehead>");
89350
+ else if (esGolpeDeKit) {
89351
+ const art = staff.track.percussionArticulations[note.percussionArticulation];
89352
+ if (art?.noteHeadDefault === _barrel_exports$4.MusicFontSymbol.NoteheadXBlack)
89353
+ s.push("<notehead>x</notehead>");
89354
+ } else if (note.isGhost) s.push('<notehead parentheses="yes">normal</notehead>');
89355
+ if (opciones.numeroDePentagrama !== null) s.push(`<staff>${opciones.numeroDePentagrama}</staff>`);
89356
+ const notaciones = [];
89357
+ if (note.isTieDestination) notaciones.push('<tied type="stop"/>');
89358
+ if (note.isTieOrigin) notaciones.push('<tied type="start"/>');
89359
+ if (!opciones.esAcorde) {
89360
+ const n = opciones.numeroDeVoz;
89361
+ if (beat.isLegatoOrigin && !beat.isLegatoDestination) notaciones.push(`<slur type="start" number="${n}"/>`);
89362
+ if (beat.isLegatoDestination && !beat.isLegatoOrigin) notaciones.push(`<slur type="stop" number="${n}"/>`);
89363
+ }
89364
+ if (opciones.abreTresillo) notaciones.push('<tuplet type="start" number="1"/>');
89365
+ if (opciones.cierraTresillo) notaciones.push('<tuplet type="stop" number="1"/>');
89366
+ const tecnica = [];
89367
+ if (note.string > 0 && staff.tuning.length > 0) {
89368
+ tecnica.push(`<string>${staff.tuning.length - note.string + 1}</string>`);
89369
+ tecnica.push(`<fret>${note.fret}</fret>`);
89370
+ }
89371
+ const ligado = (desde, hasta) => hasta >= desde ? "hammer-on" : "pull-off";
89372
+ const origen = note.hammerPullOrigin;
89373
+ if (origen !== null) {
89374
+ const nombre = ligado(origen.fret, note.fret);
89375
+ tecnica.push(`<${nombre} type="stop"/>`);
89376
+ }
89377
+ const destino = note.hammerPullDestination;
89378
+ if (note.isHammerPullOrigin && destino) {
89379
+ const nombre = ligado(note.fret, destino.fret);
89380
+ tecnica.push(`<${nombre} type="start">${nombre === "hammer-on" ? "H" : "P"}</${nombre}>`);
89381
+ }
89382
+ if (note.harmonicType !== _barrel_exports$4.HarmonicType.None) tecnica.push("<harmonic/>");
89383
+ if (beat.isPalmMute) tecnica.push("<other-technical>palm-mute</other-technical>");
89384
+ if (beat.isLetRing) tecnica.push("<other-technical>let-ring</other-technical>");
89385
+ if (tecnica.length > 0) notaciones.push("<technical>", ...tecnica, "</technical>");
89386
+ const articulaciones = [];
89387
+ if (note.accentuated === _barrel_exports$4.AccentuationType.Normal) articulaciones.push("<accent/>");
89388
+ else if (note.accentuated === _barrel_exports$4.AccentuationType.Heavy)
89389
+ articulaciones.push("<strong-accent/>");
89390
+ else if (note.accentuated === _barrel_exports$4.AccentuationType.Tenuto)
89391
+ articulaciones.push("<tenuto/>");
89392
+ if (note.isStaccato) articulaciones.push("<staccato/>");
89393
+ if (articulaciones.length > 0)
89394
+ notaciones.push("<articulations>", ...articulaciones, "</articulations>");
89395
+ if (notaciones.length > 0) s.push("<notations>", ...notaciones, "</notations>");
89396
+ if (!opciones.esAcorde) s.push(...letraDelGolpe(beat));
89397
+ s.push("</note>");
89398
+ return s;
88876
89399
  }
88877
- function leerOrtografia(xml) {
88878
- return leerDelArchivo(xml).ortografia;
89400
+ function letraDelGolpe(beat) {
89401
+ if (!beat.lyrics) return [];
89402
+ const s = [];
89403
+ beat.lyrics.forEach((verso, i2) => {
89404
+ if (!verso) return;
89405
+ const sigue = verso.endsWith("-") && verso.length > 1;
89406
+ let anterior = beat.previousBeat;
89407
+ while (anterior && !anterior.lyrics?.[i2]) anterior = anterior.previousBeat;
89408
+ const viene = !!anterior?.lyrics?.[i2]?.endsWith("-");
89409
+ const silabico = sigue ? viene ? "middle" : "begin" : viene ? "end" : "single";
89410
+ s.push(`<lyric number="${i2 + 1}">`);
89411
+ s.push(`<syllabic>${silabico}</syllabic>`);
89412
+ s.push(...elemento("text", sigue ? verso.slice(0, -1) : verso));
89413
+ s.push("</lyric>");
89414
+ });
89415
+ return s;
88879
89416
  }
88880
- function leerDelArchivo(xml) {
88881
- const ortografia = /* @__PURE__ */ new Map();
88882
- const bajos = /* @__PURE__ */ new Map();
88883
- const matices = /* @__PURE__ */ new Map();
88884
- let iParte = -1;
88885
- for (const parte of xml.matchAll(/<part\b[^>]*>([\s\S]*?)<\/part>/g)) {
88886
- iParte++;
88887
- let divisiones = 1;
88888
- let transposicion = 0;
88889
- let iCompas = -1;
88890
- for (const compas of parte[1].matchAll(/<measure\b[^>]*>([\s\S]*?)<\/measure>/g)) {
88891
- iCompas++;
88892
- let tic = 0;
88893
- let ticAnterior = 0;
89417
+ function escribirSilencio2(beat, duracion, numeroDeVoz, numeroDePentagrama) {
89418
+ const s = ["<note>", "<rest/>", `<duration>${duracion}</duration>`];
89419
+ s.push(`<voice>${numeroDeVoz}</voice>`);
89420
+ s.push(...elemento("type", nombreDeFigura(beat.duration)));
89421
+ for (let d = 0; d < beat.dots; d++) s.push("<dot/>");
89422
+ if (beat.tupletNumerator > 0 && beat.tupletDenominator > 0) {
89423
+ s.push("<time-modification>");
89424
+ s.push(`<actual-notes>${beat.tupletNumerator}</actual-notes>`);
89425
+ s.push(`<normal-notes>${beat.tupletDenominator}</normal-notes>`);
89426
+ s.push("</time-modification>");
89427
+ }
89428
+ if (numeroDePentagrama !== null) s.push(`<staff>${numeroDePentagrama}</staff>`);
89429
+ s.push(...letraDelGolpe(beat));
89430
+ s.push("</note>");
89431
+ return s;
89432
+ }
89433
+ function exportarMusicXml(score) {
89434
+ const l = [];
89435
+ l.push('<?xml version="1.0" encoding="UTF-8"?>');
89436
+ l.push('<!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 4.0 Partwise//EN"');
89437
+ l.push(' "http://www.musicxml.org/dtds/partwise.dtd">');
89438
+ l.push('<score-partwise version="4.0">');
89439
+ if (score.title) l.push("<work>", ...elemento("work-title", score.title), "</work>");
89440
+ l.push(...elemento("movement-title", score.subTitle));
89441
+ l.push("<identification>");
89442
+ if (score.music) l.push(`<creator type="composer">${escapar(score.music)}</creator>`);
89443
+ else if (score.artist) l.push(`<creator type="composer">${escapar(score.artist)}</creator>`);
89444
+ if (score.words) l.push(`<creator type="lyricist">${escapar(score.words)}</creator>`);
89445
+ l.push(...elemento("rights", score.copyright));
89446
+ l.push("<encoding>", "<software>Mandioca Tabs</software>", "</encoding>");
89447
+ l.push("</identification>");
89448
+ const instrumentosPorPista = /* @__PURE__ */ new Map();
89449
+ l.push("<part-list>");
89450
+ for (const track of score.tracks) {
89451
+ const id2 = `P${track.index + 1}`;
89452
+ l.push(`<score-part id="${id2}">`);
89453
+ l.push(...elemento("part-name", track.name || `Pista ${track.index + 1}`));
89454
+ l.push(...elemento("part-abbreviation", track.shortName));
89455
+ const esPercusion = track.staves.some((st) => st.isPercussion);
89456
+ const instrumentos = esPercusion ? instrumentosDePercusion(track) : /* @__PURE__ */ new Map();
89457
+ instrumentosPorPista.set(track.index, instrumentos);
89458
+ if (esPercusion) {
89459
+ for (const [art, idInstrumento] of instrumentos) {
89460
+ const a = track.percussionArticulations[art];
89461
+ l.push(`<score-instrument id="${idInstrumento}">`);
89462
+ l.push(...elemento("instrument-name", a?.elementType || `Percusi\xF3n ${art + 1}`));
89463
+ l.push("</score-instrument>");
89464
+ }
89465
+ for (const [art, idInstrumento] of instrumentos) {
89466
+ const a = track.percussionArticulations[art];
89467
+ l.push(`<midi-instrument id="${idInstrumento}">`);
89468
+ l.push("<midi-channel>10</midi-channel>");
89469
+ l.push(`<midi-unpitched>${(a?.outputMidiNumber ?? 36) + 1}</midi-unpitched>`);
89470
+ l.push("</midi-instrument>");
89471
+ }
89472
+ } else {
89473
+ l.push(`<score-instrument id="${id2}-I1">`);
89474
+ l.push(...elemento("instrument-name", track.name || `Pista ${track.index + 1}`));
89475
+ l.push("</score-instrument>");
89476
+ l.push(`<midi-instrument id="${id2}-I1">`);
89477
+ l.push(`<midi-channel>${track.playbackInfo.primaryChannel + 1}</midi-channel>`);
89478
+ l.push(`<midi-program>${track.playbackInfo.program + 1}</midi-program>`);
89479
+ l.push(`<volume>${Math.round(track.playbackInfo.volume / 16 * 100)}</volume>`);
89480
+ l.push(`<pan>${Math.round((track.playbackInfo.balance - 8) / 8 * 90)}</pan>`);
89481
+ l.push("</midi-instrument>");
89482
+ }
89483
+ l.push("</score-part>");
89484
+ }
89485
+ l.push("</part-list>");
89486
+ for (const track of score.tracks) {
89487
+ const instrumentos = instrumentosPorPista.get(track.index) ?? /* @__PURE__ */ new Map();
89488
+ l.push(`<part id="P${track.index + 1}">`);
89489
+ const pentagramas = track.staves;
89490
+ if (pentagramas.length === 0) {
89491
+ l.push("</part>");
89492
+ continue;
89493
+ }
89494
+ let armaduraAnterior = null;
89495
+ let compasAnterior = null;
89496
+ let clavesAnteriores = null;
89497
+ const cantidadDeCompases = Math.max(...pentagramas.map((st) => st.bars.length));
89498
+ for (let indiceDeCompas2 = 0; indiceDeCompas2 < cantidadDeCompases; indiceDeCompas2++) {
89499
+ const master = score.masterBars[indiceDeCompas2];
89500
+ l.push(`<measure number="${indiceDeCompas2 + 1}">`);
89501
+ const fifths = (pentagramas[0]?.bars[indiceDeCompas2] ?? pentagramas[0]?.bars[0])?.keySignature ?? master?.keySignature ?? 0;
89502
+ const modo = master?.keySignatureType === _barrel_exports$4.KeySignatureType.Minor ? "minor" : "major";
89503
+ const armadura = `${fifths}/${modo}`;
89504
+ const numerador = master?.timeSignatureNumerator ?? 4;
89505
+ const denominador = master?.timeSignatureDenominator ?? 4;
89506
+ const compas = `${numerador}/${denominador}`;
89507
+ const claves = pentagramas.map((st, k) => {
89508
+ const bar = st.bars[indiceDeCompas2] ?? st.bars[0];
89509
+ return { pentagrama: st, numero: k + 1, ...claveDelPentagrama(st, bar) };
89510
+ });
89511
+ const firmaDeClaves = claves.map((c) => `${c.signo}${c.linea}`).join(",");
89512
+ const cambios = [];
89513
+ if (indiceDeCompas2 === 0) cambios.push(`<divisions>${DIVISIONES_POR_NEGRA}</divisions>`);
89514
+ if (armadura !== armaduraAnterior) {
89515
+ cambios.push("<key>");
89516
+ cambios.push(`<fifths>${fifths}</fifths>`);
89517
+ cambios.push(`<mode>${modo}</mode>`);
89518
+ cambios.push("</key>");
89519
+ armaduraAnterior = armadura;
89520
+ }
89521
+ if (compas !== compasAnterior) {
89522
+ cambios.push("<time>");
89523
+ cambios.push(`<beats>${numerador}</beats>`);
89524
+ cambios.push(`<beat-type>${denominador}</beat-type>`);
89525
+ cambios.push("</time>");
89526
+ compasAnterior = compas;
89527
+ }
89528
+ if (firmaDeClaves !== clavesAnteriores) {
89529
+ if (pentagramas.length > 1 && clavesAnteriores === null)
89530
+ cambios.push(`<staves>${pentagramas.length}</staves>`);
89531
+ const num = (clave) => pentagramas.length > 1 ? ` number="${clave.numero}"` : "";
89532
+ for (const clave of claves) {
89533
+ cambios.push(`<clef${num(clave)}>`);
89534
+ cambios.push(`<sign>${clave.signo}</sign>`);
89535
+ cambios.push(`<line>${clave.linea}</line>`);
89536
+ cambios.push("</clef>");
89537
+ }
89538
+ for (const clave of claves) {
89539
+ cambios.push(`<staff-details${num(clave)}>`);
89540
+ cambios.push(`<staff-lines>${clave.lineas}</staff-lines>`);
89541
+ if (clave.conAfinacion)
89542
+ clave.pentagrama.tuning.forEach((midi, i2) => {
89543
+ const linea = clave.pentagrama.tuning.length - i2;
89544
+ const { paso, alteracion, octava } = alturaMusicXml(midi, fifths);
89545
+ cambios.push(`<staff-tuning line="${linea}">`);
89546
+ cambios.push(`<tuning-step>${paso}</tuning-step>`);
89547
+ if (alteracion !== 0) cambios.push(`<tuning-alter>${alteracion}</tuning-alter>`);
89548
+ cambios.push(`<tuning-octave>${octava}</tuning-octave>`);
89549
+ cambios.push("</staff-tuning>");
89550
+ });
89551
+ cambios.push("</staff-details>");
89552
+ }
89553
+ clavesAnteriores = firmaDeClaves;
89554
+ }
89555
+ if (indiceDeCompas2 === 0) {
89556
+ const distintas = new Set(pentagramas.map((st) => st.displayTranspositionPitch)).size > 1;
89557
+ pentagramas.forEach((st, k) => {
89558
+ if (k > 0 && !distintas) return;
89559
+ cambios.push(...elementoTranspose(st.displayTranspositionPitch, distintas ? k + 1 : null));
89560
+ });
89561
+ }
89562
+ if (cambios.length > 0) l.push("<attributes>", ...cambios, "</attributes>");
89563
+ const casilla = master?.alternateEndings ?? 0;
89564
+ const casillaAntes = score.masterBars[indiceDeCompas2 - 1]?.alternateEndings ?? 0;
89565
+ const casillaDespues = score.masterBars[indiceDeCompas2 + 1]?.alternateEndings ?? 0;
89566
+ const numerosDeCasilla = [0, 1, 2, 3, 4, 5, 6, 7].filter((b) => casilla & 1 << b).map((b) => b + 1).join(", ");
89567
+ if (master?.isRepeatStart || casilla && casilla !== casillaAntes) {
89568
+ l.push('<barline location="left">');
89569
+ if (master?.isRepeatStart) l.push("<bar-style>heavy-light</bar-style>");
89570
+ if (casilla && casilla !== casillaAntes) l.push(`<ending number="${numerosDeCasilla}" type="start"/>`);
89571
+ if (master?.isRepeatStart) l.push('<repeat direction="forward"/>');
89572
+ l.push("</barline>");
89573
+ }
89574
+ if (master?.section?.text || master?.section?.marker)
89575
+ l.push(
89576
+ '<direction placement="above">',
89577
+ "<direction-type>",
89578
+ `<rehearsal>${escapar(master.section.marker || master.section.text)}</rehearsal>`,
89579
+ "</direction-type>",
89580
+ "</direction>"
89581
+ );
89582
+ l.push(...saltosDelCompas(master));
89583
+ const ticsDelCompas = DIVISIONES_POR_NEGRA * 4 * numerador / denominador;
89584
+ const tempos = (master?.tempoAutomations ?? []).map((a) => ({ bpm: a.value, desde: a.ratioPosition }));
89585
+ if (indiceDeCompas2 === 0 && score.tempo && !tempos.some((t) => t.desde <= 1e-6))
89586
+ tempos.unshift({ bpm: score.tempo, desde: 0 });
89587
+ for (const t of tempos) {
89588
+ const bpm = Math.round(t.bpm);
89589
+ const offset = Math.round(t.desde * ticsDelCompas);
89590
+ l.push(
89591
+ '<direction placement="above">',
89592
+ "<direction-type>",
89593
+ "<metronome>",
89594
+ "<beat-unit>quarter</beat-unit>",
89595
+ `<per-minute>${bpm}</per-minute>`,
89596
+ "</metronome>",
89597
+ "</direction-type>",
89598
+ ...offset > 0 ? [`<offset>${offset}</offset>`] : [],
89599
+ `<sound tempo="${bpm}"/>`,
89600
+ "</direction>"
89601
+ );
89602
+ }
89603
+ let ultimoAvance = 0;
89604
+ let hayAlgoEscrito = false;
89605
+ for (const { pentagrama, numero: numero2 } of claves) {
89606
+ const bar = pentagrama.bars[indiceDeCompas2];
89607
+ if (!bar) continue;
89608
+ bar.voices.forEach((voice, indiceDeVoz) => {
89609
+ if (voice.beats.length === 0 || voice.isEmpty) return;
89610
+ if (hayAlgoEscrito && ultimoAvance > 0)
89611
+ l.push("<backup>", `<duration>${ultimoAvance}</duration>`, "</backup>");
89612
+ const { abren, cierran } = gruposDeTresillo(voice.beats);
89613
+ const numeroDePentagrama = pentagramas.length > 1 ? numero2 : null;
89614
+ let avance = 0;
89615
+ voice.beats.forEach((beat, i2) => {
89616
+ const duracion = beat.displayDuration;
89617
+ if (beat.graceType === _barrel_exports$4.GraceType.None) avance += duracion;
89618
+ if (beat.isRest || beat.notes.length === 0) {
89619
+ l.push(...escribirSilencio2(beat, duracion, indiceDeVoz + 1, numeroDePentagrama));
89620
+ return;
89621
+ }
89622
+ beat.notes.forEach((note, k) => {
89623
+ l.push(
89624
+ ...escribirNota(note, beat, pentagrama, {
89625
+ esAcorde: k > 0,
89626
+ fifths,
89627
+ duracion,
89628
+ // ★ La marca de tresillo va en la PRIMERA nota del golpe: es una marca del
89629
+ // golpe, no de cada cuerda que suena en él.
89630
+ abreTresillo: k === 0 && abren.has(i2),
89631
+ cierraTresillo: k === 0 && cierran.has(i2),
89632
+ instrumentos,
89633
+ numeroDeVoz: indiceDeVoz + 1,
89634
+ numeroDePentagrama
89635
+ })
89636
+ );
89637
+ });
89638
+ });
89639
+ ultimoAvance = avance;
89640
+ hayAlgoEscrito = true;
89641
+ });
89642
+ }
89643
+ const cierraCasilla = casilla !== 0 && casilla !== casillaDespues;
89644
+ if (master?.isRepeatEnd || master?.isDoubleBar || cierraCasilla) {
89645
+ l.push('<barline location="right">');
89646
+ if (master?.isRepeatEnd || master?.isDoubleBar)
89647
+ l.push(`<bar-style>${master.isRepeatEnd ? "light-heavy" : "light-light"}</bar-style>`);
89648
+ if (cierraCasilla)
89649
+ l.push(`<ending number="${numerosDeCasilla}" type="${master?.isRepeatEnd ? "stop" : "discontinue"}"/>`);
89650
+ if (master?.isRepeatEnd)
89651
+ l.push(`<repeat direction="backward" times="${Math.max(2, master.repeatCount)}"/>`);
89652
+ l.push("</barline>");
89653
+ }
89654
+ l.push("</measure>");
89655
+ }
89656
+ l.push("</part>");
89657
+ }
89658
+ l.push("</score-partwise>");
89659
+ return sangrar(l);
89660
+ }
89661
+ function sangrar(lineas) {
89662
+ const salida = [];
89663
+ let nivel = 0;
89664
+ for (const linea of lineas) {
89665
+ const cierra = linea.startsWith("</");
89666
+ if (cierra) nivel = Math.max(0, nivel - 1);
89667
+ salida.push(" ".repeat(nivel) + linea);
89668
+ const abre = /^<[^/?!]/.test(linea) && !linea.endsWith("/>") && !/<\/[^>]+>$/.test(linea);
89669
+ if (abre) nivel++;
89670
+ }
89671
+ return salida.join("\n") + "\n";
89672
+ }
89673
+ function exportarMusicXmlComprimido(score) {
89674
+ const xml = exportarMusicXml(score);
89675
+ const contenedor = `<?xml version="1.0" encoding="UTF-8"?>
89676
+ <container>
89677
+ <rootfiles>
89678
+ <rootfile full-path="partitura.musicxml" media-type="application/vnd.recordare.musicxml+xml"/>
89679
+ </rootfiles>
89680
+ </container>
89681
+ `;
89682
+ const codificador = new TextEncoder();
89683
+ return zipSync(
89684
+ {
89685
+ "META-INF/container.xml": [codificador.encode(contenedor), { level: 0 }],
89686
+ "partitura.musicxml": codificador.encode(xml)
89687
+ },
89688
+ // ⚠️ Fecha FIJA, no la de hoy: dos exportaciones iguales tienen que dar dos archivos
89689
+ // iguales byte a byte, o no hay prueba que pueda compararlos ni `diff` de git que
89690
+ // sirva. El porqué del valor concreto está en la constante — se tardó dos rojos en
89691
+ // encontrarlo, y ninguno de los dos era el que yo esperaba.
89692
+ { mtime: FECHA_FIJA_DEL_ZIP }
89693
+ );
89694
+ }
89695
+ var DIVISIONES_POR_NEGRA, FECHA_FIJA_DEL_ZIP, PASOS, NOMBRE_DE_LA_FIGURA2, ALTERACION_POR_MODO, PASOS_POR_SEMITONOS, D, SALTOS, claveDeSalto, SALTO_POR_CLAVE;
89696
+ var init_musicxml = __esm({
89697
+ "packages/io/src/musicxml.ts"() {
89698
+ init_alphaTab();
89699
+ init_esm();
89700
+ DIVISIONES_POR_NEGRA = 960;
89701
+ FECHA_FIJA_DEL_ZIP = 946728e6;
89702
+ PASOS = [
89703
+ { paso: "C", semitonos: 0 },
89704
+ { paso: "D", semitonos: 2 },
89705
+ { paso: "E", semitonos: 4 },
89706
+ { paso: "F", semitonos: 5 },
89707
+ { paso: "G", semitonos: 7 },
89708
+ { paso: "A", semitonos: 9 },
89709
+ { paso: "B", semitonos: 11 }
89710
+ ];
89711
+ NOMBRE_DE_LA_FIGURA2 = /* @__PURE__ */ new Map([
89712
+ [-4, "long"],
89713
+ [-2, "breve"],
89714
+ [1, "whole"],
89715
+ [2, "half"],
89716
+ [4, "quarter"],
89717
+ [8, "eighth"],
89718
+ [16, "16th"],
89719
+ [32, "32nd"],
89720
+ [64, "64th"],
89721
+ [128, "128th"],
89722
+ [256, "256th"]
89723
+ ]);
89724
+ ALTERACION_POR_MODO = { 2: 0, 3: 1, 4: 2, 5: -1, 6: -2 };
89725
+ PASOS_POR_SEMITONOS = [0, 1, 1, 2, 2, 3, 3, 4, 5, 5, 6, 6];
89726
+ D = _barrel_exports$4.Direction;
89727
+ SALTOS = [
89728
+ [D.TargetSegno, { simbolo: "segno", sonido: 'segno="segno"' }],
89729
+ [D.TargetSegnoSegno, { simbolo: "segno", sonido: 'segno="segno2"' }],
89730
+ [D.TargetCoda, { simbolo: "coda", sonido: 'coda="coda"' }],
89731
+ [D.TargetDoubleCoda, { simbolo: "coda", sonido: 'coda="coda2"' }],
89732
+ [D.TargetFine, { texto: "Fine", sonido: 'fine="yes"' }],
89733
+ [D.JumpDaCapo, { texto: "D.C.", sonido: 'dacapo="yes"' }],
89734
+ [D.JumpDaCapoAlCoda, { texto: "D.C. al Coda", sonido: 'dacapo="yes"' }],
89735
+ [D.JumpDaCapoAlDoubleCoda, { texto: "D.C. al Coda 2", sonido: 'dacapo="yes"' }],
89736
+ [D.JumpDaCapoAlFine, { texto: "D.C. al Fine", sonido: 'dacapo="yes"' }],
89737
+ [D.JumpDalSegno, { texto: "D.S.", sonido: 'dalsegno="segno"' }],
89738
+ [D.JumpDalSegnoAlCoda, { texto: "D.S. al Coda", sonido: 'dalsegno="segno"' }],
89739
+ [D.JumpDalSegnoAlDoubleCoda, { texto: "D.S. al Coda 2", sonido: 'dalsegno="segno"' }],
89740
+ [D.JumpDalSegnoAlFine, { texto: "D.S. al Fine", sonido: 'dalsegno="segno"' }],
89741
+ [D.JumpDalSegnoSegno, { texto: "D.S.S.", sonido: 'dalsegno="segno2"' }],
89742
+ [D.JumpDalSegnoSegnoAlCoda, { texto: "D.S.S. al Coda", sonido: 'dalsegno="segno2"' }],
89743
+ [D.JumpDalSegnoSegnoAlDoubleCoda, { texto: "D.S.S. al Coda 2", sonido: 'dalsegno="segno2"' }],
89744
+ [D.JumpDalSegnoSegnoAlFine, { texto: "D.S.S. al Fine", sonido: 'dalsegno="segno2"' }],
89745
+ [D.JumpDaCoda, { texto: "To Coda", sonido: 'tocoda="coda"' }],
89746
+ [D.JumpDaDoubleCoda, { texto: "To Coda 2", sonido: 'tocoda="coda2"' }]
89747
+ ];
89748
+ claveDeSalto = (texto4) => texto4.toLowerCase().replace(/double\s*coda/g, "coda 2").replace(/dal\s*segno\s*segno/g, "dss").replace(/dal\s*segno/g, "ds").replace(/da\s*capo/g, "dc").replace(/[^a-z0-9]/g, "");
89749
+ SALTO_POR_CLAVE = new Map(
89750
+ SALTOS.filter(([, s]) => s.texto).map(([d, s]) => [
89751
+ claveDeSalto(s.texto),
89752
+ _barrel_exports$4.Direction[d]
89753
+ ])
89754
+ );
89755
+ }
89756
+ });
89757
+
89758
+ // packages/io/src/musicxmlEntrada.ts
89759
+ function textoMusicXml(bytes) {
89760
+ if (bytes.length > 4 && bytes[0] === 80 && bytes[1] === 75) {
89761
+ let archivos;
89762
+ try {
89763
+ archivos = unzipSync(bytes);
89764
+ } catch {
89765
+ return null;
89766
+ }
89767
+ const contenedor = archivos["META-INF/container.xml"];
89768
+ if (!contenedor) return null;
89769
+ const ruta = /<rootfile\b[^>]*full-path="([^"]+)"/.exec(strFromU8(contenedor))?.[1];
89770
+ const raiz = ruta ? archivos[ruta] : void 0;
89771
+ return raiz ? decodificar(raiz) : null;
89772
+ }
89773
+ const texto4 = decodificar(bytes.subarray(0, Math.min(bytes.length, 4096)));
89774
+ if (!/<score-(partwise|timewise)\b/.test(texto4) && !/<!DOCTYPE\s+score-/.test(texto4)) return null;
89775
+ return decodificar(bytes);
89776
+ }
89777
+ function decodificar(bytes) {
89778
+ if (bytes[0] === 255 && bytes[1] === 254) return new TextDecoder("utf-16le").decode(bytes);
89779
+ if (bytes[0] === 254 && bytes[1] === 255) return new TextDecoder("utf-16be").decode(bytes);
89780
+ return new TextDecoder("utf-8").decode(bytes);
89781
+ }
89782
+ function prepararMusicXml(xml) {
89783
+ const correcciones = [];
89784
+ let atributosMovidos = 0;
89785
+ let acordesSeparados = 0;
89786
+ let saltosMovidos = 0;
89787
+ let salida = xml.replace(/<part\b[^>]*>[\s\S]*?<\/part>/g, (parteOriginal) => {
89788
+ const movidos = moverAtributosDeFinDeCompas(parteOriginal);
89789
+ atributosMovidos += movidos.cuantos;
89790
+ const separados = separarAcordesEntrePentagramas(movidos.texto);
89791
+ acordesSeparados += separados.cuantos;
89792
+ const saltos = sacarSaltosDeLaIndicacion(separados.texto);
89793
+ saltosMovidos += saltos.cuantos;
89794
+ const { texto: texto4, cambio } = compactarVoces(saltos.texto);
89795
+ if (cambio) correcciones.push(cambio);
89796
+ return texto4;
89797
+ });
89798
+ if (atributosMovidos) {
89799
+ correcciones.push(
89800
+ atributosMovidos === 1 ? "1 cambio de clave, armadura o comp\xE1s escrito entre las notas se puso al principio del comp\xE1s donde rige." : `${atributosMovidos} cambios de clave, armadura o comp\xE1s escritos entre las notas se pusieron al principio del comp\xE1s donde rigen.`
89801
+ );
89802
+ }
89803
+ if (acordesSeparados) {
89804
+ correcciones.push(
89805
+ acordesSeparados === 1 ? "1 nota de un acorde repartido entre los dos pentagramas se puso en el suyo." : `${acordesSeparados} notas de acordes repartidos entre los dos pentagramas se pusieron en el suyo.`
89806
+ );
89807
+ }
89808
+ if (saltosMovidos) correcciones.push(`${saltosMovidos} saltos (D.C., D.S., Coda, Fine, Segno) se leyeron de su indicaci\xF3n.`);
89809
+ let juntados = 0;
89810
+ salida = salida.replace(/<direction\b[^>]*>[\s\S]*?<\/direction>/g, (dir) => {
89811
+ const palabras = [...dir.matchAll(/<words\b[^>]*>([\s\S]*?)<\/words>/g)];
89812
+ if (palabras.length < 2) return dir;
89813
+ juntados++;
89814
+ const todo = palabras.map((p) => p[1].trim()).filter(Boolean).join("\n");
89815
+ let primero = true;
89816
+ return dir.replace(/<words\b([^>]*)>[\s\S]*?<\/words>/g, (_m, attrs) => {
89817
+ if (!primero) return "";
89818
+ primero = false;
89819
+ return `<words${attrs}>${todo}</words>`;
89820
+ });
89821
+ });
89822
+ if (juntados) correcciones.push(`${juntados} indicaciones de texto en varios renglones se juntaron en una.`);
89823
+ let silabas2 = 0;
89824
+ salida = salida.replace(/<lyric\b[^>]*>[\s\S]*?<\/lyric>/g, (lyric) => {
89825
+ const tipo = /<syllabic>\s*(\w+)\s*<\/syllabic>/.exec(lyric)?.[1];
89826
+ if (tipo !== "begin" && tipo !== "middle") return lyric;
89827
+ return lyric.replace(/(<text\b[^>]*>)([\s\S]*?)(<\/text>)/, (m, a, texto4, b) => {
89828
+ if (texto4.trimEnd().endsWith("-")) return m;
89829
+ silabas2++;
89830
+ return `${a}${texto4.trimEnd()}-${b}`;
89831
+ });
89832
+ });
89833
+ if (silabas2)
89834
+ correcciones.push(
89835
+ silabas2 === 1 ? "1 s\xEDlaba unida de la letra se marc\xF3 con su guion." : `${silabas2} s\xEDlabas unidas de la letra se marcaron con su guion.`
89836
+ );
89837
+ let tempos = 0;
89838
+ salida = salida.replace(/<metronome\b[^>]*>[\s\S]*?<\/metronome>/g, (met) => {
89839
+ const unidad = /<beat-unit>\s*([\w-]+)\s*<\/beat-unit>/.exec(met)?.[1];
89840
+ const porMinuto = /<per-minute>\s*([\d.]+)\s*<\/per-minute>/.exec(met)?.[1];
89841
+ const negras = unidad ? NEGRAS_POR_FIGURA[unidad] : void 0;
89842
+ if (!unidad || !porMinuto || negras === void 0 || /<beat-unit>[\s\S]*<beat-unit>/.test(met)) return met;
89843
+ const puntillos = (met.match(/<beat-unit-dot\s*\/>/g) ?? []).length;
89844
+ const factor = negras * (puntillos === 0 ? 1 : puntillos === 1 ? 1.5 : 1.75);
89845
+ if (factor === 1) return met;
89846
+ tempos++;
89847
+ const bpm = Math.round(Number.parseFloat(porMinuto) * factor * 100) / 100;
89848
+ return met.replace(/<beat-unit>[\s\S]*?<\/beat-unit>/, "<beat-unit>quarter</beat-unit>").replace(/<beat-unit-dot\s*\/>/g, "").replace(/<per-minute>[\s\S]*?<\/per-minute>/, `<per-minute>${bpm}</per-minute>`);
89849
+ });
89850
+ if (tempos) correcciones.push(`${tempos} indicaciones de tempo se pasaron a negras (mismo pulso).`);
89851
+ return { xml: salida, bytes: new TextEncoder().encode(salida), correcciones };
89852
+ }
89853
+ function compasesDe(parte) {
89854
+ return [...parte.matchAll(/(<measure\b[^>]*>)([\s\S]*?)<\/measure>/g)].map((m) => ({
89855
+ inicio: m.index,
89856
+ fin: m.index + m[0].length,
89857
+ apertura: m[1],
89858
+ contenido: m[2]
89859
+ }));
89860
+ }
89861
+ function rearmar(parte, compases, nuevos) {
89862
+ let salida = "";
89863
+ let cursor = 0;
89864
+ compases.forEach((c, i2) => {
89865
+ salida += parte.slice(cursor, c.inicio) + c.apertura + nuevos[i2] + "</measure>";
89866
+ cursor = c.fin;
89867
+ });
89868
+ return salida + parte.slice(cursor);
89869
+ }
89870
+ function tiempoAl(pedazo) {
89871
+ let t = 0;
89872
+ for (const m of pedazo.matchAll(/<(note|backup|forward)\b[\s\S]*?<\/\1>/g)) {
89873
+ const dur = entero(m[0], /<duration>\s*(\d+)\s*<\/duration>/) ?? 0;
89874
+ if (m[1] === "backup") t -= dur;
89875
+ else if (m[1] === "forward") t += dur;
89876
+ else if (!/<chord\s*\/>/.test(m[0]) && !/<grace\b/.test(m[0])) t += dur;
89877
+ }
89878
+ return t;
89879
+ }
89880
+ function moverAtributosDeFinDeCompas(parte) {
89881
+ const compases = compasesDe(parte);
89882
+ let cuantos = 0;
89883
+ let arrastre = "";
89884
+ const nuevos = compases.map((c) => {
89885
+ let contenido = c.contenido;
89886
+ const primeraNota = contenido.search(/<note\b/);
89887
+ if (primeraNota >= 0) {
89888
+ let llevar = "";
89889
+ let alPrincipio = "";
89890
+ const despues = contenido.slice(primeraNota);
89891
+ contenido = contenido.slice(0, primeraNota) + despues.replace(/<attributes\b[^>]*>([\s\S]*?)<\/attributes>/g, (bloque, adentro, donde) => {
89892
+ const hijos = adentro.match(HIJO_QUE_SE_MUEVE);
89893
+ if (!hijos) return bloque;
89894
+ cuantos += hijos.length;
89895
+ if (tiempoAl(despues.slice(0, donde)) <= 0) alPrincipio += hijos.join("");
89896
+ else llevar += hijos.join("");
89897
+ const resto = adentro.replace(HIJO_QUE_SE_MUEVE, "");
89898
+ return resto.trim() ? `<attributes>${resto}</attributes>` : "";
89899
+ });
89900
+ const entra2 = arrastre + alPrincipio;
89901
+ arrastre = llevar;
89902
+ return (entra2 ? `<attributes>${entra2}</attributes>` : "") + contenido;
89903
+ }
89904
+ const entra = arrastre;
89905
+ arrastre = "";
89906
+ return (entra ? `<attributes>${entra}</attributes>` : "") + contenido;
89907
+ });
89908
+ return cuantos ? { texto: rearmar(parte, compases, nuevos), cuantos } : { texto: parte, cuantos: 0 };
89909
+ }
89910
+ function separarAcordesEntrePentagramas(parte) {
89911
+ const compases = compasesDe(parte);
89912
+ let cuantos = 0;
89913
+ const nuevos = compases.map((c) => {
89914
+ const notas = [...c.contenido.matchAll(/<note\b[\s\S]*?<\/note>/g)];
89915
+ if (notas.length < 2) return c.contenido;
89916
+ let salida = "";
89917
+ let cursor = 0;
89918
+ let i2 = 0;
89919
+ while (i2 < notas.length) {
89920
+ const grupo = [notas[i2]];
89921
+ let j = i2 + 1;
89922
+ while (j < notas.length) {
89923
+ const anterior = notas[j - 1];
89924
+ const entre = c.contenido.slice(anterior.index + anterior[0].length, notas[j].index);
89925
+ if (entre.trim() || !/<chord\s*\/>/.test(notas[j][0])) break;
89926
+ grupo.push(notas[j]);
89927
+ j++;
89928
+ }
89929
+ i2 = j;
89930
+ const pent = (n) => entero(n[0], /<staff>\s*(\d+)\s*<\/staff>/) ?? 1;
89931
+ const cabeza = grupo[0];
89932
+ const pentDeCabeza = pent(cabeza);
89933
+ const ajenas = grupo.filter((n) => pent(n) !== pentDeCabeza);
89934
+ const durCabeza = entero(cabeza[0], /<duration>\s*(\d+)\s*<\/duration>/);
89935
+ if (ajenas.length === 0 || /<chord\s*\/>/.test(cabeza[0]) || grupo.some((n) => /<grace\b|<cue\s*\/>/.test(n[0])) || durCabeza === null) {
89936
+ continue;
89937
+ }
89938
+ cuantos += ajenas.length;
89939
+ const propias = grupo.filter((n) => pent(n) === pentDeCabeza).map((n) => n[0]);
89940
+ let texto4 = propias.join("");
89941
+ const porPentagrama = /* @__PURE__ */ new Map();
89942
+ for (const n of ajenas) porPentagrama.set(pent(n), [...porPentagrama.get(pent(n)) ?? [], n[0]]);
89943
+ for (const notasDelPent of porPentagrama.values()) {
89944
+ const primera = notasDelPent[0].replace(/<chord\s*\/>\s*/, "");
89945
+ const durPrimera = entero(primera, /<duration>\s*(\d+)\s*<\/duration>/) ?? durCabeza;
89946
+ texto4 += `<backup><duration>${durCabeza}</duration></backup>` + primera + notasDelPent.slice(1).join("");
89947
+ const delta = durCabeza - durPrimera;
89948
+ if (delta > 0) texto4 += `<forward><duration>${delta}</duration></forward>`;
89949
+ else if (delta < 0) texto4 += `<backup><duration>${-delta}</duration></backup>`;
89950
+ }
89951
+ const ultimo = grupo[grupo.length - 1];
89952
+ salida += c.contenido.slice(cursor, cabeza.index) + texto4;
89953
+ cursor = ultimo.index + ultimo[0].length;
89954
+ }
89955
+ return salida + c.contenido.slice(cursor);
89956
+ });
89957
+ return cuantos ? { texto: rearmar(parte, compases, nuevos), cuantos } : { texto: parte, cuantos: 0 };
89958
+ }
89959
+ function sacarSaltosDeLaIndicacion(parte) {
89960
+ let cuantos = 0;
89961
+ const texto4 = parte.replace(/<direction\b[^>]*>[\s\S]*?<\/direction>/g, (dir, desde, todo) => {
89962
+ const sonido = /<sound\b([^>]*?)\/?>/.exec(dir)?.[1];
89963
+ if (!sonido) return dir;
89964
+ const atributos = ATRIBUTOS_DE_SALTO.map((a) => new RegExp(`\\b${a}="([^"]*)"`).exec(sonido)?.[0]).filter((a) => a !== void 0);
89965
+ if (atributos.length === 0) return dir;
89966
+ const copia = `<sound ${atributos.join(" ")}/>`;
89967
+ if (todo.slice(desde + dir.length).trimStart().startsWith(copia)) return dir;
89968
+ cuantos++;
89969
+ return dir + copia;
89970
+ });
89971
+ return { texto: texto4, cuantos };
89972
+ }
89973
+ function compactarVoces(parte) {
89974
+ const porPentagrama = /* @__PURE__ */ new Map();
89975
+ const pentagramaDeVoz = /* @__PURE__ */ new Map();
89976
+ for (const m of parte.matchAll(/<note\b[\s\S]*?<\/note>/g)) {
89977
+ const voz = entero(m[0], /<voice>\s*(\d+)\s*<\/voice>/);
89978
+ if (voz === null) continue;
89979
+ const pent = entero(m[0], /<staff>\s*(\d+)\s*<\/staff>/) ?? 1;
89980
+ const lista2 = porPentagrama.get(pent) ?? [];
89981
+ if (!lista2.includes(voz)) lista2.push(voz);
89982
+ porPentagrama.set(pent, lista2);
89983
+ if (!pentagramaDeVoz.has(voz)) pentagramaDeVoz.set(voz, pent);
89984
+ }
89985
+ const nueva = /* @__PURE__ */ new Map();
89986
+ let hayCambio = false;
89987
+ for (const [pent, voces] of porPentagrama) {
89988
+ ;
89989
+ [...voces].sort((a, b) => a - b).forEach((voz, i2) => {
89990
+ nueva.set(`${pent}|${voz}`, i2 + 1);
89991
+ if (voz !== i2 + 1) hayCambio = true;
89992
+ });
89993
+ }
89994
+ if (!hayCambio) return { texto: parte, cambio: null };
89995
+ const renumerar = (bloque, pentPorOmision) => {
89996
+ const voz = entero(bloque, /<voice>\s*(\d+)\s*<\/voice>/);
89997
+ if (voz === null) return bloque;
89998
+ const pent = entero(bloque, /<staff>\s*(\d+)\s*<\/staff>/) ?? pentPorOmision(voz);
89999
+ const n = nueva.get(`${pent}|${voz}`);
90000
+ return n === void 0 ? bloque : bloque.replace(/<voice>\s*\d+\s*<\/voice>/, `<voice>${n}</voice>`);
90001
+ };
90002
+ const texto4 = parte.replace(/<note\b[\s\S]*?<\/note>/g, (b) => renumerar(b, () => 1)).replace(/<forward\b[\s\S]*?<\/forward>/g, (b) => renumerar(b, (v) => pentagramaDeVoz.get(v) ?? 1));
90003
+ const id2 = /<part\b[^>]*\bid="([^"]*)"/.exec(parte)?.[1] ?? "?";
90004
+ const detalle = [...porPentagrama].map(([p, vs]) => `pentagrama ${p}: voces ${[...vs].sort((a, b) => a - b).join(", ")}`).join("; ");
90005
+ return { texto: texto4, cambio: `Parte ${id2}: las voces se renumeraron por pentagrama (${detalle}).` };
90006
+ }
90007
+ function leerOrtografia(xml) {
90008
+ return leerDelArchivo(xml).ortografia;
90009
+ }
90010
+ function direccionesDeLaIndicacion(bloque) {
90011
+ const sonido = /<sound\b([^>]*?)\/?>/.exec(bloque)?.[1] ?? "";
90012
+ const atributo = (n) => new RegExp(`(?:^|\\s)${n}="([^"]*)"`).exec(sonido)?.[1];
90013
+ const palabras = [...bloque.matchAll(/<words\b[^>]*>([\s\S]*?)<\/words>/g)].map((m) => m[1]).join(" ").trim();
90014
+ const dicho = palabras ? direccionDeLasPalabras(palabras) : null;
90015
+ const salida = /* @__PURE__ */ new Set();
90016
+ const segno = atributo("segno");
90017
+ if (/<segno\b/.test(bloque) || segno !== void 0) salida.add(segno === "segno2" ? "TargetSegnoSegno" : "TargetSegno");
90018
+ const coda = atributo("coda");
90019
+ if (/<coda\b/.test(bloque) || coda !== void 0) salida.add(coda === "coda2" ? "TargetDoubleCoda" : "TargetCoda");
90020
+ if (atributo("fine") !== void 0) salida.add("TargetFine");
90021
+ const tocoda = atributo("tocoda");
90022
+ if (tocoda !== void 0) salida.add(tocoda === "coda2" ? "JumpDaDoubleCoda" : "JumpDaCoda");
90023
+ if (atributo("dacapo") !== void 0) salida.add(dicho?.startsWith("JumpDaCapo") ? dicho : "JumpDaCapo");
90024
+ const dalsegno = atributo("dalsegno");
90025
+ if (dalsegno !== void 0) {
90026
+ const familia = dalsegno === "segno2" ? "JumpDalSegnoSegno" : "JumpDalSegno";
90027
+ const delaFamilia = dicho !== null && dicho.startsWith(familia) && (familia === "JumpDalSegnoSegno" || !dicho.startsWith("JumpDalSegnoSegno"));
90028
+ salida.add(delaFamilia ? dicho : familia);
90029
+ }
90030
+ return [...salida];
90031
+ }
90032
+ function leerDelArchivo(xml) {
90033
+ const ortografia = /* @__PURE__ */ new Map();
90034
+ const bajos = /* @__PURE__ */ new Map();
90035
+ const matices = /* @__PURE__ */ new Map();
90036
+ const saltos = /* @__PURE__ */ new Map();
90037
+ let iParte = -1;
90038
+ for (const parte of xml.matchAll(/<part\b[^>]*>([\s\S]*?)<\/part>/g)) {
90039
+ iParte++;
90040
+ let divisiones = 1;
90041
+ let transposicion = 0;
90042
+ let iCompas = -1;
90043
+ for (const compas of parte[1].matchAll(/<measure\b[^>]*>([\s\S]*?)<\/measure>/g)) {
90044
+ iCompas++;
90045
+ let tic = 0;
90046
+ let ticAnterior = 0;
88894
90047
  let bajoPendiente = null;
88895
90048
  for (const el of compas[1].matchAll(/<(note|backup|forward|attributes|harmony|direction)\b[\s\S]*?<\/\1>/g)) {
88896
90049
  const bloque = el[0];
88897
90050
  const tipo = el[1];
88898
90051
  if (tipo === "direction") {
90052
+ if (iParte === 0) {
90053
+ const aca = direccionesDeLaIndicacion(bloque);
90054
+ if (aca.length > 0) saltos.set(iCompas, [.../* @__PURE__ */ new Set([...saltos.get(iCompas) ?? [], ...aca])]);
90055
+ }
88899
90056
  const nombre = /<dynamics\b[^>]*>\s*<([a-z]+)\s*\/>/.exec(bloque)?.[1];
88900
90057
  if (nombre) {
88901
90058
  const pent2 = (entero(bloque, /<staff>\s*(\d+)\s*<\/staff>/) ?? 1) - 1;
@@ -88960,12 +90117,13 @@ function leerDelArchivo(xml) {
88960
90117
  }
88961
90118
  }
88962
90119
  }
88963
- return { ortografia, bajos, matices };
90120
+ return { ortografia, bajos, matices, saltos };
88964
90121
  }
88965
90122
  var MODO_DE_ALTERACION, MODO_POR_ALTER, SEMITONOS, TICS_POR_NEGRA2, entero, HIJO_QUE_SE_MUEVE, ATRIBUTOS_DE_SALTO, NEGRAS_POR_FIGURA;
88966
90123
  var init_musicxmlEntrada = __esm({
88967
90124
  "packages/io/src/musicxmlEntrada.ts"() {
88968
90125
  init_esm();
90126
+ init_musicxml();
88969
90127
  MODO_DE_ALTERACION = {
88970
90128
  porDefecto: 0,
88971
90129
  natural: 2,
@@ -89009,6 +90167,28 @@ function piezaDeBateria(pista, numero2) {
89009
90167
  const buscada = PARIENTE_GM[midi] ?? midi;
89010
90168
  return KIT.find((p) => p.midi === buscada)?.indice ?? null;
89011
90169
  }
90170
+ function golpesEnLigadura(score) {
90171
+ const marcados = /* @__PURE__ */ new Set();
90172
+ for (const track of score.tracks)
90173
+ for (const staff of track.staves)
90174
+ for (const bar of staff.bars)
90175
+ for (const voice of bar.voices)
90176
+ for (const beat of voice.beats) {
90177
+ if (beat.isLegatoOrigin) marcados.add(beat);
90178
+ for (const nota of beat.notes) {
90179
+ const llegada = nota.slurDestination?.beat;
90180
+ if (!llegada) continue;
90181
+ const camino = [];
90182
+ let b = beat;
90183
+ while (b && b !== llegada && camino.length < 1e4) {
90184
+ camino.push(b);
90185
+ b = b.nextBeat;
90186
+ }
90187
+ if (b === llegada) for (const g of camino) marcados.add(g);
90188
+ }
90189
+ }
90190
+ return marcados;
90191
+ }
89012
90192
  function importarGp(bytes, opciones = {}) {
89013
90193
  const xml = textoMusicXml(bytes);
89014
90194
  if (xml === null) {
@@ -89018,7 +90198,7 @@ function importarGp(bytes, opciones = {}) {
89018
90198
  const preparado = prepararMusicXml(xml);
89019
90199
  const score = _barrel_exports$1.ScoreLoader.loadScoreFromBytes(preparado.bytes);
89020
90200
  const leido = leerDelArchivo(preparado.xml);
89021
- const resultado = importarScore(score, opciones, { ortografia: leido.ortografia, bajos: leido.bajos, matices: leido.matices });
90201
+ const resultado = importarScore(score, opciones, { ortografia: leido.ortografia, bajos: leido.bajos, matices: leido.matices, saltos: leido.saltos });
89022
90202
  resultado.avisos.push(...preparado.correcciones);
89023
90203
  return resultado;
89024
90204
  }
@@ -89054,6 +90234,7 @@ function importarScore(score, opciones = {}, datos = {}) {
89054
90234
  );
89055
90235
  }
89056
90236
  const saltosEscritos = saltosDelTexto(score);
90237
+ const enLigadura = golpesEnLigadura(score);
89057
90238
  const idsDeCompasMaestro = score.masterBars.map(
89058
90239
  (maestro) => documento.agregarCompasMaestro(normalizarCompasMaestro({
89059
90240
  numerador: maestro.timeSignatureNumerator,
@@ -89095,7 +90276,11 @@ function importarScore(score, opciones = {}, datos = {}) {
89095
90276
  //
89096
90277
  // ★ El nombre y no el número, además, porque acá es una LISTA y se lee: un
89097
90278
  // `["JumpDalSegnoAlCoda"]` en el documento dice algo; un `[10]` no.
89098
- direcciones: maestro.directions ? [...saltosEscritos.precisos.get(maestro.index) ?? maestro.directions].map(
90279
+ //
90280
+ // ⛔ (2026-11-11) De un MusicXML mandan las que se leyeron del archivo (`datos.saltos`):
90281
+ // alphaTab 1.8.4 confunde Segno Segno con Segno, Double Coda con Coda, y en un compás
90282
+ // vacío pierde todos los «al Coda» y «al Fine». Ver `LoQueSeLeeDelArchivo.saltos`.
90283
+ direcciones: datos.saltos?.get(maestro.index) ? [...datos.saltos.get(maestro.index)] : maestro.directions ? [...saltosEscritos.precisos.get(maestro.index) ?? maestro.directions].map(
89099
90284
  (d) => _barrel_exports$4.Direction[d] ?? String(d)
89100
90285
  ) : []
89101
90286
  }))
@@ -89107,6 +90292,7 @@ function importarScore(score, opciones = {}, datos = {}) {
89107
90292
  if (id2 !== void 0 && i2 > 0) documento.ponerSaltoDeRenglon(id2, true);
89108
90293
  }
89109
90294
  const letrasDobles = { cuantas: 0, versosDeMas: 0 };
90295
+ const temposCorridos = { movidos: [], perdidos: [] };
89110
90296
  const tipoDelPentagrama = (p) => p.isPercussion ? "percusion" : p.tuning.length === 0 ? "altura" : "trastes";
89111
90297
  for (const pista of score.tracks) {
89112
90298
  if (pista.staves.length === 0) {
@@ -89172,6 +90358,7 @@ function importarScore(score, opciones = {}, datos = {}) {
89172
90358
  for (const a of maestroDelCompas?.tempoAutomations ?? []) {
89173
90359
  if (a.ratioPosition > 0) temposAMitad.set(Math.round(a.ratioPosition * ticsDelCompas), a.value);
89174
90360
  }
90361
+ moverTemposAGolpes(temposAMitad, compasDeAlphaTab.voices[0], indice + 1, temposCorridos);
89175
90362
  }
89176
90363
  const pedalesDelCompas = /* @__PURE__ */ new Map();
89177
90364
  for (const marcador of compasDeAlphaTab.sustainPedals ?? []) {
@@ -89306,7 +90493,9 @@ function importarScore(score, opciones = {}, datos = {}) {
89306
90493
  armonicoValor: nota.harmonicType !== _barrel_exports$4.HarmonicType.None && nota.harmonicType !== _barrel_exports$4.HarmonicType.Natural ? nota.harmonicValue : 0,
89307
90494
  // ── Los signos de Guitar Pro, a nivel de nota ──────────────────
89308
90495
  acento: nota.accentuated,
89309
- ligaduraDeExpresion: nota.isSlurDestination || nota.slurDestination !== null,
90496
+ // ⛔ (D1) Antes: `isSlurDestination || slurDestination !== null`, que marcaba la nota
90497
+ // de LLEGADA (donde la curva termina) y nunca leía el `<Legato>` de un `.gp`.
90498
+ ligaduraDeExpresion: enLigadura.has(golpe),
89310
90499
  vibratoDePalanca: 0,
89311
90500
  // ★ La anchura del vibrato: el campo viejo `vibrato` solo decía si había.
89312
90501
  anchoDeVibrato: nota.vibrato,
@@ -89357,8 +90546,24 @@ function importarScore(score, opciones = {}, datos = {}) {
89357
90546
  `${letrasDobles.versosDeMas} golpe(s) tra\xEDan letra de un segundo verso o m\xE1s: se guard\xF3 el primero. Tabs lleva un rengl\xF3n de letra.`
89358
90547
  );
89359
90548
  }
90549
+ const enCompases = (lista2) => `${lista2.length === 1 ? "comp\xE1s" : "compases"} ${lista2.join(", ")}`;
90550
+ if (temposCorridos.movidos.length > 0) {
90551
+ avisos.push(
90552
+ `Un cambio de tempo ca\xEDa a mitad de una figura (${enCompases(temposCorridos.movidos)}). Se movi\xF3 al golpe m\xE1s cercano.`
90553
+ );
90554
+ }
90555
+ if (temposCorridos.perdidos.length > 0) {
90556
+ avisos.push(
90557
+ `Un cambio de tempo ca\xEDa a mitad de una figura, sin otro golpe en el comp\xE1s (${enCompases(temposCorridos.perdidos)}). No se import\xF3.`
90558
+ );
90559
+ }
89360
90560
  const audioEmbebido = importarAudioYSync(score, documento, idsDeCompasMaestro, avisos);
89361
- return { documento, avisos, ...audioEmbebido ? { audioEmbebido } : {} };
90561
+ const manosJuntadas = juntarManosDelPiano(documento);
90562
+ if (manosJuntadas > 0)
90563
+ avisos.push(
90564
+ manosJuntadas === 1 ? "El piano ven\xEDa con una pista por mano: se junt\xF3 en un solo instrumento, con su llave." : `${manosJuntadas} pianos ven\xEDan con una pista por mano: se juntaron, cada uno con su llave.`
90565
+ );
90566
+ return { documento, avisos, manosJuntadas, ...audioEmbebido ? { audioEmbebido } : {} };
89362
90567
  }
89363
90568
  function importarAudioYSync(score, documento, idsDeCompasMaestro, avisos) {
89364
90569
  const puntos = score.exportFlatSyncPoints();
@@ -89449,6 +90654,25 @@ function acordeDelGolpe(golpe, pentagrama, bajos, parte, numeroDePentagrama, com
89449
90654
  const bajo = bajos?.get(`${parte}|${numeroDePentagrama}|${compas}|${golpe.displayStart}`) ?? "";
89450
90655
  return bajo && !nombre.endsWith(bajo) ? nombre + bajo : nombre;
89451
90656
  }
90657
+ function moverTemposAGolpes(tempos, voz, compas, corridos) {
90658
+ if (tempos.size === 0) return;
90659
+ const comienzos = new Set(
90660
+ (voz?.isEmpty ? [] : voz?.beats ?? []).filter((g) => !g.isEmpty && g.graceType === _barrel_exports$4.GraceType.None && g.displayStart > 0).map((g) => g.displayStart)
90661
+ );
90662
+ const ordenados = [...tempos.entries()].sort((a, b) => a[0] - b[0]);
90663
+ tempos.clear();
90664
+ for (const [tic, bpm] of ordenados) {
90665
+ if (comienzos.has(tic)) {
90666
+ tempos.set(tic, bpm);
90667
+ continue;
90668
+ }
90669
+ let mejor = null;
90670
+ for (const c of comienzos) if (mejor === null || Math.abs(c - tic) < Math.abs(mejor - tic) || Math.abs(c - tic) === Math.abs(mejor - tic) && c > mejor) mejor = c;
90671
+ const lista2 = mejor === null ? corridos.perdidos : corridos.movidos;
90672
+ if (!lista2.includes(compas)) lista2.push(compas);
90673
+ if (mejor !== null) tempos.set(mejor, bpm);
90674
+ }
90675
+ }
89452
90676
  function tempoDelGolpe(golpe, delMaestro) {
89453
90677
  if (golpe.displayStart === 0) return void 0;
89454
90678
  const propio = golpe.automations?.find((a) => a.type === _barrel_exports$4.AutomationType.Tempo)?.value;
@@ -89475,13 +90699,15 @@ function ortografiaDe(ortografia, parte, pentagrama, compas, tic, nota) {
89475
90699
  const base = `${parte}|${pentagrama}|${compas}|${tic}|`;
89476
90700
  return ortografia.get(base + nota.calculateRealValue(false, false)) ?? ortografia.get(base + (nota.octave * 12 + nota.tone));
89477
90701
  }
89478
- var SALIDA_DE_ID_GP, PARIENTE_GM, DIR, SALTO_POR_TEXTO, claveDeSalto;
90702
+ var SALIDA_DE_ID_GP, PARIENTE_GM, DIR, SALTO_POR_TEXTO;
89479
90703
  var init_importarGp = __esm({
89480
90704
  "packages/io/src/importarGp.ts"() {
89481
90705
  init_alphaTab();
89482
90706
  init_src2();
89483
90707
  init_src2();
90708
+ init_manosDelPiano();
89484
90709
  init_musicxmlEntrada();
90710
+ init_musicxml();
89485
90711
  SALIDA_DE_ID_GP = {
89486
90712
  31: 40,
89487
90713
  83: 53,
@@ -89546,6 +90772,7 @@ var init_importarGp = __esm({
89546
90772
  dsalfine: [DIR.JumpDalSegno, DIR.JumpDalSegnoAlFine],
89547
90773
  dssalcoda: [DIR.JumpDalSegno, DIR.JumpDalSegnoSegnoAlCoda],
89548
90774
  dssalfine: [DIR.JumpDalSegno, DIR.JumpDalSegnoSegnoAlFine],
90775
+ dssalcoda2: [DIR.JumpDalSegno, DIR.JumpDalSegnoSegnoAlDoubleCoda],
89549
90776
  dss: [DIR.JumpDalSegno, DIR.JumpDalSegnoSegno],
89550
90777
  dcalcoda: [DIR.JumpDaCapo, DIR.JumpDaCapoAlCoda],
89551
90778
  dcalcoda2: [DIR.JumpDaCapo, DIR.JumpDaCapoAlDoubleCoda],
@@ -89556,7 +90783,6 @@ var init_importarGp = __esm({
89556
90783
  tocoda2: [DIR.JumpDaCoda, DIR.JumpDaDoubleCoda],
89557
90784
  fine: [DIR.TargetFine, DIR.TargetFine]
89558
90785
  };
89559
- claveDeSalto = (texto4) => texto4.toLowerCase().replace(/dals*segno/g, "ds").replace(/das*capo/g, "dc").replace(/[^a-z0-9]/g, "");
89560
90786
  }
89561
90787
  });
89562
90788
 
@@ -90054,7 +91280,7 @@ function claseDe(g) {
90054
91280
  if (g.esBateria) return "bateria";
90055
91281
  if (g.programa >= 24 && g.programa <= 31) return "guitarra";
90056
91282
  if (g.programa >= 32 && g.programa <= 39) return "bajo";
90057
- if (g.programa <= 7) return "piano";
91283
+ if (g.programa <= 7 || g.programa >= 16 && g.programa <= 20) return "piano";
90058
91284
  return "altura";
90059
91285
  }
90060
91286
  function digitar(alturas, afinacion, ocupadas) {
@@ -90068,7 +91294,36 @@ function mejorDigitacion(alturas, afinacion, prohibidas) {
90068
91294
  let mejor = null;
90069
91295
  const actual = [];
90070
91296
  const usadas = /* @__PURE__ */ new Set();
91297
+ const cuerdasLibres = afinacion.length - [...prohibidas].filter((c) => c >= 1 && c <= afinacion.length).length;
91298
+ const puedeMejorar = (i2, colocadas, maximo, suma) => {
91299
+ if (!mejor) return true;
91300
+ const libres = cuerdasLibres - usadas.size;
91301
+ const tope = colocadas + Math.min(alturas.length - i2, libres);
91302
+ if (tope < mejor.colocadas) return false;
91303
+ if (tope > mejor.colocadas) return true;
91304
+ if (maximo > mejor.maximo) return false;
91305
+ const faltan = mejor.colocadas - colocadas;
91306
+ if (faltan <= 0) return maximo < mejor.maximo || suma < mejor.suma;
91307
+ const minimos = [];
91308
+ for (let k = i2; k < alturas.length; k++) {
91309
+ let menor = Infinity;
91310
+ for (let cuerda = 1; cuerda <= afinacion.length; cuerda++) {
91311
+ if (usadas.has(cuerda) || prohibidas.has(cuerda)) continue;
91312
+ const traste = alturas[k] - afinacion[cuerda - 1];
91313
+ if (traste >= 0 && traste <= TRASTES && traste < menor) menor = traste;
91314
+ }
91315
+ if (menor !== Infinity) minimos.push(menor);
91316
+ }
91317
+ if (minimos.length < faltan) return false;
91318
+ minimos.sort((a, b) => a - b);
91319
+ const maximoPosible = Math.max(maximo, minimos[faltan - 1]);
91320
+ if (maximoPosible > mejor.maximo) return false;
91321
+ let sumaPosible = suma;
91322
+ for (let k = 0; k < faltan; k++) sumaPosible += minimos[k];
91323
+ return maximoPosible < mejor.maximo || sumaPosible < mejor.suma;
91324
+ };
90071
91325
  const probar = (i2, colocadas, maximo, suma) => {
91326
+ if (!puedeMejorar(i2, colocadas, maximo, suma)) return;
90072
91327
  if (i2 === alturas.length) {
90073
91328
  if (!mejor || colocadas > mejor.colocadas || colocadas === mejor.colocadas && (maximo < mejor.maximo || maximo === mejor.maximo && suma < mejor.suma)) {
90074
91329
  mejor = { cuerdas: [...actual], colocadas, maximo, suma };
@@ -90166,9 +91421,32 @@ function figurasQueLlenan(ini, fin, ventana) {
90166
91421
  }
90167
91422
  return salida;
90168
91423
  }
91424
+ function juntarPistasDeManos(grupos, avisos) {
91425
+ const salida = [];
91426
+ for (let i2 = 0; i2 < grupos.length; i2++) {
91427
+ const a = grupos[i2];
91428
+ const b = grupos[i2 + 1];
91429
+ const base = b && claseDe(a) === "piano" && claseDe(b) === "piano" ? baseDeLasManos(a.nombre, b.nombre) : null;
91430
+ if (!b || base === null) {
91431
+ salida.push(a);
91432
+ continue;
91433
+ }
91434
+ salida.push({
91435
+ ...a,
91436
+ nombre: base,
91437
+ canales: [.../* @__PURE__ */ new Set([...a.canales, ...b.canales])],
91438
+ notas: [...a.notas.map((n) => ({ ...n, mano: 0 })), ...b.notas.map((n) => ({ ...n, mano: 1 }))].sort(
91439
+ (x2, y) => x2.ini - y.ini || x2.altura - y.altura
91440
+ )
91441
+ });
91442
+ avisos.decir(`\xAB${a.nombre}\xBB y \xAB${b.nombre}\xBB son las dos manos de un piano: se abrieron como un solo instrumento.`);
91443
+ i2++;
91444
+ }
91445
+ return salida;
91446
+ }
90169
91447
  function armarScore(leido, opciones, avisos) {
90170
91448
  const rejilla = rejillaDe(opciones.cuantizacion);
90171
- const grupos = leido.grupos.filter((g) => g.notas.length > 0);
91449
+ const grupos = juntarPistasDeManos(leido.grupos.filter((g) => g.notas.length > 0), avisos);
90172
91450
  if (grupos.length === 0) {
90173
91451
  avisos.decir("El archivo no trae ninguna nota: se abri\xF3 una obra vac\xEDa con un piano.");
90174
91452
  }
@@ -90199,13 +91477,13 @@ function armarScore(leido, opciones, avisos) {
90199
91477
  if (fin <= ini) fin = ini + pasoEn(ventanas, ini);
90200
91478
  if (Math.abs(ini - cruda.ini) > 2) redondeadas++;
90201
91479
  else if (Math.abs(fin - cruda.fin) > 2 && clase !== "bateria") avisos.contar("duracion-ajustada");
90202
- notas.push({ ini, fin: Math.min(fin, finDeLaObra2), altura: cruda.altura, cuerda: 0 });
91480
+ notas.push({ ini, fin: Math.min(fin, finDeLaObra2), altura: cruda.altura, cuerda: 0, ...cruda.mano !== void 0 ? { mano: cruda.mano } : {} });
90203
91481
  }
90204
91482
  notas.sort((a, b) => a.altura - b.altura || a.ini - b.ini || b.fin - a.fin);
90205
91483
  const limpias = [];
90206
91484
  for (const n of notas) {
90207
91485
  const anterior = limpias[limpias.length - 1];
90208
- if (anterior && anterior.altura === n.altura) {
91486
+ if (anterior && anterior.altura === n.altura && anterior.mano === n.mano) {
90209
91487
  if (anterior.ini === n.ini) {
90210
91488
  avisos.contar("repetidas");
90211
91489
  continue;
@@ -90248,8 +91526,16 @@ function armarScore(leido, opciones, avisos) {
90248
91526
  const voces = repartirEnVoces(enRegistro, ventanas, avisos, (alturas, ocupadas) => digitar(alturas, afinacion, ocupadas));
90249
91527
  pista.pentagramas.push({ voces, clave: clase === "guitarra" ? _barrel_exports$4.Clef.G2 : _barrel_exports$4.Clef.F4 });
90250
91528
  } else if (clase === "piano") {
90251
- const derecha = notas.filter((n) => n.altura >= 60);
90252
- const izquierda = notas.filter((n) => n.altura < 60);
91529
+ let derecha;
91530
+ let izquierda;
91531
+ if (notas.some((n) => n.mano !== void 0)) {
91532
+ derecha = notas.filter((n) => n.mano !== 1);
91533
+ izquierda = notas.filter((n) => n.mano === 1);
91534
+ } else {
91535
+ const manos = repartirManos(notas);
91536
+ derecha = manos.derecha.map((i2) => notas[i2]).sort((a, b) => a.ini - b.ini || a.altura - b.altura);
91537
+ izquierda = manos.izquierda.map((i2) => notas[i2]).sort((a, b) => a.ini - b.ini || a.altura - b.altura);
91538
+ }
90253
91539
  pista.pentagramas.push({ voces: repartirEnVoces(derecha, ventanas, avisos, null), clave: _barrel_exports$4.Clef.G2 });
90254
91540
  pista.pentagramas.push({ voces: repartirEnVoces(izquierda, ventanas, avisos, null), clave: _barrel_exports$4.Clef.F4 });
90255
91541
  } else {
@@ -90277,7 +91563,8 @@ function armarScore(leido, opciones, avisos) {
90277
91563
  avisos.decir(`Se juntaron ${avisos.cuantas("repetidas")} nota(s) repetidas: la misma tecla dos veces en el mismo instante.`);
90278
91564
  }
90279
91565
  if (avisos.cuantas("sin-voz") > 0) {
90280
- avisos.decir(`Se descartaron ${avisos.cuantas("sin-voz")} nota(s) porque sonaban m\xE1s de ${MAXIMO_DE_VOCES} voces a la vez en el mismo pentagrama.`);
91566
+ const n = avisos.cuantas("sin-voz");
91567
+ avisos.decir(`${n === 1 ? "Se descart\xF3 1 nota" : `Se descartaron ${n} notas`} porque sonaban m\xE1s de ${MAXIMO_DE_VOCES} voces a la vez en el mismo pentagrama.`);
90281
91568
  }
90282
91569
  if (avisos.cuantas("sin-cuerda") > 0) {
90283
91570
  avisos.decir(`Se descartaron ${avisos.cuantas("sin-cuerda")} nota(s) que no cab\xEDan en las cuerdas (dos notas en la misma cuerda a la vez).`);
@@ -90486,7 +91773,8 @@ function llenarVoz(voice, eventos, compas, indiceDeVoz, pista, cortesDeTresillo,
90486
91773
  beat.tupletNumerator = 3;
90487
91774
  beat.tupletDenominator = 2;
90488
91775
  }
90489
- for (const n of tramo.notas ?? []) {
91776
+ const sinGolpe = pista.clase === "bateria" && (!primera || tramo.continua);
91777
+ for (const n of sinGolpe ? [] : tramo.notas ?? []) {
90490
91778
  const note = new _barrel_exports$4.Note();
90491
91779
  if (pista.clase === "bateria") {
90492
91780
  note.percussionArticulation = indiceDeTecla.get(n.altura);
@@ -90515,6 +91803,7 @@ var init_importarMidi = __esm({
90515
91803
  "packages/io/src/importarMidi.ts"() {
90516
91804
  init_alphaTab();
90517
91805
  init_src2();
91806
+ init_manosDelPiano();
90518
91807
  init_importarGp();
90519
91808
  MidiIlegible = class extends Error {
90520
91809
  constructor(mensaje) {
@@ -90667,7 +91956,7 @@ var init_importarMidi = __esm({
90667
91956
  "Sintetizador (sierra)",
90668
91957
  "Sintetizador (cal\xEDope)",
90669
91958
  "Sintetizador (chiff)",
90670
- "Sintetizador (charango)",
91959
+ "Sintetizador (charang)",
90671
91960
  "Sintetizador (voz)",
90672
91961
  "Sintetizador (quintas)",
90673
91962
  "Sintetizador (bajo y melod\xEDa)",
@@ -90755,6 +92044,16 @@ function formatoQueNecesita(documento) {
90755
92044
  const pentagramas = pista.get("pentagramas");
90756
92045
  if (typeof pentagramas === "number" && pentagramas > 1) return 5;
90757
92046
  }
92047
+ const conMarcas2 = (mapa) => {
92048
+ let hay = false;
92049
+ mapa.forEach((valor, clave) => {
92050
+ if (clave.endsWith(SUFIJO_SEGUNDA) && typeof valor === "string" && valor.includes("1")) hay = true;
92051
+ });
92052
+ return hay;
92053
+ };
92054
+ if (conMarcas2(documento.cabecera)) return 5;
92055
+ for (const grupo of [documento.compasesMaestros, documento.pistas, documento.figuras])
92056
+ for (const entidad of grupo.values()) if (conMarcas2(entidad)) return 5;
90758
92057
  return 4;
90759
92058
  }
90760
92059
  function empaquetar(documento, opciones = {}) {
@@ -90803,7 +92102,12 @@ function desempaquetar(bytes) {
90803
92102
  if (!manifiesto || typeof manifiesto !== "object") {
90804
92103
  throw new ArchivoIlegible("El manifiesto del archivo est\xE1 da\xF1ado.");
90805
92104
  }
90806
- if (!Number.isFinite(manifiesto.formato) || manifiesto.formato > VERSION_DEL_FORMATO) {
92105
+ if (typeof manifiesto.formato !== "number" || !Number.isFinite(manifiesto.formato)) {
92106
+ throw new ArchivoIlegible(
92107
+ "El manifiesto del archivo no dice de qu\xE9 versi\xF3n es. Abrirlo podr\xEDa borrarte lo que guard\xF3."
92108
+ );
92109
+ }
92110
+ if (manifiesto.formato > VERSION_DEL_FORMATO) {
90807
92111
  throw new ArchivoIlegible(
90808
92112
  `Este archivo se guard\xF3 con una versi\xF3n m\xE1s nueva de Mandioca Tabs (formato ${manifiesto.formato}; esta versi\xF3n entiende hasta el ${VERSION_DEL_FORMATO}). Abrirlo igual podr\xEDa borrarte lo que esa versi\xF3n guard\xF3.`
90809
92113
  );
@@ -90829,13 +92133,15 @@ function desempaquetar(bytes) {
90829
92133
  if (tipografiaId) fuentes.push({ tipografiaId, bytes: contenido });
90830
92134
  }
90831
92135
  }
90832
- return { documento, manifiesto, grabaciones, fuentes };
92136
+ const manosJuntadas = juntarManosDelPiano(documento);
92137
+ return { documento, manifiesto, grabaciones, fuentes, manosJuntadas };
90833
92138
  }
90834
92139
  var VERSION_DEL_FORMATO, MANIFIESTO, DOCUMENTO, CARPETA_DE_AUDIO, CARPETA_DE_FUENTES, ArchivoIlegible;
90835
92140
  var init_archivoMtabs = __esm({
90836
92141
  "packages/io/src/archivoMtabs.ts"() {
90837
92142
  init_esm();
90838
92143
  init_src2();
92144
+ init_manosDelPiano();
90839
92145
  VERSION_DEL_FORMATO = 6;
90840
92146
  MANIFIESTO = "mandioca.json";
90841
92147
  DOCUMENTO = "documento.ydoc";
@@ -90900,723 +92206,201 @@ function instalarRecorridoDeRepeticiones() {
90900
92206
  p.synthBpm = lastSyncPoint.synthBpm;
90901
92207
  } else if (syncPoints.length === 1) p.syncBpm = lastSyncPoint.synthBpm;
90902
92208
  else p.syncBpm = syncPoints[syncPoints.length - 2].syncBpm;
90903
- p.synthTime = lastSyncPoint.synthTime + ticksAMs(remainingTicks, lastSyncPoint.synthBpm);
90904
- p.syncTime = lastSyncPoint.syncTime + ticksAMs(remainingTicks, p.syncBpm);
90905
- if (!playContext.createNewSyncPoints) lastSyncPoint.updateSyncBpm(p.synthTime, p.syncTime);
90906
- syncPoints.push(p);
90907
- }
90908
- }
90909
- finish(controller.currentTick);
90910
- return playContext;
90911
- };
90912
- Generador.__recorridoDe19 = true;
90913
- return true;
90914
- }
90915
- var D, Repeticion, RecorridoDeCompases, ticksAMs;
90916
- var init_recorridoDeRepeticiones = __esm({
90917
- "packages/io/src/recorridoDeRepeticiones.ts"() {
90918
- init_alphaTab();
90919
- D = {
90920
- TargetFine: 0,
90921
- TargetSegno: 1,
90922
- TargetSegnoSegno: 2,
90923
- TargetCoda: 3,
90924
- TargetDoubleCoda: 4,
90925
- JumpDaCoda: 17,
90926
- JumpDaDoubleCoda: 18
90927
- };
90928
- Repeticion = class {
90929
- group;
90930
- /** Con casillas, cada cierre termina la pasada y todos comparten el contador. */
90931
- conCasillas;
90932
- /** La pasada actual (desde 0): elige qué casillas suenan. */
90933
- pasada = 0;
90934
- /** Las repeticiones hechas por cierre (sólo sin casillas). */
90935
- iteraciones;
90936
- /** Las casillas del último compás que las tenía: los que no tienen, las continúan. */
90937
- casillasActuales = 0;
90938
- /** @param pasadaFinal si se toca como última pasada (después de un salto): sólo la última casilla. */
90939
- constructor(group, pasadaFinal) {
90940
- this.group = group;
90941
- this.conCasillas = group.masterBars.some((m) => m.alternateEndings !== 0);
90942
- this.iteraciones = group.closings.map((c) => pasadaFinal ? Math.max(0, c.repeatCount - 1) : 0);
90943
- if (pasadaFinal) for (const i2 of this.iteraciones) this.pasada = Math.max(this.pasada, i2);
90944
- }
90945
- suena(compas) {
90946
- let casillas = compas.alternateEndings;
90947
- if (casillas === 0) casillas = this.casillasActuales;
90948
- else this.casillasActuales = casillas;
90949
- return casillas === 0 || (casillas & 1 << this.pasada) !== 0;
90950
- }
90951
- /** 0 = seguir, 1 = volver al comienzo de la repetición, 2 = terminó. */
90952
- alCerrar(cierre, sono) {
90953
- const cierres = this.group.closings;
90954
- const esElUltimo = cierre === cierres[cierres.length - 1];
90955
- if (this.conCasillas) {
90956
- if (!sono && !esElUltimo) return 0;
90957
- if (this.pasada < cierre.repeatCount - 1) {
90958
- this.pasada++;
90959
- this.casillasActuales = 0;
90960
- return 1;
90961
- }
90962
- return esElUltimo ? 2 : 0;
90963
- }
90964
- const i2 = cierres.indexOf(cierre);
90965
- if (i2 === -1) return 0;
90966
- if (this.iteraciones[i2] < cierre.repeatCount - 1) {
90967
- this.iteraciones[i2]++;
90968
- for (let j = 0; j < i2; j++) this.iteraciones[j] = 0;
90969
- this.casillasActuales = 0;
90970
- return 1;
90971
- }
90972
- return esElUltimo ? 2 : 0;
90973
- }
90974
- /** Si algún cierre todavía va a volver por encima de este compás. */
90975
- vaAVolver(compas) {
90976
- const cierres = this.group.closings;
90977
- for (let i2 = 0; i2 < cierres.length; i2++) {
90978
- const cierre = cierres[i2];
90979
- if (cierre.index >= compas.index) {
90980
- if ((this.conCasillas ? this.pasada : this.iteraciones[i2]) < cierre.repeatCount - 1) return true;
90981
- }
90982
- }
90983
- return false;
90984
- }
90985
- };
90986
- RecorridoDeCompases = class _RecorridoDeCompases {
90987
- /** Los saltos en el orden en que se revisan si un compás tiene varios. */
90988
- static SALTOS = [
90989
- { jump: 5, target: null, playUntil: null, continueAt: null },
90990
- { jump: 6, target: null, playUntil: D.JumpDaCoda, continueAt: D.TargetCoda },
90991
- { jump: 7, target: null, playUntil: D.JumpDaDoubleCoda, continueAt: D.TargetDoubleCoda },
90992
- { jump: 8, target: null, playUntil: D.TargetFine, continueAt: null },
90993
- { jump: 9, target: D.TargetSegno, playUntil: null, continueAt: null },
90994
- { jump: 10, target: D.TargetSegno, playUntil: D.JumpDaCoda, continueAt: D.TargetCoda },
90995
- { jump: 11, target: D.TargetSegno, playUntil: D.JumpDaDoubleCoda, continueAt: D.TargetDoubleCoda },
90996
- { jump: 12, target: D.TargetSegno, playUntil: D.TargetFine, continueAt: null },
90997
- { jump: 13, target: D.TargetSegnoSegno, playUntil: null, continueAt: null },
90998
- { jump: 14, target: D.TargetSegnoSegno, playUntil: D.JumpDaCoda, continueAt: D.TargetCoda },
90999
- { jump: 15, target: D.TargetSegnoSegno, playUntil: D.JumpDaDoubleCoda, continueAt: D.TargetDoubleCoda },
91000
- { jump: 16, target: D.TargetSegnoSegno, playUntil: D.TargetFine, continueAt: null }
91001
- ];
91002
- score;
91003
- pila = [];
91004
- /** El D.C./D.S. que se siguió; null si se toca normal (antes del salto o después de la coda). */
91005
- saltoActivo = null;
91006
- /** Los compases cuyo salto ya se tomó: cada salto se toma una sola vez. */
91007
- saltosTomados = /* @__PURE__ */ new Set();
91008
- shouldPlay = true;
91009
- index = 0;
91010
- currentTick = 0;
91011
- constructor(score) {
91012
- this.score = score;
91013
- }
91014
- get finished() {
91015
- return this.index >= this.score.masterBars.length;
91016
- }
91017
- processCurrent() {
91018
- const compas = this.score.masterBars[this.index];
91019
- this.entrarARepeticion(compas);
91020
- const rep = this.pila.length > 0 ? this.pila[this.pila.length - 1] : null;
91021
- this.shouldPlay = rep === null || rep.suena(compas);
91022
- if (this.shouldPlay) this.currentTick += compas.calculateDuration();
91023
- }
91024
- moveNext() {
91025
- if (this.moverConSaltos()) return;
91026
- const compas = this.score.masterBars[this.index];
91027
- if (this.pila.length > 0 && compas.repeatCount > 1) {
91028
- const rep = this.pila[this.pila.length - 1];
91029
- switch (rep.alCerrar(compas, this.shouldPlay)) {
91030
- case 1:
91031
- this.index = rep.group.opening.index;
91032
- return;
91033
- case 2:
91034
- this.pila.pop();
91035
- }
91036
- }
91037
- this.index++;
91038
- }
91039
- /** Empieza la repetición del compás si hace falta: al llegar a su apertura, o tras un salto que cayó adentro. */
91040
- entrarARepeticion(compas) {
91041
- const group = compas.repeatGroup;
91042
- if (!group.isClosed || this.pila.some((r) => r.group === group)) return;
91043
- const adentro = compas.index <= group.closings[group.closings.length - 1].index;
91044
- if (compas === group.opening || this.saltoActivo !== null && adentro) {
91045
- this.pila.push(new Repeticion(group, this.saltoActivo !== null));
91046
- }
91047
- }
91048
- moverConSaltos() {
91049
- const compas = this.score.masterBars[this.index];
91050
- const dir = compas.directions;
91051
- if (!this.shouldPlay || dir === null || dir === void 0 || dir.size === 0) return false;
91052
- if (this.saltoActivo === null) return this.tomarSalto(compas, dir);
91053
- const hasta = this.saltoActivo.playUntil;
91054
- if (hasta !== null && dir.has(hasta)) return this.seguirTrasElSalto(this.saltoActivo.continueAt);
91055
- return false;
91056
- }
91057
- tomarSalto(compas, dir) {
91058
- if (this.saltosTomados.has(compas) || this.pila.some((r) => r.vaAVolver(compas))) return false;
91059
- for (const salto of _RecorridoDeCompases.SALTOS) {
91060
- if (!dir.has(salto.jump)) continue;
91061
- const destino = salto.target === null ? 0 : this.buscar(salto.target, true);
91062
- if (destino === -1) continue;
91063
- this.saltosTomados.add(compas);
91064
- this.saltoActivo = salto;
91065
- this.pila = [];
91066
- this.index = destino;
91067
- return true;
91068
- }
91069
- return false;
91070
- }
91071
- seguirTrasElSalto(seguirEn) {
91072
- if (seguirEn === null) {
91073
- this.index = this.score.masterBars.length;
91074
- return true;
91075
- }
91076
- const coda = this.buscar(seguirEn, false);
91077
- if (coda === -1) return false;
91078
- this.saltoActivo = null;
91079
- this.pila = [];
91080
- this.index = coda;
91081
- return true;
91082
- }
91083
- buscar(direccion, primeroAtras) {
91084
- const paso = primeroAtras ? -1 : 1;
91085
- const i2 = this.buscarHacia(direccion, paso);
91086
- return i2 !== -1 ? i2 : this.buscarHacia(direccion, -paso);
91087
- }
91088
- buscarHacia(direccion, paso) {
91089
- const compases = this.score.masterBars;
91090
- for (let i2 = this.index; i2 >= 0 && i2 < compases.length; i2 += paso) {
91091
- const d = compases[i2].directions;
91092
- if (d !== null && d !== void 0 && d.has(direccion)) return i2;
91093
- }
91094
- return -1;
91095
- }
91096
- };
91097
- ticksAMs = (ticks, tempo) => ticks * (6e4 / (tempo * 960)) | 0;
91098
- }
91099
- });
91100
-
91101
- // packages/io/src/musicxml.ts
91102
- function escapar(texto4) {
91103
- return texto4.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
91104
- }
91105
- function elemento(nombre, texto4) {
91106
- if (texto4 === null || texto4 === void 0 || texto4 === "") return [];
91107
- return [`<${nombre}>${escapar(String(texto4))}</${nombre}>`];
91108
- }
91109
- function alturaMusicXml(midi, fifths, modo = 0) {
91110
- const octavaAlphaTab = Math.floor(midi / 12);
91111
- const clase = midi - octavaAlphaTab * 12;
91112
- const octava = octavaAlphaTab - 1;
91113
- const pedida = ALTERACION_POR_MODO[modo];
91114
- if (pedida !== void 0) {
91115
- const sinAlterar = clase - pedida;
91116
- const vueltas = Math.floor(sinAlterar / 12);
91117
- const paso = PASOS.find((p) => p.semitonos === sinAlterar - vueltas * 12);
91118
- if (paso) return { paso: paso.paso, alteracion: pedida, octava: octava + vueltas };
91119
- }
91120
- const natural = PASOS.find((p) => p.semitonos === clase);
91121
- if (natural) return { paso: natural.paso, alteracion: 0, octava };
91122
- if (fifths < 0) {
91123
- const arriba = PASOS.find((p) => p.semitonos === clase + 1);
91124
- if (arriba) return { paso: arriba.paso, alteracion: -1, octava };
91125
- }
91126
- const abajo = PASOS.find((p) => p.semitonos === clase - 1);
91127
- if (abajo) return { paso: abajo.paso, alteracion: 1, octava };
91128
- throw new Error(`altura MIDI no representable: ${midi}`);
91129
- }
91130
- function claveDelPentagrama(staff, bar) {
91131
- if (staff.isPercussion) return { signo: "percussion", linea: 2, lineas: 5, conAfinacion: false };
91132
- if (staff.showTablature && staff.tuning.length > 0)
91133
- return { signo: "TAB", linea: 5, lineas: staff.tuning.length, conAfinacion: true };
91134
- switch (bar.clef) {
91135
- case _barrel_exports$4.Clef.C3:
91136
- return { signo: "C", linea: 3, lineas: 5, conAfinacion: false };
91137
- case _barrel_exports$4.Clef.C4:
91138
- return { signo: "C", linea: 4, lineas: 5, conAfinacion: false };
91139
- case _barrel_exports$4.Clef.F4:
91140
- return { signo: "F", linea: 4, lineas: 5, conAfinacion: false };
91141
- case _barrel_exports$4.Clef.Neutral:
91142
- return { signo: "percussion", linea: 2, lineas: 5, conAfinacion: false };
91143
- default:
91144
- return { signo: "G", linea: 2, lineas: 5, conAfinacion: false };
91145
- }
91146
- }
91147
- function gruposDeTresillo(beats) {
91148
- const abren = /* @__PURE__ */ new Set();
91149
- const cierran = /* @__PURE__ */ new Set();
91150
- let i2 = 0;
91151
- while (i2 < beats.length) {
91152
- const b = beats[i2];
91153
- if (b.tupletNumerator <= 0 || b.tupletDenominator <= 0) {
91154
- i2++;
91155
- continue;
91156
- }
91157
- let j = i2;
91158
- while (j + 1 < beats.length && beats[j + 1].tupletNumerator === b.tupletNumerator && beats[j + 1].tupletDenominator === b.tupletDenominator && (b.tupletGroup === null || beats[j + 1].tupletGroup === b.tupletGroup))
91159
- j++;
91160
- abren.add(i2);
91161
- cierran.add(j);
91162
- i2 = j + 1;
91163
- }
91164
- return { abren, cierran };
91165
- }
91166
- function saltosDelCompas(master) {
91167
- const s = [];
91168
- for (const [direccion, salto] of SALTOS) {
91169
- if (!master?.directions?.has(direccion)) continue;
91170
- s.push('<direction placement="above">', "<direction-type>");
91171
- s.push(salto.simbolo ? `<${salto.simbolo}/>` : `<words>${escapar(salto.texto ?? "")}</words>`);
91172
- s.push("</direction-type>", `<sound ${salto.sonido}/>`, "</direction>");
91173
- }
91174
- return s;
91175
- }
91176
- function nombreDeFigura(duracion) {
91177
- return NOMBRE_DE_LA_FIGURA2.get(duracion) ?? "quarter";
91178
- }
91179
- function instrumentosDePercusion(track) {
91180
- const usadas = /* @__PURE__ */ new Set();
91181
- for (const staff of track.staves)
91182
- for (const bar of staff.bars)
91183
- for (const voice of bar.voices)
91184
- for (const beat of voice.beats)
91185
- for (const note of beat.notes)
91186
- if (note.percussionArticulation >= 0) usadas.add(note.percussionArticulation);
91187
- const ids = /* @__PURE__ */ new Map();
91188
- for (const art of [...usadas].sort((a, b) => a - b))
91189
- ids.set(art, `P${track.index + 1}-I${art + 1}`);
91190
- return ids;
91191
- }
91192
- function posicionDePercusion(staffLine) {
91193
- const indiceDeB = 6;
91194
- const bruto = indiceDeB + (5 - staffLine);
91195
- const paso = PASOS[(bruto % 7 + 7) % 7].paso;
91196
- return { paso, octava: 4 + Math.floor(bruto / 7) };
91197
- }
91198
- function escribirNota(note, beat, staff, opciones) {
91199
- const s = ["<note>"];
91200
- const esAdorno = beat.graceType !== _barrel_exports$4.GraceType.None;
91201
- if (esAdorno) s.push(beat.graceType === _barrel_exports$4.GraceType.BeforeBeat ? "<grace/>" : '<grace slash="yes"/>');
91202
- if (opciones.esAcorde) s.push("<chord/>");
91203
- const esGolpeDeKit = staff.isPercussion && note.percussionArticulation >= 0;
91204
- if (esGolpeDeKit) {
91205
- const art = staff.track.percussionArticulations[note.percussionArticulation];
91206
- const { paso, octava } = posicionDePercusion(art?.staffLine ?? 5);
91207
- s.push("<unpitched>");
91208
- s.push(`<display-step>${paso}</display-step>`);
91209
- s.push(`<display-octave>${octava}</display-octave>`);
91210
- s.push("</unpitched>");
91211
- } else {
91212
- const { paso, alteracion, octava } = alturaMusicXml(
91213
- note.calculateRealValue(false, false),
91214
- opciones.fifths,
91215
- note.accidentalMode
91216
- );
91217
- s.push("<pitch>");
91218
- s.push(`<step>${paso}</step>`);
91219
- if (alteracion !== 0) s.push(`<alter>${alteracion}</alter>`);
91220
- s.push(`<octave>${octava}</octave>`);
91221
- s.push("</pitch>");
91222
- }
91223
- if (!esAdorno) s.push(`<duration>${opciones.duracion}</duration>`);
91224
- if (note.isTieDestination) s.push('<tie type="stop"/>');
91225
- if (note.isTieOrigin) s.push('<tie type="start"/>');
91226
- const idDeInstrumento = opciones.instrumentos.get(note.percussionArticulation);
91227
- if (esGolpeDeKit && idDeInstrumento) s.push(`<instrument id="${idDeInstrumento}"/>`);
91228
- s.push(`<voice>${opciones.numeroDeVoz}</voice>`);
91229
- s.push(...elemento("type", nombreDeFigura(beat.duration)));
91230
- for (let d = 0; d < beat.dots; d++) s.push("<dot/>");
91231
- if (beat.tupletNumerator > 0 && beat.tupletDenominator > 0) {
91232
- s.push("<time-modification>");
91233
- s.push(`<actual-notes>${beat.tupletNumerator}</actual-notes>`);
91234
- s.push(`<normal-notes>${beat.tupletDenominator}</normal-notes>`);
91235
- s.push("</time-modification>");
91236
- }
91237
- if (note.isDead) s.push("<notehead>x</notehead>");
91238
- else if (esGolpeDeKit) {
91239
- const art = staff.track.percussionArticulations[note.percussionArticulation];
91240
- if (art?.noteHeadDefault === _barrel_exports$4.MusicFontSymbol.NoteheadXBlack)
91241
- s.push("<notehead>x</notehead>");
91242
- } else if (note.isGhost) s.push('<notehead parentheses="yes">normal</notehead>');
91243
- if (opciones.numeroDePentagrama !== null) s.push(`<staff>${opciones.numeroDePentagrama}</staff>`);
91244
- const notaciones = [];
91245
- if (note.isTieDestination) notaciones.push('<tied type="stop"/>');
91246
- if (note.isTieOrigin) notaciones.push('<tied type="start"/>');
91247
- if (opciones.abreTresillo) notaciones.push('<tuplet type="start" number="1"/>');
91248
- if (opciones.cierraTresillo) notaciones.push('<tuplet type="stop" number="1"/>');
91249
- const tecnica = [];
91250
- if (note.string > 0 && staff.tuning.length > 0) {
91251
- tecnica.push(`<string>${staff.tuning.length - note.string + 1}</string>`);
91252
- tecnica.push(`<fret>${note.fret}</fret>`);
91253
- }
91254
- const ligado = (desde, hasta) => hasta >= desde ? "hammer-on" : "pull-off";
91255
- const origen = note.hammerPullOrigin;
91256
- if (origen !== null) {
91257
- const nombre = ligado(origen.fret, note.fret);
91258
- tecnica.push(`<${nombre} type="stop"/>`);
91259
- }
91260
- const destino = note.hammerPullDestination;
91261
- if (note.isHammerPullOrigin && destino) {
91262
- const nombre = ligado(note.fret, destino.fret);
91263
- tecnica.push(`<${nombre} type="start">${nombre === "hammer-on" ? "H" : "P"}</${nombre}>`);
91264
- }
91265
- if (note.harmonicType !== _barrel_exports$4.HarmonicType.None) tecnica.push("<harmonic/>");
91266
- if (beat.isPalmMute) tecnica.push("<other-technical>palm-mute</other-technical>");
91267
- if (beat.isLetRing) tecnica.push("<other-technical>let-ring</other-technical>");
91268
- if (tecnica.length > 0) notaciones.push("<technical>", ...tecnica, "</technical>");
91269
- const articulaciones = [];
91270
- if (note.accentuated === _barrel_exports$4.AccentuationType.Normal) articulaciones.push("<accent/>");
91271
- else if (note.accentuated === _barrel_exports$4.AccentuationType.Heavy)
91272
- articulaciones.push("<strong-accent/>");
91273
- else if (note.accentuated === _barrel_exports$4.AccentuationType.Tenuto)
91274
- articulaciones.push("<tenuto/>");
91275
- if (note.isStaccato) articulaciones.push("<staccato/>");
91276
- if (articulaciones.length > 0)
91277
- notaciones.push("<articulations>", ...articulaciones, "</articulations>");
91278
- if (notaciones.length > 0) s.push("<notations>", ...notaciones, "</notations>");
91279
- if (!opciones.esAcorde) s.push(...letraDelGolpe(beat));
91280
- s.push("</note>");
91281
- return s;
91282
- }
91283
- function letraDelGolpe(beat) {
91284
- if (!beat.lyrics) return [];
91285
- const s = [];
91286
- beat.lyrics.forEach((verso, i2) => {
91287
- if (!verso) return;
91288
- const sigue = verso.endsWith("-") && verso.length > 1;
91289
- let anterior = beat.previousBeat;
91290
- while (anterior && !anterior.lyrics?.[i2]) anterior = anterior.previousBeat;
91291
- const viene = !!anterior?.lyrics?.[i2]?.endsWith("-");
91292
- const silabico = sigue ? viene ? "middle" : "begin" : viene ? "end" : "single";
91293
- s.push(`<lyric number="${i2 + 1}">`);
91294
- s.push(`<syllabic>${silabico}</syllabic>`);
91295
- s.push(...elemento("text", sigue ? verso.slice(0, -1) : verso));
91296
- s.push("</lyric>");
91297
- });
91298
- return s;
91299
- }
91300
- function escribirSilencio2(beat, duracion, numeroDeVoz, numeroDePentagrama) {
91301
- const s = ["<note>", "<rest/>", `<duration>${duracion}</duration>`];
91302
- s.push(`<voice>${numeroDeVoz}</voice>`);
91303
- s.push(...elemento("type", nombreDeFigura(beat.duration)));
91304
- for (let d = 0; d < beat.dots; d++) s.push("<dot/>");
91305
- if (beat.tupletNumerator > 0 && beat.tupletDenominator > 0) {
91306
- s.push("<time-modification>");
91307
- s.push(`<actual-notes>${beat.tupletNumerator}</actual-notes>`);
91308
- s.push(`<normal-notes>${beat.tupletDenominator}</normal-notes>`);
91309
- s.push("</time-modification>");
91310
- }
91311
- if (numeroDePentagrama !== null) s.push(`<staff>${numeroDePentagrama}</staff>`);
91312
- s.push(...letraDelGolpe(beat));
91313
- s.push("</note>");
91314
- return s;
91315
- }
91316
- function exportarMusicXml(score) {
91317
- const l = [];
91318
- l.push('<?xml version="1.0" encoding="UTF-8"?>');
91319
- l.push('<!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 4.0 Partwise//EN"');
91320
- l.push(' "http://www.musicxml.org/dtds/partwise.dtd">');
91321
- l.push('<score-partwise version="4.0">');
91322
- if (score.title) l.push("<work>", ...elemento("work-title", score.title), "</work>");
91323
- l.push(...elemento("movement-title", score.subTitle));
91324
- l.push("<identification>");
91325
- if (score.music) l.push(`<creator type="composer">${escapar(score.music)}</creator>`);
91326
- else if (score.artist) l.push(`<creator type="composer">${escapar(score.artist)}</creator>`);
91327
- if (score.words) l.push(`<creator type="lyricist">${escapar(score.words)}</creator>`);
91328
- l.push(...elemento("rights", score.copyright));
91329
- l.push("<encoding>", "<software>Mandioca Tabs</software>", "</encoding>");
91330
- l.push("</identification>");
91331
- const instrumentosPorPista = /* @__PURE__ */ new Map();
91332
- l.push("<part-list>");
91333
- for (const track of score.tracks) {
91334
- const id2 = `P${track.index + 1}`;
91335
- l.push(`<score-part id="${id2}">`);
91336
- l.push(...elemento("part-name", track.name || `Pista ${track.index + 1}`));
91337
- l.push(...elemento("part-abbreviation", track.shortName));
91338
- const esPercusion = track.staves.some((st) => st.isPercussion);
91339
- const instrumentos = esPercusion ? instrumentosDePercusion(track) : /* @__PURE__ */ new Map();
91340
- instrumentosPorPista.set(track.index, instrumentos);
91341
- if (esPercusion) {
91342
- for (const [art, idInstrumento] of instrumentos) {
91343
- const a = track.percussionArticulations[art];
91344
- l.push(`<score-instrument id="${idInstrumento}">`);
91345
- l.push(...elemento("instrument-name", `Percusi\xF3n ${art + 1}`));
91346
- l.push("</score-instrument>");
91347
- l.push(`<midi-instrument id="${idInstrumento}">`);
91348
- l.push("<midi-channel>10</midi-channel>");
91349
- l.push(`<midi-unpitched>${(a?.outputMidiNumber ?? 36) + 1}</midi-unpitched>`);
91350
- l.push("</midi-instrument>");
91351
- }
91352
- } else {
91353
- l.push(`<score-instrument id="${id2}-I1">`);
91354
- l.push(...elemento("instrument-name", track.name || `Pista ${track.index + 1}`));
91355
- l.push("</score-instrument>");
91356
- l.push(`<midi-instrument id="${id2}-I1">`);
91357
- l.push(`<midi-channel>${track.playbackInfo.primaryChannel + 1}</midi-channel>`);
91358
- l.push(`<midi-program>${track.playbackInfo.program + 1}</midi-program>`);
91359
- l.push(`<volume>${Math.round(track.playbackInfo.volume / 16 * 100)}</volume>`);
91360
- l.push(`<pan>${Math.round((track.playbackInfo.balance - 8) / 8 * 90)}</pan>`);
91361
- l.push("</midi-instrument>");
91362
- }
91363
- l.push("</score-part>");
91364
- }
91365
- l.push("</part-list>");
91366
- for (const track of score.tracks) {
91367
- const instrumentos = instrumentosPorPista.get(track.index) ?? /* @__PURE__ */ new Map();
91368
- l.push(`<part id="P${track.index + 1}">`);
91369
- const pentagramas = track.staves;
91370
- if (pentagramas.length === 0) {
91371
- l.push("</part>");
91372
- continue;
92209
+ p.synthTime = lastSyncPoint.synthTime + ticksAMs(remainingTicks, lastSyncPoint.synthBpm);
92210
+ p.syncTime = lastSyncPoint.syncTime + ticksAMs(remainingTicks, p.syncBpm);
92211
+ if (!playContext.createNewSyncPoints) lastSyncPoint.updateSyncBpm(p.synthTime, p.syncTime);
92212
+ syncPoints.push(p);
92213
+ }
91373
92214
  }
91374
- let armaduraAnterior = null;
91375
- let compasAnterior = null;
91376
- let clavesAnteriores = null;
91377
- const cantidadDeCompases = Math.max(...pentagramas.map((st) => st.bars.length));
91378
- for (let indiceDeCompas2 = 0; indiceDeCompas2 < cantidadDeCompases; indiceDeCompas2++) {
91379
- const master = score.masterBars[indiceDeCompas2];
91380
- l.push(`<measure number="${indiceDeCompas2 + 1}">`);
91381
- const fifths = (pentagramas[0]?.bars[indiceDeCompas2] ?? pentagramas[0]?.bars[0])?.keySignature ?? master?.keySignature ?? 0;
91382
- const modo = master?.keySignatureType === _barrel_exports$4.KeySignatureType.Minor ? "minor" : "major";
91383
- const armadura = `${fifths}/${modo}`;
91384
- const numerador = master?.timeSignatureNumerator ?? 4;
91385
- const denominador = master?.timeSignatureDenominator ?? 4;
91386
- const compas = `${numerador}/${denominador}`;
91387
- const claves = pentagramas.map((st, k) => {
91388
- const bar = st.bars[indiceDeCompas2] ?? st.bars[0];
91389
- return { pentagrama: st, numero: k + 1, ...claveDelPentagrama(st, bar) };
91390
- });
91391
- const firmaDeClaves = claves.map((c) => `${c.signo}${c.linea}`).join(",");
91392
- const cambios = [];
91393
- if (indiceDeCompas2 === 0) cambios.push(`<divisions>${DIVISIONES_POR_NEGRA}</divisions>`);
91394
- if (armadura !== armaduraAnterior) {
91395
- cambios.push("<key>");
91396
- cambios.push(`<fifths>${fifths}</fifths>`);
91397
- cambios.push(`<mode>${modo}</mode>`);
91398
- cambios.push("</key>");
91399
- armaduraAnterior = armadura;
92215
+ finish(controller.currentTick);
92216
+ return playContext;
92217
+ };
92218
+ Generador.__recorridoDe19 = true;
92219
+ return true;
92220
+ }
92221
+ var D2, Repeticion, RecorridoDeCompases, ticksAMs;
92222
+ var init_recorridoDeRepeticiones = __esm({
92223
+ "packages/io/src/recorridoDeRepeticiones.ts"() {
92224
+ init_alphaTab();
92225
+ D2 = {
92226
+ TargetFine: 0,
92227
+ TargetSegno: 1,
92228
+ TargetSegnoSegno: 2,
92229
+ TargetCoda: 3,
92230
+ TargetDoubleCoda: 4,
92231
+ JumpDaCoda: 17,
92232
+ JumpDaDoubleCoda: 18
92233
+ };
92234
+ Repeticion = class {
92235
+ group;
92236
+ /** Con casillas, cada cierre termina la pasada y todos comparten el contador. */
92237
+ conCasillas;
92238
+ /** La pasada actual (desde 0): elige qué casillas suenan. */
92239
+ pasada = 0;
92240
+ /** Las repeticiones hechas por cierre (sólo sin casillas). */
92241
+ iteraciones;
92242
+ /** Las casillas del último compás que las tenía: los que no tienen, las continúan. */
92243
+ casillasActuales = 0;
92244
+ /** @param pasadaFinal si se toca como última pasada (después de un salto): sólo la última casilla. */
92245
+ constructor(group, pasadaFinal) {
92246
+ this.group = group;
92247
+ this.conCasillas = group.masterBars.some((m) => m.alternateEndings !== 0);
92248
+ this.iteraciones = group.closings.map((c) => pasadaFinal ? Math.max(0, c.repeatCount - 1) : 0);
92249
+ if (pasadaFinal) for (const i2 of this.iteraciones) this.pasada = Math.max(this.pasada, i2);
91400
92250
  }
91401
- if (compas !== compasAnterior) {
91402
- cambios.push("<time>");
91403
- cambios.push(`<beats>${numerador}</beats>`);
91404
- cambios.push(`<beat-type>${denominador}</beat-type>`);
91405
- cambios.push("</time>");
91406
- compasAnterior = compas;
92251
+ suena(compas) {
92252
+ let casillas = compas.alternateEndings;
92253
+ if (casillas === 0) casillas = this.casillasActuales;
92254
+ else this.casillasActuales = casillas;
92255
+ return casillas === 0 || (casillas & 1 << this.pasada) !== 0;
91407
92256
  }
91408
- if (firmaDeClaves !== clavesAnteriores) {
91409
- if (pentagramas.length > 1 && clavesAnteriores === null)
91410
- cambios.push(`<staves>${pentagramas.length}</staves>`);
91411
- const num = (clave) => pentagramas.length > 1 ? ` number="${clave.numero}"` : "";
91412
- for (const clave of claves) {
91413
- cambios.push(`<clef${num(clave)}>`);
91414
- cambios.push(`<sign>${clave.signo}</sign>`);
91415
- cambios.push(`<line>${clave.linea}</line>`);
91416
- cambios.push("</clef>");
92257
+ /** 0 = seguir, 1 = volver al comienzo de la repetición, 2 = terminó. */
92258
+ alCerrar(cierre, sono) {
92259
+ const cierres = this.group.closings;
92260
+ const esElUltimo = cierre === cierres[cierres.length - 1];
92261
+ if (this.conCasillas) {
92262
+ if (!sono && !esElUltimo) return 0;
92263
+ if (this.pasada < cierre.repeatCount - 1) {
92264
+ this.pasada++;
92265
+ this.casillasActuales = 0;
92266
+ return 1;
92267
+ }
92268
+ return esElUltimo ? 2 : 0;
91417
92269
  }
91418
- for (const clave of claves) {
91419
- cambios.push(`<staff-details${num(clave)}>`);
91420
- cambios.push(`<staff-lines>${clave.lineas}</staff-lines>`);
91421
- if (clave.conAfinacion)
91422
- clave.pentagrama.tuning.forEach((midi, i2) => {
91423
- const linea = clave.pentagrama.tuning.length - i2;
91424
- const { paso, alteracion, octava } = alturaMusicXml(midi, fifths);
91425
- cambios.push(`<staff-tuning line="${linea}">`);
91426
- cambios.push(`<tuning-step>${paso}</tuning-step>`);
91427
- if (alteracion !== 0) cambios.push(`<tuning-alter>${alteracion}</tuning-alter>`);
91428
- cambios.push(`<tuning-octave>${octava}</tuning-octave>`);
91429
- cambios.push("</staff-tuning>");
91430
- });
91431
- cambios.push("</staff-details>");
92270
+ const i2 = cierres.indexOf(cierre);
92271
+ if (i2 === -1) return 0;
92272
+ if (this.iteraciones[i2] < cierre.repeatCount - 1) {
92273
+ this.iteraciones[i2]++;
92274
+ for (let j = 0; j < i2; j++) this.iteraciones[j] = 0;
92275
+ this.casillasActuales = 0;
92276
+ return 1;
91432
92277
  }
91433
- clavesAnteriores = firmaDeClaves;
92278
+ return esElUltimo ? 2 : 0;
91434
92279
  }
91435
- if (cambios.length > 0) l.push("<attributes>", ...cambios, "</attributes>");
91436
- const casilla = master?.alternateEndings ?? 0;
91437
- const casillaAntes = score.masterBars[indiceDeCompas2 - 1]?.alternateEndings ?? 0;
91438
- const casillaDespues = score.masterBars[indiceDeCompas2 + 1]?.alternateEndings ?? 0;
91439
- const numerosDeCasilla = [0, 1, 2, 3, 4, 5, 6, 7].filter((b) => casilla & 1 << b).map((b) => b + 1).join(", ");
91440
- if (master?.isRepeatStart || casilla && casilla !== casillaAntes) {
91441
- l.push('<barline location="left">');
91442
- if (master?.isRepeatStart) l.push("<bar-style>heavy-light</bar-style>");
91443
- if (casilla && casilla !== casillaAntes) l.push(`<ending number="${numerosDeCasilla}" type="start"/>`);
91444
- if (master?.isRepeatStart) l.push('<repeat direction="forward"/>');
91445
- l.push("</barline>");
92280
+ /** Si algún cierre todavía va a volver por encima de este compás. */
92281
+ vaAVolver(compas) {
92282
+ const cierres = this.group.closings;
92283
+ for (let i2 = 0; i2 < cierres.length; i2++) {
92284
+ const cierre = cierres[i2];
92285
+ if (cierre.index >= compas.index) {
92286
+ if ((this.conCasillas ? this.pasada : this.iteraciones[i2]) < cierre.repeatCount - 1) return true;
92287
+ }
92288
+ }
92289
+ return false;
91446
92290
  }
91447
- if (master?.section?.text || master?.section?.marker)
91448
- l.push(
91449
- '<direction placement="above">',
91450
- "<direction-type>",
91451
- `<rehearsal>${escapar(master.section.marker || master.section.text)}</rehearsal>`,
91452
- "</direction-type>",
91453
- "</direction>"
91454
- );
91455
- l.push(...saltosDelCompas(master));
91456
- const ticsDelCompas = DIVISIONES_POR_NEGRA * 4 * numerador / denominador;
91457
- const tempos = (master?.tempoAutomations ?? []).map((a) => ({ bpm: a.value, desde: a.ratioPosition }));
91458
- if (indiceDeCompas2 === 0 && score.tempo && !tempos.some((t) => t.desde <= 1e-6))
91459
- tempos.unshift({ bpm: score.tempo, desde: 0 });
91460
- for (const t of tempos) {
91461
- const bpm = Math.round(t.bpm);
91462
- const offset = Math.round(t.desde * ticsDelCompas);
91463
- l.push(
91464
- '<direction placement="above">',
91465
- "<direction-type>",
91466
- "<metronome>",
91467
- "<beat-unit>quarter</beat-unit>",
91468
- `<per-minute>${bpm}</per-minute>`,
91469
- "</metronome>",
91470
- "</direction-type>",
91471
- ...offset > 0 ? [`<offset>${offset}</offset>`] : [],
91472
- `<sound tempo="${bpm}"/>`,
91473
- "</direction>"
91474
- );
92291
+ };
92292
+ RecorridoDeCompases = class _RecorridoDeCompases {
92293
+ /** Los saltos en el orden en que se revisan si un compás tiene varios. */
92294
+ static SALTOS = [
92295
+ { jump: 5, target: null, playUntil: null, continueAt: null },
92296
+ { jump: 6, target: null, playUntil: D2.JumpDaCoda, continueAt: D2.TargetCoda },
92297
+ { jump: 7, target: null, playUntil: D2.JumpDaDoubleCoda, continueAt: D2.TargetDoubleCoda },
92298
+ { jump: 8, target: null, playUntil: D2.TargetFine, continueAt: null },
92299
+ { jump: 9, target: D2.TargetSegno, playUntil: null, continueAt: null },
92300
+ { jump: 10, target: D2.TargetSegno, playUntil: D2.JumpDaCoda, continueAt: D2.TargetCoda },
92301
+ { jump: 11, target: D2.TargetSegno, playUntil: D2.JumpDaDoubleCoda, continueAt: D2.TargetDoubleCoda },
92302
+ { jump: 12, target: D2.TargetSegno, playUntil: D2.TargetFine, continueAt: null },
92303
+ { jump: 13, target: D2.TargetSegnoSegno, playUntil: null, continueAt: null },
92304
+ { jump: 14, target: D2.TargetSegnoSegno, playUntil: D2.JumpDaCoda, continueAt: D2.TargetCoda },
92305
+ { jump: 15, target: D2.TargetSegnoSegno, playUntil: D2.JumpDaDoubleCoda, continueAt: D2.TargetDoubleCoda },
92306
+ { jump: 16, target: D2.TargetSegnoSegno, playUntil: D2.TargetFine, continueAt: null }
92307
+ ];
92308
+ score;
92309
+ pila = [];
92310
+ /** El D.C./D.S. que se siguió; null si se toca normal (antes del salto o después de la coda). */
92311
+ saltoActivo = null;
92312
+ /** Los compases cuyo salto ya se tomó: cada salto se toma una sola vez. */
92313
+ saltosTomados = /* @__PURE__ */ new Set();
92314
+ shouldPlay = true;
92315
+ index = 0;
92316
+ currentTick = 0;
92317
+ constructor(score) {
92318
+ this.score = score;
91475
92319
  }
91476
- let ultimoAvance = 0;
91477
- let hayAlgoEscrito = false;
91478
- for (const { pentagrama, numero: numero2 } of claves) {
91479
- const bar = pentagrama.bars[indiceDeCompas2];
91480
- if (!bar) continue;
91481
- bar.voices.forEach((voice, indiceDeVoz) => {
91482
- if (voice.beats.length === 0 || voice.isEmpty) return;
91483
- if (hayAlgoEscrito && ultimoAvance > 0)
91484
- l.push("<backup>", `<duration>${ultimoAvance}</duration>`, "</backup>");
91485
- const { abren, cierran } = gruposDeTresillo(voice.beats);
91486
- const numeroDePentagrama = pentagramas.length > 1 ? numero2 : null;
91487
- let avance = 0;
91488
- voice.beats.forEach((beat, i2) => {
91489
- const duracion = beat.displayDuration;
91490
- if (beat.graceType === _barrel_exports$4.GraceType.None) avance += duracion;
91491
- if (beat.isRest || beat.notes.length === 0) {
91492
- l.push(...escribirSilencio2(beat, duracion, indiceDeVoz + 1, numeroDePentagrama));
92320
+ get finished() {
92321
+ return this.index >= this.score.masterBars.length;
92322
+ }
92323
+ processCurrent() {
92324
+ const compas = this.score.masterBars[this.index];
92325
+ this.entrarARepeticion(compas);
92326
+ const rep = this.pila.length > 0 ? this.pila[this.pila.length - 1] : null;
92327
+ this.shouldPlay = rep === null || rep.suena(compas);
92328
+ if (this.shouldPlay) this.currentTick += compas.calculateDuration();
92329
+ }
92330
+ moveNext() {
92331
+ if (this.moverConSaltos()) return;
92332
+ const compas = this.score.masterBars[this.index];
92333
+ if (this.pila.length > 0 && compas.repeatCount > 1) {
92334
+ const rep = this.pila[this.pila.length - 1];
92335
+ switch (rep.alCerrar(compas, this.shouldPlay)) {
92336
+ case 1:
92337
+ this.index = rep.group.opening.index;
91493
92338
  return;
91494
- }
91495
- beat.notes.forEach((note, k) => {
91496
- l.push(
91497
- ...escribirNota(note, beat, pentagrama, {
91498
- esAcorde: k > 0,
91499
- fifths,
91500
- duracion,
91501
- // ★ La marca de tresillo va en la PRIMERA nota del golpe: es una marca del
91502
- // golpe, no de cada cuerda que suena en él.
91503
- abreTresillo: k === 0 && abren.has(i2),
91504
- cierraTresillo: k === 0 && cierran.has(i2),
91505
- instrumentos,
91506
- numeroDeVoz: indiceDeVoz + 1,
91507
- numeroDePentagrama
91508
- })
91509
- );
91510
- });
91511
- });
91512
- ultimoAvance = avance;
91513
- hayAlgoEscrito = true;
91514
- });
92339
+ case 2:
92340
+ this.pila.pop();
92341
+ }
92342
+ }
92343
+ this.index++;
91515
92344
  }
91516
- const cierraCasilla = casilla !== 0 && casilla !== casillaDespues;
91517
- if (master?.isRepeatEnd || master?.isDoubleBar || cierraCasilla) {
91518
- l.push('<barline location="right">');
91519
- if (master?.isRepeatEnd || master?.isDoubleBar)
91520
- l.push(`<bar-style>${master.isRepeatEnd ? "light-heavy" : "light-light"}</bar-style>`);
91521
- if (cierraCasilla)
91522
- l.push(`<ending number="${numerosDeCasilla}" type="${master?.isRepeatEnd ? "stop" : "discontinue"}"/>`);
91523
- if (master?.isRepeatEnd)
91524
- l.push(`<repeat direction="backward" times="${Math.max(2, master.repeatCount)}"/>`);
91525
- l.push("</barline>");
92345
+ /** Empieza la repetición del compás si hace falta: al llegar a su apertura, o tras un salto que cayó adentro. */
92346
+ entrarARepeticion(compas) {
92347
+ const group = compas.repeatGroup;
92348
+ if (!group.isClosed || this.pila.some((r) => r.group === group)) return;
92349
+ const adentro = compas.index <= group.closings[group.closings.length - 1].index;
92350
+ if (compas === group.opening || this.saltoActivo !== null && adentro) {
92351
+ this.pila.push(new Repeticion(group, this.saltoActivo !== null));
92352
+ }
91526
92353
  }
91527
- l.push("</measure>");
91528
- }
91529
- l.push("</part>");
91530
- }
91531
- l.push("</score-partwise>");
91532
- return sangrar(l);
91533
- }
91534
- function sangrar(lineas) {
91535
- const salida = [];
91536
- let nivel = 0;
91537
- for (const linea of lineas) {
91538
- const cierra = linea.startsWith("</");
91539
- if (cierra) nivel = Math.max(0, nivel - 1);
91540
- salida.push(" ".repeat(nivel) + linea);
91541
- const abre = /^<[^/?!]/.test(linea) && !linea.endsWith("/>") && !/<\/[^>]+>$/.test(linea);
91542
- if (abre) nivel++;
91543
- }
91544
- return salida.join("\n") + "\n";
91545
- }
91546
- function exportarMusicXmlComprimido(score) {
91547
- const xml = exportarMusicXml(score);
91548
- const contenedor = `<?xml version="1.0" encoding="UTF-8"?>
91549
- <container>
91550
- <rootfiles>
91551
- <rootfile full-path="partitura.musicxml" media-type="application/vnd.recordare.musicxml+xml"/>
91552
- </rootfiles>
91553
- </container>
91554
- `;
91555
- const codificador = new TextEncoder();
91556
- return zipSync(
91557
- {
91558
- "META-INF/container.xml": [codificador.encode(contenedor), { level: 0 }],
91559
- "partitura.musicxml": codificador.encode(xml)
91560
- },
91561
- // ⚠️ Fecha FIJA, no la de hoy: dos exportaciones iguales tienen que dar dos archivos
91562
- // iguales byte a byte, o no hay prueba que pueda compararlos ni `diff` de git que
91563
- // sirva. El porqué del valor concreto está en la constante — se tardó dos rojos en
91564
- // encontrarlo, y ninguno de los dos era el que yo esperaba.
91565
- { mtime: FECHA_FIJA_DEL_ZIP }
91566
- );
91567
- }
91568
- var DIVISIONES_POR_NEGRA, FECHA_FIJA_DEL_ZIP, PASOS, NOMBRE_DE_LA_FIGURA2, ALTERACION_POR_MODO, D2, SALTOS;
91569
- var init_musicxml = __esm({
91570
- "packages/io/src/musicxml.ts"() {
91571
- init_alphaTab();
91572
- init_esm();
91573
- DIVISIONES_POR_NEGRA = 960;
91574
- FECHA_FIJA_DEL_ZIP = 946728e6;
91575
- PASOS = [
91576
- { paso: "C", semitonos: 0 },
91577
- { paso: "D", semitonos: 2 },
91578
- { paso: "E", semitonos: 4 },
91579
- { paso: "F", semitonos: 5 },
91580
- { paso: "G", semitonos: 7 },
91581
- { paso: "A", semitonos: 9 },
91582
- { paso: "B", semitonos: 11 }
91583
- ];
91584
- NOMBRE_DE_LA_FIGURA2 = /* @__PURE__ */ new Map([
91585
- [-4, "long"],
91586
- [-2, "breve"],
91587
- [1, "whole"],
91588
- [2, "half"],
91589
- [4, "quarter"],
91590
- [8, "eighth"],
91591
- [16, "16th"],
91592
- [32, "32nd"],
91593
- [64, "64th"],
91594
- [128, "128th"],
91595
- [256, "256th"]
91596
- ]);
91597
- ALTERACION_POR_MODO = { 2: 0, 3: 1, 4: 2, 5: -1, 6: -2 };
91598
- D2 = _barrel_exports$4.Direction;
91599
- SALTOS = [
91600
- [D2.TargetSegno, { simbolo: "segno", sonido: 'segno="segno"' }],
91601
- [D2.TargetSegnoSegno, { simbolo: "segno", sonido: 'segno="segno2"' }],
91602
- [D2.TargetCoda, { simbolo: "coda", sonido: 'coda="coda"' }],
91603
- [D2.TargetDoubleCoda, { simbolo: "coda", sonido: 'coda="coda2"' }],
91604
- [D2.TargetFine, { texto: "Fine", sonido: 'fine="yes"' }],
91605
- [D2.JumpDaCapo, { texto: "D.C.", sonido: 'dacapo="yes"' }],
91606
- [D2.JumpDaCapoAlCoda, { texto: "D.C. al Coda", sonido: 'dacapo="yes"' }],
91607
- [D2.JumpDaCapoAlDoubleCoda, { texto: "D.C. al Coda 2", sonido: 'dacapo="yes"' }],
91608
- [D2.JumpDaCapoAlFine, { texto: "D.C. al Fine", sonido: 'dacapo="yes"' }],
91609
- [D2.JumpDalSegno, { texto: "D.S.", sonido: 'dalsegno="segno"' }],
91610
- [D2.JumpDalSegnoAlCoda, { texto: "D.S. al Coda", sonido: 'dalsegno="segno"' }],
91611
- [D2.JumpDalSegnoAlDoubleCoda, { texto: "D.S. al Coda 2", sonido: 'dalsegno="segno"' }],
91612
- [D2.JumpDalSegnoAlFine, { texto: "D.S. al Fine", sonido: 'dalsegno="segno"' }],
91613
- [D2.JumpDalSegnoSegno, { texto: "D.S.S.", sonido: 'dalsegno="segno2"' }],
91614
- [D2.JumpDalSegnoSegnoAlCoda, { texto: "D.S.S. al Coda", sonido: 'dalsegno="segno2"' }],
91615
- [D2.JumpDalSegnoSegnoAlDoubleCoda, { texto: "D.S.S. al Coda 2", sonido: 'dalsegno="segno2"' }],
91616
- [D2.JumpDalSegnoSegnoAlFine, { texto: "D.S.S. al Fine", sonido: 'dalsegno="segno2"' }],
91617
- [D2.JumpDaCoda, { texto: "To Coda", sonido: 'tocoda="coda"' }],
91618
- [D2.JumpDaDoubleCoda, { texto: "To Coda 2", sonido: 'tocoda="coda2"' }]
91619
- ];
92354
+ moverConSaltos() {
92355
+ const compas = this.score.masterBars[this.index];
92356
+ const dir = compas.directions;
92357
+ if (!this.shouldPlay || dir === null || dir === void 0 || dir.size === 0) return false;
92358
+ if (this.saltoActivo === null) return this.tomarSalto(compas, dir);
92359
+ const hasta = this.saltoActivo.playUntil;
92360
+ if (hasta !== null && dir.has(hasta)) return this.seguirTrasElSalto(this.saltoActivo.continueAt);
92361
+ return false;
92362
+ }
92363
+ tomarSalto(compas, dir) {
92364
+ if (this.saltosTomados.has(compas) || this.pila.some((r) => r.vaAVolver(compas))) return false;
92365
+ for (const salto of _RecorridoDeCompases.SALTOS) {
92366
+ if (!dir.has(salto.jump)) continue;
92367
+ const destino = salto.target === null ? 0 : this.buscar(salto.target, true);
92368
+ if (destino === -1) continue;
92369
+ this.saltosTomados.add(compas);
92370
+ this.saltoActivo = salto;
92371
+ this.pila = [];
92372
+ this.index = destino;
92373
+ return true;
92374
+ }
92375
+ return false;
92376
+ }
92377
+ seguirTrasElSalto(seguirEn) {
92378
+ if (seguirEn === null) {
92379
+ this.index = this.score.masterBars.length;
92380
+ return true;
92381
+ }
92382
+ const coda = this.buscar(seguirEn, false);
92383
+ if (coda === -1) return false;
92384
+ this.saltoActivo = null;
92385
+ this.pila = [];
92386
+ this.index = coda;
92387
+ return true;
92388
+ }
92389
+ buscar(direccion, primeroAtras) {
92390
+ const paso = primeroAtras ? -1 : 1;
92391
+ const i2 = this.buscarHacia(direccion, paso);
92392
+ return i2 !== -1 ? i2 : this.buscarHacia(direccion, -paso);
92393
+ }
92394
+ buscarHacia(direccion, paso) {
92395
+ const compases = this.score.masterBars;
92396
+ for (let i2 = this.index; i2 >= 0 && i2 < compases.length; i2 += paso) {
92397
+ const d = compases[i2].directions;
92398
+ if (d !== null && d !== void 0 && d.has(direccion)) return i2;
92399
+ }
92400
+ return -1;
92401
+ }
92402
+ };
92403
+ ticksAMs = (ticks, tempo) => ticks * (6e4 / (tempo * 960)) | 0;
91620
92404
  }
91621
92405
  });
91622
92406
 
@@ -91780,6 +92564,13 @@ function obraAbierta() {
91780
92564
  }
91781
92565
  return abierta;
91782
92566
  }
92567
+ function olvidarLaObra() {
92568
+ abierta = null;
92569
+ sigueViva = null;
92570
+ }
92571
+ function soltarLaObraDeLaPesta\u00F1a() {
92572
+ if (abierta?.origen.clase === "pesta\xF1a") olvidarLaObra();
92573
+ }
91783
92574
  async function abrirDeArchivo(ruta) {
91784
92575
  let bytes;
91785
92576
  try {
@@ -91796,7 +92587,9 @@ async function abrirDeArchivo(ruta) {
91796
92587
  abierta = {
91797
92588
  documento: archivo.documento,
91798
92589
  origen: { clase: "archivo", ruta },
91799
- avisos: [],
92590
+ // ★ (2026-11-11) Una obra vieja con el piano en dos pistas vuelve junta: se dice, para que
92591
+ // la IA no busque la «mano izquierda» como pista aparte.
92592
+ avisos: archivo.manosJuntadas > 0 ? ["El piano estaba guardado en dos pistas (una por mano): ahora es un solo instrumento con dos pentagramas."] : [],
91800
92593
  grabaciones: archivo.grabaciones,
91801
92594
  grabacionesEnEspera: [],
91802
92595
  fuentes: archivo.fuentes
@@ -91917,7 +92710,7 @@ function avisoDeLoQueNoSuena(mudas, huerfanas) {
91917
92710
  const partes = [];
91918
92711
  if (mudas > 0) {
91919
92712
  partes.push(
91920
- `${mudas} grabaci\xF3n(es) viajaron con su onda pero sin audio: no est\xE1n cargadas en esta sesi\xF3n. El archivo las conserva como fichas.`
92713
+ mudas === 1 ? "1 grabaci\xF3n viaj\xF3 con su onda pero sin audio: no est\xE1 cargada en esta sesi\xF3n. El archivo la conserva como ficha." : `${mudas} grabaciones viajaron con su onda pero sin audio: no est\xE1n cargadas en esta sesi\xF3n. El archivo las conserva como fichas.`
91921
92714
  );
91922
92715
  }
91923
92716
  if (huerfanas > 0) {
@@ -91980,7 +92773,7 @@ var VERSION_DEL_CONECTOR;
91980
92773
  var init_version = __esm({
91981
92774
  "apps/mcp/src/version.ts"() {
91982
92775
  "use strict";
91983
- VERSION_DEL_CONECTOR = true ? "0.2.17" : null;
92776
+ VERSION_DEL_CONECTOR = true ? "0.2.19" : null;
91984
92777
  }
91985
92778
  });
91986
92779
 
@@ -91995,9 +92788,11 @@ __export(puente_exports, {
91995
92788
  SALA_DE_LA_OBRA: () => SALA_DE_LA_OBRA,
91996
92789
  SALA_DE_PEDIDOS: () => SALA_DE_PEDIDOS,
91997
92790
  SALA_OCIOSA_MS: () => SALA_OCIOSA_MS,
92791
+ VENCIMIENTO_DEL_OFRECIDO_MS: () => VENCIMIENTO_DEL_OFRECIDO_MS,
91998
92792
  abrirPuente: () => abrirPuente,
91999
92793
  buscarLaPesta\u00F1a: () => buscarLaPesta\u00F1a,
92000
92794
  cerrarPuente: () => cerrarPuente,
92795
+ cuantosArchivosOfrecidos: () => cuantosArchivosOfrecidos,
92001
92796
  elegirLaObra: () => elegirLaObra,
92002
92797
  exigirPesta\u00F1a: () => exigirPesta\u00F1a,
92003
92798
  obraDeLaEtiqueta: () => obraDeLaEtiqueta,
@@ -92010,6 +92805,7 @@ __export(puente_exports, {
92010
92805
  pesta\u00F1asAhora: () => pesta\u00F1asAhora,
92011
92806
  puenteAbierto: () => puenteAbierto,
92012
92807
  reservarArchivo: () => reservarArchivo,
92808
+ retirarArchivo: () => retirarArchivo,
92013
92809
  soltarReserva: () => soltarReserva
92014
92810
  });
92015
92811
  import { createServer } from "node:http";
@@ -92072,30 +92868,31 @@ function obraDeLaEtiqueta(etiqueta) {
92072
92868
  const buscada = etiqueta.trim().toUpperCase();
92073
92869
  return puenteAbierto().obras().find((o) => o.etiqueta === buscada) ?? null;
92074
92870
  }
92075
- function puenteAbierto() {
92871
+ function puenteAbierto(reclamar = true) {
92076
92872
  if (!puente) {
92077
92873
  throw new ProblemaConLaObra(
92078
92874
  "El puente con la aplicaci\xF3n no est\xE1 abierto. Usa `conectar_con_la_app` primero."
92079
92875
  );
92080
92876
  }
92081
- reclamado = true;
92877
+ if (reclamar) reclamado = true;
92082
92878
  return puente;
92083
92879
  }
92084
92880
  async function pedirQueLoSuelte(puerto) {
92085
92881
  try {
92086
92882
  const r = await fetch(`http://127.0.0.1:${puerto}/soltar`, { method: "POST" });
92087
- if (!r.ok) return false;
92883
+ if (r.status === 409) return "lo-usa-otra-ia";
92884
+ if (!r.ok) return "no-es-un-conector";
92088
92885
  for (let i2 = 0; i2 < 40; i2++) {
92089
92886
  try {
92090
92887
  await fetch(`http://127.0.0.1:${puerto}/version`);
92091
92888
  } catch {
92092
- return true;
92889
+ return "soltado";
92093
92890
  }
92094
92891
  await new Promise((listo) => setTimeout(listo, 50));
92095
92892
  }
92096
- return false;
92893
+ return "no-es-un-conector";
92097
92894
  } catch {
92098
- return false;
92895
+ return "no-es-un-conector";
92099
92896
  }
92100
92897
  }
92101
92898
  function exigirPesta\u00F1a() {
@@ -92107,7 +92904,7 @@ function exigirPesta\u00F1a() {
92107
92904
  }
92108
92905
  return p;
92109
92906
  }
92110
- function ofrecerArchivo(ruta) {
92907
+ function ofrecerArchivo(ruta, venceEnMs = VENCIMIENTO_DEL_OFRECIDO_MS) {
92111
92908
  let tama\u00F1o;
92112
92909
  try {
92113
92910
  const datos = statSync(ruta);
@@ -92119,8 +92916,15 @@ function ofrecerArchivo(ruta) {
92119
92916
  const ficha = randomUUID();
92120
92917
  const ofrecido = { ruta, nombre: basename2(ruta), bytes: tama\u00F1o };
92121
92918
  mostrador.set(ficha, ofrecido);
92919
+ setTimeout(() => mostrador.delete(ficha), venceEnMs).unref?.();
92122
92920
  return { ...ofrecido, ficha };
92123
92921
  }
92922
+ function retirarArchivo(ficha) {
92923
+ mostrador.delete(ficha);
92924
+ }
92925
+ function cuantosArchivosOfrecidos() {
92926
+ return mostrador.size;
92927
+ }
92124
92928
  function olvidarArchivosOfrecidos() {
92125
92929
  mostrador.clear();
92126
92930
  casillero.clear();
@@ -92182,7 +92986,7 @@ function recibirEnElCasillero(ficha, pedido, respuesta) {
92182
92986
  });
92183
92987
  pedido.pipe(salida);
92184
92988
  }
92185
- function atenderElMostrador(pedido, respuesta) {
92989
+ function atenderElMostrador(pedido, respuesta, puerto) {
92186
92990
  respuesta.setHeader("Access-Control-Allow-Origin", "*");
92187
92991
  const ruta = new URL(pedido.url ?? "", "http://127.0.0.1").pathname;
92188
92992
  if (pedido.method === "OPTIONS") {
@@ -92192,6 +92996,12 @@ function atenderElMostrador(pedido, respuesta) {
92192
92996
  return;
92193
92997
  }
92194
92998
  if (ruta === "/version") {
92999
+ respuesta.removeHeader("Access-Control-Allow-Origin");
93000
+ const origen = pedido.headers.origin;
93001
+ if (origen !== void 0 && origenPermitido(origen, puerto)) {
93002
+ respuesta.setHeader("Access-Control-Allow-Origin", origen);
93003
+ }
93004
+ respuesta.setHeader("Vary", "Origin");
92195
93005
  respuesta.writeHead(200, { "Content-Type": "application/json" });
92196
93006
  respuesta.end(JSON.stringify({ version: VERSION_DEL_CONECTOR }));
92197
93007
  return;
@@ -92339,7 +93149,7 @@ function abrirDeVerdad(puerto) {
92339
93149
  };
92340
93150
  for (const [sala, doc] of docs) escuchar(sala, doc);
92341
93151
  return new Promise((resolver, rechazar) => {
92342
- const http = createServer(atenderElMostrador);
93152
+ const http = createServer((pedido, respuesta) => atenderElMostrador(pedido, respuesta, puerto));
92343
93153
  const servidor2 = new import_websocket_server.default({
92344
93154
  server: http,
92345
93155
  // Ver `origenPermitido`: una página de otro sitio recibe un 401 y no llega a la obra.
@@ -92377,6 +93187,13 @@ function abrirDeVerdad(puerto) {
92377
93187
  writeSyncStep1(saludo, doc);
92378
93188
  conexion.send(toUint8Array(saludo));
92379
93189
  conexion.on("message", (crudo) => {
93190
+ try {
93191
+ atenderElMensaje(crudo);
93192
+ } catch {
93193
+ conexion.close(1003, "mensaje ilegible");
93194
+ }
93195
+ });
93196
+ const atenderElMensaje = (crudo) => {
92380
93197
  const bytes = new Uint8Array(crudo instanceof ArrayBuffer ? crudo : crudo);
92381
93198
  const lector = createDecoder(bytes);
92382
93199
  const tipo = readVarUint(lector);
@@ -92395,7 +93212,7 @@ function abrirDeVerdad(puerto) {
92395
93212
  if (otra !== conexion && otra.readyState === import_websocket.default.OPEN) otra.send(bytes);
92396
93213
  }
92397
93214
  }
92398
- });
93215
+ };
92399
93216
  conexion.on("close", () => {
92400
93217
  conexiones.delete(conexion);
92401
93218
  olvidarSiQuedoVacia(sala);
@@ -92576,7 +93393,7 @@ async function pedirALaPesta\u00F1a(herramienta, argumentos = {}, msDelAcuse = E
92576
93393
  canal.transact(() => mapa.delete(id2));
92577
93394
  }
92578
93395
  }
92579
- var envoltoriosPorDoc, SINCRONIZAR, PRESENCIA, SALA_DE_LA_OBRA, SALA_DE_PEDIDOS, PUERTO_POR_OMISION, ORIGENES_DE_TABS, PUERTOS_LOCALES_DE_TABS, PUERTO_DEL_CONECTOR, puente, reclamado, ESPERA_POR_LA_PESTA\u00D1A, mostrador, casillero, TIPOS, abriendo, siguienteId, INSTANCIA, ESPERA_DEL_ACUSE_MS, LATIDO_MS, LATIDO_DEL_PUENTE_MS, AUSENCIA_PARA_DARLA_POR_IDA_MS, SALA_OCIOSA_MS, MARGEN_PARA_QUE_ENTRE_MS;
93396
+ var envoltoriosPorDoc, SINCRONIZAR, PRESENCIA, SALA_DE_LA_OBRA, SALA_DE_PEDIDOS, PUERTO_POR_OMISION, ORIGENES_DE_TABS, PUERTOS_LOCALES_DE_TABS, PUERTO_DEL_CONECTOR, puente, reclamado, ESPERA_POR_LA_PESTA\u00D1A, mostrador, VENCIMIENTO_DEL_OFRECIDO_MS, casillero, TIPOS, abriendo, siguienteId, INSTANCIA, ESPERA_DEL_ACUSE_MS, LATIDO_MS, LATIDO_DEL_PUENTE_MS, AUSENCIA_PARA_DARLA_POR_IDA_MS, SALA_OCIOSA_MS, MARGEN_PARA_QUE_ENTRE_MS;
92580
93397
  var init_puente = __esm({
92581
93398
  "apps/mcp/src/puente.ts"() {
92582
93399
  "use strict";
@@ -92606,6 +93423,7 @@ var init_puente = __esm({
92606
93423
  reclamado = false;
92607
93424
  ESPERA_POR_LA_PESTA\u00D1A = 3500;
92608
93425
  mostrador = /* @__PURE__ */ new Map();
93426
+ VENCIMIENTO_DEL_OFRECIDO_MS = 10 * 6e4;
92609
93427
  casillero = /* @__PURE__ */ new Map();
92610
93428
  TIPOS = Object.fromEntries(
92611
93429
  FORMATOS_DE_GRABACION.map((f) => [f.extension, f.mime])
@@ -100125,6 +100943,9 @@ function proyectar(documento, opciones = {}) {
100125
100943
  const figurasPorCompas = /* @__PURE__ */ new Map();
100126
100944
  for (const [id2, entidad] of documento.figuras.entries()) {
100127
100945
  const figura = normalizarFigura(entidad.toJSON());
100946
+ if (!Number.isInteger(figura.voz) || figura.voz < 0 || figura.voz >= VOCES_MAXIMAS) continue;
100947
+ if (figura.tiempo < 0 || figura.tiempo > TIEMPO_MAXIMO) continue;
100948
+ if (figura.puntillos > PUNTILLOS_MAXIMOS) continue;
100128
100949
  const lista2 = figurasPorCompas.get(figura.compasId);
100129
100950
  if (lista2) lista2.push({ id: id2, datos: figura });
100130
100951
  else figurasPorCompas.set(figura.compasId, [{ id: id2, datos: figura }]);
@@ -100350,7 +101171,6 @@ function proyectar(documento, opciones = {}) {
100350
101171
  notaAlphaTab.trillValue = nota.trino;
100351
101172
  notaAlphaTab.trillSpeed = nota.velocidadDeTrino;
100352
101173
  notaAlphaTab.accidentalMode = nota.alteracion;
100353
- if (nota.ligaduraDeExpresion) notaAlphaTab.isEffectSlurOrigin = true;
100354
101174
  if (nota.bend.length > 0) {
100355
101175
  notaAlphaTab.bendType = _barrel_exports$4.BendType.Custom;
100356
101176
  notaAlphaTab.bendStyle = _barrel_exports$4.BendStyle.Default;
@@ -100393,6 +101213,7 @@ function proyectar(documento, opciones = {}) {
100393
101213
  if (marca.subrayar) anotarSubrayado(notaAlphaTab, marca.subrayar);
100394
101214
  golpe.addNote(notaAlphaTab);
100395
101215
  }
101216
+ if (notasDelGolpe2.some((n) => n.ligaduraDeExpresion)) golpe.isLegatoOrigin = true;
100396
101217
  voz.addBeat(golpe);
100397
101218
  }
100398
101219
  if (voz.beats.length === 0) {
@@ -100425,6 +101246,12 @@ function proyectar(documento, opciones = {}) {
100425
101246
  });
100426
101247
  }
100427
101248
  score.finish(ajustes);
101249
+ for (const track of score.tracks)
101250
+ for (const staff of track.staves)
101251
+ for (const bar of staff.bars)
101252
+ for (const voice of bar.voices)
101253
+ for (const beat of voice.beats)
101254
+ if (beat.isLegatoOrigin && (!beat.nextBeat || beat.nextBeat.isRest)) beat.isLegatoOrigin = false;
100428
101255
  aplicarSync(score, documento);
100429
101256
  return score;
100430
101257
  }
@@ -100466,7 +101293,7 @@ function pasarLetraALaFila(score) {
100466
101293
  golpe.text = null;
100467
101294
  }
100468
101295
  }
100469
- var VOCES_POR_COMPAS, volumenDeMezcla;
101296
+ var VOCES_POR_COMPAS, VOCES_MAXIMAS, TIEMPO_MAXIMO, PUNTILLOS_MAXIMOS, volumenDeMezcla;
100470
101297
  var init_proyectar = __esm({
100471
101298
  "packages/projection/src/proyectar.ts"() {
100472
101299
  init_alphaTab();
@@ -100477,6 +101304,9 @@ var init_proyectar = __esm({
100477
101304
  init_src2();
100478
101305
  init_src2();
100479
101306
  VOCES_POR_COMPAS = 4;
101307
+ VOCES_MAXIMAS = 16;
101308
+ TIEMPO_MAXIMO = 960 * 4 * 64;
101309
+ PUNTILLOS_MAXIMOS = 8;
100480
101310
  volumenDeMezcla = /* @__PURE__ */ new WeakMap();
100481
101311
  }
100482
101312
  });
@@ -124605,6 +125435,7 @@ var LEEN = /* @__PURE__ */ new Set([
124605
125435
  "ajustes_de_video",
124606
125436
  "atajos_del_programa",
124607
125437
  "audios_rechazados_por_largos",
125438
+ "buscar_en_las_paletas",
124608
125439
  "captura",
124609
125440
  "direcciones_disponibles",
124610
125441
  "efectos_disponibles",
@@ -124624,7 +125455,7 @@ var LEEN = /* @__PURE__ */ new Set([
124624
125455
  "obras_conectadas",
124625
125456
  "obras_recientes",
124626
125457
  "piezas_de_bateria",
124627
- "precision_del_alineador",
125458
+ // ⛔ (E10) `precision_del_alineador` ya NO está acá: con `precision` cambia un ajuste.
124628
125459
  "que_hace_un_control",
124629
125460
  "secciones_de_la_obra",
124630
125461
  "signos_de_una_nota",
@@ -124654,11 +125485,15 @@ var BORRAN = /* @__PURE__ */ new Set([
124654
125485
  "exportar_pdf",
124655
125486
  "exportar_video"
124656
125487
  ]);
125488
+ var BAJAN_MODELOS = /* @__PURE__ */ new Set(["separar_en_stems", "regenerar_stems", "sincronizar_letra"]);
124657
125489
  function anotacionesDe(nombre) {
124658
125490
  if (LEEN.has(nombre)) return { readOnlyHint: true, openWorldHint: false };
124659
- return { readOnlyHint: false, destructiveHint: BORRAN.has(nombre), openWorldHint: false };
125491
+ return { readOnlyHint: false, destructiveHint: BORRAN.has(nombre), openWorldHint: BAJAN_MODELOS.has(nombre) };
124660
125492
  }
124661
125493
 
125494
+ // apps/mcp/src/servidor.ts
125495
+ init_obra();
125496
+
124662
125497
  // apps/mcp/src/herramientas/documento.ts
124663
125498
  init_src2();
124664
125499
  init_src3();
@@ -124677,7 +125512,7 @@ function texto2(cosa) {
124677
125512
  function empezarUnPaso() {
124678
125513
  obraAbierta().documento.historial().stopCapturing();
124679
125514
  }
124680
- function situarse(pista, compasDelMusico, voz) {
125515
+ function situarse(pista, compasDelMusico, voz, pentagrama) {
124681
125516
  const { documento } = obraAbierta();
124682
125517
  documento.historial().stopCapturing();
124683
125518
  const indice = indexar(documento);
@@ -124689,7 +125524,11 @@ function situarse(pista, compasDelMusico, voz) {
124689
125524
  volcado,
124690
125525
  pistaId,
124691
125526
  compas: indiceDeCompas(compasDelMusico, indice),
124692
- voz: (voz ?? 1) - 1
125527
+ voz: (voz ?? 1) - 1,
125528
+ // ⛔ (E7, 2026-11-11) El pentagrama, desde 0 y comprobado contra la pista. Sin esto, a la
125529
+ // mano izquierda del piano sólo llegaban `escribir_notas` y `borrar_notas`: una
125530
+ // duración, un signo o una sílaba pedidos ahí caían en la derecha.
125531
+ pentagrama: pentagramaDelMusico(pentagrama, indice, pistaId, pista ?? 1)
124693
125532
  };
124694
125533
  }
124695
125534
  function indiceDeCompas(numeroDelMusico, indice) {
@@ -124754,6 +125593,7 @@ function ticsDelGolpe(indice, pistaId, compas, voz, golpe, tics, pentagrama = 0)
124754
125593
  function cursorEn(indice, pistaId, compas, voz, tiempo, cuerda, pentagrama = 0) {
124755
125594
  return { ...cursorInicial(indice, pistaId), compas, voz, tiempo, cuerda, ...pentagrama > 0 ? { pentagrama } : {} };
124756
125595
  }
125596
+ var campoDePentagrama = external_exports.number().int().min(1).max(4).optional().describe("En qu\xE9 pentagrama de la pista, desde 1. S\xF3lo el piano tiene dos: 1 la mano derecha (arriba), 2 la izquierda. Por omisi\xF3n el 1.");
124757
125597
  function pentagramaDelMusico(pedido, indice, pistaId, numeroDePista) {
124758
125598
  if (pedido === void 0) return 0;
124759
125599
  const tiene = indice.pentagramasDe(pistaId);
@@ -124794,7 +125634,7 @@ var unaNota = {
124794
125634
  golpe: external_exports.number().int().min(1).max(999).optional(),
124795
125635
  tics: external_exports.number().int().min(0).max(99999).optional(),
124796
125636
  voz: external_exports.number().int().min(1).max(4).optional(),
124797
- pentagrama: external_exports.number().int().min(1).max(4).optional().describe("En qu\xE9 pentagrama de la pista, desde 1. S\xF3lo el piano tiene dos: 1 la mano derecha (arriba), 2 la izquierda. Por omisi\xF3n el 1.")
125637
+ pentagrama: campoDePentagrama
124798
125638
  };
124799
125639
  function piezaPorNombre(nombre) {
124800
125640
  const plano = (t) => t.normalize("NFD").replace(/[̀-ͯ]/g, "").trim().toLowerCase();
@@ -125213,16 +126053,24 @@ function registrarHerramientasDelDocumento(servidor2) {
125213
126053
  let borradas = 0;
125214
126054
  const laPista = pistaDelMusico(pista ?? 1, volcar(documento), documento);
125215
126055
  const pistaId = laPista.id;
125216
- for (const nota of notas) {
125217
- const indice = indexar(documento);
125218
- const compas = indiceDeCompas(nota.compas, indice);
125219
- const voz = (nota.voz ?? 1) - 1;
125220
- const pentagrama = pentagramaDelMusico(nota.pentagrama, indice, pistaId, pista ?? 1);
125221
- const tiempo = ticsDelGolpe(indice, pistaId, compas, voz, nota.golpe, nota.tics, pentagrama);
125222
- const nombrada = comoSeNombraLaNota(laPista.tipo, laPista.nombre, pista ?? 1, nota);
125223
- const cursor = cursorEn(indice, pistaId, compas, voz, tiempo, nombrada.clase === "trastes" ? nombrada.cuerda : 1, pentagrama);
125224
- const salio = nombrada.clase === "altura" ? borrarAltura(documento, indice, cursor, nombrada.altura) : nombrada.clase === "percusion" ? borrarPercusion(documento, indice, cursor, nombrada.articulacion) : borrarNotaDeCuerda(documento, indice, cursor, nombrada.cuerda);
125225
- if (salio) borradas += 1;
126056
+ try {
126057
+ for (const nota of notas) {
126058
+ const indice = indexar(documento);
126059
+ const compas = indiceDeCompas(nota.compas, indice);
126060
+ const voz = (nota.voz ?? 1) - 1;
126061
+ const pentagrama = pentagramaDelMusico(nota.pentagrama, indice, pistaId, pista ?? 1);
126062
+ const tiempo = ticsDelGolpe(indice, pistaId, compas, voz, nota.golpe, nota.tics, pentagrama);
126063
+ const nombrada = comoSeNombraLaNota(laPista.tipo, laPista.nombre, pista ?? 1, nota);
126064
+ const cursor = cursorEn(indice, pistaId, compas, voz, tiempo, nombrada.clase === "trastes" ? nombrada.cuerda : 1, pentagrama);
126065
+ const salio = nombrada.clase === "altura" ? borrarAltura(documento, indice, cursor, nombrada.altura) : nombrada.clase === "percusion" ? borrarPercusion(documento, indice, cursor, nombrada.articulacion) : borrarNotaDeCuerda(documento, indice, cursor, nombrada.cuerda);
126066
+ if (salio) borradas += 1;
126067
+ }
126068
+ } catch (e) {
126069
+ if (borradas > 0) documento.historial().undo();
126070
+ const motivo = e instanceof Error ? e.message : String(e);
126071
+ throw new ProblemaConLaObra(
126072
+ borradas > 0 ? `${motivo} No se borr\xF3 ninguna nota de esta tanda: las ${borradas} anteriores volvieron.` : motivo
126073
+ );
125226
126074
  }
125227
126075
  return texto2({ pedidas: notas.length, borradas });
125228
126076
  }
@@ -125270,11 +126118,23 @@ function registrarHerramientasDelPuente(servidor2) {
125270
126118
  },
125271
126119
  async ({ puerto }) => {
125272
126120
  const elPuerto = puerto ?? PUERTO_POR_OMISION;
126121
+ let abierto;
125273
126122
  try {
125274
- await abrirPuente(elPuerto);
126123
+ abierto = await abrirPuente(elPuerto);
125275
126124
  } catch (causa) {
125276
- if (!await pedirQueLoSuelte(elPuerto)) throw causa;
125277
- await abrirPuente(elPuerto);
126125
+ const respuesta = await pedirQueLoSuelte(elPuerto);
126126
+ if (respuesta === "lo-usa-otra-ia") {
126127
+ throw new ProblemaConLaObra(
126128
+ `El puerto ${elPuerto} lo est\xE1 usando otra IA conectada a Mandioca Tabs (otra sesi\xF3n o ventana). Que el m\xFAsico la desconecte desde ah\xED con \`desconectar_de_la_app\`, o la cierre, y vuelve a intentarlo.`
126129
+ );
126130
+ }
126131
+ if (respuesta !== "soltado") throw causa;
126132
+ abierto = await abrirPuente(elPuerto);
126133
+ }
126134
+ if (puerto !== void 0 && abierto.puerto !== puerto) {
126135
+ throw new ProblemaConLaObra(
126136
+ `El puente ya est\xE1 abierto en el puerto ${abierto.puerto}, no en el ${puerto}. Para cambiar de puerto, primero \`desconectar_de_la_app\`.`
126137
+ );
125278
126138
  }
125279
126139
  const p = await buscarLaPesta\u00F1a() ?? puenteAbierto();
125280
126140
  const conectadas = p.obras();
@@ -125288,7 +126148,7 @@ function registrarHerramientasDelPuente(servidor2) {
125288
126148
  pesta\u00F1asConectadas: p.pesta\u00F1as(),
125289
126149
  // ⚠️ Se dice lo que falta, sin adornarlo. Si no hay pestaña, el asistente tiene que
125290
126150
  // saberlo ANTES de prometerle algo al músico — no descubrirlo al primer fallo.
125291
- siguiente: conectadas.length > 1 ? `Hay ${conectadas.length} obras abiertas. Mira \`obras_conectadas\` y elige una con \`elegir_obra\`, por su letra, antes de escribir nada.` : p.pesta\u00F1as() > 0 ? "Ya hay una pesta\xF1a conectada: la obra que se lee y se escribe es la suya." : "Todav\xEDa no hay ninguna pesta\xF1a. P\xEDdele que abra Mandioca Tabs y active P\xEDdele que apriete \xABConectar IA\xBB, el bot\xF3n de la barra de estado (abajo del todo, dice \xABConectar IA\xBB)."
126151
+ siguiente: conectadas.length > 1 ? `Hay ${conectadas.length} obras abiertas. Mira \`obras_conectadas\` y elige una con \`elegir_obra\`, por su letra, antes de escribir nada.` : p.pesta\u00F1as() > 0 ? "Ya hay una pesta\xF1a conectada: la obra que se lee y se escribe es la suya." : "Todav\xEDa no hay ninguna pesta\xF1a. P\xEDdele que abra Mandioca Tabs y apriete \xABConectar IA\xBB, el bot\xF3n de la barra de estado (abajo del todo)."
125292
126152
  });
125293
126153
  }
125294
126154
  );
@@ -125300,7 +126160,7 @@ function registrarHerramientasDelPuente(servidor2) {
125300
126160
  inputSchema: {}
125301
126161
  },
125302
126162
  () => {
125303
- const p = puenteAbierto();
126163
+ const p = puenteAbierto(false);
125304
126164
  const conectadas = p.obras();
125305
126165
  const idActivo = idDeLaObraActiva();
125306
126166
  const idDeLaActiva = idActivo ?? (conectadas.length === 1 ? conectadas[0].id : null);
@@ -125347,7 +126207,7 @@ function registrarHerramientasDelPuente(servidor2) {
125347
126207
  inputSchema: {}
125348
126208
  },
125349
126209
  () => {
125350
- const p = puenteAbierto();
126210
+ const p = puenteAbierto(false);
125351
126211
  const pesta\u00F1as = p.pesta\u00F1as();
125352
126212
  if (pesta\u00F1as === 0) {
125353
126213
  return texto3({
@@ -125405,6 +126265,7 @@ function registrarHerramientasDelPuente(servidor2) {
125405
126265
  inputSchema: {}
125406
126266
  },
125407
126267
  async () => {
126268
+ soltarLaObraDeLaPesta\u00F1a();
125408
126269
  await cerrarPuente();
125409
126270
  return texto3({ desconectado: true });
125410
126271
  }
@@ -125413,7 +126274,7 @@ function registrarHerramientasDelPuente(servidor2) {
125413
126274
  "sonar",
125414
126275
  {
125415
126276
  title: "Reproducir",
125416
- description: "Le pide a la aplicaci\xF3n que reproduzca la obra desde donde est\xE1 el cursor. Necesita la pesta\xF1a conectada: el sonido s\xF3lo existe en el navegador. \u26A0\uFE0F Si el sonido de alg\xFAn instrumento todav\xEDa se est\xE1 cargando, NO suena y contesta con un error que nombra la pista que falta: esper\xE1 unos segundos y vuelve a pedirlo. Nunca suena a medias.",
126277
+ description: "Le pide a la aplicaci\xF3n que reproduzca la obra desde donde est\xE1 el cursor. Necesita la pesta\xF1a conectada: el sonido s\xF3lo existe en el navegador. \u26A0\uFE0F Si el sonido de alg\xFAn instrumento todav\xEDa se est\xE1 cargando, NO suena y contesta con un error que nombra la pista que falta: espera unos segundos y vuelve a pedirlo. Nunca suena a medias.",
125417
126278
  inputSchema: {}
125418
126279
  },
125419
126280
  async () => texto3(await pedirALaPesta\u00F1a("sonar"))
@@ -125616,7 +126477,7 @@ function registrarHerramientasDelPuente(servidor2) {
125616
126477
  "quitar_grabacion",
125617
126478
  {
125618
126479
  title: "Sacar la grabaci\xF3n de la sesi\xF3n",
125619
- description: "Deja de sonar la grabaci\xF3n de referencia. \u26A0\uFE0F **NO toca el documento**: los puntos de sincronizaci\xF3n y la onda guardada siguen donde estaban. Es \xABno quiero o\xEDr esto ahora\xBB, no \xABborr\xE1 mi trabajo\xBB.",
126480
+ description: "Deja de sonar la grabaci\xF3n de referencia. \u26A0\uFE0F **NO toca el documento**: los puntos de sincronizaci\xF3n y la onda guardada siguen donde estaban. Es \xABno quiero o\xEDr esto ahora\xBB, no \xABborra mi trabajo\xBB.",
125620
126481
  inputSchema: {}
125621
126482
  },
125622
126483
  async () => texto3(await pedirALaPesta\u00F1a("quitar_grabacion"))
@@ -126019,7 +126880,7 @@ function registrarHerramientasDelPuente(servidor2) {
126019
126880
  "sincronizar_letra",
126020
126881
  {
126021
126882
  title: "Alinear una letra contra la grabaci\xF3n",
126022
- description: "Pega cada s\xEDlaba de la letra al instante en que se canta, aline\xE1ndola contra la grabaci\xF3n **elegida**. Es F04. \u26A0\uFE0F\u26A0\uFE0F **TARDA MINUTOS** \u2014medido: unos 0,85 s por segundo de audio, o sea ~3 min para un tema de 3:30\u2014 y baja un modelo la primera vez. \u26A0\uFE0F Se alinea contra la grabaci\xF3n que est\xE9 ELEGIDA: conviene poner el stem de voz con `elegir_grabacion` antes, porque el modelo est\xE1 entrenado con habla y la mezcla completa lo confunde. \u26A0\uFE0F Esto NO es `poner_letra`: aquello escribe la s\xEDlaba en el pliego y no necesita audio; esto le pone milisegundos. Son dos letras distintas. \u26A0\uFE0F Tampoco es `guardar_letra`, que deja el texto puesto sin alinear nada y no tarda nada: si lo que se quiere es dejarle la letra lista al m\xFAsico, es aqu\xE9lla.",
126883
+ description: "Pega cada s\xEDlaba de la letra al instante en que se canta, aline\xE1ndola contra la grabaci\xF3n **elegida**. \u26A0\uFE0F\u26A0\uFE0F **TARDA MINUTOS** \u2014medido: unos 0,85 s por segundo de audio, o sea ~3 min para un tema de 3:30\u2014 y baja un modelo la primera vez. \u26A0\uFE0F Se alinea contra la grabaci\xF3n que est\xE9 ELEGIDA: conviene poner el stem de voz con `elegir_grabacion` antes, porque el modelo est\xE1 entrenado con habla y la mezcla completa lo confunde. \u26A0\uFE0F Esto NO es `poner_letra`: aquello escribe la s\xEDlaba en el pliego y no necesita audio; esto le pone milisegundos. Son dos letras distintas. \u26A0\uFE0F Tampoco es `guardar_letra`, que deja el texto puesto sin alinear nada y no tarda nada: si lo que se quiere es dejarle la letra lista al m\xFAsico, es aqu\xE9lla.",
126023
126884
  inputSchema: {
126024
126885
  texto: external_exports.string().min(1).optional().describe(
126025
126886
  "La letra completa. Se parte en s\xEDlabas sola; `silabificar` sirve para verla antes. \u26A0\uFE0F **Si se omite, se usa la letra ESTACIONADA en la obra** (`guardar_letra`), que es la que el m\xFAsico tiene escrita en su cuadro. Mandarla ac\xE1 igual sirve para alinear otra cosa, pero entonces el resultado no va a coincidir con lo que \xE9l est\xE1 viendo."
@@ -126031,7 +126892,20 @@ function registrarHerramientasDelPuente(servidor2) {
126031
126892
  hasta: external_exports.number().optional().describe("En qu\xE9 segundo de la grabaci\xF3n termina el tramo.")
126032
126893
  }
126033
126894
  },
126034
- async (args2) => texto3(await pedirALaPesta\u00F1a("sincronizar_letra", args2))
126895
+ async (args2) => {
126896
+ const { desde, hasta } = args2;
126897
+ if (desde === void 0 !== (hasta === void 0)) {
126898
+ throw new ProblemaConLaObra(
126899
+ "Un tramo va con `desde` y `hasta` juntos. Para la grabaci\xF3n entera, sin ninguno de los dos."
126900
+ );
126901
+ }
126902
+ if (desde !== void 0 && hasta !== void 0 && hasta <= desde) {
126903
+ throw new ProblemaConLaObra(
126904
+ `El tramo va al rev\xE9s o est\xE1 vac\xEDo: empieza en el segundo ${desde} y termina en el ${hasta}.`
126905
+ );
126906
+ }
126907
+ return texto3(await pedirALaPesta\u00F1a("sincronizar_letra", args2));
126908
+ }
126035
126909
  );
126036
126910
  servidor2.registerTool(
126037
126911
  "audios_rechazados_por_largos",
@@ -126054,14 +126928,18 @@ function registrarHerramientasDelPuente(servidor2) {
126054
126928
  async ({ ruta }) => {
126055
126929
  exigirPesta\u00F1a();
126056
126930
  const ofrecido = ofrecerArchivo(ruta);
126057
- return texto3(
126058
- await pedirALaPesta\u00F1a("cargar_tipografia", {
126059
- nombre: ofrecido.nombre,
126060
- ruta,
126061
- bytes: ofrecido.bytes,
126062
- desde: `http://127.0.0.1:${puenteAbierto().puerto}/archivo/${ofrecido.ficha}`
126063
- })
126064
- );
126931
+ try {
126932
+ return texto3(
126933
+ await pedirALaPesta\u00F1a("cargar_tipografia", {
126934
+ nombre: ofrecido.nombre,
126935
+ ruta,
126936
+ bytes: ofrecido.bytes,
126937
+ desde: `http://127.0.0.1:${puenteAbierto().puerto}/archivo/${ofrecido.ficha}`
126938
+ })
126939
+ );
126940
+ } finally {
126941
+ retirarArchivo(ofrecido.ficha);
126942
+ }
126065
126943
  }
126066
126944
  );
126067
126945
  servidor2.registerTool(
@@ -126132,16 +127010,20 @@ function registrarHerramientasDelPuente(servidor2) {
126132
127010
  async ({ ruta }) => {
126133
127011
  exigirPesta\u00F1a();
126134
127012
  const ofrecido = ofrecerArchivo(ruta);
126135
- return texto3(
126136
- await pedirALaPesta\u00F1a("abrir_grabacion", {
126137
- nombre: ofrecido.nombre,
126138
- ruta,
126139
- bytes: ofrecido.bytes,
126140
- // ★ La pestaña recibe una DIRECCIÓN, no los bytes. Los bytes viajan por HTTP, en
126141
- // trozos, y sólo si el músico acepta.
126142
- desde: `http://127.0.0.1:${puenteAbierto().puerto}/archivo/${ofrecido.ficha}`
126143
- })
126144
- );
127013
+ try {
127014
+ return texto3(
127015
+ await pedirALaPesta\u00F1a("abrir_grabacion", {
127016
+ nombre: ofrecido.nombre,
127017
+ ruta,
127018
+ bytes: ofrecido.bytes,
127019
+ // ★ La pestaña recibe una DIRECCIÓN, no los bytes. Los bytes viajan por HTTP, en
127020
+ // trozos, y sólo si el músico acepta.
127021
+ desde: `http://127.0.0.1:${puenteAbierto().puerto}/archivo/${ofrecido.ficha}`
127022
+ })
127023
+ );
127024
+ } finally {
127025
+ retirarArchivo(ofrecido.ficha);
127026
+ }
126145
127027
  }
126146
127028
  );
126147
127029
  servidor2.registerTool(
@@ -126156,13 +127038,17 @@ function registrarHerramientasDelPuente(servidor2) {
126156
127038
  async ({ ruta }) => {
126157
127039
  exigirPesta\u00F1a();
126158
127040
  const ofrecido = ofrecerArchivo(ruta);
126159
- return texto3(
126160
- await pedirALaPesta\u00F1a("abrir_en_la_app", {
126161
- nombre: ofrecido.nombre,
126162
- ruta,
126163
- desde: `http://127.0.0.1:${puenteAbierto().puerto}/archivo/${ofrecido.ficha}`
126164
- })
126165
- );
127041
+ try {
127042
+ return texto3(
127043
+ await pedirALaPesta\u00F1a("abrir_en_la_app", {
127044
+ nombre: ofrecido.nombre,
127045
+ ruta,
127046
+ desde: `http://127.0.0.1:${puenteAbierto().puerto}/archivo/${ofrecido.ficha}`
127047
+ })
127048
+ );
127049
+ } finally {
127050
+ retirarArchivo(ofrecido.ficha);
127051
+ }
126166
127052
  }
126167
127053
  );
126168
127054
  }
@@ -126217,10 +127103,11 @@ function registrarHerramientasDeSignos(servidor2) {
126217
127103
  tics: external_exports.number().int().min(0).max(99999).optional(),
126218
127104
  pista: external_exports.number().int().min(1).max(999).optional(),
126219
127105
  voz: external_exports.number().int().min(1).max(4).optional(),
127106
+ pentagrama: campoDePentagrama,
126220
127107
  valor: external_exports.number().optional().describe("Para los signos que tienen grados (digitaci\xF3n, tipo de arm\xF3nico\u2026). Sin esto, alterna.")
126221
127108
  }
126222
127109
  },
126223
- ({ clave, compas, cuerda, golpe, tics, pista, voz, valor }) => {
127110
+ ({ clave, compas, cuerda, golpe, tics, pista, voz, pentagrama, valor }) => {
126224
127111
  const elegido = signo(clave);
126225
127112
  if (!elegido) {
126226
127113
  throw new ProblemaConLaObra(
@@ -126232,9 +127119,9 @@ function registrarHerramientasDeSignos(servidor2) {
126232
127119
  `\xAB${elegido.nombre}\xBB es un signo de NOTA: vive en una cuerda concreta, as\xED que hay que decir cu\xE1l. Los signos del golpe entero \u2014acentos, palm mute\u2014 no la necesitan.`
126233
127120
  );
126234
127121
  }
126235
- const sitio = situarse(pista, compas, voz);
126236
- const tiempo = ticsDelGolpe(sitio.indice, sitio.pistaId, sitio.compas, sitio.voz, golpe, tics);
126237
- const cursor = cursorEn(sitio.indice, sitio.pistaId, sitio.compas, sitio.voz, tiempo, cuerda ?? 1);
127122
+ const sitio = situarse(pista, compas, voz, pentagrama);
127123
+ const tiempo = ticsDelGolpe(sitio.indice, sitio.pistaId, sitio.compas, sitio.voz, golpe, tics, sitio.pentagrama);
127124
+ const cursor = cursorEn(sitio.indice, sitio.pistaId, sitio.compas, sitio.voz, tiempo, cuerda ?? 1, sitio.pentagrama);
126238
127125
  const resultado = aplicarSigno(sitio.documento, sitio.indice, cursor, clave, valor);
126239
127126
  if (!resultado.hecho) {
126240
127127
  throw new ProblemaConLaObra(
@@ -126264,18 +127151,19 @@ function registrarHerramientasDeSignos(servidor2) {
126264
127151
  tics: external_exports.number().int().min(0).max(99999).optional(),
126265
127152
  pista: external_exports.number().int().min(1).max(999).optional(),
126266
127153
  voz: external_exports.number().int().min(1).max(4).optional(),
127154
+ pentagrama: campoDePentagrama,
126267
127155
  color: external_exports.string().regex(/^#[0-9a-fA-F]{6}$/, { error: (i2) => `\xAB${String(i2.input)}\xBB no es un color: va como #rrggbb, por ejemplo #c62026.` }).optional().describe("El color como #rrggbb. Obligatorio salvo para quitar."),
126268
127156
  como: external_exports.enum(["pintar", "subrayar"]).optional().describe("Pintar la nota (por omisi\xF3n) o subrayarla."),
126269
127157
  quitar: external_exports.boolean().optional().describe("true = quitarle la marca a la nota.")
126270
127158
  }
126271
127159
  },
126272
- ({ compas, cuerda, golpe, tics, pista, voz, color, como, quitar }) => {
127160
+ ({ compas, cuerda, golpe, tics, pista, voz, pentagrama, color, como, quitar }) => {
126273
127161
  if (!quitar && !color) {
126274
127162
  throw new ProblemaConLaObra("Falta el color (#rrggbb). Para sacar la marca, `quitar: true`.");
126275
127163
  }
126276
- const sitio = situarse(pista, compas, voz);
126277
- const tiempo = ticsDelGolpe(sitio.indice, sitio.pistaId, sitio.compas, sitio.voz, golpe, tics);
126278
- const cursor = cursorEn(sitio.indice, sitio.pistaId, sitio.compas, sitio.voz, tiempo, cuerda);
127164
+ const sitio = situarse(pista, compas, voz, pentagrama);
127165
+ const tiempo = ticsDelGolpe(sitio.indice, sitio.pistaId, sitio.compas, sitio.voz, golpe, tics, sitio.pentagrama);
127166
+ const cursor = cursorEn(sitio.indice, sitio.pistaId, sitio.compas, sitio.voz, tiempo, cuerda, sitio.pentagrama);
126279
127167
  const marca = quitar ? null : { color, subrayada: como === "subrayar" };
126280
127168
  const tocadas = ponerMarcaDeColor(sitio.documento, sitio.indice, cursor, marca);
126281
127169
  if (tocadas === 0) {
@@ -126283,7 +127171,7 @@ function registrarHerramientasDeSignos(servidor2) {
126283
127171
  `No hay nota en el comp\xE1s ${compas}, cuerda ${cuerda}. La marca va sobre una nota escrita: si ah\xED no hay nada, primero hay que escribirla.`
126284
127172
  );
126285
127173
  }
126286
- const leida = marcaDeColorDeLasNotas(situarse(pista, compas, voz).indice, cursor);
127174
+ const leida = marcaDeColorDeLasNotas(situarse(pista, compas, voz, pentagrama).indice, cursor);
126287
127175
  return texto2({
126288
127176
  compas,
126289
127177
  cuerda,
@@ -126303,13 +127191,14 @@ function registrarHerramientasDeSignos(servidor2) {
126303
127191
  golpe: external_exports.number().int().min(1).max(999).optional(),
126304
127192
  tics: external_exports.number().int().min(0).max(99999).optional(),
126305
127193
  pista: external_exports.number().int().min(1).max(999).optional(),
126306
- voz: external_exports.number().int().min(1).max(4).optional()
127194
+ voz: external_exports.number().int().min(1).max(4).optional(),
127195
+ pentagrama: campoDePentagrama
126307
127196
  }
126308
127197
  },
126309
- ({ compas, cuerda, golpe, tics, pista, voz }) => {
126310
- const sitio = situarse(pista, compas, voz);
126311
- const tiempo = ticsDelGolpe(sitio.indice, sitio.pistaId, sitio.compas, sitio.voz, golpe, tics);
126312
- const cursor = cursorEn(sitio.indice, sitio.pistaId, sitio.compas, sitio.voz, tiempo, cuerda);
127198
+ ({ compas, cuerda, golpe, tics, pista, voz, pentagrama }) => {
127199
+ const sitio = situarse(pista, compas, voz, pentagrama);
127200
+ const tiempo = ticsDelGolpe(sitio.indice, sitio.pistaId, sitio.compas, sitio.voz, golpe, tics, sitio.pentagrama);
127201
+ const cursor = cursorEn(sitio.indice, sitio.pistaId, sitio.compas, sitio.voz, tiempo, cuerda, sitio.pentagrama);
126313
127202
  const puestos = signosPuestos(sitio.indice, cursor);
126314
127203
  const marca = marcaDeColorDeLasNotas(sitio.indice, cursor).comun;
126315
127204
  return texto2({
@@ -126336,12 +127225,13 @@ var DONDE = {
126336
127225
  golpe: external_exports.number().int().min(1).max(999).optional(),
126337
127226
  tics: external_exports.number().int().min(0).max(99999).optional(),
126338
127227
  pista: external_exports.number().int().min(1).max(999).optional(),
126339
- voz: external_exports.number().int().min(1).max(4).optional()
127228
+ voz: external_exports.number().int().min(1).max(4).optional(),
127229
+ pentagrama: campoDePentagrama
126340
127230
  };
126341
127231
  function ubicar(donde) {
126342
- const sitio = situarse(donde.pista, donde.compas, donde.voz);
126343
- const tiempo = ticsDelGolpe(sitio.indice, sitio.pistaId, sitio.compas, sitio.voz, donde.golpe, donde.tics);
126344
- const cursor = cursorEn(sitio.indice, sitio.pistaId, sitio.compas, sitio.voz, tiempo, 1);
127232
+ const sitio = situarse(donde.pista, donde.compas, donde.voz, donde.pentagrama);
127233
+ const tiempo = ticsDelGolpe(sitio.indice, sitio.pistaId, sitio.compas, sitio.voz, donde.golpe, donde.tics, sitio.pentagrama);
127234
+ const cursor = cursorEn(sitio.indice, sitio.pistaId, sitio.compas, sitio.voz, tiempo, 1, sitio.pentagrama);
126345
127235
  let seleccion;
126346
127236
  if (donde.hasta !== void 0) {
126347
127237
  if (donde.hasta < donde.compas) {
@@ -126350,12 +127240,12 @@ function ubicar(donde) {
126350
127240
  );
126351
127241
  }
126352
127242
  const ultimo = indiceDeCompas(donde.hasta, sitio.indice);
126353
- const compasId = sitio.indice.compasId(sitio.pistaId, ultimo);
127243
+ const compasId = sitio.indice.compasId(sitio.pistaId, ultimo, sitio.pentagrama);
126354
127244
  const golpes = sitio.indice.figuras(compasId, sitio.voz);
126355
127245
  const finDelUltimo = golpes.length > 0 ? golpes[golpes.length - 1].datos.tiempo : 0;
126356
127246
  seleccion = extenderHasta(
126357
127247
  seleccionDesde(cursor),
126358
- cursorEn(sitio.indice, sitio.pistaId, ultimo, sitio.voz, finDelUltimo, 1)
127248
+ cursorEn(sitio.indice, sitio.pistaId, ultimo, sitio.voz, finDelUltimo, 1, sitio.pentagrama)
126359
127249
  );
126360
127250
  }
126361
127251
  return { ...sitio, cursor, seleccion };
@@ -126966,7 +127856,11 @@ function registrarHerramientasDeEstructura(servidor2) {
126966
127856
  "cambiar_metrica_del_compas",
126967
127857
  {
126968
127858
  title: "Cambiar la m\xE9trica de un comp\xE1s",
126969
- description: "Le cambia el comp\xE1s de amalgama a un comp\xE1s: 6/8, 3/4, 7/8\u2026 Vale de ah\xED en adelante hasta que otro comp\xE1s la vuelva a cambiar. \u26A0\uFE0F El denominador dice qu\xE9 figura lleva el pulso, as\xED que tiene que ser potencia de dos: no existe un comp\xE1s de x/5.",
127859
+ description: (
127860
+ // ⛔ (E14, 2026-11-11) Decía «vale de ahí en adelante», y `cambiarMetricaDelCompas`
127861
+ // cambia UN compás, como está decidido. El texto dice lo que hace.
127862
+ "Le cambia la m\xE9trica a UN comp\xE1s: 6/8, 3/4, 7/8\u2026 S\xF3lo a \xE9se: los que siguen quedan como estaban. Para cambiar varios, se llama una vez por comp\xE1s. \u26A0\uFE0F El denominador dice qu\xE9 figura lleva el pulso, as\xED que tiene que ser potencia de dos: no existe un comp\xE1s de x/5."
127863
+ ),
126970
127864
  inputSchema: {
126971
127865
  compas: external_exports.number().int().min(1).max(9999),
126972
127866
  numerador: external_exports.number().int().min(1).max(32).describe("Cu\xE1ntos pulsos entran: el 6 de 6/8."),
@@ -127267,9 +128161,9 @@ init_src3();
127267
128161
  init_obra();
127268
128162
  var portapapeles = null;
127269
128163
  function ubicar2(donde) {
127270
- const sitio = situarse(donde.pista, donde.compas, donde.voz);
127271
- const tiempo = ticsDelGolpe(sitio.indice, sitio.pistaId, sitio.compas, sitio.voz, donde.golpe, donde.tics);
127272
- const cursor = cursorEn(sitio.indice, sitio.pistaId, sitio.compas, sitio.voz, tiempo, donde.cuerda ?? 1);
128164
+ const sitio = situarse(donde.pista, donde.compas, donde.voz, donde.pentagrama);
128165
+ const tiempo = ticsDelGolpe(sitio.indice, sitio.pistaId, sitio.compas, sitio.voz, donde.golpe, donde.tics, sitio.pentagrama);
128166
+ const cursor = cursorEn(sitio.indice, sitio.pistaId, sitio.compas, sitio.voz, tiempo, donde.cuerda ?? 1, sitio.pentagrama);
127273
128167
  if (donde.hasta === void 0) return { ...sitio, cursor, seleccion: void 0 };
127274
128168
  if (donde.hasta < donde.compas) {
127275
128169
  throw new ProblemaConLaObra(
@@ -127277,7 +128171,7 @@ function ubicar2(donde) {
127277
128171
  );
127278
128172
  }
127279
128173
  const ultimo = indiceDeCompas(donde.hasta, sitio.indice);
127280
- const compasId = sitio.indice.compasId(sitio.pistaId, ultimo);
128174
+ const compasId = sitio.indice.compasId(sitio.pistaId, ultimo, sitio.pentagrama);
127281
128175
  const golpes = sitio.indice.figuras(compasId, sitio.voz);
127282
128176
  const fin = golpes.length > 0 ? golpes[golpes.length - 1].datos.tiempo : 0;
127283
128177
  return {
@@ -127285,7 +128179,7 @@ function ubicar2(donde) {
127285
128179
  cursor,
127286
128180
  seleccion: extenderHasta(
127287
128181
  seleccionDesde(cursor),
127288
- cursorEn(sitio.indice, sitio.pistaId, ultimo, sitio.voz, fin, donde.cuerda ?? 1)
128182
+ cursorEn(sitio.indice, sitio.pistaId, ultimo, sitio.voz, fin, donde.cuerda ?? 1, sitio.pentagrama)
127289
128183
  )
127290
128184
  };
127291
128185
  }
@@ -127295,7 +128189,8 @@ var RANGO = {
127295
128189
  golpe: external_exports.number().int().min(1).max(999).optional(),
127296
128190
  tics: external_exports.number().int().min(0).max(99999).optional(),
127297
128191
  pista: external_exports.number().int().min(1).max(999).optional(),
127298
- voz: external_exports.number().int().min(1).max(4).optional()
128192
+ voz: external_exports.number().int().min(1).max(4).optional(),
128193
+ pentagrama: campoDePentagrama
127299
128194
  };
127300
128195
  function registrarHerramientasDeMusica(servidor2) {
127301
128196
  servidor2.registerTool(
@@ -127303,7 +128198,7 @@ function registrarHerramientasDeMusica(servidor2) {
127303
128198
  {
127304
128199
  title: "Qu\xE9 acorde forman estas notas",
127305
128200
  description: "Deduce el cifrado de las notas que suenan juntas en una posici\xF3n, con Tonal.js. Sirve para saber qu\xE9 se escribi\xF3 sin tener que leerlo nota por nota.",
127306
- inputSchema: { compas: external_exports.number().int().min(1).max(9999), golpe: external_exports.number().int().min(1).max(999).optional(), tics: external_exports.number().int().min(0).max(99999).optional(), pista: external_exports.number().int().min(1).max(999).optional(), voz: external_exports.number().int().min(1).max(4).optional() }
128201
+ inputSchema: { compas: external_exports.number().int().min(1).max(9999), golpe: external_exports.number().int().min(1).max(999).optional(), tics: external_exports.number().int().min(0).max(99999).optional(), pista: external_exports.number().int().min(1).max(999).optional(), voz: external_exports.number().int().min(1).max(4).optional(), pentagrama: campoDePentagrama }
127307
128202
  },
127308
128203
  (donde) => {
127309
128204
  const sitio = ubicar2(donde);
@@ -127334,6 +128229,7 @@ function registrarHerramientasDeMusica(servidor2) {
127334
128229
  tics: external_exports.number().int().min(0).max(99999).optional(),
127335
128230
  pista: external_exports.number().int().min(1).max(999).optional(),
127336
128231
  voz: external_exports.number().int().min(1).max(4).optional(),
128232
+ pentagrama: campoDePentagrama,
127337
128233
  forma: external_exports.string().optional().describe("La letra de la digitaci\xF3n \u2014\xABA\xBB, \xABB\xBB, \xABC\xBB\u2026\u2014 tal como la informa `acordes_de_la_obra`. Si ese acorde se toca de una sola manera, su letra es \xABA\xBB."),
127338
128234
  segundaTipografia: campoDeSegundaTipografia,
127339
128235
  sinNombre: external_exports.boolean().optional().describe("true escribe s\xF3lo las notas de `forma`, sin el cifrado arriba: el Ctrl + clic de la paleta. Pide `forma` y un golpe sin notas.")
@@ -127416,7 +128312,8 @@ function registrarHerramientasDeMusica(servidor2) {
127416
128312
  golpe: external_exports.number().int().min(1).max(999).optional(),
127417
128313
  tics: external_exports.number().int().min(0).max(99999).optional(),
127418
128314
  pista: external_exports.number().int().min(1).max(999).optional(),
127419
- voz: external_exports.number().int().min(1).max(4).optional()
128315
+ voz: external_exports.number().int().min(1).max(4).optional(),
128316
+ pentagrama: campoDePentagrama
127420
128317
  }
127421
128318
  },
127422
128319
  (donde) => {
@@ -127457,7 +128354,13 @@ function registrarHerramientasDeMusica(servidor2) {
127457
128354
  "deshacer",
127458
128355
  {
127459
128356
  title: "Deshacer",
127460
- description: "Vuelve atr\xE1s un paso. Es el MISMO historial que el Ctrl+Z del m\xFAsico: si \xE9l escribi\xF3 lo \xFAltimo, esto deshace lo suyo.",
128357
+ description: (
128358
+ // ⛔ (E3, 2026-11-11) Decía que compartía el Ctrl+Z del músico, y no: el historial
128359
+ // sólo graba los cambios hechos desde este conector (`historial()` en
128360
+ // `documento.ts` acota por origen). Lo que el músico escribe llega por el puente
128361
+ // con otro origen y queda afuera.
128362
+ "Vuelve atr\xE1s un paso de lo que hizo la IA: cada herramienta que escribe es un paso. No deshace lo que escribi\xF3 el m\xFAsico; eso lo deshace \xE9l con su Ctrl+Z."
128363
+ ),
127461
128364
  inputSchema: { pasos: external_exports.number().int().min(1).max(50, { error: (i2) => i2.input === void 0 ? "Falta este dato." : `\xAB${i2.input}\xBB son demasiados pasos de una vez: van de 1 a 50.` }).optional() }
127462
128365
  },
127463
128366
  ({ pasos }) => {
@@ -127626,6 +128529,7 @@ function registrarHerramientasDeMusica(servidor2) {
127626
128529
  golpe: external_exports.number().int().min(1).max(999).optional(),
127627
128530
  pista: external_exports.number().int().min(1).max(999).optional(),
127628
128531
  voz: external_exports.number().int().min(1).max(4).optional(),
128532
+ pentagrama: campoDePentagrama,
127629
128533
  figura: external_exports.number().int().min(1).max(64, { error: (i2) => i2.input === void 0 ? "Falta este dato." : `\xAB${i2.input}\xBB no es una figura: 1 redonda, 2 blanca, 4 negra, 8 corchea, 16, 32, 64.` }).optional().describe("1 redonda, 2 blanca, 4 negra\u2026 Por omisi\xF3n, negra."),
127630
128534
  puntillos: external_exports.number().int().min(0).max(2).optional()
127631
128535
  }
@@ -127653,6 +128557,7 @@ function registrarHerramientasDeMusica(servidor2) {
127653
128557
  golpe: external_exports.number().int().min(1).max(999).optional(),
127654
128558
  pista: external_exports.number().int().min(1).max(999).optional(),
127655
128559
  voz: external_exports.number().int().min(1).max(4).optional(),
128560
+ pentagrama: campoDePentagrama,
127656
128561
  sinAcorde: external_exports.boolean().optional()
127657
128562
  }
127658
128563
  },
@@ -127944,7 +128849,8 @@ function registrarHerramientasDeMusica(servidor2) {
127944
128849
  golpe: external_exports.number().int().min(1).max(999).optional(),
127945
128850
  tics: external_exports.number().int().min(0).max(99999).optional(),
127946
128851
  pista: external_exports.number().int().min(1).max(999).optional(),
127947
- voz: external_exports.number().int().min(1).max(4).optional()
128852
+ voz: external_exports.number().int().min(1).max(4).optional(),
128853
+ pentagrama: campoDePentagrama
127948
128854
  }
127949
128855
  },
127950
128856
  ({ campo, variante, cuerda, ...donde }) => {
@@ -127996,7 +128902,8 @@ function registrarHerramientasDeMusica(servidor2) {
127996
128902
  golpe: external_exports.number().int().min(1).max(999).optional(),
127997
128903
  tics: external_exports.number().int().min(0).max(99999).optional(),
127998
128904
  pista: external_exports.number().int().min(1).max(999).optional(),
127999
- voz: external_exports.number().int().min(1).max(4).optional()
128905
+ voz: external_exports.number().int().min(1).max(4).optional(),
128906
+ pentagrama: campoDePentagrama
128000
128907
  }
128001
128908
  },
128002
128909
  ({ matiz, ...donde }) => {
@@ -128033,12 +128940,13 @@ var DONDE2 = {
128033
128940
  tics: external_exports.number().int().min(0).max(99999).optional(),
128034
128941
  pista: external_exports.number().int().min(1).max(999).optional(),
128035
128942
  voz: external_exports.number().int().min(1).max(4).optional(),
128036
- cuerda: external_exports.number().int().min(1).optional().describe("La cuerda donde est\xE1 la nota, desde la M\xC1S GRAVE empezando en 1.")
128943
+ cuerda: external_exports.number().int().min(1).optional().describe("La cuerda donde est\xE1 la nota, desde la M\xC1S GRAVE empezando en 1."),
128944
+ pentagrama: campoDePentagrama
128037
128945
  };
128038
128946
  function ubicarNota(donde) {
128039
- const sitio = situarse(donde.pista, donde.compas, donde.voz);
128040
- const tiempo = ticsDelGolpe(sitio.indice, sitio.pistaId, sitio.compas, sitio.voz, donde.golpe, donde.tics);
128041
- const cursor = cursorEn(sitio.indice, sitio.pistaId, sitio.compas, sitio.voz, tiempo, donde.cuerda ?? 1);
128947
+ const sitio = situarse(donde.pista, donde.compas, donde.voz, donde.pentagrama);
128948
+ const tiempo = ticsDelGolpe(sitio.indice, sitio.pistaId, sitio.compas, sitio.voz, donde.golpe, donde.tics, sitio.pentagrama);
128949
+ const cursor = cursorEn(sitio.indice, sitio.pistaId, sitio.compas, sitio.voz, tiempo, donde.cuerda ?? 1, sitio.pentagrama);
128042
128950
  return { ...sitio, cursor, seleccion: void 0 };
128043
128951
  }
128044
128952
  function registrarHerramientasDeBend(servidor2) {
@@ -128247,7 +129155,9 @@ function registrarHerramientasDePistas(servidor2) {
128247
129155
  afinacion: external_exports.array(external_exports.number().int().min(0).max(127)).optional().describe(
128248
129156
  "Las notas al aire en n\xFAmeros MIDI, de la M\xC1S GRAVE a la m\xE1s aguda. Tiene que traer tantas como cuerdas tenga la pista. Ver `afinaciones_disponibles`."
128249
129157
  ),
128250
- capo: external_exports.number().int().min(0).optional().describe("Traste de la cejilla. 0 = sin cejilla."),
129158
+ // ⛔ (E8, 2026-11-11) Sin tope, `capo: 500` se aceptaba y la cuerda quedaba en MIDI 543.
129159
+ // El tope es el mismo traste máximo del editor (`MAX_TRASTE`).
129160
+ capo: external_exports.number().int().min(0).max(MAX_TRASTE, { error: (i2) => `\xAB${String(i2.input)}\xBB no es un traste para la cejilla: van de 0 a ${MAX_TRASTE}.` }).optional().describe(`Traste de la cejilla, de 0 a ${MAX_TRASTE}. 0 = sin cejilla.`),
128251
129161
  programaMidi: external_exports.number().int().min(0).max(127).optional().describe("El sonido, en General MIDI."),
128252
129162
  nombreSegundaTipografia: campoDeSegundaTipografia,
128253
129163
  nombreCortoSegundaTipografia: campoDeSegundaTipografia
@@ -128304,7 +129214,7 @@ function registrarHerramientasDePistas(servidor2) {
128304
129214
  "poner_letra",
128305
129215
  {
128306
129216
  title: "Escribir la letra sobre los golpes",
128307
- description: "Escribe una s\xEDlaba por golpe a partir de un comp\xE1s, que es como se escribe una letra en una tablatura. \u26A0\uFE0F **No crea golpes**: escribe sobre los que ya hay y devuelve cu\xE1ntas s\xEDlabas sobraron. Crear golpes para alojar palabras inventar\xEDa ritmo que nadie toc\xF3. \u26A0\uFE0F Una s\xEDlaba vac\xEDa en la lista SALTA ese golpe, que es c\xF3mo se dice \xABac\xE1 no va letra\xBB sin desalinear lo que sigue. \u26A0\uFE0F Esto NO es la letra sincronizada con el audio (F04): es el texto que se dibuja en el pliego, y no necesita ninguna grabaci\xF3n. Para partir un texto, `silabificar`.",
129217
+ description: "Escribe una s\xEDlaba por golpe a partir de un comp\xE1s, que es como se escribe una letra en una tablatura. \u26A0\uFE0F **No crea golpes**: escribe sobre los que ya hay y devuelve cu\xE1ntas s\xEDlabas sobraron. Crear golpes para alojar palabras inventar\xEDa ritmo que nadie toc\xF3. \u26A0\uFE0F Una s\xEDlaba vac\xEDa en la lista SALTA ese golpe, que es c\xF3mo se dice \xABac\xE1 no va letra\xBB sin desalinear lo que sigue. \u26A0\uFE0F Esto NO es la letra sincronizada con el audio: es el texto que se dibuja en el pliego, y no necesita ninguna grabaci\xF3n. Para partir un texto, `silabificar`.",
128308
129218
  inputSchema: {
128309
129219
  silabas: external_exports.array(external_exports.string()).describe("Las s\xEDlabas, en orden. Una vac\xEDa salta un golpe."),
128310
129220
  compas: external_exports.number().int().min(1).max(9999).default(1).describe("Desde qu\xE9 comp\xE1s empezar."),
@@ -128350,21 +129260,23 @@ function registrarHerramientasDePistas(servidor2) {
128350
129260
  tics: external_exports.number().int().min(0).max(99999).optional(),
128351
129261
  pista: external_exports.number().int().min(1).max(999).optional(),
128352
129262
  voz: external_exports.number().int().min(1).max(4).optional(),
129263
+ pentagrama: campoDePentagrama,
128353
129264
  segundaTipografia: campoDeSegundaTipografia
128354
129265
  }
128355
129266
  },
128356
129267
  ({ silaba, segundaTipografia, ...donde }) => {
128357
129268
  empezarUnPaso();
128358
- const sitio = situarse(donde.pista, donde.compas, donde.voz);
129269
+ const sitio = situarse(donde.pista, donde.compas, donde.voz, donde.pentagrama);
128359
129270
  const tiempo = ticsDelGolpe(
128360
129271
  sitio.indice,
128361
129272
  sitio.pistaId,
128362
129273
  sitio.compas,
128363
129274
  sitio.voz,
128364
129275
  donde.golpe,
128365
- donde.tics
129276
+ donde.tics,
129277
+ sitio.pentagrama
128366
129278
  );
128367
- const cursor = cursorEn(sitio.indice, sitio.pistaId, sitio.compas, sitio.voz, tiempo, 1);
129279
+ const cursor = cursorEn(sitio.indice, sitio.pistaId, sitio.compas, sitio.voz, tiempo, 1, sitio.pentagrama);
128368
129280
  const marcas = silaba ? marcasPedidas(silaba, segundaTipografia) : void 0;
128369
129281
  if (!ponerSilabaEnElGolpe(sitio.documento, sitio.indice, cursor, silaba, marcas)) {
128370
129282
  throw new ProblemaConLaObra(
@@ -128380,7 +129292,7 @@ function contarNotasDe(documento, numero2) {
128380
129292
  }
128381
129293
 
128382
129294
  // apps/mcp/src/herramientas/archivo.ts
128383
- import { writeFile as writeFile2, access } from "node:fs/promises";
129295
+ import { writeFile as writeFile2, access, rename as rename2, rm } from "node:fs/promises";
128384
129296
  import { extname as extname3 } from "node:path";
128385
129297
  init_obra();
128386
129298
  function registrarHerramientasDeArchivo(servidor2) {
@@ -128400,7 +129312,7 @@ function registrarHerramientasDeArchivo(servidor2) {
128400
129312
  "exportar",
128401
129313
  {
128402
129314
  title: "Exportar la obra",
128403
- description: "Escribe la obra en otro formato. La ruta va completa y con su extensi\xF3n. \u26A0\uFE0F No pisa un archivo que ya exista salvo que se pida expl\xEDcitamente.",
129315
+ description: "Escribe la obra en otro formato. La ruta va completa y con la extensi\xF3n del formato. No escribe un .mtabs: eso es `guardar_obra`. \u26A0\uFE0F No pisa un archivo que ya exista salvo que se pida expl\xEDcitamente.",
128404
129316
  inputSchema: {
128405
129317
  ruta: external_exports.string().describe("Ruta completa del archivo a escribir, con extensi\xF3n."),
128406
129318
  formato: external_exports.string().optional().describe("gp \xB7 musicxml \xB7 mxl \xB7 mid \xB7 alphatex. Por omisi\xF3n, se deduce de la extensi\xF3n."),
@@ -128420,6 +129332,17 @@ function registrarHerramientasDeArchivo(servidor2) {
128420
129332
  `No s\xE9 exportar a \xAB${formato ?? (extname3(ruta) || "(sin extensi\xF3n)")}\xBB. Los formatos son: ${claves.join(", ")}. Mira \`formatos_de_exportacion\`.`
128421
129333
  );
128422
129334
  }
129335
+ if (extension2 === "mtabs") {
129336
+ throw new ProblemaConLaObra(
129337
+ `\xAB${ruta}\xBB es un .mtabs: exportar no escribe la obra. Para guardarla est\xE1 \`guardar_obra\`.`
129338
+ );
129339
+ }
129340
+ const detalleElegido = FORMATOS_DE_EXPORTACION2.find((f) => f.clave === elegido);
129341
+ if (extension2 !== detalleElegido.extension) {
129342
+ throw new ProblemaConLaObra(
129343
+ `El formato ${elegido} se escribe con la extensi\xF3n .${detalleElegido.extension}, y \xAB${ruta}\xBB tiene ${extension2 ? `.${extension2}` : "ninguna"}. Cambia la ruta o el formato.`
129344
+ );
129345
+ }
128423
129346
  if (!pisar) {
128424
129347
  const yaEsta = await access(ruta).then(
128425
129348
  () => true,
@@ -128432,7 +129355,15 @@ function registrarHerramientasDeArchivo(servidor2) {
128432
129355
  }
128433
129356
  }
128434
129357
  const bytes = exportarComo2(proyectar2(documento), elegido);
128435
- await writeFile2(ruta, bytes);
129358
+ const parcial = `${ruta}.parcial`;
129359
+ try {
129360
+ await writeFile2(parcial, bytes);
129361
+ await rename2(parcial, ruta);
129362
+ } catch (e) {
129363
+ await rm(parcial, { force: true }).catch(() => {
129364
+ });
129365
+ throw new ProblemaConLaObra(`No se pudo escribir \xAB${ruta}\xBB: ${e.message}`);
129366
+ }
128436
129367
  const detalle = FORMATOS_DE_EXPORTACION2.find((f) => f.clave === elegido);
128437
129368
  return texto2({
128438
129369
  escrito: ruta,
@@ -128446,6 +129377,94 @@ function registrarHerramientasDeArchivo(servidor2) {
128446
129377
  );
128447
129378
  }
128448
129379
 
129380
+ // apps/mcp/src/herramientas/paletas.ts
129381
+ init_src2();
129382
+ init_src3();
129383
+ init_obra();
129384
+ var FAMILIAS = ["figura", "tecnica", "signo", "acorde", "kit", "matiz", "seccion"];
129385
+ function herramientaDeLaFicha(familia, ficha) {
129386
+ const id2 = ficha.id;
129387
+ switch (familia) {
129388
+ case "figura":
129389
+ if (id2 === "puntillo") return { herramienta: "alternar_puntillo", argumentos: {} };
129390
+ if (id2 === "silencio") return { herramienta: "escribir_silencio", argumentos: {} };
129391
+ return { herramienta: "cambiar_duracion", argumentos: { figura: Number(id2.slice("figura-".length)) } };
129392
+ case "tecnica":
129393
+ if (id2.startsWith("efecto-")) return { herramienta: "poner_efecto", argumentos: { clave: id2.slice("efecto-".length) } };
129394
+ if (id2.startsWith("bend-")) return { herramienta: "poner_bend", argumentos: { forma: id2.slice("bend-".length) } };
129395
+ if (id2.startsWith("sube-")) return { herramienta: "poner_bend", argumentos: { cuartos: Number(id2.slice("sube-".length)) } };
129396
+ return { herramienta: "escribir_curva_de_bend", argumentos: {} };
129397
+ case "signo":
129398
+ return { herramienta: "poner_signo", argumentos: { clave: id2.slice("signo-".length) } };
129399
+ case "matiz":
129400
+ return { herramienta: "poner_dinamica", argumentos: { matiz: id2.slice("matiz-".length) } };
129401
+ case "kit": {
129402
+ const pieza = KIT[Number(id2.slice("kit-".length))];
129403
+ return pieza ? { herramienta: "escribir_notas", argumentos: { notas: [{ pieza: pieza.nombre }] } } : null;
129404
+ }
129405
+ case "acorde":
129406
+ if (id2 === MIRA_DEDUCIR) return { herramienta: "acorde_en_el_cursor", argumentos: {} };
129407
+ {
129408
+ const [nombre, letra] = ficha.nombre.split(" ");
129409
+ return { herramienta: "poner_acorde", argumentos: { nombre, ...letra ? { forma: letra } : {} } };
129410
+ }
129411
+ case "seccion":
129412
+ if (id2 === SECCION_NUEVA) return null;
129413
+ return { herramienta: "poner_seccion", argumentos: { nombre: id2.slice("seccion:".length) } };
129414
+ }
129415
+ }
129416
+ function paletasParaBuscar() {
129417
+ let acordes = [];
129418
+ let secciones = [];
129419
+ try {
129420
+ const indice = indexar(obraAbierta().documento);
129421
+ acordes = fichasDeAcordes(acordesUsados(indice)).filter((f) => f.id !== MIRA_DEDUCIR);
129422
+ const nombres = [];
129423
+ for (let i2 = 0; i2 < indice.cantidadDeCompases; i2++) {
129424
+ nombres.push(String(indice.compasMaestro(i2)?.datos.seccion ?? ""));
129425
+ }
129426
+ secciones = fichasDeSecciones(nombres).filter((f) => f.id !== SECCION_NUEVA);
129427
+ } catch {
129428
+ }
129429
+ return FAMILIAS.map((familia) => ({
129430
+ familia,
129431
+ fichas: familia === "acorde" ? acordes : familia === "seccion" ? secciones : fichasFijas(familia)
129432
+ }));
129433
+ }
129434
+ function registrarHerramientasDePaletas(servidor2) {
129435
+ servidor2.registerTool(
129436
+ "buscar_en_las_paletas",
129437
+ {
129438
+ title: "Buscar en las paletas",
129439
+ description: "Busca por nombre en las siete paletas del programa a la vez \u2014figuras, t\xE9cnicas, signos, acordes, bater\xEDa, matices y secciones\u2014 y dice qu\xE9 es cada resultado, en qu\xE9 paleta est\xE1, su tecla de f\xE1brica y **qu\xE9 herramienta lo escribe, con qu\xE9 argumentos**. Acepta sin\xF3nimos en espa\xF1ol y los nombres en ingl\xE9s de los programas: \xABligado\xBB, \xABhammer\xBB, \xABfermata\xBB, \xABhi-hat\xBB. Es la misma b\xFAsqueda de \xABEditar \u25B8 Buscar en las paletas\u2026\xBB. Sirve cuando no sabes la clave de algo: b\xFAscalo ac\xE1 y usa la herramienta que te indica, agregando d\xF3nde (comp\xE1s, golpe, pista\u2026).",
129440
+ inputSchema: {
129441
+ texto: external_exports.string().min(1).max(60).describe("Lo que se busca: \xABacento\xBB, \xABmartillo\xBB, \xABcorchea\xBB, \xABff\xBB\u2026"),
129442
+ limite: external_exports.number().int().min(1).max(30).optional().describe("Cu\xE1ntos resultados, como mucho. Por omisi\xF3n 12.")
129443
+ }
129444
+ },
129445
+ ({ texto: buscado, limite }) => {
129446
+ const paletas = paletasParaBuscar();
129447
+ const hayObra = paletas.some((p) => (p.familia === "acorde" || p.familia === "seccion") && p.fichas.length > 0);
129448
+ const resultados = buscarEnLasPaletas(paletas, buscado, limite ?? 12).map((r) => {
129449
+ const tecla = r.ficha.atajo ? ATAJOS.find((a) => a.id === r.ficha.atajo)?.teclas[0] : void 0;
129450
+ return {
129451
+ nombre: r.ficha.nombre,
129452
+ paleta: NOMBRE_DE_LA_FAMILIA[r.familia],
129453
+ ...tecla ? { teclaDeFabrica: comoSeLee(tecla) } : {},
129454
+ ...herramientaDeLaFicha(r.familia, r.ficha) ?? {}
129455
+ };
129456
+ });
129457
+ return texto2({
129458
+ resultados,
129459
+ // ⚠️ Un cero se tiene que poder leer como «no existe» y no como «no busqué».
129460
+ ...resultados.length === 0 ? { nota: `nada se llama \xAB${buscado}\xBB en las paletas` } : {},
129461
+ ...hayObra ? {} : { sinObra: "los acordes y las secciones salen de la obra: sin una abierta, no se buscan" },
129462
+ teclas: "las teclas son las de f\xE1brica: el m\xFAsico pudo haberlas reasignado en su navegador"
129463
+ });
129464
+ }
129465
+ );
129466
+ }
129467
+
128449
129468
  // apps/mcp/src/servidor.ts
128450
129469
  var NOMBRE = "mandioca-tabs";
128451
129470
  var VERSION = VERSION_DEL_CONECTOR ?? "0.0.0";
@@ -128487,13 +129506,30 @@ DOS MANERAS DE LLEGAR A LA OBRA:
128487
129506
  aparece en su pantalla al instante, y lo que \xE9l escriba lo ves t\xFA.
128488
129507
  Si el m\xFAsico habla de "mi partitura" mientras tiene el programa abierto, quiere la segunda.
128489
129508
  `.trim();
129509
+ function cerrarElPaso() {
129510
+ try {
129511
+ obraAbierta().documento.historial().stopCapturing();
129512
+ } catch {
129513
+ }
129514
+ }
128490
129515
  function crearServidor() {
128491
129516
  const servidor2 = new McpServer(
128492
129517
  { name: NOMBRE, version: VERSION },
128493
129518
  { instructions: INSTRUCCIONES }
128494
129519
  );
128495
129520
  const registrar = servidor2.registerTool.bind(servidor2);
128496
- servidor2.registerTool = ((nombre, config2, cb) => registrar(nombre, { ...config2, annotations: { ...anotacionesDe(nombre), ...config2.annotations } }, cb));
129521
+ servidor2.registerTool = ((nombre, config2, cb) => registrar(
129522
+ nombre,
129523
+ { ...config2, annotations: { ...anotacionesDe(nombre), ...config2.annotations } },
129524
+ LEEN.has(nombre) ? cb : async (...a) => {
129525
+ cerrarElPaso();
129526
+ try {
129527
+ return await cb(...a);
129528
+ } finally {
129529
+ cerrarElPaso();
129530
+ }
129531
+ }
129532
+ ));
128497
129533
  registrarHerramientasDelDocumento(servidor2);
128498
129534
  registrarHerramientasDelPuente(servidor2);
128499
129535
  registrarHerramientasDeSignos(servidor2);
@@ -128503,6 +129539,7 @@ function crearServidor() {
128503
129539
  registrarHerramientasDeBend(servidor2);
128504
129540
  registrarHerramientasDePistas(servidor2);
128505
129541
  registrarHerramientasDeArchivo(servidor2);
129542
+ registrarHerramientasDePaletas(servidor2);
128506
129543
  return servidor2;
128507
129544
  }
128508
129545