this repo has no description
3
fork

Configure Feed

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

๐Ÿ“ Include some code snippets in sections, start diagrams

+361 -126
+1
Justfile
··· 33 33 ./shapemaker examples shapeshed --resolution 1920 paper/shapeshed.svg 34 34 ./shapemaker examples colors-shed --resolution 1920 paper/colorshed.svg 35 35 ./shapemaker examples grid --resolution 1920 paper/grid.svg 36 + typstyle format-all paper 36 37 typst compile --root . paper/main.typ 37 38 38 39 readme:
-32
paper/include-function.typ
··· 1 - #let include-function = ( 2 - filepath, 3 - name, 4 - lang: "rust", 5 - transform: it => it 6 - ) => { 7 - let lines = read(filepath).split(regex("\r?\n")) 8 - let pattern = regex("^pub fn " + name) 9 - let function_start_index = lines.position( 10 - it => it.starts-with(pattern) 11 - ) 12 - 13 - if function_start_index == none { 14 - [ 15 - Woops! function #name not in #filepath .\_. 16 - Searched for a line beginning with #pattern in: 17 - 18 - #raw(lang: lang, lines.join("\n")) 19 - ] 20 - } else { 21 - let lines_from_function = lines.slice(function_start_index) 22 - raw( 23 - lang: lang, 24 - transform( 25 - lines_from_function.slice( 26 - 0, 27 - lines_from_function.position(it => it == "}") 28 - ).join("\n") + "\n}" 29 - ) 30 - ) 31 - } 32 - }
paper/main.pdf

This is a binary file and will not be displayed.

