Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 41 additions & 2 deletions .claude/re/open_questions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand All @@ -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

Expand Down
23 changes: 23 additions & 0 deletions .claude/re/ploytec_bulk_devices.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
4 changes: 2 additions & 2 deletions common/devices/ploytec/ploytec_defs.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
37 changes: 14 additions & 23 deletions macos/Devices/Ploytec/PloytecCodec.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
}
Expand Down Expand Up @@ -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
}
}
}
38 changes: 28 additions & 10 deletions macos/Devices/Ploytec/PloytecEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ bool PloytecEngine::Start() {
ReadFirmwareVersion();
GetHardwareFrameRate();

if (!SetHardwareFrameRate(96000)) {
if (!SetHardwareFrameRate(48000)) {
LogPloytec("handshake failed: sample rate");
return false;
}
Expand All @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down
27 changes: 14 additions & 13 deletions macos/OzzyHAL/OzzyHAL.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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();

Expand Down Expand Up @@ -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<AudioBufferList*>(&mInputConfig); }
AudioBufferList* OzzyHAL::GetOutputStreamConfiguration() const { return const_cast<AudioBufferList*>(&mOutputConfig); }
Expand All @@ -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) {
Expand Down Expand Up @@ -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; }
Expand Down
4 changes: 4 additions & 0 deletions macos/Shared/OzzyLog.h
Original file line number Diff line number Diff line change
Expand Up @@ -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