ipc thing, and some more stuff i guess

This commit is contained in:
2026-07-11 13:37:41 +02:00
parent 564ea1c20e
commit 5bad2b8200
17 changed files with 271 additions and 264 deletions
+1 -1
View File
@@ -3,7 +3,7 @@
"debug": "disable", "debug": "disable",
"os": "disable", "os": "disable",
"jit": "disable", "jit": "disable",
"ffi":"disable", "ffi":"disable"
}, },
"workspace.library": ["context.lua"], "workspace.library": ["context.lua"],
"diagnostics.disable": [ "diagnostics.disable": [
+27 -2
View File
@@ -14,7 +14,7 @@ add_executable(rds95 ${SOURCES})
find_package(Lua REQUIRED) find_package(Lua REQUIRED)
target_include_directories(rds95 PRIVATE ${LUA_INCLUDE_DIR}) target_include_directories(rds95 PRIVATE ${LUA_INCLUDE_DIR})
target_link_libraries(rds95 PRIVATE m pthread pulse pulse-simple inih ${LUA_LIBRARIES}) target_link_libraries(rds95 PRIVATE m pthread inih ${LUA_LIBRARIES})
install(TARGETS rds95 DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) install(TARGETS rds95 DESTINATION ${CMAKE_INSTALL_PREFIX}/bin)
@@ -23,7 +23,9 @@ install(CODE
# Define the paths for the source and destination files # Define the paths for the source and destination files
set(PREFIX_FILE \"${CMAKE_CURRENT_SOURCE_DIR}/.script.lua\") set(PREFIX_FILE \"${CMAKE_CURRENT_SOURCE_DIR}/.script.lua\")
set(SCRIPTS_DIR \"${CMAKE_CURRENT_SOURCE_DIR}/scripts\") set(SCRIPTS_DIR \"${CMAKE_CURRENT_SOURCE_DIR}/scripts\")
set(MODULES_DIR \"${CMAKE_CURRENT_SOURCE_DIR}/modules\")
set(DEST_FILE \"/etc/rds95.lua\") set(DEST_FILE \"/etc/rds95.lua\")
set(DEST_MODULES_DIR \"/etc/rds95\")
# Initialize content variable # Initialize content variable
set(FINAL_CONTENT \"\") set(FINAL_CONTENT \"\")
@@ -62,13 +64,36 @@ install(CODE
# Write the resulting content to the destination file # Write the resulting content to the destination file
message(STATUS \"Installing script file to \${DEST_FILE}\") message(STATUS \"Installing script file to \${DEST_FILE}\")
file(WRITE \${DEST_FILE} \"\${FINAL_CONTENT}\") file(WRITE \${DEST_FILE} \"\${FINAL_CONTENT}\")
if(EXISTS \${MODULES_DIR} AND IS_DIRECTORY \${MODULES_DIR})
file(MAKE_DIRECTORY \${DEST_MODULES_DIR})
file(GLOB LUA_MODULES \"\${MODULES_DIR}/*.lua\")
list(LENGTH LUA_MODULES MODULE_COUNT)
if(MODULE_COUNT GREATER 0)
message(STATUS \"Installing \${MODULE_COUNT} Lua module(s) to \${DEST_MODULES_DIR}.\")
foreach(LUA_MODULE \${LUA_MODULES})
get_filename_component(MODULE_NAME \${LUA_MODULE} NAME)
file(COPY \${LUA_MODULE} DESTINATION \${DEST_MODULES_DIR})
message(STATUS \"Installed module: \${MODULE_NAME}\")
endforeach()
else()
message(STATUS \"No Lua modules found in modules directory.\")
endif()
else()
message(STATUS \"Modules directory not found.\")
endif()
# Change ownership to the user who invoked sudo (if applicable) # Change ownership to the user who invoked sudo (if applicable)
if(DEFINED ENV{SUDO_USER}) if(DEFINED ENV{SUDO_USER})
message(STATUS \"Changing ownership of \${DEST_FILE} to \$ENV{SUDO_USER}\") message(STATUS \"Changing ownership of \${DEST_FILE} to \$ENV{SUDO_USER}\")
execute_process(COMMAND chown \$ENV{SUDO_USER}:\$ENV{SUDO_USER} \${DEST_FILE}) execute_process(COMMAND chown \$ENV{SUDO_USER}:\$ENV{SUDO_USER} \${DEST_FILE})
if(EXISTS \${DEST_MODULES_DIR})
message(STATUS \"Changing ownership of \${DEST_MODULES_DIR} to \$ENV{SUDO_USER}\")
execute_process(COMMAND chown -R \$ENV{SUDO_USER}:\$ENV{SUDO_USER} \${DEST_MODULES_DIR})
endif()
else() else()
message(STATUS \"No SUDO_USER detected, skipping chown\") message(STATUS \"No SUDO_USER detected, skipping chown\")
endif() endif()
" "
) )
+9 -78
View File
@@ -63,7 +63,7 @@ hooks.on_start = {}
---@type function[] ---@type function[]
hooks.on_state = {} hooks.on_state = {}
---This function is called every second (or rather if the second has changed since the last stream 0 group) ---This function is called every second
---It should be defined by the user in the script. ---It should be defined by the user in the script.
---This is a table of functions. Each will be called ---This is a table of functions. Each will be called
---@type function[] ---@type function[]
@@ -81,12 +81,18 @@ hooks.minute_tick = {}
---@type function[] ---@type function[]
hooks.rt_transmission = {} hooks.rt_transmission = {}
---This function is calld every time a PS transmits start to end ---This function is called every time a PS transmits start to end
---It should be defined by the user in the script. ---It should be defined by the user in the script.
---This is a table of functions. Each will be called ---This is a table of functions. Each will be called
---@type function[] ---@type function[]
hooks.ps_transmission = {} hooks.ps_transmission = {}
---This function is called before the next group is determined
---It should be defined by the user in the script.
---This is a table of functions. Each will be called
---@type function[]
hooks.pre_tx = {}
---This function is called in order to handle UDP data. The string returned is sent back to the UDP peer as a response ---This function is called in order to handle UDP data. The string returned is sent back to the UDP peer as a response
---It should be defined by the user in the script. ---It should be defined by the user in the script.
---@param data string ---@param data string
@@ -272,79 +278,4 @@ function userdata.get() end
---@param offset integer ---@param offset integer
---@param size integer ---@param size integer
---@return string ---@return string
function userdata.get_offset(offset, size) end function userdata.get_offset(offset, size) end
---@class ext
ext = {}
---@class RDSext
RDS.ext = {}
-- RT Plus Tags
---Sets RT+ tags: type1, start1, len1, type2, start2, len2
---@param ertp boolean
---@param t1 integer
---@param s1 integer
---@param l1 integer
---@param t2 integer
---@param s2 integer
---@param l2 integer
function RDS.ext.set_rtplus_tags(ertp, t1, s1, l1, t2, s2, l2) end
---Gets RT+ tags: type1, start1, len1, type2, start2, len2
---@param ertp boolean
---@return integer type1, integer start1, integer len1, integer type2, integer start2, integer len2
function RDS.ext.get_rtplus_tags(ertp) end
---Toggles RTP or ERTP's toggle switch
---@param ertp boolean
function RDS.ext.toggle_rtp(ertp) end
---Sets the metadata of RTP or ERTP
---@param ertp boolean
---@param running boolean
function RDS.ext.set_rtp_meta(ertp, running) end
---Gets the metadata of RTP and ERTP
---@param ertp boolean
---@return boolean running
function RDS.ext.get_rtp_meta(ertp) end
---Sets the AFs included in the ODA
---@param afs table
function RDS.ext.set_af_oda(afs) end
---Registers an ODA to be used in the 0x6 of the group sequence. ODAs are stored as state data, thus running reset_rds will clear it
---Groups 14, 15, 2, 0 cannot be registered either version, groups 10, 4, 1 can be only registered as B, any other is free to take
---Group 3A will mean that there will be no group handler for this ODA, meaning it can only be interacted with via the 3A AID group, handler set is not possible with such groups
---@param group integer
---@param group_version boolean
---@param aid integer
---@param data integer
---@param temp boolean Send AID once (no data) and destroy
---@return integer oda_id
function ext.register_oda(group, group_version, aid, data, temp) end
---Unregisters an ODA, this stops the handler or AID being called/sent
---@param oda_id integer
function ext.unregister_oda(oda_id) end
---Sets the data for a existing ODA group
---@param oda_id integer
---@param data integer
function ext.set_oda_id_data(oda_id, data) end
---The callback function for an ODA handler
---@alias ODAHandler fun(): (boolean, integer, integer, integer)
---Sets a function to handle the ODA data generation.
---The handler is called when the group sequence '0xff' slot is processed.
---The function must return 3 integers representing RDS Blocks B, C, and D.
---Please note that you do not need to compute the block B to indentify the group and group version, that will be done for you and EVERY SINGLE group has PTY and TP inserted (and also PI if its a B inside block C)
---You are asked to set groups B last 5 bits, leave rest 0
---@param oda_id integer The ID returned by register_oda
---@param fun ODAHandler
function ext.set_oda_handler(oda_id, fun) end
---@param ert string
function RDS.ext.set_ert(ert) end
---@return string
function RDS.ext.get_ert() end
+14 -8
View File
@@ -1,7 +1,11 @@
_Af_Oda_id = nil local oda = require("oda")
_Af_Oda_state = 0 ---@class AfModule
_Af_Oda_len = 0 local af = {}
_Af_Oda_afs = {}
local _Af_Oda_id = nil
local _Af_Oda_state = 0
local _Af_Oda_len = 0
local _Af_Oda_afs = {}
local USERDATA_ODA_OFFSET = 274 local USERDATA_ODA_OFFSET = 274
@@ -55,8 +59,8 @@ end
local function init_af_oda() local function init_af_oda()
if _Af_Oda_id == nil then if _Af_Oda_id == nil then
_Af_Oda_id = ext.register_oda(7, false, 0x6365, 0, false) _Af_Oda_id = oda.register_oda(7, false, 0x6365, 0, false)
ext.set_oda_handler(_Af_Oda_id, function() oda.set_oda_handler(_Af_Oda_id, function()
local b, c, d = get_next_af_oda_group() local b, c, d = get_next_af_oda_group()
return true, b, c, d return true, b, c, d
end) end)
@@ -106,7 +110,7 @@ end
---Sets the AFs included in the ODA and saves them ---Sets the AFs included in the ODA and saves them
---@param afs table List of numbers (e.g., {98.1, 102.5}) ---@param afs table List of numbers (e.g., {98.1, 102.5})
function RDS.ext.set_af_oda(afs) function af.set_af_oda(afs)
_process_af_list(afs) _process_af_list(afs)
save_af_to_userdata(afs) save_af_to_userdata(afs)
end end
@@ -114,4 +118,6 @@ end
table.insert(hooks.on_state, function () table.insert(hooks.on_state, function ()
load_af_from_userdata() load_af_from_userdata()
if _Af_Oda_len ~= 0 then init_af_oda() end if _Af_Oda_len ~= 0 then init_af_oda() end
end) end)
return af
+17 -11
View File
@@ -1,13 +1,17 @@
_Ert_state = 0 local oda = require("oda")
_Ert_oda_id = nil ---@class ErtModule
local ert = {}
local _Ert_state = 0
local _Ert_oda_id = nil
--- Size: 259 bytes --- Size: 259 bytes
local USERDATA_ERT_OFFSET = 0 local USERDATA_ERT_OFFSET = 0
local function init_ert() local function init_ert()
if _Ert_oda_id == nil then if _Ert_oda_id == nil then
_Ert_oda_id = ext.register_oda(13, false, 0x6552, 1, false) _Ert_oda_id = oda.register_oda(13, false, 0x6552, 1, false)
ext.set_oda_handler(_Ert_oda_id, function () oda.set_oda_handler(_Ert_oda_id, function ()
if string.byte(userdata.get_offset(USERDATA_ERT_OFFSET+258, 1)) == 1 then if string.byte(userdata.get_offset(USERDATA_ERT_OFFSET+258, 1)) == 1 then
local new_data = userdata.get_offset(USERDATA_ERT_OFFSET, 128) local new_data = userdata.get_offset(USERDATA_ERT_OFFSET, 128)
local new_segments = string.byte(userdata.get_offset(USERDATA_ERT_OFFSET+128, 1)) local new_segments = string.byte(userdata.get_offset(USERDATA_ERT_OFFSET+128, 1))
@@ -34,22 +38,22 @@ local function init_ert()
end end
end end
function unregister_ert() function ert.unregister_ert()
if _Ert_oda_id ~= nil then if _Ert_oda_id ~= nil then
ext.unregister_oda(_Ert_oda_id) oda.unregister_oda(_Ert_oda_id)
_Ert_oda_id = nil _Ert_oda_id = nil
end end
end end
function RDS.ext.set_ert(ert) function ert.set_ert(text)
if #ert == 0 then if #text == 0 then
userdata.set_offset(USERDATA_ERT_OFFSET, 128, "") userdata.set_offset(USERDATA_ERT_OFFSET, 128, "")
userdata.set_offset(USERDATA_ERT_OFFSET+128, 1, string.char(0)) userdata.set_offset(USERDATA_ERT_OFFSET+128, 1, string.char(0))
userdata.set_offset(USERDATA_ERT_OFFSET+258, 1, string.char(1)) userdata.set_offset(USERDATA_ERT_OFFSET+258, 1, string.char(1))
return return
end end
local data = ert .. "\r" local data = text .. "\r"
data = string.sub(data, 1, 128) data = string.sub(data, 1, 128)
local padding = (4 - (#data % 4)) % 4 local padding = (4 - (#data % 4)) % 4
@@ -72,7 +76,7 @@ function RDS.ext.set_ert(ert)
if _Ert_oda_id == nil then init_ert() end if _Ert_oda_id == nil then init_ert() end
end end
function RDS.ext.get_ert() function ert.get_ert()
local segments = string.byte(userdata.get_offset(USERDATA_ERT_OFFSET+128, 1)) local segments = string.byte(userdata.get_offset(USERDATA_ERT_OFFSET+128, 1))
if segments == 0 then return "" end if segments == 0 then return "" end
@@ -82,4 +86,6 @@ end
table.insert(hooks.on_state, function () table.insert(hooks.on_state, function ()
if string.byte(userdata.get_offset(USERDATA_ERT_OFFSET+257, 1)) ~= 0 then init_ert() end if string.byte(userdata.get_offset(USERDATA_ERT_OFFSET+257, 1)) ~= 0 then init_ert() end
end) end)
return ert
+11 -5
View File
@@ -1,3 +1,5 @@
---@class OdaModule
local oda = {}
local _ODA = { group = 0, group_version = false, aid = 0, data = 0, handler = false, temp = false } local _ODA = { group = 0, group_version = false, aid = 0, data = 0, handler = false, temp = false }
function _ODA.new(group, group_version, aid, data, handler, temp) function _ODA.new(group, group_version, aid, data, handler, temp)
@@ -18,7 +20,7 @@ local _RDS_ODA_pointer = 1
---@param data integer ---@param data integer
---@param temp boolean ---@param temp boolean
---@return integer oda_id ---@return integer oda_id
function ext.register_oda(group, group_version, aid, data, temp) function oda.register_oda(group, group_version, aid, data, temp)
if group == 14 or group == 15 or group == 2 or group == 0 then error("Group is incorrect", 2) end if group == 14 or group == 15 or group == 2 or group == 0 then error("Group is incorrect", 2) end
if (group == 10 or group == 4 or group == 1) and group_version then error("Group is incorrect", 2) end if (group == 10 or group == 4 or group == 1) and group_version then error("Group is incorrect", 2) end
local oda = _ODA.new(group, group_version, aid, data, false, temp) local oda = _ODA.new(group, group_version, aid, data, false, temp)
@@ -34,7 +36,7 @@ end
---Unregisters an ODA, this stops the handler or AID being called/sent ---Unregisters an ODA, this stops the handler or AID being called/sent
---@param oda_id integer ---@param oda_id integer
function ext.unregister_oda(oda_id) function oda.unregister_oda(oda_id)
if oda_id < 1 or oda_id > #_RDS_ODAs or _RDS_ODAs[oda_id] == false then error("Invalid ODA ID: " .. tostring(oda_id), 2) end if oda_id < 1 or oda_id > #_RDS_ODAs or _RDS_ODAs[oda_id] == false then error("Invalid ODA ID: " .. tostring(oda_id), 2) end
_RDS_ODAs[oda_id] = false _RDS_ODAs[oda_id] = false
@@ -46,11 +48,13 @@ end
---Sets the id_data for a existing ODA group ---Sets the id_data for a existing ODA group
---@param oda_id integer ---@param oda_id integer
---@param data integer ---@param data integer
function ext.set_oda_id_data(oda_id, data) function oda.set_oda_id_data(oda_id, data)
if oda_id < 1 or oda_id > #_RDS_ODAs or _RDS_ODAs[oda_id] == false then error("Invalid ODA ID: " .. tostring(oda_id), 2) end if oda_id < 1 or oda_id > #_RDS_ODAs or _RDS_ODAs[oda_id] == false then error("Invalid ODA ID: " .. tostring(oda_id), 2) end
_RDS_ODAs[oda_id].data = data _RDS_ODAs[oda_id].data = data
end end
---@alias ODAHandler fun(): (boolean, integer, integer, integer)
---Sets a function to handle the ODA data generation. ---Sets a function to handle the ODA data generation.
---The handler is called when the group sequence '\xff' slot is processed. ---The handler is called when the group sequence '\xff' slot is processed.
---The function must return 3 integers representing RDS Blocks B, C, and D. ---The function must return 3 integers representing RDS Blocks B, C, and D.
@@ -58,7 +62,7 @@ end
---You are asked to set groups B last 5 bits, leave rest 0 ---You are asked to set groups B last 5 bits, leave rest 0
---@param oda_id integer The ID returned by register_oda ---@param oda_id integer The ID returned by register_oda
---@param fun ODAHandler ---@param fun ODAHandler
function ext.set_oda_handler(oda_id, fun) function oda.set_oda_handler(oda_id, fun)
if oda_id < 1 or oda_id > #_RDS_ODAs or _RDS_ODAs[oda_id] == false then error("Invalid ODA ID: " .. tostring(oda_id), 2) end if oda_id < 1 or oda_id > #_RDS_ODAs or _RDS_ODAs[oda_id] == false then error("Invalid ODA ID: " .. tostring(oda_id), 2) end
if _RDS_ODAs[oda_id].group == 3 then error("3A ODAs cannot have handlers.", 2) end if _RDS_ODAs[oda_id].group == 3 then error("3A ODAs cannot have handlers.", 2) end
_RDS_ODAs[oda_id].handler = fun _RDS_ODAs[oda_id].handler = fun
@@ -124,4 +128,6 @@ require("group").register_group("\x06\xff", group_handler)
table.insert(hooks.on_state, function () table.insert(hooks.on_state, function ()
_RDS_ODAs = {} _RDS_ODAs = {}
_RDS_ODA_pointer = 1 _RDS_ODA_pointer = 1
end) end)
return oda
+27 -21
View File
@@ -1,15 +1,19 @@
_Rtp_oda_id = nil local oda = require("oda")
_Ertp_oda_id = nil ---@class RtpModule
_Rtp_toggle = false local rtp = {}
_Ertp_toggle = false
local _Rtp_oda_id = nil
local _Ertp_oda_id = nil
local _Rtp_toggle = false
local _Ertp_toggle = false
--- Size: 15 bytes --- Size: 15 bytes
local USERDATA_RTP_OFFSET = 259 local USERDATA_RTP_OFFSET = 259
local function init_rtp() local function init_rtp()
if _Rtp_oda_id == nil then if _Rtp_oda_id == nil then
_Rtp_oda_id = ext.register_oda(11, false, 0x4BD7, 0, false) _Rtp_oda_id = oda.register_oda(11, false, 0x4BD7, 0, false)
ext.set_oda_handler(_Rtp_oda_id, function () oda.set_oda_handler(_Rtp_oda_id, function ()
local b = (_Rtp_toggle and 1 or 0) << 4 | string.byte(userdata.get_offset(USERDATA_RTP_OFFSET, 1)) << 3 local b = (_Rtp_toggle and 1 or 0) << 4 | string.byte(userdata.get_offset(USERDATA_RTP_OFFSET, 1)) << 3
local data_0 = userdata.get_offset(USERDATA_RTP_OFFSET+1, 3) local data_0 = userdata.get_offset(USERDATA_RTP_OFFSET+1, 3)
local data_1 = userdata.get_offset(USERDATA_RTP_OFFSET+4, 3) local data_1 = userdata.get_offset(USERDATA_RTP_OFFSET+4, 3)
@@ -31,8 +35,8 @@ end
local function init_ertp() local function init_ertp()
if _Ertp_oda_id == nil then if _Ertp_oda_id == nil then
_Ertp_oda_id = ext.register_oda(12, false, 0x4BD8, 0, false) _Ertp_oda_id = oda.register_oda(12, false, 0x4BD8, 0, false)
ext.set_oda_handler(_Ertp_oda_id, function () oda.set_oda_handler(_Ertp_oda_id, function ()
local b = (_Ertp_toggle and 1 or 0) << 4 | string.byte(userdata.get_offset(USERDATA_RTP_OFFSET+7, 1)) << 3 local b = (_Ertp_toggle and 1 or 0) << 4 | string.byte(userdata.get_offset(USERDATA_RTP_OFFSET+7, 1)) << 3
local data_0 = userdata.get_offset(USERDATA_RTP_OFFSET+8, 3) local data_0 = userdata.get_offset(USERDATA_RTP_OFFSET+8, 3)
local data_1 = userdata.get_offset(USERDATA_RTP_OFFSET+11, 3) local data_1 = userdata.get_offset(USERDATA_RTP_OFFSET+11, 3)
@@ -52,7 +56,7 @@ local function init_ertp()
end end
end end
function RDS.ext.set_rtp_meta(ertp, running) function rtp.set_rtp_meta(ertp, running)
if ertp then if ertp then
if running and _Ertp_oda_id == nil then init_ertp() end if running and _Ertp_oda_id == nil then init_ertp() end
userdata.set_offset(USERDATA_RTP_OFFSET+7, 1, string.char(running and 1 or 0)) userdata.set_offset(USERDATA_RTP_OFFSET+7, 1, string.char(running and 1 or 0))
@@ -61,35 +65,37 @@ function RDS.ext.set_rtp_meta(ertp, running)
userdata.set_offset(USERDATA_RTP_OFFSET, 1, string.char(running and 1 or 0)) userdata.set_offset(USERDATA_RTP_OFFSET, 1, string.char(running and 1 or 0))
end end
end end
function RDS.ext.get_rtp_meta(ertp) function rtp.get_rtp_meta(ertp)
local offset = ertp and (USERDATA_RTP_OFFSET+7) or USERDATA_RTP_OFFSET local offset = ertp and (USERDATA_RTP_OFFSET+7) or USERDATA_RTP_OFFSET
return string.byte(userdata.get_offset(offset, 1)) ~= 0 return string.byte(userdata.get_offset(offset, 1)) ~= 0
end end
function RDS.ext.toggle_rtp(ertp) function rtp.toggle_rtp(ertp)
if ertp then _Ertp_toggle = not _Ertp_toggle if ertp then _Ertp_toggle = not _Ertp_toggle
else _Rtp_toggle = not _Rtp_toggle end else _Rtp_toggle = not _Rtp_toggle end
end end
function RDS.ext.set_rtplus_tags(ertp, t1, s1, l1, t2, s2, l2) function rtp.set_rtplus_tags(ertp, t1, s1, l1, t2, s2, l2)
RDS.ext.set_rtp_meta(ertp, true) rtp.set_rtp_meta(ertp, true)
RDS.ext.toggle_rtp(ertp) rtp.toggle_rtp(ertp)
userdata.set_offset(ertp and (USERDATA_RTP_OFFSET+8) or (USERDATA_RTP_OFFSET+1), 6, string.char(t1, s1, l1, t2, s2, l2)) userdata.set_offset(ertp and (USERDATA_RTP_OFFSET+8) or (USERDATA_RTP_OFFSET+1), 6, string.char(t1, s1, l1, t2, s2, l2))
end end
function RDS.ext.get_rtplus_tags(ertp) function rtp.get_rtplus_tags(ertp)
return string.byte(userdata.get_offset(ertp and (USERDATA_RTP_OFFSET+8) or (USERDATA_RTP_OFFSET+1), 6), 1, 6) return string.byte(userdata.get_offset(ertp and (USERDATA_RTP_OFFSET+8) or (USERDATA_RTP_OFFSET+1), 6), 1, 6)
end end
function unregister_rtp(ertp) function rtp.unregister_rtp(ertp)
if ertp and _Ertp_oda_id ~= nil then if ertp and _Ertp_oda_id ~= nil then
ext.unregister_oda(_Ertp_oda_id) oda.unregister_oda(_Ertp_oda_id)
_Ertp_oda_id = nil _Ertp_oda_id = nil
elseif _Rtp_oda_id ~= nil then elseif _Rtp_oda_id ~= nil then
ext.unregister_oda(_Rtp_oda_id) oda.unregister_oda(_Rtp_oda_id)
_Rtp_oda_id = nil _Rtp_oda_id = nil
end end
end end
table.insert(hooks.on_state, function () table.insert(hooks.on_state, function ()
if RDS.ext.get_rtp_meta(false) then init_rtp() end if rtp.get_rtp_meta(false) then init_rtp() end
if RDS.ext.get_rtp_meta(true) then init_ertp() end if rtp.get_rtp_meta(true) then init_ertp() end
end) end)
return rtp
+53 -12
View File
@@ -1,13 +1,15 @@
uecp = {} ---@class UecpModule
local uecp = {}
uecp.site_addreses = {} uecp.site_addreses = {}
uecp.encoder_addreses = {} uecp.encoder_addreses = {}
uecp.rt_buffer = {} uecp.rt_buffer = {}
uecp.rt_buffer_index = 1 uecp.rt_buffer_index = 1
uecp.rt_tx_remaining = 0 uecp.rt_tx_remaining = 0
uecp.freedata_buffer = {}
uecp.freedata_buffer_index = 1
hooks.rt_transmission[#hooks.rt_transmission + 1] = function () hooks.rt_transmission[#hooks.rt_transmission + 1] = function ()
if #uecp.rt_buffer == 0 then return end if #uecp.rt_buffer == 0 then return end
if #uecp.rt_buffer == 1 then return end if #uecp.rt_buffer == 1 then return end
if uecp.rt_tx_remaining > 1 then if uecp.rt_tx_remaining > 1 then
@@ -23,7 +25,8 @@ hooks.rt_transmission[#hooks.rt_transmission + 1] = function ()
if entry.toggle_ab then RDS.toggle_rt_ab() end if entry.toggle_ab then RDS.toggle_rt_ab() end
end end
---@param dsn integer
---@param write function
local function dsn_helper(dsn, write) local function dsn_helper(dsn, write)
if dsn == 0 then write() if dsn == 0 then write()
elseif dsn == 254 then elseif dsn == 254 then
@@ -47,7 +50,7 @@ local function dsn_helper(dsn, write)
end end
local mec_handlers = {} local mec_handlers = {}
mec_handlers[1] = function(data) mec_handlers[0x01] = function(data)
-- PI -- PI
local dsn = string.byte(data, 2) local dsn = string.byte(data, 2)
local psn = string.byte(data, 3) local psn = string.byte(data, 3)
@@ -59,7 +62,7 @@ mec_handlers[1] = function(data)
end) end)
return 5 return 5
end end
mec_handlers[2] = function(data) mec_handlers[0x02] = function(data)
-- PS -- PS
local dsn = string.byte(data, 2) local dsn = string.byte(data, 2)
local psn = string.byte(data, 3) local psn = string.byte(data, 3)
@@ -84,7 +87,7 @@ mec_handlers[0x21] = function(data)
end) end)
return 4 + mel return 4 + mel
end end
mec_handlers[4] = function(data) mec_handlers[0x04] = function(data)
-- PTYI -- PTYI
local dsn = string.byte(data, 2) local dsn = string.byte(data, 2)
local psn = string.byte(data, 3) local psn = string.byte(data, 3)
@@ -94,7 +97,7 @@ mec_handlers[4] = function(data)
end) end)
return 4 return 4
end end
mec_handlers[3] = function(data) mec_handlers[0x03] = function(data)
-- TP/TA -- TP/TA
local dsn = string.byte(data, 2) local dsn = string.byte(data, 2)
local psn = string.byte(data, 3) local psn = string.byte(data, 3)
@@ -105,7 +108,7 @@ mec_handlers[3] = function(data)
end) end)
return 4 return 4
end end
mec_handlers[7] = function(data) mec_handlers[0x07] = function(data)
-- PTY -- PTY
local dsn = string.byte(data, 2) local dsn = string.byte(data, 2)
local psn = string.byte(data, 3) local psn = string.byte(data, 3)
@@ -218,21 +221,57 @@ end
mec_handlers[0x2E] = function (data) mec_handlers[0x2E] = function (data)
-- Linkage Information -- Linkage Information
-- What? What is the main PSN? Does the server tell me that or am i to fucking guess? -- What? What is the main PSN? Does the server tell me that or am i to fucking guess?
-- (yes it does tell me that :) mec 0x28)
return 5 return 5
end end
hooks.pre_tx[#hooks.pre_tx+1] = function()
if #uecp.freedata_buffer == 0 then return end
local entry = uecp.freedata_buffer[uecp.freedata_buffer_index]
uecp.freedata_buffer_index = uecp.freedata_buffer_index + 1
if uecp.freedata_buffer_index > #uecp.freedata_buffer then
uecp.freedata_buffer_index = 1
end
RDS.put_custom_group(entry.b, entry.c, entry.d)
end
mec_handlers[0x24] = function (data) mec_handlers[0x24] = function (data)
-- Free-format data in type A or B group -- Free-format data in type A or B group
local group = string.byte(data, 2) local group = string.byte(data, 2)
local bufferdata = string.byte(data, 3) local bufferdata = string.byte(data, 3)
local buffer = (bufferdata >> 5) & 3 -- No clue local buffer = (bufferdata >> 5) & 3
local blockb = bufferdata & 31 local blockb = bufferdata & 31
local blockc_msb = string.byte(data, 4) local blockc_msb = string.byte(data, 4)
local blockc_lsb = string.byte(data, 5) local blockc_lsb = string.byte(data, 5)
local blockd_msb = string.byte(data, 6) local blockd_msb = string.byte(data, 6)
local blockd_lsb = string.byte(data, 7) local blockd_lsb = string.byte(data, 7)
RDS.put_custom_group((group << 11) | blockb, (blockc_msb << 8) | blockc_lsb, (blockd_msb << 8) | blockd_lsb) local b, c, d = (group << 11) | blockb, (blockc_msb << 8) | blockc_lsb, (blockd_msb << 8) | blockd_lsb
if buffer == 0 then
RDS.put_custom_group(b, c, d)
elseif buffer == 2 then
uecp.freedata_buffer[#uecp.freedata_buffer + 1] = { b = b, c = c, d = d }
elseif buffer == 3 then
uecp.freedata_buffer = {}
uecp.freedata_buffer_index = 1
end
return 7 return 7
end end
mec_handlers[0x40] = function(data)
local group = string.byte(data, 2)
local aid_msb = string.byte(data, 3)
local aid_lsb = string.byte(data, 4)
local buffer = string.byte(data, 5) & 3
local msg_msb = string.byte(data, 6) -- 6
local msg_lsb = string.byte(data, 7) -- 7
local timeout = string.byte(data, 8)
-- TODO:
return 8
end
-- Fuck you mean, i have to implement some fucking "spinning wHELLs"? may the lord have mercy -- Fuck you mean, i have to implement some fucking "spinning wHELLs"? may the lord have mercy
-- Also no time setting via UECP - this is linux, just install fucking chrony -- Also no time setting via UECP - this is linux, just install fucking chrony
mec_handlers[0x19] = function (data) mec_handlers[0x19] = function (data)
@@ -385,7 +424,9 @@ function uecp.parse_uecp(packet)
break break
end end
local advance = handler(string.sub(data, consumed)) local advance = handler(string.sub(data, consumed))
consumed = consumed + advance
Data.set_writing_program(Data.get_output_program()) Data.set_writing_program(Data.get_output_program())
consumed = consumed + advance
end end
end end
return uecp
-6
View File
@@ -33,12 +33,6 @@ Yet another unique feature, the Lua engine allows you to control the encoder, al
Every RDS2 subcarrier is in full control of Lua (except tunneling - core fills in PTY and the rest) Every RDS2 subcarrier is in full control of Lua (except tunneling - core fills in PTY and the rest)
### RFT
For a long time there was no RFT in RDS95, but on 28th December 2025 that changed when i implemented it in lua, just call the load function with the path and id 0, and see the your station's logo in the decoder
And yes, it is implemented in only Lua and the ODA engine which is also in Lua
## Disclaimer ## Disclaimer
RDS95 is "based" on [Anthony96922](https://github.com/Anthony96922/)'s [MiniRDS](https://github.com/Anthony96922/MiniRDS) licensed under GPLv3 RDS95 is "based" on [Anthony96922](https://github.com/Anthony96922/)'s [MiniRDS](https://github.com/Anthony96922/MiniRDS) licensed under GPLv3
+17 -12
View File
@@ -1,3 +1,8 @@
local af = require("af")
local ert = require("ert")
local rtp = require("rtp")
local uecp = require("uecp")
---@param data string ---@param data string
function hooks.parse_ascii(data) function hooks.parse_ascii(data)
if string.sub(data, 1, 4):lower() == "lua=" then if string.sub(data, 1, 4):lower() == "lua=" then
@@ -59,21 +64,21 @@ function hooks.parse_ascii(data)
elseif data == "rdsgen" then return string.format("RDSGEN=%s\r\n", string.format("%d", RDS.get_streams())) elseif data == "rdsgen" then return string.format("RDSGEN=%s\r\n", string.format("%d", RDS.get_streams()))
elseif data == "link" then return string.format("LINK=%s\r\n", string.format("%d", (RDS.get_link() and 1 or 0))) elseif data == "link" then return string.format("LINK=%s\r\n", string.format("%d", (RDS.get_link() and 1 or 0)))
elseif data == "rtp" then elseif data == "rtp" then
local t1, s1, l1, t2, s2, l2 = RDS.ext.get_rtplus_tags(false) local t1, s1, l1, t2, s2, l2 = rtp.get_rtplus_tags(false)
return string.format("RTP=%d,%d,%d,%d,%d,%d\r\n", t1, s1, l1, t2, s2, l2) return string.format("RTP=%d,%d,%d,%d,%d,%d\r\n", t1, s1, l1, t2, s2, l2)
elseif data == "ertp" then elseif data == "ertp" then
local t1, s1, l1, t2, s2, l2 = RDS.ext.get_rtplus_tags(true) local t1, s1, l1, t2, s2, l2 = rtp.get_rtplus_tags(true)
return string.format("ERTP=%d,%d,%d,%d,%d,%d\r\n", t1, s1, l1, t2, s2, l2) return string.format("ERTP=%d,%d,%d,%d,%d,%d\r\n", t1, s1, l1, t2, s2, l2)
elseif data == "rtprun" then elseif data == "rtprun" then
local running = RDS.ext.get_rtp_meta(false) local running = rtp.get_rtp_meta(false)
local f1 = 2 or (running and 1 or 0) local f1 = 2 or (running and 1 or 0)
return string.format("RTPRUN=%d\r\n", f1) return string.format("RTPRUN=%d\r\n", f1)
elseif data == "ertprun" then elseif data == "ertprun" then
local running = RDS.ext.get_rtp_meta(true) local running = rtp.get_rtp_meta(true)
local f1 = 2 or (running and 1 or 0) local f1 = 2 or (running and 1 or 0)
return string.format("ERTPRUN=%d\r\n", f1) return string.format("ERTPRUN=%d\r\n", f1)
elseif data == "lps" then return string.format("LPS=%s\r\n", RDS.get_lps()) elseif data == "lps" then return string.format("LPS=%s\r\n", RDS.get_lps())
elseif data == "ert" then return string.format("ERT=%s\r\n", RDS.ext.get_ert()) elseif data == "ert" then return string.format("ERT=%s\r\n", ert.get_ert())
else else
local eon_cmd, eon_num = data:match("^eon(%d+)([a-z]+)$") local eon_cmd, eon_num = data:match("^eon(%d+)([a-z]+)$")
if eon_cmd then if eon_cmd then
@@ -232,7 +237,7 @@ function hooks.parse_ascii(data)
RDS.set_lps(value) RDS.set_lps(value)
return "+\r\n" return "+\r\n"
elseif cmd == "ert" then elseif cmd == "ert" then
RDS.ext.set_ert(value) ert.set_ert(value)
return "+\r\n" return "+\r\n"
elseif cmd == "link" then elseif cmd == "link" then
local link = tonumber(value) local link = tonumber(value)
@@ -252,7 +257,7 @@ function hooks.parse_ascii(data)
local t1, s1, l1, t2, s2, l2 = value:match("(%d+),(%d+),(%d+),(%d+),(%d+),(%d+)") local t1, s1, l1, t2, s2, l2 = value:match("(%d+),(%d+),(%d+),(%d+),(%d+),(%d+)")
if not l2 then return "-\r\n" end if not l2 then return "-\r\n" end
RDS.ext.set_rtplus_tags( rtp.set_rtplus_tags(
is_ertp, is_ertp,
---@diagnostic disable-next-line: param-type-mismatch ---@diagnostic disable-next-line: param-type-mismatch
tonumber(t1), tonumber(s1), tonumber(l1), tonumber(t2), tonumber(s2), tonumber(l2) tonumber(t1), tonumber(s1), tonumber(l1), tonumber(t2), tonumber(s2), tonumber(l2)
@@ -282,8 +287,8 @@ function hooks.parse_ascii(data)
local f2 = tonumber(f2_str) or 0 local f2 = tonumber(f2_str) or 0
local running = (f1 & 1) ~= 0 local running = (f1 & 1) ~= 0
RDS.ext.set_rtp_meta(is_ertp, running) rtp.set_rtp_meta(is_ertp, running)
if f2 ~= 0 then RDS.ext.toggle_rtp(is_ertp) end if f2 ~= 0 then rtp.toggle_rtp(is_ertp) end
return "+\r\n" return "+\r\n"
elseif cmd == "af" then elseif cmd == "af" then
local af_table = {} local af_table = {}
@@ -307,7 +312,7 @@ function hooks.parse_ascii(data)
local af_table = {} local af_table = {}
if value == "" or value == "0" then if value == "" or value == "0" then
RDS.ext.set_af_oda({}) af.set_af_oda({})
return "+\r\n" return "+\r\n"
end end
@@ -319,7 +324,7 @@ function hooks.parse_ascii(data)
if #af_table > 25 then return "-\r\n" end if #af_table > 25 then return "-\r\n" end
RDS.ext.set_af_oda(af_table) af.set_af_oda(af_table)
return "+\r\n" return "+\r\n"
else return "?\r\n" end else return "?\r\n" end
end end
@@ -328,4 +333,4 @@ end
function hooks.data_handle(data) function hooks.data_handle(data)
if uecp.parse_uecp and string.byte(data, 1) == 0xfe then return uecp.parse_uecp(data) if uecp.parse_uecp and string.byte(data, 1) == 0xfe then return uecp.parse_uecp(data)
else return hooks.parse_ascii(data) end else return hooks.parse_ascii(data) end
end end
+69
View File
@@ -0,0 +1,69 @@
#include "ipc_client.h"
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
int ipc_connect(IPC_Client *client, const char *socket_path) {
client->fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (client->fd < 0) { perror("fm95 ipc: socket"); return -1; }
struct sockaddr_un addr = { .sun_family = AF_UNIX };
strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1);
if (connect(client->fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("fm95 ipc: connect");
close(client->fd);
client->fd = -1;
return -1;
}
memset(client->prev_output, 0, sizeof(client->prev_output));
return 0;
}
void ipc_close(IPC_Client *client) {
if (client->fd >= 0) close(client->fd);
client->fd = -1;
}
// Pulls one group's worth of bits (BITS_PER_GROUP), differentially encodes,
// packs 8-to-a-byte, sends as opcode 112 with the stream index.
int ipc_send_bits(IPC_Client *client, RDSEncoder *enc, uint8_t stream) {
if (client->fd < 0) return -1;
uint8_t raw_bits[BITS_PER_GROUP];
get_rds_bits(enc, raw_bits, stream);
uint8_t packed[(BITS_PER_GROUP + 7) / 8];
memset(packed, 0, sizeof(packed));
uint8_t prev = client->prev_output[stream];
for (int i = 0; i < BITS_PER_GROUP; i++) {
uint8_t cur = prev ^ raw_bits[i]; // the differential step, moved here
prev = cur;
if (cur) packed[i / 8] |= (0x80 >> (i % 8));
}
client->prev_output[stream] = prev;
uint8_t msg[2 + sizeof(packed)];
msg[0] = 112;
msg[1] = stream;
memcpy(msg + 2, packed, sizeof(packed));
ssize_t sent = send(client->fd, msg, sizeof(msg), 0);
if (sent != (ssize_t)sizeof(msg)) {
fprintf(stderr, "fm95 ipc: short/failed send\n");
return -1;
}
uint8_t reply;
if (recv(client->fd, &reply, 1, 0) <= 0) {
fprintf(stderr, "fm95 ipc: no reply / disconnected\n");
return -1;
}
if (reply == 2) fprintf(stderr, "fm95 reported bitring overrun on stream %d\n", stream);
else if (reply == 1) fprintf(stderr, "fm95 rejected message (unknown opcode?)\n");
return 0;
}
+11
View File
@@ -0,0 +1,11 @@
#include <stdint.h>
#include "rds.h" // for RDSEncoder, get_rds_bits, BITS_PER_GROUP
typedef struct {
int fd;
uint8_t prev_output[4]; // per-stream differential state
} IPC_Client;
int ipc_connect(IPC_Client *client, const char *socket_path);
void ipc_close(IPC_Client *client);
int ipc_send_bits(IPC_Client *client, RDSEncoder *enc, uint8_t stream);
+2 -6
View File
@@ -118,18 +118,14 @@ uint8_t init_lua(RDSEncoder* _enc) {
lua_newtable(globalL); lua_newtable(globalL);
lua_setfield(globalL, -2, "ps_transmission"); lua_setfield(globalL, -2, "ps_transmission");
lua_newtable(globalL); lua_newtable(globalL);
lua_setfield(globalL, -2, "pre_tx");
lua_newtable(globalL);
lua_setfield(globalL, -2, "group"); lua_setfield(globalL, -2, "group");
lua_pushvalue(globalL, -1); lua_pushvalue(globalL, -1);
hooks_ref = luaL_ref(globalL, LUA_REGISTRYINDEX); hooks_ref = luaL_ref(globalL, LUA_REGISTRYINDEX);
lua_setglobal(globalL, "hooks"); lua_setglobal(globalL, "hooks");
lua_newtable(globalL); lua_newtable(globalL);
lua_setglobal(globalL, "ext");
lua_newtable(globalL);
lua_newtable(globalL);
lua_setfield(globalL, -2, "ext");
lua_pushinteger(globalL, EONs); lua_pushinteger(globalL, EONs);
lua_setfield(globalL, -2, "eon_count"); lua_setfield(globalL, -2, "eon_count");
-51
View File
@@ -1,51 +0,0 @@
#include "modulator.h"
#include "fs.h"
void init_rds_modulator(RDSModulator* rdsMod, RDSEncoder* enc, uint8_t num_streams) {
memset(rdsMod, 0, sizeof(RDSModulator));
rdsMod->num_streams = num_streams;
memset(enc, 0, sizeof(RDSEncoder));
rdsMod->enc = enc;
rdsMod->data = (RDSModulatorModulationData*)calloc(num_streams, sizeof(RDSModulatorModulationData));
if (rdsMod->data == NULL) {
fprintf(stderr, "Error: Could not allocate memory for RDS modulation data\n");
return;
}
for (uint8_t i = 0; i < num_streams; i++) {
rdsMod->data[i].symbol_shift = i * M_PI / 2.0f;
rdsMod->data[i].bit_pos = BITS_PER_GROUP;
}
}
void cleanup_rds_modulator(RDSModulator* rdsMod) {
if (rdsMod->data) {
free(rdsMod->data);
rdsMod->data = NULL;
}
}
float get_rds_sample(RDSModulator* rdsMod, uint8_t stream) {
if(stream >= rdsMod->num_streams) return 0.0f;
rdsMod->data[stream].phase += 1187.5 / RDS_SAMPLE_RATE;
if (rdsMod->data[stream].phase >= 1.0f) {
rdsMod->data[stream].phase -= 1.0f;
if (rdsMod->data[stream].bit_pos == BITS_PER_GROUP) {
get_rds_bits(rdsMod->enc, rdsMod->data[stream].bit_buffer, stream);
rdsMod->data[stream].bit_pos = 0;
}
rdsMod->data[stream].cur_bit = rdsMod->data[stream].bit_buffer[rdsMod->data[stream].bit_pos++];
rdsMod->data[stream].prev_output = rdsMod->data[stream].cur_output;
rdsMod->data[stream].cur_output = rdsMod->data[stream].prev_output ^ rdsMod->data[stream].cur_bit; // Something with differential encoding? Instead of encoding the data, encode if the bit of data changes
}
float sample = sinf(M_2PI * rdsMod->data[stream].phase + rdsMod->data[stream].symbol_shift);
if(rdsMod->data[stream].cur_output == 0) sample = -sample; // do bpsk, if you comment this part out, nothing will be decoded
return sample;
}
-25
View File
@@ -1,25 +0,0 @@
#pragma once
#include "common.h"
#include "lib.h"
#include "rds.h"
typedef struct
{
uint8_t bit_buffer[BITS_PER_GROUP];
uint8_t bit_pos : 7;
uint8_t prev_output : 1;
uint8_t cur_output : 1;
uint8_t cur_bit : 1;
float symbol_shift;
float phase;
} RDSModulatorModulationData;
typedef struct {
RDSModulatorModulationData *data;
RDSEncoder* enc;
uint8_t num_streams;
} RDSModulator;
void init_rds_modulator(RDSModulator* rdsMod, RDSEncoder* enc, uint8_t num_streams);
void cleanup_rds_modulator(RDSModulator* rdsMod);
float get_rds_sample(RDSModulator* rdsMod, uint8_t stream);
+3 -2
View File
@@ -1,6 +1,5 @@
#include "rds.h" #include "rds.h"
#include "fs.h" #include "fs.h"
#include "modulator.h"
#include "lib.h" #include "lib.h"
#include "lua_rds.h" #include "lua_rds.h"
#include <time.h> #include <time.h>
@@ -97,7 +96,7 @@ void get_rds_group(RDSEncoder* enc, RDSGroup *group, uint8_t stream) {
time(&now); time(&now);
utc = gmtime(&now); utc = gmtime(&now);
if(utc->tm_sec != enc->state[enc->program].last_second && stream == 0) { if(utc->tm_sec != enc->state[enc->program].last_second) {
enc->state[enc->program].last_second = utc->tm_sec; enc->state[enc->program].last_second = utc->tm_sec;
lua_call_tfunction("tick"); lua_call_tfunction("tick");
} }
@@ -112,6 +111,8 @@ void get_rds_group(RDSEncoder* enc, RDSGroup *group, uint8_t stream) {
} }
} }
lua_call_tfunction("pre_tx");
uint8_t good_group = 0; uint8_t good_group = 0;
uint8_t grp_sqc_idx = 0; uint8_t grp_sqc_idx = 0;
char grp; char grp;
+10 -24
View File
@@ -2,13 +2,11 @@
#include <signal.h> #include <signal.h>
#include <getopt.h> #include <getopt.h>
#include <pthread.h> #include <pthread.h>
#include <pulse/simple.h>
#include <pulse/error.h>
#include "../inih/ini.h" #include "../inih/ini.h"
#include "rds.h" #include "rds.h"
#include "ipc_client.h"
#include "fs.h" #include "fs.h"
#include "modulator.h"
#include "udp_server.h" #include "udp_server.h"
#include "tcp_server.h" #include "tcp_server.h"
#include "lib.h" #include "lib.h"
@@ -49,8 +47,7 @@ static inline void show_help(char *name) {
); );
} }
typedef struct typedef struct {
{
uint16_t udp_port; uint16_t udp_port;
uint16_t tcp_port; uint16_t tcp_port;
char rds_device_name[48]; char rds_device_name[48];
@@ -201,28 +198,17 @@ int main(int argc, char **argv) {
} }
if(config.asciig == 0) { if(config.asciig == 0) {
int pulse_error; IPC_Client client;
if (ipc_connect(&client, "/etc/fm95/ctl.socket") < 0) goto exit;
float *rds_buffer = (float*)malloc(NUM_MPX_FRAMES * config.num_streams * sizeof(float)); while (!stop_rds) {
if (rds_buffer == NULL) { for (uint8_t s = 0; s < config.num_streams; s++) {
fprintf(stderr, "Error: Could not allocate memory for RDS buffer\n"); ipc_send_bits(&client, &rdsEncoder, s);
goto exit; }
msleep((int)(1000.0 * BITS_PER_GROUP / 1187.5));
} }
while(!stop_rds) { ipc_close(&client);
for (uint16_t i = 0; i < NUM_MPX_FRAMES * config.num_streams; i++) {
uint8_t stream = i % config.num_streams;
if((rdsEncoder.enabled_streams > stream ? 1 : 0) == 0) rds_buffer[i] = 0.0f;
rds_buffer[i] = get_rds_sample(&rdsModulator, stream);
}
if (pa_simple_write(rds_device, rds_buffer, NUM_MPX_FRAMES * config.num_streams * sizeof(float), &pulse_error) != 0) {
fprintf(stderr, "Error: could not play audio. (%s : %d)\n", pa_strerror(pulse_error), pulse_error);
break;
}
}
free(rds_buffer);
} else { } else {
RDSGroup group; RDSGroup group;
char output_buffer[1024]; char output_buffer[1024];