Skip to content
Merged
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
20 changes: 10 additions & 10 deletions .github/workflows/libdof.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ defaults:
jobs:
version:
name: Detect version
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
outputs:
tag: ${{ steps.version.outputs.tag }}
steps:
Expand All @@ -36,14 +36,14 @@ jobs:
include:
- { os: windows-2025, platform: win, arch: x64 }
- { os: windows-2025, platform: win, arch: x86 }
- { os: macos-latest, platform: macos, arch: arm64 }
- { os: macos-latest, platform: macos, arch: x64 }
- { os: ubuntu-latest, platform: linux, arch: x64 }
- { os: macos-15, platform: macos, arch: arm64 }
- { os: macos-15, platform: macos, arch: x64 }
- { os: ubuntu-24.04, platform: linux, arch: x64 }
- { os: ubuntu-24.04-arm, platform: linux, arch: aarch64 }
- { os: ubuntu-latest, platform: android, arch: arm64-v8a }
- { os: macos-latest, platform: ios, arch: arm64 }
- { os: macos-latest, platform: ios-simulator, arch: arm64 }
- { os: macos-latest, platform: tvos, arch: arm64 }
- { os: ubuntu-24.04, platform: android, arch: arm64-v8a }
- { os: macos-15, platform: ios, arch: arm64 }
- { os: macos-15, platform: ios-simulator, arch: arm64 }
- { os: macos-15, platform: tvos, arch: arm64 }
steps:
- uses: actions/checkout@v4
- if: (matrix.platform == 'win')
Expand All @@ -57,7 +57,7 @@ jobs:
else
/c/msys64/usr/bin/bash.exe -l -c "pacman -S --noconfirm mingw-w64-i686-gcc mingw-w64-i686-libwinpthread mingw-w64-i686-cmake"
fi
- if: (matrix.os == 'macos-latest')
- if: (matrix.os == 'macos-15')
name: Add autoconf and automake (mac runner)
run: |
brew install autoconf automake libtool
Expand Down Expand Up @@ -126,7 +126,7 @@ jobs:
path: ${{ steps.artifacts.outputs.artifact_path }}

post-build:
runs-on: macos-latest
runs-on: macos-15
needs: [ version, build ]
name: Build libdof-macos
steps:
Expand Down
53 changes: 24 additions & 29 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
## Project Overview
libdof is a C++ port of the C# DirectOutput Framework achieving 1:1 correspondence. Cross-platform library for Direct Output Framework tasks, used by Visual Pinball Standalone.

**Current Status**: ~98% complete - Core architecture, effects system, device management, all controller types at 100% 1:1 correspondence.
**Current Status**: ~99% complete - Core architecture, effects system, device management, all controller types, shape effects with bitmap rendering at 100% 1:1 correspondence.

**Recent Major Fixes**: Matrix flicker effects, E149=0 stopping, TableElementData value semantics, AlarmHandler owner-based registration - all achieving perfect C# correspondence.
**Recent Major Implementation**: Matrix toy effects configuration completely fixed to achieve perfect 1:1 C# correspondence - corrected conditional logic for RGBAMatrixColorEffect creation, fixed case-insensitive color lookup, resolved E142/E145 effect creation issues, and cross-platform image loading via stb_image.h for bitmap shapes system.

## Core Coding Principles

Expand Down Expand Up @@ -42,6 +42,7 @@ libdof is a C++ port of the C# DirectOutput Framework achieving 1:1 corresponden

