logicxkit 0.2.0__tar.gz → 0.3.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 (338) hide show
  1. logicxkit-0.3.0/CHANGELOG.md +192 -0
  2. {logicxkit-0.2.0 → logicxkit-0.3.0}/CONTRIBUTING.md +9 -0
  3. {logicxkit-0.2.0 → logicxkit-0.3.0}/MANIFEST.in +1 -1
  4. {logicxkit-0.2.0/src/logicxkit.egg-info → logicxkit-0.3.0}/PKG-INFO +22 -9
  5. {logicxkit-0.2.0 → logicxkit-0.3.0}/README.md +21 -8
  6. logicxkit-0.3.0/bin/regen_data.py +252 -0
  7. {logicxkit-0.2.0 → logicxkit-0.3.0}/docs/CAPABILITIES.md +13 -8
  8. {logicxkit-0.2.0 → logicxkit-0.3.0}/docs/INSTALLATION.md +5 -3
  9. {logicxkit-0.2.0 → logicxkit-0.3.0}/docs/README.md +1 -0
  10. {logicxkit-0.2.0 → logicxkit-0.3.0}/docs/commands.md +26 -3
  11. {logicxkit-0.2.0 → logicxkit-0.3.0}/pyproject.toml +2 -2
  12. logicxkit-0.3.0/src/logicxkit/data/donors/147-v5.slot +0 -0
  13. logicxkit-0.3.0/src/logicxkit/data/donors/150-v5.slot +0 -0
  14. logicxkit-0.3.0/src/logicxkit/data/donors/154-v5.slot +0 -0
  15. logicxkit-0.3.0/src/logicxkit/data/donors/157-v5.slot +0 -0
  16. logicxkit-0.3.0/src/logicxkit/data/donors/158-v5.slot +0 -0
  17. logicxkit-0.3.0/src/logicxkit/data/donors/166-v5.slot +0 -0
  18. logicxkit-0.3.0/src/logicxkit/data/donors/183-v5.slot +0 -0
  19. logicxkit-0.3.0/src/logicxkit/data/donors/236-v5.slot +0 -0
  20. logicxkit-0.3.0/src/logicxkit/data/donors/287-v5.slot +0 -0
  21. logicxkit-0.3.0/src/logicxkit/data/donors/manifest.json +65 -0
  22. logicxkit-0.3.0/src/logicxkit/data/logic/arrangement-track-12.3.1.json +45 -0
  23. logicxkit-0.3.0/src/logicxkit/data/logic/audio-channel-12.3.1.json +5 -0
  24. logicxkit-0.3.0/src/logicxkit/data/logic/audio-region-12.3.1.json +16 -0
  25. logicxkit-0.3.0/src/logicxkit/data/logic/aux-track-12.3.1.json +5 -0
  26. logicxkit-0.3.0/src/logicxkit/data/logic/group-12.3.1.json +15 -0
  27. logicxkit-0.3.0/src/logicxkit/data/logic/inst-track-12.3.1.json +7 -0
  28. logicxkit-0.3.0/src/logicxkit/data/logic/midi-region-12.3.1.json +20 -0
  29. logicxkit-0.3.0/src/logicxkit/data/logic/rba-sequence-12.3.1.json +9 -0
  30. logicxkit-0.3.0/src/logicxkit/data/logic/section-text-12.3.1.json +5 -0
  31. logicxkit-0.3.0/src/logicxkit/data/logic/send-12.3.1.json +5 -0
  32. logicxkit-0.3.0/src/logicxkit/data/logic/stack-folder-12.3.1.json +21 -0
  33. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/_capabilities.py +76 -13
  34. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/_chains_cmd.py +2 -2
  35. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/_groups.py +10 -6
  36. logicxkit-0.3.0/src/logicxkit/logic/_midi_cmd.py +118 -0
  37. logicxkit-0.3.0/src/logicxkit/logic/_patch_cmd.py +83 -0
  38. logicxkit-0.3.0/src/logicxkit/logic/_plugins_cmd.py +54 -0
  39. logicxkit-0.3.0/src/logicxkit/logic/_quantize_cmd.py +88 -0
  40. logicxkit-0.3.0/src/logicxkit/logic/_regions_cmd.py +88 -0
  41. logicxkit-0.3.0/src/logicxkit/logic/_sessionplayer_cmd.py +37 -0
  42. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/cli.py +15 -2
  43. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/arrangement_write.py +72 -10
  44. logicxkit-0.3.0/src/logicxkit/logic/services/audio_regions.py +131 -0
  45. logicxkit-0.3.0/src/logicxkit/logic/services/audio_write.py +284 -0
  46. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/chain_report.py +2 -1
  47. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/donors.py +17 -11
  48. logicxkit-0.3.0/src/logicxkit/logic/services/flexmarkers.py +124 -0
  49. logicxkit-0.3.0/src/logicxkit/logic/services/flexmode.py +48 -0
  50. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/groups.py +56 -57
  51. logicxkit-0.3.0/src/logicxkit/logic/services/midi.py +133 -0
  52. logicxkit-0.3.0/src/logicxkit/logic/services/midi_write.py +238 -0
  53. logicxkit-0.3.0/src/logicxkit/logic/services/onsets.py +137 -0
  54. logicxkit-0.3.0/src/logicxkit/logic/services/patch.py +225 -0
  55. logicxkit-0.3.0/src/logicxkit/logic/services/plugins.py +106 -0
  56. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/project.py +17 -2
  57. logicxkit-0.3.0/src/logicxkit/logic/services/quantize_drums.py +247 -0
  58. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/regions.py +16 -1
  59. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/registry.py +13 -0
  60. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/sends_write.py +13 -8
  61. logicxkit-0.3.0/src/logicxkit/logic/services/sessionplayer.py +95 -0
  62. logicxkit-0.3.0/src/logicxkit/logic/services/smf.py +91 -0
  63. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/stack_create.py +53 -15
  64. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/tempo.py +2 -0
  65. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/tempo_write.py +33 -12
  66. logicxkit-0.3.0/src/logicxkit/utils/data.py +58 -0
  67. {logicxkit-0.2.0 → logicxkit-0.3.0/src/logicxkit.egg-info}/PKG-INFO +22 -9
  68. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit.egg-info/SOURCES.txt +64 -0
  69. logicxkit-0.3.0/tests/goldens/corpus.json +4 -0
  70. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/goldens/manifest.json +1034 -0
  71. logicxkit-0.3.0/tests/goldens/test_audio_regions.py +36 -0
  72. logicxkit-0.3.0/tests/goldens/test_audio_write.py +267 -0
  73. logicxkit-0.3.0/tests/goldens/test_bypass.py +25 -0
  74. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/goldens/test_groups.py +20 -0
  75. logicxkit-0.3.0/tests/goldens/test_midi.py +94 -0
  76. logicxkit-0.3.0/tests/goldens/test_midi_export.py +129 -0
  77. logicxkit-0.3.0/tests/goldens/test_midi_targeting.py +119 -0
  78. logicxkit-0.3.0/tests/goldens/test_midi_write.py +158 -0
  79. logicxkit-0.3.0/tests/goldens/test_patch.py +55 -0
  80. logicxkit-0.3.0/tests/goldens/test_plugins.py +20 -0
  81. logicxkit-0.3.0/tests/goldens/test_quantize_drums.py +186 -0
  82. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/goldens/test_reorder.py +11 -0
  83. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/goldens/test_routing.py +15 -0
  84. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/goldens/test_sends_level.py +13 -5
  85. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/goldens/test_sends_write.py +19 -0
  86. logicxkit-0.3.0/tests/goldens/test_sessionplayer.py +46 -0
  87. logicxkit-0.3.0/tests/goldens/test_signature.py +54 -0
  88. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/goldens/test_signature_write.py +45 -2
  89. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/goldens/test_song_write.py +68 -1
  90. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/goldens/test_stack_create.py +41 -0
  91. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/goldens/test_transplant.py +15 -0
  92. logicxkit-0.3.0/tests/logic/test_audio_write.py +79 -0
  93. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/test_capabilities.py +10 -7
  94. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/test_cli_paths.py +2 -1
  95. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/test_donors.py +19 -0
  96. logicxkit-0.3.0/tests/logic/test_flexmarkers.py +100 -0
  97. logicxkit-0.3.0/tests/logic/test_flexmode.py +48 -0
  98. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/test_groups.py +48 -9
  99. logicxkit-0.3.0/tests/logic/test_midi_write.py +56 -0
  100. logicxkit-0.3.0/tests/logic/test_onsets.py +170 -0
  101. logicxkit-0.3.0/tests/logic/test_patch.py +87 -0
  102. logicxkit-0.3.0/tests/logic/test_patch_build.py +196 -0
  103. logicxkit-0.3.0/tests/logic/test_plugins.py +88 -0
  104. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/test_project.py +26 -0
  105. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/test_regions.py +32 -0
  106. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/test_sends_write.py +8 -5
  107. logicxkit-0.3.0/tests/logic/test_smf.py +97 -0
  108. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/test_stack_create.py +36 -0
  109. logicxkit-0.3.0/tests/logic/test_tempo_write.py +27 -0
  110. logicxkit-0.3.0/tests/test_clean_install_writers.py +59 -0
  111. logicxkit-0.3.0/tests/test_packaged_data.py +76 -0
  112. logicxkit-0.3.0/tests/test_regen_data.py +35 -0
  113. logicxkit-0.2.0/CHANGELOG.md +0 -102
  114. logicxkit-0.2.0/src/logicxkit/utils/data.py +0 -49
  115. logicxkit-0.2.0/tests/goldens/corpus.json +0 -2
  116. logicxkit-0.2.0/tests/goldens/test_signature.py +0 -32
  117. {logicxkit-0.2.0 → logicxkit-0.3.0}/.env.example +0 -0
  118. {logicxkit-0.2.0 → logicxkit-0.3.0}/CODE_OF_CONDUCT.md +0 -0
  119. {logicxkit-0.2.0 → logicxkit-0.3.0}/LICENSE +0 -0
  120. {logicxkit-0.2.0 → logicxkit-0.3.0}/NOTICE +0 -0
  121. {logicxkit-0.2.0 → logicxkit-0.3.0}/bin/fetch_corpus.py +0 -0
  122. {logicxkit-0.2.0 → logicxkit-0.3.0}/config/example-chains.json +0 -0
  123. {logicxkit-0.2.0 → logicxkit-0.3.0}/config/example-psts.json +0 -0
  124. {logicxkit-0.2.0 → logicxkit-0.3.0}/config/example-retrack.json +0 -0
  125. {logicxkit-0.2.0 → logicxkit-0.3.0}/config/example-strips.json +0 -0
  126. {logicxkit-0.2.0 → logicxkit-0.3.0}/setup.cfg +0 -0
  127. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/__init__.py +0 -0
  128. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/au/__init__.py +0 -0
  129. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/au/_views.py +0 -0
  130. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/au/cli.py +0 -0
  131. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/au/services/__init__.py +0 -0
  132. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/au/services/aupreset.py +0 -0
  133. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/au/services/embed.py +0 -0
  134. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/au/services/ffp.py +0 -0
  135. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/au/services/host.py +0 -0
  136. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/au/services/juce.py +0 -0
  137. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/au/services/report.py +0 -0
  138. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/au/services/sonible.py +0 -0
  139. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/au/services/tables.py +0 -0
  140. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/au/services/tr5.py +0 -0
  141. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/au/services/waves.py +0 -0
  142. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/cli.py +0 -0
  143. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/__init__.py +0 -0
  144. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/_apply.py +0 -0
  145. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/_apply_template.py +0 -0
  146. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/_apply_tracks.py +0 -0
  147. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/_binary.py +0 -0
  148. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/_controlbar.py +0 -0
  149. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/_diagnose.py +0 -0
  150. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/_edit.py +0 -0
  151. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/_header.py +0 -0
  152. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/_inspect.py +0 -0
  153. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/_metronome.py +0 -0
  154. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/_modes.py +0 -0
  155. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/_prefs.py +0 -0
  156. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/_song.py +0 -0
  157. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/_toolbar.py +0 -0
  158. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/_width.py +0 -0
  159. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/orchestrators/__init__.py +0 -0
  160. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/orchestrators/apply_ops.py +0 -0
  161. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/orchestrators/apply_template.py +0 -0
  162. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/orchestrators/ops.py +0 -0
  163. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/__init__.py +0 -0
  164. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/addtrack.py +0 -0
  165. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/arrangement.py +0 -0
  166. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/binding.py +0 -0
  167. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/chains.py +0 -0
  168. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/channel_alloc.py +0 -0
  169. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/comp.py +0 -0
  170. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/controlbar.py +0 -0
  171. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/environment.py +0 -0
  172. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/eq.py +0 -0
  173. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/events.py +0 -0
  174. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/graft.py +0 -0
  175. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/header.py +0 -0
  176. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/inputs_create.py +0 -0
  177. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/insert.py +0 -0
  178. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/instout.py +0 -0
  179. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/integrity.py +0 -0
  180. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/keyflags.py +0 -0
  181. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/levels.py +0 -0
  182. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/library.py +0 -0
  183. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/limiter.py +0 -0
  184. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/manifest.py +0 -0
  185. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/metronome.py +0 -0
  186. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/modes.py +0 -0
  187. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/neural.py +0 -0
  188. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/ocr.py +0 -0
  189. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/pairing.py +0 -0
  190. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/prefs.py +0 -0
  191. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/prefs_table.py +0 -0
  192. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/projdiff.py +0 -0
  193. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/pst.py +0 -0
  194. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/recbuild.py +0 -0
  195. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/recdiff.py +0 -0
  196. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/records.py +0 -0
  197. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/reorder.py +0 -0
  198. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/retrack.py +0 -0
  199. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/routing.py +0 -0
  200. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/selection.py +0 -0
  201. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/sends.py +0 -0
  202. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/sequence.py +0 -0
  203. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/settings.py +0 -0
  204. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/signature.py +0 -0
  205. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/signature_write.py +0 -0
  206. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/slotkeys.py +0 -0
  207. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/slots.py +0 -0
  208. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/spec.py +0 -0
  209. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/stacks.py +0 -0
  210. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/stripsave.py +0 -0
  211. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/toolbar.py +0 -0
  212. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/tracklist.py +0 -0
  213. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/transplant.py +0 -0
  214. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logic/services/validate.py +0 -0
  215. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logicx/__init__.py +0 -0
  216. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/logicx/container.py +0 -0
  217. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/native/aulatency.swift +0 -0
  218. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/native/auprobe.swift +0 -0
  219. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/native/vision_ocr.swift +0 -0
  220. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/py.typed +0 -0
  221. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/utils/__init__.py +0 -0
  222. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/utils/env.py +0 -0
  223. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit/utils/swiftrun.py +0 -0
  224. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit.egg-info/dependency_links.txt +0 -0
  225. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit.egg-info/entry_points.txt +0 -0
  226. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit.egg-info/requires.txt +0 -0
  227. {logicxkit-0.2.0 → logicxkit-0.3.0}/src/logicxkit.egg-info/top_level.txt +0 -0
  228. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/_data.py +0 -0
  229. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/_goldens.py +0 -0
  230. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/_liveguard.py +0 -0
  231. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/_paths.py +0 -0
  232. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/au/_juce.py +0 -0
  233. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/au/test_au_report.py +0 -0
  234. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/au/test_aupreset.py +0 -0
  235. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/au/test_cli_views.py +0 -0
  236. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/au/test_embed.py +0 -0
  237. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/au/test_ffp.py +0 -0
  238. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/au/test_host.py +0 -0
  239. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/au/test_juce.py +0 -0
  240. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/au/test_sonible.py +0 -0
  241. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/au/test_strip_path.py +0 -0
  242. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/au/test_tables.py +0 -0
  243. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/au/test_tr5.py +0 -0
  244. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/au/test_waves.py +0 -0
  245. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/conftest.py +0 -0
  246. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/goldens/__init__.py +0 -0
  247. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/goldens/test_addtrack.py +0 -0
  248. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/goldens/test_apply_template.py +0 -0
  249. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/goldens/test_binding.py +0 -0
  250. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/goldens/test_chains.py +0 -0
  251. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/goldens/test_channel_create.py +0 -0
  252. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/goldens/test_controlbar.py +0 -0
  253. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/goldens/test_environment.py +0 -0
  254. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/goldens/test_groups_leave.py +0 -0
  255. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/goldens/test_header.py +0 -0
  256. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/goldens/test_instout.py +0 -0
  257. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/goldens/test_integrity.py +0 -0
  258. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/goldens/test_levels.py +0 -0
  259. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/goldens/test_manifest.py +0 -0
  260. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/goldens/test_metronome.py +0 -0
  261. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/goldens/test_modes.py +0 -0
  262. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/goldens/test_neural.py +0 -0
  263. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/goldens/test_projdiff.py +0 -0
  264. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/goldens/test_project.py +0 -0
  265. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/goldens/test_sends.py +0 -0
  266. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/goldens/test_sequence.py +0 -0
  267. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/goldens/test_settings.py +0 -0
  268. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/goldens/test_song.py +0 -0
  269. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/goldens/test_stack_moves.py +0 -0
  270. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/goldens/test_stacks.py +0 -0
  271. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/goldens/test_toolbar.py +0 -0
  272. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/goldens/test_width.py +0 -0
  273. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/__init__.py +0 -0
  274. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/_fixtures.py +0 -0
  275. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/_invariants.py +0 -0
  276. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/_records.py +0 -0
  277. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/test_addtrack.py +0 -0
  278. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/test_apply_template.py +0 -0
  279. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/test_binding.py +0 -0
  280. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/test_build_cli.py +0 -0
  281. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/test_chains.py +0 -0
  282. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/test_channel_alloc.py +0 -0
  283. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/test_channel_width.py +0 -0
  284. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/test_chunks.py +0 -0
  285. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/test_controlbar.py +0 -0
  286. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/test_donor_version.py +0 -0
  287. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/test_environment.py +0 -0
  288. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/test_events.py +0 -0
  289. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/test_graft.py +0 -0
  290. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/test_header.py +0 -0
  291. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/test_insert.py +0 -0
  292. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/test_instout.py +0 -0
  293. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/test_keyflags.py +0 -0
  294. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/test_levels.py +0 -0
  295. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/test_library.py +0 -0
  296. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/test_logic.py +0 -0
  297. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/test_manifest.py +0 -0
  298. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/test_modes.py +0 -0
  299. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/test_neural.py +0 -0
  300. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/test_ocr.py +0 -0
  301. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/test_pairing.py +0 -0
  302. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/test_prefs.py +0 -0
  303. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/test_projdiff.py +0 -0
  304. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/test_pst.py +0 -0
  305. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/test_recdiff.py +0 -0
  306. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/test_registry.py +0 -0
  307. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/test_reorder.py +0 -0
  308. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/test_retrack.py +0 -0
  309. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/test_routing.py +0 -0
  310. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/test_selection.py +0 -0
  311. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/test_sends.py +0 -0
  312. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/test_sequence.py +0 -0
  313. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/test_settings.py +0 -0
  314. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/test_signature.py +0 -0
  315. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/test_slot_base.py +0 -0
  316. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/test_slotkeys.py +0 -0
  317. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/test_slots.py +0 -0
  318. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/test_song.py +0 -0
  319. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/test_song_write.py +0 -0
  320. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/test_stacks.py +0 -0
  321. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/test_strip_values.py +0 -0
  322. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/test_stripsave.py +0 -0
  323. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/test_toolbar.py +0 -0
  324. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/test_transplant.py +0 -0
  325. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/test_validate.py +0 -0
  326. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/logic/test_writer_gate.py +0 -0
  327. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/rig/test_rig_preflight.py +0 -0
  328. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/test_clean_clone.py +0 -0
  329. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/test_cli_dispatch.py +0 -0
  330. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/test_env.py +0 -0
  331. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/test_fetch_corpus.py +0 -0
  332. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/test_goldens_isolation.py +0 -0
  333. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/test_goldens_report.py +0 -0
  334. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/test_goldens_resolution.py +0 -0
  335. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/test_native_assets.py +0 -0
  336. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/test_no_live_library.py +0 -0
  337. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/test_package_layering.py +0 -0
  338. {logicxkit-0.2.0 → logicxkit-0.3.0}/tests/test_typed_marker.py +0 -0
