Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
a45aded
Introduce raw audio setting in UI. Opus encoding in client now depend…
dingodoppelt Apr 17, 2026
73d983f
addition to last commit
dingodoppelt Apr 17, 2026
03f7ab3
client still sends raw audio to stock servers and causes garbled soun…
dingodoppelt Apr 17, 2026
9a05dd1
make client opus decoding depend on audio quality (raw audio)
dingodoppelt Apr 17, 2026
0f22ee9
no crashes but bad noise on raw audio setting
dingodoppelt Apr 17, 2026
7f47dd8
first working version (needs testing)
dingodoppelt Apr 17, 2026
7734d75
fix style
dingodoppelt Apr 17, 2026
fa61b55
fix style
dingodoppelt Apr 17, 2026
68deea9
Merge branch 'jamulussoftware:main' into rawaudio-dev
dingodoppelt Apr 17, 2026
5a467e0
fix mute myself
dingodoppelt Apr 17, 2026
8f53817
remove redundant include
dingodoppelt Apr 19, 2026
c77029d
Change audio quality option from 'Raw' to 'Max'
dingodoppelt Apr 20, 2026
d36448a
Only send raw audio if the server supports it (checked by version num…
dingodoppelt Apr 20, 2026
8079eed
fix style
dingodoppelt Apr 20, 2026
5c73b6a
fix style
dingodoppelt Apr 20, 2026
a597843
change version
dingodoppelt Apr 20, 2026
7fa20b7
Update changelog
dingodoppelt Apr 20, 2026
b602efa
fix changelog
dingodoppelt Apr 20, 2026
42af9ab
Fix noise issue with rawaudio servers when Max audio quality is selected
dingodoppelt Apr 20, 2026
1cb8a22
Merge branch 'rawaudio-dev' into autobuild-rawaudio
dingodoppelt Apr 20, 2026
c3b47fd
fix style (I'll never learn)
dingodoppelt Apr 20, 2026
1544eb7
fix last commit
dingodoppelt Apr 20, 2026
6fd09bb
Merge branch 'rawaudio-dev' into autobuild-rawaudio
dingodoppelt Apr 20, 2026
23399c5
Disable bRawAudioIsSupported when leaving a server
dingodoppelt Apr 21, 2026
5b8a709
limit rawaudio to server version 3.11.1 only
dingodoppelt Apr 21, 2026
2aad9b5
opus fallback on disconnect
dingodoppelt Apr 21, 2026
754d7b0
disable raw setting when unconnected to join a new server with forced…
dingodoppelt Apr 21, 2026
14d83ed
stop sound before calling client init
dingodoppelt Apr 21, 2026
d4da62c
Merge branch 'jamulussoftware:main' into rawaudio-dev
dingodoppelt Apr 22, 2026
d630234
make raw audio quality setting persistent in config file
dingodoppelt Apr 22, 2026
32fca21
remove redundant setting of bRawAudioIsSupported
dingodoppelt Apr 22, 2026
bbe3329
Update changelog
dingodoppelt Apr 23, 2026
6fa5089
Account for iSndCrdFrameSizeFactor when calculating packet sizes
dingodoppelt Apr 23, 2026
28cb295
here we go again, fix style
dingodoppelt Apr 23, 2026
1d402d9
Fix compiler warnings
dingodoppelt Apr 23, 2026
d434d64
fix packet size calculation for iSndCrdFrameSizeFactor != 1, makes al…
dingodoppelt Apr 24, 2026
d977e0f
Append hash to build version
dingodoppelt Apr 24, 2026
8b5ad53
Hopefully shorten version enough to be able to register with central …
dingodoppelt Apr 24, 2026
8773fb2
Fix changelog
dingodoppelt Apr 24, 2026
a954933
Revert checking for packet sizes greater or equal than iCeltNumCodedB…
dingodoppelt Apr 24, 2026
d55f50b
Improve readybility/consistency
dingodoppelt Apr 26, 2026
7a64330
Remove redundant code
dingodoppelt Apr 26, 2026
763c95d
Refactor conditions, remove redundancy
dingodoppelt Apr 26, 2026
e6888af
Refactor condition
dingodoppelt Apr 26, 2026
ac78b16
Refactor for consistency in client and server
dingodoppelt Apr 26, 2026
7c11c63
Refactor conditions
dingodoppelt Apr 26, 2026
c3c0b16
Fix style
dingodoppelt Apr 26, 2026
5aeb676
Don't call opus_custom_encoder_ctl when sending raw audio
dingodoppelt Apr 26, 2026
43d321c
Fix style
dingodoppelt Apr 26, 2026
bb33cdf
Refactor signalling for VersionAndOSReceived
dingodoppelt Apr 26, 2026
6243d59
Fix style, who would have thought...
dingodoppelt Apr 26, 2026
b394701
Edit comments. Explain packet size calculation
dingodoppelt Apr 26, 2026
e5d822a
Fix latency calculation for raw audio
dingodoppelt Apr 26, 2026
c8a4adc
Really fix latency calculation
dingodoppelt Apr 26, 2026
8691f1f
Initialise bRawAudioIsSupported in correct order
dingodoppelt Apr 26, 2026
6e233ac
fix compiler warnings
dingodoppelt Apr 26, 2026
3f059b3
Fix style (tough one...)
dingodoppelt Apr 26, 2026
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
4 changes: 4 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
### 3.11.1dev ###

- Add uncompressed audio transmission - dedicated to the memory of Hans Petter Selasky (1982 - 2023)
Comment thread
dingodoppelt marked this conversation as resolved.

### 3.11.0dev <- NOTE: the release version number will be 3.12.0 ###

- Extended SRV record support (#3556).
Expand Down
2 changes: 1 addition & 1 deletion Jamulus.pro
Comment thread
dingodoppelt marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
VERSION = 3.11.0dev
VERSION = 3.11.1dev

# Using lrelease and embed_translations only works for Qt 5.12 or later.
# See https://github.com/jamulussoftware/jamulus/pull/3288 for these changes.
Expand Down
128 changes: 113 additions & 15 deletions src/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ CClient::CClient ( const quint16 iPortNumber,
bJitterBufferOK ( true ),
bEnableIPv6 ( bNEnableIPv6 ),
bMuteMeInPersonalMix ( bNMuteMeInPersonalMix ),
iServerSockBufNumFrames ( DEF_NET_BUF_SIZE_NUM_BL )
iServerSockBufNumFrames ( DEF_NET_BUF_SIZE_NUM_BL ),
bRawAudioIsSupported ( false )
{
int iOpusError;

Expand Down Expand Up @@ -135,7 +136,7 @@ CClient::CClient ( const quint16 iPortNumber,

QObject::connect ( &Channel, &CChannel::LicenceRequired, this, &CClient::LicenceRequired );

QObject::connect ( &Channel, &CChannel::VersionAndOSReceived, this, &CClient::VersionAndOSReceived );
QObject::connect ( &Channel, &CChannel::VersionAndOSReceived, this, &CClient::OnVersionAndOSReceived );
Comment thread
dingodoppelt marked this conversation as resolved.

QObject::connect ( &Channel, &CChannel::RecorderStateReceived, this, &CClient::RecorderStateReceived );

Expand Down Expand Up @@ -395,6 +396,32 @@ void CClient::OnConClientListMesReceived ( CVector<CChannelInfo> vecChanInfo )
emit ConClientListMesReceived ( vecChanInfo );
}

void CClient::OnVersionAndOSReceived ( COSUtil::EOpSystemType eOSType, QString strVersion )
Comment thread
dingodoppelt marked this conversation as resolved.
{
Comment thread
dingodoppelt marked this conversation as resolved.
#if QT_VERSION >= QT_VERSION_CHECK( 5, 6, 0 )
const bool bWasRunning = Sound.IsRunning();
if ( bWasRunning )
{
Sound.Stop();
}
if ( QVersionNumber::compare ( QVersionNumber::fromString ( strVersion ), QVersionNumber ( 3, 11, 1 ) ) == 0 )
{
bRawAudioIsSupported = true;
Init();
}
else
{
bRawAudioIsSupported = false;
Init();
}
if ( bWasRunning )
{
Sound.Start();
}
#endif
emit VersionAndOSReceived ( eOSType, strVersion );
}

void CClient::CreateServerJitterBufferMessage()
{
// per definition in the client: if auto jitter buffer is enabled, both,
Expand Down Expand Up @@ -1017,6 +1044,10 @@ void CClient::Stop()
// disable channel
Channel.SetEnable ( false );

// Fall back to opus in case raw was used
bRawAudioIsSupported = false;
Init();

// wait for approx. 100 ms to make sure no audio packet is still in the
// network queue causing the channel to be reconnected right after having
// received the disconnect message (seems not to gain much, disconnect is
Expand Down Expand Up @@ -1156,6 +1187,16 @@ void CClient::Init()
case AQ_HIGH:
iCeltNumCodedBytes = OPUS_NUM_BYTES_MONO_HIGH_QUALITY_DBLE_FRAMESIZE;
break;
case AQ_RAW:
if ( bRawAudioIsSupported && Channel.IsEnabled() )
{
iCeltNumCodedBytes = iNumAudioChannels * iOPUSFrameSizeSamples * sizeof ( int16_t );
}
else
{
iCeltNumCodedBytes = OPUS_NUM_BYTES_MONO_HIGH_QUALITY_DBLE_FRAMESIZE;
}
break;
}
}
else
Expand All @@ -1175,6 +1216,16 @@ void CClient::Init()
case AQ_HIGH:
iCeltNumCodedBytes = OPUS_NUM_BYTES_STEREO_HIGH_QUALITY_DBLE_FRAMESIZE;
break;
case AQ_RAW:
if ( bRawAudioIsSupported && Channel.IsEnabled() )
{
iCeltNumCodedBytes = iNumAudioChannels * iOPUSFrameSizeSamples * sizeof ( int16_t );
}
else
{
iCeltNumCodedBytes = OPUS_NUM_BYTES_STEREO_HIGH_QUALITY_DBLE_FRAMESIZE;
}
break;
}
}
}
Expand All @@ -1199,6 +1250,16 @@ void CClient::Init()
case AQ_HIGH:
iCeltNumCodedBytes = OPUS_NUM_BYTES_MONO_HIGH_QUALITY;
break;
case AQ_RAW:
if ( bRawAudioIsSupported && Channel.IsEnabled() )
{
iCeltNumCodedBytes = iNumAudioChannels * iOPUSFrameSizeSamples * sizeof ( int16_t );
}
else
{
iCeltNumCodedBytes = OPUS_NUM_BYTES_MONO_HIGH_QUALITY;
}
break;
}
}
else
Expand All @@ -1218,6 +1279,16 @@ void CClient::Init()
case AQ_HIGH:
iCeltNumCodedBytes = OPUS_NUM_BYTES_STEREO_HIGH_QUALITY;
break;
case AQ_RAW:
if ( bRawAudioIsSupported && Channel.IsEnabled() )
{
iCeltNumCodedBytes = iNumAudioChannels * iOPUSFrameSizeSamples * sizeof ( int16_t );
}
else
{
iCeltNumCodedBytes = OPUS_NUM_BYTES_STEREO_HIGH_QUALITY;
}
break;
}
}
}
Expand All @@ -1229,8 +1300,11 @@ void CClient::Init()
vecZeros.Init ( iStereoBlockSizeSam, 0 );
vecsStereoSndCrdMuteStream.Init ( iStereoBlockSizeSam );