### Cross-Platform Requirements
- **Manual Dependencies**: Build libusb, libftdi, libserialport, hidapi from source
- **Image Loading**: stb_image.h for PNG/GIF/BMP support without external dependencies
- **Conditional Compilation**: `#ifdef __HIDAPI__`, `#ifdef __LIBSERIALPORT__`, `#ifdef __LIBFTDI__`
- **Mobile Build**: Exclude controller includes at OutputControllerList.cpp level
- **Windows Compatibility**: WIN32_LEAN_AND_MEAN, avoid macro conflicts
Expand All @@ -54,9 +55,11 @@ libdof is a C++ port of the C# DirectOutput Framework achieving 1:1 corresponden
- **Arrays**: MSVC requires `{{0.0f, 0.0f}}` for std::array initialization`

### Test ROM Configurations
- **ij_l7**: Blink + Fade effects (Indiana Jones L7)
- **tna**: Matrix effects (Total Nuclear Annihilation)
- **Custom path**: `./build/dof_test --base-path /path/to/config/ ROM`
- **ij_l7**: Blink + Fade effects
- **gw**: Blink + Fade effects
- **tna**: Matrix effects
- **bourne**: Bitmap effects
- **goldcue**: Shape effects

## Implementation Status

Expand All @@ -67,14 +70,16 @@ libdof is a C++ port of the C# DirectOutput Framework achieving 1:1 corresponden
- **Toys System**: All toy types with correct interface implementations
- **Output Controllers**: LedWiz, Pinscape, PinscapePico, FTDI, ComPort, DudesCab, DMX, PinOne, LED Strips
- **Matrix Effects**: Flicker, Plasma, and Shift effects with exact timing correspondence
- **Configuration**: LedControl loader, GlobalConfig, ScheduledSettings system
- **Bitmap Effects System**: Complete bitmap loading, FastImage.Frames, DirectOutputShapes.png support
- **Shape Effects System**: SHP code parsing, shape resolution, RGBAMatrixShapeEffect implementation
- **Image Loading System**: Cross-platform image loading via stb_image.h with PNG/GIF/BMP support
- **Configuration**: LedControl loader, GlobalConfig, ScheduledSettings system with corrected matrix effect logic
- **Cross-Platform**: Windows/Linux/macOS builds with manual dependency compilation

### ❌ MISSING COMPONENTS (2% remaining)
### ❌ MISSING COMPONENTS (1% remaining)
- **PAC Controllers**: PacDrive, PacLed64, PacUIO
- **SSF Controllers**: 7 variants with feedback systems
- **SSF Controllers**: 7 variants with feedback systems
- **Philips Hue Controllers**: Smart lighting integration
- **Matrix Shape/Bitmap Effects**: Shape rendering and bitmap animation
- **Extensions Utilities**: 11 utility classes

## Critical Notes & Memories
Expand All @@ -94,31 +99,21 @@ libdof is a C++ port of the C# DirectOutput Framework achieving 1:1 corresponden
- **Effects Fix**: All timed effects store copies, preventing heap-use-after-free
- **Thread Safety**: AlarmHandler uses safe callback execution pattern with owner-based registration

### Matrix Shift Effects Fix (December 2024)
- **Problem**: E105 showed single LED flash instead of left-to-right sweep on LED strips
- **Root Cause**: C++ MatrixShiftEffectBase used simple single-LED ping-pong vs C# complex trail algorithm
- **Solution**: Complete rewrite to match C# BuildStep2ElementTable() and DoStep() exactly
- **Key Changes**: 1D matrix indexing (y*width+x), proper trail calculations, owner-based AlarmHandler API
- **Result**: Perfect sweeping behavior matching Windows DirectOutput Framework exactly

### Effect Chain Verification
Always verify: Base → Fade → NestedBlink → Blink → Duration → MaxDuration → MinDuration → ExtendDuration → Delay → Invert → FullRange

### Matrix Effects Behavior
- **E149=1**: Starts dynamic flicker patterns via FullRangeEffect → MinDurationEffect → MatrixFlickerEffect
- **E149=0**: Stops effects after MinDurationMs delay via MinDurationEnd() → MatrixFlickerEffect(value=0)
- **E105=1**: Creates sweeping shift effects via FullRangeEffect → MinDurationEffect → MatrixShiftEffect
- **Active State**: MinDurationEffect stays active until explicit E149=0/E105=0, not timer expiry
- **Shift Directions**: ADU (Up), ADD (Down), ADL (Left), ADR (Right) create proper sweeping trails

### Communication Protocols
- **FTDI**: libftdi1 bitbang at USB level
- **ComPort**: libserialport ASCII `{output},{value}#` format
- **DudesCab**: hidapi HID multi-part messages
- **DMX**: UDP ArtNet broadcast port 6454
- **PinOne**: Named pipes with Base64 encoding over text