@@ -0,0 +1,192 @@
1
+ # Changelog
2
+
3
+ Notable changes to logicxkit. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/);
4
+ versions follow [semantic versioning](https://semver.org/spec/v2.0.0.html).
5
+
6
+ ## 0.3.0 — 2026-09-14
7
+
8
+ ### Added
9
+
10
+ - `logic quantize-drums`: the live-drum quantize on a copy without Logic — the drum group with
11
+ Editing (Selection) and Quantize-Locked (Audio), other groups off, Q-Reference on the
12
+ reference tracks, flex Slicing, and one flex marker per hit found in the reference audio with
13
+ its target on the 1/N grid (`services/onsets.py`, `flexmarkers.py`, `quantize_drums.py`).
14
+ CONFIRMED: Logic re-saved a written take with every marker intact.
15
+ - `logic regions` reads a region's first frame within its file.
16
+ - `midi --region` and `regions --audio` place their entry correctly on a project whose regions
17
+ carry flex markers.
18
+ - `logic regions` maps an entry to its region record by ranking the counters of every audio
19
+ entry in every sequence, take folders included; the earlier base-offset rule mislabelled a
20
+ project with take folders.
21
+ - `logic quantize-drums` takes a reference's audio from the file named after its region,
22
+ handles member regions that start at different bars, and names the files it used.
23
+ - `logic project` names a native insert from its type id when the slot carries no name string,
24
+ as `plugins` does; a built patch's eight inserts read as eight.
25
+ - The drum-quantize onset detector is tuned against the transients Logic marked across four
26
+ grids: 78% of them found, 88% of its own among them (was 55% and 90%).
27
+ - The package carries Logic's own record templates and native plug-in donor slots, regenerated
28
+ from the public corpus by `bin/regen_data.py`, so `add-track`, `stack-create`, `send --add`,
29
+ `arrangement --add` and native `chains` work after `pip install` alone; a data root
30
+ (`LOGICXKIT_DATA`) still takes precedence, and `chains` reads both donor libraries.
31
+ - `stack-create` works on a session with no stack, patterning on Logic's own first stack;
32
+ `arrangement --add` makes the arrangement track when the song has none; `send --add` works on
33
+ a project with no send to clone.
34
+ - `logic midi` reads a song's MIDI regions — notes, controllers, program changes, pitch bends,
35
+ loop flag — and `--export` writes a format-1 Standard MIDI File.
36
+ - `logic plugins` names every slot's plug-in and reports which third-party components this Mac
37
+ lacks, for one project or a folder of them.
38
+ - `logic patch` reads a Library patch bundle — its nodes, each channel's settings, strip and
39
+ plug-ins — in both shapes Logic writes.
40
+ - The group-events loss reported on 2026-09-08 does not reproduce on the blank-born project;
41
+ `tests/goldens/test_groups.py` pins create, assign, add, assign.
42
+ - `logic midi --region` and `--note` write an empty MIDI region and notes on a copy; Logic re-saved
43
+ one with its notes intact.
44
+ - `logic regions` reads every MIDI and audio region with its file record (name, format, frames,
45
+ rate, channels, bits); `--audio` imports a PCM WAV at the project's rate as a region.
46
+ - `logic sessionplayer` reads a Session Player region's drummer, preset and settings (the JSON in
47
+ its record) and its generated notes; Complexity, Fill Amount and Swing pinned by one editor
48
+ move per save.
49
+ - `logic patch --build` writes a patch bundle from a `.cst`, into Logic's own library only with
50
+ `--install`; a built patch loaded from the Library with all its inserts.
51
+ - Nine more public goldens: a third send and the slot base it moves to 4, the Signature List's
52
+ meter and key creates and edits after bar 1, and a Tempo List point and its edit.
53
+ - Sixty-two more public goldens: MIDI region writes and their re-saves, audio region writes and
54
+ a re-save, three audio imports, a Session Player track and three settings, a Library-saved
55
+ patch and a built one loaded.
56
+ - `tools/driver` and `tools/stage_public.py`: the accessibility driver that records goldens from
57
+ Logic, with the recipe and the traps; CONTRIBUTING describes recording one.
58
+ - Forty-three public goldens: the arrangement track and sections, an instrument track, a track
59
+ header click, eight native inserts, MIDI events one field per save, meter changes, and
60
+ Logic's re-saves of route, transplant, bypass, send, stack-create, arrangement and reorder
61
+ outputs.
62
+
63
+ ### Changed
64
+
65
+ - `tempo --add` and `--ramp` write each point's time word (data +8, 1/2000 s from the SMPTE
66
+ origin) as Logic computes it; Logic's own points and curve runs match to the digit.
67
+ - `route`, `transplant`, `bypass` and `reorder` are CONFIRMED: Logic re-saved one write of each
68
+ with the written bytes intact.
69
+ - `logic donors` names Logic's plug-ins that ship without factory presets (Gain, EnVerb).
70
+
71
+ ### Fixed
72
+
73
+ - `logic regions --audio` numbers an import as Logic's own imports do — entry word, record header
74
+ slots, file ordinal and link chain, registry entry, current and selected marks — and refuses a
75
+ project whose audio regions are in any other layout, or a WAV whose name it already holds,
76
+ before anything is copied. Logic re-saved two such imports with every region record kept.
77
+ - `logic midi --note` goes into the region on the named track that holds its bar, never another
78
+ track's region at the same tick, and is refused when no region or several hold it.
79
+ - `logic midi --region` writes a region's length and track for a name of any length; Logic
80
+ re-saved regions named four and nine bytes long as written.
81
+ - `logic midi --export` writes the song's tempo map and time signatures, and refuses events
82
+ before bar 1 instead of raising.
83
+ - `logic quantize-drums` reads 32/64-bit float and extensible WAVs, and refuses a song whose tempo
84
+ changes and reference audio with no hits.
85
+ - `logic patch --build` refuses a file that is not a channel strip, leaves nothing behind when it
86
+ fails, and replaces a bundle with `--overwrite` only once the new one is complete, exiting 1
87
+ with the path of a replaced bundle it could not remove.
88
+ - A channel's group field is a bitmask, one bit per group: `logic group` reads a channel that
89
+ is in several groups, or in group 3 or higher, as Logic does; `--create` keeps a member's
90
+ other groups, `--assign` moves it out of every other one.
91
+ - `logic group` shows a switched-off group and `--group N --on/--off` sets it (flags bit 31).
92
+ - `logic regions` reads a recorded project's regions: the entry counter's base, the flex
93
+ marker blocks after a flexed entry, and a flexed audio region's RBA sequence is not a MIDI
94
+ region.
95
+
96
+ ## 0.2.0 — 2026-09-12
97
+
98
+ ### Fixed
99
+
100
+ - `validate_project` checks third-party plugin slots for duplicate keys and index collisions.
101
+ - `apply-template` places each added track after the one added just above it, so a run of new
102
+ tracks lands in template order in one pass.
103
+ - `stacks --move` and `levels --to` write through the integrity gate; a refused result is
104
+ discarded.
105
+ - A project writer whose step fails part-way discards the whole copy, including alternatives
106
+ it had already written.
107
+ - `logic project` reads a channel's inserts from its plugin-slot records at slot base 2, 3 or 4;
108
+ a plugin name in a property record, such as an aux's input source, is not an insert.
109
+ - `logic header` computes the header width from the project's own name-column width and never
110
+ below Logic's 180-pixel floor, and says when a width grown from one stored on that floor is
111
+ an estimate.
112
+ - `logic controlbar` keeps a project's stored button order and inserts a newly shown control
113
+ where Logic does.
114
+ - `logic stacks` reads summing stacks (a grouping header bound to an Aux with members under
115
+ it) as well as folder stacks; `--move` refuses a summing stack.
116
+ - `logic reorder` moves a stack header together with its members, and refuses to move a row into
117
+ or out of a summing stack.
118
+ - `apply-template` moves a project's slot keys from base 2 to 4 only when a channel carries a
119
+ send at key 2.
120
+ - Bar numbers given to `arrangement`, `tempo` and `signature` follow the song's time signatures.
121
+ - `logic arrangement` refuses a non-ASCII section name.
122
+ - `build` and `pst` refuse Logic's own library without `--install` whatever
123
+ `LOGICXKIT_AUDIO_MUSIC_APPS` is set to, and refuse under the folder it names.
124
+ - `build`, `verify` and `pst` refuse a preset name that is not a plain file name.
125
+ - `logic pst` marks a failed preset `!!` and exits 1.
126
+ - `apply-template` without `--out`, `--plan` or `--propose-map` exits 2 with a message.
127
+ - A quoted `~` expands in every path argument, including lists such as `recdiff --baseline`.
128
+ - A JUCE plugin state nested past the recursion limit decodes to nothing.
129
+ - Scanning a file for embedded plists takes one parse per plist.
130
+
131
+ ### Added
132
+
133
+ - `apply-template --plan` names the session tracks the template has no counterpart for.
134
+ - `chains` and `levels --to` are CONFIRMED.
135
+ - Sends read their level (`Send.level`, `Send.level_exact`).
136
+ - Leaving a group is measured against Logic's own No Group save.
137
+ - The slot base is read from the channel records' own word when they agree, and follows the
138
+ number of sends (2, 3 or 4).
139
+ - `bin/run fetch-corpus` downloads the public golden corpus; `LOGICXKIT_REQUIRE_GOLDENS=public`
140
+ and `LOGICXKIT_GOLDENS=owner` steer the goldens between the two corpora.
141
+ - `logic image --overwrite`; without it an existing file is kept.
142
+
143
+ ## 0.1.1 — 2026-09-11
144
+
145
+ ### Fixed
146
+
147
+ - The headless AU host finds `auprobe.swift` inside the installed package, so `au params` and
148
+ state decodes use it.
149
+
150
+ ### Added
151
+
152
+ - `docs/README.md` (documentation index), `docs/INSTALLATION.md` and `docs/commands.md`.
153
+ - `py.typed`: the package is annotated and now says so to type checkers.
154
+ - `aulatency.swift` is documented in `src/logicxkit/au/README.md`; it ships in the wheel and
155
+ is run directly with `swift`.
156
+
157
+ ## 0.1.0 — 2026-09-10
158
+
159
+ First public release.
160
+
161
+ ### Added
162
+
163
+ - `logicxkit logic` — Logic Pro channel-strip (`.cst`) build and decode, read-only `.logicx`
164
+ project analysis, and writers for the track list, stacks, groups, sends, routing, channel
165
+ levels and width, arrangement, tempo, time and key signatures, transport modes, metronome,
166
+ track headers, control bar, toolbar and Logic's own settings. Every project-mutating command
167
+ requires `--out` and works on a copy.
168
+ - `logicxkit logic apply-template` — migrate a session onto a template, pairing by Environment
169
+ object id within a lineage and by an explicit map across lineages.
170
+ - `logicxkit logic diff` — project-to-project and project-to-strip-library drift.
171
+ - `logicxkit logic image` / `ocr` — extract and OCR the auto-saved WindowImage via Apple Vision.
172
+ - `logicxkit au` — Audio Unit preset and state decode: FabFilter `.ffp` and `.aupreset`, Waves
173
+ XPst, TR5 chain XML, sonible protobuf field walk, and the third-party states embedded in
174
+ `.cst` strips and `.logicx` projects.
175
+ - Headless AU host (`auprobe.swift`) — loads an installed plugin's state and dumps every
176
+ parameter with real names and UI-formatted values.
177
+ - `logicxkit logic capabilities` — what each command is trusted for, generated from
178
+ `logic/_capabilities.py`.
179
+ - `logicxkit --version` (`-V`) prints the installed version.
180
+ - Write gate (`logic/services/integrity.py`): every project write is held against its input and
181
+ refused on any regression; a refused run discards the copy.
182
+ - A write by a command whose capability level is `CLAIMED`, `DERIVED` or `BROKEN` prints a
183
+ one-line notice naming that level (`LOGICXKIT_NO_NOTICE=1` silences it).
184
+ - `logic transplant` refuses a clone that overruns the channel's slot key range or crosses a
185
+ record class version; `--force` overrides.
186
+ - `logic build` and `logic pst` refuse to write into Logic's own library unless `--install` is
187
+ passed.
188
+ - Strip specs take `output_root`, which moves where built strips land without moving where
189
+ their sources are read from.
190
+ - CI on a macOS runner; `v*` tags build an sdist + wheel, check the wheel carries the Swift
191
+ helpers and no reference material, and publish to PyPI through a trusted publisher gated on
192
+ reviewer approval.
@@ -52,6 +52,15 @@ diff the record list against the input.
52
52
  and not history.