opus_custom_encoder_ctl ( CurOpusEncoder,
OPUS_SET_BITRATE ( CalcBitRateBitsPerSecFromCodedBytes ( iCeltNumCodedBytes, iOPUSFrameSizeSamples ) ) );
if ( !bRawAudioIsSupported )
{
opus_custom_encoder_ctl ( CurOpusEncoder,
OPUS_SET_BITRATE ( CalcBitRateBitsPerSecFromCodedBytes ( iCeltNumCodedBytes, iOPUSFrameSizeSamples ) ) );
}

// inits for network and channel
vecbyNetwData.Init ( iCeltNumCodedBytes );
Expand Down Expand Up @@ -1391,19 +1465,33 @@ void CClient::ProcessAudioDataIntern ( CVector<int16_t>& vecsStereoSndCrd )

for ( i = 0, j = 0; i < iSndCrdFrameSizeFactor; i++, j += iNumAudioChannels * iOPUSFrameSizeSamples )
{
// OPUS encoding
if ( CurOpusEncoder != nullptr )
if ( eAudioQuality != AQ_RAW || !bRawAudioIsSupported )
{
// OPUS encoding
if ( CurOpusEncoder != nullptr )
{
if ( bMuteOutStream )
{
iUnused = opus_custom_encode ( CurOpusEncoder, &vecZeros[j], iOPUSFrameSizeSamples, &vecCeltData[0], iCeltNumCodedBytes );
}
else
{
iUnused = opus_custom_encode ( CurOpusEncoder, &vecsStereoSndCrd[j], iOPUSFrameSizeSamples, &vecCeltData[0], iCeltNumCodedBytes );
}
}
}
else
{
if ( bMuteOutStream )
{
iUnused = opus_custom_encode ( CurOpusEncoder, &vecZeros[j], iOPUSFrameSizeSamples, &vecCeltData[0], iCeltNumCodedBytes );
memset ( &vecCeltData[0], 0, iCeltNumCodedBytes );
}
else
{
iUnused = opus_custom_encode ( CurOpusEncoder, &vecsStereoSndCrd[j], iOPUSFrameSizeSamples, &vecCeltData[0], iCeltNumCodedBytes );
// Send raw samples instead of OPUS
memcpy ( &vecCeltData[0], &vecsStereoSndCrd[j], iCeltNumCodedBytes );
}
}

// send coded audio through the network
Channel.PrepAndSendPacket ( &Socket, vecCeltData, iCeltNumCodedBytes );
Comment thread
dingodoppelt marked this conversation as resolved.
}
Expand All @@ -1423,23 +1511,33 @@ void CClient::ProcessAudioDataIntern ( CVector<int16_t>& vecsStereoSndCrd )
// get pointer to coded data and manage the flags
if ( bReceiveDataOk )
{
pCurCodedData = &vecbyNetwData[0];

if ( eAudioQuality == AQ_RAW && bRawAudioIsSupported )
{
memcpy ( &vecsStereoSndCrd[j], &vecbyNetwData[0], iCeltNumCodedBytes );
pCurCodedData = nullptr;
}
else
{
pCurCodedData = &vecbyNetwData[0];
}
// on any valid received packet, we clear the initialization phase flag
bIsInitializationPhase = false;
}
else
{
if ( eAudioQuality == AQ_RAW && bRawAudioIsSupported )
{
memset ( &vecsStereoSndCrd[j], 0, iCeltNumCodedBytes );
}
// for lost packets use null pointer as coded input data
pCurCodedData = nullptr;

// invalidate the buffer OK status flag
bJitterBufferOK = false;
}