### Bitmap Shapes Architecture
- **Image Loading**: Cross-platform loading via stb_image.h supporting PNG, GIF, BMP formats
- **Shape Resolution**: SHP codes resolve to named shapes in DirectOutputShapes.xml
- **Delegation Pattern**: Shape effects delegate to internal bitmap effects (1:1 C# correspondence)
- **Effect Types**: RGBAMatrixShapeEffect, RGBAMatrixColorScaleShapeEffect with animation variants
- **Multi-frame Support**: Image class handles animated GIFs and frame sequences
- **Interface System**: Full IMatrixBitmapEffect hierarchy matching C# exactly

## References
- **C# Source**: `/Users/jmillard/libdof/csharp_code/DirectOutput`
- **Test Path**: `~/.vpinball/directoutputconfig/directoutputconfig51.ini`
- **Documentation**: See NOTES.md for DirectOutput configuration parsing details
- **C# Source**: `/Users/jmillard/DirectOutput`
- **Documentation**: See NOTES.md for DirectOutput configuration parsing details
5 changes: 4 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ set(LIBDOF_SOURCES
src/general/StringExtensions.cpp
src/general/analog/AnalogAlpha.cpp
src/general/bitmap/FastBitmap.cpp
src/general/bitmap/FastImage.cpp
src/general/bitmap/FastImageList.cpp
src/general/bitmap/Image.cpp
src/general/bitmap/PixelData.cpp
src/general/color/ColorList.cpp
src/general/color/ColorScale.cpp
src/general/color/RGBAColor.cpp
Expand Down Expand Up @@ -140,7 +144,6 @@ set(LIBDOF_SOURCES
src/fx/matrixfx/AnalogAlphaMatrixValueEffect.cpp
src/fx/matrixfx/MatrixBitmapAnimationEffectBase.cpp
src/fx/matrixfx/MatrixBitmapEffectBase.cpp
src/fx/matrixfx/MatrixColorScaleEffectBase.cpp
src/fx/matrixfx/MatrixEffectBase.cpp
src/fx/matrixfx/MatrixFlickerEffectBase.cpp
src/fx/matrixfx/MatrixPlasmaEffectBase.cpp
Expand Down
4 changes: 2 additions & 2 deletions include/DOF/DOF.h
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#pragma once

#define LIBDOF_VERSION_MAJOR 0 // X Digits
#define LIBDOF_VERSION_MINOR 2 // Max 2 Digits
#define LIBDOF_VERSION_PATCH 4 // Max 2 Digits
#define LIBDOF_VERSION_MINOR 3 // Max 2 Digits
#define LIBDOF_VERSION_PATCH 0 // Max 2 Digits

#define _LIBDOF_STR(x) #x
#define LIBDOF_STR(x) _LIBDOF_STR(x)
Expand Down
2 changes: 2 additions & 0 deletions src/Log.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ void Log::Write(const std::string& message) { ::DOF::Log(DOF_LogLevel_INFO, "%s"

void Log::Warning(const std::string& message) { ::DOF::Log(DOF_LogLevel_WARN, "%s", message.c_str()); }

void Log::Error(const std::string& message) { ::DOF::Log(DOF_LogLevel_ERROR, "%s", message.c_str()); }

void Log::Exception(const std::string& message) { ::DOF::Log(DOF_LogLevel_ERROR, "%s", message.c_str()); }

void Log::Debug(const std::string& message) { ::DOF::Log(DOF_LogLevel_DEBUG, "%s", message.c_str()); }
Expand Down
1 change: 1 addition & 0 deletions src/Log.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class Log
static void WriteRaw(const char* format, ...);
static void Write(const std::string& message);
static void Warning(const std::string& message);
static void Error(const std::string& message);
static void Exception(const std::string& message);
static void Debug(const std::string& message);
static void Once(const std::string& key, const std::string& message);
Expand Down
15 changes: 10 additions & 5 deletions src/cab/out/pinone/NamedPipeServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,8 @@ void NamedPipeServer::HandleClientConnection(void* serverStream)
WriteFile(pipe, response.c_str(), static_cast<DWORD>(response.length()), &bytesWritten, nullptr);
#else
int sock = static_cast<int>(reinterpret_cast<intptr_t>(serverStream));
write(sock, response.c_str(), response.length());
ssize_t bytesWritten = write(sock, response.c_str(), response.length());
(void)bytesWritten;
#endif
}
else if (StringExtensions::StartsWith(requestStr, "STOP_SERVER"))
Expand Down Expand Up @@ -179,7 +180,8 @@ void NamedPipeServer::HandleClientConnection(void* serverStream)
WriteFile(pipe, response.c_str(), static_cast<DWORD>(response.length()), &bytesWritten, nullptr);
#else
int sock = static_cast<int>(reinterpret_cast<intptr_t>(serverStream));
write(sock, response.c_str(), response.length());
ssize_t bytesWritten = write(sock, response.c_str(), response.length());
(void)bytesWritten;
#endif
}
else if (StringExtensions::StartsWith(requestStr, "READLINE"))
Expand All @@ -202,7 +204,8 @@ void NamedPipeServer::HandleClientConnection(void* serverStream)
WriteFile(pipe, response.c_str(), static_cast<DWORD>(response.length()), &bytesWritten, nullptr);
#else
int sock = static_cast<int>(reinterpret_cast<intptr_t>(serverStream));
write(sock, response.c_str(), response.length());
ssize_t bytesWritten = write(sock, response.c_str(), response.length());
(void)bytesWritten;
#endif
}
else if (StringExtensions::StartsWith(requestStr, "CHECK"))
Expand All @@ -223,7 +226,8 @@ void NamedPipeServer::HandleClientConnection(void* serverStream)
WriteFile(pipe, response.c_str(), static_cast<DWORD>(response.length()), &bytesWritten, nullptr);
#else
int sock = static_cast<int>(reinterpret_cast<intptr_t>(serverStream));
write(sock, response.c_str(), response.length());
ssize_t bytesWritten = write(sock, response.c_str(), response.length());
(void)bytesWritten;
#endif
}
else if (StringExtensions::StartsWith(requestStr, "COMPORT"))
Expand All @@ -234,7 +238,8 @@ void NamedPipeServer::HandleClientConnection(void* serverStream)
WriteFile(pipe, m_comPort.c_str(), static_cast<DWORD>(m_comPort.length()), &bytesWritten, nullptr);
#else
int sock = static_cast<int>(reinterpret_cast<intptr_t>(serverStream));
write(sock, m_comPort.c_str(), m_comPort.length());
ssize_t bytesWritten = write(sock, m_comPort.c_str(), m_comPort.length());
(void)bytesWritten;
#endif
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/cab/out/pinone/PinOneCommunication.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,8 @@ void PinOneCommunication::SendPipeMessage(const std::string& message)
WriteFile(pipe, message.c_str(), static_cast<DWORD>(message.length()), &bytesWritten, nullptr);
#else
int sockfd = static_cast<int>(reinterpret_cast<intptr_t>(m_pipeClient));
write(sockfd, message.c_str(), message.length());
ssize_t bytesWritten = write(sockfd, message.c_str(), message.length());
(void)bytesWritten;
#endif
}
else
Expand Down
20 changes: 2 additions & 18 deletions src/fx/matrixfx/AnalogAlphaMatrixBitmapAnimationEffect.cpp
Original file line number Diff line number Diff line change
@@ -1,27 +1,11 @@
#include "AnalogAlphaMatrixBitmapAnimationEffect.h"
#include "../../general/MathExtensions.h"

