diff --git a/Nodes/DebayerSource.nosnode b/Nodes/DebayerSource.nosnode index dbcd184..89659b4 100644 --- a/Nodes/DebayerSource.nosnode +++ b/Nodes/DebayerSource.nosnode @@ -2364,30 +2364,6 @@ "contents_type": "JobPin", "contents": {} }, - { - "id": "3d7354ea-cfaf-4649-9f74-6731f7885e0d", - "name": "ReferenceSource", - "type_name": "string", - "show_as": "PROPERTY", - "can_show_as": "PROPERTY_ONLY", - "data": "Reference In", - "def": "", - "readonly": true, - "meta_data_map": [ - { - "key": "Category", - "value": "" - } - ], - "contents_type": "JobPin", - "contents": {}, - "visualizers": [ - { - "type": "COMBO_BOX", - "name": "aja.ReferenceSource.399086ca-0c84-466f-99f5-5125d4ed3ecc" - } - ] - }, { "id": "4294d67c-c676-4a57-8304-f479e800df38", "name": "ForceInterlaced", @@ -5732,30 +5708,6 @@ "contents_type": "JobPin", "contents": {} }, - { - "id": "4c2c9821-2999-4714-99c6-fc90e8bce9f5", - "name": "ReferenceSource", - "type_name": "string", - "show_as": "PROPERTY", - "can_show_as": "PROPERTY_ONLY", - "data": "Reference In", - "def": "", - "readonly": true, - "meta_data_map": [ - { - "key": "Category", - "value": "" - } - ], - "contents_type": "JobPin", - "contents": {}, - "visualizers": [ - { - "type": "COMBO_BOX", - "name": "aja.ReferenceSource.3759cad0-513d-47ef-a01b-681847bf6a6a" - } - ] - }, { "id": "9ec4c33a-9393-4af3-8e56-7e5e746227e8", "name": "ForceInterlaced", diff --git a/Nodes/FillKey.nosnode b/Nodes/FillKey.nosnode index 72ea749..312ebef 100644 --- a/Nodes/FillKey.nosnode +++ b/Nodes/FillKey.nosnode @@ -578,44 +578,6 @@ }, "display_name": "Frame Buffer Format" }, - { - "id": "0b8c74ba-0d6e-4327-92bb-036c821c36d3", - "name": "ReferenceSource", - "type_name": "string", - "show_as": "PROPERTY", - "can_show_as": "PROPERTY_ONLY", - "data": "None", - "def": "", - "meta_data_map": [ - { - "key": "AdvancedProperty", - "value": "false" - }, - { - "key": "Category", - "value": "Key" - }, - { - "key": "PinDeduplicationGroup", - "value": "12" - }, - { - "key": "PinPropertyPaneOrder", - "value": "11" - } - ], - "contents_type": "PortalPin", - "contents": { - "source_id": "cc0b4c9f-5510-43b8-b4f3-7d370fbd4ec6" - }, - "display_name": "Reference Source", - "visualizers": [ - { - "type": "COMBO_BOX", - "name": "aja.ReferenceSource.7a5eb76d-90b1-4e0a-bbcc-ae0df5283b0d" - } - ] - }, { "id": "e687032d-4e33-463f-8130-d705a3293423", "name": "QuadLinkOutputMode (1)", @@ -807,44 +769,6 @@ } ] }, - { - "id": "2ffd038e-2f93-462a-9c5a-d389251bcc5d", - "name": "ReferenceSource (1)", - "type_name": "string", - "show_as": "PROPERTY", - "can_show_as": "PROPERTY_ONLY", - "data": "None", - "def": "", - "meta_data_map": [ - { - "key": "AdvancedProperty", - "value": "false" - }, - { - "key": "Category", - "value": "Fill" - }, - { - "key": "PinDeduplicationGroup", - "value": "12" - }, - { - "key": "PinPropertyPaneOrder", - "value": "0" - } - ], - "contents_type": "PortalPin", - "contents": { - "source_id": "432de869-372a-476b-92ca-8bcbd7625b27" - }, - "display_name": "Reference Source", - "visualizers": [ - { - "type": "COMBO_BOX", - "name": "aja.ReferenceSource.7a5eb76d-90b1-4e0a-bbcc-ae0df5283b0d" - } - ] - }, { "id": "a50c3594-ead3-4288-abc0-79dc52064758", "name": "IsOpen", @@ -1235,32 +1159,6 @@ }, "display_name": "Frame Buffer Format" }, - { - "id": "cdd2c053-6150-46f8-b2ae-a14ea3c3ff66", - "name": "ReferenceSource", - "type_name": "string", - "show_as": "PROPERTY", - "can_show_as": "PROPERTY_ONLY", - "data": "None", - "def": "", - "meta_data_map": [ - { - "key": "AdvancedProperty", - "value": "false" - } - ], - "contents_type": "PortalPin", - "contents": { - "source_id": "cc0b4c9f-5510-43b8-b4f3-7d370fbd4ec6" - }, - "display_name": "Reference Source", - "visualizers": [ - { - "type": "COMBO_BOX", - "name": "aja.ReferenceSource.7a5eb76d-90b1-4e0a-bbcc-ae0df5283b0d" - } - ] - }, { "id": "9a5998c2-de0e-4e2e-9aff-e07115d750c3", "name": "QuadLinkOutputMode", @@ -3588,33 +3486,6 @@ "contents": {}, "display_name": "Frame Buffer Format" }, - { - "id": "cc0b4c9f-5510-43b8-b4f3-7d370fbd4ec6", - "name": "ReferenceSource", - "type_name": "string", - "show_as": "PROPERTY", - "can_show_as": "PROPERTY_ONLY", - "data": "None", - "referred_by": [ - "0b8c74ba-0d6e-4327-92bb-036c821c36d3", - "cdd2c053-6150-46f8-b2ae-a14ea3c3ff66" - ], - "def": "", - "meta_data_map": [ - { - "key": "AdvancedProperty", - "value": "false" - } - ], - "contents_type": "JobPin", - "contents": {}, - "visualizers": [ - { - "type": "COMBO_BOX", - "name": "aja.ReferenceSource.32ff7c90-5d51-4541-beab-642b52952a03" - } - ] - }, { "id": "6b61f62a-3e4d-4c17-bc2d-64ecea85761c", "name": "IsQuad", @@ -6757,36 +6628,6 @@ }, "display_name": "Frame Buffer Format" }, - { - "id": "0a265981-2727-4b5c-8ae7-2e4d2218be06", - "name": "ReferenceSource", - "type_name": "string", - "show_as": "PROPERTY", - "can_show_as": "PROPERTY_ONLY", - "data": "None", - "def": "", - "meta_data_map": [ - { - "key": "AdvancedProperty", - "value": "false" - }, - { - "key": "Category", - "value": "Fill" - } - ], - "contents_type": "PortalPin", - "contents": { - "source_id": "432de869-372a-476b-92ca-8bcbd7625b27" - }, - "display_name": "Reference Source", - "visualizers": [ - { - "type": "COMBO_BOX", - "name": "aja.ReferenceSource.7a5eb76d-90b1-4e0a-bbcc-ae0df5283b0d" - } - ] - }, { "id": "8bd2a0c1-b463-469f-9a1e-24af37b1bcfe", "name": "QuadLinkOutputMode", @@ -9172,33 +9013,6 @@ "contents": {}, "display_name": "Frame Buffer Format" }, - { - "id": "432de869-372a-476b-92ca-8bcbd7625b27", - "name": "ReferenceSource", - "type_name": "string", - "show_as": "PROPERTY", - "can_show_as": "PROPERTY_ONLY", - "data": "None", - "referred_by": [ - "2ffd038e-2f93-462a-9c5a-d389251bcc5d", - "0a265981-2727-4b5c-8ae7-2e4d2218be06" - ], - "def": "", - "meta_data_map": [ - { - "key": "AdvancedProperty", - "value": "false" - } - ], - "contents_type": "JobPin", - "contents": {}, - "visualizers": [ - { - "type": "COMBO_BOX", - "name": "aja.ReferenceSource.a783f4fb-06a6-4de4-8bf0-ab4d66c1bda0" - } - ] - }, { "id": "4b46af1d-972f-40e0-b960-e27bbf82bf2d", "name": "IsQuad", diff --git a/Nodes/InputNode.nosnode b/Nodes/InputNode.nosnode index 225f3dd..650d9a3 100644 --- a/Nodes/InputNode.nosnode +++ b/Nodes/InputNode.nosnode @@ -6347,24 +6347,6 @@ "contents": { }, "orphan_state": { } }, - { - "id": "72b92a3e-0d28-402f-bda4-2aa481ba6eef", - "name": "ReferenceSource", - "type_name": "string", - "show_as": "PROPERTY", - "can_show_as": "PROPERTY_ONLY", - "visualizers": [], - "data": "None", - "def": "", - "readonly": true, - "meta_data_map": [ - { "key": "AdvancedProperty", "value": "true" }, - { "key": "Category", "value": "" } - ], - "contents_type": "JobPin", - "contents": { }, - "orphan_state": { "type": "ORPHAN", "message": "Pin not found in owner node's definition" } - }, { "id": "8900ca61-97ad-436d-b121-5c69d9d9a89d", "name": "ForceInterlaced", diff --git a/Nodes/InputWithANC.nosnode b/Nodes/InputWithANC.nosnode index 27a8c40..8666d4f 100644 --- a/Nodes/InputWithANC.nosnode +++ b/Nodes/InputWithANC.nosnode @@ -16918,24 +16918,6 @@ "contents": { }, "orphan_state": { } }, - { - "id": "fe0dc905-de55-4a69-9710-80f27c6b4df0", - "name": "ReferenceSource", - "type_name": "string", - "show_as": "PROPERTY", - "can_show_as": "PROPERTY_ONLY", - "visualizers": [], - "data": "None", - "def": "", - "readonly": true, - "meta_data_map": [ - { "key": "AdvancedProperty", "value": "true" }, - { "key": "Category", "value": "" } - ], - "contents_type": "JobPin", - "contents": { }, - "orphan_state": { "type": "ORPHAN", "message": "Pin not found in owner node's definition" } - }, { "id": "859f01bb-ff7c-4658-88ba-4d29cd28dc48", "name": "ForceInterlaced", diff --git a/Nodes/InputWithAudio.nosnode b/Nodes/InputWithAudio.nosnode index 86c115e..1793d5c 100644 --- a/Nodes/InputWithAudio.nosnode +++ b/Nodes/InputWithAudio.nosnode @@ -645,24 +645,6 @@ "contents": { }, "orphan_state": { } }, - { - "id": "f76520e6-ad8c-40e6-a6f8-d77c965a05e7", - "name": "ReferenceSource", - "type_name": "string", - "show_as": "PROPERTY", - "can_show_as": "PROPERTY_ONLY", - "visualizers": [], - "data": "None", - "def": "", - "readonly": true, - "meta_data_map": [ - { "key": "AdvancedProperty", "value": "true" }, - { "key": "Category", "value": "" } - ], - "contents_type": "JobPin", - "contents": { }, - "orphan_state": { "type": "ORPHAN", "message": "Pin not found in owner node's definition" } - }, { "id": "c64e3cfe-8e4a-4e35-9fa3-221a723eba95", "name": "ForceInterlaced", diff --git a/Source/AJADevice.cpp b/Source/AJADevice.cpp index dae4e27..e980e1e 100644 --- a/Source/AJADevice.cpp +++ b/Source/AJADevice.cpp @@ -13,6 +13,8 @@ #include #include +#include + #if !defined(_WIN32) #define ARRAYSIZE(x) (sizeof(x) / sizeof(x[0])) @@ -241,7 +243,9 @@ void AJADevice::ClearState() SetSDITransmitEnable(channel, false); SetMode(channel, NTV2_MODE_INVALID); } - SetReference(NTV2_REFERENCE_EXTERNAL); + // Routing is gone, but the reference is a device-wide setting owned by the devices pane: + // put back whatever is selected there rather than falling back to the built-in default. + SetReference(SelectedReference); } uint32_t AJADevice::GetFBSize(NTV2Channel channel) @@ -258,6 +262,12 @@ uint32_t AJADevice::GetFBSize(NTV2Channel channel) AJADevice::~AJADevice() { DeviceLock lock(this); + // Drop the listeners before ClearState(): it sets the reference, which would otherwise call back + // into nodes that are holding a raw pointer to this half-destroyed device. + { + std::unique_lock listenerLock(ReferenceListeners.Mutex); + ReferenceListeners.Map.clear(); + } nosDevice->UnregisterDevice(GlobalDeviceId); ClearState(); Close(); @@ -270,19 +280,29 @@ NOS_REGISTER_NAME(string); std::string GetReferenceStringListName(uint64_t serialNumber) { return "aja.ReferenceSource." + std::to_string(serialNumber); } static constexpr const char* REFERENCE_DEFAULTS[2] = { "Reference In", "Free Run" }; +static constexpr char SDI_IN_REFERENCE_PREFIX[] = "SDI In "; nosResult AJADevice::UpdateSettingsCallback(const char* entryName, nosBuffer itemValue) { if (!nos::Name(entryName).AsString().starts_with(NSN_Reference)) return NOS_RESULT_FAILED; std::string serialNumberStr = nos::Name(entryName).AsCStr() + NSN_Reference.AsString().length() + 1; - uint64_t serialNum = std::stoull(serialNumberStr); - + uint64_t serialNum = 0; + auto [_, ec] = std::from_chars(serialNumberStr.data(), serialNumberStr.data() + serialNumberStr.size(), serialNum); + if (ec != std::errc{}) + { + nosEngine.LogE("Malformed serial number in reference settings entry '%s'", entryName); + return NOS_RESULT_FAILED; + } + auto device = Devices.find(serialNum); if (device == Devices.end()) return NOS_RESULT_FAILED; - device->second->UpdateReferenceSource(nos::InterpretObjectData(itemValue), false); + // Reporting failure makes the settings subsystem keep the default value instead of persisting + // a reference the card never accepted. + if (!device->second->UpdateReferenceSource(nos::InterpretObjectData(itemValue), false)) + return NOS_RESULT_FAILED; return NOS_RESULT_SUCCESS; } @@ -332,15 +352,18 @@ AJADevice::AJADevice(std::string const& serial) NOS_AJA_SOFT_CHECK(SetEveryFrameServices(NTV2_OEM_TASKS)); NOS_AJA_SOFT_CHECK(SetMultiFormatMode(true)); - NOS_AJA_SOFT_CHECK(SetReference(NTV2_REFERENCE_EXTERNAL)); + // ClearState() applies SelectedReference, which is still the default here. RegisterSettings() + // overwrites it with the persisted devices pane selection once the device is constructed. ClearState(); } std::string firmwareMsg, firmwareMsgDetails; + // Must outlive the RegisterDevice call below: driverProp.Value is a borrowed pointer into it. + std::string driverPropMessage; nosDeviceProperty driverProp{}; bool isFirmwareValid = true; if (!CheckFirmware(firmwareMsg, firmwareMsgDetails)) { - std::string driverPropMessage = firmwareMsg + "\n Details: " + firmwareMsgDetails; + driverPropMessage = firmwareMsg + "\n Details: " + firmwareMsgDetails; driverProp = {.Name = nos::Name("Firmware Info"), .Value = driverPropMessage.c_str()}; isFirmwareValid = false; } @@ -980,12 +1003,38 @@ void AJADevice::GetReferenceAndFrameRate(NTV2ReferenceSource& reference, NTV2Fra void AJADevice::UpdateReferenceStringList() { std::vector list{ REFERENCE_DEFAULTS[0], REFERENCE_DEFAULTS[1] }; - for (int i = 1; i <= NTV2DeviceGetNumVideoInputs(ID); ++i) - list.push_back("SDI In " + std::to_string(i)); + for (int i = 1; i <= int(NTV2DeviceGetNumVideoInputs(ID)); ++i) + list.push_back(SDI_IN_REFERENCE_PREFIX + std::to_string(i)); nos::UpdateStringList(GetReferenceStringListName(GetSerialNumber()), list); } +std::string AJADevice::ReferenceSourceToString(NTV2ReferenceSource source) const +{ + if (source == NTV2_REFERENCE_EXTERNAL) + return REFERENCE_DEFAULTS[0]; + if (source == NTV2_REFERENCE_FREERUN) + return REFERENCE_DEFAULTS[1]; + for (int i = 0; i < int(NTV2DeviceGetNumVideoInputs(ID)); ++i) + if (ChannelToRefSrc(NTV2Channel(i)) == source) + return SDI_IN_REFERENCE_PREFIX + std::to_string(i + 1); + return NTV2ReferenceSourceToString(source, true); +} + +uint32_t AJADevice::AddReferenceSourceListener(std::function listener) +{ + std::unique_lock lock(ReferenceListeners.Mutex); + auto id = ReferenceListeners.NextID++; + ReferenceListeners.Map[id] = std::move(listener); + return id; +} + +void AJADevice::RemoveReferenceSourceListener(uint32_t id) +{ + std::unique_lock lock(ReferenceListeners.Mutex); + ReferenceListeners.Map.erase(id); +} + bool AJADevice::SetReference(const NTV2ReferenceSource inRefSource, const bool inKeepFramePulseSelect) { auto set = CNTV2Card::SetReference(inRefSource, inKeepFramePulseSelect); @@ -1027,26 +1076,54 @@ bool AJADevice::WaitVBL(NTV2Channel channel, bool isInput, NTV2FieldID fieldId) } } -void AJADevice::UpdateReferenceSource(std::string referenceValue, bool updateSettingsEntry) +bool AJADevice::ParseReferenceSource(std::string const& referenceValue, NTV2ReferenceSource& outSource) const { - auto ReferenceSource = NTV2_REFERENCE_INVALID; if (referenceValue.empty()) - nosEngine.LogE("Empty value received for reference pin!"); - else if (std::string::npos != referenceValue.find("Reference In")) - ReferenceSource = NTV2_REFERENCE_EXTERNAL; - else if (std::string::npos != referenceValue.find("Free Run")) - ReferenceSource = NTV2_REFERENCE_FREERUN; - else if (auto pos = referenceValue.find("SDI In"); std::string::npos != pos) - ReferenceSource = AJADevice::ChannelToRefSrc(NTV2Channel(referenceValue[pos + 7] - '1')); - if (ReferenceSource != NTV2_REFERENCE_INVALID) + return false; + if (std::string::npos != referenceValue.find(REFERENCE_DEFAULTS[0])) { - NTV2ReferenceSource curRef{}; - if (GetReference(curRef) && curRef != ReferenceSource) { - SetReference(ReferenceSource); - if (updateSettingsEntry) - nosSettings->UpdateEntryValue((NSN_Reference.AsString() + "\\" + std::to_string(GetSerialNumber())).c_str(), nosBuffer{ .Data = &referenceValue[0], .Size = referenceValue.length() + 1 }); - } + outSource = NTV2_REFERENCE_EXTERNAL; + return true; } + if (std::string::npos != referenceValue.find(REFERENCE_DEFAULTS[1])) + { + outSource = NTV2_REFERENCE_FREERUN; + return true; + } + auto pos = referenceValue.find(SDI_IN_REFERENCE_PREFIX); + if (std::string::npos == pos) + return false; + const char* first = referenceValue.data() + pos + std::size(SDI_IN_REFERENCE_PREFIX) - 1; + int inputIndex = 0; + auto [_, ec] = std::from_chars(first, referenceValue.data() + referenceValue.size(), inputIndex); + if (ec != std::errc{} || inputIndex < 1 || inputIndex > int(NTV2DeviceGetNumVideoInputs(ID))) + return false; + outSource = AJADevice::ChannelToRefSrc(NTV2Channel(inputIndex - 1)); + return true; +} + +bool AJADevice::UpdateReferenceSource(std::string referenceValue, bool updateSettingsEntry) +{ + NTV2ReferenceSource referenceSource = NTV2_REFERENCE_INVALID; + if (!ParseReferenceSource(referenceValue, referenceSource)) + { + nosEngine.LogE("Device %s: Unsupported reference source '%s'", GetDisplayName().c_str(), referenceValue.c_str()); + return false; + } + + // Remember the selection first, so that a later ClearState() restores it instead of the default. + SelectedReference = referenceSource; + + NTV2ReferenceSource curRef{}; + if ((!GetReference(curRef) || curRef != referenceSource) && !SetReference(referenceSource)) + { + nosEngine.LogE("Device %s: Failed to set reference source to '%s'", GetDisplayName().c_str(), referenceValue.c_str()); + return false; + } + + if (updateSettingsEntry) + nosSettings->UpdateEntryValue((NSN_Reference.AsString() + "\\" + std::to_string(GetSerialNumber())).c_str(), nosBuffer{ .Data = &referenceValue[0], .Size = referenceValue.length() + 1 }); + return true; } void AJADevice::RegisterNode(nos::uuid id) diff --git a/Source/AJADevice.h b/Source/AJADevice.h index 5045984..e4a9d1a 100644 --- a/Source/AJADevice.h +++ b/Source/AJADevice.h @@ -5,6 +5,7 @@ #pragma once // stl +#include #include #include #include @@ -154,7 +155,14 @@ struct AJADevice : CNTV2Card void UnregisterNode(nos::uuid id); bool SetReference (const NTV2ReferenceSource inRefSource, const bool inKeepFramePulseSelect = false) override; - void UpdateReferenceSource(std::string referenceValue, bool updateSettingsEntry); + // Translates a devices pane entry ("Reference In", "Free Run", "SDI In ") into the reference + // source it names, rejecting values this device cannot do. Returns false if the value is unusable. + bool ParseReferenceSource(std::string const& referenceValue, NTV2ReferenceSource& outSource) const; + // Inverse of ParseReferenceSource: the devices pane wording for a reference source. + std::string ReferenceSourceToString(NTV2ReferenceSource source) const; + // Writes the reference selected in the devices pane onto the card. Returns false if the value could + // not be parsed or the card refused it, in which case nothing is written to the settings entry. + bool UpdateReferenceSource(std::string referenceValue, bool updateSettingsEntry); void UpdateReferenceStringList(); void RegisterSettings(); static nosResult UpdateSettingsCallback(const char* entryName, nosBuffer itemValue); @@ -193,6 +201,10 @@ struct AJADevice : CNTV2Card std::recursive_mutex Mutex; } ReferenceListeners; + // Last reference selected in the devices pane. ClearState() re-applies it so that resetting the + // card does not silently drop the user's selection. + std::atomic SelectedReference = NTV2_REFERENCE_EXTERNAL; + std::shared_mutex RegisteredNodesMutex; std::unordered_set RegisteredNodes; }; diff --git a/Source/ChannelNode.cpp b/Source/ChannelNode.cpp index 0b18cce..eddb63b 100644 --- a/Source/ChannelNode.cpp +++ b/Source/ChannelNode.cpp @@ -111,6 +111,7 @@ struct ChannelNode : NodeContext { if (oldDevice) { + UnsubscribeFromReference(oldDevice); oldDevice->UnregisterNode(NodeId); if (DeviceAcquired) { @@ -126,6 +127,7 @@ struct ChannelNode : NodeContext Device->AcquireDevice(); DeviceAcquired = true; } + SubscribeToReference(); } } if (DevicePinValue.vendor_name != PIN_VALUE_NONE && !Device) @@ -262,6 +264,7 @@ struct ChannelNode : NodeContext { if (Device) { + UnsubscribeFromReference(Device); Device->UnregisterNode(NodeId); if (DeviceAcquired) Device->ReleaseDevice(); @@ -269,6 +272,24 @@ struct ChannelNode : NodeContext CurrentChannel.Close(); } + // Follow the device's reference so the node reflects a change made in the devices pane immediately. + void SubscribeToReference() + { + if (!Device || ReferenceListenerId) + return; + ReferenceListenerId = Device->AddReferenceSourceListener([this](NTV2ReferenceSource) { UpdateReferenceSource(); }); + UpdateReferenceSource(); + } + + void UnsubscribeFromReference(AJADevice* device) + { + if (!ReferenceListenerId) + return; + device->RemoveReferenceSourceListener(*ReferenceListenerId); + ReferenceListenerId.reset(); + CurrentChannel.ClearStatus(Channel::StatusType::Reference); + } + static nosResult MigrateNode(nosFbNodePtr node, nosBuffer* outBuffer) { auto migrated = MigrateChannelNode(node); @@ -282,15 +303,23 @@ struct ChannelNode : NodeContext mediaio::YCbCrPixelFormat CurrentPixelFormat = mediaio::YCbCrPixelFormat::YUV8; + // The reference is no longer a pin on this node; it is picked per device in the devices pane. Show + // what that selection currently is on the node, and re-check it against the channel's frame rate. void UpdateReferenceSource() { - if (IsInput) + if (!Device) return; NTV2ReferenceSource curRef{}; NTV2FrameRate refFrameRate{}; Device->GetReferenceAndFrameRate(curRef, refFrameRate); + CurrentChannel.SetStatus(aja::Channel::StatusType::Reference, fb::NodeStatusMessageType::INFO, + "Reference: " + Device->ReferenceSourceToString(curRef), "", 0, false); + + if (IsInput) + return; + if (GetFrameRateFamily(refFrameRate) != GetFrameRateFamily(FrameRate)) CurrentChannel.SetStatus(aja::Channel::StatusType::ReferenceInvalid, fb::NodeStatusMessageType::WARNING, "Reference incompatible with frame rate", "", 5, false); else @@ -336,6 +365,8 @@ struct ChannelNode : NodeContext channelPin.frame_buffer_format = static_cast(CurrentPixelFormat); channelPin.is_interlaced = !IsProgressivePicture(format); CurrentChannel.Update(std::move(channelPin), true); + // The frame rate may have changed, so re-check it against the reference. + UpdateReferenceSource(); } void CheckChannelConfig() @@ -726,6 +757,8 @@ struct ChannelNode : NodeContext std::atomic_bool TryFindChannel = false; + std::optional ReferenceListenerId; + nosResult ExecuteNode(NodeExecuteParams const& params) override { params.MarkAllOutsDirty = false; diff --git a/Source/Migrations.cpp b/Source/Migrations.cpp index 58c1b18..9a47fc9 100644 --- a/Source/Migrations.cpp +++ b/Source/Migrations.cpp @@ -86,7 +86,11 @@ std::optional MigrateChannelNode(nosFbNodePtr node) return true; } - device->UpdateReferenceSource(refValue, true); + // Old graphs can carry a placeholder ("None") in this pin; only a value that names + // a real reference is worth promoting to the device's settings entry. + NTV2ReferenceSource parsed{}; + if (device->ParseReferenceSource(refValue, parsed)) + device->UpdateReferenceSource(refValue, true); return true; } return false;