// OPUS decoding
if ( CurOpusDecoder != nullptr )
if ( ( eAudioQuality != AQ_RAW || !bRawAudioIsSupported ) && CurOpusDecoder != nullptr )
{
// OPUS decoding
iUnused = opus_custom_decode ( CurOpusDecoder, pCurCodedData, iCeltNumCodedBytes, &vecsStereoSndCrd[j], iOPUSFrameSizeSamples );
}
}
Expand Down Expand Up @@ -1519,7 +1617,7 @@ int CClient::EstimatedOverallDelay ( const int iPingTimeMs )
const float fDelayToFillNetworkPacketsMs = GetSystemMonoBlSize() * 1000.0f / SYSTEM_SAMPLE_RATE_HZ;

// OPUS additional delay at small frame sizes is half a frame size
const float fAdditionalAudioCodecDelayMs = fSystemBlockDurationMs / 2;
const float fAdditionalAudioCodecDelayMs = ( eAudioQuality == AQ_RAW && bRawAudioIsSupported ) ? 0.0f : fSystemBlockDurationMs / 2;

const float fTotalBufferDelayMs =
fDelayToFillNetworkPacketsMs + fTotalJitterBufferDelayMs + fTotalSoundCardDelayMs + fAdditionalAudioCodecDelayMs;
Expand Down
4 changes: 3 additions & 1 deletion src/client.h
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,8 @@ class CClient : public QObject
QMutex MutexDriverReinit;