53
53
  - Tests travel with the change.
54
54
 
55
+ ## Recording a golden
56
+
57
+ Every format claim rests on one deliberate change in Logic and a byte diff against the save
58
+ before it. `tools/driver/README.md` has the loop and the traps; `tools/driver/drive.py` performs
59
+ one action and one Save As per step, `tools/driver/diff.py` diffs two saves, and
60
+ `tools/stage_public.py` files the saves under `resources/public/` with a manifest key and the
61
+ facts a test may assert. Only saves of a project born in Logic on your own machine go into the
62
+ public corpus; a session that holds anyone's music or third-party plug-in state stays out.
63
+
55
64
  ## Licensing of contributions
56
65
 
57
66
  logicxkit is Apache-2.0. Under section 5 of that licence, anything you deliberately submit for
@@ -2,7 +2,7 @@
2
2
  # `test`. conftest.py and the `_*.py` helpers it imports are left behind, so the tree cannot collect.
3
3
  recursive-include tests *.py
4
4
  # Named, not globbed: an sdist is built from the working tree, where untracked files can sit.
5
- include bin/fetch_corpus.py tests/goldens/corpus.json tests/goldens/manifest.json
5
+ include bin/fetch_corpus.py bin/regen_data.py tests/goldens/corpus.json tests/goldens/manifest.json
6
6
  # test_capabilities.py reads these two; without them the shipped suite fails rather than skips.
