# Catch it 1.0 — Cyclonium

Un oscillateur, une représentation sur un plan, un son et une vue 3D.
Version du 11 octobre 2026. Aucun framework, aucune dépendance externe.

## Fichiers

- `index.html`, `style.css`, `app.js` : interface.
- `signal-core.js` : calcul indépendant du DOM, accessible au script classique.
- `signal.mjs` : même moteur, avec exports ES pour un autre module.
- `catchIt.1.html` : version autonome, générée par `build.py`.
- `test-signal.mjs` (racine des sources) : tests numériques, lancer avec Node.js.

Pour travailler à partir des sources, lancer à la racine :

    python -m http.server 8000 --directory dist

Puis ouvrir http://localhost:8000/. La page autonome fonctionne également sans
serveur dans les navigateurs qui autorisent l’audio et les workers locaux.
Le bouton Écouter constitue le geste utilisateur qui active l’audio.

## Lois de production

Le modèle calcule 4 secondes à 48 kHz en Float64Array. Sa phase initiale est zéro.
La position mécanique initiale est le paramètre amplitude ; la vitesse est zéro.

1. Sinus : phi' = 2 pi f, s = A sin(phi).
2. Balayage : f(t) varie linéairement de f0 à f1 sur 4 secondes. La phase est
   intégrée avec la fréquence au milieu du pas, sans saut de phase.
3. Ressort : x'' + 2 gamma x' + omega0^2 x = 0, masse unitaire.
4. Duffing durcissant : x'' + 2 gamma x' + omega0^2 (x + beta x^3) = F(t),
   F(t) = omega0^2 * drive * sin(omega0 * driveRatio * t).

Les lois mécaniques utilisent Velocity-Verlet, des sous-pas et deux demi-pas
d’amortissement exponentiel. Une commande de paramètre recalcule toute la prise
depuis l’état initial, elle n’agit pas sur un oscillateur audio persistant.
Les fréquences affichées sont des références de modèle ; la hauteur perçue et
la fréquence d’un oscillateur non linéaire ne s’en déduisent pas directement.

## Adaptateur spatial

Sur le carré [-1,1] × [-1,1] :

    z(x,y,t) = s(t - d(x,y) * delayMs / 1000) * exp(-attenuation * d(x,y))

La distance vaut sqrt(x*x+y*y) en mode radial, x+1 pour les fronts droits.
Le signal vaut zéro avant et après l’enregistrement. L’interpolation temporelle
est linéaire. Ce champ est une mise en image à retards, pas une résolution
d’une membrane ou de l’équation d’onde acoustique. Le signal source ne dépend
ni de la caméra, ni de cet adaptateur. Les couleurs saturent à |z| >= 1, tandis
que les données exportées et le relief conservent les valeurs du modèle.

Le plan utilise 128 × 128 valeurs ; le relief un maillage de 38 × 38 cellules.
Ces résolutions sont différentes : aux fréquences élevées ou retards importants,
le maillage peut sous-échantillonner les ondulations. Réduire le retard permet
d’observer moins de cycles. Le PNG est une représentation 8 bits ; l’export
JSON du plan conserve les nombres flottants et son domaine spatial.

## Horloges

- Ralenti : le temps de la vue avance à 1/D, sans modifier l’écoute.
- Suivi audio : la vue suit la tête de lecture des 4 secondes sonores.
- Figer : seule la vue est figée ; le son peut continuer.
- Le début et la fin de l’enregistrement ne sont pas raccordés en un cycle.
  La boucle visuelle revient simplement au début, le son s’arrête après 4 s.

La valeur D correspond à `diviseurTemps / 1000` dans Concomitances V8,
qui exprimait son temps de shader en millisecondes.

## Analyse temps–fréquence

STFT sur le signal brut, avant volume et fondus. Fenêtre de Hann de 1024, 2048
ou 4096 échantillons ; 320 positions temporelles ; complément par zéros aux
bords. Spectre unilatéral, amplitude rapportée à la somme de la fenêtre.
Le dB est 20 log10(amplitude / 1). L’image affiche -80 à 0 dB, 0 à 5 kHz,
avec une échelle fréquentielle linéaire. Le pas des bins ne constitue pas à lui
seul le pouvoir de séparation spectral de la fenêtre. Aucun zéro-padding de
l’analyse ne crée une résolution réelle supplémentaire.

## Écoute et WAV

Sortie mono flottante 32 bits. Le gain vaut volume / max(1, crête), pour rester
dans le domaine de sortie sans écrêtage ; il est indiqué dans les métadonnées.
Le signal brut n’est pas normalisé. Des fondus de 5 ms aux extrémités évitent
les clics de la prise audio ; ils ne sont pas appliqués à l’analyse ni au plan.
Le WAV inclut un bloc LIST/INFO/ICMT contenant la recette JSON.

## Captures et sauvegardes

Catch it fige l’instant et garde une miniature du plan et sa recette en mémoire.
Cliquer la miniature recharge celle-ci ; la capture peut être exportée ou retirée.
La galerie n’est pas persistante : exporter avant de fermer la page.
Les PNG du plan, du relief et du spectrogramme contiennent un bloc tEXt
`Cyclonium` avec les métadonnées JSON (loi, paramètres, état initial, caméra,
instant, analyse, provenance). Certains éditeurs d’image retirent ces blocs.
Le JSON de recette est donc aussi proposé séparément.

Le format de cette première version est `cyclonium.catch-it`, version 1.
Ce n’est pas encore le contrat `cyclonium.instrument` d’Ondulations : l’intégration
dans un instrument commun demandera un adaptateur et une politique de durée.
Le moteur `signal.mjs` est néanmoins réutilisable indépendamment :

    import { generateSignal, computeSpectrogram } from './signal.mjs';
    const result = generateSignal({law: 'chirp', frequency: 80, endFrequency: 880});
    const image = computeSpectrogram(result.samples, 2048, 320);

## Vérifications

Tests : sinus contre sa formule analytique ; balayage contre sa phase analytique ;
ressort amorti contre sa solution analytique ; FFT d’une impulsion ; fréquence et
amplitude d’un sinus dans l’analyse ; états mécaniques extrêmes ; causalité du
retard ; validation des paramètres. Le moteur est expérimental et non un étalon
métrologique ; les très hautes harmoniques ne sont pas garanties sans repliement.
L’interface et l’audio n’ont pas été vérifiés dans un navigateur réel lors de
cette livraison.
