abletoolz 2.0.0__tar.gz → 2.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. {abletoolz-2.0.0 → abletoolz-2.1.0}/PKG-INFO +228 -37
  2. {abletoolz-2.0.0 → abletoolz-2.1.0}/README.md +225 -36
  3. {abletoolz-2.0.0 → abletoolz-2.1.0}/abletoolz/__init__.py +1 -1
  4. {abletoolz-2.0.0 → abletoolz-2.1.0}/abletoolz/__main__.py +1 -3
  5. {abletoolz-2.0.0 → abletoolz-2.1.0}/abletoolz/alc.py +1 -3
  6. {abletoolz-2.0.0 → abletoolz-2.1.0}/abletoolz/asd/analysis.py +12 -1
  7. {abletoolz-2.0.0 → abletoolz-2.1.0}/abletoolz/asd/parser.py +1 -1
  8. abletoolz-2.1.0/abletoolz/cli.py +764 -0
  9. {abletoolz-2.0.0 → abletoolz-2.1.0}/abletoolz/color_tools.py +6 -8
  10. abletoolz-2.1.0/abletoolz/console.py +287 -0
  11. abletoolz-2.1.0/abletoolz/data/fabfilter/pro-c-2.json +594 -0
  12. {abletoolz-2.0.0 → abletoolz-2.1.0}/abletoolz/decode_encode.py +4 -6
  13. {abletoolz-2.0.0 → abletoolz-2.1.0}/abletoolz/dj_crates.py +90 -29
  14. abletoolz-2.1.0/abletoolz/live_set/apply_ops.py +395 -0
  15. abletoolz-2.1.0/abletoolz/live_set/clips.py +978 -0
  16. abletoolz-2.1.0/abletoolz/live_set/describe.py +379 -0
  17. abletoolz-2.1.0/abletoolz/live_set/devices.py +226 -0
  18. {abletoolz-2.0.0 → abletoolz-2.1.0}/abletoolz/live_set/document.py +33 -3
  19. {abletoolz-2.0.0 → abletoolz-2.1.0}/abletoolz/live_set/persistence.py +52 -17
  20. abletoolz-2.1.0/abletoolz/live_set/plugins.py +642 -0
  21. {abletoolz-2.0.0 → abletoolz-2.1.0}/abletoolz/live_set/sample_ref.py +99 -31
  22. {abletoolz-2.0.0 → abletoolz-2.1.0}/abletoolz/live_set/samples.py +18 -5
  23. abletoolz-2.1.0/abletoolz/live_set/sections.py +340 -0
  24. {abletoolz-2.0.0 → abletoolz-2.1.0}/abletoolz/live_set/tracks.py +17 -4
  25. abletoolz-2.1.0/abletoolz/live_set/xml_edit.py +129 -0
  26. abletoolz-2.1.0/abletoolz/meta.py +181 -0
  27. {abletoolz-2.0.0 → abletoolz-2.1.0}/abletoolz/misc.py +49 -2
  28. abletoolz-2.1.0/abletoolz/plugin_parsers/__init__.py +64 -0
  29. {abletoolz-2.0.0 → abletoolz-2.1.0}/abletoolz/plugin_parsers/base.py +42 -20
  30. {abletoolz-2.0.0 → abletoolz-2.1.0}/abletoolz/plugin_parsers/config.py +22 -0
  31. abletoolz-2.1.0/abletoolz/plugin_parsers/format_translation.py +536 -0
  32. abletoolz-2.1.0/abletoolz/plugin_parsers/mapping.py +556 -0
  33. abletoolz-2.1.0/abletoolz/plugin_parsers/parsers/__init__.py +1 -0
  34. abletoolz-2.1.0/abletoolz/plugin_parsers/parsers/maschine.py +195 -0
  35. abletoolz-2.0.0/abletoolz/plugin_parsers/serato_sample_parser.py → abletoolz-2.1.0/abletoolz/plugin_parsers/parsers/serato.py +9 -8
  36. abletoolz-2.1.0/abletoolz/plugin_parsers/parsers/xfadelooper.py +188 -0
  37. abletoolz-2.1.0/abletoolz/plugin_parsers/plugin_db.py +341 -0
  38. abletoolz-2.1.0/abletoolz/plugin_parsers/read_plugin_files.py +295 -0
  39. {abletoolz-2.0.0 → abletoolz-2.1.0}/abletoolz/plugin_parsers/registry.py +5 -6
  40. abletoolz-2.1.0/abletoolz/plugin_parsers/repair.py +370 -0
  41. abletoolz-2.1.0/abletoolz/plugin_parsers/state/__init__.py +353 -0
  42. abletoolz-2.1.0/abletoolz/plugin_parsers/state/derived.py +305 -0
  43. abletoolz-2.1.0/abletoolz/plugin_parsers/state/fabfilter.py +825 -0
  44. abletoolz-2.1.0/abletoolz/plugin_parsers/state/families.py +306 -0
  45. abletoolz-2.1.0/abletoolz/plugin_parsers/state/fxbk.py +82 -0
  46. abletoolz-2.1.0/abletoolz/plugin_parsers/state/izotope.py +34 -0
  47. abletoolz-2.1.0/abletoolz/plugin_parsers/state/measured.py +185 -0
  48. abletoolz-2.1.0/abletoolz/plugin_parsers/uid_sources.py +431 -0
  49. {abletoolz-2.0.0 → abletoolz-2.1.0}/abletoolz/plugin_parsers/upgrade_rules.py +4 -3
  50. abletoolz-2.1.0/abletoolz/report.py +274 -0
  51. {abletoolz-2.0.0 → abletoolz-2.1.0}/abletoolz/sample_databaser/create_db.py +7 -2
  52. {abletoolz-2.0.0 → abletoolz-2.1.0}/abletoolz/sample_matcher.py +5 -4
  53. {abletoolz-2.0.0 → abletoolz-2.1.0}/abletoolz/utils.py +25 -20
  54. {abletoolz-2.0.0 → abletoolz-2.1.0}/abletoolz/versioning.py +6 -6
  55. {abletoolz-2.0.0 → abletoolz-2.1.0}/abletoolz.egg-info/PKG-INFO +228 -37
  56. abletoolz-2.1.0/abletoolz.egg-info/SOURCES.txt +101 -0
  57. {abletoolz-2.0.0 → abletoolz-2.1.0}/abletoolz.egg-info/requires.txt +2 -0
  58. {abletoolz-2.0.0 → abletoolz-2.1.0}/pyproject.toml +17 -3
  59. abletoolz-2.1.0/test/test_apply_ops.py +510 -0
  60. abletoolz-2.1.0/test/test_au.py +122 -0
  61. {abletoolz-2.0.0 → abletoolz-2.1.0}/test/test_cli.py +53 -0
  62. abletoolz-2.1.0/test/test_cli_battery.py +306 -0
  63. abletoolz-2.1.0/test/test_clips.py +179 -0
  64. abletoolz-2.1.0/test/test_clips_write.py +522 -0
  65. abletoolz-2.1.0/test/test_describe.py +272 -0
  66. abletoolz-2.1.0/test/test_devices.py +173 -0
  67. abletoolz-2.1.0/test/test_devices_write.py +358 -0
  68. {abletoolz-2.0.0 → abletoolz-2.1.0}/test/test_dj_crates.py +50 -18
  69. abletoolz-2.1.0/test/test_fabfilter.py +773 -0
  70. abletoolz-2.1.0/test/test_families.py +271 -0
  71. abletoolz-2.1.0/test/test_format_translation.py +406 -0
  72. abletoolz-2.1.0/test/test_mapping.py +555 -0
  73. abletoolz-2.1.0/test/test_maschine.py +167 -0
  74. abletoolz-2.1.0/test/test_meta.py +264 -0
  75. abletoolz-2.1.0/test/test_persistence.py +110 -0
  76. abletoolz-2.1.0/test/test_plugin_db.py +408 -0
  77. abletoolz-2.1.0/test/test_read_plugin_files.py +248 -0
  78. abletoolz-2.1.0/test/test_repair.py +989 -0
  79. abletoolz-2.1.0/test/test_report.py +230 -0
  80. abletoolz-2.1.0/test/test_sample_fix.py +181 -0
  81. abletoolz-2.1.0/test/test_sections.py +547 -0
  82. {abletoolz-2.0.0 → abletoolz-2.1.0}/test/test_serato_sample.py +1 -1
  83. abletoolz-2.1.0/test/test_state.py +356 -0
  84. {abletoolz-2.0.0 → abletoolz-2.1.0}/test/test_version_matrix.py +67 -5
  85. abletoolz-2.1.0/test/test_vst3.py +266 -0
  86. abletoolz-2.1.0/test/test_xfadelooper.py +230 -0
  87. abletoolz-2.1.0/test/test_xml_edit.py +113 -0
  88. abletoolz-2.0.0/abletoolz/cli.py +0 -462
  89. abletoolz-2.0.0/abletoolz/console.py +0 -80
  90. abletoolz-2.0.0/abletoolz/live_set/plugins.py +0 -246
  91. abletoolz-2.0.0/abletoolz/plugin_parsers/__init__.py +0 -68
  92. abletoolz-2.0.0/abletoolz/plugin_parsers/read_plugin_files.py +0 -173
  93. abletoolz-2.0.0/abletoolz.egg-info/SOURCES.txt +0 -55
  94. abletoolz-2.0.0/test/test_cli_battery.py +0 -167
  95. abletoolz-2.0.0/test/test_persistence.py +0 -52
  96. abletoolz-2.0.0/test/test_sample_fix.py +0 -95
  97. {abletoolz-2.0.0 → abletoolz-2.1.0}/LICENSE +0 -0
  98. {abletoolz-2.0.0 → abletoolz-2.1.0}/abletoolz/asd/__init__.py +0 -0
  99. {abletoolz-2.0.0 → abletoolz-2.1.0}/abletoolz/asd/writer.py +0 -0
  100. {abletoolz-2.0.0 → abletoolz-2.1.0}/abletoolz/data/__init__.py +0 -0
  101. {abletoolz-2.0.0 → abletoolz-2.1.0}/abletoolz/data/clip_template.alc +0 -0
  102. {abletoolz-2.0.0 → abletoolz-2.1.0}/abletoolz/live_set/__init__.py +0 -0
  103. {abletoolz-2.0.0 → abletoolz-2.1.0}/abletoolz/live_set/transport.py +0 -0
  104. {abletoolz-2.0.0 → abletoolz-2.1.0}/abletoolz/sample_databaser/__init__.py +0 -0
  105. {abletoolz-2.0.0 → abletoolz-2.1.0}/abletoolz/schema.py +0 -0
  106. {abletoolz-2.0.0 → abletoolz-2.1.0}/abletoolz.egg-info/dependency_links.txt +0 -0
  107. {abletoolz-2.0.0 → abletoolz-2.1.0}/abletoolz.egg-info/entry_points.txt +0 -0
  108. {abletoolz-2.0.0 → abletoolz-2.1.0}/abletoolz.egg-info/top_level.txt +0 -0
  109. {abletoolz-2.0.0 → abletoolz-2.1.0}/setup.cfg +0 -0
  110. {abletoolz-2.0.0 → abletoolz-2.1.0}/test/test_alc.py +0 -0
  111. {abletoolz-2.0.0 → abletoolz-2.1.0}/test/test_asd.py +0 -0
  112. {abletoolz-2.0.0 → abletoolz-2.1.0}/test/test_versioning.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: abletoolz