7
7
  recursive-include docs *.md
8
8
  recursive-include config *.json
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: logicxkit
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Logic Pro project and channel-strip tooling, and Audio Unit preset and state decoding
5
5
  Author: Mike Farr
6
6
  License-Expression: Apache-2.0
@@ -61,8 +61,8 @@ deliberate change per Logic save, then a byte diff against the save before it. T
61
61
  is the tidiest example — every button id was pinned on fifty single-toggle saves (2026-09-04),
62
62
  and a control bar written by this tool and copied whole onto another project came up in Logic
63
63
  with that exact set. That standard is not uniform across the tool. Some commands have been
64
- opened in Logic and confirmed, some are reasoned from diffs and never opened, and a few carry
65
- defects reproduced on real projects. **[`docs/CAPABILITIES.md`][caps]
64
+ opened in Logic and confirmed, some are reasoned from diffs and never opened, and a defect
65
+ reproduced on a real project is recorded until a regression test closes it. **[`docs/CAPABILITIES.md`][caps]
66
66
  carries the level and the evidence for every command, and `bin/run logic capabilities` prints
67
67
  the same table.** Read it before you point a writer at a session you care about.
68
68
 
@@ -90,7 +90,8 @@ pip install logicxkit
90
90
  logicxkit logic project ~/Music/Logic/Song.logicx # read-only, to see it working
