468 lines
12 KiB
ReStructuredText
468 lines
12 KiB
ReStructuredText
Core Concepts
|
|
=============
|
|
|
|
This page explains the key concepts and architecture of Logic Plugin Manager.
|
|
|
|
Architecture Overview
|
|
---------------------
|
|
|
|
Logic Plugin Manager is structured around three main areas:
|
|
|
|
1. **Components**: Audio Unit plugin bundles and their metadata
|
|
2. **Logic Management**: High-level interface for plugin discovery and organization
|
|
3. **Tags & Categories**: Logic Pro's categorization system
|
|
|
|
The Library's Structure
|
|
~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
::
|
|
|
|
logic_plugin_manager/
|
|
├── components/ # Audio Unit components
|
|
│ ├── AudioComponent # Individual plugin representation
|
|
│ ├── Component # .component bundle parser
|
|
│ └── AudioUnitType # Audio Unit type enumeration
|
|
├── logic/ # Main management interface
|
|
│ ├── Logic # Primary entry point
|
|
│ ├── Plugins # Plugin collection with search
|
|
│ └── SearchResult # Search result with scoring
|
|
└── tags/ # Category and tag management
|
|
├── Category # Plugin category management
|
|
├── MusicApps # Database interface
|
|
├── Tagpool # Plugin count tracking
|
|
├── Properties # Category sorting
|
|
└── Tagset # Plugin tag files
|
|
|
|
Understanding Audio Components
|
|
------------------------------
|
|
|
|
Audio Component Bundle Structure
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
macOS Audio Unit plugins are distributed as ``.component`` bundles:
|
|
|
|
::
|
|
|
|
PluginName.component/
|
|
└── Contents/
|
|
├── Info.plist # Metadata and component definitions
|
|
├── MacOS/
|
|
│ └── PluginName # Executable binary
|
|
└── Resources/ # UI resources, presets, etc.
|
|
|
|
The ``Info.plist`` file contains an ``AudioComponents`` array defining one or more Audio Units within the bundle.
|
|
|
|
AudioComponent Class
|
|
~~~~~~~~~~~~~~~~~~~~
|
|
|
|
Each Audio Unit is represented by an ``AudioComponent`` instance with:
|
|
|
|
- **Identification**: Type, subtype, and manufacturer codes
|
|
- **Metadata**: Name, description, version
|
|
- **Categories**: Tags assigned in Logic Pro
|
|
- **Tags ID**: Unique identifier derived from codes
|
|
|
|
.. code-block:: python
|
|
|
|
from logic_plugin_manager import Logic
|
|
|
|
logic = Logic()
|
|
plugin = logic.plugins.get_by_name("Pro-Q 3")
|
|
|
|
# Access component information
|
|
print(f"Full Name: {plugin.full_name}")
|
|
print(f"Manufacturer: {plugin.manufacturer}")
|
|
print(f"Type Code: {plugin.type_code}")
|
|
print(f"Subtype Code: {plugin.subtype_code}")
|
|
print(f"Manufacturer Code: {plugin.manufacturer_code}")
|
|
print(f"Tags ID: {plugin.tags_id}")
|
|
|
|
Audio Unit Types
|
|
~~~~~~~~~~~~~~~~
|
|
|
|
Logic Plugin Manager recognizes five Audio Unit types:
|
|
|
|
.. list-table::
|
|
:header-rows: 1
|
|
:widths: 15 25 20 40
|
|
|
|
* - Code
|
|
- Display Name
|
|
- Alternative Name
|
|
- Description
|
|
* - ``aufx``
|
|
- Audio FX
|
|
- Effect
|
|
- Audio effect processors
|
|
* - ``aumu``
|
|
- Instrument
|
|
- Music Device
|
|
- Software instruments and synthesizers
|
|
* - ``aumf``
|
|
- MIDI-controlled Effects
|
|
- Music Effect
|
|
- Effects controlled by MIDI input
|
|
* - ``aumi``
|
|
- MIDI FX
|
|
- MIDI Generator
|
|
- MIDI processors and generators
|
|
* - ``augn``
|
|
- Generator
|
|
- Generator
|
|
- Audio generators
|
|
|
|
.. code-block:: python
|
|
|
|
from logic_plugin_manager import AudioUnitType
|
|
|
|
# Get type by code
|
|
instrument_type = AudioUnitType.from_code("aumu")
|
|
print(instrument_type.display_name) # "Instrument"
|
|
|
|
# Search for types
|
|
effect_types = AudioUnitType.search("effect")
|
|
|
|
The Logic Class
|
|
---------------
|
|
|
|
The ``Logic`` class is the main entry point for plugin management.
|
|
|
|
Initialization Process
|
|
~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
When you create a ``Logic`` instance (with ``lazy=False``, the default):
|
|
|
|
1. Loads MusicApps database (tagpool and properties files)
|
|
2. Scans ``/Library/Audio/Plug-Ins/Components`` directory
|
|
3. Parses each ``.component`` bundle's ``Info.plist``
|
|
4. Creates ``AudioComponent`` instances
|
|
5. Loads tagsets and categories for each plugin
|
|
6. Indexes plugins for fast lookups
|
|
|
|
.. code-block:: python
|
|
|
|
from logic_plugin_manager import Logic
|
|
|
|
# Full initialization
|
|
logic = Logic() # Discovers everything
|
|
|
|
# Lazy initialization (manual control)
|
|
logic = Logic(lazy=True)
|
|
logic.discover_plugins() # When ready to load plugins
|
|
logic.discover_categories() # When ready to load categories
|
|
|
|
Logic Instance Attributes
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
.. code-block:: python
|
|
|
|
logic = Logic()
|
|
|
|
# Access discovered data
|
|
logic.plugins # Plugins collection
|
|
logic.components # Set of Component bundles
|
|
logic.categories # Dict of category_name -> Category
|
|
logic.musicapps # MusicApps database interface
|
|
|
|
# Configuration paths
|
|
logic.components_path # Path to Components directory
|
|
logic.tags_path # Path to tags database
|
|
|
|
Plugin Collection & Search
|
|
---------------------------
|
|
|
|
The Plugins Class
|
|
~~~~~~~~~~~~~~~~~
|
|
|
|
The ``Plugins`` class provides a searchable collection with multiple indexes:
|
|
|
|
- **By full name**: Exact manufacturer + plugin name match
|
|
- **By manufacturer**: All plugins from a vendor
|
|
- **By name**: Plugin name only
|
|
- **By codes**: Type, subtype, manufacturer codes
|
|
- **By category**: All plugins in a category
|
|
- **By tags_id**: Unique component identifier
|
|
|
|
.. code-block:: python
|
|
|
|
logic = Logic()
|
|
|
|
# Exact lookups (fast)
|
|
plugin = logic.plugins.get_by_full_name("fabfilter: pro-q 3")
|
|
plugin = logic.plugins.get_by_tags_id("61756678-65517033-46466")
|
|
|
|
# Set lookups
|
|
fabfilter = logic.plugins.get_by_manufacturer("fabfilter")
|
|
effects = logic.plugins.get_by_type_code("aufx")
|
|
eq_plugins = logic.plugins.get_by_category("Effects:EQ")
|
|
|
|
Search Algorithm
|
|
~~~~~~~~~~~~~~~~
|
|
|
|
The ``search()`` method implements a sophisticated scoring system:
|
|
|
|
**Priority Levels** (highest to lowest):
|
|
|
|
1. **Tags ID exact match** (score: 1000)
|
|
2. **Name substring match** (850-900)
|
|
3. **Full name substring match** (750-800)
|
|
4. **Fuzzy name match** (650-700, requires rapidfuzz)
|
|
5. **Manufacturer match** (580-650)
|
|
6. **Category match** (480-550)
|
|
7. **Type code match** (380-450)
|
|
8. **Subtype code match** (290-350)
|
|
9. **Manufacturer code match** (190-250)
|
|
|
|
.. code-block:: python
|
|
|
|
logic = Logic()
|
|
|
|
# Fuzzy search with scoring
|
|
results = logic.plugins.search(
|
|
"serum",
|
|
use_fuzzy=True,
|
|
fuzzy_threshold=80,
|
|
max_results=10
|
|
)
|
|
|
|
for result in results:
|
|
print(f"{result.plugin.full_name}")
|
|
print(f" Score: {result.score}")
|
|
print(f" Matched: {result.match_field}")
|
|
|
|
Categories & Tags
|
|
-----------------
|
|
|
|
Hierarchical Structure
|
|
~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
Categories in Logic Pro use colon-separated hierarchies:
|
|
|
|
::
|
|
|
|
Effects
|
|
Effects:Dynamics
|
|
Effects:Dynamics:Compressor
|
|
Effects:EQ
|
|
Instruments
|
|
Instruments:Synth
|
|
|
|
.. code-block:: python
|
|
|
|
logic = Logic()
|
|
|
|
# Navigate hierarchy
|
|
dynamics = logic.categories["Effects:Dynamics"]
|
|
parent = dynamics.parent # "Effects" category
|
|
child = parent.child("EQ") # "Effects:EQ" category
|
|
|
|
# Check properties
|
|
print(dynamics.plugin_amount)
|
|
print(dynamics.is_root)
|
|
|
|
Category Operations
|
|
~~~~~~~~~~~~~~~~~~~
|
|
|
|
.. code-block:: python
|
|
|
|
logic = Logic()
|
|
|
|
# Create new category
|
|
my_cat = logic.introduce_category("Studio Essentials")
|
|
|
|
# Manage plugins
|
|
plugin = logic.plugins.get_by_name("Pro-Q 3")
|
|
plugin.add_to_category(my_cat)
|
|
plugin.remove_from_category(my_cat)
|
|
plugin.move_to_category(my_cat) # Removes from all others
|
|
|
|
# Bulk operations
|
|
plugins_set = {plugin1, plugin2, plugin3}
|
|
logic.add_plugins_to_category(my_cat, plugins_set)
|
|
|
|
# Update plugin count
|
|
logic.sync_category_plugin_amount(my_cat)
|
|
|
|
Category Sorting
|
|
~~~~~~~~~~~~~~~~
|
|
|
|
Categories have a sort order managed by the ``Properties`` database:
|
|
|
|
.. code-block:: python
|
|
|
|
category = logic.categories["Effects:EQ"]
|
|
|
|
# Check position
|
|
print(category.index)
|
|
print(category.is_first)
|
|
print(category.is_last)
|
|
prev, next = category.neighbors
|
|
|
|
# Reorder
|
|
category.move_up(steps=2)
|
|
category.move_down()
|
|
category.move_to_top()
|
|
category.move_to_bottom()
|
|
category.move_before(other_category)
|
|
category.move_after(other_category)
|
|
category.swap(other_category)
|
|
|
|
Tagsets
|
|
-------
|
|
|
|
What are Tagsets?
|
|
~~~~~~~~~~~~~~~~~
|
|
|
|
Each ``AudioComponent`` has an associated ``.tagset`` file stored at:
|
|
|
|
``~/Music/Audio Music Apps/Databases/Tags/<tags_id>.tagset``
|
|
|
|
These XML plist files store:
|
|
|
|
- **nickname**: Custom display name
|
|
- **shortname**: Abbreviated name for UI
|
|
- **tags**: Dictionary of category assignments
|
|
|
|
.. code-block:: python
|
|
|
|
logic = Logic()
|
|
plugin = logic.plugins.get_by_name("Pro-Q 3")
|
|
|
|
# Access tagset
|
|
print(plugin.tagset.nickname)
|
|
print(plugin.tagset.shortname)
|
|
print(plugin.tagset.tags) # {"Effects:EQ": "user"}
|
|
|
|
# Modify tagset
|
|
plugin.set_nickname("My Favorite EQ")
|
|
plugin.set_shortname("PQ3")
|
|
|
|
Tag Values
|
|
~~~~~~~~~~
|
|
|
|
Category tags typically have the value ``"user"`` indicating user assignment, but can have other values from Logic Pro's internal management.
|
|
|
|
MusicApps Database
|
|
------------------
|
|
|
|
Database Files
|
|
~~~~~~~~~~~~~~
|
|
|
|
Logic Pro stores category information in two files:
|
|
|
|
**MusicApps.tagpool**
|
|
Maps category names to plugin counts:
|
|
|
|
.. code-block:: python
|
|
|
|
{
|
|
"Effects": 245,
|
|
"Effects:EQ": 18,
|
|
"Instruments": 89,
|
|
...
|
|
}
|
|
|
|
**MusicApps.properties**
|
|
Stores category sort order and preferences:
|
|
|
|
.. code-block:: python
|
|
|
|
{
|
|
"sorting": ["Effects", "Effects:Dynamics", ...],
|
|
"user_sorted": "property" # or absent for alphabetical
|
|
}
|
|
|
|
Accessing the Database
|
|
~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
.. code-block:: python
|
|
|
|
logic = Logic()
|
|
musicapps = logic.musicapps
|
|
|
|
# Access tagpool
|
|
print(musicapps.tagpool.categories)
|
|
|
|
# Access properties
|
|
print(musicapps.properties.sorting)
|
|
print(musicapps.properties.user_sorted)
|
|
|
|
# Modify database
|
|
musicapps.introduce_category("New Category")
|
|
musicapps.remove_category("Old Category")
|
|
|
|
Thread Safety
|
|
-------------
|
|
|
|
Logic Plugin Manager is **not thread-safe**. The library performs file I/O operations on Logic Pro's database files without locking mechanisms.
|
|
|
|
**Recommendations:**
|
|
|
|
- Use a single ``Logic`` instance per process
|
|
- Avoid concurrent writes to categories or tagsets
|
|
- Reload data after external changes (e.g., Logic Pro modifying categories)
|
|
|
|
Performance Considerations
|
|
--------------------------
|
|
|
|
Initial Discovery
|
|
~~~~~~~~~~~~~~~~~
|
|
|
|
Loading all plugins can take several seconds depending on the number of installed components. Use ``lazy=True`` for faster startup when you don't need immediate access.
|
|
|
|
Indexing
|
|
~~~~~~~~
|
|
|
|
The ``Plugins`` collection maintains multiple indexes. Reindexing is required if you:
|
|
|
|
- Modify plugin properties externally
|
|
- Add plugins after initialization
|
|
|
|
.. code-block:: python
|
|
|
|
logic = Logic(lazy=True)
|
|
logic.discover_plugins()
|
|
# ... modify plugins externally ...
|
|
logic.plugins.reindex_all()
|
|
|
|
Search Performance
|
|
~~~~~~~~~~~~~~~~~~
|
|
|
|
- Exact lookups by indexes are O(1)
|
|
- Fuzzy search with ``rapidfuzz`` is O(n) but optimized
|
|
- Use ``max_results`` to limit computation for large result sets
|
|
|
|
Best Practices
|
|
--------------
|
|
|
|
1. **Single Instance**: Create one ``Logic`` instance and reuse it
|
|
2. **Error Handling**: Wrap operations in try-except blocks for specific exceptions
|
|
3. **Lazy Loading**: Use for CLI tools or services that don't need immediate access
|
|
4. **Reloading**: Call ``load()`` methods after external modifications to databases
|
|
|
|
.. code-block:: python
|
|
|
|
from logic_plugin_manager import (
|
|
Logic,
|
|
CategoryValidationError,
|
|
PluginLoadError
|
|
)
|
|
|
|
try:
|
|
logic = Logic()
|
|
except PluginLoadError as e:
|
|
print(f"Failed to load plugins: {e}")
|
|
# Handle gracefully
|
|
|
|
# Batch operation example
|
|
favorites = {
|
|
logic.plugins.get_by_name(name)
|
|
for name in ["Pro-Q 3", "Serum", "Valhalla VintageVerb"]
|
|
if logic.plugins.get_by_name(name)
|
|
}
|
|
|
|
if favorites:
|
|
fav_category = logic.introduce_category("Favorites")
|
|
logic.add_plugins_to_category(fav_category, favorites)
|
|
logic.sync_category_plugin_amount(fav_category)
|