// server settings
int iServerSockBufNumFrames;
int iServerSockBufNumFrames;
bool bRawAudioIsSupported;

// for ping measurement
QElapsedTimer PreciseTime;
Expand Down Expand Up @@ -456,6 +457,7 @@ protected slots:
void OnMuteStateHasChangedReceived ( int iServerChanID, bool bIsMuted );
void OnCLChannelLevelListReceived ( CHostAddress InetAddr, CVector<uint16_t> vecLevelList );
void OnConClientListMesReceived ( CVector<CChannelInfo> vecChanInfo );
void OnVersionAndOSReceived ( COSUtil::EOpSystemType eOSType, QString strVersion );

signals:
void ConClientListMesReceived ( CVector<CChannelInfo> vecChanInfo );
Expand Down
1 change: 1 addition & 0 deletions src/clientsettingsdlg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,7 @@ CClientSettingsDlg::CClientSettingsDlg ( CClient* pNCliP, CClientSettings* pNSet
cbxAudioQuality->addItem ( tr ( "Low" ) ); // AQ_LOW
cbxAudioQuality->addItem ( tr ( "Normal" ) ); // AQ_NORMAL
cbxAudioQuality->addItem ( tr ( "High" ) ); // AQ_HIGH
cbxAudioQuality->addItem ( tr ( "Max" ) ); // AQ_RAW
cbxAudioQuality->setCurrentIndex ( static_cast<int> ( pClient->GetAudioQuality() ) );

// GUI design (skin) combo box
Expand Down
75 changes: 54 additions & 21 deletions src/server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -874,22 +874,44 @@ void CServer::DecodeReceiveData ( const int iChanCnt, const int iNumClients )
return;
}

