sq-midi 0.9.0
A package to control Allen & Heath SQ mixers via MIDI
Requires Python: <3.13,>=3.9

Library to control Allen & Heath SQ Mixers via MIDI

# Installation & Setup
```bash
pip install sq-midi
```

On the mixer, go to Utility -> General -> MIDI and set 'NRPN Fader Law' to _Audio Taper_. This is currently the only supported option, however the linear taper may be added in the future.

# Usage
To connect to the mixer's MIDI interface, the [Allen & Heath MIDI Control](https://www.allen-heath.com/hardware/controllers/midi-control/) application can be used. When in *MIDI Thru* mode, two virtual MIDI ports will be created, one input and one output, both named *MIDI Control 1*.
This allows a connection to the mixer either via a physical USB cable, or over a network connection using TCP.

## Basic Example
```python
from sq_midi import Mixer

# Initialise mixer
mixer = Mixer()

# Get Input 1 (Ip1)
ip1 = mixer.inputs.ip1 # or mixer.inputs[1]

# Mute
ip1.mute()

# Set level to 100% (0dB)
ip1.level = 100

```

## Detailed Usage
The `Mixer` class is the starting point, and handles the connection to the mixer, as well as providing interfaces for the various channels and mixes.

Each channel or mix has various attributes, including _assignments_, _levels_, and _panning_, which can be used to set the appropriate parameters on the mixer. Please note that not all mixes have all parameters.

To get/set specific parameter, the appropriate interface should be used e.g. `mixer.inputs[1].assignments.aux[1]`. All indexes refer to the channel/mix numbers, so `aux[1]` refers to Aux 1.

### Mixer setup

```python
from sq_midi import Mixer

mixer = Mixer(
  input_name="MIDI Control 1",  # Input MIDI port name
  output_name="MIDI Control 1",  # Output MIDI port name
  channel=1,  # MIDI channel to use
  debug=False,  # Enable debug logging
  use_percent_scale=True  # Use loudness percentage scale
)
```

#### Audio Level Scales
There are currently two possible audio level scales which can be used. The default is a loudness percentage scale, which has a range from 0 to 200. At 0, the level is -inf; at 100, 0dB and 200, +10dB. On this scale therefore, 50 represents a perceived halving of the volume. The other scale is more closely related to the actual fader position, and ranges from 0.0 to 2.0. 0.0 is -inf, 1.0 is 0dB and 2.0 is +10dB, however 0.5 is ~-18dB, roughly halfway between 0dB and -inf on the fader scale.

### Input channels

Each input channel can be accessed using the `mixer.inputs` interface. They can be accessed either by name e.g. `mixer.inputs.ip14` or by number e.g. `mixer.inputs[14]`.

* Number of channels: **48** 
* Channel prefix: **ip** , e.g **ip34**

* Available options:
  * `level` - master level
  * `pan` - master panning
  * `assignments` - assignments
    * `aux` - auxes
    * `groups` - groups
    * `fx`- FX Sends
    * `lr` - LR (default for `level` and `pan`)
  * `panning` - panning
    * `aux`
    * `groups`
    * `fx`
    * `lr`

Example:
```python
ip1 = mixer.inputs.ip1 # or ...inputs[1]

print(ip1.name) # Ip1
print(ip1.number) # 1

# Mute
ip1.mute()
ip1.unmute()
print(ip1.muted) # True or False
ip1.muted = True # Same as ip1.mute()

# Master Level
ip1.level = 100 # range [0 to 200] % equivalent to [-inf to +10] dB

# Master Panning
print(ip1.pan) # -100 to 100
ip1.pan = 0 # Center
ip1.center() # Same as ip1.pan = 0
ip1.pan = 100 # Full right
ip1.pan = -100 # Full left

# Routing Assignments - available: aux, groups, fx, lr 
print(ip1.assignments.aux[3]) # True / False
ip1.assignments.aux[1] = True # Assign to Aux1
ip1.assignments.groups[3] = True # Assign to Grp3
ip1.assignments.fx[1] = True # Assign to FX1Send
ip1.assignments.lr = False # Unassign from LR
```
### Main LR Output

The main LR channel can be accessed using `mixer.lr`.

Available options:
* `level` - master level
* `mute`
* `pan`
* `assignments` - assignments
  * `mtx` - matrices
* `panning`
  * `lr` - (default)

Example:
```python
lr = mixer.lr

# Assignments - available: mtx (Matrices)
lr.assignments.mtx[1] = True # Assign to Matrix 1
```

### Groups

Example:
```python
grp1 = mixer.groups[1]

# Assignments - available: lr, mtx, fx, aux
grp1.assignments.aux[1] = True # Assign to aux1
```


### Auxes

Example:
```python
"""Aux"""
aux1 = mixer.aux[1]

# Levels
aux1.levels.lr = 100 # Same as aux1.level = 100

# Assignments - available: mtx (Matrices)
aux1.assignments.mtx[3] = True # Assign to Matrix 3
```

### DCAs

DCA channels use the `mixer.dca` interface, and allow the 'remote' control of multiple channels. The MIDI protocol allows the level and mute of a DCA to be controlled.

Available options:
* `level` - DCA level
* `mute` - DCA mute


Example:
```python
dca1 = mixer.dca[1]

dca1.level = 100
```

### FX Sends

FX Sends can be accessed using `mixer.fxsends`.

Available options:
* `level` - level
* `mute` - mute

Example:
```python
fx1send = mixer.fxsends[1]

# Level
fx1send.level = 50 # 50%
```
### FX Returns

FX Returns can be accessed using `mixer.fxreturns`.

Available options:
* `level`
* `mute`
* `pan`
* `assignments`
  * `lr`
  * `aux`
  * `groups`
* `panning`
  * `lr` (default)
  * `aux`
* `levels`
  * `lr` - (default)
  * `aux`

Example:
```python
fx1return = mixer.fxreturns[1]

# Levels
fx1return.level = 200 # 200% (+10dB)
fx1return.levels.aux[4] = 100 # 100% (0dB)

# Assignments
fx1return.assignments.aux[4] = True

# Panning
fx1return.panning.aux[4] = 100
```
### Matrices

Available options:

* `mute`
* `level`
* `pan`

Example:
```python
mtx1 = mixer.matrices[1]

# Level
mtx1.level = 100

# Panning
mtx1.panning = -100 # Full left
```
### Mute Groups

Mute groups are accessed using `mixer.mutegroups`. The assignment of channels to a mute group must be defined within the mixer, using 'Routing' -> 'Mute Assign' for the desired channels.

Available options:

* `mute`

Example:
```python
"""Mute group"""
mgrp1 = mixer.mutegroups[1]

mgrp1.mute()
mgrp1.unmute()
```