diff --git a/.claude/re/open_questions.md b/.claude/re/open_questions.md index 03533d4..d2dd53c 100644 --- a/.claude/re/open_questions.md +++ b/.claude/re/open_questions.md @@ -10,8 +10,42 @@ pattern may be a Windows driver quirk, or redundant. Need to test if 2 calls wor ### Junction encoder vs our bit-interleaved codec `pcmTo24Junction6CH` does **channel reordering** (0→0, 3→1, 1→2, 4→3, 2→4, 5→5) and extracts 3 bytes from 4-byte int samples. It does NOT do bit-level interleaving. -Our codec (`ploytec_codec.h`) does bit-interleaved encoding. Need to verify these produce -the same wire format, or if our codec is wrong. +Our codec (`ploytec_codec.h`) does bit-interleaved encoding. These likely produce different +wire formats, but `pcmTo24Junction8CH` (used for DB2/DB4 8-channel output) has not yet been +decompiled to confirm whether IT does bit-scatter or linear. + +**Current status (DB2 debug session) — USB capture confirms bit-scatter IS correct:** +- `PloytecEncodePCM` matches Linux `ploytec_encode_frame` exactly (confirmed byte-for-byte). +- DB2 does NOT appear in the Linux driver's USB ID table — Linux is NOT a reliable reference. +- Windows USB capture shows audio data only in bytes 0x00-0x01 of each + sub-packet — consistent with bit-scatter encoding where ch0 occupies bit 0 of every byte. + This **confirms bit-scatter is the correct encoding** for DB2 output. +- Audio flows correctly into the HAL (peak ~0.1 confirmed with Glass.aiff), MIDI at byte 480. +- Root cause of silence was two mismatches vs Windows driver (both now fixed): + 1. Sample rate: we used 96000 Hz, Windows uses 48000 Hz. + 2. Interrupt sub-packet format: we used 482 bytes (no padding), device expects 512 bytes + (bulk format: 480 PCM + 2 MIDI + 30 padding), confirmed by Windows URBs (14×512=7168). + +### Missing configurationDone initialization steps (SUSPECTED ROOT CAUSE) +Our `Start()` handshake sequence is minimal vs the official driver's `configurationDone`: + +Our sequence: +1. ReadFirmwareVersion +2. GetHardwareFrameRate / SetHardwareFrameRate +3. STATUS read-modify-write (set MODE5 bit) +4. Submit USB transfers + +Official `configurationDone` also calls: +- `updateAjInputSelector` (twice — before and after encoder selection) +- `usbInitDeviceControls` + `usbApplyDeviceControls` (writes device state via vendor 'I' requests) +- `startStreaming` (may be needed to enable USB audio output at the hardware level) +- `AJ::configurationDone` (purpose unclear — AJ subsystem) + +Without `usbApplyDeviceControls`, the device may not route USB audio to its physical +outputs. This is the most likely cause of the silence on DB2. + +**What's needed:** USB capture of the official driver initializing a DB2, then diff +against our init sequence to find the missing vendor requests. ### Index 0 bits 3-7 (0x08–0x80) - For product 0x644: bits 3-4 (mask `0x18`) come from `this[0xd2]`, rest preserved with `& 0xE7` @@ -33,6 +67,11 @@ related to level metering or some control protocol. Has its own pipe assignment - ~~setEsuCpldByte~~ — Writes to 'I' wIndex=1 with `| 0xE7` mask - ~~this[0x1888] meaning~~ — USB 2.0 High Speed flag, NOT bulk device flag - ~~wValue high byte~~ — Sign extension artifact from `(short)(char)byte` cast +- ~~Interrupt sub-packet MIDI position~~ — MIDI at byte 480 (after 10 samples), same as bulk. NOT at 432. +- ~~wValue wrong for DB2~~ — `ploytec_confirm_wvalue` correctly sign-extends via `(uint16_t)(int16_t)(int8_t)modified`. DB2 status=0x12 → writes 0x0032 (not 0xFF32). Already fixed in `common/devices/ploytec/ploytec_protocol.h`. +- ~~DB2 interrupt sub-packet format~~ — Confirmed 512 bytes (bulk format: 480 PCM + 2 MIDI + 30 pad). NOT 482. Windows capture shows 14×512=7168-byte URBs on interrupt endpoint. +- ~~DB2 sample rate~~ — Windows driver sets 48000 Hz, NOT 96000 Hz. Device default is 44100 Hz. Fixed in `PloytecEngine::Start()` and `OzzyHAL`. +- ~~DB2 bit-scatter vs linear encoding~~ — Bit-scatter confirmed correct by capture: audio data only in bytes 0x00-0x01 per sub-packet, consistent with ch0 at bit 0. ## Leads diff --git a/.claude/re/ploytec_bulk_devices.md b/.claude/re/ploytec_bulk_devices.md index ff545ff..d5dc169 100644 --- a/.claude/re/ploytec_bulk_devices.md +++ b/.claude/re/ploytec_bulk_devices.md @@ -46,6 +46,29 @@ Examples from `onServiceStart`: - 0x2573/0x0012: HS=16in+8out, FS=2in+2out - 0x200C/0x1006: HS=6in+6out, FS=2in+2out +## Interrupt sub-packet wire format (confirmed for DB2 by Windows USB capture) + +Both bulk and interrupt sub-packets use **identical 512-byte wire format**: + +``` +Bulk: [480 bytes PCM (10 frames × 48)][2 bytes MIDI][30 bytes padding] = 512 bytes +Interrupt: [480 bytes PCM (10 frames × 48)][2 bytes MIDI][30 bytes padding] = 512 bytes +``` + +**MIDI is always at byte offset 480** — immediately after the 10th PCM frame. + +Evidence from Windows USB capture (Windows driver, DB2 interrupt endpoint 0x05): +- URBs are 7168 bytes = 14 × 512 sub-packets (bulk format, NOT 14 × 482) +- Zero regions appear every 512 bytes at offset 480 (MIDI + 30-byte pad) +- Audio data only in bytes 0x00-0x01 per sub-packet (bit-scatter ch0 confirmed) + +The Windows driver sends 14 sub-packets per URB (7168 bytes). Our driver uses 8 per URB +(4096 bytes). Sub-packet count per URB should not affect correctness — the device consumes +sub-packets independently. The 512-byte wire format is the critical invariant. + +**DB2 sample rate**: 48000 Hz (0x00BB80). Windows driver sets 48kHz, NOT 96kHz. +Device power-on default is 44100 Hz (0x00AC44). + ## Firmware version and transfer types For the DB4 specifically: diff --git a/common/devices/ploytec/ploytec_defs.h b/common/devices/ploytec/ploytec_defs.h index c43fb64..853fcee 100644 --- a/common/devices/ploytec/ploytec_defs.h +++ b/common/devices/ploytec/ploytec_defs.h @@ -36,14 +36,14 @@ extern "C" { /* USB sub-packet sizes (minimal transfer unit) */ #define PLOYTEC_BULK_OUT_SUBPKT_SIZE 512 /* bytes per bulk output sub-packet */ -#define PLOYTEC_INT_OUT_SUBPKT_SIZE 482 /* bytes per interrupt output sub-packet */ +#define PLOYTEC_INT_OUT_SUBPKT_SIZE 512 /* bytes per interrupt output sub-packet (same as bulk: 480 PCM + 2 MIDI + 30 pad) */ #define PLOYTEC_IN_SUBPKT_SIZE 512 /* bytes per input sub-packet (bulk & interrupt) */ #define PLOYTEC_FRAMES_PER_OUT_SUBPKT 10 /* audio frames per output sub-packet */ #define PLOYTEC_FRAMES_PER_IN_SUBPKT 8 /* audio frames per input sub-packet */ /* Packet sizes */ #define PLOYTEC_BULK_OUT_PKT_SIZE 4096 /* 8 bulk sub-packets (512 * 8) */ -#define PLOYTEC_INT_OUT_PKT_SIZE 3856 /* 8 interrupt sub-packets (482 * 8) */ +#define PLOYTEC_INT_OUT_PKT_SIZE 4096 /* 8 interrupt sub-packets (512 * 8, same bulk wire format confirmed by USB capture) */ #define PLOYTEC_IN_PKT_SIZE 5120 /* 10 input sub-packets (512 * 10) */ /* USB interface configuration */ diff --git a/macos/Devices/Ploytec/PloytecCodec.mm b/macos/Devices/Ploytec/PloytecCodec.mm index 6235505..6e12daf 100644 --- a/macos/Devices/Ploytec/PloytecCodec.mm +++ b/macos/Devices/Ploytec/PloytecCodec.mm @@ -125,28 +125,20 @@ void PloytecWriteOutputBulk(uint8_t* ringBuffer, const float* srcFrames, uint64_ } // INTERRUPT Mode: Ring buffer mirrors USB packet structure for zero-copy -// Packet structure: [432 bytes PCM (9 samples)][2 bytes MIDI][48 bytes PCM (1 sample)] = 482 bytes/packet +// Packet structure: [480 bytes PCM (10 samples)][2 bytes MIDI][30 bytes padding] = 512 bytes/packet +// Wire format confirmed identical to bulk by USB capture (Windows driver, Xone:DB2). void PloytecWriteOutputInterrupt(uint8_t* ringBuffer, const float* srcFrames, uint64_t sampleTime, uint32_t frameCount, uint32_t ringSize, uint32_t bytesPerFrame) { for (uint32_t i = 0; i < frameCount; i++) { uint32_t sampleOffset = (uint32_t)((sampleTime + i) % ringSize); - + // Each logical packet = 80 frames = 8 USB sub-packets of 10 frames each uint32_t logicalPacket = sampleOffset / 80; uint32_t frameInLogicalPacket = sampleOffset % 80; uint32_t usbSubPacket = frameInLogicalPacket / 10; uint32_t sampleInSubPacket = frameInLogicalPacket % 10; - - // Calculate byte address within the USB sub-packet - uint32_t sampleByteOffset; - if (sampleInSubPacket < 9) { - // Samples 0-8: at beginning of USB sub-packet - sampleByteOffset = sampleInSubPacket * bytesPerFrame; - } else { - // Sample 9: after MIDI bytes (432 + 2) - sampleByteOffset = 434; - } - - uint32_t byteOffset = (logicalPacket * kOzzyMaxPacketSize) + (usbSubPacket * 482) + sampleByteOffset; + + // All 10 samples are contiguous; MIDI follows at byte 480 + uint32_t byteOffset = (logicalPacket * kOzzyMaxPacketSize) + (usbSubPacket * 512) + (sampleInSubPacket * bytesPerFrame); PloytecEncodePCM(ringBuffer + byteOffset, srcFrames + (i * 8)); } } @@ -191,22 +183,21 @@ void PloytecClearOutputBulk(uint8_t* outputBuffer, uint32_t bufferSize) { // Clear output buffer - INTERRUPT mode: Clear PCM samples only, preserve MIDI byte positions // Ring buffer layout: 128 logical packets at kOzzyMaxPacketSize stride // Each logical packet contains 8 USB sub-packets -// Packet structure: [432 bytes PCM (9 samples)][2 bytes MIDI][48 bytes PCM (1 sample)] = 482 bytes/packet +// Packet structure: [480 bytes PCM (10 samples)][2 bytes MIDI][30 bytes padding] = 512 bytes/packet void PloytecClearOutputInterrupt(uint8_t* outputBuffer, uint32_t bufferSize) { - const uint32_t usbPacketSize = 482; - const uint32_t pcm1Size = 432; - const uint32_t pcm2Size = 48; + const uint32_t usbPacketSize = 512; + const uint32_t pcmSize = 480; const uint32_t numLogicalPackets = kOzzyNumPackets; // 128 logical packets const uint32_t usbSubPacketsPerLogical = 8; - + for (uint32_t logicalPacket = 0; logicalPacket < numLogicalPackets; logicalPacket++) { uint32_t logicalPacketBase = logicalPacket * kOzzyMaxPacketSize; - + for (uint32_t subPacket = 0; subPacket < usbSubPacketsPerLogical; subPacket++) { uint8_t* usbPacket = outputBuffer + logicalPacketBase + (subPacket * usbPacketSize); - memset(usbPacket, 0, pcm1Size); // Clear PCM bytes 0-431 (9 samples) - // Leave MIDI bytes 432-433 untouched for CoreMIDI - memset(usbPacket + 434, 0, pcm2Size); // Clear PCM bytes 434-481 (1 sample) + memset(usbPacket, 0, pcmSize); // Clear PCM bytes 0-479 (10 samples) + // Leave MIDI bytes 480-481 untouched + memset(usbPacket + 482, 0, 30); // Clear padding bytes 482-511 } } } diff --git a/macos/Devices/Ploytec/PloytecEngine.cpp b/macos/Devices/Ploytec/PloytecEngine.cpp index 4f1e364..5d2ea48 100644 --- a/macos/Devices/Ploytec/PloytecEngine.cpp +++ b/macos/Devices/Ploytec/PloytecEngine.cpp @@ -55,7 +55,7 @@ bool PloytecEngine::Start() { ReadFirmwareVersion(); GetHardwareFrameRate(); - if (!SetHardwareFrameRate(96000)) { + if (!SetHardwareFrameRate(48000)) { LogPloytec("handshake failed: sample rate"); return false; } @@ -79,15 +79,22 @@ bool PloytecEngine::Start() { // Hardware is confirmed working and communicating shm->audio.hardwarePresent = true; - // 3. Reset Engine State + // 3. Reset Engine State — initialize timestamp with current host time so + // GetZeroTimeStamp never returns hostTime=0 (which makes CoreAudio think + // the clock started at boot and floods the ring buffer to "catch up"). mHwSampleTime = 0; - shm->audio.timestamp.sampleTime = 0; + { + volatile auto* ts = &shm->audio.timestamp; + ts->sequence = 0; + ts->sampleTime = 0; + ts->hostTime = mBus->GetTime(); + } // 4. Pre-fill MIDI/UART sync pattern (0xFD) in entire output buffer // Each logical packet occupies kOzzyMaxPacketSize bytes - // Each logical packet contains 8 USB sub-packets (482 or 512 bytes each) - uint32_t subPacketSize = mIsBulk ? 512 : 482; - uint32_t midiOffset = mIsBulk ? 480 : 432; + // Each logical packet contains 8 USB sub-packets (512 bytes each for both bulk and interrupt) + uint32_t subPacketSize = 512; + uint32_t midiOffset = 480; // MIDI always follows 10 samples × 48 bytes; 30-byte pad follows MIDI for (uint32_t logicalPacket = 0; logicalPacket < kOzzyNumPackets; logicalPacket++) { uint32_t logicalPacketBase = logicalPacket * kOzzyMaxPacketSize; @@ -136,8 +143,7 @@ void PloytecEngine::ProcessMIDIOutput(uint32_t packetIdx) { // Calculate MIDI byte positions for this packet (8 USB sub-packets per logical packet) uint32_t logicalPacket = packetIdx % kOzzyNumPackets; uint32_t logicalPacketBase = logicalPacket * kOzzyMaxPacketSize; - uint32_t subPacketSize = mIsBulk ? 512 : 482; - uint32_t midiOffset = mIsBulk ? 480 : 432; + uint32_t midiOffset = 480; // MIDI always follows 10 samples × 48 bytes; 30-byte pad follows MIDI // Only use the first MIDI byte position in the first sub-packet // This limits MIDI output to ~1200 bytes/sec (9600 bps) at 96kHz, safely below 31.25 kbps @@ -302,8 +308,20 @@ bool PloytecEngine::SetHardwareFrameRate(uint32_t r) { uint8_t buf[3]; ploytec_encode_rate(r, buf); - mBus->VendorRequest(PLOYTEC_CMD_SET_RATE_TYPE, PLOYTEC_CMD_SET_RATE_REQ, 0x0100, PLOYTEC_EP_RATE_IN, buf, 3); - return mBus->VendorRequest(PLOYTEC_CMD_SET_RATE_TYPE, PLOYTEC_CMD_SET_RATE_REQ, 0x0100, PLOYTEC_EP_RATE_OUT, buf, 3); + /* Windows driver sends 7 SET_CUR calls alternating IN/OUT, starting with IN: + * IN, OUT, IN, OUT, IN, OUT, IN (4× IN endpoint 0x86, 3× OUT endpoint 0x05) + * Confirmed from USB capture of Allen & Heath Xone:DB2 on Windows. */ + const uint16_t eps[7] = { + PLOYTEC_EP_RATE_IN, PLOYTEC_EP_RATE_OUT, + PLOYTEC_EP_RATE_IN, PLOYTEC_EP_RATE_OUT, + PLOYTEC_EP_RATE_IN, PLOYTEC_EP_RATE_OUT, + PLOYTEC_EP_RATE_IN + }; + for (int i = 0; i < 7; i++) { + if (!mBus->VendorRequest(PLOYTEC_CMD_SET_RATE_TYPE, PLOYTEC_CMD_SET_RATE_REQ, 0x0100, eps[i], buf, 3)) + return false; + } + return true; } bool PloytecEngine::WriteHardwareStatus(uint16_t v) { diff --git a/macos/OzzyHAL/OzzyHAL.mm b/macos/OzzyHAL/OzzyHAL.mm index cc32748..4f64669 100644 --- a/macos/OzzyHAL/OzzyHAL.mm +++ b/macos/OzzyHAL/OzzyHAL.mm @@ -70,7 +70,7 @@ static OSStatus WriteObjectID(UInt32 inMax, UInt32* outSize, void* outData, Audi mManufacturerUID = CFSTR("Ozzy"); mZeroTimestampPeriod = 640; - mCurrentStreamFormat.mSampleRate = 96000.0; + mCurrentStreamFormat.mSampleRate = 48000.0; mCurrentStreamFormat.mFormatID = kAudioFormatLinearPCM; mCurrentStreamFormat.mFormatFlags = kAudioFormatFlagIsFloat; mCurrentStreamFormat.mBytesPerPacket = 32; @@ -79,8 +79,8 @@ static OSStatus WriteObjectID(UInt32 inMax, UInt32* outSize, void* outData, Audi mCurrentStreamFormat.mChannelsPerFrame = 8; mCurrentStreamFormat.mBitsPerChannel = 32; - mAvailableSampleRates.mMinimum = 96000.0; - mAvailableSampleRates.mMaximum = 96000.0; + mAvailableSampleRates.mMinimum = 48000.0; + mAvailableSampleRates.mMaximum = 48000.0; RebuildStreamConfigs(); @@ -468,7 +468,7 @@ static OSStatus WriteObjectID(UInt32 inMax, UInt32* outSize, void* outData, Audi CFStringRef OzzyHAL::GetModelUID() const { return mModelUID; } UInt32 OzzyHAL::GetTransportType() const { return kAudioDeviceTransportTypeUSB; } UInt32 OzzyHAL::GetClockDomain() const { return 0x504C4F59; } -Float64 OzzyHAL::GetNominalSampleRate() const { return 96000.0; } +Float64 OzzyHAL::GetNominalSampleRate() const { return 48000.0; } AudioValueRange OzzyHAL::GetAvailableSampleRates() const { return mAvailableSampleRates; } AudioBufferList* OzzyHAL::GetInputStreamConfiguration() const { return const_cast(&mInputConfig); } AudioBufferList* OzzyHAL::GetOutputStreamConfiguration() const { return const_cast(&mOutputConfig); } @@ -477,25 +477,26 @@ static OSStatus WriteObjectID(UInt32 inMax, UInt32* outSize, void* outData, Audi AudioStreamBasicDescription OzzyHAL::GetStreamFormat() const { return mCurrentStreamFormat; } AudioStreamRangedDescription OzzyHAL::GetStreamRangedDescription() const { AudioStreamRangedDescription outDesc; outDesc.mFormat = GetStreamFormat(); - outDesc.mSampleRateRange.mMinimum = 96000.0; outDesc.mSampleRateRange.mMaximum = 96000.0; + outDesc.mSampleRateRange.mMinimum = 48000.0; outDesc.mSampleRateRange.mMaximum = 48000.0; return outDesc; } UInt32 OzzyHAL::GetZeroTimestampPeriod() const { return mZeroTimestampPeriod; } AudioChannelLayout* OzzyHAL::GetPreferredChannelLayout(AudioObjectPropertyScope inScope) { AudioChannelLayout* layout = (AudioChannelLayout*)mChannelLayoutBuffer; - layout->mChannelLayoutTag = kAudioChannelLayoutTag_UseChannelDescriptions; + /* MPEG_7_1_A: L, R, C, LFE, Ls, Rs, Lss, Rss (8 channels with standard L/R labels). + * CoreAudio maps stereo afplay (L→ch0, R→ch1) because the first two channels carry + * kAudioChannelLabel_Left and kAudioChannelLabel_Right. + * DiscreteInOrder would assign kAudioChannelLabel_Discrete_0..7 which never matches + * any stereo source label, causing CoreAudio to silence the entire mix. */ + layout->mChannelLayoutTag = kAudioChannelLayoutTag_MPEG_7_1_A; layout->mChannelBitmap = 0; - layout->mNumberChannelDescriptions = 8; - for (UInt32 i = 0; i < 8; ++i) { - layout->mChannelDescriptions[i].mChannelLabel = kAudioChannelLabel_Unknown; - layout->mChannelDescriptions[i].mChannelFlags = kAudioChannelFlags_AllOff; - } + layout->mNumberChannelDescriptions = 0; return layout; } UInt32 OzzyHAL::GetPreferredChannelLayoutSize(AudioObjectPropertyScope inScope) const { - return offsetof(AudioChannelLayout, mChannelDescriptions) + (8 * sizeof(AudioChannelDescription)); + return offsetof(AudioChannelLayout, mChannelDescriptions); /* no descriptions when using tag */ } extern "C" HRESULT OzzyInitialize(AudioServerPlugInDriverRef, AudioServerPlugInHostRef inHost) { @@ -696,7 +697,7 @@ static OSStatus WriteObjectID(UInt32 inMax, UInt32* outSize, void* outData, Audi extern "C" HRESULT OzzyStartIO(AudioServerPlugInDriverRef, AudioObjectID, UInt32) { return OzzyHAL::Get().StartIO(); } extern "C" HRESULT OzzyStopIO(AudioServerPlugInDriverRef, AudioObjectID, UInt32) { return OzzyHAL::Get().StopIO(); } extern "C" HRESULT OzzyGetZeroTimeStamp(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID, UInt32 inClientID, Float64* outSampleTime, UInt64* outHostTime, UInt64* outSeed) { return OzzyHAL::Get().GetZeroTimeStamp(inDriver, inDeviceObjectID, inClientID, outSampleTime, outHostTime, outSeed); } -extern "C" OSStatus OzzyWillDoIOOperation(AudioServerPlugInDriverRef, AudioObjectID, UInt32, UInt32 inOperationID, Boolean* outWillDo, Boolean* outWillDoInPlace) { bool willDo = false; bool willDoInPlace = true; switch (inOperationID) { case kAudioServerPlugInIOOperationWriteMix: case kAudioServerPlugInIOOperationReadInput: willDo = true; break; default: willDo = false; break; } if (outWillDo) *outWillDo = willDo; if (outWillDoInPlace) *outWillDoInPlace = willDoInPlace; return kAudioHardwareNoError; } +extern "C" OSStatus OzzyWillDoIOOperation(AudioServerPlugInDriverRef, AudioObjectID, UInt32, UInt32 inOperationID, Boolean* outWillDo, Boolean* outWillDoInPlace) { bool willDo = false; bool willDoInPlace = true; switch (inOperationID) { case kAudioServerPlugInIOOperationWriteMix: willDo = true; willDoInPlace = false; break; case kAudioServerPlugInIOOperationReadInput: willDo = true; break; default: willDo = false; break; } if (outWillDo) *outWillDo = willDo; if (outWillDoInPlace) *outWillDoInPlace = willDoInPlace; return kAudioHardwareNoError; } extern "C" OSStatus OzzyBeginIOOperation(AudioServerPlugInDriverRef, AudioObjectID, UInt32, UInt32, UInt32, const AudioServerPlugInIOCycleInfo*) { return 0; } extern "C" OSStatus OzzyDoIOOperation(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID, UInt32 inStreamObjectID, UInt32 inClientID, UInt32 inOperationID, UInt32 inIOBufferFrameSize, const AudioServerPlugInIOCycleInfo* inIOCycleInfo, void* ioMainBuffer, void* ioSecondaryBuffer) { return OzzyHAL::Get().ioOperation(inDriver, inDeviceObjectID, inStreamObjectID, inClientID, inOperationID, inIOBufferFrameSize, inIOCycleInfo, ioMainBuffer, ioSecondaryBuffer); } extern "C" OSStatus OzzyEndIOOperation(AudioServerPlugInDriverRef, AudioObjectID, UInt32, UInt32, UInt32, const AudioServerPlugInIOCycleInfo*) { return 0; } diff --git a/macos/Shared/OzzyLog.h b/macos/Shared/OzzyLog.h index c0035da..2d50c74 100644 --- a/macos/Shared/OzzyLog.h +++ b/macos/Shared/OzzyLog.h @@ -41,4 +41,8 @@ static inline os_log_t GetOzzyMIDILog() { #define LogOzzyMIDI(fmt, ...) os_log_info(GetOzzyMIDILog(), "[OzzyMIDI] " fmt, ##__VA_ARGS__) #define LogOzzyMIDIError(fmt, ...) os_log_error(GetOzzyMIDILog(), "[OzzyMIDI] " fmt, ##__VA_ARGS__) +/* Kext macros available in userspace (clangd, unit tests). */ +#define LogOzzyKext(fmt, ...) os_log(OS_LOG_DEFAULT, "[OzzyKext] " fmt, ##__VA_ARGS__) +#define LogPloytec(fmt, ...) os_log(OS_LOG_DEFAULT, "[Ploytec] " fmt, ##__VA_ARGS__) + #endif