Data_Lines

A data line is a lightweight transport lane that exposes addresses of live engine state to another module. PDJE uses data lines when another subsystem needs the latest playback or input state without copying whole buffers or rebuilding a second timing model.

Why Data Lines Exist

The main reason to use a data line is zero-copy synchronization:

  • the producer keeps ownership of the live state

  • the consumer receives a small struct of non-owning pointers

  • polling remains cheap enough for UI, gameplay, or wrapper-side bridge code

This is the mechanism used between the core, input, and judge modules.

Typical Use Cases

  • timeline UI that follows nowCursor and maxCursor

  • waveform preview, meters, or spectrogram-style visualizers that read preRenderedData

  • lightweight synchronization between playback and an external gameplay or tool loop

  • judge startup, where the judge needs both the core timing line and the input event line

General Rules

  • All members are non-owning pointers.

  • The producing engine owns the storage behind every pointer.

  • Resetting or recreating the producer can invalidate prior addresses.

  • Read quickly and copy out what you need if another thread will use the data later.

  • Treat these structs as read-mostly bridges. Use control-panel APIs or module methods for mutations instead of writing through raw pointers.

Lifetime, Threading, And ABI Notes

  • Data lines are only valid while the producing object still owns the pointed storage.

  • If you reset or recreate a player or input module, call PullOutDataLine() again instead of caching old pointers.

  • Updates can happen on audio threads, backend threads, or subprocess bridges.

  • If another thread needs long-lived access, copy the values into your own snapshot structure rather than keeping raw pointers.

  • The struct layout can evolve with the project. If you expose these structs across another ABI boundary, treat them as version-sensitive.

Warning

A data line is a set of non-owning pointers. Do not free the memory behind them, and do not hold them across module teardown or reset boundaries.

Core Data Line

PDJE_CORE_DATA_LINE PDJE::PullOutDataLine()
struct PDJE_CORE_DATA_LINE

data line for transmission with other mosudles. all datas are pointer. use carefully.

struct audioSyncData

Field meanings in the current implementation:

  • nowCursor current playback position in PCM frames

  • maxCursor total rendered frame count

  • preRenderedData interleaved stereo PCM buffer when pre-rendered content exists

  • syncD points at audioSyncData, which carries consumed_frames, pre_calculated_unused_frames, and the latest microsecond timestamp

This is the minimal common bridge for playback-side synchronization.

auto line = engine.PullOutDataLine();
if (line.nowCursor && line.maxCursor) {
    auto current = *line.nowCursor;
    auto total = *line.maxCursor;
    (void)current;
    (void)total;
}

if (line.preRenderedData && line.nowCursor) {
    auto frame_index = *line.nowCursor;
    float left = line.preRenderedData[frame_index * 2];
    float right = line.preRenderedData[frame_index * 2 + 1];
    (void)left;
    (void)right;
}

if (line.syncD) {
    auto sync = line.syncD->load(std::memory_order_acquire);
    (void)sync.consumed_frames;
    (void)sync.pre_calculated_unused_frames;
    (void)sync.microsecond;
}

Input Data Line

PDJE_INPUT_DATA_LINE PDJE_Input::PullOutDataLine()

pull out input data line. The input Loop will pass datas in here.

struct PDJE_INPUT_DATA_LINE
struct PDJE_Input_Log

PDJE_INPUT_DATA_LINE exposes two independent channels:

  • input_arena standard keyboard and mouse transport. Call Receive() and then inspect the datas vector of PDJE_Input_Log.

  • midi_datas atomic double buffer of PDJE_MIDI::MIDI_EV. Call Get() to swap the active buffer and inspect the returned vector snapshot.

This split matters operationally:

  • input_arena is the current judge-facing standard-input bridge

  • midi_datas is the MIDI event stream and can be consumed independently

auto line = input.PullOutDataLine();

if (line.input_arena) {
    line.input_arena->Receive();
    for (const auto &event : line.input_arena->datas) {
        std::string name(event.name, event.name_len);
        (void)name;
        (void)event.microSecond;
    }
}

if (line.midi_datas) {
    auto *midi_events = line.midi_datas->Get();
    for (const auto &event : *midi_events) {
        std::string port_name(event.port_name, event.port_name_len);
        (void)port_name;
        (void)event.highres_time;
    }
}

Binding Notes

The older docs also described wrapper access patterns. The current state is:

  • native C++ can read the struct fields directly

  • the current SWIG C# and Python core bindings return opaque SWIGTYPE_p_PDJE_CORE_DATA_LINE handles from PullOutDataLine() rather than field-by-field wrapper objects

  • the Godot-facing wrapper path exposes helper accessors for the core line and uses InputLine signals for input delivery instead of raw PDJE_INPUT_DATA_LINE pointers

Example Godot-side core access:

var engine:PDJE_Wrapper = PDJE_Wrapper.new()
engine.InitEngine("res://database/path")

var core_line = engine.PullOutCoreLine()
var frames = core_line.GetPreRenderedFrames()
var max_cursor = core_line.GetMaxCursor()
var now_cursor = core_line.GetNowCursor()

print(frames.size(), max_cursor, now_cursor)

For the Godot-side input wrapper path, see Input_Engine.

Threading Notes

  • On the core side, the audio thread updates the cursor and sync data.

  • On the input side, backend threads or subprocess bridges update event storage.

  • Consumers should not hold the raw pointers longer than needed.

  • If you rebuild the player or input module, reacquire the line instead of caching old pointers.