const int iOffset = iB * SYSTEM_FRAME_SIZE_SAMPLES * vecNumAudioChannels[iChanCnt];
Comment thread
pljones marked this conversation as resolved.
// Recognise a raw audio packet by its size:
// The client doesn't pass a value for the selected audio quality implicitly.
// Rather the server is passed the length of the data sent by the client in iClientFrameSizeSamples.
// We know the exact size to expect from a client sending raw audio packets.
// The length is calculated in the client by: iNumAudioChannels * iOPUSFrameSizeSamples * sizeof ( int16_t )
// iOPUSFrameSizeSamples can be either 64 or 128 (small network buffers enabled|disabled)
// iNumAudioChannels is either 1 for mono or 2 for stereo and mono-in/stereo-out
// sizeof ( int16_t ) is the size in bytes for the raw pcm audio data = 2
// Sizes other than that are considered OPUS coded because those depend on hardcoded sizes in client.h
const bool bIsRawAudio =
( iCeltNumCodedBytes == static_cast<int> ( iClientFrameSizeSamples * vecNumAudioChannels[iChanCnt] * sizeof ( int16_t ) ) );

// get pointer to coded data
if ( eGetStat == GS_BUFFER_OK )
{
pCurCodedData = &vecvecbyCodedData[iChanCnt][0];
if ( bIsRawAudio )
{
memcpy ( &vecvecsData[iChanCnt][iOffset], &vecvecbyCodedData[iChanCnt][0], iCeltNumCodedBytes );
pCurCodedData = nullptr;
}
else
{
pCurCodedData = &vecvecbyCodedData[iChanCnt][0];
}
}
else
{
if ( bIsRawAudio )
{
memset ( &vecvecsData[iChanCnt][iOffset], 0, iCeltNumCodedBytes );
}
// for lost packets use null pointer as coded input data
pCurCodedData = nullptr;
}