3
- Version: 2.0.0
3
+ Version: 2.1.0
4
4
  Summary: Tools made for editing, fixing and analyzing Ableton Live sets.
5
5
  Author: ElixirBeats
6
6
  License: GNU GENERAL PUBLIC LICENSE
@@ -707,23 +707,24 @@ Requires-Dist: pytest-cov; extra == "dev"
707
707
  Requires-Dist: mypy; extra == "dev"
708
708
  Requires-Dist: ruff; extra == "dev"
709
709
  Requires-Dist: types-PyYAML; extra == "dev"
710
+ Requires-Dist: types-colorama; extra == "dev"
711
+ Requires-Dist: types-tqdm; extra == "dev"
710
712
  Dynamic: license-file
711
713
 
712
714
  ![Abletoolz](https://github.com/elixirbeats/abletoolz/raw/master/doc/gradient.png)
713
715
  # Abletoolz
714
716
 
715
- Abletoolz is a Python command line tool to edit, fix and analyze Ableton Live sets. Primarily the purpose is to
716
- automate things that aren't available in Live and make your life easier.
717
+ Abletoolz is a command line tool to edit, fix and analyze Ableton Live sets. Primarily the purpose is
718
+ to automate things that aren't available in Live and make your life easier. It is also a Python library —
719
+ if you write Python, jump down to [Using as a library](#using-as-a-library).
717
720
  It can:
718
721
  - Run on one set, or an entire directory of sets. So you can fix/analyze everything with one command.
719
722
  - Color all your tracks/clips with random color gradients.
720
723
  - Create a sample database of all your sample folders, which can then be used to automatically fix any broken samples in your ableton sets.
721
724
  - Set all your Master/Cue outputs to a specific output, so if you buy a new audio interface you can fix all your master outs to point to 7/8 in one go.
722
- - Validate all plugins in a set are installed. MacOS VST3s currently do not work for this.
723
- - Analyze plugin state with plugin-specific parsers, and fix what they understand (Serato Sample's missing sample paths, for a start).
725
+ - Validate all plugins in a set are installed.
726
+ - Analyze plugin state with plugin-specific parsers, and fix what they understand — like sample paths inside a plugin's saved state that no longer exist on disk. `--list-parsers` shows what's registered.
724
727
  - Fold/Unfold all tracks, and/or set track height and widths.
725
- - Prepend the set version name to the beginning of the file.
726
- - Append the number of bars of the track, and the bpm to the end of the file.
727
728
  - Dump the XML of the set, in case you want to dissect how they are structured or contribute to this project : )
728
729
 
729
730
  It also:
@@ -732,6 +733,27 @@ It also:
732
733
  - Works on sets from Ableton 8.2 through Live 12, including current Live 12 betas (not every command works with the oldest versions).
733
734
  - Preserves the original set modification time.
734
735
 
736
+ ## What's new in 2.1
737
+
738
+ - **Repair plugins Live can no longer load.** `--repair-plugins` rewrites dead VST2 devices as the VST3 you
739
+ have installed and brings the saved patch along, so an old set comes back with its settings instead of
740
+ blank devices. You decide what becomes what: `--suggest-plugin-mappings` drafts that table from the
741
+ plugins on your machine and `--plugin-db` scans them once so it's quick.
742
+ - **Every run writes down what it found.** Each set gets a `.meta.yaml` sidecar — Live version, bars, bpm,
743
+ what was missing, what was fixed — with `status` and `notes` fields that are yours to fill in and are
744
+ never overwritten. Each run also writes a JSON report of everything it did. The sidecar doubles as a
745
+ cache, so scanning a big library a second time is much faster, and `--output` keeps it all in a folder
746
+ of your choosing instead of next to your sets.
747
+ - **Plugin checks work properly on macOS.** VST3s are found as bundles and Audio Units are matched by their
748
+ component identity, so a plugin that moved or got renamed is still found.
749
+ - **A lot more you can do from Python.** `AbletonSet` picked up `clips` and `devices`, so writing MIDI notes,
750
+ copying a section of the arrangement and grafting a device chain from one set onto another are all
751
+ possible now. See [Using as a library](#using-as-a-library).
752
+ - **More sampler parsers**, so more plugins can have the broken sample and kit paths inside their own saved
753
+ state found and fixed. `--list-parsers` shows the current roster.
754
+ - `--append-bars-bpm` and `--prepend-version` are deprecated — they still work, but the bars, bpm and Live
755
+ version now land in each set's sidecar without renaming your files.
756
+
735
757
  ## What's new in 2.0
736
758
 
737
759
  - **Live 12 support.** Live 12 renamed core elements (`MasterTrack` → `MainTrack`, and it finally fixed the
@@ -746,7 +768,6 @@ It also:
746
768
  projects: a set full of dead 32-bit plugins can be retargeted at the modern versions you actually have
747
769
  installed, and where a translator exists for the plugin's state format, your old settings come along instead
748
770
  of being lost. It can even translate one plugin into a different one once someone decodes both formats.
749
- Serato Sample is the first supported parser (finds and fixes its broken sample paths).
750
771
  - **Safer saves.** Sets are serialized before anything on disk is touched — a failure can no longer leave you
751
772
  without your original file (which was always backed up, but still).
752
773
  - **Honest batch results.** Directory runs process sets concurrently (`--jobs`), report `N ok, M failed`, and
@@ -775,9 +796,10 @@ Once you verify you have python 3.12+, install with pip:
775
796
  pip install abletoolz
776
797
  ```
777
798
  This will install abletoolz as a command in your command line, you can now call `abletoolz` from anywhere if the
778
- installation completed successfully. (Create an issue if you run into any errors please!)
799
+ installation completed successfully, but make sure to open a new shell before trying.
779
800
 
780
801
  ## Usage:
802
+
781
803
  `-h` Print argument usage.
782
804
  `-v` Verbosity. For some commands, displays more information.
783
805
 
@@ -808,15 +830,15 @@ without quotes, backslashes are fine (but you'll need to use quotes if you have
808
830
  sample as long as one of the two are valid. If relative path doesn't exist(Not collected and saved) only absolute path
809
831
  is checked. By default only missing samples are displayed to reduce clutter, use `-v` to show all found samples as well.
810
832
 
811
- `--check-plugins` Checks plugin VST paths and verifies they exist. **Note**: When loading a set, if Ableton finds the
833
+ `--check-plugins` Checks installed VST and Audio Unit references. **Note**: When loading a set, if Ableton finds the
812
834
  same plugin name in a different path it will automatically fix any broken paths the next time you save your project. This
813
835
  command attempts to find missing VSTs and show an updated path if it finds one that Ableton will most likely load.
814
- Mac Audio Units/AU are not stored with paths, just plugin names. Mac OS is not supported for this yet.
815
836
  ```
816
837
  [MidiTrack: 1-Serum] Plugin: Serum_x64.dll, Path: C:\Program Files\VstPlugins\Xfer\Serum_x64.dll, Exists: True
817
838
  [AudioTrack: 2-Audio] Plugin: Effectrix.dll, Path: C:\Program Files\VstPlugins\Effectrix.dll, Exists: True
818
839
  [AudioTrack: 3-Audio] Plugin: DrumLeveler.dll, Path: None, Exists: False
819
- Mac OS Audio Units are not saved with paths. Plugin FabFilter: FF Pro-Q 2 cannot be verified.
840
+ [AudioTrack: 4-Audio] Plugin: Apple: AUBandpass, Path: None, Exists: True
841
+ [MidiTrack: 5-MIDI] Plugin: Arturia: Analog Lab V, Path: /Library/Audio/Plug-Ins/Components/Analog Lab V.component, Exists: True
820
842
  ```
821
843
 
822
844
  `--list-tracks` List track information.
@@ -832,17 +854,58 @@ Track type ReturnTrack, Name A-Reverb, Id 2, Group id -1, Color 60, Wid
832
854
 
833
855
  Plugin state inside a set is an opaque buffer per plugin; parsers teach abletoolz specific formats.
834
856
 
857
+ Only a handful of plugins are covered currently, so out of the box this won't fix your sets.. but the value is in the framework being able to manipulate those plugin buffers/shapes easily so it is possible to reverse engineer and recover old sets or turn them into newer plugins even. This depends entirely on if you'll be able to decode the buffer. Once you figure out the format (and I recommend using agents to help you do this), from then on every device of that plugin across every set you own is fixable
858
+ with one command. Old collections tend to be broken by the same few plugins over and over, so each one
859
+ cracked recovers sets in bulk.
860
+
835
861
  `--list-parsers` List registered plugin parsers and their buffer formats.
836
862
 
837
- `--analyze-plugins` Deep analysis using the registered parsers — reports issues like missing samples inside
838
- Serato Sample instances.
863
+ `--analyze-plugins` Deep analysis using the registered parsers — reports issues like sample or kit
864
+ references inside a plugin's state that are no longer on disk.
839
865
 
840
- `--fix-plugins` Fix what a parser knows how to fix (e.g. broken sample paths inside Serato Sample), using the
841
- sample database. Use with `-s` to write changes.
866
+ `--fix-plugins` Fix what a parser knows how to fix — typically broken sample paths inside a plugin's
867
+ state — using the sample database. Use with `-s` to write changes.
842
868
 
843
869
  `--upgrade-plugins` Upgrade plugin paths when a rule and an installed target exist. Rules live in a config file
844
870
  so nothing is guessed.
845
871
 
872
+ `--repair-plugins` Replace the plugin devices Live can no longer load with what your mappings say they
873
+ become, keeping the patch they were saved with. A device that still loads is left alone, and a broken device
874
+ with no mapping is reported by name rather than guessed at. Use with `-s` to write changes.
875
+
876
+ There is no format to choose on the command line, because the mapping says which way each plugin goes:
877
+
878
+ ```yaml
879
+ plugin_translation:
880
+ targets:
881
+ "Some Plugin.64": # the name the VST2 device stores
882
+ to: vst3 # the format it becomes (this is the default)
883
+ name: "Some Plugin" # the name the VST3 goes by
884
+ state: verbatim # or a named state policy a parser provides
885
+ ```
886
+
887
+ `--plugin-db` Scan every plugin installed on this machine into a local plugin database — the plugin half of
888
+ `--db`. `--repair-plugins` and `--suggest-plugin-mappings` read it, so run this once first (and again after
889
+ installing plugins). Takes no set and no folders; add folders under `plugin_database.paths` in your config,
890
+ the way `sample_database.paths` works.
891
+
892
+ ```yaml
893
+ plugin_database:
894
+ paths:
895
+ - "D:/VstPlugins"
896
+ ```
897
+
898
+ `--suggest-plugin-mappings [file.yaml]` Write that mapping table for you, or most of it. It takes no set: it
899
+ pairs the names that look like the same plugin in two formats and writes a YAML file for you to read. Every
900
+ suggestion aims at something you actually have installed; where nothing can perform a translation yet, it
901
+ says so.
902
+
903
+ **Everything it writes comes out commented, including the suggestions that look certain.** Uncommenting a
904
+ line is how you enable it, and that is meant to be a decision you make one plugin at a time. Each line says
905
+ how sure the match is and flags the ones to watch — a version change that won't load the old patch, or two
906
+ plugins whose names agree and whose vendors do not, which is a different plugin wearing a familiar name.
907
+ Nothing is in force until you move a line into your own config file, which this never touches.
908
+
846
909
  `--dump-plugins` Dump plugin buffer hex + decoded preview — the starting point for writing a new parser.
847
910
  Contributions welcome.
848
911
 
@@ -919,12 +982,39 @@ development. You can edit this xml file, rename it from `.xml` to `.als` and Ab
919
982
  option multiple times, the previous xml file will be moved into the `abletoolz_backup`
920
983
  folder with the same renaming behavior as `-s/--save`.
921
984
 
922
- `--append-bars-bpm` Used with `-s/--save`, appends the longest clip or furthest arrangement bar length and bpm to the
923
- set name. For example,
924
- `myset.als` --> `myset_32bars_90bpm.als`. Running this multiple times overwrites this section only (so your filename
925
- wont keep growing).
985
+ ### Records - what a run writes down
986
+
987
+ Runs also save their results to files, so you can look back later or process them with other tools.
988
+
989
+ **Per-set sidecars.** Any run that checks or fixes plugins or samples leaves a `<set name>.meta.yaml` beside
990
+ each set it looked at: the Live version, bars and bpm, the missing plugins by device count, what was fixed,
991
+ the missing samples, and the hash of the set as it was read. Underneath there are two fields that are yours —
992
+ `status` and `notes` — plus anything else you care to add. abletoolz reads them, carries them across every
993
+ rescan and never writes them itself.
994
+ ```yaml
995
+ status: needs work
996
+ notes: reverb tail runs long in the breakdown
997
+ scan:
998
+ scanned: '2026-08-17T10:16:07.959317-07:00'
999
+ scanned_with: abletoolz 2.0.0
1000
+ set_hash: 499af899b43a5ad850688c93ddbdeaae30602101fc005f7d2a7adec1e329eea9
1001
+ live_version: Ableton Live 11.3.42
1002
+ bars: 128
1003
+ bpm: 174.0
1004
+ plugins_missing:
1005
+ Serum_x64: 2
1006
+ samples_missing: 3
1007
+ ```
1008
+ The sidecar also acts as a cache: if a set hasn't changed since the last scan, the run reuses the stored
1009
+ results and prints `Cached`, which makes a second pass over a big library much faster. Edit the set and it
1010
+ gets scanned again from scratch. `--no-meta` turns the whole thing off.
926
1011
 
927
- `--prepend-version` Puts the ableton version used to create set at beginning of file name.
1012
+ **Run reports.** Every run writes `abletoolz_report_<timestamp>.json` into the folder it processed: one
1013
+ record per set (what changed, what was fixed, what was missing, what was refused and why, what failed to
1014
+ parse), plus totals for the whole run — like every missing plugin across your library with counts. Useful
1015
+ when you want to script against results instead of reading console output.
1016
+
1017
+ With `--output`, both land in the output folder instead. Nothing is written next to the originals.
928
1018
 
929
1019
  ## Examples
930
1020
  Check all samples in sets
@@ -967,35 +1057,136 @@ Saved set to D:\all_sets\myset.als
967
1057
  Took 0:00:00.371096 to process 1 set(s): 1 ok, 0 failed
968
1058
  ```
969
1059
 
1060
+ ## Using as a library
1061
+
1062
+ abletoolz started life as a command line tool, but the more powerful way to use it now is as a Python
1063
+ library. The command line above is this library pointed at a whole collection. A set is an object: open
1064
+ one, read what is in it, change it, save it or create one from scratch. `AbletonSet` is the
1065
+ document, and what a set holds hangs off it as `transport`, `tracks`, `clips`, `devices`, `samples` and
1066
+ `plugins`. Nothing reaches disk until you call `save_set()`, which moves the original into the backup
1067
+ folder first, exactly as `-s/--save` does.
1068
+
1069
+ **Read a set's tempo, length and tracks.**
1070
+
1071
+ ```python
1072
+ import pathlib
1073
+
1074
+ from abletoolz.live_set import AbletonSet
1075
+
1076
+ live_set = AbletonSet(pathlib.Path("my_set.als"))
1077
+ live_set.parse()
1078
+ length = live_set.transport.length()
1079
+ print(f"{live_set.version}: {length.bpm} bpm, {length.bars} bars, {length}")
1080
+ for track in live_set.tracks.load():
1081
+ print(track.type, track.name, "height", track.height, "color", track.color)
970
1082
  ```
971
- abletoolz "D:\all_sets\myset.als" -s --append-bars-bpm
1083
+
1084
+ **Find missing samples and fix them.** Build the sample database once — this is what `--db` does — and hand it to
1085
+ any set.
1086
+
1087
+ ```python
1088
+ import pathlib
1089
+
1090
+ from abletoolz.live_set import AbletonSet
1091
+ from abletoolz.sample_databaser import create_db
1092
+
1093
+ create_db.create_or_update_db(["D:/samples"], db_path=pathlib.Path("sample_db.json"))
1094
+ db = create_db.load_db(pathlib.Path("sample_db.json"))
1095
+ live_set = AbletonSet(pathlib.Path("my_set.als"))
1096
+ live_set.parse()
1097
+ live_set.find_project_root_folder() # where collect-and-save copies to
1098
+ for ref in live_set.samples.check():
1099
+ print("missing:", ref.name, ref.absolute)
1100
+ if live_set.samples.fix(db, collect_and_save=True):
1101
+ live_set.save_set()
972
1102
  ```
1103
+
1104
+ **Check that every plugin a set uses is still installed.** Each reference comes back with where it points, whether
1105
+ that file is there, and the closest thing found on this machine when it is not.
1106
+
1107
+ ```python
1108
+ import pathlib
1109
+
1110
+ from abletoolz.live_set import AbletonSet
1111
+
1112
+ live_set = AbletonSet(pathlib.Path("my_set.als"))
1113
+ live_set.parse()
1114
+ for ref in live_set.plugins.scan([pathlib.Path("D:/VstPlugins")]):
1115
+ where = ref.path if ref.exists else ref.alternative or "nowhere on this machine"
1116
+ print(f"{ref.track_location}: {ref.name} ({ref.kind}) -> {where}")
973
1117
  ```
974
- Appending bars and bpm, new set name: myset_400bars_124.00bpm.als
975
- Saved set to D:\all_sets\myset_400bars_124.00bpm.als
976
- Restored creation and modification times: 05/17/2020 05:13:08, 05/17/2020 05:13:12
1118
+
1119
+ **Write MIDI notes into a clip.** A note is pitch, start and duration in beats; everything else defaults to what
1120
+ Live gives a freshly drawn note.
1121
+
1122
+ ```python
1123
+ import pathlib
1124
+
1125
+ from abletoolz.live_set import AbletonSet
1126
+ from abletoolz.live_set.clips import MidiNote
1127
+
1128
+ live_set = AbletonSet(pathlib.Path("my_set.als"))
1129
+ live_set.parse()
1130
+ clip = live_set.clips.midi()[0]
1131
+ bar = [MidiNote(pitch, start=float(beat), duration=0.5) for beat, pitch in enumerate((36, 42, 38, 42))]
1132
+ live_set.clips.set_notes(clip, bar)
1133
+ live_set.save_set()
977
1134
  ```
978
1135
 
979
- ## Library use
1136
+ **Copy a section of the arrangement somewhere else.** Clips and track automation inside the window travel
1137
+ together, and a clip crossing either edge is reported rather than trimmed.
980
1138
 
981
- Everything the CLI does is available programmatically:
1139
+ ```python
1140
+ import pathlib
1141
+
1142
+ from abletoolz.live_set import AbletonSet
1143
+ from abletoolz.live_set.sections import SectionMode
1144
+
1145
+ live_set = AbletonSet(pathlib.Path("my_set.als"))
1146
+ live_set.parse()
1147
+ report = live_set.clips.copy_section(64.0, 128.0, 448.0, mode=SectionMode.REFUSE) # beats
1148
+ for line in report.lines():
1149
+ print(line)
1150
+ live_set.save_set()
1151
+ ```
1152
+
1153
+ **Graft a device chain from one set onto a track in another.** Both sets have to be the same major Live version,
1154
+ and the chain arrives as a copy Live will open rather than a paste that takes the set down with it.
982
1155
 
983
1156
  ```python
1157
+ import pathlib
1158
+
984
1159
  from abletoolz.live_set import AbletonSet
985
1160
 
986
- live_set = AbletonSet(pathlib.Path("myset.als"))
1161
+ donor = AbletonSet(pathlib.Path("donor_set.als"))
1162
+ donor.parse()
1163
+ chain = next(track for track in donor.devices.inventory() if track.track_name == "Kick")
1164
+ live_set = AbletonSet(pathlib.Path("my_set.als"))
987
1165
  live_set.parse()
988
- print(live_set.version_tuple, live_set.transport.bpm())
989
- for track in live_set.tracks.load():
990
- print(track.type, track.name, track.color)
991
- missing = live_set.samples.check()
1166
+ for device in live_set.devices.graft_chain(chain, "2-Audio", mode="append"):
1167
+ print("grafted", device.display_name)
1168
+ live_set.save_set()
1169
+ ```
1170
+
1171
+ **Read back the sidecar a scan left.** It comes out as an object rather than a blob of YAML, your own `status` and
1172
+ `notes` included — enough to triage a whole library without opening a set.
1173
+
1174
+ ```python
1175
+ import pathlib
1176
+
1177
+ from abletoolz import meta
1178
+
1179
+ sidecar = meta.read(pathlib.Path("my_set.als"))
1180
+ if sidecar is not None and sidecar.scan is not None:
1181
+ print(sidecar.scan.live_version, sidecar.scan.bars, sidecar.scan.bpm)
1182
+ print("missing plugins:", sidecar.scan.plugins_missing)
1183
+ print("missing samples:", sidecar.scan.samples_missing)
1184
+ print("yours:", sidecar.status, "--", sidecar.notes)
992
1185
  ```
993
1186
 
994
1187
  ## Future plans:
995
- - Export sets as an older Live version (Live's compatibility is one-directional; most of the schema differences
996
- are mechanical).
1188
+ - Export sets as an any / older Live versions (Live's compatibility is one-directional; most of the schema differences
1189
+ are mechanical). Some of this is partially implemented.
997
1190
  - More plugin parsers — the upgrade framework is in place, formats need decoding one plugin at a time.
998
- - Figure out way to verify AU plugins on MacOs.
999
- - Analyze audio clips and color them based on a Serato like gradient(red for bass, turqoise for hi end etc...)
1000
1191
  - Figure out how ableton calculates CRC's for samples and use it to make perfect sample fixing. The current algorithm has a very low probability of being wrong, but this would guarantee each result is correct.
1001
- - Attempt to detect key based on non drum track midi notes.
1192
+ - More AI agentic tool interaction interfacing