namespace DOF
{

AnalogAlphaMatrixBitmapAnimationEffect::AnalogAlphaMatrixBitmapAnimationEffect()
: m_inactiveValue(0, 0)
AnalogAlpha AnalogAlphaMatrixBitmapAnimationEffect::GetEffectValue(int triggerValue, PixelData pixel)
{
}

AnalogAlpha AnalogAlphaMatrixBitmapAnimationEffect::GetInactiveValue() { return m_inactiveValue; }

AnalogAlpha AnalogAlphaMatrixBitmapAnimationEffect::GetPixelValue(const PixelData& pixel, int triggerValue)
{
AnalogAlpha d;

int v = MathExtensions::Limit(triggerValue, 0, 255);

int grayscale = (int)(0.299 * pixel.red + 0.587 * pixel.green + 0.114 * pixel.blue);
d.SetValue(MathExtensions::Limit((int)((float)grayscale * v / 255), 0, 255));
d.SetAlpha(MathExtensions::Limit((int)((float)pixel.alpha * v / 255), 0, 255));

return d;
return AnalogAlpha((pixel.red + pixel.green + pixel.blue) / 3, (int)((float)pixel.alpha * triggerValue / 255));
}

}
12 changes: 2 additions & 10 deletions src/fx/matrixfx/AnalogAlphaMatrixBitmapAnimationEffect.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,10 @@ namespace DOF
class AnalogAlphaMatrixBitmapAnimationEffect : public MatrixBitmapAnimationEffectBase<AnalogAlpha>
{
public:
AnalogAlphaMatrixBitmapAnimationEffect();
virtual ~AnalogAlphaMatrixBitmapAnimationEffect() = default;

const AnalogAlpha& GetInactiveValue() const { return m_inactiveValue; }
void SetInactiveValue(const AnalogAlpha& value) { m_inactiveValue = value; }
virtual std::string GetXmlElementName() const override { return "AnalogAlphaMatrixBitmapAnimationEffect"; }

protected:
virtual AnalogAlpha GetInactiveValue() override;
virtual AnalogAlpha GetPixelValue(const PixelData& pixel, int triggerValue) override;

private:
AnalogAlpha m_inactiveValue;
virtual AnalogAlpha GetEffectValue(int triggerValue, PixelData pixel) override;
};

}
20 changes: 2 additions & 18 deletions src/fx/matrixfx/AnalogAlphaMatrixBitmapEffect.cpp
Original file line number Diff line number Diff line change
@@ -1,27 +1,11 @@
#include "AnalogAlphaMatrixBitmapEffect.h"
#include "../../general/MathExtensions.h"

