Page 1 of 1

TOOL: Generate OSC Input Profile XML with Python

Posted: Fri Jun 14, 2024 1:57 am
by strauberry
Hi, I'm not sure who needs this out there... but I sure did.

Huge shoutout to the QLC+ forum user @janosvitok who wrote a post in 2017 detailing how the hashing function works. I couldn't have cracked this problem without their contribution.

The Python script below generates an Input Profile XML with all the correctly computed hashes derived from the OSC address.

If you have to manually create a ton of mappings in your project for some weird reason like I did... well here ya go.

RELATED INFO:

https://bugreports.qt.io/browse/QTBUG-8595
viewtopic.php?t=12587

PYTHON SCRIPT:

Code: Select all

import xml.etree.ElementTree as ET
from xml.dom import minidom

def reflect_bits(data, width):
    reflection = 0
    for i in range(width):
        if data & (1 << i):
            reflection |= (1 << ((width - 1) - i))
    return reflection

def crc16_qt(data):
    crc = 0xFFFF  # Initial value
    polynomial = 0x1021
    
    for byte in data:
        byte = reflect_bits(byte, 8)
        crc ^= (byte << 8)
        for _ in range(8):
            if crc & 0x8000:
                crc = (crc << 1) ^ polynomial
            else:
                crc = crc << 1
            crc &= 0xFFFF  # Ensure CRC remains a 16-bit value

    crc = reflect_bits(crc, 16)
    crc ^= 0xFFFF
    return crc

def get_hash(path):
    data = path.encode('utf-8')
    hash_value = crc16_qt(data)
    return hash_value

def create_input_profile_xml(osc_paths, types, output_file_path):
    input_profile = ET.Element('InputProfile', xmlns="http://www.qlcplus.org/InputProfile")
    
    # Create and append Creator element
    creator = ET.SubElement(input_profile, 'Creator')
    ET.SubElement(creator, 'Name').text = 'Q Light Controller Plus'
    ET.SubElement(creator, 'Version').text = '4.13.0'
    ET.SubElement(creator, 'Author').text = 'StraubNet'
    
    # Add Manufacturer and Model
    ET.SubElement(input_profile, 'Manufacturer').text = 'Ableton'
    ET.SubElement(input_profile, 'Model').text = 'Live'
    ET.SubElement(input_profile, 'Type').text = 'OSC'
    
    # Create and append Channel elements
    for path, type_ in zip(osc_paths, types):
        channel_number = get_hash(path)
        channel = ET.SubElement(input_profile, 'Channel', Number=str(channel_number))
        ET.SubElement(channel, 'Name').text = path
        ET.SubElement(channel, 'Type').text = type_
    
    # Pretty print the XML
    xml_str = ET.tostring(input_profile, encoding='unicode')
    pretty_xml = minidom.parseString(xml_str).toprettyxml(indent="  ")
    
    # Write to output file
    with open(output_file_path, 'w', encoding='utf-8') as f:
        f.write(pretty_xml)

# Example usage
osc_paths = [
    '/QLC/MSTRPAR', '/QLC/CUE/4', '/QLC/MSTRUV', '/QLC/MSTRSTAGE',
    '/QLC/GRANDMSTR', '/QLC/CUE/3', '/QLC/MSTRSYN', '/QLC/CUE/2',
    '/QLC/CUE/1', '/QLC/HAZE', '/QLC/MSTRMVR', '/QLC/MSTRBEAMS'
]
types = [
    'Slider', 'Button', 'Slider', 'Slider', 'Slider', 'Button', 'Slider', 
    'Button', 'Button', 'Slider', 'Slider', 'Slider'
]

output_file_path = 'QLC_Input_Profile_Generated.xml'
create_input_profile_xml(osc_paths, types, output_file_path)

print(f"Generated QLC+ Input Profile XML saved to {output_file_path}")

Re: TOOL: Generate OSC Input Profile XML with Python

Posted: Tue Jun 18, 2024 12:03 pm
by GGGss
Welcome to the forum,

And THANK YOU for this welcome contribution.