91
91
  ```
92
92
 
93
- That gets you the `logicxkit` command. To work on it instead, clone it and let `bin/run` build
93
+ That gets you the `logicxkit` command, and with it the record templates and native plug-in
94
+ donors the writers need — Logic's own, taken from its saves of a blank project. To work on it instead, clone it and let `bin/run` build
94
95
  the venv — every command below is written that way, and `bin/run logic …` and
95
96
  `logicxkit logic …` are the same thing:
96
97
 
@@ -112,6 +113,12 @@ bin/run logic levels "<song.logicx>" # channel fader + pan
112
113
  bin/run logic stacks "<song.logicx>" [--tracks] # track stacks / arrange list
113
114
  bin/run logic ocr "<song.logicx>" # OCR the auto-saved WindowImage
114
115
  bin/run logic neural "<strip.cst | song.logicx>" # Neural DSP knob values
116
+ bin/run logic midi "<song.logicx>" [--export out.mid] # MIDI regions, or a .mid of them
117
+ bin/run logic plugins "<song.logicx | folder>" # plug-ins referenced, and which are missing
118
+ bin/run logic patch "<name.patch | folder>" # a Library patch: channels, strips, plug-ins
119
+ bin/run logic regions "<song.logicx>" # every MIDI and audio region, with files
120
+ bin/run logic sessionplayer "<song.logicx>" # Session Player drummer, preset, settings
121
+ bin/run logic quantize-drums "<song.logicx>" --out DIR # quantize a drum take to the grid, no Logic
115
122
  bin/run au strip "<strip.cst | song.logicx>" # every embedded 3rd-party state
116
123
  bin/run au preset "<preset.aupreset | .ffp>" # a preset file, named, in real units
117
124
  bin/run logic capabilities -v # what each writer is trusted for
@@ -125,7 +132,12 @@ bin/run logic capabilities -v # what each writer is t
125
132
  (extract the auto-saved WindowImage) and **`logic ocr`** (Apple-Vision OCR of it — reads the
126
133
  mixer as Logic drew it); **`logic levels`** (fader + pan, read and copy between projects);
127
134
  **`logic stacks`** (folder stacks and the arrange track list, and `--move` to put a track into
128
- a stack); **Neural DSP state decode** (`logic neural`); and the project editors — track
135
+ a stack); **`logic midi`** (MIDI regions and their export as a `.mid`); **`logic plugins`**
136
+ (every referenced plug-in, and which this Mac lacks); **`logic patch`** (a Library patch
137
+ bundle's channels, strips and plug-ins, and `--build` to make one); **`logic regions`** (MIDI
138
+ and audio regions with their files, and `--audio` to import a WAV); **`logic sessionplayer`**
139
+ (a Session Player region's settings and generated notes); **Neural DSP state decode** (`logic
140
+ neural`); and the project editors — MIDI regions and notes, track
129
141
  header, control bar, toolbar, transport modes, metronome, channel width, mixer groups,
130
142
  arrangement sections, tempo, time signature and key, track add/rename/colour/hide/reorder,
131
143
  sends, routing, and `apply-template` to move a session onto another project's layout. See
@@ -182,7 +194,7 @@ Three more things write outside `--out`, and one warning:
182
194
  slot keys, ending at its `.cst` reference record; a clone that overruns that run deletes the
183
195
  reference, and `validate_project` cannot see the loss. A clone across record class versions
184
196
  cannot be legalised either. Both stop the run and say what to do instead; `--force` writes
185
- anyway. The remaining open defects are listed in
197
+ anyway. Any open defect is listed in
186
198
  [`docs/CAPABILITIES.md`][caps].
187
199
 
188
200
  ## Layout
@@ -224,9 +236,10 @@ release asset and fetched by `bin/run fetch-corpus` into `resources/public/`, pi
224
236
  checksum in `tests/goldens/corpus.json`. `tests/goldens/manifest.json` names each save by a
225
237
  neutral key with the facts a test may assert. The **owner's corpus** — controlled saves cut
226
238
  from real sessions, project templates, finished mixes, a channel-strip library snapshot — is
227
- Logic-authored material containing real music and **is not here**; the same goes for the
228
- data root the tools load (record templates Logic wrote, plugin-slot donor records, AU
229
- parameter tables), none of which are ours to publish.
239
+ Logic-authored material containing real music and **is not here**. The record templates and
240
+ native plug-in donors the writers need ship inside the package (`src/logicxkit/data`, Logic's
241
+ own from the public corpus); a data root (`LOGICXKIT_DATA`) adds third-party donors and AU
242
+ parameter tables, which are not ours to publish.
230
243
 
231
244
  The consequence for a fresh clone: **`bin/run pytest` runs green, but every golden skips until
232
245
  `bin/run fetch-corpus` has run**, and the keys only the owner's corpus has skip regardless. The
@@ -31,8 +31,8 @@ deliberate change per Logic save, then a byte diff against the save before it. T
31
31
  is the tidiest example — every button id was pinned on fifty single-toggle saves (2026-09-04),
32
32
  and a control bar written by this tool and copied whole onto another project came up in Logic
33
33
  with that exact set. That standard is not uniform across the tool. Some commands have been
34
- opened in Logic and confirmed, some are reasoned from diffs and never opened, and a few carry
35
- defects reproduced on real projects. **[`docs/CAPABILITIES.md`][caps]
34
+ opened in Logic and confirmed, some are reasoned from diffs and never opened, and a defect
35
+ reproduced on a real project is recorded until a regression test closes it. **[`docs/CAPABILITIES.md`][caps]
36
36
  carries the level and the evidence for every command, and `bin/run logic capabilities` prints
37
37
  the same table.** Read it before you point a writer at a session you care about.
38
38
 
@@ -60,7 +60,8 @@ pip install logicxkit
60
60
  logicxkit logic project ~/Music/Logic/Song.logicx # read-only, to see it working
61
61
  ```
