this repo has no description
3
fork

Configure Feed

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

:memo: finish except thanks

+210 -133
+163 -103
paper/main.typ
··· 8 8 ) 9 9 10 10 11 - #let imagefigure(path, caption) = figure( 12 - image(path, width: 100%), 11 + #let imagefigure(path, caption, size: 100%) = figure( 12 + image(path, width: size), 13 13 caption: caption, 14 14 ) 15 15 ··· 128 128 == Une approche procédurale ? 129 129 130 130 #figure( 131 - 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”", 131 + caption: "Exemples d'œuvres résultant d'une procédure de génération semi-aléatoire", 132 132 grid( 133 - columns: (1fr, 1fr, 1fr), 133 + columns: (1fr, 1fr, 1fr, 1fr, 1fr), 134 134 ..( 135 135 "designing-a-font", 136 136 "drone-operating-system", 137 137 "HAL-9000", 138 138 "japan-sledding-olympics", 139 139 "lunatic-green-energy", 140 - // "measuring-spirits", 140 + "measuring-spirits", 141 141 "phone-cameras", 142 142 "reflections", 143 143 "spline-optimisation", ··· 196 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-delà de Paris, allant même jusqu'à l'ISS @spaceinvadersiss). 197 197 198 198 199 - #let work = (slug, caption, with-context: false, screenshot: true) => figure( 199 + #let work = (slug, caption, with-context: false, only-context: false, screenshot: true) => figure( 200 200 caption: caption, 201 201 grid( 202 202 gutter: 0.5em, 203 203 columns: if screenshot { 204 - (if with-context { 2fr } else { 1fr }, 3fr) 204 + (if with-context and not only-context { 2fr } else { 1fr }, 3fr) 205 205 } else { 206 206 1fr 207 207 } ··· 209 209 if screenshot { 210 210 grid.cell(rowspan: 2, image("./street/" + slug + "-screenshot.png")) 211 211 }, 212 - image("./street/" + slug + ".jpeg"), 213 - if with-context { 212 + if not only-context { 213 + image("./street/" + slug + ".jpeg") 214 + }, 215 + if with-context or only-context { 214 216 image("./street/" + slug + "-context.jpeg") 215 217 }, 216 218 ), ··· 219 221 220 222 #work("reflets-citadins", ["Reflets Citadins", nommée par _Enide_]) 221 223 #work("paramount", ["Paramount"]) 222 - // #work( 223 - // "lenvolée-du-cerf-volant", 224 - // ["l'envolée du Cerf-Volant", nommée par _Nicolas C._], 225 - // ) 224 + #work( 225 + "lenvolée-du-cerf-volant", 226 + ["l'envolée du Cerf-Volant", nommée par _Nicolas C._], 227 + ) 226 228 227 229 Certaines ont été souvent renommées, beaucoup ont été volées, et certaines restent encore inconquises. 228 230 229 231 #work("danse-le-ciel", ["Danse le ciel"], with-context: true) 230 - #work("bridging", [_Sans titre_], with-context: true) 232 + #work("bridging", [_Sans titre_], only-context: true) 231 233 232 234 == Lien musical 233 235 ··· 257 259 258 260 La création d'un procédé de génération est conceptualisée par un canvas, composé de une ou plusieurs couches ou _layers_ d'objets. Ces objets sont _colorés_ (possèdent une information sur la manière dont il faut les remplir: bleu solide, hachures cyan, etc.), et peuvent également subir des filtres et transformations #footnote[Avec un peu de recul, le terme d'objet texturé est plus approprié, mais le code n'a pas encore changé]. Ils sont aussi _placés_ dans l'espace du canvas: le canvas possède une information de _région_, un intervalle 2D de points valables. Les objets se placent dans cette région, en stockant dans leur structure les coordonnées de _points_ marquant leur positionnement dans l'espace (coins pour un #raw(lang: "rust", "Object::Rectangle")) 259 261 262 + 260 263 #diagram( 261 264 caption: [Modèle objet du Canvas], 262 - size: 90%, 265 + size: 70%, 263 266 ```dot 264 267 digraph { 265 268 // rankdir="LR"; ··· 691 694 Malheureusement, là où l'export d'un projet musical en stems se résume à un simple clic dans un menu, l'export en MIDI est souvent plus complexe. Par exemple, sur FL Studio, il demande à créer _une copie du projet, avec toutes les pistes converties en "instruments MIDI"_, ce qui est fastidieux: 692 695 693 696 #imagefigure( 697 + size: 80%, 694 698 "./flstudiomidimacro.png", 695 699 [ 696 700 Dialogue d'avertissement lors de l'utilisation de la macro "Prepare for MIDI export" dans FL Studio ··· 732 736 Il existe une bibliothèque Python, pyflp @pyflp, qui permet de parser les fichiers de projets FL Studio, et d'en extraire la quasi totalité. 733 737 734 738 #codesnippet( 739 + size: 0.9em, 735 740 include-function( 736 741 "../research/adapters/flstudio/adapter.py", 737 742 "main", 738 743 lang: "python", 739 - transform: it => "import pyflp\n\n" + it.replace("\n# end", ""), 744 + transform: it => "import pyflp\n\n" + it.replace("\n\n# end", ""), 740 745 ), 741 746 ) 742 747 ··· 803 808 #diagram( 804 809 caption: [Exfiltration de données depuis la chaîne de traitement du logiciel de MAO], 805 810 size: 75%, 806 - ```dot 807 - digraph G { 808 - rankdir="LR"; 809 - // splines=ortho; 810 - compound=true; 811 - node[shape="record"]; 811 + [ 812 + ```dot 813 + digraph G { 814 + rankdir="LR"; 815 + // splines=ortho; 816 + compound=true; 817 + node[shape="record"]; 818 + 819 + subgraph cluster_host { 820 + label = "Logiciel de MAO" 821 + 822 + subgraph cluster_bass { 823 + label = "Bass" 824 + midi -> synth [style=dashed] 825 + synth -> probe_1 826 + midi -> probe_1 [style=dashed] 827 + autom_in_bass [shape=point, style=invis, label=""] 828 + autom_in_bass -> probe_1 [style=dotted] 829 + autom_in_bass -> synth [style=dotted] 830 + 831 + probe_1[label="probe #1"] 832 + } 833 + subgraph cluster_drums { 834 + label = "Drums" 835 + midi_2 [label="midi"] 836 + midi_2 -> drums [style=dashed] 837 + drums -> probe_2 838 + midi_2 -> probe_2 [style=dashed] 839 + autom_in_drums [shape=plaintext, label=""] 812 840 813 - subgraph cluster_host { 814 - label = "Logiciel de MAO" 841 + probe_2[label="probe #2"] 842 + } 815 843 816 - subgraph cluster_bass { 817 - label = "Bass" 818 - midi -> synth -> probe_1 819 - midi -> probe_1 820 - autom_in_bass [shape=point, label=""] 821 - autom_in_bass -> probe_1 822 - autom_in_bass -> synth 844 + subgraph cluster_voice { 845 + label = "Voice" 846 + sampler -> effects -> probe_3 847 + autom_in_voice [shape=point, style=invis, label=""] 848 + autom_in_voice -> probe_3 [style=dotted] 849 + autom_in_voice -> effects [style=dotted] 823 850 824 - probe_1[label="probe #1"] 825 - } 826 - subgraph cluster_drums { 827 - label = "Drums" 828 - midi_2 [label="midi"] 829 - midi_2 -> drums -> probe_2 830 - midi_2 -> probe_2 831 - autom_in_drums [shape=plaintext, label=""] 851 + probe_2[label="probe #3"] 852 + } 832 853 833 - probe_2[label="probe #2"] 854 + automation -> autom_in_bass [arrowhead=none, style=dotted] 855 + automation -> autom_in_voice [arrowhead=none, style=dotted] 856 + automation -> autom_in_drums [style=invis] 834 857 } 835 858 836 - subgraph cluster_voice { 837 - label = "Voice" 838 - sampler -> effects -> probe_3 839 - autom_in_voice [shape=point, label=""] 840 - autom_in_voice -> probe_3 841 - autom_in_voice -> effects 842 - 843 - probe_2[label="probe #3"] 859 + subgraph cluster_shapemaker { 860 + label = "Shapemaker" 861 + wip[label="(en développement)", shape="plaintext"] 862 + beacon -> wip 844 863 } 845 864 846 - automation -> autom_in_bass [arrowhead=none] 847 - automation -> autom_in_voice [arrowhead=none] 848 - automation -> autom_in_drums [style=invis] 849 - } 865 + probe_1 -> beacon [label="ws://", color=darkblue] 866 + probe_2 -> beacon [label="ws://", color=darkblue] 867 + probe_3 -> beacon [label="ws://", color=darkblue] 850 868 851 - subgraph cluster_shapemaker { 852 - label = "Shapemaker" 853 - wip[label="(en développement)", shape="plaintext"] 854 - beacon -> wip 855 869 } 870 + ``` 856 871 857 - probe_1 -> beacon [label="ws://"] 858 - probe_2 -> beacon [label="ws://"] 859 - probe_3 -> beacon [label="ws://"] 872 + #place( 873 + dy: -7em, 874 + dx: 35em, 875 + ```dot 876 + digraph { 877 + rankdir=LR; 878 + // splines=ortho; 879 + label = "Légende" 880 + node[style=invis,shape=point,label=""] 881 + a1 -> b1 [style=dotted, label="Automation"] 882 + a2 -> b2 [style=dashed, label="Notes"] 883 + } 884 + ```, 885 + ) 860 886 861 - } 862 - ```, 887 + #place( 888 + dy: -7em, 889 + dx: 47em, 890 + ```dot 891 + digraph { 892 + rankdir=LR; 893 + // splines=ortho; 894 + label = "Légende" 895 + node[style=invis,shape=point,label=""] 896 + a3 -> b3 [style=solid, label="Audio"] 897 + a4 -> b4 [color=darkblue, label="Syncdata"] 898 + } 899 + ```, 900 + ) 901 + ], 863 902 ) 864 903 865 904 ··· 971 1010 label = "Rust" 972 1011 subgraph cluster_each_frame { 973 1012 label = "Chaque frame" 974 - canvas -> "Frame 0037.svg" 975 - "Frame 0037.svg" -> "Frame 0037.png" [label="resvg"] 1013 + canvas -> "SVG string" 1014 + "SVG string" -> "Pixmap" [label="resvg"] 976 1015 } 977 - "Frame 0037.png" -> "video.mp4" [label="libx264"] 1016 + Pixmap -> "video.mp4" [label="libx264"] 978 1017 } 979 1018 } 980 1019 ```, ··· 1003 1042 Une fois cette optimisation faite, qui a *divisé par 10* le temps de rendu, on peut se pencher sur le détail de la boucle de rendu pour identifier les potentiels gains de performance 1004 1043 1005 1044 1006 - #diagram( 1007 - caption: [Détail de la boucle de rendu], 1008 - [ 1009 - ```dot 1010 - digraph G { 1011 - compound=true; 1012 - // Either of these makes edge labels disappear... 1013 - // splines="ortho"; 1014 - // node[shape="record"]; 1045 + #grid( 1046 + columns: (1.3fr, 1.1fr), 1047 + gutter: 1em, 1048 + diagram( 1049 + size: 73%, 1050 + caption: [Détail de la boucle de rendu], 1051 + [ 1052 + ```dot 1053 + digraph G { 1054 + compound=true; 1055 + // Either of these makes edge labels disappear... 1056 + // splines="ortho"; 1057 + // node[shape="record"]; 1015 1058 1016 - hooks -> canvas; 1017 - subgraph cluster_tosvg { 1018 - label = "SVG string rendering [0.2ms]" 1019 - subgraph g_svg { 1020 - rank=same; 1021 - canvas -> render_to_svg [label="0.1ms"] 1022 - render_to_svg -> stringify_svg [label="0.1ms"] 1059 + hooks -> canvas; 1060 + subgraph cluster_tosvg { 1061 + label = "SVG string rendering [0.2ms]" 1062 + subgraph g_svg { 1063 + rank=same; 1064 + canvas -> render_to_svg [label="0.1ms"] 1065 + render_to_svg -> stringify_svg [label="0.1ms"] 1066 + } 1023 1067 } 1024 - } 1025 - stringify_svg -> "svg string" [label="0.1ms"] 1026 - subgraph cluster_rasterize { 1027 - label = "Encode frame [167ms]" 1028 - subgraph g_rasterize { 1029 - rank=same; 1030 - "svg string" -> "usvg tree" [label="48ms"] 1031 - "usvg tree" -> pixmap [label="11ms"] 1032 - pixmap -> "hwc frame" [label="108ms"] 1068 + stringify_svg -> "svg" [label="0.1ms"] 1069 + subgraph cluster_rasterize { 1070 + label = "Encode frame [167ms]" 1071 + subgraph g_rasterize { 1072 + rank=same; 1073 + svg [label="svg\n(str)"] 1074 + usvg [label="usvg\n(tree)"] 1075 + "svg" -> "usvg" [label="48ms"] 1076 + } 1077 + subgraph g_rasterize2 { 1078 + rank=same; 1079 + "usvg" -> pixmap [label="11ms"] 1080 + pixmap -> "hwc" [label="108ms"] 1081 + } 1033 1082 } 1083 + 1084 + canvas -> "svg" [weight=10, style=invis] 1034 1085 } 1035 - 1036 - canvas -> "svg string" [weight=10, style=invis] 1037 - } 1038 - ``` 1039 - ], 1040 - ) 1041 - 1042 - #figure( 1043 - caption: "Durées d'exécution par tâche, pour une vidéo de test de 5 secondes", 1044 - table( 1045 - columns: 3, 1046 - inset: 0.75em, 1047 - [*Tâche*], [*Durée [ms]*], [*\#*], 1048 - ..csv("../results.csv").slice(1).flatten() 1086 + ``` 1087 + ], 1088 + ), 1089 + figure( 1090 + caption: "Durées d'exécution par tâche, pour une vidéo de test de 5 secondes (millisecondes)", 1091 + table( 1092 + columns: 3, 1093 + inset: 0.5em, 1094 + [*Tâche*], [*$Delta t$*], [*\#*], 1095 + ..csv("../results.csv").slice(1).flatten() 1096 + ), 1049 1097 ), 1050 1098 ) 1051 1099 ··· 1122 1170 ) 1123 1171 1124 1172 On effectue toujours de la copie, mais la conversion est nettement plus rapide ainsi. 1173 + 1174 + Bien évidemment, il ne faut pas faire d'erreur dans les calculs des coordonnées des pixels, ce qui peut donner des résultats surprenants, et éventuellement artistiquement intéréssants: 1175 + 1176 + #grid( 1177 + columns: (1fr, 1fr), 1178 + imagefigure("./hwccorrect.png", [Frame cible correcte]), 1179 + imagefigure("./hwcwrong.png", [Erreur dans le calcul des coordonnées des pixels: inversion de `%` et `/`]), 1180 + ) 1181 + 1182 + ==== Aller plus loin 1183 + 1184 + L'opération reste de loin la plus coûteuse de la chaîne de rendu. 1125 1185 1126 1186 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. 1127 1187
+26 -19
research/adapters/flstudio/adapter.py
··· 2 2 """ 3 3 Usage: flp_to_json.py <input_flp_file> <output_json_file> 4 4 """ 5 + 5 6 from pathlib import Path 6 7 from docopt import docopt 7 8 import pyflp ··· 87 88 return clips_names[0] 88 89 89 90 91 + def serialize_track(track): 92 + out = {} 93 + for clip in track: 94 + out[clip.position] = { 95 + "length": clip.length, 96 + "name": clip_name(clip), 97 + "data": clip_data(clip), 98 + } 99 + 100 + return out 101 + 102 + 90 103 def main(): 91 104 args = docopt(__doc__) 92 - 93 105 project = pyflp.parse(args["<input_flp_file>"]) 94 106 95 107 out = { 96 - "info": { 97 - "name": project.title, 98 - "bpm": project.tempo, 99 - }, 108 + "info": {"name": project.title, "bpm": project.tempo}, 100 109 "arrangements": {}, 101 110 } 102 - for arrangement in project.arrangements: 103 - current_arrangement = {"tracks": {}, "markers": {}} 104 - for track in arrangement.tracks: 105 - current_track = {} 106 - for clip in track: 107 - current_track[clip.position] = { 108 - "length": clip.length, 109 - "name": clip_name(clip), 110 - "data": clip_data(clip), 111 - } 112 - current_arrangement["tracks"][track_name(track)] = current_track 113 - for marker in arrangement.timemarkers: 114 - current_arrangement["markers"][marker.position] = marker.name 115 - out["arrangements"][arrangement.name] = current_arrangement 111 + 112 + for a in project.arrangements: 113 + out["arrangements"][a.name] = { 114 + "tracks": { 115 + track_name(track): serialize_track(track) for track in a.tracks 116 + }, 117 + "markers": { 118 + marker.position: marker.name for marker in a.timemarkers 119 + }, 120 + } 116 121 117 122 Path(args["<output_json_file>"]).write_text(json.dumps(out, indent=4)) 118 123 124 + 119 125 # end 126 + 120 127 121 128 if __name__ == "__main__": 122 129 main()
+21 -11
results.csv
··· 1 - Tâche,Durée [ms],# 2 - render_to_svg,0.127,150 3 - stringify_svg,0.135,150 4 - create_pixmap,0.251,150 5 - setup_encoder,5.160,1 6 - usvg_tree_to_pixmap,10.823,150 7 - svg_to_usvg_tree,47.558,150 8 - pixmap_to_hwc_frame,107.686,150 9 - load_midi_notes,119.540,1 10 - load_fonts,148.610,1 11 - encode_frame,167.082,150 1 + Tâche,Durée [ms],# 2 + 3 + render_to_svg,0.127,150 4 + 5 + stringify_svg,0.135,150 6 + 7 + create_pixmap,0.251,150 8 + 9 + setup_encoder,5.160,1 10 + 11 + usvg_to_pixmap,10.823,150 12 + 13 + svg_to_usvg,47.558,150 14 + 15 + pixmap_to_hwc,107.686,150 16 + 17 + load_midi_notes,119.540,1 18 + 19 + load_fonts,148.610,1 20 + 21 + encode_frame,167.082,150