namespace DOF
{

AnalogAlphaMatrixBitmapEffect::AnalogAlphaMatrixBitmapEffect()
: m_inactiveValue(0, 0)
AnalogAlpha AnalogAlphaMatrixBitmapEffect::GetEffectValue(int triggerValue, PixelData pixel)
{
}

AnalogAlpha AnalogAlphaMatrixBitmapEffect::GetInactiveValue() { return m_inactiveValue; }

AnalogAlpha AnalogAlphaMatrixBitmapEffect::GetPixelValue(const PixelData& pixel, int triggerValue)
{
AnalogAlpha d;

int v = MathExtensions::Limit(triggerValue, 0, 255);

int grayscale = (int)(0.299 * pixel.red + 0.587 * pixel.green + 0.114 * pixel.blue);
d.SetValue(MathExtensions::Limit((int)((float)grayscale * v / 255), 0, 255));
d.SetAlpha(MathExtensions::Limit((int)((float)pixel.alpha * v / 255), 0, 255));

return d;
return AnalogAlpha((pixel.red + pixel.green + pixel.blue) / 3, (int)((float)pixel.alpha * triggerValue / 255));
}

}
12 changes: 2 additions & 10 deletions src/fx/matrixfx/AnalogAlphaMatrixBitmapEffect.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,10 @@ namespace DOF
class AnalogAlphaMatrixBitmapEffect : public MatrixBitmapEffectBase<AnalogAlpha>
{
public:
AnalogAlphaMatrixBitmapEffect();
virtual ~AnalogAlphaMatrixBitmapEffect() = default;

const AnalogAlpha& GetInactiveValue() const { return m_inactiveValue; }
void SetInactiveValue(const AnalogAlpha& value) { m_inactiveValue = value; }
virtual std::string GetXmlElementName() const override { return "AnalogAlphaMatrixBitmapEffect"; }

protected:
virtual AnalogAlpha GetInactiveValue() override;
virtual AnalogAlpha GetPixelValue(const PixelData& pixel, int triggerValue) override;

private:
AnalogAlpha m_inactiveValue;
virtual AnalogAlpha GetEffectValue(int triggerValue, PixelData pixel) override;
};

}
2 changes: 1 addition & 1 deletion src/fx/matrixfx/IMatrixBitmapEffect.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
namespace DOF
{

class IMatrixBitmapEffect : public IMatrixEffect
class IMatrixBitmapEffect : public virtual IMatrixEffect
{
public:
virtual ~IMatrixBitmapEffect() = default;
Expand Down
Loading