62
62
 
63
- That gets you the `logicxkit` command. To work on it instead, clone it and let `bin/run` build
63
+ That gets you the `logicxkit` command, and with it the record templates and native plug-in
64
+ donors the writers need — Logic's own, taken from its saves of a blank project. To work on it instead, clone it and let `bin/run` build
64
65
  the venv — every command below is written that way, and `bin/run logic …` and
65
66
  `logicxkit logic …` are the same thing:
66
67
 
@@ -82,6 +83,12 @@ bin/run logic levels "<song.logicx>" # channel fader + pan
82
83
  bin/run logic stacks "<song.logicx>" [--tracks] # track stacks / arrange list
83
84
  bin/run logic ocr "<song.logicx>" # OCR the auto-saved WindowImage
84
85
  bin/run logic neural "<strip.cst | song.logicx>" # Neural DSP knob values
86
+ bin/run logic midi "<song.logicx>" [--export out.mid] # MIDI regions, or a .mid of them
87
+ bin/run logic plugins "<song.logicx | folder>" # plug-ins referenced, and which are missing
88
+ bin/run logic patch "<name.patch | folder>" # a Library patch: channels, strips, plug-ins
89
+ bin/run logic regions "<song.logicx>" # every MIDI and audio region, with files
90
+ bin/run logic sessionplayer "<song.logicx>" # Session Player drummer, preset, settings
91
+ bin/run logic quantize-drums "<song.logicx>" --out DIR # quantize a drum take to the grid, no Logic
85
92
  bin/run au strip "<strip.cst | song.logicx>" # every embedded 3rd-party state
86
93
  bin/run au preset "<preset.aupreset | .ffp>" # a preset file, named, in real units
87
94
  bin/run logic capabilities -v # what each writer is trusted for
@@ -95,7 +102,12 @@ bin/run logic capabilities -v # what each writer is t
95
102
  (extract the auto-saved WindowImage) and **`logic ocr`** (Apple-Vision OCR of it — reads the
96
103
  mixer as Logic drew it); **`logic levels`** (fader + pan, read and copy between projects);
