Allen & Heath SQ-Drive: WAV Conversion Script for SQ5+ USB storage playback (PYTHON3)

Uses the FFmpeg library, needs to be installed and of course python3.
This was tested on Mac, converts wav’s to 48kHz 24Bit SQ drive WAV format.

The tracks need to be mono. You can only link 1+2 3+4 5+6 etc., make sure there are no gaps. Best practice is to number the tracks before conversion, the script runs all .WAV files in the directory and ignores .sh and .py files.

Howto:

  • Create an empty file called “convert.py”, copy the script code from the post into the empty file with a text editor.
  • Copy the convert.py file to the directory holding the mono wav tracks
  • run the script from the console with “python3 convert.py” inside of the directory
  • tracks are created: TRK01.WAV, TRK02.WAV etc.
  • move the converted tracks to the SQ-Drive under :\AHSQ\USBMTK create a SQ-MTXXX folder with an unused number e.g. SQ-MT012

If you have problems installing python3 or ffmpeg, ask Google AI, it can easily help you out. The script is not flawless it’s AI assisted, use at own risk (no unit tests ran).
Works well here though.

Script:

#!/usr/bin/env python3
import os
import subprocess
import glob
import struct
import re

# Collect all files in the current working directory
files = sorted(glob.glob('*'))
track_index = 1

print("Starting byte-perfect audio conversion for Allen & Heath SQ-Drive...")

for f in files:
    if not os.path.isfile(f):
        continue

# Skip already converted TRKxx.WAV files and script files to avoid loops
if re.match(r'^TRK\d+\.WAV$', f, re.IGNORECASE) or f.endswith('.py') or f.endswith('.sh'):
    continue

# Safely query audio stream indices via ffprobe
cmd = ['ffprobe', '-v', 'error', '-select_streams', 'a', '-show_entries', 'stream=index', '-of', 'csv=p=0', f]
res = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
if res.returncode != 0 or not res.stdout.strip():
    continue
    
streams = res.stdout.strip().split('\n')
for si in streams:
    if not si.strip():
        continue
        
    out_name = f"TRK{track_index:02d}.WAV"
    raw_name = f"tmp_{track_index}.raw"
    
    print(f"Processing '{f}' (Stream #{si})...")
    
    # 1. Extract raw 24-bit little-endian PCM stream via FFmpeg (completely containerless)
    ffmpeg_cmd = [
        'ffmpeg', '-y', '-i', f, '-map', f'0:a:{si}',
        '-ac', '1', '-ar', '48000', '-c:a', 'pcm_s24le', '-f', 's24le', '-vn', raw_name
    ]
    subprocess.run(ffmpeg_cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    
    if os.path.exists(raw_name):
        with open(raw_name, 'rb') as rf:
            pcm_data = rf.read()
        os.remove(raw_name)
        
        data_size = len(pcm_data)
        riff_size = data_size + 504
        
        # 2. Manually stitch a strict 512-byte byte-perfect console header block
        # This perfectly emulates the console's exact chunk sizes and structural layout:
        # - RIFF WAVE fmt (36 bytes)
        # - LIST INFO INAM (30 bytes)
        # - junk padding (438 bytes)
        # Total text/structural headers = 504 bytes. Then 8 bytes of 'data' tag/size = 512 bytes offset.
        header_part1 = struct.pack('<4sI4s4sIHHIIHH4sI4s4sI9sB4sI',
            b'RIFF', riff_size, b'WAVE',
            b'fmt ', 16, 1, 1, 48000, 144000, 3, 24,
            b'LIST', 22, b'INFO', b'INAM', 9, b'MainLR L\x00', 0,
            b'junk', 430
        )
        junk_padding = b'\x00' * 430
        header_part2 = struct.pack('<4sI', b'data', data_size)
        
        full_header = header_part1 + junk_padding + header_part2
        
        # 3. Combine header and payload into the final recognized track file
        with open(out_name, 'wb') as wf:
            wf.write(full_header)
            wf.write(pcm_data)
            
        print(f" -> Successfully generated: {out_name}")
        track_index += 1

print(f"\nDone! Processed {track_index - 1} audio tracks cleanly.")

If you’re on Mac, you can use this script to spit up stereo tracks, it preserves mono tracks but changes the header to match the other tracks.
This was tested on zsh. Didn’t test it with other shells. It also uses the ffmpeg library (use brew to install or another packet manager).
Use the terminal to navigate to the directory of choice (pwd) , copy paste the script and enter.

{

OUTPUT_DIR="./Split_Mono_Tracks"

mkdir -p "$OUTPUT_DIR"

echo "=== Processing Audio Files in Current Directory ==="

for file in *; do

    if [ -f "$file" ]; then

        ext="${file##*.}"

        if [ "$ext" = "wav" ] || [ "$ext" = "WAV" ] || [ "$ext" = "aif" ] || [ "$ext" = "AIF" ] || [ "$ext" = "aiff" ] || [ "$ext" = "AIFF" ]; then

            filename=$(basename "$file")

            basename="${filename%.*}"

            channels=$(ffprobe -v error -select_streams a:0 -show_entries stream=channels -of default=noprint_wrappers=1:nokey=1 "$file" 2>/dev/null)

            if [ -z "$channels" ]; then

                continue

            fi

            if [ "$channels" -eq 1 ]; then

                echo "Processing: $filename (Already Mono)"

                ffmpeg -y -i "$file" -c:a pcm_s24le "$OUTPUT_DIR/$filename" -loglevel quiet

                if [ $? -eq 0 ]; then

                    echo " -> Normalized headers and copied successfully."

                else

                    echo " -> Error processing mono file."

                fi

            elif [ "$channels" -eq 2 ]; then

                echo "Processing: $filename (Stereo)"

                ffmpeg -y -i "$file" -filter_complex "[0:a]channelsplit=channel_layout=stereo[left][right]" -map "[left]" -c:a pcm_s24le "$OUTPUT_DIR/${basename}_L.$ext" -map "[right]" -c:a pcm_s24le "$OUTPUT_DIR/${basename}_R.$ext" -loglevel quiet

                if [ $? -eq 0 ]; then

                    echo " -> Successfully split into separate _L and _R mono channels."

                else

                    echo " -> Error splitting stereo file."

                fi

            else

                echo "Processing: $filename ($channels Channels)"

                echo " -> Skipping: Unsupported multi-channel configuration."

            fi

            echo "----------------------------------------"

        fi

    fi

done

echo "=== Batch Processing Complete ==="

echo "All deliverables can be found in: $OUTPUT_DIR"

}


Just figured out you can just create a custom layer to fill the assignment gaps but it’s better to handle the numbering of the channels before you copy the files.