Monorepo for Aesthetic.Computer aesthetic.computer

m4l: add Knob Map v1.0.0 MIDI effect for parameter control via MIDI keys

New M4L device maps MIDI note numbers to any Ableton parameter value.
Scaled mode maps 0-127 across param range, Direct mode uses raw values.
Also fixes build.py for source-file devices and wires /m4l-plugins route.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+746 -9
+693
ac-m4l/AC-KnobMap.amxd.json
··· 1 + { 2 + "patcher": { 3 + "fileversion": 1, 4 + "appversion": { 5 + "major": 9, 6 + "minor": 0, 7 + "revision": 7, 8 + "architecture": "x64", 9 + "modernui": 1 10 + }, 11 + "classnamespace": "box", 12 + "rect": [100.0, 100.0, 950.0, 550.0], 13 + "openrect": [0.0, 0.0, 250.0, 170.0], 14 + "openinpresentation": 1, 15 + "gridsize": [15.0, 15.0], 16 + "enablehscroll": 0, 17 + "enablevscroll": 0, 18 + "devicewidth": 250.0, 19 + "description": "Map MIDI note numbers to any Ableton parameter value", 20 + "boxes": [ 21 + { 22 + "box": { 23 + "id": "obj-panel", 24 + "maxclass": "panel", 25 + "numinlets": 1, 26 + "numoutlets": 0, 27 + "patching_rect": [700.0, 30.0, 250.0, 170.0], 28 + "presentation": 1, 29 + "presentation_rect": [0.0, 0.0, 250.0, 170.0], 30 + "rounded": 0, 31 + "mode": 0, 32 + "bgcolor": [0.114, 0.106, 0.18, 1.0] 33 + } 34 + }, 35 + { 36 + "box": { 37 + "id": "obj-title", 38 + "maxclass": "comment", 39 + "numinlets": 1, 40 + "numoutlets": 0, 41 + "patching_rect": [700.0, 32.0, 100.0, 20.0], 42 + "presentation": 1, 43 + "presentation_rect": [10.0, 8.0, 95.0, 20.0], 44 + "text": "KNOB MAP", 45 + "fontsize": 13.0, 46 + "fontface": 1, 47 + "textcolor": [0.875, 0.863, 0.922, 1.0] 48 + } 49 + }, 50 + { 51 + "box": { 52 + "id": "obj-version", 53 + "maxclass": "comment", 54 + "numinlets": 1, 55 + "numoutlets": 0, 56 + "patching_rect": [810.0, 32.0, 50.0, 18.0], 57 + "presentation": 1, 58 + "presentation_rect": [200.0, 11.0, 42.0, 18.0], 59 + "text": "v1.0.0", 60 + "fontsize": 10.0, 61 + "textcolor": [0.486, 0.467, 0.584, 1.0] 62 + } 63 + }, 64 + { 65 + "box": { 66 + "id": "obj-grab", 67 + "maxclass": "live.text", 68 + "numinlets": 1, 69 + "numoutlets": 2, 70 + "outlettype": ["", ""], 71 + "patching_rect": [200.0, 30.0, 56.0, 20.0], 72 + "presentation": 1, 73 + "presentation_rect": [10.0, 38.0, 56.0, 20.0], 74 + "parameter_enable": 1, 75 + "saved_attribute_attributes": { 76 + "valueof": { 77 + "parameter_longname": "Grab", 78 + "parameter_shortname": "Grab", 79 + "parameter_type": 2, 80 + "parameter_enum": ["GRAB", "GRAB"], 81 + "parameter_mmax": 1.0 82 + } 83 + }, 84 + "varname": "Grab", 85 + "mode": 0, 86 + "text": "GRAB", 87 + "texton": "GRAB" 88 + } 89 + }, 90 + { 91 + "box": { 92 + "id": "obj-status", 93 + "maxclass": "message", 94 + "numinlets": 2, 95 + "numoutlets": 1, 96 + "outlettype": [""], 97 + "patching_rect": [700.0, 65.0, 170.0, 22.0], 98 + "presentation": 1, 99 + "presentation_rect": [72.0, 40.0, 168.0, 18.0], 100 + "text": "Click a knob then GRAB", 101 + "textcolor": [0.588, 0.494, 0.761, 1.0], 102 + "bgcolor": [0.0, 0.0, 0.0, 0.0], 103 + "fontsize": 10.0 104 + } 105 + }, 106 + { 107 + "box": { 108 + "id": "obj-line1", 109 + "maxclass": "panel", 110 + "numinlets": 1, 111 + "numoutlets": 0, 112 + "patching_rect": [700.0, 92.0, 230.0, 1.0], 113 + "presentation": 1, 114 + "presentation_rect": [10.0, 66.0, 230.0, 1.0], 115 + "bgcolor": [0.3, 0.278, 0.38, 1.0], 116 + "mode": 0, 117 + "rounded": 0 118 + } 119 + }, 120 + { 121 + "box": { 122 + "id": "obj-range-label", 123 + "maxclass": "comment", 124 + "numinlets": 1, 125 + "numoutlets": 0, 126 + "patching_rect": [700.0, 96.0, 42.0, 18.0], 127 + "presentation": 1, 128 + "presentation_rect": [10.0, 74.0, 42.0, 16.0], 129 + "text": "Range", 130 + "fontsize": 10.0, 131 + "textcolor": [0.486, 0.467, 0.584, 1.0] 132 + } 133 + }, 134 + { 135 + "box": { 136 + "id": "obj-range-display", 137 + "maxclass": "message", 138 + "numinlets": 2, 139 + "numoutlets": 1, 140 + "outlettype": [""], 141 + "patching_rect": [700.0, 116.0, 170.0, 22.0], 142 + "presentation": 1, 143 + "presentation_rect": [52.0, 73.0, 188.0, 18.0], 144 + "text": "—", 145 + "textcolor": [0.588, 0.576, 0.686, 1.0], 146 + "bgcolor": [0.0, 0.0, 0.0, 0.0], 147 + "fontsize": 10.0 148 + } 149 + }, 150 + { 151 + "box": { 152 + "id": "obj-val-label", 153 + "maxclass": "comment", 154 + "numinlets": 1, 155 + "numoutlets": 0, 156 + "patching_rect": [700.0, 140.0, 40.0, 18.0], 157 + "presentation": 1, 158 + "presentation_rect": [10.0, 97.0, 42.0, 16.0], 159 + "text": "Value", 160 + "fontsize": 10.0, 161 + "textcolor": [0.486, 0.467, 0.584, 1.0] 162 + } 163 + }, 164 + { 165 + "box": { 166 + "id": "obj-val-display", 167 + "maxclass": "message", 168 + "numinlets": 2, 169 + "numoutlets": 1, 170 + "outlettype": [""], 171 + "patching_rect": [700.0, 160.0, 80.0, 22.0], 172 + "presentation": 1, 173 + "presentation_rect": [52.0, 96.0, 70.0, 18.0], 174 + "text": "—", 175 + "textcolor": [0.875, 0.863, 0.922, 1.0], 176 + "bgcolor": [0.0, 0.0, 0.0, 0.0], 177 + "fontsize": 11.0, 178 + "fontface": 1 179 + } 180 + }, 181 + { 182 + "box": { 183 + "id": "obj-note-label", 184 + "maxclass": "comment", 185 + "numinlets": 1, 186 + "numoutlets": 0, 187 + "patching_rect": [700.0, 182.0, 60.0, 18.0], 188 + "presentation": 1, 189 + "presentation_rect": [10.0, 120.0, 50.0, 16.0], 190 + "text": "Note", 191 + "fontsize": 10.0, 192 + "textcolor": [0.486, 0.467, 0.584, 1.0] 193 + } 194 + }, 195 + { 196 + "box": { 197 + "id": "obj-note-display", 198 + "maxclass": "message", 199 + "numinlets": 2, 200 + "numoutlets": 1, 201 + "outlettype": [""], 202 + "patching_rect": [760.0, 182.0, 60.0, 22.0], 203 + "presentation": 1, 204 + "presentation_rect": [52.0, 119.0, 40.0, 18.0], 205 + "text": "—", 206 + "textcolor": [0.875, 0.863, 0.922, 1.0], 207 + "bgcolor": [0.0, 0.0, 0.0, 0.0], 208 + "fontsize": 10.0 209 + } 210 + }, 211 + { 212 + "box": { 213 + "id": "obj-ac-label", 214 + "maxclass": "comment", 215 + "numinlets": 1, 216 + "numoutlets": 0, 217 + "patching_rect": [850.0, 182.0, 100.0, 18.0], 218 + "presentation": 1, 219 + "presentation_rect": [117.0, 152.0, 130.0, 14.0], 220 + "text": "aesthetic.computer/ableton", 221 + "fontsize": 9.0, 222 + "textcolor": [0.35, 0.333, 0.447, 1.0] 223 + } 224 + }, 225 + { 226 + "box": { 227 + "id": "obj-mode-label", 228 + "maxclass": "comment", 229 + "numinlets": 1, 230 + "numoutlets": 0, 231 + "patching_rect": [700.0, 202.0, 40.0, 18.0], 232 + "presentation": 1, 233 + "presentation_rect": [10.0, 143.0, 40.0, 16.0], 234 + "text": "Mode", 235 + "fontsize": 10.0, 236 + "textcolor": [0.486, 0.467, 0.584, 1.0] 237 + } 238 + }, 239 + { 240 + "box": { 241 + "id": "obj-mode", 242 + "maxclass": "live.menu", 243 + "numinlets": 1, 244 + "numoutlets": 3, 245 + "outlettype": ["", "", "float"], 246 + "patching_rect": [430.0, 30.0, 100.0, 15.0], 247 + "presentation": 1, 248 + "presentation_rect": [52.0, 143.0, 85.0, 15.0], 249 + "parameter_enable": 1, 250 + "saved_attribute_attributes": { 251 + "valueof": { 252 + "parameter_longname": "Mode", 253 + "parameter_shortname": "Mode", 254 + "parameter_type": 2, 255 + "parameter_enum": ["Scaled", "Direct"], 256 + "parameter_mmax": 1.0 257 + } 258 + }, 259 + "varname": "Mode" 260 + } 261 + }, 262 + 263 + { 264 + "box": { 265 + "id": "obj-thisdevice", 266 + "maxclass": "newobj", 267 + "numinlets": 1, 268 + "numoutlets": 3, 269 + "outlettype": ["bang", "int", "int"], 270 + "patching_rect": [30.0, 30.0, 95.0, 22.0], 271 + "text": "live.thisdevice" 272 + } 273 + }, 274 + { 275 + "box": { 276 + "id": "obj-songpath", 277 + "maxclass": "newobj", 278 + "numinlets": 1, 279 + "numoutlets": 3, 280 + "outlettype": ["", "", ""], 281 + "patching_rect": [30.0, 70.0, 120.0, 22.0], 282 + "text": "live.path live_set" 283 + } 284 + }, 285 + { 286 + "box": { 287 + "id": "obj-song-reader", 288 + "maxclass": "newobj", 289 + "numinlets": 2, 290 + "numoutlets": 3, 291 + "outlettype": ["", "", ""], 292 + "patching_rect": [30.0, 150.0, 80.0, 22.0], 293 + "text": "live.object" 294 + } 295 + }, 296 + { 297 + "box": { 298 + "id": "obj-grab-filter", 299 + "maxclass": "newobj", 300 + "numinlets": 2, 301 + "numoutlets": 2, 302 + "outlettype": ["bang", ""], 303 + "patching_rect": [200.0, 70.0, 30.0, 22.0], 304 + "text": "sel 1" 305 + } 306 + }, 307 + { 308 + "box": { 309 + "id": "obj-msg-getsel", 310 + "maxclass": "message", 311 + "numinlets": 2, 312 + "numoutlets": 1, 313 + "outlettype": [""], 314 + "patching_rect": [200.0, 110.0, 165.0, 22.0], 315 + "text": "getattr selected_parameter" 316 + } 317 + }, 318 + { 319 + "box": { 320 + "id": "obj-grab-dist", 321 + "maxclass": "newobj", 322 + "numinlets": 1, 323 + "numoutlets": 2, 324 + "outlettype": ["bang", "int"], 325 + "patching_rect": [30.0, 200.0, 40.0, 22.0], 326 + "text": "t b i" 327 + } 328 + }, 329 + { 330 + "box": { 331 + "id": "obj-reader-min", 332 + "maxclass": "newobj", 333 + "numinlets": 2, 334 + "numoutlets": 3, 335 + "outlettype": ["", "", ""], 336 + "patching_rect": [90.0, 270.0, 80.0, 22.0], 337 + "text": "live.object" 338 + } 339 + }, 340 + { 341 + "box": { 342 + "id": "obj-reader-max", 343 + "maxclass": "newobj", 344 + "numinlets": 2, 345 + "numoutlets": 3, 346 + "outlettype": ["", "", ""], 347 + "patching_rect": [230.0, 270.0, 80.0, 22.0], 348 + "text": "live.object" 349 + } 350 + }, 351 + { 352 + "box": { 353 + "id": "obj-reader-name", 354 + "maxclass": "newobj", 355 + "numinlets": 2, 356 + "numoutlets": 3, 357 + "outlettype": ["", "", ""], 358 + "patching_rect": [370.0, 270.0, 80.0, 22.0], 359 + "text": "live.object" 360 + } 361 + }, 362 + { 363 + "box": { 364 + "id": "obj-writer", 365 + "maxclass": "newobj", 366 + "numinlets": 2, 367 + "numoutlets": 3, 368 + "outlettype": ["", "", ""], 369 + "patching_rect": [500.0, 350.0, 80.0, 22.0], 370 + "text": "live.object" 371 + } 372 + }, 373 + { 374 + "box": { 375 + "id": "obj-msg-getmin", 376 + "maxclass": "message", 377 + "numinlets": 2, 378 + "numoutlets": 1, 379 + "outlettype": [""], 380 + "patching_rect": [90.0, 240.0, 70.0, 22.0], 381 + "text": "getattr min" 382 + } 383 + }, 384 + { 385 + "box": { 386 + "id": "obj-msg-getmax", 387 + "maxclass": "message", 388 + "numinlets": 2, 389 + "numoutlets": 1, 390 + "outlettype": [""], 391 + "patching_rect": [230.0, 240.0, 75.0, 22.0], 392 + "text": "getattr max" 393 + } 394 + }, 395 + { 396 + "box": { 397 + "id": "obj-msg-getname", 398 + "maxclass": "message", 399 + "numinlets": 2, 400 + "numoutlets": 1, 401 + "outlettype": [""], 402 + "patching_rect": [370.0, 240.0, 80.0, 22.0], 403 + "text": "getattr name" 404 + } 405 + }, 406 + { 407 + "box": { 408 + "id": "obj-msg-setval", 409 + "maxclass": "message", 410 + "numinlets": 2, 411 + "numoutlets": 1, 412 + "outlettype": [""], 413 + "patching_rect": [500.0, 320.0, 85.0, 22.0], 414 + "text": "set value $1" 415 + } 416 + }, 417 + { 418 + "box": { 419 + "id": "obj-notein", 420 + "maxclass": "newobj", 421 + "numinlets": 1, 422 + "numoutlets": 3, 423 + "outlettype": ["int", "int", "int"], 424 + "patching_rect": [500.0, 30.0, 50.0, 22.0], 425 + "text": "notein" 426 + } 427 + }, 428 + { 429 + "box": { 430 + "id": "obj-stripnote", 431 + "maxclass": "newobj", 432 + "numinlets": 2, 433 + "numoutlets": 2, 434 + "outlettype": ["int", "int"], 435 + "patching_rect": [500.0, 70.0, 60.0, 22.0], 436 + "text": "stripnote" 437 + } 438 + }, 439 + { 440 + "box": { 441 + "id": "obj-expr", 442 + "maxclass": "newobj", 443 + "numinlets": 3, 444 + "numoutlets": 1, 445 + "outlettype": [""], 446 + "patching_rect": [500.0, 150.0, 250.0, 22.0], 447 + "text": "expr $f1 / 127. * ($f2 - $f3) + $f3" 448 + } 449 + }, 450 + { 451 + "box": { 452 + "id": "obj-mode-sel", 453 + "maxclass": "newobj", 454 + "numinlets": 3, 455 + "numoutlets": 3, 456 + "outlettype": ["bang", "bang", ""], 457 + "patching_rect": [430.0, 60.0, 50.0, 22.0], 458 + "text": "sel 0 1" 459 + } 460 + }, 461 + { 462 + "box": { 463 + "id": "obj-gate-scaled", 464 + "maxclass": "newobj", 465 + "numinlets": 2, 466 + "numoutlets": 1, 467 + "outlettype": [""], 468 + "patching_rect": [500.0, 190.0, 85.0, 22.0], 469 + "text": "gate 1 1" 470 + } 471 + }, 472 + { 473 + "box": { 474 + "id": "obj-gate-direct", 475 + "maxclass": "newobj", 476 + "numinlets": 2, 477 + "numoutlets": 1, 478 + "outlettype": [""], 479 + "patching_rect": [500.0, 220.0, 85.0, 22.0], 480 + "text": "gate 1 0" 481 + } 482 + }, 483 + { 484 + "box": { 485 + "id": "obj-clip", 486 + "maxclass": "newobj", 487 + "numinlets": 3, 488 + "numoutlets": 1, 489 + "outlettype": [""], 490 + "patching_rect": [500.0, 110.0, 80.0, 22.0], 491 + "text": "clip 0. 127." 492 + } 493 + }, 494 + { 495 + "box": { 496 + "id": "obj-tofloat", 497 + "maxclass": "newobj", 498 + "numinlets": 1, 499 + "numoutlets": 1, 500 + "outlettype": ["float"], 501 + "patching_rect": [500.0, 250.0, 35.0, 22.0], 502 + "text": "t f" 503 + } 504 + }, 505 + { 506 + "box": { 507 + "id": "obj-prepend-set-status", 508 + "maxclass": "newobj", 509 + "numinlets": 1, 510 + "numoutlets": 1, 511 + "outlettype": [""], 512 + "patching_rect": [370.0, 330.0, 85.0, 22.0], 513 + "text": "prepend set" 514 + } 515 + }, 516 + { 517 + "box": { 518 + "id": "obj-prepend-set-val", 519 + "maxclass": "newobj", 520 + "numinlets": 1, 521 + "numoutlets": 1, 522 + "outlettype": [""], 523 + "patching_rect": [600.0, 250.0, 85.0, 22.0], 524 + "text": "prepend set" 525 + } 526 + }, 527 + { 528 + "box": { 529 + "id": "obj-prepend-set-note", 530 + "maxclass": "newobj", 531 + "numinlets": 1, 532 + "numoutlets": 1, 533 + "outlettype": [""], 534 + "patching_rect": [600.0, 110.0, 85.0, 22.0], 535 + "text": "prepend set" 536 + } 537 + }, 538 + { 539 + "box": { 540 + "id": "obj-prepend-set-range", 541 + "maxclass": "newobj", 542 + "numinlets": 1, 543 + "numoutlets": 1, 544 + "outlettype": [""], 545 + "patching_rect": [90.0, 370.0, 85.0, 22.0], 546 + "text": "prepend set" 547 + } 548 + }, 549 + { 550 + "box": { 551 + "id": "obj-sprintf-range", 552 + "maxclass": "newobj", 553 + "numinlets": 2, 554 + "numoutlets": 1, 555 + "outlettype": [""], 556 + "patching_rect": [90.0, 340.0, 140.0, 22.0], 557 + "text": "sprintf %g — %g" 558 + } 559 + }, 560 + { 561 + "box": { 562 + "id": "obj-store-min", 563 + "maxclass": "newobj", 564 + "numinlets": 2, 565 + "numoutlets": 1, 566 + "outlettype": ["float"], 567 + "patching_rect": [90.0, 310.0, 35.0, 22.0], 568 + "text": "float" 569 + } 570 + }, 571 + { 572 + "box": { 573 + "id": "obj-store-max", 574 + "maxclass": "newobj", 575 + "numinlets": 2, 576 + "numoutlets": 1, 577 + "outlettype": ["float"], 578 + "patching_rect": [230.0, 310.0, 35.0, 22.0], 579 + "text": "float" 580 + } 581 + }, 582 + { 583 + "box": { 584 + "id": "obj-midiin", 585 + "maxclass": "newobj", 586 + "numinlets": 1, 587 + "numoutlets": 1, 588 + "outlettype": ["int"], 589 + "patching_rect": [620.0, 30.0, 45.0, 22.0], 590 + "text": "midiin" 591 + } 592 + }, 593 + { 594 + "box": { 595 + "id": "obj-midiout", 596 + "maxclass": "newobj", 597 + "numinlets": 1, 598 + "numoutlets": 0, 599 + "patching_rect": [620.0, 70.0, 50.0, 22.0], 600 + "text": "midiout" 601 + } 602 + }, 603 + { 604 + "box": { 605 + "id": "obj-print", 606 + "maxclass": "newobj", 607 + "numinlets": 1, 608 + "numoutlets": 0, 609 + "patching_rect": [30.0, 420.0, 115.0, 22.0], 610 + "text": "print [AC-KNOBMAP]" 611 + } 612 + } 613 + ], 614 + 615 + "lines": [ 616 + {"patchline": {"destination": ["obj-songpath", 0], "source": ["obj-thisdevice", 0]}}, 617 + {"patchline": {"destination": ["obj-song-reader", 1], "source": ["obj-songpath", 1]}}, 618 + 619 + {"patchline": {"destination": ["obj-grab-filter", 0], "source": ["obj-grab", 0]}}, 620 + {"patchline": {"destination": ["obj-msg-getsel", 0], "source": ["obj-grab-filter", 0]}}, 621 + {"patchline": {"destination": ["obj-song-reader", 0], "source": ["obj-msg-getsel", 0]}}, 622 + 623 + {"patchline": {"destination": ["obj-grab-dist", 0], "source": ["obj-song-reader", 0]}}, 624 + 625 + {"patchline": {"destination": ["obj-reader-min", 1], "source": ["obj-grab-dist", 1]}}, 626 + {"patchline": {"destination": ["obj-reader-max", 1], "source": ["obj-grab-dist", 1]}}, 627 + {"patchline": {"destination": ["obj-reader-name", 1], "source": ["obj-grab-dist", 1]}}, 628 + {"patchline": {"destination": ["obj-writer", 1], "source": ["obj-grab-dist", 1]}}, 629 + 630 + {"patchline": {"destination": ["obj-msg-getmin", 0], "source": ["obj-grab-dist", 0]}}, 631 + {"patchline": {"destination": ["obj-msg-getmax", 0], "source": ["obj-grab-dist", 0]}}, 632 + {"patchline": {"destination": ["obj-msg-getname", 0], "source": ["obj-grab-dist", 0]}}, 633 + 634 + {"patchline": {"destination": ["obj-reader-min", 0], "source": ["obj-msg-getmin", 0]}}, 635 + {"patchline": {"destination": ["obj-reader-max", 0], "source": ["obj-msg-getmax", 0]}}, 636 + {"patchline": {"destination": ["obj-reader-name", 0], "source": ["obj-msg-getname", 0]}}, 637 + 638 + {"patchline": {"destination": ["obj-store-min", 0], "source": ["obj-reader-min", 0]}}, 639 + {"patchline": {"destination": ["obj-store-max", 0], "source": ["obj-reader-max", 0]}}, 640 + 641 + {"patchline": {"destination": ["obj-expr", 2], "source": ["obj-store-min", 0]}}, 642 + {"patchline": {"destination": ["obj-expr", 1], "source": ["obj-store-max", 0]}}, 643 + 644 + {"patchline": {"destination": ["obj-clip", 1], "source": ["obj-store-min", 0]}}, 645 + {"patchline": {"destination": ["obj-clip", 2], "source": ["obj-store-max", 0]}}, 646 + 647 + {"patchline": {"destination": ["obj-sprintf-range", 0], "source": ["obj-store-min", 0]}}, 648 + {"patchline": {"destination": ["obj-sprintf-range", 1], "source": ["obj-store-max", 0]}}, 649 + {"patchline": {"destination": ["obj-prepend-set-range", 0], "source": ["obj-sprintf-range", 0]}}, 650 + {"patchline": {"destination": ["obj-range-display", 0], "source": ["obj-prepend-set-range", 0]}}, 651 + 652 + {"patchline": {"destination": ["obj-prepend-set-status", 0], "source": ["obj-reader-name", 0]}}, 653 + {"patchline": {"destination": ["obj-status", 0], "source": ["obj-prepend-set-status", 0]}}, 654 + {"patchline": {"destination": ["obj-print", 0], "source": ["obj-reader-name", 0]}}, 655 + 656 + {"patchline": {"destination": ["obj-stripnote", 0], "source": ["obj-notein", 0]}}, 657 + {"patchline": {"destination": ["obj-stripnote", 1], "source": ["obj-notein", 1]}}, 658 + 659 + {"patchline": {"destination": ["obj-expr", 0], "source": ["obj-stripnote", 0]}}, 660 + {"patchline": {"destination": ["obj-clip", 0], "source": ["obj-stripnote", 0]}}, 661 + 662 + {"patchline": {"destination": ["obj-prepend-set-note", 0], "source": ["obj-stripnote", 0]}}, 663 + {"patchline": {"destination": ["obj-note-display", 0], "source": ["obj-prepend-set-note", 0]}}, 664 + 665 + {"patchline": {"destination": ["obj-gate-scaled", 1], "source": ["obj-expr", 0]}}, 666 + {"patchline": {"destination": ["obj-gate-direct", 1], "source": ["obj-clip", 0]}}, 667 + 668 + {"patchline": {"destination": ["obj-tofloat", 0], "source": ["obj-gate-scaled", 0]}}, 669 + {"patchline": {"destination": ["obj-tofloat", 0], "source": ["obj-gate-direct", 0]}}, 670 + 671 + {"patchline": {"destination": ["obj-msg-setval", 0], "source": ["obj-tofloat", 0]}}, 672 + {"patchline": {"destination": ["obj-writer", 0], "source": ["obj-msg-setval", 0]}}, 673 + 674 + {"patchline": {"destination": ["obj-prepend-set-val", 0], "source": ["obj-tofloat", 0]}}, 675 + {"patchline": {"destination": ["obj-val-display", 0], "source": ["obj-prepend-set-val", 0]}}, 676 + 677 + {"patchline": {"destination": ["obj-gate-scaled", 0], "source": ["obj-mode-sel", 0]}}, 678 + {"patchline": {"destination": ["obj-gate-direct", 0], "source": ["obj-mode-sel", 1]}}, 679 + {"patchline": {"destination": ["obj-mode-sel", 0], "source": ["obj-mode", 0]}}, 680 + 681 + {"patchline": {"destination": ["obj-midiout", 0], "source": ["obj-midiin", 0]}} 682 + ], 683 + 684 + "dependency_cache": [], 685 + "latency": 0, 686 + "is_mpe": 0, 687 + "external_mpe_tuning_enabled": 0, 688 + "minimum_live_version": "", 689 + "minimum_max_version": "", 690 + "platform_compatibility": 0, 691 + "autosave": 0 692 + } 693 + }
+32 -9
ac-m4l/build.py
··· 33 33 34 34 def generate_patcher(device: dict, defaults: dict, production: bool = False) -> dict: 35 35 """Generate a complete M4L patcher for a device.""" 36 - 36 + 37 + # If device has a source JSON file, load it directly 38 + source = device.get("source") 39 + if source: 40 + source_path = Path(__file__).parent / source 41 + with open(source_path) as f: 42 + return json.load(f) 43 + 37 44 # Check if this is an effect device (has audio input) 38 45 is_effect = device.get("type") == "effect" 39 - 46 + 40 47 if is_effect: 41 48 return generate_effect_patcher(device, defaults, production) 42 49 else: ··· 696 703 697 704 def build_device(device: dict, defaults: dict, production: bool = False) -> bytes: 698 705 """Build a complete .amxd file for a device.""" 699 - 700 - patcher = generate_patcher(device, defaults, production) 701 - 706 + 707 + # Source-based devices load patcher from a JSON file (no jweb~ generation) 708 + source = device.get("source") 709 + if source: 710 + source_path = Path(__file__).parent / source 711 + with open(source_path) as f: 712 + patcher = json.load(f) 713 + else: 714 + patcher = generate_patcher(device, defaults, production) 715 + 702 716 # Serialize patcher to JSON 703 717 patcher_json = json.dumps(patcher, separators=(',', ':')).encode('utf-8') 704 718 ··· 748 762 built = [] 749 763 for device in devices: 750 764 piece = device["piece"] 765 + # Skip devices with slashes in piece names (can't be filenames) 766 + if '/' in piece: 767 + print(f"\n⏭️ Skipping {piece} (slash in name)") 768 + continue 751 769 device_type = device.get("type", "instrument") 752 770 type_label = device_type.capitalize() 753 771 754 - # Build filename 755 - if production: 756 - filename = f"AC 🎸 {piece} (aesthetic.computer).amxd" if device_type == "effect" else f"AC 🟪 {piece} (aesthetic.computer).amxd" 772 + # Build filename — icon by type 773 + icons = {"effect": "🎸", "midi": "🎹", "instrument": "🟪"} 774 + icon = icons.get(device_type, "🟪") 775 + # Source-based devices (like knob-map) use the same name in dev and prod 776 + if device.get("source"): 777 + filename = f"AC {icon} {piece} (aesthetic.computer).amxd" 778 + elif production: 779 + filename = f"AC {icon} {piece} (aesthetic.computer).amxd" 757 780 else: 758 - filename = f"AC 🎸 {piece} (localhost:8888).amxd" if device_type == "effect" else f"AC 🟪 {piece} (localhost:8888).amxd" 781 + filename = f"AC {icon} {piece} (localhost:8888).amxd" 759 782 760 783 # Build device 761 784 data = build_device(device, defaults, production)
+10
ac-m4l/devices.json
··· 37 37 "width": 400, 38 38 "height": 250, 39 39 "type": "effect" 40 + }, 41 + { 42 + "name": "AC 🎹 knob-map (aesthetic.computer)", 43 + "piece": "knob-map", 44 + "description": "Map MIDI note numbers to any Ableton parameter — play keys to control knobs", 45 + "width": 250, 46 + "height": 170, 47 + "type": "midi", 48 + "source": "AC-KnobMap.amxd.json", 49 + "version": "1.0.0" 40 50 } 41 51 ], 42 52 "defaults": {
+11
system/netlify.toml
··· 587 587 to = "/.netlify/functions/version" 588 588 status = 200 589 589 590 + # M4L plugins API 591 + [[redirects]] 592 + from = "/m4l-plugins" 593 + to = "/.netlify/functions/m4l-plugins" 594 + status = 200 595 + 596 + [[redirects]] 597 + from = "/m4l-plugins/*" 598 + to = "/.netlify/functions/m4l-plugins/:splat" 599 + status = 200 600 + 590 601 # FF1 API routes 591 602 [[redirects]] 592 603 from = "/api/ff1/*"