this repo has no description
3
fork

Configure Feed

Select the types of activity you want to include in your feed.

:memo: Omigod finished???

+202 -85
+19
paper/bibliography.yaml
··· 369 369 url: 370 370 value: https://github.com/typst/typst/blob/1b2714e1a758d6ee0f9471fd1e49cb02f6d8cde4/Cargo.toml#L124 371 371 date: 2025-03-24 372 + 373 + rustcompileunits: 374 + title: Rust’s Huge Compilation Units 375 + type: article 376 + publisher: TiDB 377 + author: Brian Anderson 378 + date: 2020-06-22 379 + url: 380 + value: https://www.pingcap.com/blog/rust-huge-compilation-units/ 381 + date: 2025-03-24 382 + 383 + taurihmr: 384 + title: Tauri 2.0 Stable Release 385 + type: article 386 + publisher: Tauri 387 + date: 2024-10-02 388 + url: 389 + value: https://v2.tauri.app/blog/tauri-20/#hot-module-replacement 390 + date: 2025-03-24
paper/main.pdf

This is a binary file and will not be displayed.

+182 -84
paper/main.typ
··· 35 35 fill: luma(240), 36 36 radius: 4pt, 37 37 width: 100%, 38 - // Figure itself is already non breakable, afaik 38 + // Figure itself is already non breakable, AFAIK 39 39 breakable: caption != "", 40 40 if type(content) == str { 41 41 raw( ··· 108 108 ], 109 109 ) 110 110 111 - Avec cette idée dans la tête, je me mets à gribouiller une ébauche d'"alphabet des formes", qui, naïvement, chercher à énumérer toutes les formes construisibles à partir de formes simples, que l'on peut superposer, pivoter et translater. 111 + Avec cette idée dans la tête, je me mets à gribouiller une ébauche d'"alphabet des formes", qui, naïvement, chercher à énumérer toutes les formes constructibles à partir de formes simples, que l'on peut superposer, pivoter et translater. 112 112 113 113 #grid( 114 114 columns: (1fr, 1fr), ··· 119 119 120 120 Principalement par simple intérêt esthétique, je vectorise cette page via Illustrator. Vectoriser signifie convertir une image bitmap, représentée par des pixels, en une image vectorielle, qui est décrite par une série d'instructions permettant de tracer des vecteurs (d'où le nom), leur ajouter des attributs comme des couleurs, des règles de remplissage (Even-Odd, Non-Zero, etc.), des effets de dégradés, etc. 121 121 122 - Un aspect intéréssant est que, parmi les différents formats d'image vectorielles existant, le _SVG_, pour _Scalable Vector Graphics_, est indéniablement le plus populaire, et est un standard ouvert décrivant un format texte. 122 + Un aspect intéressant est que, parmi les différents formats d'image vectorielles existant, le _SVG_, pour _Scalable Vector Graphics_, est indéniablement le plus populaire, et est un standard ouvert décrivant un format texte. 123 123 124 124 Il est donc très facile de programmatiquement générer des images vectorielles à travers ce format. 125 125 ··· 140 140 "reflections", 141 141 "spline-optimisation", 142 142 "weaving", 143 - ).map(artwork => grid.cell( 144 - image("../examples/gallery/" + artwork + ".svg", width: 100%), 145 - )) 143 + ).map(artwork => grid.cell(image("../examples/gallery/" + artwork + ".svg", width: 100%))) 146 144 ), 147 145 ) 148 146 149 - L'étape prochaine dans cette démarche était évidemment donc de générer procéduralement ces formes. Afin d'avoir des résultats intéréssants, et devant l'évidente absurdité d'un projet d'énumération _complète_ de _toutes les formes_, on préfèrera des générations procédurales dites "semi-aléatoires", dans le sens où certains aspects du résultat final sont laissés à l'aléatoire, comme le placement des formes élémentaires, tandis que de d'autres, comme la palette de couleurs, sont des décisions de l'artiste. 147 + L'étape prochaine dans cette démarche était évidemment donc de générer procéduralement ces formes. Afin d'avoir des résultats intéressants, et devant l'évidente absurdité d'un projet d'énumération _complète_ de _toutes les formes_, on préférera des générations procédurales dites "semi-aléatoires", dans le sens où certains aspects du résultat final sont laissés à l'aléatoire, comme le placement des formes élémentaires, tandis que de d'autres, comme la palette de couleurs, sont des décisions de l'artiste. 150 148 151 149 Le modèle initialement choisi dans les premières ébauches de Shapemaker est le suivant: 152 150 ··· 187 185 188 186 === Interprétation collective 189 187 190 - Avec 30 œuvres abstraites sans nom, je me suis posé la question de comment les nommer. J'aurais pu les nommer au gré de ma propre imagination, mais j'ai trouvé intéréssant le faire de laisser cette décision au grand public, qui tomberait né à né avec ces manifestations de pseudo-hasard virtuel. 188 + Avec 30 œuvres abstraites sans nom, je me suis posé la question de comment les nommer. J'aurais pu les nommer au gré de ma propre imagination, mais j'ai trouvé intéressant le faire de laisser cette décision au grand public, qui tomberait né à né avec ces manifestations de pseudo-hasard virtuel. 191 189 192 190 Le choix du nom d'une œuvre, en particulier quand elle est aussi abstraite et dénuée de contexte explicite, peut se faire parmi une potentielle infinité de titres, du littéral, au descriptiviste au poétique. 193 191 194 192 Les œuvres possèdent toutes un QR code amenant sur une page web qui permet de (re)nommer l'œuvre, en y apposant optionnellement son nom, en l'adoptant jusqu'à ce que lea prochain·e n'en prenne la garde. 195 193 196 - J'ai donc laissé le public trouver ces œuvres, cachées à travers la ville, dans l'esprit des fameux _Spaces Invaders_ de Paris @spaceinvadersparis (qui d'ailleurs étendent leur colonisation bien au-délà de Paris, allant même jusqu'à l'ISS @spaceinvadersiss). 194 + J'ai donc laissé le public trouver ces œuvres, cachées à travers la ville, dans l'esprit des fameux _Spaces Invaders_ de Paris @spaceinvadersparis (qui d'ailleurs étendent leur colonisation bien au-delà de Paris, allant même jusqu'à l'ISS @spaceinvadersiss). 197 195 198 196 199 197 #let work = (slug, caption, with-context: false, screenshot: true) => figure( ··· 217 215 ) 218 216 219 217 220 - #work("paramount", ["Paramount"]) 221 218 #work("reflets-citadins", ["Reflets Citadins", nommée par _Enide_]) 222 - #work( 223 - "lenvolée-du-cerf-volant", 224 - ["l'envolée du Cerf-Volant", nommée par _Nicolas C._], 225 - ) 219 + #work("paramount", ["Paramount"]) 220 + // #work( 221 + // "lenvolée-du-cerf-volant", 222 + // ["l'envolée du Cerf-Volant", nommée par _Nicolas C._], 223 + // ) 226 224 227 225 Certaines ont été souvent renommées, beaucoup ont été volées, et certaines restent encore inconquises. 228 226 ··· 289 287 Les concepts de transformations et de filtres sont également très proche de ce qu'on peut retrouver dans des logiciels de création d'images raster, comme Photoshop. 290 288 291 289 290 + == Découpage en modules 291 + 292 + Pour render la bibliothèque plus claire, et éventuellement pouvoir facilement séparer la crate en plusieurs sous-crates pour améliorer la vitesse de compilation @rustcompileunits, la crate est découpée en plusieurs modules: 292 293 293 294 #grid( 294 295 columns: (1fr, 1fr), 295 - gutter: 1em, 296 + gutter: 2em, 296 297 [ 297 - La bibliothèque fournit une grande quantité de fonctions utiles pour redimensionner des régions, en prendre le milieu. 298 - 299 - La partie purement géométrique de la bibliothèque, définissans `Point`, `Region` et leurs opérations utiles associées (itérer les points d'une région, calculer le milieu d'une région, etc.), sont regroupées dans `shapemaker::geometry`. 300 - 301 - Les définitions des objets et de tout leurs aspects visuels (`Fill`, `Transform`, `Filter`, `Color`, `Object`, `ColoredObject`) sont regroupées dans `shapemaker::objects`. 302 - 303 - Il y a également `shapemaker::random` qui regroupe des fonctions de génération aléatoire, permettant d'introduire facilement et de manière plus ou moins granulaire, une part d'aléatoire dans le processus de génération: `Region.random_point()`, `Color::random()`, etc. 304 - 305 - Enfin, `shapemaker::rendering` implémente le rendu d'un canvas et de tout ce qu'il contient en SVG 298 + / geometry: partie purement géométrique de la bibliothèque, définissant `Point`, `Region` et leurs opérations utiles associées 299 + / graphics: définitions des objets et tout leurs aspects visuels (`Fill`, `Transform`, `Filter`, `Color`, `Object`, `ColoredObject`) 300 + / random: fonctions de génération aléatoire, permettant d'introduire facilement et de manière plus ou moins granulaire, une part d'aléatoire dans le processus de génération: `Region.random_point()`, `Color::random()`, etc. 301 + / rendering: implémentation du rendu en SVG, et conversion en PNG 302 + / video: cf #ref(<crate::video>) 303 + / synchronization: cf #ref(<crate::synchronization>) 304 + / vst: cf #ref(<crate::vst>) 305 + / wasm: cf #ref(<crate::wasm>) 306 306 ], 307 307 diagram( 308 308 caption: [Dépendances entre les modules de la bibliothèque], ··· 318 318 ), 319 319 ) 320 320 321 - 322 - 323 321 = Rendu en images 324 322 325 323 Maintenant que l'on a cette structure, il est bien évidemment essentiel de pouvoir la rendre en un fichier image exploitable, en PNG par exemple. 326 324 327 - L'idée est d'exploiter le standard SVG et tout l'écosystème existant autour pour éviter d'avoir à ré-implémenter un moteur de rasterisation à la main: SVG possède déjà énormément de fonctionnalités, et faire ainsi nous permet de fournir un "escape hatch" et de fournir à Shapemaker des fragments de code SVG pour des cas spécifiques que la bibliothèque ne couvrirait pas, à travers `Object::RawSVG`, qui prend en argument un arbre SVG brut. 325 + L'idée est d'exploiter le standard SVG et tout l'écosystème existant autour pour éviter d'avoir à ré-implémenter un moteur de rastérisation à la main: SVG possède déjà énormément de fonctionnalités, et faire ainsi nous permet de fournir un "escape hatch" et de fournir à Shapemaker des fragments de code SVG pour des cas spécifiques que la bibliothèque ne couvrirait pas, à travers `Object::RawSVG`, qui prend en argument un arbre SVG brut. 328 326 329 327 Ce processus de rendu est réalisé via l'implémentation d'un trait, une sorte d'équivalent des interfaces dans les langages orientés objet @rusttraits: 330 328 ··· 339 337 340 338 Ce _trait_ est ensuite implémenté par la plupart des structures de `shapemaker::graphics`: 341 339 342 - / Canvas: rendu de toutes ses `Layer`, en prenant garde à les ordonner correctement pour que les premières couches soit déssinées par dessus les dernières 340 + / Canvas: rendu de toutes ses `Layer`, en prenant garde à les ordonner correctement pour que les premières couches soit dessinées par dessus les dernières 343 341 / Layer: rendu de l'ensemble des `ColoredObject` qu'elle contient, en les regroupant dans un groupe SVG #raw(lang: "svg", "<g>") 344 342 / ColoredObject: rendu de l'`Object` qu'il contient, en appliquant les transformations et filtres 345 343 / Object: dépend de la variante: `Object::Rectangle` est rendu comme un #raw(lang: "svg", "<rect>"), `Object::Circle` est rendu comme un #raw(lang: "svg", "<circle>"), etc. ··· 393 391 ), 394 392 ) 395 393 396 - En suite, pour convertir en PNG, on utilise une autre bibliothèque, _resvg_, qui implémente presque complétement la spécification SVG 1.1, et l'implémente même mieux que Firefox, Safari et Chrome @resvg. L'arbre SVG que l'on a construit est sérialisé en string, puis parsé par _resvg_, qui le transforme en un arbre de rendu, qui est ensuite rasterisé en une pixmap#footnote[Matrice plate de pixels RGBA], qui est finalement écrit dans un fichier PNG. 394 + En suite, pour convertir en PNG, on utilise une autre bibliothèque, _resvg_, qui implémente presque complètement la spécification SVG 1.1, et l'implémente même mieux que Firefox, Safari et Chrome @resvg. L'arbre SVG que l'on a construit est sérialisé en string, puis parsé par _resvg_, qui le transforme en un arbre de rendu, qui est ensuite rasterisé en une pixmap#footnote[Matrice plate de pixels RGBA], qui est finalement écrit dans un fichier PNG. 397 395 398 396 #diagram( 399 397 caption: [Rendu d'un canvas SVG en PNG], ··· 412 410 Le passage par une string svg est évidemment une perte de performance, qui est discutée #ref(<perf-svgstring>, form: "page") 413 411 414 412 415 - = Render loop et hooks 413 + = Render loop et hooks <crate::video> 416 414 417 - On peut maintenant rasteriser un canvas. Passer à l'étape vidéo donc à réaliser cette opération sur chaque _frame_ de la vidéo finale. Cependant, la vidéo devant se synchroniser au son, la tâche est rendu plus difficile: en effet, il ne suffit pas d'exposer à l'artiste une fonction `render_frame`, qui prendrait en argument le numéro de frame actuel et permettrait de définir le canvas pour chaque frame: on a besoin de moyen de _réagir_ à des moments clés de la musique. 415 + On peut maintenant rastériser un canvas. Passer à l'étape vidéo donc à réaliser cette opération sur chaque _frame_ de la vidéo finale. Cependant, la vidéo devant se synchroniser au son, la tâche est rendu plus difficile: en effet, il ne suffit pas d'exposer à l'artiste une fonction `render_frame`, qui prendrait en argument le numéro de frame actuel et permettrait de définir le canvas pour chaque frame: on a besoin de moyen de _réagir_ à des moments clés de la musique. 418 416 419 - Pour donner les moyens à l'artiste d'exprimer cela, on utilise un concept assez commun en programmation, les _hooks_, nommés ainsi car, essentiellement, ils permettent à du code utilisateur de s'imiscer dans certains moments de l'exécution d'une bibliothèque @hooks. 417 + Pour donner les moyens à l'artiste d'exprimer cela, on utilise un concept assez commun en programmation, les _hooks_, nommés ainsi car, essentiellement, ils permettent à du code utilisateur de s’immiscer dans certains moments de l'exécution d'une bibliothèque @hooks. 420 418 421 419 Dans notre cas, on va donner les hooks suivants: 422 420 ··· 450 448 451 449 Un hook reçoit notamment une référence mutable au Canvas #raw(lang: "rust", "&mut Canvas") car il _modifie le canvas de la frame en cours_. Le moteur de rendu vidéo ne possède en fait qu'un seul canvas, qui est successivement modifié au long de la vidéo. 452 450 453 - Le générique #raw(lang: "rust", "<C>") existe car l'artiste peut définir des données additionelles à stocker dans le contexte, pratique pour stocker des données à travers la vidéo, au delà de l'exécution d'un unique hook#footnote[Par exemple, "quelle a été la dernière ligne de parole affichée? il faut passer à la prochaine"] 451 + Le générique #raw(lang: "rust", "<C>") existe car l'artiste peut définir des données additionnelles à stocker dans le contexte, pratique pour stocker des données à travers la vidéo, au delà de l'exécution d'un unique hook#footnote[Par exemple, "quelle a été la dernière ligne de parole affichée? il faut passer à la prochaine"] 454 452 455 453 On met également à disposition une méthode `with_hook`, qui rajoute un hook à la liste, permettant de facilement les définir: 456 454 ··· 462 460 lang: "rust", 463 461 is_method: true, 464 462 transform: it => ( 465 - "impl Video<C> {\n ...\n" 466 - + it.replace("<AdditionalContext>", "<C>") 467 - + "\n}" 463 + "impl Video<C> {\n ...\n" + it.replace("<AdditionalContext>", "<C>") + "\n}" 468 464 ), 469 465 ), 470 466 ) ··· 479 475 lang: "rust", 480 476 is_method: true, 481 477 transform: it => ( 482 - "impl Video<C> {\n ...\n" 483 - + it.replace("<AdditionalContext>", "<C>") 484 - + "\n}" 478 + "impl Video<C> {\n ...\n" + it.replace("<AdditionalContext>", "<C>") + "\n}" 485 479 ), 486 480 ), 487 481 ) 488 482 489 - Le moteur de rendu vidéo est donc une boucle qui, à chaque frame, regarde dans l'ensemble des _hooks_ enregistrés, lesquels doivent être exécutés, les exécute, puis rasterise le canvas en une frame qui est ensuite donnée à l'encodeur vidéo: 483 + Le moteur de rendu vidéo est donc une boucle qui, à chaque frame, regarde dans l'ensemble des _hooks_ enregistrés, lesquels doivent être exécutés, les exécute, puis rastérise le canvas en une frame qui est ensuite donnée à l'encodeur vidéo: 490 484 491 485 #diagram( 492 486 caption: [Pipeline], ··· 530 524 ```, 531 525 ) 532 526 533 - La boucle de rendu en elle-même itère sur *les instants, ms par ms, et non pas les frames*. C'est important pour garder la vidéo en synchronisation avec le son. J'avais initialiement fait la boucle sur les frames, et la vidéo se décalait progressivement. 527 + La boucle de rendu en elle-même itère sur *les instants, ms par ms, et non pas les frames*. C'est important pour garder la vidéo en synchronisation avec le son. J'avais initialement fait la boucle sur les frames, et la vidéo se décalait progressivement. 534 528 535 529 #codesnippet(```rust 536 530 let render_ms_range = self.start_rendering_at..self.duration_ms(); ··· 547 541 #codesnippet( 548 542 dedent( 549 543 cut-around( 550 - it => it 551 - .trim() 552 - .starts-with("if context.frame != previous_rendered_frame"), 544 + it => it.trim().starts-with("if context.frame != previous_rendered_frame"), 553 545 it => it.trim().ends-with("}"), 554 546 read("../src/video/encoding.rs"), 555 547 ), ··· 559 551 La rastérisation est l'encodage sont réalisés après la fin de la boucle de rendu pour pouvoir paralléliser la rastérisation, voir #ref(<perf-parallelrasterize>). 560 552 561 553 562 - = Sources de synchronisation 554 + = Sources de synchronisation <crate::synchronization> 563 555 564 556 On a pu voir dans les exemples de code précédents que les hooks reçoivent deux arguments essentiels dans leur fonctions: le _canvas_, discuté précédemment, et un _contexte_. 565 557 ··· 578 570 Dans chacun de ces cas, l'objectif est de pouvoir inférer depuis ces ressources les informations suivantes: 579 571 580 572 - Le BPM#footnote[Beats per minute, aussi appelé tempo] du morceau, avec éventuellement des évolutions au cours du morceau 581 - - D'éventuels marqueurs temporels permettant de réagir à des changements de phrases musicales (par exemple, la classique construction _build-up_ / _drop_ / _break_ en EDM#footnote[Electronic Dance Music]), sans avoir à harcoder un timestamp dans le code de la vidéo: ces marqueurs sont placés dans le logiciel de production musicale (cf #ref(<flstudiomarkers>), #ref(<flstudiomarkers>, form: "page")) 573 + - D'éventuels marqueurs temporels permettant de réagir à des changements de phrases musicales (par exemple, la classique construction _build-up_ / _drop_ / _break_ en EDM#footnote[Electronic Dance Music]), sans avoir à coder en dur un timestamp dans le code de la vidéo: ces marqueurs sont placés dans le logiciel de production musicale (cf #ref(<flstudiomarkers>), #ref(<flstudiomarkers>, form: "page")) 582 574 - Pour chaque instrument, et à chaque instant: 583 575 - Les notes jouées: pitch#footnote[hauteur] et vélocité#footnote[intensité avec laquelle la note a été jouée] 584 576 - Des éventuelles évolutions de paramètres influant sur le timbre de l'instrument (ouverture d'un filtre passe bas pour un synthétiseur, pédale de sustain pour un piano, etc) ··· 641 633 642 634 …Sauf que les coordonnées temporelles MIDI sont en _deltas de ticks MIDI_. Les ticks sont indépendant du BPM, et les deltas sont des simples différences du nombre de ticks passés entre deux évènements. 643 635 644 - La durée d'un tick est aussi dépendante du _PPQ_, ou _Pulse per quarter_, qui correspond à la résolution temporellle d'un fichier MIDI, c'est l'équivalent des FPS en vidéos ou de la fréquence d'échantillonage en audio @midippq. 636 + La durée d'un tick est aussi dépendante du _PPQ_, ou _Pulse per quarter_, qui correspond à la résolution temporelle d'un fichier MIDI, c'est l'équivalent des FPS en vidéos ou de la fréquence d’échantillonnage en audio @midippq. 645 637 646 638 #codesnippet( 647 639 include-function( ··· 699 691 #imagefigure( 700 692 "./flstudiomidimacro.png", 701 693 [ 702 - Dialoge d'avertissement lors de l'utilisation de la macro "Prepare for MIDI export" dans FL Studio 694 + Dialogue d'avertissement lors de l'utilisation de la macro "Prepare for MIDI export" dans FL Studio 703 695 ], 704 696 ) 705 697 ··· 729 721 730 722 Étant donné l'aspect fastidieux de la solution précédente, il est intéressant de se pencher sur les fichiers de projet des logiciels de production musicale, afin de _remonter totalement à la source du morceau de musique_: le fichier qui est ouvert par l'artiste, celui sur lequel iel travaille. 731 723 732 - Malheureusement, les logiciel libres sont très loin derrière les standards de l'industrie en terme de production musicale, et il est ajourd'hui assez irréaliste de penser pouvoir produire de la musique avec des alternatives libres qui possède des formats de fichier de projet ouverts. 724 + Malheureusement, les logiciel libres sont très loin derrière les standards de l'industrie en terme de production musicale, et il est aujourd'hui assez irréaliste de penser pouvoir produire de la musique avec des alternatives libres qui possède des formats de fichier de projet ouverts. 733 725 734 - On doit donc se tourner vers de la rétro-ingénierie, et avoir une implémentation d'un "adapteur" pour chaque logiciel de production musicale que l'on souhaite supporter. 726 + On doit donc se tourner vers de la rétro-ingénierie, et avoir une implémentation d'un "adaptateur" pour chaque logiciel de production musicale que l'on souhaite supporter. 735 727 736 728 === FL Studio 737 729 ··· 746 738 ), 747 739 ) 748 740 749 - Cependant, l'auteur·ice de la bibliothèque n'a malheureusemnet plus le temps de la maintenir @pyflp3.12, et, étant donné l'évolution de FL Studio, le parser est voué à progressivement ne plus supporter les dernières versions du logiciel. 741 + Cependant, l'auteur·ice de la bibliothèque n'a malheureusement plus le temps de la maintenir @pyflp3.12, et, étant donné l'évolution de FL Studio, le parser est voué à progressivement ne plus supporter les dernières versions du logiciel. 750 742 751 743 Étant donné que je suis utilisatrice de FL Studio, je n'a pas cherché de potentielles solutions pour d'autres logiciels de MAO. 752 744 ··· 754 746 755 747 Étant donné que l'adapter est en Python, l'intégrer proprement dans Shapemaker consisterai à éventuellement utiliser une solution de FFI#footnote[Foreign Function Interface, permettant d'appeler des fonctions écrites dans un autre langage de programmation] comme PyOxide @pyo3, ce qui demanderait également beaucoup de travail d'adaptation. 756 748 757 - == Dépôt de "sondes" dans le logiciel de MAO 749 + == Dépôt de "sondes" dans le logiciel de MAO <crate::vst> 758 750 759 751 #grid( 760 752 columns: (3fr, 1fr), ··· 765 757 766 758 L'avantage de cette approche est qu'elle est agnostique au logiciel de MAO: en effet, VST est _le_ standard de plugins audio, supporté par tout les logiciels. 767 759 768 - C'est via cette technologie que les artistes peuvent jouer des instruments virtuels, allant des pianos physiquement simulés @pianoteq, en passant par vocaloïdes#footnote[simuateurs de parole chantée, cas à application musicale de la synthèse vocale] (comme par exemple Hatsune Miku @mikudayooo), aux synthétiseurs additifs, soustractifs, à wavetables (dont un exemple très populaire est Serum @serum). 760 + C'est via cette technologie que les artistes peuvent jouer des instruments virtuels, allant des pianos physiquement simulés @pianoteq, en passant par vocaloïdes#footnote[simulateurs de parole chantée, cas à application musicale de la synthèse vocale] (comme par exemple Hatsune Miku @mikudayooo), aux synthétiseurs additifs, soustractifs, à wavetables (dont un exemple très populaire est Serum @serum). 769 761 770 762 C'est aussi cette technologie qui est utilisée pour appliquer des effets aux signaux audio créés par les instruments (on parle de VST _effets_, contrairement aux VST _générateurs_), allant des modélisations de pédales d'effets de guitare ou de compresseurs analogiques à tube, aux simulation de compression digitale de signaux ("bitcrushing"), aux égaliseurs fréquentiels. 771 763 ··· 781 773 Il est donc possible de recevoir du signal, *autant audio que MIDI*, en entrée d'un VST. 782 774 ] 783 775 784 - Autre possibilité, qui s'avère utile parmis nos objectifs: les VSTs peuvent exposer à l'hôte (le logiciel de MAO) des paramètres changeables, ce qui permet de faire évoluer le timbre d'un instrument, l'intensité d'une réverbération, etc. Faire varier des paramètres au cours du temps est un aspect essentiel de la musique, en particulier électronique, qui contribue à "donner vie" à un morceau. 776 + Autre possibilité, qui s'avère utile parmi nos objectifs: les VSTs peuvent exposer à l'hôte (le logiciel de MAO) des paramètres changeables, ce qui permet de faire évoluer le timbre d'un instrument, l'intensité d'une réverbération, etc. Faire varier des paramètres au cours du temps est un aspect essentiel de la musique, en particulier électronique, qui contribue à "donner vie" à un morceau. 785 777 786 778 On peut donc également exposer des paramètres sur notre VST-sonde, qui peuvent servir à automatiser des changements de couleurs, de formes, etc, en suivant une évolution dans le timbre d'un instrument, par exemple, depuis la source directement (il suffit d'envoyer le signal d'automatisation au VST-sonde, en plus de l'instrument lui-même). 787 779 788 - On exfiltre ensuite ces données hors du logiciel vers un "beacon", via un simple API WebSocket, qui permet une communication instantanée beaucoup plus performante que des requêtes HTTP, et est plus approprié à l'envoie de potentiellement plusieurs miliers de points de données par secondes: en effet, le VST-sonde s'imiscant dans la chaîne de traitement audio, il ne doit pas la ralentir considérablement, sous peine de rendre le logiciel de MAO inutilisable 780 + On exfiltre ensuite ces données hors du logiciel vers un "beacon", via un simple API WebSocket, qui permet une communication instantanée beaucoup plus performante que des requêtes HTTP, et est plus approprié à l'envoie de potentiellement plusieurs milliers de points de données par secondes: en effet, le VST-sonde s’immisçant dans la chaîne de traitement audio, il ne doit pas la ralentir considérablement, sous peine de rendre le logiciel de MAO inutilisable 789 781 790 782 #codesnippet( 791 783 caption: "Implémentation de la fonction permettant à une probe de se signaler auprès du beacon", ··· 808 800 809 801 #diagram( 810 802 caption: [Exfiltration de données depuis la chaîne de traitement du logiciel de MAO], 811 - size: 80%, 803 + size: 75%, 812 804 ```dot 813 805 digraph G { 814 806 rankdir="LR"; 807 + // splines=ortho; 815 808 compound=true; 816 809 node[shape="record"]; 817 810 818 811 subgraph cluster_host { 819 812 label = "Logiciel de MAO" 820 813 821 - subgraph cluster_track { 822 - label = "Pour chaque piste" 823 - midi -> instrument -> effects -> probe 824 - midi -> probe 825 - automation -> instrument 826 - automation -> probe 814 + subgraph cluster_bass { 815 + label = "Bass" 816 + midi -> synth -> probe_1 817 + midi -> probe_1 818 + autom_in_bass [shape=point, label=""] 819 + autom_in_bass -> probe_1 820 + autom_in_bass -> synth 821 + 822 + probe_1[label="probe #1"] 823 + } 824 + subgraph cluster_drums { 825 + label = "Drums" 826 + midi_2 [label="midi"] 827 + midi_2 -> drums -> probe_2 828 + midi_2 -> probe_2 829 + autom_in_drums [shape=plaintext, label=""] 830 + 831 + probe_2[label="probe #2"] 827 832 } 833 + 834 + subgraph cluster_voice { 835 + label = "Voice" 836 + sampler -> effects -> probe_3 837 + autom_in_voice [shape=point, label=""] 838 + autom_in_voice -> probe_3 839 + autom_in_voice -> effects 840 + 841 + probe_2[label="probe #3"] 842 + } 843 + 844 + automation -> autom_in_bass [arrowhead=none] 845 + automation -> autom_in_voice [arrowhead=none] 846 + automation -> autom_in_drums [style=invis] 828 847 } 829 848 830 849 subgraph cluster_shapemaker { 831 850 label = "Shapemaker" 832 - wip[label="(en développement)", shape="plaintext"] 851 + wip[label="(en développement)", shape="plaintext"] 833 852 beacon -> wip 834 853 } 835 854 836 - probe -> beacon [label="ws://"] 855 + probe_1 -> beacon [label="ws://"] 856 + probe_2 -> beacon [label="ws://"] 857 + probe_3 -> beacon [label="ws://"] 837 858 838 859 } 839 860 ```, 840 861 ) 841 862 842 863 843 - == Temps réel: WASM et WebMIDI 864 + == Temps réel: WASM et WebMIDI <crate::wasm> 844 865 845 - Il est possible de réagir en temps réel à des pressions de touches sur des appareils conçus pour la production musicale assistée par ordinateur (MAO): des claviers, des potentiomères pour ajuster des réglages affectant le timbre d'un son, des pads pour déclencher des sons et, par exemple, jouer des percussions, etc. 866 + Il est possible de réagir en temps réel à des pressions de touches sur des appareils conçus pour la production musicale assistée par ordinateur (MAO): des claviers, des potentiomètres pour ajuster des réglages affectant le timbre d'un son, des pads pour déclencher des sons et, par exemple, jouer des percussions, etc. 846 867 847 868 Ces appareils sont appelés "contrôleurs MIDI", du protocole standard qui régit leur communication avec l'ordinateur. 848 869 849 - S'il est évidemment possible d'interagit avec ces contrôleurs depuis un programme natif (c'est après tout ce que font les logiciels de production musicale), j'ai préféré tenté l'approche Web, pour en faciliter l'accessibilité et en réduire le temps nécéssaire à la mise en place #footnote[ 850 - Imaginez, votre ordinateur a un problème 5 minutes avant le début d'une installation live, et vous aviez prévu d'utiliser Shapemaker pour des visuels. En faisant du dispostif un site web, il suffit de brancher son contrôleur à l'ordinateur d'un·e ami·e, et c'est tout bon. 870 + S'il est évidemment possible d'interagit avec ces contrôleurs depuis un programme natif (c'est après tout ce que font les logiciels de production musicale), j'ai préféré tenté l'approche Web, pour en faciliter l'accessibilité et en réduire le temps nécessaire à la mise en place #footnote[ 871 + Imaginez, votre ordinateur a un problème 5 minutes avant le début d'une installation live, et vous aviez prévu d'utiliser Shapemaker pour des visuels. En faisant du dispositif un site web, il suffit de brancher son contrôleur à l'ordinateur d'un·e ami·e, et c'est tout bon. 851 872 ]. 852 873 853 874 Comme pour de nombreuses autres technologies existant à la frontière entre le matériel et le logiciel, les navigateurs mettent à disposition des sites web une technologie permettant de communiquer avec les périphériques MIDI connectés à la machine: c'est l'API WebMIDI @webmidi. ··· 856 877 857 878 Il existe cependant un moyen de "faire tourner du code Rust" dans un navigateur Web: la compilation vers WebAssembly (WASM), un langage assembleur pour le web @wasm, qui est une cible de compilation pour quelques des langages compilés plus modernes, comme Go @gowasm or Rust @rustwasm 858 879 859 - En exportant la _crate_ shapemaker en bibliothèque Javascript via wasm-bindgen @wasmbindgen, il est donc possible d'exoser à une balise #raw("<script>", lang: "html") les fonctions de la bibliothèque, et brancher donc celles-ci à des _callbacks_ donnés par l'API WebMIDI: 880 + En exportant la _crate_ shapemaker en bibliothèque Javascript via wasm-bindgen @wasmbindgen, il est donc possible d’exposer à une balise #raw("<script>", lang: "html") les fonctions de la bibliothèque, et brancher donc celles-ci à des _callbacks_ donnés par l'API WebMIDI: 860 881 861 882 #figure( 862 883 caption: "Exposition de fonctions à WASM depuis Rust, et utilisation de celles-ci dans un script Javascript", ··· 906 927 ), 907 928 ) 908 929 909 - Au final, on peut arriver à une performance live interactive @pianowasmdemo intéréssante, et assez réactive pour ne pas avoir de latence (et donc de désynchronisation audio/vidéo) perceptible. 930 + Au final, on peut arriver à une performance live interactive @pianowasmdemo intéressante, et assez réactive pour ne pas avoir de latence (et donc de désynchronisation audio/vidéo) perceptible. 910 931 911 - Les navigateurs Web supportant nativement le format SVG, qui se décrit notamment comme incluable directement dans le code HTML d'une page web @svginhtml, il est possible de simplement générer le code SVG, et de laisser le navigateur faire le rendu, ce qui s'avère être une solution très performante. 932 + Les navigateurs Web supportant nativement le format SVG, qui se décrit notamment comme directement incluable dans le code HTML d'une page web @svginhtml, il est possible de simplement générer le code SVG, et de laisser le navigateur faire le rendu, ce qui s'avère être une solution très performante. 912 933 913 934 = Performance 914 935 ··· 957 978 ```, 958 979 ) 959 980 960 - L'inconvénient est que, pour la partie encoding vidéo, il n'existe pas encore vraiment d'encodeur H.264#footnote[Codec vidéo, très souvent utilisé pour les fichiers MP4, par exemple] en pur Rust, la plupart des solutions étant des bindings#footnote[bibliothèque utilisant des FFIs pour donner un accès idiomatique à une bibloithèque provenant d'un autre langage de programmation] vers des bibliothèques C, notamment ffmpeg. 981 + L'inconvénient est que, pour la partie encoding vidéo, il n'existe pas encore vraiment d'encodeur H.264#footnote[Codec vidéo, très souvent utilisé pour les fichiers MP4, par exemple] en pur Rust, la plupart des solutions étant des bindings#footnote[bibliothèque utilisant des FFIs pour donner un accès idiomatique à une bibliothèque provenant d'un autre langage de programmation] vers des bibliothèques C, notamment ffmpeg. 961 982 962 983 Cela rend l'installation de la bibliothèque beaucoup plus complexe, notamment sur Windows (les logiciels de production musicale sont très rares à fonctionner correctement sur Linux, surtout quand on prend en compte que les VSTs doivent eux aussi fonctionner sur Linux): 963 984 964 985 #codesnippet( 965 - caption: "Erreur recontrée pendant la compilation des bindings Rust à libx264", 986 + caption: "Erreur rencontrée pendant la compilation des bindings Rust à libx264", 966 987 ``` 967 988 Compiling ffmpeg-sys-next v7.1.0 968 989 error: failed to run custom build command for `ffmpeg-sys-next v7.1.0` ··· 982 1003 983 1004 #diagram( 984 1005 caption: [Détail de la boucle de rendu], 985 - scale(90%, reflow: true)[ 1006 + [ 986 1007 ```dot 987 1008 digraph G { 988 1009 compound=true; 989 - splines="ortho"; 990 - node[shape="record"]; 1010 + // Either of these makes edge labels disappear... 1011 + // splines="ortho"; 1012 + // node[shape="record"]; 991 1013 992 1014 hooks -> canvas; 993 1015 subgraph cluster_tosvg { ··· 998 1020 render_to_svg -> stringify_svg [label="0.1ms"] 999 1021 } 1000 1022 } 1023 + stringify_svg -> "svg string" [label="0.1ms"] 1001 1024 subgraph cluster_rasterize { 1002 1025 label = "Encode frame [167ms]" 1003 1026 subgraph g_rasterize { 1004 1027 rank=same; 1005 - stringify_svg -> "svg string" 1006 1028 "svg string" -> "usvg tree" [label="48ms"] 1007 1029 "usvg tree" -> pixmap [label="11ms"] 1008 1030 pixmap -> "hwc frame" [label="108ms"] 1009 1031 } 1010 1032 } 1011 1033 1012 - canvas -> "svg string" [weight=10, style=invis] 1034 + canvas -> "svg string" [weight=10, style=invis] 1013 1035 } 1014 1036 ``` 1015 1037 ], ··· 1063 1085 ``` 1064 1086 ] 1065 1087 1066 - Il est donc nécéssaire de convertir entre ces deux formats, ce qui est lent car demande de copier les données. 1088 + Il est donc nécessaire de convertir entre ces deux formats, ce qui est lent car demande de copier les données. 1089 + 1090 + La solution initiale utilisait `video_rs::Frame::from_shape_fn`: 1091 + 1092 + #codesnippet[ 1093 + ```rust 1094 + Ok(video_rs::Frame::from_shape_fn( 1095 + (pixmap.height() as usize, pixmap.width() as usize, 3), 1096 + |(y, x, c)| { 1097 + let pixel = pixmap 1098 + .pixel(x as u32, y as u32) 1099 + .expect(&format!("No pixel found at x, y = {x}, {y}")); 1100 + match c { 1101 + 0 => pixel.red(), 1102 + 1 => pixel.green(), 1103 + 2 => pixel.blue(), 1104 + _ => unreachable!(), 1105 + } 1106 + }, 1107 + )) 1108 + ``` 1109 + ] 1110 + 1111 + Cependant, cette solution est très lente car _non parallélisée_, je l'ai donc réimplémentée avec de la parallélisation sur chaque pixel: 1112 + 1113 + #codesnippet( 1114 + include-function( 1115 + "../src/video/encoding.rs", 1116 + "pixmap_to_hwc_frame", 1117 + lang: "rust", 1118 + is_method: true, 1119 + ), 1120 + ) 1067 1121 1068 - Une solution serait de passer à une bibiothèque plus bas niveau et voir s'il est possible de donner directement les données de pixmap à l'encodeur, sans conversion, ou tout du moins sans avoir à copier les données. 1122 + On effectue toujours de la copie, mais la conversion est nettement plus rapide ainsi. 1069 1123 1070 - Une autre solution est de faire proposer une contribution à la bibiothèque de rendu utilisée par _resvg_, _tiny_skia_#footnote[Tiny-skia est notamment utilisé par Typst @typsttinyskia @typsttinyskiacargotoml, l'alternative moderne à LaTeX sur laquelle ce papier a été typeset], pour pouvoir instrumentaliser les lectures et écritures à sa pixmap, et ainsi écrire dans la représentation voulue par libx264 directement. 1124 + Une solution serait de passer à une bibliothèque plus bas niveau et voir s'il est possible de donner directement les données de pixmap à l'encodeur, sans conversion, ou tout du moins sans avoir à copier les données. 1125 + 1126 + Une autre solution est de faire proposer une contribution à la bibliothèque de rendu utilisée par _resvg_, _tiny_skia_#footnote[Tiny-skia est notamment utilisé par Typst @typsttinyskia @typsttinyskiacargotoml, l'alternative moderne à LaTeX sur laquelle ce papier a été typeset], pour pouvoir instrumentaliser les lectures et écritures à sa pixmap, et ainsi écrire dans la représentation voulue par libx264 directement. 1071 1127 1072 1128 == SVG vers string vers SVG <perf-svgstring> 1073 1129 ··· 1075 1131 1076 1132 = Conclusion 1077 1133 1134 + Malgré les multiples solutions de synchronisation audio-vidéo testées, avec certaines s’avérant infructueuses, l'approche par VST-sondes semble prometteuse, et permettrait de remplir presque tout les objectifs fixés au début du #ref(<crate::synchronization>). 1135 + 1136 + L'approche WASM/WebMIDI explorée au #ref(<crate::wasm>) est une solution appropriée pour des installations live, qui mérite d'être d'avantage explorée, possiblement en vue de la création d'une solution de scripting pour VJing#footnote[Visual Jockeying, l'art de mixer des visuels en live, souvent en concert ou en boîte de nuit] 1137 + 1138 + == Pistes d'améliorations 1139 + 1140 + === Feedback loop 1141 + 1142 + Enfin, un des points les plus importants à améliorer reste la "feedback loop" _pendant la conception d'une procédure de génération_, qui reste extrêmement longue à cause de la lenteur de compilation de Rust, et du fait que, contrairement à un logiciel de montage vidéo, par exemple, on ne peut que re-rendre la vidéo en MP4 (même si l'on peut décider de rendre qu'une petite partie), ouvrir le fichier, et regarder le résultat. 1143 + 1144 + Une idée serait de, là aussi, utiliser le backend WASM/WebMIDI pour fournir une sorte de preview du code en temps réel: une interface simple permet de placer une tête de lecture à un instant, et montre la frame à cet instant, et se rafraîchit quand le code change. Avec éventuellement la possibilité de faire "play". 1145 + 1146 + Encore faut-il que la vitesse de recompilation de Rust le permette, même si ce serait à proiri possible tant que la crate utilisant Shapemaker (celle que l'artiste écrit) reste légère. 1147 + 1148 + === Un langage de scripting 1149 + 1150 + Rust étant un des langages de programmation les plus difficiles à utiliser, on pourrait éventuellement exposer l'API de Shapemaker à un langage de scripting plus léger, comme Lua par exemple, ce qui permettrait également de rendre le projet plus accessible. 1151 + 1152 + Cela permettrait éventuellement aussi d'améliorer la vitesse de compilation de la crate écrite par l'artiste, qui pourrait, si elle est trop faible, empêcher l'implémentation de la solution de feedback loop telle qu'évoquée plus tôt. Des projets comme Tauri embarque un système de HMR#footnote[Hot Module Replacement, permettant de recharger du code en temps réel sans recharger la page, technologie assez prévalente dans le développement web frontend], non pas pour leur bibliothèque Rust, mais pour les bindings JavaScript exposé aux utilisateur·ice·s de la bibliothèque @taurihmr. 1153 + 1154 + On pourrait même envisager afficher cette _preview_ dans le logiciel de MAO, en tant qu'un 2e VST, "Shapemaker Preview". Ceci demande d'implémenter encore un backend de rendu, autre que H.264 ou WASM, mais serait certainement la meilleure solution en terme d'UX#footnote[expérience utilisateur·ice] 1155 + 1156 + == Code source 1157 + 1158 + Le code source du projet est disponible en ligne sur Github: 1159 + 1160 + #align(center)[ 1161 + #link("https://github.com/gwennlbh/shapemaker")[gwennlbh/shapemaker] 1162 + ] 1163 + 1164 + Le répertoire `paper/` contient la source de ce papier, écrit en Typst 1165 + 1166 + == Exemples 1167 + 1168 + Le projet n'étant pas encore terminé, il n'a pas encore de clips musicaux publiés. Cependant, voici des liens vers quelques tests: 1169 + 1170 + - #link("https://youtu.be/3lx6VAz_UKM") 1171 + - #link("https://instagram.com/p/C62JfogoUt9") 1172 + 1173 + #bibliography("bibliography.yaml") 1078 1174 1079 1175 #show: arkheion-appendices 1080 1176 = Marqueurs dans un logiciel de MAO ··· 1089 1185 ], 1090 1186 ) <flstudiomarkers> 1091 1187 1092 - // Add bibliography and create Bibiliography section 1093 - // #bibliography("bibliography.yaml", style: "./ieee-with-locations.csl") 1094 - #bibliography("bibliography.yaml") 1188 + = Série "interprétation collective" 1 1189 + #grid( 1190 + columns: 6, 1191 + ..range(1, 31).map(it => image("./street/frames/" + str(it) + ".svg")) 1192 + )
+1 -1
src/README.md
··· 22 22 rendering --> video 23 23 synchronization --> video 24 24 rendering --> wasm 25 - rendering --> vst 25 + synchronization --> vst 26 26 ```