P1: explorador del espacio de voicings
Secciones
Ajustes de lectura
Ninguna voz para este idioma en este navegador.
Más voces
Código y términos técnicos en inglés
Desactivado: la voz de la página lo lee todo, sin pausa entre dos voces.
Ninguna voz para este idioma en este navegador.
Más voces
Más voces: Microsoft Edge ofrece voces naturales en línea, y Windows (Configuración › Hora e idioma › Voz), macOS y Android instalan otras.
El índice fijado de Guitar Alchemist guarda cada voicing como un vector compacto de 124 números, extraído del embedding OPTIC-K de 240 dimensiones. Este prototipo muestra una muestra fija de 30.000 de sus 297.910 voicings de guitarra como una nube de puntos que se puede girar, señalar y escuchar, y luego mide qué parte del espacio real muestra la imagen. La respuesta corta: un buen mapa, y una mala guía del vecindario.

En la versión publicada, los diez vecinos se buscan entre los 30.000 voicings de la muestra, no en todo el índice. Los filtros ocultan puntos de la nube sin recalcular esa lista; la selección y sus vecinos resaltados pueden seguir visibles a pesar de un filtro. En pantallas pequeñas, abra la página completa para disponer de más espacio. Si WebGPU no funciona, pruebe la versión WebGL 2.
La página publicada pesa 3,7 MB: 2,7 MB de voicings, 0,9 MB de JavaScript con three.js, y un manifiesto. Cada cifra de esta lección viene de code/ga-protos/p1-voicing-explorer, y las hipótesis se confirmaron en un commit antes de las mediciones.
Lo que ya conoce: un índice vectorial
Sección titulada «Lo que ya conoce: un índice vectorial»Si ha usado la búsqueda vectorial de Azure AI Search o la búsqueda de los k vecinos más cercanos de Elasticsearch, ya conoce la forma del índice de voicings de GA. Cada documento se convierte en un vector; una consulta también; la respuesta son los k documentos cuyos vectores obtienen la mejor puntuación frente a ella. En C#, el núcleo del cálculo es un bucle de llamadas a TensorPrimitives.Dot y una cola de prioridad de los k mejores.
Dos cosas cambian cuando se quiere mirar el índice en lugar de consultarlo.
- Una pantalla tiene dos dimensiones, una vista 3D tiene tres. Los vectores tienen 124. Hay que proyectarlos, y una proyección pierde información.
- Un servicio de búsqueda responde a «¿qué está cerca de esto?». Una imagen invita a la misma pregunta, a ojo: los puntos dibujados junto a un voicing parecen sus vecinos. Si de verdad lo son se puede medir, y es la medición principal de esta lección.
OPTIC-K en un párrafo
Sección titulada «OPTIC-K en un párrafo»La representación de GA está construida a mano, no aprendida. El curso sobre la IA de GA la desmonta: 240 números con nombre repartidos en 11 particiones, de las cuales 6 particiones y 124 números cuentan para la similitud. STRUCTURE describe el conjunto de clases de altura (su croma, su vector de intervalos), MORPHOLOGY la forma en el mástil, CONTEXT la función armónica, SYMBOLIC las etiquetas de técnica, MODAL los modos a los que pertenece el acorde, ROOT la fundamental. Cada partición se normaliza y se multiplica por la raíz cuadrada de su peso (0,45, 0,25, 0,20, 0,10, 0,10, 0,05), de modo que un solo producto escalar da la suma ponderada de los cosenos por partición, 1,15 como máximo. La lección 3 de ese curso lee el índice binario byte a byte, y el apéndice OPTIC del curso de teoría musical explica las equivalencias que le dan nombre.
Los datos, fijados
Sección titulada «Los datos, fijados»La rama main de GA estaba en 66bdd049. El archivo del índice no está en el repositorio: FretboardVoicingsCLI --export-embeddings lo genera. Una sesión que trabajaba en el rendimiento de GA había compilado esa herramienta en ese commit y exportado el índice 25 minutos antes; leí ese archivo sin reconstruirlo, y lo fijé por su huella:
| archivo | optick.index, 183.770.270 bytes |
| SHA-256 | 1e91e4695bab8aa4bb451fdd1f199a4afaa4c4ad913f08acc5163c6ab07caa23 |
| voicings | 313.047: 297.910 de guitarra, 7.795 de bajo, 7.342 de ukelele |
| dimensiones | 124, huella de esquema 0x37cd8ecf |
| exportación | 688.351 voicings en bruto, 313.047 conservados tras la deduplicación, 149 s |
La misma sesión exportó el índice una segunda vez con la misma compilación. compare-indexes.mjs empareja las dos exportaciones voicing a voicing:
{"small":313047,"big":313047,"identical":312977,"differ":0,"missing":70,"vectorFound":313011,"sameName":312977,"maxAbsDifference":0}Cada vector presente en ambas es idéntico bit a bit, pero 70 voicings de una exportación no están en la otra. La exportación deduplica los voicings que comparten una forma de trastes y un conjunto de clases de altura, y conserva el más fácil de tocar (Program.cs, líneas 759-787); el generador corre en paralelo, y cuando dos candidatos cuestan lo mismo, gana el primero que llega. Así que «el índice en el commit 66bdd049» no es un solo archivo: es la huella la que fija los datos.
Qué cuerda va primero
Sección titulada «Qué cuerda va primero»El índice guarda cada diagrama como texto. La fila 1000 dice:
diagram "1-2-x-5-x-1", midiNotes [65, 61, 55, 41], quality_inferred "Gm7b5/F"Leído desde la cuerda de mi agudo, 1 es un fa 4, MIDI 65, la primera nota de la lista. Leído desde el mi grave, 1 sería un fa 2, MIDI 41, la última. El índice de GA escribe primero la cuerda 1, el mi agudo. Un guitarrista escribe la misma forma con el mi grave primero, 1x5x21. El componente React FretDiagram del propio GA y su analizador FretDiagram.cs esperan el mi grave primero. El prototipo conserva el orden de GA en sus datos y convierte una sola vez, en voicing.mjs, y el paso de construcción comprueba cada fila de la muestra: el diagrama leído con el mi agudo primero debe devolver las notas MIDI del índice. Encontró 0 discrepancias en 30.000 filas, y una prueba unitaria fija la fila 1000.
La cadena de construcción
Sección titulada «La cadena de construcción»flowchart LR A["optick.index<br/>313.047 voicings x 124 flotantes<br/>183,8 MB"] --> B["Leer la cabecera,<br/>los vectores y los metadatos msgpack"] B --> C["ACP sobre los 297.910<br/>vectores de guitarra<br/>(Jacobi, float64)"] B --> D["Muestra con semilla<br/>30.000 filas<br/>semilla 20260916"] C --> E["Proyectar la muestra<br/>sobre 3 componentes"] D --> E D --> F["Los 10 vecinos más cercanos<br/>exactos en 124 dimensiones"] D --> G["Nombre, familia, extensión,<br/>tensión, diagrama"] E --> H["voicings.bin 2,7 MB<br/>+ manifest.json"] F --> H G --> H H --> I["Página three.js<br/>WebGPU o WebGL 2"]build-data.mjs es Node.js puro, sin dependencias:
cd code/ga-protos/p1-voicing-explorernode scripts/build-data.mjs --index G:/learn-33/ga-p1/optick.index --out public/data --ga-sha 66bdd049ad3f4b10f996e4cc6a9c6e58797cf30esampled 30000 of 297910 guitar voicings with seed 20260916PCA on 297910 vectors: 11 Jacobi sweeps, first 3 components explain 22.2 %exact 10-NN of 30000 voicings in 124 dimensions: 84.9 schecks: {"midiMismatches":0,"stringCountMismatches":0}PCA explained variance, first 3 components: 8.8 %, 8.2 %, 5.3 %wrote public\data\voicings.bin (2670024 bytes) and manifest.json (3380 chord names)timings: openMs 48, pcaMs 1291, knnMs 84876, totalMs 86348Cada paso está elegido para dar los mismos bytes en todas las máquinas.
- El lector (
optick.mjs) sigue la disposición deOptickIndexReader, y recalcula la huella de esquema como el CRC-32 de la cadena de disposición: un archivo escrito con otra disposición se rechaza. - La ACP (
pca.mjs) construye la matriz de covarianza de 124 × 124 enfloat64y la diagonaliza con rotaciones de Jacobi cíclicas, el método de la lección 5 del curso IX. El signo de un vector propio es arbitrario, así que cada uno se invierte hasta que su mayor componente sea positiva: sin eso, una reconstrucción podría dar la nube en espejo. Una prueba ajusta dos veces los mismos datos y compara los bits. - La muestra (
sample.mjs) es una mezcla de Fisher-Yates parcial dirigida por mulberry32, un generador escrito solo conMath.imul, de modo que ningún redondeo de coma flotante puede variar entre plataformas. La prueba fija las primeras filas que elige la semilla 20260916: 4, 10, 42, 47, 52. - Los vecinos (
knn.mjs) son exactos: cada par de los 30.000 voicings, 900 millones de productos escalares, 85 segundos en un solo hilo. Los empates van a la fila más baja, como en elOptickSearchStrategyde GA. - El archivo (
format.mjs) es una cabecera de 24 bytes seguida de arrays tipados, cada uno alineado a 4 bytes, para que la página los lea en su sitio: posiciones enFloat32Array, trastes enInt8Array, identificadores de nombres y de vecinos enUint16Array, puntuaciones enFloat32Array. Los 3.380 nombres de acordes distintos, las familias y la procedencia van enmanifest.json.
La muestra publicada contiene 30.000 de los 297.910 voicings de guitarra, el 10 %. El bajo y el ukelele quedan fuera: sus diagramas tienen cuatro cuerdas, y la página dibuja diagramas de guitarra.
La página
Sección titulada «La página»main.js usa el WebGPURenderer de three.js, que recurre a WebGL 2 cuando falta WebGPU, como en el curso de three.js.
- Los puntos. WebGPU dibuja las primitivas de tipo punto con un solo píxel de ancho, se pida el tamaño que se pida (
PointsNodeMateriallo dice). Por eso la página dibuja un soloSprite, 30.000 veces: su material lee la posición, el color y la visibilidad de cada instancia en atributos instanciados, y una expresión TSL sobre las coordenadas de textura del cuadrilátero lo hace redondo. Filtrar pone a cero el tamaño de una instancia: un pequeño envío de búfer, ninguna geometría nueva. - El color. Por familia de acordes (mayor, menor, séptima dominante… o «nombrado solo por clase de conjunto» cuando GA no tiene símbolo de acorde), por la medida de tensión, por la posición en el mástil, o por el número de notas.
- La selección. Cuando el puntero se mueve, la página proyecta cada punto visible con la matriz de la cámara y se queda con el más cercano a menos de 8 píxeles: un simple bucle sobre un
Float32Array, como se escribiría en C#. - El diagrama está portado del
FretDiagram.tsxde GA a un lienzo 2D, geometría y regla del traste inicial incluidas, y recibe los trastes convertidos con el mi grave primero. - El sonido es una síntesis Karplus-Strong en
synth.mjs: una línea de retardo de un periodo de largo, llena de ruido, realimentada a través de la media de dos muestras vecinas. Esa media es un filtro paso bajo: los parciales agudos se apagan primero, como en una cuerda real. Las notas empiezan con 35 ms de separación, la cuerda grave primero, y las muestras van a la Web Audio API en un solo búfer. - Los vecinos listados a la derecha son los diez precalculados, con la puntuación de GA y su distancia en la vista 3D. Unas líneas amarillas los unen al voicing en la nube.
Los filtros de extensión y de tensión usan dos valores calculados por el paso de construcción. La extensión es la distancia entre la nota pisada más baja y la más alta. La tensión no es la de GA: STRUCTURE tiene una casilla «tensión», pero el índice la guarda normalizada con su partición, y el valor no se puede recuperar. En su lugar, el prototipo cuenta los semitonos, los tonos y los tritonos del conjunto de clases de altura: 0 para una tríada mayor, 2 para una séptima dominante.
Mediciones
Sección titulada «Mediciones»measure.mjs tarda unos dos minutos en un solo hilo y escribe results/measurements.json:
node scripts/measure.mjs --index G:/learn-33/ga-p1/optick.indexElige 2.000 voicings de consulta entre los 30.000 con la semilla 20260917, encuentra sus 10 vecinos más cercanos exactos por producto escalar en 124 dimensiones, y los compara con los 10 más cercanos por distancia euclídea tras proyectar sobre 2, 3, 10 o 32 componentes principales. La exhaustividad a 10 es la fracción de los diez vecinos verdaderos que aparecen entre los diez vecinos proyectados.
Lo que explican los tres ejes
Sección titulada «Lo que explican los tres ejes»| Componentes | 1 | 2 | 3 | 10 | 32 | 64 |
|---|---|---|---|---|---|---|
| Varianza explicada | 8.8 % | 17.0 % | 22.2 % | 48.0 % | 84.7 % | 99.9 % |
Los dos primeros ejes son la forma. Los cuadrados de los coeficientes de la primera componente son un 85 % MORPHOLOGY y un 12 % ROOT; los de la segunda, un 83 % y un 13 %; la tercera es un 84 % STRUCTURE, las clases de altura. CONTEXT no contribuye a ninguna: es la misma constante para todos los voicings, un hallazgo que también hizo el curso sobre la IA de GA. Hacia 64 componentes se alcanza el 99,9 % de la varianza: los 124 números viven en realidad en unas sesenta dimensiones.
¿Los vecinos en pantalla son los vecinos verdaderos?
Sección titulada «¿Los vecinos en pantalla son los vecinos verdaderos?»| Exhaustividad a 10 | ACP 2 | ACP 3 | ACP 10 | ACP 32 |
|---|---|---|---|---|
| dentro de la muestra de 30.000 | 0.036 | 0.168 | 0.438 | 0.693 |
| frente a los 297.910 voicings de guitarra | 0.168 | 0.348 | 0.765 | 0.787 |
En la vista del explorador, aproximadamente uno de cada seis de los diez puntos más cercanos a un voicing es uno de sus diez vecinos verdaderos. Las líneas amarillas lo muestran: cruzan la nube.
Una segunda medida hace una pregunta de músico: ¿los vecinos de un voicing pertenecen a la misma familia de acordes?
| Misma familia entre los 10 vecinos | exactos, 124 dim. | ACP 3 | ACP 10 | ACP 32 |
|---|---|---|---|---|
| dentro de la muestra | 61.3 % | 36.9 % | 73.0 % | 60.5 % |
| frente a todos los voicings de guitarra | 96.5 % | 60.6 % | 96.9 % | 95.3 % |
Diez componentes conservan vecindarios tan coherentes musicalmente como el espacio completo, y algo más dentro de la muestra.
Los empates
Sección titulada «Los empates»Para el 10,1 % de las consultas dentro de la muestra, y el 47,4 % frente al índice completo, el 10.º y el 11.º vecinos verdaderos tienen la misma puntuación. La causa es más profunda que un redondeo: 121.768 de los 297.910 vectores de guitarra (40,9 %) son copias exactas del vector de otro voicing, bit a bit. Hay 176.142 vectores distintos, y el grupo más grande reúne 12 voicings: 5-1-2-4-2-x, 5-1-4-4-2-x, 5-1-4-4-x-2… todos un acorde de fa sostenido disminuido con la arriba, sobre si, sobre do o sin bajo. Voicings con el mismo conjunto de clases de altura, la misma nota aguda y una forma de mano parecida son indistinguibles para OPTIC-K. El «top 10» es entonces una elección entre iguales, y la medición también da una exhaustividad que cuenta como encontrado a todo vecino cuya puntuación alcance la del 10.º: 0,354 en lugar de 0,348 para tres componentes frente al índice completo.
Los diagramas
Sección titulada «Los diagramas»El FretDiagram de GA muestra cinco trastes, y empieza en la cejuela cuando la nota pisada más baja está en el traste 1 o 2 (línea 31). Un voicing que va del traste 2 al 6 necesita seis filas, y la línea 104 descarta sin avisar el punto que no cabe. En todo el índice de guitarra, 15.853 voicings (5,32 %) pierden al menos un punto. El portado conserva la regla, para que el explorador muestre lo que muestra GA; el ejercicio 4 la corrige.
El tiempo de fotograma
Sección titulada «El tiempo de fotograma»probe.mjs abre la página en el Chromium 153 sin interfaz de Playwright, a 1280 × 720, renderiza 10 fotogramas de calentamiento, luego 120 fotogramas mientras gira la cámara, y da tres tiempos por fotograma: el tiempo de pared, desde render() hasta que la GPU anuncia que ha terminado (onSubmittedWorkDone en WebGPU, un readPixels de un píxel en WebGL 2); el tiempo de CPU, solo la llamada a render(); y la pasada de GPU, la pasada de render en sí, medida con las consultas de marca de tiempo de la GPU. frame-series.sh lanza la serie. La máquina tiene una NVIDIA RTX 5080 con el controlador 610.88; WebGPU usa su adaptador «blackwell», y WebGL 2 pasa por ANGLE sobre Direct3D 11. Medianas, en milisegundos:
| Puntos | Backend | Dibujados como | Pared | CPU | Pasada de GPU | Selección |
|---|---|---|---|---|---|---|
| 30,000 | WebGPU | sprites | 3.5 | 0.3 | 0.066 | 0.2 |
| 30,000 | WebGL 2 | sprites | 0.9 | 0.3 | 0.071 | 0.1 |
| 30,000 | WebGPU | puntos de un píxel | 3.5 | 0.3 | 0.066 | 0.2 |
| 297,910 | WebGPU | sprites | 3.6 | 0.3 | 0.262 | 1.4 |
| 297,910 | WebGL 2 | sprites | 1.1 | 0.3 | 0.276 | 1.3 |
| 1,000,000 aleatorios | WebGPU | sprites | 3.7 | 0.3 | 0.786 | 4.8 |
| 1,000,000 aleatorios | WebGL 2 | sprites | 1.8 | 0.3 | 0.832 | 4.4 |
| 3,000,000 aleatorios | WebGPU | sprites | 4.4 | 0.3 | 2.294 | 14.1 |
Las líneas en bruto están en results/frame-times.jsonl. Destacan tres cosas.
- La escena casi no cuesta nada. Dibujar 30.000 cuadriláteros lleva 66 microsegundos de GPU, y el coste crece más o menos con el número de puntos: 0,26 ms para 300.000, 2,3 ms para 3 millones. En la CPU se queda en 0,3 ms, porque la nube es siempre una sola llamada de dibujo.
- El tiempo de pared mide la espera, no el trabajo. El tiempo de pared de WebGPU se queda en 3,5 ms sea cual sea el número de puntos, 50 veces la pasada de render: es lo que tarda
onSubmittedWorkDoneen volver en este navegador, no lo que tarda la GPU en dibujar. Mi primera serie solo usaba esa cifra, y en ella WebGPU parecía cuatro veces más lento que WebGL 2. Las consultas de marca de tiempo muestran que los dos backends hacen el mismo trabajo. - La selección crece linealmente, y es lo primero que se rompe. Proyectar cada punto en la CPU cuesta 1,4 ms con 300.000 puntos y 14 ms con 3 millones, la mayor parte de un fotograma a 60 Hz. La página hace como mucho una selección cada 40 ms mientras se mueve el puntero.
En la primera serie, renderer.info.render.drawCalls mostraba 434, y luego números negativos: three.js pone ese contador a cero al principio de cada fotograma de animación, y las esperas de la sonda cruzan fotogramas. La tabla omite ese recuento en lugar de mostrar uno falso.
Hipótesis frente a resultados
Sección titulada «Hipótesis frente a resultados»| Predicción, escrita primero | Resultado | Veredicto | |
|---|---|---|---|
| H1 | 3 componentes explican menos del 30 % | 22,2 % | acertada, pero vista antes de escribirla |
| H2 | exhaustividad a 10 de la vista 3D por debajo de 0,15 en la muestra; por encima de 0,6 con 32 componentes | 0,168; 0,693 | fallida por poco; acertada |
| H3 | exhaustividad peor frente al índice completo que dentro de la muestra; puntuación del 10.º vecino al menos 0,05 más alta | mejor (0,348 frente a 0,168); +0,051 | fallida; acertada por los pelos |
| H4 | 10.º y 11.º vecinos empatados para más del 5 % de los voicings | 10,1 % en la muestra, 47,4 % en el índice | acertada, y la causa (40,9 % de vectores duplicados) no estaba prevista |
| H5 | 30.000 puntos por debajo de 2 ms de tiempo de GPU, todos los voicings de guitarra por debajo de 5 ms; WebGL 2 a menos de un factor 2 de WebGPU | pasada de render de 0,066 ms y 0,262 ms; WebGL 2 a un 8 % | acertada, una vez usado el reloj correcto |
| H6 | selección por debajo de 5 ms para 30.000 puntos, por encima de 16 ms para 297.910 | 0,2 ms; 1,4 ms, con un bucle de proyección en lugar de Raycaster |
acertada; fallida, 10 veces más rápida de lo que pensaba |
| H7 | más del 1 % de los diagramas pierden un punto | 5,32 % | acertada |
H3 falló por una razón que vale la pena conservar. Razonaba así: los vecinos verdaderos de un voicing de la muestra están casi todos fuera de ella, así que la muestra saldría peor. Pero la exhaustividad compara dos listas de vecinos dentro del mismo conjunto de candidatos. Frente al índice completo, los vecinos verdaderos están tan cerca (puntuación media del 10.º: 1,107 sobre 1,15) que son casi duplicados, y los casi duplicados siguen cerca unos de otros en cualquier proyección. En una muestra dispersa, los diez más cercanos están más lejos y más repartidos, y la proyección los mezcla.
Modo local: todo el índice de guitarra
Sección titulada «Modo local: todo el índice de guitarra»La página publicada se detiene en 30.000 voicings. La misma página puede cargar los 297.910, con sus vectores de 124 dimensiones, y buscar los vecinos en el navegador. Esos datos pesan 156 MB y se quedan en su máquina:
node scripts/build-data.mjs --index G:/learn-33/ga-p1/optick.index --out public/data-full --sample 297910 --k 0 --with-vectors --ga-sha 66bdd049ad3f4b10f996e4cc6a9c6e58797cf30enpm run dev# luego abra http://localhost:5198/?data=data-full--k 0 omite el precálculo de los vecinos, que llevaría unas 100 veces los 85 segundos de la muestra; en su lugar, la página calcula los diez vecinos del voicing pulsado, sobre todos los vectores. Medido con la sonda en la misma máquina: cargar y decodificar los 156 MB desde el servidor local lleva 341 ms, el montón de JavaScript ocupa 180 MB, la pasada de render sigue en 0,26 ms, y encontrar los diez vecinos exactos de un voicing pulsado entre 297.910 vectores lleva 28 ms en el navegador, en un solo hilo. Por encima de un millón de voicings, esa búsqueda, como la selección, necesitaría un hilo de trabajo aparte o la GPU.
Lo que funcionó, lo que no
Sección titulada «Lo que funcionó, lo que no»Lo que funcionó. El paso de construcción es rápido, salvo los vecinos exactos, y determinista, con una prueba para cada pieza. Los sprites instanciados dibujan todos los puntos en una sola llamada de dibujo, e incluso 3 millones de puntos dejan libre la mayor parte de un fotograma a 60 Hz. La comprobación del orden de las cuerdas no encontró nada, y de eso se trata: el orden ahora se prueba en lugar de suponerse. Y la página es divertida: saltar de vecino en vecino escuchándolos es una buena manera de sentir lo que OPTIC-K llama «similar».
Lo que no funcionó.
- La vista 3D es un mapa, no un vecindario. Un 22 % de la varianza y una exhaustividad de 0,17 significan que los puntos dibujados juntos, la mayoría de las veces, no son vecinos en el espacio de GA. Por eso el panel lista los vecinos verdaderos en lugar de fiarse de la imagen, y las líneas hacen visible el desacuerdo.
- Los ejes muestran sobre todo la forma en el mástil. Coloree por familia, y las familias se solapan; coloree por posición en el mástil, y aparecen degradados. Una proyección ajustada solo sobre STRUCTURE mostraría mejor la armonía: es el ejercicio 2.
- Mis hipótesis H3 y H6 fallaron, y H2 no alcanzó su umbral.
- Mi primera serie de tiempos de fotograma medía lo que no era. Cronometraba
render()másonSubmittedWorkDone, y daba WebGPU a 3,5 ms y WebGL 2 a 0,5 ms para la misma escena. La segunda serie añadió las consultas de marca de tiempo de la GPU, que mostraron el mismo trabajo de 0,07 ms en los dos. La primera serie se conserva enresults/frame-times-run1.jsonl. - Una exportación parcial no puede comprobar la procedencia. Intenté confirmar el índice regenerando 3.000 voicings con la herramienta de GA (
--export-max 3000). El límite se detiene tras los 3.000 primeros voicings en bruto en orden de generación, que empezaba en el traste 19; la deduplicación, dentro de ese conjunto pequeño, conservó 2.641 voicings altos en el mástil, y ninguno de sus vectores existe en el índice completo, donde ganaron formas más fáciles, más abajo en el mástil. La comprobación que funcionó fue comparar dos exportaciones completas. - UMAP no se probó. Conservaría mejor los vecindarios locales que la ACP, a costa del determinismo entre versiones de la biblioteca, y de una dependencia. Es el ejercicio 6.
Ejercicios
Sección titulada «Ejercicios»- La página colorea por familia de acordes. Con
manifest.json, cuente cuántos de los 3.380 nombres de acordes están «nombrados solo por clase de conjunto o por intervalo», y cuántos voicings de la muestra los llevan. - Ajuste la ACP solo sobre la partición STRUCTURE (dimensiones compactas 0 a 23) en lugar de las 124 dimensiones. Prediga primero: ¿la exhaustividad a 10 frente a los vectores completos subirá o bajará, y se separarán mejor las familias?
- El paso de construcción guarda los identificadores de vecinos en
Uint16Arraypara 30.000 voicings. ¿Hasta qué tamaño de muestra funciona, y qué haceformat.mjsmás allá? - Corrija la regla de diagrama de GA para que nunca se pierda un punto, manteniendo cinco filas. Mida cuántos de los 297.910 voicings siguen perdiendo un punto.
- Explique por qué dos voicings con la misma puntuación frente a una consulta pueden estar en lugares distintos de la vista 3D.
- Añada UMAP (por ejemplo el paquete
umap-js) con una semilla fija como segunda proyección. ¿Qué habría que fijar para que la construcción siga siendo determinista, y cómo lo probaría? - La medida de tensión cuenta los pares de notas a un semitono, un tono o un tritono de distancia, por clase de altura. ¿Cuáles son la tensión más baja y la más alta posibles para un acorde de cuatro clases de altura distintas, y qué acordes las alcanzan?
Soluciones
- Filtre
manifest.namesconfamilyOf(name) === FAMILY_LIST.length - 1: 104 de los 3.380 nombres son nombres que GA no supo escribir como un acorde, comoForte 5-7 (pentachord)oC + Gb (Tritone). Decodificarvoicings.biny contarfamily[i] === 11da 1.237 voicings de la muestra, el número que muestra la leyenda de la página. - Haga que
fitPca(index.vectors, index.dim, fitRows)trabaje sobre una copia que solo conserve los 24 primeros números de cada vector, y proyecte del mismo modo. La exhaustividad frente a los vecinos en 124 dimensiones debería bajar, porque la forma, que domina los ejes actuales, ya no se ve; la separación de las familias debería mejorar, porque las familias se definen por las clases de altura. Mida las dos cosas conmeasure.mjsantes de creerse ninguna. Uint16Arrayguarda identificadores hasta 65.535, es decir, hasta 65.536 voicings.encodecompruebacount <= 65536y pasa aUint32Array, anotando la anchura (2 o 4 bytes) en la cabecera, quedecodelee antes de construir las vistas.- Una corrección: empezar la cuadrícula en la cejuela solo si todas las notas pisadas caben en cinco filas desde ahí,
base = max <= 5 ? 1 : min. La ventana de GA mantiene cada forma dentro de una extensión de 4 trastes, así que cinco filas bastan siempre a partir demin. Sobre los 297.910 voicings de guitarra, con el bucle demeasure.mjs, el número de voicings que pierden un punto pasa de 15.853 a 0. - La puntuación es una suma sobre 124 dimensiones; la posición 3D solo conserva las tres direcciones de mayor varianza. Dos voicings pueden parecerse igual de bien a la consulta en particiones distintas (uno en STRUCTURE, el otro en MORPHOLOGY), y esas particiones apuntan a lo largo de ejes principales distintos. Los vectores duplicados, en cambio, caen siempre en el mismo punto 3D.
- Fije la versión del paquete en el archivo de bloqueo, pase una función aleatoria con semilla (mulberry32) en lugar de
Math.random, y fije cada parámetro (vecinos, distancia mínima, épocas). Pruebe como con la ACP: construya dos veces, compare los bytes, y fije algunas coordenadas en una prueba, para que una actualización de la biblioteca que cambie el resultado haga fallar la CI en lugar de mover la nube en silencio. - Cuatro clases de altura forman seis pares. La tríada aumentada do, mi, sol sostenido no tiene ni semitono, ni tono, ni tritono, pero cualquier cuarta nota cae a un semitono o a un tono de alguna de sus notas, y el bucle sobre los 495 conjuntos de cuatro notas no encuentra ningún acorde con 0: el mínimo es 1, alcanzado por ejemplo por Cmaj7 (do, mi, sol, si, donde solo si y do rozan). El máximo es 5, alcanzado por el racimo do, re bemol, re, mi bemol: tres semitonos y dos tonos, y el sexto par es una tercera menor. Escribir ese bucle es la manera fiable de responder.
Puntos clave
Sección titulada «Puntos clave»- Una imagen de un índice vectorial responde bien a «¿dónde está cada cosa?» y mal a «¿qué está cerca de esto?»: tres componentes principales conservan el 22 % de la varianza de GA y uno de cada seis vecinos verdaderos.
- Diez componentes conservan vecindarios tan coherentes musicalmente como las 124 dimensiones; el espacio tiene en realidad unas sesenta dimensiones.
- El 40,9 % de los vectores de guitarra de GA son copias exactas del vector de otro voicing: un «top 10» es a menudo una elección entre iguales.
- El índice de GA escribe los diagramas con el mi agudo primero, mientras que los componentes de diagrama de GA leen el mi grave primero: pruebe el orden frente a las notas MIDI, no lo suponga.
- Una construcción determinista necesita más que una semilla: números aleatorios enteros, signos de vectores propios fijados, empates resueltos por identificador, y la huella del archivo de entrada, ya que dos exportaciones en el mismo commit difieren.
- Cronometre la GPU con su propio reloj: esperar a la cola llevaba 3,5 ms donde la pasada de render llevaba 0,07 ms.
- Escriba la hipótesis primero. Tres de las mías fallaron o no alcanzaron su umbral, y la razón por la que H3 falló me enseñó más que las que acertaron.
Fuentes
Sección titulada «Fuentes»- GuitarAlchemist/ga en
66bdd049:Common/GA.Business.ML/Embeddings/EmbeddingSchema.cs,Common/GA.Business.ML/Search/OptickIndexReader.cs,Demos/Music Theory/FretboardVoicingsCLI/OptickIndexWriter.csyProgram.cs,Common/GA.Business.ML/Agents/FretDiagram.cs,ReactComponents/ga-react-components/src/components/FretDiagram.tsx. - Kevin Karplus y Alex Strong, «Digital Synthesis of Plucked-String and Drum Timbres», Computer Music Journal 7 (2), 1983, pp. 43-55.
- Karl Pearson, «On Lines and Planes of Closest Fit to Systems of Points in Space», Philosophical Magazine 2 (11), 1901, pp. 559-572, el origen del análisis de componentes principales; Wikipedia, Principal component analysis y Jacobi eigenvalue algorithm.
- three.js r186:
WebGPURenderer,PointsNodeMaterialy su código fuente,Sprite,OrbitControls. - MDN: WebGPU API,
GPUQueue.onSubmittedWorkDone, Web Audio API. - Microsoft Learn: Vector search in Azure AI Search,
TensorPrimitives.Dot; Elastic, kNN search. - Vite, Playwright, el ejecutor de pruebas de Node.js, MessagePack.