+251 -76
paper/main.typ
··· 1 1 #import "template.typ": arkheion, arkheion-appendices 2 - #import "include-function.typ": include-function 2 + #import "utils.typ": include-function, cut-around, cut-between 3 + 4 + #import "@preview/diagraph:0.3.2" 5 + #show raw.where(lang: "dot"): it => diagraph.render(it.text) 6 + #show raw.where(lang: "mermaid"): it => diagraph.render( 7 + it.text.replace("graph TD", "digraph {").replace("-->", "->") + "}", 8 + ) 9 + 10 + 11 + #let imagefigure(path, caption) = figure( 12 + image(path, width: 100%), 13 + caption: caption, 14 + ) 15 + 16 + #let diagram(caption: "", content) = figure( 17 + caption: caption, 18 + kind: image, 19 + content, 20 + ) 21 + 22 + #show link: underline 3 23 4 24 #show: arkheion.with( 5 25 title: "Shapemaker: Crรฉations audiovisuelles procรฉdurales musicalement synchrones", 6 26 authors: ( 7 - (name: "Gwenn Le Bihan", email: "gwenn.lebihan@etu.inp-n7.fr", affiliation: "ENSEEIHT"), 27 + ( 28 + name: "Gwenn Le Bihan", 29 + email: "gwenn.lebihan@etu.inp-n7.fr", 30 + affiliation: "ENSEEIHT", 31 + ), 8 32 ), 9 33 date: [#datetime.today().day() Mars 2025], 10 - keywords: ("audiovisuel", "procรฉdural", "SVG", "Rust", "WASM", "WebMIDI", "VST"), 34 + keywords: ( 35 + "audiovisuel", 36 + "procรฉdural", 37 + "SVG", 38 + "Rust", 39 + "WASM", 40 + "WebMIDI", 41 + "VST", 42 + ), 11 43 ) 12 44 13 - #let citeauthor(label) = cite(label, style: "chicago-author-date") 14 - #show cite: set cite(style: "ieee") 15 - #let imagefigure(path, caption) = figure(image(path, width: 100%), caption: caption) 16 - 17 - #show link: underline 18 45 19 46 #align(center, pad(y: 1.7em, image("./dna-analysis-machine.png", width: 100%))) 20 47 21 48 #include-function( 22 49 "../src/examples.rs", 23 50 "dna_analysis_machine", 24 - lang: "rust", 25 - transform: it => "use shapemaker::*\n\n" + it 51 + lang: "rust", 52 + transform: it => "use shapemaker::*\n\n" + it, 26 53 ) 27 54 28 55 #pagebreak() 29 56 30 57 #outline() 31 58 32 - = Introduction 59 + = Introduction 33 60 34 61 == ร€ la recherche d'une impossible รฉnumรฉration des formes 35 62 ··· 38 65 #grid( 39 66 columns: (1fr, 1.5fr), 40 67 gutter: 2em, 41 - imagefigure("./majus.png", [MAJUS #citeauthor(<vasarely-majus>)]), 68 + imagefigure("./majus.png", [MAJUS @vasarely-majus]), 42 69 [ 43 - Fascinรฉe depuis longtemps par les ล“uvres du plasticien et artiste Op-Art _Victor Vasarely_, j'ai รฉtรฉ saisie par une de ses pรฉriodes, la pรฉriode "Planetary Folklore", pendant laquelle il a expรฉrimentรฉ ร  travers plusieurs ล“uvres autour de l'idรฉe d'un alphabet universel employant des sรฉries combinaisons simples de formes et couleurs. D'apparence trรจs simple, ces combinaisons sont d'une maniรจres assez fascinantes uniques, d'oรน l'idรฉe d'alphabet @planetary-folklore-period. 70 + Fascinรฉe depuis longtemps par les ล“uvres du plasticien et artiste Op-Art _Victor Vasarely_, j'ai รฉtรฉ saisie par une de ses pรฉriodes, la pรฉriode "Planetary Folklore", pendant laquelle il a expรฉrimentรฉ ร  travers plusieurs ล“uvres autour de l'idรฉe d'un alphabet universel employant des sรฉries combinaisons simples de formes et couleurs. D'apparence trรจs simple, ces combinaisons sont d'une maniรจres assez fascinantes uniques, d'oรน l'idรฉe d'alphabet @planetary-folklore-period. 44 71 45 72 En particulier, un tableau, MAJUS, implรฉmente ร  la fois ce concept, et est รฉgalement une transcription d'une fugue de Bach. 46 - ] 73 + ], 47 74 ) 48 75 49 76 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. ··· 52 79 columns: (1fr, 1fr), 53 80 gutter: 1em, 54 81 imagefigure("./alphabetdesformes.png", "Un โ€œalphabetโ€ incomplet"), 55 - imagefigure("./alphabetdesformes.svg", "Une vectorisation") 82 + imagefigure("./alphabetdesformes.svg", "Une vectorisation"), 56 83 ) 57 84 58 85 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. ··· 63 90 64 91 == Une approche procรฉdurale ? 65 92 66 - #figure(caption: "Exemples d'ล“uvres rรฉsultant d'une procรฉdure de gรฉnรฉration semi-alรฉatoire, basรฉe sur une grille de 8 โ€œpoints d'ancragesโ€", grid( 67 - columns: (1fr, 1fr, 1fr), 68 - ..( "designing-a-font", 69 - "drone-operating-system", 70 - "HAL-9000", 71 - "japan-sledding-olympics", 72 - "lunatic-green-energy", 73 - // "measuring-spirits", 74 - "phone-cameras", 75 - "reflections", 76 - "spline-optimisation", 77 - "weaving").map(artwork => grid.cell(image("../examples/gallery/" + artwork + ".svg", width: 100%)) ) 78 - )) 93 + #figure( 94 + caption: "Exemples d'ล“uvres rรฉsultant d'une procรฉdure de gรฉnรฉration semi-alรฉatoire, basรฉe sur une grille de 8 โ€œpoints d'ancragesโ€", 95 + grid( 96 + columns: (1fr, 1fr, 1fr), 97 + ..( 98 + "designing-a-font", 99 + "drone-operating-system", 100 + "HAL-9000", 101 + "japan-sledding-olympics", 102 + "lunatic-green-energy", 103 + // "measuring-spirits", 104 + "phone-cameras", 105 + "reflections", 106 + "spline-optimisation", 107 + "weaving", 108 + ).map(artwork => grid.cell( 109 + image("../examples/gallery/" + artwork + ".svg", width: 100%), 110 + )) 111 + ), 112 + ) 79 113 80 114 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. 81 115 ··· 89 123 grid.cell(image("./grid.svg"), align: center), 90 124 grid.cell(image("./shapeshed.svg"), align: center), 91 125 grid.cell(image("./colorshed.svg"), align: center) 92 - ) 126 + ), 93 127 ) 94 128 95 129 L'idรฉe est donc de limiter la part d'alรฉatoire ร  des choix dans des ensembles prรฉdรฉfinis d'รฉlรฉments, que ce soit dans le choix des couleurs, des placements ou des formes รฉlรฉmentaires. ··· 105 139 stack( 106 140 image("./street/workshop.jpeg"), 107 141 // image("./street/stack.jpeg") 108 - ) 142 + ), 109 143 ) 110 144 111 145 Bien รฉvidemment, les dรฉcisions dans le processus crรฉatif ne s'arrรชtent pas au choix du vocabulaire visuel utilisรฉ par le processus de gรฉnรฉration. ··· 118 152 119 153 === Interprรฉtation collective 120 154 121 - 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. 155 + 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. 122 156 123 157 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. 124 158 ··· 131 165 caption: caption, 132 166 grid( 133 167 gutter: 0.5em, 134 - columns: 135 - if screenshot { 136 - (if with-context { 2fr } else { 1fr }, 3fr) 137 - } else { 138 - 1fr 139 - } 168 + columns: if screenshot { 169 + (if with-context { 2fr } else { 1fr }, 3fr) 170 + } else { 171 + 1fr 172 + } 140 173 , 141 174 if screenshot { 142 175 grid.cell(rowspan: 2, image("./street/" + slug + "-screenshot.png")) ··· 145 178 if with-context { 146 179 image("./street/" + slug + "-context.jpeg") 147 180 }, 148 - ) 181 + ), 149 182 ) 150 183 151 184 152 185 #work("paramount", ["Paramount"]) 153 186 #work("reflets-citadins", ["Reflets Citadins", nommรฉe par _Enide_]) 154 - #work("lenvolรฉe-du-cerf-volant", ["l'envolรฉe du Cerf-Volant", nommรฉe par _Nicolas C._]) 187 + #work( 188 + "lenvolรฉe-du-cerf-volant", 189 + ["l'envolรฉe du Cerf-Volant", nommรฉe par _Nicolas C._], 190 + ) 155 191 156 192 Certaines ont รฉtรฉ souvent renommรฉes, beaucoup ont รฉtรฉ volรฉes, et certaines restent encore inconquises. 157 193 ··· 164 200 caption: [Frames d'une _story_ Instagram montrant une premiรจre esquisse de vidรฉo], 165 201 stack( 166 202 dir: ltr, 167 - ..range(7).map(it => image("./blackmirrorlike/frame-" + str(it) + ".png", width: 14% )) 168 - ) 203 + ..range(7).map(it => image( 204 + "./blackmirrorlike/frame-" + str(it) + ".png", 205 + width: 14%, 206 + )), 207 + ), 169 208 ) 170 209 171 210 ร€ force de gรฉnรฉrer des centaines de petites images gรฉomรฉtriques, il m'est venu ร  l'idรฉe de les transformer en frames d'une _vidรฉo_. ··· 176 215 177 216 = Une _crate_ Rust avec un API sympathique 178 217 218 + #diagram( 219 + caption: [Pipeline], 220 + [ 221 + ```dot 222 + digraph G { 223 + // rankdir="LR"; 224 + compound=true; 225 + node[shape="record"]; 226 + 227 + subgraph cluster_0 { 228 + graph[style="filled", color="#f0f0f0"]; 229 + label = "Render loop"; 230 + "next frame" -> hooks -> canvas -> "render to SVG" -> rasterize -> "next frame" 231 + } 232 + 233 + syncdata[label=<sync data <br/> <font color='blue'>kick notes</font>>]; 234 + 235 + audioin[label=<stems .wav + BPM <br/> <font color='blue'>kick.wav, 120 BPM</font>>] 236 + midi[label=<MIDI <br/> <font color='blue'>project.midi</font>>] 237 + flp[label=<Project file <br/> <font color='blue'>project.flp</font>>] 238 + 239 + midi -> syncdata 240 + audioin -> syncdata 241 + flp -> syncdata 242 + 243 + syncdata -> "next frame" 244 + 245 + usercode[label=<user code<br/><font color='blue'>quand le kick tape, <br />affiche un point rouge</font>>]; 246 + usercode -> hooks 247 + 248 + "rasterize" -> "video encoder" 249 + syncdata -> audio -> "video encoder" 250 + } 251 + ``` 252 + ] 253 + ) 254 + 255 + #diagram( 256 + caption: [Organisation des sous-modules], 257 + raw( 258 + lang: "mermaid", 259 + cut-between( 260 + it => it == "```mermaid", 261 + it => it == "```", 262 + read("../src/README.md"), 263 + ), 264 + ), 265 + ) 266 + 267 + 179 268 = Render loop et hooks 180 269 181 270 = Sources de synchronisation ··· 201 290 #figure( 202 291 caption: "Exposition de fonctions ร  WASM depuis Rust, et utilisation de celles-ci dans un script Javascript", 203 292 grid( 204 - columns: (1fr, 1fr), 205 - gutter: 2em, 206 - text(size: 0.8em, [ 207 - ```rust 208 - #[wasm_bindgen] 209 - pub fn render_image(opacity: f32, color: Color) -> Result<(), JsValue> { 210 - let mut canvas = /* ... */ 293 + columns: (1fr, 1fr), 294 + gutter: 2em, 295 + text( 296 + size: 0.75em, 297 + [ 298 + ```rust 299 + #[wasm_bindgen] 300 + pub fn render_image(opacity: f32, color: Color) -> Result<(), JsValue> { 301 + let mut canvas = /* ... */ 211 302 212 - *WEB_CANVAS.lock().unwrap() = canvas; 213 - render_canvas_at(String::from("body")); 303 + *WEB_CANVAS.lock().unwrap() = canvas; 304 + render_canvas_at(String::from("body")); 214 305 215 - Ok(()) 216 - } 217 - ``` 218 - ]), 219 - text(size: 0.8em, [ 220 - ```js 221 - import init, { render_image } from "./shapemaker.js" 306 + Ok(()) 307 + } 308 + ``` 309 + ], 310 + ), 311 + text( 312 + size: 0.75em, 313 + [ 314 + ```js 315 + import init, { render_image } from "./shapemaker.js" 222 316 223 - void init() 317 + void init() 224 318 225 - navigator.requestMIDIAccess().then((midi) => { 226 - Array.from(midi.inputs).forEach((input) => { 227 - input[1].onmidimessage = (msg) => { 228 - const [cmd, ...args] = [...msg.data] 229 - if (cmd !== 144) return 319 + navigator.requestMIDIAccess().then((midi) => { 320 + Array.from(midi.inputs).forEach((input) => { 321 + input[1].onmidimessage = (msg) => { 322 + const [cmd, ...args] = [...msg.data] 323 + if (cmd !== 144) return 230 324 231 - const [pitch, velocity] = args 232 - const octave = Math.floor(pitch / 12) - 1 325 + const [pitch, velocity] = args 326 + const octave = Math.floor(pitch / 12) - 1 233 327 234 - if (velocity === 0) { 235 - fadeOutElement(frameElement(color)) 236 - } else { 237 - render_image(velocity / 128, octave) 238 - } 239 - } 240 - }) 241 - }) 242 - ``` 243 - ]) 244 - )) 328 + render_image(velocity / 128, colors[octave]) 329 + } 330 + }) 331 + }) 332 + ``` 333 + ], 334 + ), 335 + ), 336 + ) 245 337 246 338 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. 247 339 ··· 249 341 250 342 == Amplitudes de _stems_ 251 343 344 + ```rs 345 + let mut reader = hound::WavReader::open(path.clone()) 346 + .map_err(|e| format!("Failed to read stem file: {}", e)) 347 + .unwrap(); 348 + 349 + let spec = reader.spec(); 350 + 351 + let sample_index_to_frame = |sample: usize| { 352 + (sample / spec.channels / spec.sample_rate * self.fps) as usize 353 + }; 354 + 355 + let mut amplitude_db: Vec<f32> = vec![]; 356 + let mut current_amplitude_sum: f32 = 0.0; 357 + let mut current_amplitude_buffer_size: usize = 0; 358 + let mut latest_loaded_frame = 0; 359 + 360 + for (i, sample) in reader.samples::<i16>().enumerate() { 361 + let sample = sample.unwrap(); 362 + if sample_index_to_frame(i) > latest_loaded_frame { 363 + amplitude_db 364 + .push(current_amplitude_sum / current_amplitude_buffer_size as f32); 365 + current_amplitude_sum = 0.0; 366 + current_amplitude_buffer_size = 0; 367 + latest_loaded_frame = sample_index_to_frame(i); 368 + } else { 369 + current_amplitude_sum += sample.abs() as f32; 370 + current_amplitude_buffer_size += 1; 371 + } 372 + } 373 + 374 + let stem = Stem { 375 + amplitude_max: *amplitude_db.iter().max().unwrap(), 376 + amplitude_db, 377 + duration_ms: (reader.duration() / spec.sample_rate * 1000.0) as usize, 378 + }; 379 + 380 + // Write loaded stem to a CBOR cache file 381 + Stem::save_to_cbor(&stem, &cached_stem_path); 382 + ``` 383 + 252 384 == Export MIDI 253 385 386 + #raw( 387 + lang: "rust", 388 + cut-around( 389 + it => it.trim().starts-with("// Add notes"), 390 + it => it == " }", 391 + read("../src/synchronization/midi.rs"), 392 + ), 393 + ) 394 + 395 + ``` 396 + Commit 7ae7a14a90f16f664edee3f433ade9b8c5019ffa 397 + 398 + โš—๏ธ Figure out a POC to get notes from MIDI file into note[ms][stem_name] 399 + 400 + And the conversion from MIDI ticks to milliseconds does not drift at 401 + all, after 6 mins on a real-world track (see research_midi/source.mid), 402 + it's still fucking _spot on_, to the FUCKING CENTISECOND (FL Studio 403 + can't show me more precision anyways). 404 + 405 + So beautiful. 406 + 407 + aight, imma go to sleep now 408 + ``` 409 + 254 410 == Fichier de projet 255 411 412 + #include-function( 413 + "../research/adapters/flstudio/adapter.py", 414 + "main", 415 + lang: "python", 416 + ) 417 + 256 418 == Dรฉpรดt de "sondes" dans le logiciel de MAO 257 419 420 + #include-function( 421 + "../src/vst/beacon.rs", 422 + "connect_to_beacon", 423 + lang: "rust", 424 + ) 425 + 426 + #include-function( 427 + "../src/vst/beacon.rs", 428 + "register_probe", 429 + lang: "rust", 430 + ) 431 + 258 432 = Performance 259 433 260 434 = Conclusion 261 435 262 436 263 437 // Add bibliography and create Bibiliography section 438 + // #bibliography("bibliography.yaml", style: "./ieee-with-locations.csl") 264 439 #bibliography("bibliography.yaml")
+16 -18
paper/template.typ
··· 16 16 numbering: "1", 17 17 number-align: center, 18 18 ) 19 - show raw: set text(size: 0.85em, font: "Martian Mono", weight: "bold") 19 + show raw: set text(size: 0.85em, font: "Martian Mono", weight: "bold") 20 20 set text(font: "New Computer Modern", lang: "fr") 21 21 show math.equation: set text(weight: 400) 22 22 show math.equation: set block(spacing: 0.65em) ··· 30 30 if it.level == 1 { 31 31 pad( 32 32 bottom: 10pt, 33 - it 33 + it, 34 34 ) 35 - } 36 - else if it.level == 2 { 35 + } else if it.level == 2 { 37 36 pad( 38 37 bottom: 8pt, 39 - it 38 + it, 40 39 ) 41 - } 42 - else if it.level > 3 { 40 + } else if it.level > 3 { 43 41 text(11pt, weight: "bold", it.body + " ") 44 42 } else { 45 43 it ··· 51 49 top: 1em, 52 50 align(center)[ 53 51 #image(logo, width: 80%) 54 - ] 52 + ], 55 53 ) 56 54 } 57 55 ··· 65 63 align(center)[ 66 64 #block(text(weight: 500, 1.75em, title)) 67 65 #v(1em, weak: true) 68 - ] 66 + ], 69 67 ) 70 68 if logo == none { 71 69 line(length: 100%, stroke: 2pt) ··· 84 82 ..authors.map(author => align(center)[ 85 83 #if author.keys().contains("orcid") { 86 84 link("http://orcid.org/" + author.orcid)[ 87 - #pad(bottom: -8pt, 85 + #pad( 86 + bottom: -8pt, 88 87 grid( 89 88 columns: (8pt, auto, 8pt), 90 89 rows: 10pt, ··· 92 91 [*#author.name*], 93 92 [ 94 93 #pad(left: 4pt, top: -4pt, image("orcid.svg", width: 8pt)) 95 - ] 96 - ) 94 + ], 95 + ), 97 96 ) 98 97 ] 99 98 } else { 100 99 grid( 101 - columns: (auto), 100 + columns: auto, 102 101 rows: 2pt, 103 102 [*#author.name*], 104 103 ) ··· 134 133 135 134 // Keywords 136 135 if keywords.len() > 0 { 137 - [*_Mots clรฉs_* #h(0.3cm)] + keywords.map(str).join(" ยท ") 136 + [*_Mots clรฉs_* #h(0.3cm)] + keywords.map(str).join(" ยท ") 138 137 } 139 138 // Main body. 140 139 set par(justify: true) ··· 153 152 let value = "ABCDEFGHIJ".at(vals.at(0) - 1) 154 153 if vals.len() == 1 { 155 154 return "APPENDIX " + value 156 - } 157 - else { 155 + } else { 158 156 return value + "." + nums.pos().slice(1).map(str).join(".") 159 157 } 160 - } 161 - ); 158 + }, 159 + ) 162 160 [#pagebreak() #body] 163 161 }
+92
paper/utils.typ
··· 1 + #let cut-lines = ( 2 + starts, 3 + ends, 4 + content, 5 + keep_delimiting: false, 6 + ) => { 7 + let lines = content.split(regex("\r?\n")) 8 + let predicate = pred => if type(pred) == str { 9 + it => it.trim() == str 10 + } else if type(pred) == function { 11 + pred 12 + } else if type(pred) == regex { 13 + it => it.find(pred) != none 14 + } else { 15 + panic("cut-between predicates must be strings or functions") 16 + } 17 + let start_index = lines.position(predicate(starts)) 18 + 19 + if start_index == none { 20 + none 21 + } else { 22 + let lines_from_start = lines.slice(if keep_delimiting { 23 + start_index 24 + } else { 25 + calc.max(start_index + 1, 0) 26 + }) 27 + 28 + lines_from_start 29 + .slice( 30 + 0, 31 + lines_from_start.position(predicate(ends)) 32 + + if keep_delimiting { 1 } else { 0 }, 33 + ) 34 + .join("\n") 35 + } 36 + } 37 + 38 + #let cut-between = (starts, ends, content) => cut-lines( 39 + starts, 40 + ends, 41 + content, 42 + keep_delimiting: false, 43 + ) 44 + #let cut-around = (starts, ends, content) => cut-lines( 45 + starts, 46 + ends, 47 + content, 48 + keep_delimiting: true, 49 + ) 50 + 51 + 52 + #let include-function = ( 53 + filepath, 54 + name, 55 + lang: none, 56 + transform: it => it, 57 + ) => { 58 + let start_pattern = if lang == "rust" { 59 + regex("^pub fn " + name) 60 + } else if lang == "python" { 61 + regex("^def " + name) 62 + } else if lang == none { 63 + panic("specify a source language") 64 + } else { 65 + panic(lang + " is not supported for now. Use cut-between directly.") 66 + } 67 + 68 + let end_pattern = if lang == "rust" { 69 + regex("^\}") 70 + } else if lang == "python" { 71 + regex("^# end") // TODO pass next line to cut-between 72 + } else { 73 + none 74 + } 75 + 76 + let contents = cut-around( 77 + start_pattern, 78 + end_pattern, 79 + read(filepath), 80 + ) 81 + 82 + if contents == none { 83 + [ 84 + Woops! function #name not in #filepath .\_. 85 + Searched for a line beginning with #start_pattern in: 86 + 87 + #raw(lang: lang, read(filepath)) 88 + ] 89 + } else { 90 + raw(lang: lang, transform(contents)) 91 + } 92 + }
+1
research/adapters/flstudio/adapter.py
··· 116 116 117 117 Path(args["<output_json_file>"]).write_text(json.dumps(out, indent=4)) 118 118 119 + # end 119 120 120 121 if __name__ == "__main__": 121 122 main()