alpha
Login
or
Join now
gwen.works
/
shapemaker
Star
0
Fork
0
Atom
Configure Feed
Issues
Pull Requests
Commits
Tags
Feed URL
Select the types of activity you want to include in your feed.
This repository has no description
Star
0
Fork
0
Atom
Configure Feed
Issues
Pull Requests
Commits
Tags
Feed URL
Select the types of activity you want to include in your feed.
Overview
Issues
Pulls
Pipelines
:memo: finish except thanks
author
Gwenn Le Bihan
date
1 year ago
(Mar 24, 2025, 11:31 PM +0100)
commit
5f7b9375
5f7b9375cf4aa607f3ec7167c7ff362498af82dd
parent
a1fc5142
a1fc5142de9d1b2cd701a074b645a94a96e03b01
+210
-133
3 changed files
Expand all
Collapse all
Unified
Split
paper
main.typ
research
adapters
flstudio
adapter.py
results.csv
+163
-103
paper/main.typ
Reviewed
···
8
8
)
9
9
10
10
11
11
-
#let imagefigure(path, caption) = figure(
12
12
-
image(path, width: 100%),
11
11
+
#let imagefigure(path, caption, size: 100%) = figure(
12
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
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
131
+
caption: "Exemples d'œuvres résultant d'une procédure de génération semi-aléatoire",
132
132
grid(
133
133
-
columns: (1fr, 1fr, 1fr),
133
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
140
-
// "measuring-spirits",
140
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
199
-
#let work = (slug, caption, with-context: false, screenshot: true) => figure(
199
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
204
-
(if with-context { 2fr } else { 1fr }, 3fr)
204
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
212
-
image("./street/" + slug + ".jpeg"),
213
213
-
if with-context {
212
212
+
if not only-context {
213
213
+
image("./street/" + slug + ".jpeg")
214
214
+
},
215
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
222
-
// #work(
223
223
-
// "lenvolée-du-cerf-volant",
224
224
-
// ["l'envolée du Cerf-Volant", nommée par _Nicolas C._],
225
225
-
// )
224
224
+
#work(
225
225
+
"lenvolée-du-cerf-volant",
226
226
+
["l'envolée du Cerf-Volant", nommée par _Nicolas C._],
227
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
230
-
#work("bridging", [_Sans titre_], with-context: true)
232
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
262
+
260
263
#diagram(
261
264
caption: [Modèle objet du Canvas],
262
262
-
size: 90%,
265
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
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
739
+
size: 0.9em,
735
740
include-function(
736
741
"../research/adapters/flstudio/adapter.py",
737
742
"main",
738
743
lang: "python",
739
739
-
transform: it => "import pyflp\n\n" + it.replace("\n# end", ""),
744
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
806
-
```dot
807
807
-
digraph G {
808
808
-
rankdir="LR";
809
809
-
// splines=ortho;
810
810
-
compound=true;
811
811
-
node[shape="record"];
811
811
+
[
812
812
+
```dot
813
813
+
digraph G {
814
814
+
rankdir="LR";
815
815
+
// splines=ortho;
816
816
+
compound=true;
817
817
+
node[shape="record"];
818
818
+
819
819
+
subgraph cluster_host {
820
820
+
label = "Logiciel de MAO"
821
821
+
822
822
+
subgraph cluster_bass {
823
823
+
label = "Bass"
824
824
+
midi -> synth [style=dashed]
825
825
+
synth -> probe_1
826
826
+
midi -> probe_1 [style=dashed]
827
827
+
autom_in_bass [shape=point, style=invis, label=""]
828
828
+
autom_in_bass -> probe_1 [style=dotted]
829
829
+
autom_in_bass -> synth [style=dotted]
830
830
+
831
831
+
probe_1[label="probe #1"]
832
832
+
}
833
833
+
subgraph cluster_drums {
834
834
+
label = "Drums"
835
835
+
midi_2 [label="midi"]
836
836
+
midi_2 -> drums [style=dashed]
837
837
+
drums -> probe_2
838
838
+
midi_2 -> probe_2 [style=dashed]
839
839
+
autom_in_drums [shape=plaintext, label=""]
812
840
813
813
-
subgraph cluster_host {
814
814
-
label = "Logiciel de MAO"
841
841
+
probe_2[label="probe #2"]
842
842
+
}
815
843
816
816
-
subgraph cluster_bass {
817
817
-
label = "Bass"
818
818
-
midi -> synth -> probe_1
819
819
-
midi -> probe_1
820
820
-
autom_in_bass [shape=point, label=""]
821
821
-
autom_in_bass -> probe_1
822
822
-
autom_in_bass -> synth
844
844
+
subgraph cluster_voice {
845
845
+
label = "Voice"
846
846
+
sampler -> effects -> probe_3
847
847
+
autom_in_voice [shape=point, style=invis, label=""]
848
848
+
autom_in_voice -> probe_3 [style=dotted]
849
849
+
autom_in_voice -> effects [style=dotted]
823
850
824
824
-
probe_1[label="probe #1"]
825
825
-
}
826
826
-
subgraph cluster_drums {
827
827
-
label = "Drums"
828
828
-
midi_2 [label="midi"]
829
829
-
midi_2 -> drums -> probe_2
830
830
-
midi_2 -> probe_2
831
831
-
autom_in_drums [shape=plaintext, label=""]
851
851
+
probe_2[label="probe #3"]
852
852
+
}
832
853
833
833
-
probe_2[label="probe #2"]
854
854
+
automation -> autom_in_bass [arrowhead=none, style=dotted]
855
855
+
automation -> autom_in_voice [arrowhead=none, style=dotted]
856
856
+
automation -> autom_in_drums [style=invis]
834
857
}
835
858
836
836
-
subgraph cluster_voice {
837
837
-
label = "Voice"
838
838
-
sampler -> effects -> probe_3
839
839
-
autom_in_voice [shape=point, label=""]
840
840
-
autom_in_voice -> probe_3
841
841
-
autom_in_voice -> effects
842
842
-
843
843
-
probe_2[label="probe #3"]
859
859
+
subgraph cluster_shapemaker {
860
860
+
label = "Shapemaker"
861
861
+
wip[label="(en développement)", shape="plaintext"]
862
862
+
beacon -> wip
844
863
}
845
864
846
846
-
automation -> autom_in_bass [arrowhead=none]
847
847
-
automation -> autom_in_voice [arrowhead=none]
848
848
-
automation -> autom_in_drums [style=invis]
849
849
-
}
865
865
+
probe_1 -> beacon [label="ws://", color=darkblue]
866
866
+
probe_2 -> beacon [label="ws://", color=darkblue]
867
867
+
probe_3 -> beacon [label="ws://", color=darkblue]
850
868
851
851
-
subgraph cluster_shapemaker {
852
852
-
label = "Shapemaker"
853
853
-
wip[label="(en développement)", shape="plaintext"]
854
854
-
beacon -> wip
855
869
}
870
870
+
```
856
871
857
857
-
probe_1 -> beacon [label="ws://"]
858
858
-
probe_2 -> beacon [label="ws://"]
859
859
-
probe_3 -> beacon [label="ws://"]
872
872
+
#place(
873
873
+
dy: -7em,
874
874
+
dx: 35em,
875
875
+
```dot
876
876
+
digraph {
877
877
+
rankdir=LR;
878
878
+
// splines=ortho;
879
879
+
label = "Légende"
880
880
+
node[style=invis,shape=point,label=""]
881
881
+
a1 -> b1 [style=dotted, label="Automation"]
882
882
+
a2 -> b2 [style=dashed, label="Notes"]
883
883
+
}
884
884
+
```,
885
885
+
)
860
886
861
861
-
}
862
862
-
```,
887
887
+
#place(
888
888
+
dy: -7em,
889
889
+
dx: 47em,
890
890
+
```dot
891
891
+
digraph {
892
892
+
rankdir=LR;
893
893
+
// splines=ortho;
894
894
+
label = "Légende"
895
895
+
node[style=invis,shape=point,label=""]
896
896
+
a3 -> b3 [style=solid, label="Audio"]
897
897
+
a4 -> b4 [color=darkblue, label="Syncdata"]
898
898
+
}
899
899
+
```,
900
900
+
)
901
901
+
],
863
902
)
864
903
865
904
···
971
1010
label = "Rust"
972
1011
subgraph cluster_each_frame {
973
1012
label = "Chaque frame"
974
974
-
canvas -> "Frame 0037.svg"
975
975
-
"Frame 0037.svg" -> "Frame 0037.png" [label="resvg"]
1013
1013
+
canvas -> "SVG string"
1014
1014
+
"SVG string" -> "Pixmap" [label="resvg"]
976
1015
}
977
977
-
"Frame 0037.png" -> "video.mp4" [label="libx264"]
1016
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
1006
-
#diagram(
1007
1007
-
caption: [Détail de la boucle de rendu],
1008
1008
-
[
1009
1009
-
```dot
1010
1010
-
digraph G {
1011
1011
-
compound=true;
1012
1012
-
// Either of these makes edge labels disappear...
1013
1013
-
// splines="ortho";
1014
1014
-
// node[shape="record"];
1045
1045
+
#grid(
1046
1046
+
columns: (1.3fr, 1.1fr),
1047
1047
+
gutter: 1em,
1048
1048
+
diagram(
1049
1049
+
size: 73%,
1050
1050
+
caption: [Détail de la boucle de rendu],
1051
1051
+
[
1052
1052
+
```dot
1053
1053
+
digraph G {
1054
1054
+
compound=true;
1055
1055
+
// Either of these makes edge labels disappear...
1056
1056
+
// splines="ortho";
1057
1057
+
// node[shape="record"];
1015
1058
1016
1016
-
hooks -> canvas;
1017
1017
-
subgraph cluster_tosvg {
1018
1018
-
label = "SVG string rendering [0.2ms]"
1019
1019
-
subgraph g_svg {
1020
1020
-
rank=same;
1021
1021
-
canvas -> render_to_svg [label="0.1ms"]
1022
1022
-
render_to_svg -> stringify_svg [label="0.1ms"]
1059
1059
+
hooks -> canvas;
1060
1060
+
subgraph cluster_tosvg {
1061
1061
+
label = "SVG string rendering [0.2ms]"
1062
1062
+
subgraph g_svg {
1063
1063
+
rank=same;
1064
1064
+
canvas -> render_to_svg [label="0.1ms"]
1065
1065
+
render_to_svg -> stringify_svg [label="0.1ms"]
1066
1066
+
}
1023
1067
}
1024
1024
-
}
1025
1025
-
stringify_svg -> "svg string" [label="0.1ms"]
1026
1026
-
subgraph cluster_rasterize {
1027
1027
-
label = "Encode frame [167ms]"
1028
1028
-
subgraph g_rasterize {
1029
1029
-
rank=same;
1030
1030
-
"svg string" -> "usvg tree" [label="48ms"]
1031
1031
-
"usvg tree" -> pixmap [label="11ms"]
1032
1032
-
pixmap -> "hwc frame" [label="108ms"]
1068
1068
+
stringify_svg -> "svg" [label="0.1ms"]
1069
1069
+
subgraph cluster_rasterize {
1070
1070
+
label = "Encode frame [167ms]"
1071
1071
+
subgraph g_rasterize {
1072
1072
+
rank=same;
1073
1073
+
svg [label="svg\n(str)"]
1074
1074
+
usvg [label="usvg\n(tree)"]
1075
1075
+
"svg" -> "usvg" [label="48ms"]
1076
1076
+
}
1077
1077
+
subgraph g_rasterize2 {
1078
1078
+
rank=same;
1079
1079
+
"usvg" -> pixmap [label="11ms"]
1080
1080
+
pixmap -> "hwc" [label="108ms"]
1081
1081
+
}
1033
1082
}
1083
1083
+
1084
1084
+
canvas -> "svg" [weight=10, style=invis]
1034
1085
}
1035
1035
-
1036
1036
-
canvas -> "svg string" [weight=10, style=invis]
1037
1037
-
}
1038
1038
-
```
1039
1039
-
],
1040
1040
-
)
1041
1041
-
1042
1042
-
#figure(
1043
1043
-
caption: "Durées d'exécution par tâche, pour une vidéo de test de 5 secondes",
1044
1044
-
table(
1045
1045
-
columns: 3,
1046
1046
-
inset: 0.75em,
1047
1047
-
[*Tâche*], [*Durée [ms]*], [*\#*],
1048
1048
-
..csv("../results.csv").slice(1).flatten()
1086
1086
+
```
1087
1087
+
],
1088
1088
+
),
1089
1089
+
figure(
1090
1090
+
caption: "Durées d'exécution par tâche, pour une vidéo de test de 5 secondes (millisecondes)",
1091
1091
+
table(
1092
1092
+
columns: 3,
1093
1093
+
inset: 0.5em,
1094
1094
+
[*Tâche*], [*$Delta t$*], [*\#*],
1095
1095
+
..csv("../results.csv").slice(1).flatten()
1096
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
1173
+
1174
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
1175
+
1176
1176
+
#grid(
1177
1177
+
columns: (1fr, 1fr),
1178
1178
+
imagefigure("./hwccorrect.png", [Frame cible correcte]),
1179
1179
+
imagefigure("./hwcwrong.png", [Erreur dans le calcul des coordonnées des pixels: inversion de `%` et `/`]),
1180
1180
+
)
1181
1181
+
1182
1182
+
==== Aller plus loin
1183
1183
+
1184
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
Reviewed
···
2
2
"""
3
3
Usage: flp_to_json.py <input_flp_file> <output_json_file>
4
4
"""
5
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
91
+
def serialize_track(track):
92
92
+
out = {}
93
93
+
for clip in track:
94
94
+
out[clip.position] = {
95
95
+
"length": clip.length,
96
96
+
"name": clip_name(clip),
97
97
+
"data": clip_data(clip),
98
98
+
}
99
99
+
100
100
+
return out
101
101
+
102
102
+
90
103
def main():
91
104
args = docopt(__doc__)
92
92
-
93
105
project = pyflp.parse(args["<input_flp_file>"])
94
106
95
107
out = {
96
96
-
"info": {
97
97
-
"name": project.title,
98
98
-
"bpm": project.tempo,
99
99
-
},
108
108
+
"info": {"name": project.title, "bpm": project.tempo},
100
109
"arrangements": {},
101
110
}
102
102
-
for arrangement in project.arrangements:
103
103
-
current_arrangement = {"tracks": {}, "markers": {}}
104
104
-
for track in arrangement.tracks:
105
105
-
current_track = {}
106
106
-
for clip in track:
107
107
-
current_track[clip.position] = {
108
108
-
"length": clip.length,
109
109
-
"name": clip_name(clip),
110
110
-
"data": clip_data(clip),
111
111
-
}
112
112
-
current_arrangement["tracks"][track_name(track)] = current_track
113
113
-
for marker in arrangement.timemarkers:
114
114
-
current_arrangement["markers"][marker.position] = marker.name
115
115
-
out["arrangements"][arrangement.name] = current_arrangement
111
111
+
112
112
+
for a in project.arrangements:
113
113
+
out["arrangements"][a.name] = {
114
114
+
"tracks": {
115
115
+
track_name(track): serialize_track(track) for track in a.tracks
116
116
+
},
117
117
+
"markers": {
118
118
+
marker.position: marker.name for marker in a.timemarkers
119
119
+
},
120
120
+
}
116
121
117
122
Path(args["<output_json_file>"]).write_text(json.dumps(out, indent=4))
118
123
124
124
+
119
125
# end
126
126
+
120
127
121
128
if __name__ == "__main__":
122
129
main()
+21
-11
results.csv
Reviewed
···
1
1
-
Tâche,Durée [ms],#
2
2
-
render_to_svg,0.127,150
3
3
-
stringify_svg,0.135,150
4
4
-
create_pixmap,0.251,150
5
5
-
setup_encoder,5.160,1
6
6
-
usvg_tree_to_pixmap,10.823,150
7
7
-
svg_to_usvg_tree,47.558,150
8
8
-
pixmap_to_hwc_frame,107.686,150
9
9
-
load_midi_notes,119.540,1
10
10
-
load_fonts,148.610,1
11
11
-
encode_frame,167.082,150
1
1
+
Tâche,Durée [ms],#
2
2
+
3
3
+
render_to_svg,0.127,150
4
4
+
5
5
+
stringify_svg,0.135,150
6
6
+
7
7
+
create_pixmap,0.251,150
8
8
+
9
9
+
setup_encoder,5.160,1
10
10
+
11
11
+
usvg_to_pixmap,10.823,150
12
12
+
13
13
+
svg_to_usvg,47.558,150
14
14
+
15
15
+
pixmap_to_hwc,107.686,150
16
16
+
17
17
+
load_midi_notes,119.540,1
18
18
+
19
19
+
load_fonts,148.610,1
20
20
+
21
21
+
encode_frame,167.082,150