Source code for jdxi_editor.devel.query_program_f18

#!/usr/bin/env python3
"""
Script to query JD-Xi for Program Bank F, Program 18 and collect SysEx responses.

This script:
1. Connects to the JD-Xi MIDI ports
2. Loads Program Bank F, Program 18
3. Requests all parameter data
4. Captures all SysEx JSON responses
5. Saves them to a JSON file
"""

import json
import sys
import time
from pathlib import Path
from typing import Any, Dict, List

from PySide6.QtCore import QCoreApplication, QObject, QTimer, Signal

from jdxi_editor.midi.sysex.sections import SysExSection

# Add the project root to the path
sys.path.insert(0, str(Path(__file__).parent))

from decologr import Decologr as log

from jdxi_editor.midi.io.helper import MidiIOHelper
from jdxi_editor.midi.program.helper import JDXiProgramHelper
from jdxi_editor.midi.sysex.request.midi_requests import MidiRequests
from jdxi_editor.ui.editors.helpers.program import calculate_midi_values


[docs] class SysExCollector(QObject): """Collects SysEx JSON responses from the JD-Xi."""
[docs] collection_complete = Signal()
def __init__(self, output_file: Path): super().__init__()
[docs] self.output_file = output_file
[docs] self.collected_responses: List[Dict[str, Any]] = []
[docs] self.collection_timeout = 10.0 # seconds
[docs] self.start_time: float = 0.0
[docs] def on_sysex_json(self, json_string: str) -> None: """ Handle incoming SysEx JSON data. :param json_string: str JSON string containing SysEx data """ try: data = json.loads(json_string) self.collected_responses.append(data) address = data.get(SysExSection.ADDRESS, "unknown") temporary_area = data.get(SysExSection.TEMPORARY_AREA, "unknown") synth_tone = data.get(SysExSection.SYNTH_TONE, "unknown") log.message( f"✓ Collected SysEx response #{len(self.collected_responses)} - ADDRESS: {address}, AREA: {temporary_area}, TONE: {synth_tone}" ) except json.JSONDecodeError as ex: log.error(f"Failed to parse JSON SysEx: {ex}")
[docs] def start_collection(self) -> None: """Start the collection timer.""" self.start_time = time.time() log.message( f"Started collecting SysEx responses (timeout: {self.collection_timeout}s)" )
[docs] def check_timeout(self) -> None: """Check if collection timeout has been reached.""" elapsed = time.time() - self.start_time if elapsed >= self.collection_timeout: log.header_message( f"Collection timeout reached ({self.collection_timeout}s)" ) log.message( f"Total SysEx responses collected: {len(self.collected_responses)}" ) if len(self.collected_responses) == 0: log.warning( "No SysEx responses were collected. The JD-Xi may not have responded, or the program may not exist." ) self.save_responses() self.collection_complete.emit()
[docs] def save_responses(self) -> None: """Save collected responses to JSON file.""" try: output_data = { "program": "F18", "bank": "F", "program_number": 18, "total_responses": len(self.collected_responses), "responses": self.collected_responses, } with open(self.output_file, "w", encoding="utf-8") as f: json.dump(output_data, f, indent=2, ensure_ascii=False) log.header_message( f"✓ Successfully saved {len(self.collected_responses)} SysEx responses" ) log.message(f"Output file: {self.output_file}") # Log summary of collected responses if len(self.collected_responses) > 0: addresses = [ r.get(SysExSection.ADDRESS, "unknown") for r in self.collected_responses ] log.message(f"Collected addresses: {', '.join(set(addresses))}") except Exception as ex: log.error(f"Failed to save responses: {ex}")
[docs] def main(): """Main function to query and collect program data.""" # Create Qt application app = QCoreApplication(sys.argv) # Determine output file output_file = Path.home() / "jdxi_program_f18_sysex.json" log.message(f"Output file: {output_file}") # Initialize MIDI helper log.message("Initializing MIDI helper...") midi_helper = MidiIOHelper() # Find and open JD-Xi ports automatically log.message("Attempting to auto-connect to JD-Xi...") if not midi_helper.auto_connect_jdxi(): log.error( "Could not find or connect to JD-Xi MIDI ports. Please ensure the device is connected." ) return 1 log.message( f"Connected to JD-Xi ports - Input: {midi_helper.in_port_name}, Output: {midi_helper.out_port_name}" ) # Create SysEx collector collector = SysExCollector(output_file) midi_helper.midi_sysex_json.connect(collector.on_sysex_json) # Calculate MIDI values for Bank F, Program 18 bank = "F" program_number = 18 msb, lsb, pc = calculate_midi_values(bank, program_number) log.message(f"Bank {bank}, Program {program_number}: MSB={msb}, LSB={lsb}, PC={pc}") # Create program helper program_helper = JDXiProgramHelper(midi_helper, channel=1) # Set up collection timeout timer timeout_timer = QTimer() timeout_timer.timeout.connect(collector.check_timeout) timeout_timer.setSingleShot(False) timeout_timer.start(1000) # Check every second # Start collection collector.start_collection() # Load the program log.header_message(f"Loading Program {bank}{program_number:02d}...") program_helper.load_program(bank, program_number) # Wait a moment for the program to load time.sleep(0.5) # Request data - this will trigger SysEx responses log.message("Requesting parameter data from JD-Xi...") program_helper.data_request() # Wait for collection to complete def on_collection_complete(): log.header_message("Collection complete!") app.quit() collector.collection_complete.connect(on_collection_complete) # Set a maximum wait time max_wait_timer = QTimer() max_wait_timer.setSingleShot(True) max_wait_timer.timeout.connect( lambda: ( collector.save_responses(), log.header_message("Maximum wait time reached. Saving and exiting..."), app.quit(), ) ) max_wait_timer.start(int((collector.collection_timeout + 2) * 1000)) # Run the application log.header_message("Waiting for SysEx responses from JD-Xi...") log.message("Note: Some parsing errors are normal for non-SysEx MIDI messages") log.message(f"Collection will timeout after {collector.collection_timeout} seconds") return app.exec()
if __name__ == "__main__": sys.exit(main())