// OPUS decode received data stream
if ( CurOpusDecoder != nullptr )
if ( !bIsRawAudio && CurOpusDecoder != nullptr )
{
const int iOffset = iB * SYSTEM_FRAME_SIZE_SAMPLES * vecNumAudioChannels[iChanCnt];

iUnused = opus_custom_decode ( CurOpusDecoder,
pCurCodedData,
iCeltNumCodedBytes,
Expand Down Expand Up @@ -1154,30 +1176,41 @@ void CServer::MixEncodeTransmitData ( const int iChanCnt, const int iNumClients
DoubleFrameSizeConvBufOut[iCurChanID].GetAll ( vecsSendData, DOUBLE_SYSTEM_FRAME_SIZE_SAMPLES * vecNumAudioChannels[iChanCnt] );
}

// OPUS encoding
if ( pCurOpusEncoder != nullptr )
if ( iCeltNumCodedBytes != static_cast<int> ( iClientFrameSizeSamples * vecNumAudioChannels[iChanCnt] * sizeof ( int16_t ) ) )
{
//### TODO: BEGIN ###//
// find a better place than this: the setting does not change all the time so for speed
// optimization it would be better to set it only if the network frame size is changed
opus_custom_encoder_ctl ( pCurOpusEncoder,
OPUS_SET_BITRATE ( CalcBitRateBitsPerSecFromCodedBytes ( iCeltNumCodedBytes, iClientFrameSizeSamples ) ) );
//### TODO: END ###//
// OPUS encoding
if ( pCurOpusEncoder != nullptr )
{
//### TODO: BEGIN ###//
// find a better place than this: the setting does not change all the time so for speed
// optimization it would be better to set it only if the network frame size is changed
opus_custom_encoder_ctl ( pCurOpusEncoder,
OPUS_SET_BITRATE ( CalcBitRateBitsPerSecFromCodedBytes ( iCeltNumCodedBytes, iClientFrameSizeSamples ) ) );
//### TODO: END ###//

for ( int iB = 0; iB < vecNumFrameSizeConvBlocks[iChanCnt]; iB++ )
{
const int iOffset = iB * SYSTEM_FRAME_SIZE_SAMPLES * vecNumAudioChannels[iChanCnt];

iUnused = opus_custom_encode ( pCurOpusEncoder,
&vecsSendData[iOffset],
iClientFrameSizeSamples,
&vecvecbyCodedData[iChanCnt][0],
iCeltNumCodedBytes );
}
}
}
else
{
for ( int iB = 0; iB < vecNumFrameSizeConvBlocks[iChanCnt]; iB++ )
{
const int iOffset = iB * SYSTEM_FRAME_SIZE_SAMPLES * vecNumAudioChannels[iChanCnt];

iUnused = opus_custom_encode ( pCurOpusEncoder,
&vecsSendData[iOffset],
iClientFrameSizeSamples,
&vecvecbyCodedData[iChanCnt][0],
iCeltNumCodedBytes );

// send separate mix to current clients
vecChannels[iCurChanID].PrepAndSendPacket ( &Socket, vecvecbyCodedData[iChanCnt], iCeltNumCodedBytes );
memcpy ( &vecvecbyCodedData[iChanCnt][0], &vecsSendData[iOffset], iCeltNumCodedBytes );
}
}
// send separate mix to current clients
vecChannels[iCurChanID].PrepAndSendPacket ( &Socket, vecvecbyCodedData[iChanCnt], iCeltNumCodedBytes );
}

Q_UNUSED ( iUnused )
Expand Down
2 changes: 1 addition & 1 deletion src/settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -605,7 +605,7 @@ void CClientSettings::ReadSettingsFromXML ( const QDomDocument& IniXMLDocument,
}

// audio quality
if ( GetNumericIniSet ( IniXMLDocument, "client", "audioquality", 0, 2 /* AQ_HIGH */, iValue ) )
if ( GetNumericIniSet ( IniXMLDocument, "client", "audioquality", 0, 3 /* AQ_RAW */, iValue ) )
{
pClient->SetAudioQuality ( static_cast<EAudioQuality> ( iValue ) );
}
Expand Down
3 changes: 2 additions & 1 deletion src/util.h
Original file line number Diff line number Diff line change
Expand Up @@ -505,7 +505,8 @@ enum EAudioQuality
// used for settings and the comobo box index -> enum values must be fixed!
AQ_LOW = 0,
AQ_NORMAL = 1,
AQ_HIGH = 2
AQ_HIGH = 2,
AQ_RAW = 3
Comment thread
dingodoppelt marked this conversation as resolved.
Comment thread
dingodoppelt marked this conversation as resolved.
};

// Get data status enum --------------------------------------------------------
Expand Down
Loading