97
104
  **`logic stacks`** (folder stacks and the arrange track list, and `--move` to put a track into
98
- a stack); **Neural DSP state decode** (`logic neural`); and the project editors — track
105
+ a stack); **`logic midi`** (MIDI regions and their export as a `.mid`); **`logic plugins`**
106
+ (every referenced plug-in, and which this Mac lacks); **`logic patch`** (a Library patch
107
+ bundle's channels, strips and plug-ins, and `--build` to make one); **`logic regions`** (MIDI
108
+ and audio regions with their files, and `--audio` to import a WAV); **`logic sessionplayer`**
109
+ (a Session Player region's settings and generated notes); **Neural DSP state decode** (`logic
110
+ neural`); and the project editors — MIDI regions and notes, track
99
111
  header, control bar, toolbar, transport modes, metronome, channel width, mixer groups,
100
112
  arrangement sections, tempo, time signature and key, track add/rename/colour/hide/reorder,
101
113
  sends, routing, and `apply-template` to move a session onto another project's layout. See
@@ -152,7 +164,7 @@ Three more things write outside `--out`, and one warning:
152
164
  slot keys, ending at its `.cst` reference record; a clone that overruns that run deletes the
153
165
  reference, and `validate_project` cannot see the loss. A clone across record class versions
154
166
  cannot be legalised either. Both stop the run and say what to do instead; `--force` writes
155
- anyway. The remaining open defects are listed in
167
+ anyway. Any open defect is listed in
156
168
  [`docs/CAPABILITIES.md`][caps].
157
169
 
158
170
  ## Layout
@@ -194,9 +206,10 @@ release asset and fetched by `bin/run fetch-corpus` into `resources/public/`, pi
194
206
  checksum in `tests/goldens/corpus.json`. `tests/goldens/manifest.json` names each save by a
195
207
  neutral key with the facts a test may assert. The **owner's corpus** — controlled saves cut
196
208
  from real sessions, project templates, finished mixes, a channel-strip library snapshot — is
197
- Logic-authored material containing real music and **is not here**; the same goes for the
198
- data root the tools load (record templates Logic wrote, plugin-slot donor records, AU
199
- parameter tables), none of which are ours to publish.
209
+ Logic-authored material containing real music and **is not here**. The record templates and
210
+ native plug-in donors the writers need ship inside the package (`src/logicxkit/data`, Logic's
211
+ own from the public corpus); a data root (`LOGICXKIT_DATA`) adds third-party donors and AU
212
+ parameter tables, which are not ours to publish.
200
213
 
201
214
  The consequence for a fresh clone: **`bin/run pytest` runs green, but every golden skips until
202
215
  `bin/run fetch-corpus` has run**, and the keys only the owner's corpus has skip regardless. The
@@ -0,0 +1,252 @@
1
+ """Regenerate src/logicxkit/data/logic from the public golden corpus (`bin/run fetch-corpus`).
2
+
3
+ bin/run python bin/regen_data.py
4
+
5
+ Every template is a record Logic wrote in a public save of a blank project, so the package
6
+ ships Logic's own bytes and nothing vendor-authored.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ import struct
12
+ import sys
13
+ from pathlib import Path
14
+
15
+ ROOT = Path(__file__).resolve().parents[1]
16
+ sys.path.insert(0, str(ROOT / "tests"))
17
+ import _goldens # noqa: E402
18
+ from logicxkit.logic.services.binding import channels # noqa: E402
19
+ from logicxkit.logic.services.channel_alloc import mixer_record # noqa: E402
20
+ from logicxkit.logic.services.environment import ENV_TAG, object_id_of # noqa: E402
21
+ from logicxkit.logic.services.groups import ( # noqa: E402
22
+ EVENT,
23
+ EVENT_FADER_AT,
24
+ EVENT_OBJECT_AT,
25
+ EVENT_VALUE_AT,
26
+ EVENT_VALUE_HI_AT,
27
+ EVENT_VALUE_LO_AT,
28
+ FADER_IDS,
29
+ TAIL,
30
+ read_groups,
31
+ )
32
+ from logicxkit.logic.services.insert import HEADER, project_records # noqa: E402
33
+ from logicxkit.logic.services.recbuild import slot_of # noqa: E402
34
+ from logicxkit.logic.services.recdiff import load_project_data # noqa: E402
35
+ from logicxkit.logic.services.sends import is_send # noqa: E402
36
+ from logicxkit.logic.services.stacks import read_stacks # noqa: E402
37
+ from logicxkit.logic.services.tracklist import arrange_run, flat_run, row_object # noqa: E402
38
+
39
+ TEXT_HEAD = 98 # a qSxT's fixed head; the section name follows
40
+ SLOT_KEYS = range(2, 20) # a channel's plug-in and property records
41
+
42
+ OUT = ROOT / "src" / "logicxkit" / "data"
43
+ SOURCE = ("Logic Pro 12.3.1 on a blank project: {what} (public corpus {a} -> {b}; "
44
+ "regenerated by bin/regen_data.py)")
45
+
46
+
47
+ def _load(key: str) -> bytes:
48
+ p = _goldens.path(key)
49
+ if p is None:
50
+ raise SystemExit(f"golden {key!r} is not on this machine — run bin/run fetch-corpus")
51
+ return load_project_data(p)
52
+
53
+
54
+ def _new(pair: tuple[str, str]) -> list:
55
+ """Records of the second save whose (tag, payload) the first lacks."""
56
+ a, b = (project_records(_load(k)) for k in pair)
57
+ seen = {(r.tag, r.raw[HEADER:]) for r in a}
58
+ return [r for r in b if (r.tag, r.raw[HEADER:]) not in seen]
59
+
60
+
61
+ def _split(raw: bytes) -> dict:
62
+ return {"header": raw[:HEADER].hex(), "payload": raw[HEADER:].hex()}
63
+
64
+
65
+ def _source(pair: tuple[str, str], what: str) -> str:
66
+ return SOURCE.format(what=what, a=pair[0], b=pair[1])
67
+
68
+
69
+ def channel(pair: tuple[str, str], label: str) -> dict:
70
+ """The mixer record of the strip labelled ``label`` in the second save."""
71
+ data = _load(pair[1])
72
+ owner = next((o for o, c in channels(data).items() if c.label == label), None)
73
+ if owner is None:
74
+ raise SystemExit(f"{pair[1]}: no strip labelled {label!r}")
75
+ return {"source": _source(pair, f"the {label} strip"), **_split(mixer_record(project_records(data), owner))}
76
+
77
+
78
+ def send(pair: tuple[str, str]) -> dict:
79
+ """The one send the second save added."""
80
+ added = [r for r in _new(pair) if is_send(r)]
81
+ if len(added) != 1:
82
+ raise SystemExit(f"{pair}: {len(added)} new send record(s), wanted one")
83
+ return {"source": _source(pair, "the send the second save added"), **_split(added[0].raw)}
84
+
85
+
86
+ def _bare_event(event: bytes, fader: str) -> str:
87
+ e = bytearray(event)
88
+ struct.pack_into("<I", e, EVENT_OBJECT_AT, 0)
89
+ struct.pack_into("<I", e, EVENT_FADER_AT, FADER_IDS[fader])
90
+ struct.pack_into("<I", e, EVENT_VALUE_AT, 0)
91
+ struct.pack_into("<H", e, EVENT_VALUE_HI_AT, 0)
92
+ struct.pack_into("<H", e, EVENT_VALUE_LO_AT, 0)
93
+ return bytes(e).hex()
94
+
95
+
96
+ def group(pair: tuple[str, str], name: str) -> dict:
97
+ """The group triple named ``name``: the `qeSM`, its `karT` marker and its `qSvE` events."""
98
+ records = project_records(_load(pair[1]))
99
+ g = next((g for g in read_groups(_load(pair[1])) if g.name == name), None)
100
+ if g is None:
101
+ raise SystemExit(f"{pair[1]}: no group named {name!r}")
102
+ qesm, marker, qsve = (records[g.start + k].raw for k in range(3))
103
+ events = qsve[HEADER:-TAIL]
104
+ if not events or len(events) % EVENT:
105
+ raise SystemExit(f"{pair[1]}: group {name!r} has no member events to pattern on")
106
+ return {"source": _source(pair, f"the group {name!r}"),
107
+ "qesm_header": qesm[:HEADER].hex(), "qesm_payload": qesm[HEADER:].hex(),
108
+ "marker_header": marker[:HEADER].hex(), "qsve_header": qsve[:HEADER].hex(),
109
+ "qsve_tail": qsve[-TAIL:].hex(),
110
+ "member_events": {f: _bare_event(events[:EVENT], f) for f in FADER_IDS},
111
+ "notes": "member_events: one event per linked fader, from the group's first event "
112
+ "with the object id (+4), fader id (+12) and value (+8, +20, +30) reset; "
113
+ "the writer stamps all three"}
114
+
115
+
116
+ def stack_folder(pair: tuple[str, str], name: str) -> dict:
117
+ """The folder stack ``name``: its Sub strip, header object, arrange row and flat row."""
118
+ data = _load(pair[1])
119
+ records = project_records(data)
120
+ stack = next((s for s in read_stacks(data, None) if s.name == name), None)
121
+ if stack is None:
122
+ raise SystemExit(f"{pair[1]}: no stack named {name!r}")
123
+ obj = next(r.raw for r in records if r.tag == ENV_TAG and object_id_of(r) == stack.object_id)
124
+ run = arrange_run(records)
125
+ row = next(records[i].raw for i in run if row_object(records[i].raw) == stack.object_id)
126
+ flat = next(records[i].raw for i in flat_run(records, run)
127
+ if row_object(records[i].raw) == stack.object_id)
128
+ roles = {"strip": mixer_record(records, stack.owner), "object": obj, "row": row, "flat_row": flat}
129
+ return {"source": _source(pair, f"the folder stack {name!r} Logic created over three tracks"),
130
+ "records": {role: _split(raw) for role, raw in roles.items()}}
131
+
132
+
133
+ def inst_track(pair: tuple[str, str], label: str) -> dict:
134
+ """The instrument slot and property records Logic gave the strip labelled ``label``."""
135
+ data = _load(pair[1])
136
+ owner = next((o for o, c in channels(data).items() if c.label == label), None)
137
+ if owner is None:
138
+ raise SystemExit(f"{pair[1]}: no strip labelled {label!r}")
139
+ recs = [r for r in project_records(data) if r.tag == b"UCuA" and r.owner == owner and r.key in SLOT_KEYS]
140
+ return {"_": _source(pair, f"the {label} strip's slot records; owner re-stamped on use"),
141
+ "records": {str(r.key): r.raw.hex() for r in sorted(recs, key=lambda r: r.key)}}
142
+
143
+
144
+ def section_text(pair: tuple[str, str], name: str) -> dict:
145
+ """The name record of the section called ``name``, less the name itself."""
146
+ text = name.encode("ascii") + b"\0"
147
+ text += b"\0" * (len(text) % 2)
148
+ hit = next((r for r in _new(pair) if r.tag == b"qSxT" and r.raw[HEADER + TEXT_HEAD:] == text), None)
149
+ if hit is None:
150
+ raise SystemExit(f"{pair}: no new section text record named {name!r}")
151
+ return {"source": _source(pair, f"the section {name!r}'s name record"),
152
+ "header": hit.raw[:HEADER].hex(), "head": hit.raw[HEADER:HEADER + TEXT_HEAD].hex()}
153
+
154
+
155
+ def arrangement_track(pair: tuple[str, str]) -> dict:
156
+ """Everything Logic added for the arrangement track and its first section: the section
157
+ and track sequence triples with the `snrT` after the last `tSxT`, the `OgnS` before the
158
+ first `rpyH`, the last `MneG` and the empty text record."""
159
+ records = project_records(_load(pair[1]))
160
+ anchor = max(i for i, r in enumerate(records) if r.tag == b"tSxT")
161
+ run = records[anchor + 1:anchor + 8]
162
+ if [r.tag for r in run] != [b"qeSM", b"karT", b"qSvE"] * 2 + [b"snrT"]:
163
+ raise SystemExit(f"{pair[1]}: no two sequence triples and snrT after the last tSxT")
164
+ first_rpyh = next(i for i, r in enumerate(records) if r.tag == b"rpyH")
165
+ ogns = max(i for i, r in enumerate(records[:first_rpyh]) if r.tag == b"OgnS")
166
+ mneg = max(i for i, r in enumerate(records) if r.tag == b"MneG")
167
+ empty = next(r for r in records if r.tag == b"qSxT" and slot_of(r.raw) == 0 and r.raw[HEADER + TEXT_HEAD:] == b"\0")
168
+ roles = dict(zip(("section_qesm", "section_marker", "section_qsve", "track_qesm", "track_marker",
169
+ "track_qsve", "snrt"), (r.raw for r in run)))
170
+ roles |= {"ogns": records[ogns].raw, "mneg": records[mneg].raw, "text_empty": empty.raw}
171
+ return {"source": _source(pair, "the arrangement track shown and its first section added"),
172
+ "records": {role: _split(raw) for role, raw in roles.items()}}
173
+
174
+
175
+ def midi_region(pair: tuple[str, str], track_label: str) -> dict:
176
+ """The region Logic made on the strip labelled ``track_label``: its sequence triple and the
177
+ song container's 80-byte entry for it."""
178
+ from logicxkit.logic.services.regions import ENTRY, TAIL, placements, song_container
179
+ from logicxkit.logic.services.sequence import sequences, triple_by_slot
180
+ data = _load(pair[1])
181
+ records = project_records(data)
182
+ run = arrange_run(records)
183
+ song = song_container(records, run)
184
+ payload = records[song.end].raw[HEADER:]
185
+ labels = {}
186
+ from logicxkit.logic.services.stacks import read_tracks
187
+ names = {r["object_id"]: r["name"] for r in read_tracks(data, None)}
188
+ entries = [(off, oid) for off, oid, _row in placements(records) if names.get(oid) == track_label]
189
+ if len(entries) != 1:
190
+ raise SystemExit(f"{pair[1]}: {len(entries)} region(s) on {track_label!r}, wanted one")
191
+ off, _oid = entries[0]
192
+ entry = payload[off:off + ENTRY]
193
+ slot = struct.unpack_from("<I", entry, 32)[0]
194
+ t = triple_by_slot(sequences(records), slot)
195
+ roles = {"qesm": records[t.start].raw, "marker": records[t.start + 1].raw, "qsve": records[t.end].raw, "entry": entry}
196
+ return {"source": _source(pair, f"the empty MIDI region on {track_label!r} (Pencil tool click)"),
197
+ "records": {role: _split(raw) if role != "entry" else {"payload": raw.hex()} for role, raw in roles.items()}}
198
+
199
+ def audio_region(pair: tuple[str, str]) -> dict:
200
+ """The audio file record, region record and song-container entry Logic added for one
201
+ imported WAV."""
202
+ from logicxkit.logic.services.regions import ENTRY, TAIL, song_container
203
+ new = _new(pair)
204
+ lfua = [r for r in new if r.tag == b"lFuA"]
205
+ grua = [r for r in new if r.tag == b"gRuA"]
206
+ if len(lfua) != 1 or len(grua) != 1:
207
+ raise SystemExit(f"{pair}: {len(lfua)} file and {len(grua)} region record(s), wanted one each")
208
+ records = project_records(_load(pair[1]))
209
+ song = song_container(records, arrange_run(records))
210
+ payload = records[song.end].raw[HEADER:]
211
+ entries = [payload[i:i + ENTRY] for i in range(0, len(payload) - TAIL, ENTRY)]
212
+ audio = [e for e in entries if struct.unpack_from("<H", e, 0)[0] == 0x24]
213
+ if len(audio) != 1:
214
+ raise SystemExit(f"{pair[1]}: {len(audio)} audio entries, wanted one")
215
+ return {"source": _source(pair, "one WAV imported with File > Import > Audio File"),
216
+ "records": {"lfua": _split(lfua[0].raw), "grua": _split(grua[0].raw), "entry": {"payload": audio[0].hex()}}}
217
+
218
+ # name -> (golden keys the extraction reads, how to make it)
219
+ TEMPLATES = {
220
+ "audio-channel-12.3.1.json": (("tracks-two-audio-logic",),
221
+ lambda: channel(("blank-base", "tracks-two-audio-logic"), "Audio 2")),
222
+ "aux-track-12.3.1.json": (("stack-summing-logic",),
223
+ lambda: channel(("stack-folder-flattened-logic", "stack-summing-logic"), "Aux 1")),
224
+ "group-12.3.1.json": (("group-drums-logic",),
225
+ lambda: group(("stack-folder-flattened-logic", "group-drums-logic"), "Drums")),
226
+ "send-12.3.1.json": (("levels-resave-logic", "send-bus-1-logic"),
227
+ lambda: send(("levels-resave-logic", "send-bus-1-logic"))),
228
+ "stack-folder-12.3.1.json": (("stack-folder-logic",),
229
+ lambda: stack_folder(("tracks-three-audio-logic", "stack-folder-logic"), "Sub 1")),
230
+ "inst-track-12.3.1.json": (("tracks-instrument-logic",),
231
+ lambda: inst_track(("arrangement-section-1-selected-logic", "tracks-instrument-logic"), "Inst 1")),
232
+ "section-text-12.3.1.json": (("arrangement-track-logic", "arrangement-first-section-logic"),
233
+ lambda: section_text(("arrangement-track-logic", "arrangement-first-section-logic"), "Intro")),
234
+ "arrangement-track-12.3.1.json": (("arrangement-first-section-logic",),
235
+ lambda: arrangement_track(("stack-folder-flattened-logic", "arrangement-first-section-logic"))),
236
+ "midi-region-12.3.1.json": (("midi-empty-region-logic",),
237
+ lambda: midi_region(("inserts-native-all-logic", "midi-empty-region-logic"), "Inst 1")),
238
+ "audio-region-12.3.1.json": (("midi-write-resave-logic", "audio-one-region-logic"),
239
+ lambda: audio_region(("midi-write-resave-logic", "audio-one-region-logic"))),
240
+ }
241
+
242
+
243
+ def main() -> int:
244
+ (OUT / "logic").mkdir(parents=True, exist_ok=True)
245
+ for name, (_keys, make) in TEMPLATES.items():
246
+ (OUT / "logic" / name).write_text(json.dumps(make(), indent=1) + "\n")
247
+ print("wrote", name)
248
+ return 0
249
+
250
+
251
+ if __name__ == "__main__":
252
+ sys.exit(main())