diff --git a/ChangeLog b/ChangeLog index 3013fac972..99310c203a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,8 +1,78 @@ -### 3.11.0dev <- NOTE: the release version number will be 3.12.0 ### +### 3.12.1dev <- NOTE: the release version number will be 4.0.0 ### + +- Add uncompressed audio transmission - dedicated to the memory of Hans Petter Selasky (1982 - 2023) + (contributed by @dingodoppelt) + +### 3.12.1 (2026-05-17) ### + +- Client: Fix occasional spurious version updated message display (#3691). + (contributed by @softins) + +### 3.12.0 (2026-05-02) ### + +- FreeBSD: make -6 option work properly with both IPv6 and IPv4 peers (#3664). + (contributed by @softins) + +- Added missing override specifiers for virtual methods (#3667). + (contributed by @softins) + +- Client+Server: Correct QoS setting for IPv6 (#3622). + (contributed by @rdica) + +- Tools: Updated checkkeys.pl to flag missing accelerator key in translation (#3635). + (contributed by @softins) + +- Tools: Updated changelog-helper to report skipped PRs (#3639). + (contributed by @softins) + +- Server/Client: Allow registration and display of longer version numbers (#3657). + (contributed by @softins) - Extended SRV record support (#3556). (contributed by @softins, @rdica) +- Client RPC: Added a jamulusclient/setFaderLevel method to the RPC remote interface (#3571). + (contributed by @corrados) + +- Client RPC: Added jamulusclient/pollServerList methods and jamulusclient/receivedServerList notification to JSON-RPC interface (#3479). + (contributed by @ann0see) + +- Server RPC: Added jamulusserver/setDirectory request (#3533). + (contributed by @pljones) + +- iOS/Android: Use compact view as default view on mobile OS for better usability (#3587). + (contributed by @ann0see) + +- Added link to privacy policy (#3586). + (contributed by @ann0see) + +- Tools: checkkeys.pl now automatically finds the translation directory (#3590). + (contributed by @softins) + +- Tools: updated checkkeys.pl to use XML::LibXML (#3558). + (contributed by @softins) + +- Tools: added improvements to changelog-helper.sh (#3459). + (contributed by @softins) + +- Tools: Added missing Makefile.in files to Opus 1.5.2 distro (#3488). + (contributed by @softins) + +- Translation: Added Japanese translation (#3568). + (contributed by @tsukurun) + +- Translation: Updated Slovak translation (#3650). + (contributed by @jose1711) + +- Translations updated from Hosted Weblate (#3540, #3453, #3441, #3388, #3652, #3638, #3620, #3656, #3663). + (contributed by @weblate) + +- Client: Added MIDI tab to Settings GUI exposing MIDI parameters. MIDI Learn feature also added (#3502). + (contributed by @ignotus666) + +- Client: Added screen-reader accessibility to server list in the Connect dialog (#3606). + (contributed by @chigkim) + - Client: Bug: Fix missing variables passed to connect dialog (#3578). (contributed by @ann0see) @@ -27,30 +97,18 @@ - Client: Added native MIDI support to the ASIO (non-Jack) Windows build (#3431). (contributed by @softins) -- Client RPC: Added a jamulusclient/setFaderLevel method to the RPC remote interface (#3571). - (contributed by @corrados) - -- Client RPC: Added jamulusclient/pollServerList methods and jamulusclient/receivedServerList notification to JSON-RPC interface (#3479). - (contributed by @ann0see) - - Server: Disabled swap memory usage on Linux (#3381). (contributed by @dtinth) - Server: The `-m`/`--htmlstatus` option is considered deprecated and has been replaced by JSON RPC's `jamulusserver/getClients` method. The `-m` option will be removed in future (#3398). (contributed by @ann0see) -- Server RPC: Added jamulusserver/setDirectory request (#3533). - (contributed by @pljones) - - Windows: Avoid screensaver or sleeping if connected to a server (#3498). (contributed by @ann0see) - Linux: Reorder service initialization to wait for network interface before starting the Server (#3440). (contributed by @rdica) -- iOS/Android: Use compact view as default view on mobile OS for better usability (#3587). - (contributed by @ann0see) - - iOS: Fixed GUI issue preventing the connect dialog to show correctly (#3343). (contributed by @ann0see) @@ -75,26 +133,20 @@ - iOS: Fix crash on Qt6 after closing the chat window (#3413). (contributed by @ann0see) -- Added Japanese translation (ja_JP) (#3568). - (contributed by @tsukurun) - -- Translations updated from Hosted Weblate (#3540, #3453, #3441, #3388). - (contributed by @weblate) - -- Added link to privacy policy (#3586). - (contributed by @ann0see) +- Build: Updated bundled Qt6 to version 6.10.1 (iOS 6.7.3) (#3407, #3262, #3599). + (contributed by @app/github-actions) -- Tools: checkkeys.pl now automatically finds the translation directory (#3590). - (contributed by @softins) +- Build: Updated Windows Installer base (NSIS) to version 3.11 (#3483). + (contributed by @app/github-actions) -- Tools: updated checkkeys.pl to use XML::LibXML (#3558). - (contributed by @softins) +- Build: Bump maxim-lobanov/setup-xcode from 1.6.0 to 1.7.0 (#3645). + (contributed by @app/dependabot) -- Tools: added improvements to changelog-helper.sh (#3459). - (contributed by @softins) +- Build: Bump actions/download-artifact from 7 to 8 (#3633). + (contributed by @app/dependabot) -- Tools: Added missing Makefile.in files to Opus 1.5.2 distro (#3488). - (contributed by @softins) +- Build: Bump actions/upload-artifact from 6 to 7 (#3634). + (contributed by @app/dependabot) - Build: Improvements and ASIO fix for Windows dependencies (#3612). (contributed by @softins) @@ -123,12 +175,6 @@ - Build: Bump Qt6 from 6.10.1 to 6.10.2 (#3616). (contributed by @app/github-actions) -- Build: Updated bundled Qt6 to version 6.9.1 (iOS 6.7.3) (#3407, #3262). - (contributed by @app/github-actions) - -- Build: Updated Windows Installer base (NSIS) to version 3.11 (#3483). - (contributed by @app/github-actions) - - Build: Updated create-dmg (macOS) to version 1.2.3 (#3561). (contributed by @app/github-actions) diff --git a/Jamulus.pro b/Jamulus.pro index 28f3bacd74..421a7c6218 100644 --- a/Jamulus.pro +++ b/Jamulus.pro @@ -1,4 +1,4 @@ -VERSION = 3.11.0dev +VERSION = 3.12.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. @@ -85,7 +85,7 @@ DEFINES += APP_VERSION=\\\"$$VERSION\\\" \ CUSTOM_MODES \ _REENTRANT -# some depreciated functions need to be kept for older versions to build +# some deprecated functions need to be kept for older versions to build # TODO as soon as we drop support for the old Qt version, remove the following line DEFINES += QT_NO_DEPRECATED_WARNINGS diff --git a/android/res/drawable-hdpi/icon.png b/android/res/drawable-hdpi/icon.png index 3ba6f7222f..704dc57108 100644 Binary files a/android/res/drawable-hdpi/icon.png and b/android/res/drawable-hdpi/icon.png differ diff --git a/android/res/drawable-ldpi/icon.png b/android/res/drawable-ldpi/icon.png index ea9221bedf..2c26f9697c 100644 Binary files a/android/res/drawable-ldpi/icon.png and b/android/res/drawable-ldpi/icon.png differ diff --git a/android/res/drawable-mdpi/icon.png b/android/res/drawable-mdpi/icon.png index 9ea7673bd8..601c2f31f3 100644 Binary files a/android/res/drawable-mdpi/icon.png and b/android/res/drawable-mdpi/icon.png differ diff --git a/android/res/drawable-xhdpi/icon.png b/android/res/drawable-xhdpi/icon.png index 33f37bdaae..5e395bfe4d 100644 Binary files a/android/res/drawable-xhdpi/icon.png and b/android/res/drawable-xhdpi/icon.png differ diff --git a/android/res/drawable-xxhdpi/icon.png b/android/res/drawable-xxhdpi/icon.png index 873c1fc25b..32a0492448 100644 Binary files a/android/res/drawable-xxhdpi/icon.png and b/android/res/drawable-xxhdpi/icon.png differ diff --git a/android/res/drawable-xxxhdpi/icon.png b/android/res/drawable-xxxhdpi/icon.png index 43c1851e6e..3085415583 100644 Binary files a/android/res/drawable-xxxhdpi/icon.png and b/android/res/drawable-xxxhdpi/icon.png differ diff --git a/src/channel.cpp b/src/channel.cpp index 9bd409889a..47a492dcb9 100644 --- a/src/channel.cpp +++ b/src/channel.cpp @@ -83,6 +83,8 @@ CChannel::CChannel ( const bool bNIsServer ) : QObject::connect ( &Protocol, &CProtocol::ClientIDReceived, this, &CChannel::ClientIDReceived ); + QObject::connect ( &Protocol, &CProtocol::RawAudioSupported, this, &CChannel::RawAudioSupported ); + QObject::connect ( &Protocol, &CProtocol::MuteStateHasChangedReceived, this, &CChannel::MuteStateHasChangedReceived ); QObject::connect ( &Protocol, &CProtocol::ChangeChanInfo, this, &CChannel::OnChangeChanInfo ); diff --git a/src/channel.h b/src/channel.h index 1567268307..096a88c9e9 100644 --- a/src/channel.h +++ b/src/channel.h @@ -149,6 +149,7 @@ class CChannel : public QObject } } void CreateClientIDMes ( const int iChanID ) { Protocol.CreateClientIDMes ( iChanID ); } + void CreateRawAudioSupportedMes() { Protocol.CreateRawAudioSupportedMes(); } void CreateReqNetwTranspPropsMes() { Protocol.CreateReqNetwTranspPropsMes(); } void CreateReqSplitMessSupportMes() { Protocol.CreateReqSplitMessSupportMes(); } void CreateReqJitBufMes() { Protocol.CreateReqJitBufMes(); } @@ -272,6 +273,7 @@ public slots: void ConClientListMesReceived ( CVector vecChanInfo ); void ChanInfoHasChanged(); void ClientIDReceived ( int iChanID ); + void RawAudioSupported(); void MuteStateHasChanged ( int iChanID, bool bIsMuted ); void MuteStateHasChangedReceived ( int iChanID, bool bIsMuted ); void ReqChanInfo(); diff --git a/src/chatdlg.cpp b/src/chatdlg.cpp index b0096e5917..5b799c2680 100644 --- a/src/chatdlg.cpp +++ b/src/chatdlg.cpp @@ -43,16 +43,24 @@ CChatDlg::CChatDlg ( QWidget* parent ) : CBaseDlg ( parent, Qt::Window ) // use edtLocalInputText->setAccessibleName ( tr ( "New chat text edit box" ) ); - // clear chat window and edit line - txvChatWindow->clear(); + // clear edit line edtLocalInputText->clear(); - // we do not want to show a cursor in the chat history - txvChatWindow->setCursorWidth ( 0 ); - // set a placeholder text to make sure where to type the message in (#384) edtLocalInputText->setPlaceholderText ( tr ( "Type a message here" ) ); + // Set up the list model and delegate for accessible per-message rows ------ + m_pChatModel = new QStandardItemModel ( this ); + txvChatWindow->setModel ( m_pChatModel ); + txvChatWindow->setItemDelegate ( new ChatDelegate ( txvChatWindow ) ); + txvChatWindow->setSelectionMode ( QAbstractItemView::SingleSelection ); + txvChatWindow->setEditTriggers ( QAbstractItemView::NoEditTriggers ); + txvChatWindow->setWordWrap ( true ); + txvChatWindow->setResizeMode ( QListView::Adjust ); + txvChatWindow->setContextMenuPolicy ( Qt::CustomContextMenu ); + txvChatWindow->installEventFilter ( this ); + txvChatWindow->viewport()->installEventFilter ( this ); + // Menu ------------------------------------------------------------------- QMenuBar* pMenu = new QMenuBar ( this ); QMenu* pEditMenu = new QMenu ( tr ( "&Edit" ), this ); @@ -76,7 +84,7 @@ CChatDlg::CChatDlg ( QWidget* parent ) : CBaseDlg ( parent, Qt::Window ) // use QObject::connect ( butSend, &QPushButton::clicked, this, &CChatDlg::OnSendText ); - QObject::connect ( txvChatWindow, &QTextBrowser::anchorClicked, this, &CChatDlg::OnAnchorClicked ); + QObject::connect ( txvChatWindow, &QListView::customContextMenuRequested, this, &CChatDlg::OnChatContextMenu ); #if defined( Q_OS_IOS ) QObject::connect ( closeAction, &QAction::triggered, this, &CChatDlg::OnCloseClicked ); @@ -105,15 +113,11 @@ void CChatDlg::OnSendText() void CChatDlg::OnClearChatHistory() { - // clear chat window - txvChatWindow->clear(); + m_pChatModel->clear(); } void CChatDlg::AddChatText ( QString strChatText ) { - // notify accessibility plugin that text has changed - QAccessible::updateAccessibility ( new QAccessibleValueChangeEvent ( txvChatWindow, strChatText ) ); - // analyze strChatText to check if hyperlink (limit ourselves to http(s)://) but do not // replace the hyperlinks if any HTML code for a hyperlink was found (the user has done the HTML // coding hisself and we should not mess with that) @@ -134,8 +138,49 @@ void CChatDlg::AddChatText ( QString strChatText ) "\\1" ); } - // add new text in chat window - txvChatWindow->append ( strChatText ); + // DisplayRole stores HTML; strip tags for the accessible name so VoiceOver reads + // clean text rather than raw angle-bracket markup when navigating to this item + QTextDocument plainDoc; + plainDoc.setHtml ( strChatText ); + QString strPlainText = plainDoc.toPlainText(); + + QStandardItem* pItem = new QStandardItem ( strChatText ); + pItem->setData ( strPlainText, Qt::AccessibleTextRole ); + m_pChatModel->appendRow ( pItem ); + txvChatWindow->scrollToBottom(); + + // tell screen readers a new row exists, then announce its text as the list's + // current value — drives VoiceOver live-region-style announcement on macOS + int row = m_pChatModel->rowCount() - 1; + QAccessibleTableModelChangeEvent* pChangeEvent = + new QAccessibleTableModelChangeEvent ( txvChatWindow, QAccessibleTableModelChangeEvent::RowsInserted ); + pChangeEvent->setFirstRow ( row ); + pChangeEvent->setLastRow ( row ); + pChangeEvent->setFirstColumn ( 0 ); + pChangeEvent->setLastColumn ( 0 ); + QAccessible::updateAccessibility ( pChangeEvent ); + QAccessible::updateAccessibility ( new QAccessibleValueChangeEvent ( txvChatWindow, strPlainText ) ); +} + +void CChatDlg::OnCopyChatMessage() +{ + QModelIndexList sel = txvChatWindow->selectionModel()->selectedIndexes(); + if ( sel.isEmpty() ) + return; + QTextDocument doc; + doc.setHtml ( sel.first().data ( Qt::DisplayRole ).toString() ); + QApplication::clipboard()->setText ( doc.toPlainText() ); +} + +void CChatDlg::OnChatContextMenu ( const QPoint& pos ) +{ + QModelIndex idx = txvChatWindow->indexAt ( pos ); + if ( !idx.isValid() ) + return; + txvChatWindow->setCurrentIndex ( idx ); + QMenu menu ( this ); + menu.addAction ( tr ( "Copy message" ), this, &CChatDlg::OnCopyChatMessage ); + menu.exec ( txvChatWindow->viewport()->mapToGlobal ( pos ) ); } void CChatDlg::OnAnchorClicked ( const QUrl& Url ) @@ -153,6 +198,59 @@ void CChatDlg::OnAnchorClicked ( const QUrl& Url ) } } +bool CChatDlg::eventFilter ( QObject* obj, QEvent* event ) +{ + if ( obj == txvChatWindow && event->type() == QEvent::KeyPress ) + { + QKeyEvent* ke = static_cast ( event ); + if ( ke->matches ( QKeySequence::Copy ) ) + { + OnCopyChatMessage(); + return true; + } + } + if ( obj == txvChatWindow->viewport() ) + { + if ( event->type() == QEvent::MouseMove ) + { + QMouseEvent* me = static_cast ( event ); + QModelIndex idx = txvChatWindow->indexAt ( me->pos() ); + if ( idx.isValid() ) + { + QRect rect = txvChatWindow->visualRect ( idx ); + QTextDocument doc; + doc.setHtml ( idx.data ( Qt::DisplayRole ).toString() ); + doc.setTextWidth ( rect.width() ); + QString anchor = doc.documentLayout()->anchorAt ( me->pos() - rect.topLeft() ); + txvChatWindow->viewport()->setCursor ( anchor.isEmpty() ? Qt::ArrowCursor : Qt::PointingHandCursor ); + } + else + { + txvChatWindow->viewport()->setCursor ( Qt::ArrowCursor ); + } + } + if ( event->type() == QEvent::MouseButtonPress ) + { + QMouseEvent* me = static_cast ( event ); + QModelIndex idx = txvChatWindow->indexAt ( me->pos() ); + if ( idx.isValid() ) + { + QRect rect = txvChatWindow->visualRect ( idx ); + QTextDocument doc; + doc.setHtml ( idx.data ( Qt::DisplayRole ).toString() ); + doc.setTextWidth ( rect.width() ); + QString anchor = doc.documentLayout()->anchorAt ( me->pos() - rect.topLeft() ); + if ( !anchor.isEmpty() ) + { + OnAnchorClicked ( QUrl ( anchor ) ); + return true; + } + } + } + } + return CBaseDlg::eventFilter ( obj, event ); +} + #if defined( Q_OS_IOS ) || defined( ANDROID ) || defined( Q_OS_ANDROID ) void CChatDlg::OnCloseClicked() { @@ -166,4 +264,4 @@ void CChatDlg::OnCloseClicked() close(); # endif } -#endif \ No newline at end of file +#endif diff --git a/src/chatdlg.h b/src/chatdlg.h index 1519a05265..301ea715af 100644 --- a/src/chatdlg.h +++ b/src/chatdlg.h @@ -35,10 +35,53 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include "global.h" #include "util.h" #include "ui_chatdlgbase.h" +class ChatDelegate : public QStyledItemDelegate +{ + Q_OBJECT +public: + explicit ChatDelegate ( QObject* parent = nullptr ) : QStyledItemDelegate ( parent ) {} + + void paint ( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const override + { + painter->save(); + if ( option.state & QStyle::State_Selected ) + painter->fillRect ( option.rect, option.palette.highlight() ); + QTextDocument doc; + doc.setHtml ( index.data ( Qt::DisplayRole ).toString() ); + doc.setTextWidth ( option.rect.width() ); + painter->translate ( option.rect.topLeft() ); + doc.drawContents ( painter, option.rect.translated ( -option.rect.topLeft() ) ); + painter->restore(); + } + + QSize sizeHint ( const QStyleOptionViewItem& option, const QModelIndex& index ) const override + { + // Use the actual viewport width so items wrap correctly during initial layout + QListView* view = qobject_cast ( parent() ); + int w = ( view && view->viewport()->width() > 0 ) ? view->viewport()->width() : option.rect.width(); + QTextDocument doc; + doc.setHtml ( index.data ( Qt::DisplayRole ).toString() ); + doc.setTextWidth ( w > 0 ? w : 200 ); + return QSize ( w, static_cast ( doc.size().height() ) ); + } +}; + /* Classes ********************************************************************/ class CChatDlg : public CBaseDlg, private Ui_CChatDlgBase { @@ -54,10 +97,18 @@ public slots: void OnLocalInputTextTextChanged ( const QString& strNewText ); void OnClearChatHistory(); void OnAnchorClicked ( const QUrl& Url ); + void OnCopyChatMessage(); + void OnChatContextMenu ( const QPoint& pos ); #if defined( Q_OS_IOS ) || defined( ANDROID ) || defined( Q_OS_ANDROID ) void OnCloseClicked(); #endif signals: void NewLocalInputText ( QString strNewText ); + +protected: + bool eventFilter ( QObject* obj, QEvent* event ) override; + +private: + QStandardItemModel* m_pChatModel; }; diff --git a/src/chatdlgbase.ui b/src/chatdlgbase.ui index 7d59db9661..0b4eb659a4 100644 --- a/src/chatdlgbase.ui +++ b/src/chatdlgbase.ui @@ -28,19 +28,10 @@ - + false - - Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - false - - - false - diff --git a/src/client.cpp b/src/client.cpp index 417f08a404..13eac289b6 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -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; @@ -131,6 +132,8 @@ CClient::CClient ( const quint16 iPortNumber, QObject::connect ( &Channel, &CChannel::ClientIDReceived, this, &CClient::OnClientIDReceived ); + QObject::connect ( &Channel, &CChannel::RawAudioSupported, this, &CClient::OnRawAudioSupported ); + QObject::connect ( &Channel, &CChannel::MuteStateHasChangedReceived, this, &CClient::OnMuteStateHasChangedReceived ); QObject::connect ( &Channel, &CChannel::LicenceRequired, this, &CClient::LicenceRequired ); @@ -989,6 +992,27 @@ void CClient::OnClientIDReceived ( int iServerChanID ) emit ClientIDReceived ( iChanID ); } +void CClient::OnRawAudioSupported() +{ + if ( !bRawAudioIsSupported ) + { + const bool bWasRunning = Sound.IsRunning(); + + if ( bWasRunning ) + { + Sound.Stop(); + } + + bRawAudioIsSupported = true; + Init(); + + if ( bWasRunning ) + { + Sound.Start(); + } + } +} + void CClient::Start() { // init object @@ -1017,6 +1041,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 @@ -1156,6 +1184,21 @@ void CClient::Init() case AQ_HIGH: iCeltNumCodedBytes = OPUS_NUM_BYTES_MONO_HIGH_QUALITY_DBLE_FRAMESIZE; break; + case AQ_RAW: + if ( bRawAudioIsSupported ) + { + // no OPUS encoding or decoding + CurOpusEncoder = nullptr; + CurOpusDecoder = nullptr; + + iCeltNumCodedBytes = sizeof ( int16_t ) * iNumAudioChannels * iOPUSFrameSizeSamples; + } + else + { + // fall back to highest OPUS quality + iCeltNumCodedBytes = OPUS_NUM_BYTES_MONO_HIGH_QUALITY_DBLE_FRAMESIZE; + } + break; } } else @@ -1175,6 +1218,21 @@ void CClient::Init() case AQ_HIGH: iCeltNumCodedBytes = OPUS_NUM_BYTES_STEREO_HIGH_QUALITY_DBLE_FRAMESIZE; break; + case AQ_RAW: + if ( bRawAudioIsSupported ) + { + // no OPUS encoding or decoding + CurOpusEncoder = nullptr; + CurOpusDecoder = nullptr; + + iCeltNumCodedBytes = sizeof ( int16_t ) * iNumAudioChannels * iOPUSFrameSizeSamples; + } + else + { + // fall back to highest OPUS quality + iCeltNumCodedBytes = OPUS_NUM_BYTES_STEREO_HIGH_QUALITY_DBLE_FRAMESIZE; + } + break; } } } @@ -1199,6 +1257,21 @@ void CClient::Init() case AQ_HIGH: iCeltNumCodedBytes = OPUS_NUM_BYTES_MONO_HIGH_QUALITY; break; + case AQ_RAW: + if ( bRawAudioIsSupported ) + { + // no OPUS encoding or decoding + CurOpusEncoder = nullptr; + CurOpusDecoder = nullptr; + + iCeltNumCodedBytes = sizeof ( int16_t ) * iNumAudioChannels * iOPUSFrameSizeSamples; + } + else + { + // fall back to highest OPUS quality + iCeltNumCodedBytes = OPUS_NUM_BYTES_MONO_HIGH_QUALITY; + } + break; } } else @@ -1218,6 +1291,21 @@ void CClient::Init() case AQ_HIGH: iCeltNumCodedBytes = OPUS_NUM_BYTES_STEREO_HIGH_QUALITY; break; + case AQ_RAW: + if ( bRawAudioIsSupported ) + { + // no OPUS encoding or decoding + CurOpusEncoder = nullptr; + CurOpusDecoder = nullptr; + + iCeltNumCodedBytes = sizeof ( int16_t ) * iNumAudioChannels * iOPUSFrameSizeSamples; + } + else + { + // fall back to highest OPUS quality + iCeltNumCodedBytes = OPUS_NUM_BYTES_STEREO_HIGH_QUALITY; + } + break; } } } @@ -1229,8 +1317,12 @@ void CClient::Init() vecZeros.Init ( iStereoBlockSizeSam, 0 ); vecsStereoSndCrdMuteStream.Init ( iStereoBlockSizeSam ); - opus_custom_encoder_ctl ( CurOpusEncoder, - OPUS_SET_BITRATE ( CalcBitRateBitsPerSecFromCodedBytes ( iCeltNumCodedBytes, iOPUSFrameSizeSamples ) ) ); + // In case we are connected to a non raw audio server or we don't use raw audio we need to initialze the codec + if ( CurOpusEncoder != nullptr ) + { + opus_custom_encoder_ctl ( CurOpusEncoder, + OPUS_SET_BITRATE ( CalcBitRateBitsPerSecFromCodedBytes ( iCeltNumCodedBytes, iOPUSFrameSizeSamples ) ) ); + } // inits for network and channel vecbyNetwData.Init ( iCeltNumCodedBytes ); @@ -1391,9 +1483,10 @@ void CClient::ProcessAudioDataIntern ( CVector& vecsStereoSndCrd ) for ( i = 0, j = 0; i < iSndCrdFrameSizeFactor; i++, j += iNumAudioChannels * iOPUSFrameSizeSamples ) { - // OPUS encoding + // OPUS encoding or copying RAW audio? if ( CurOpusEncoder != nullptr ) { + // OPUS encoding if ( bMuteOutStream ) { iUnused = opus_custom_encode ( CurOpusEncoder, &vecZeros[j], iOPUSFrameSizeSamples, &vecCeltData[0], iCeltNumCodedBytes ); @@ -1403,6 +1496,20 @@ void CClient::ProcessAudioDataIntern ( CVector& vecsStereoSndCrd ) iUnused = opus_custom_encode ( CurOpusEncoder, &vecsStereoSndCrd[j], iOPUSFrameSizeSamples, &vecCeltData[0], iCeltNumCodedBytes ); } } + else if ( bRawAudioIsSupported ) + { + // RAW audio + if ( bMuteOutStream ) + { + // output muted - fill with silence + memset ( &vecCeltData[0], 0, iCeltNumCodedBytes ); + } + else + { + // copy raw audio data + memcpy ( &vecCeltData[0], &vecsStereoSndCrd[j], iCeltNumCodedBytes ); + } + } // send coded audio through the network Channel.PrepAndSendPacket ( &Socket, vecCeltData, iCeltNumCodedBytes ); @@ -1437,11 +1544,26 @@ void CClient::ProcessAudioDataIntern ( CVector& vecsStereoSndCrd ) bJitterBufferOK = false; } - // OPUS decoding + // OPUS decoding or copying RAW audio? if ( CurOpusDecoder != nullptr ) { + // OPUS decoding iUnused = opus_custom_decode ( CurOpusDecoder, pCurCodedData, iCeltNumCodedBytes, &vecsStereoSndCrd[j], iOPUSFrameSizeSamples ); } + else if ( bRawAudioIsSupported ) + { + // RAW audio + if ( pCurCodedData != nullptr ) + { + // copy raw audio data + memcpy ( &vecsStereoSndCrd[j], pCurCodedData, iCeltNumCodedBytes ); + } + else + { + // missing audio - fill with silence + memset ( &vecsStereoSndCrd[j], 0, iCeltNumCodedBytes ); + } + } } // for muted stream we have to add our local data here @@ -1488,7 +1610,7 @@ int CClient::EstimatedOverallDelay ( const int iPingTimeMs ) // length. Since that is usually not the case but the buffers are usually // a bit larger than necessary, we introduce some factor for compensation. // Consider the jitter buffer on the client and on the server side, too. - const float fTotalJitterBufferDelayMs = fSystemBlockDurationMs * ( GetSockBufNumFrames() + GetServerSockBufNumFrames() ) * 0.7f; + const float fTotalJitterBufferDelayMs = fSystemBlockDurationMs * ( GetSockBufNumFrames() + GetServerSockBufNumFrames() ) * JITTBUF_COMP_FACTOR; // consider delay introduced by the sound card conversion buffer by using // "GetSndCrdConvBufAdditionalDelayMonoBlSize()" @@ -1519,7 +1641,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 = CurOpusDecoder != nullptr ? fSystemBlockDurationMs / 2 : 0.0f; const float fTotalBufferDelayMs = fDelayToFillNetworkPacketsMs + fTotalJitterBufferDelayMs + fTotalSoundCardDelayMs + fAdditionalAudioCodecDelayMs; diff --git a/src/client.h b/src/client.h index 6139ef599e..c71fc414ab 100644 --- a/src/client.h +++ b/src/client.h @@ -410,7 +410,8 @@ class CClient : public QObject QMutex MutexDriverReinit; // server settings - int iServerSockBufNumFrames; + int iServerSockBufNumFrames; + bool bRawAudioIsSupported; // for ping measurement QElapsedTimer PreciseTime; @@ -453,6 +454,7 @@ protected slots: void OnControllerInFaderIsMute ( int iChannelIdx, bool bIsMute ); void OnControllerInMuteMyself ( bool bMute ); void OnClientIDReceived ( int iServerChanID ); + void OnRawAudioSupported(); void OnMuteStateHasChangedReceived ( int iServerChanID, bool bIsMuted ); void OnCLChannelLevelListReceived ( CHostAddress InetAddr, CVector vecLevelList ); void OnConClientListMesReceived ( CVector vecChanInfo ); diff --git a/src/clientdlg.cpp b/src/clientdlg.cpp index 6063547896..9fdb52f3a1 100644 --- a/src/clientdlg.cpp +++ b/src/clientdlg.cpp @@ -825,25 +825,31 @@ void CClientDlg::OnVersionAndOSReceived ( COSUtil::EOpSystemType, QString strVer void CClientDlg::OnCLVersionAndOSReceived ( CHostAddress InetAddr, COSUtil::EOpSystemType, QString strVersion ) { - // display version in connect dialog - ConnectDlg.SetServerVersionResult ( InetAddr, strVersion ); - - // update check + // if connect dialog showing, pass version to it, and don't do update check + if ( bConnectDlgWasShown ) + { + // display version in connect dialog + ConnectDlg.SetServerVersionResult ( InetAddr, strVersion ); + } + else + { + // update check #if ( QT_VERSION >= QT_VERSION_CHECK( 5, 6, 0 ) ) && !defined( DISABLE_VERSION_CHECK ) - int mySuffixIndex; - QVersionNumber myVersion = QVersionNumber::fromString ( VERSION, &mySuffixIndex ); + int mySuffixIndex; + QVersionNumber myVersion = QVersionNumber::fromString ( VERSION, &mySuffixIndex ); - int serverSuffixIndex; - QVersionNumber serverVersion = QVersionNumber::fromString ( strVersion, &serverSuffixIndex ); + int serverSuffixIndex; + QVersionNumber serverVersion = QVersionNumber::fromString ( strVersion, &serverSuffixIndex ); - // only compare if the server version has no suffix (such as dev or beta) - if ( strVersion.size() == serverSuffixIndex && QVersionNumber::compare ( serverVersion, myVersion ) > 0 ) - { - // show the label and hide it after one minute again - lblUpdateCheck->show(); - QTimer::singleShot ( 60000, [this]() { lblUpdateCheck->hide(); } ); - } + // only compare if the server version has no suffix (such as dev or beta) + if ( strVersion.size() == serverSuffixIndex && QVersionNumber::compare ( serverVersion, myVersion ) > 0 ) + { + // show the label and hide it after one minute again + lblUpdateCheck->show(); + QTimer::singleShot ( 60000, [this]() { lblUpdateCheck->hide(); } ); + } #endif + } } void CClientDlg::OnChatTextReceived ( QString strChatText ) diff --git a/src/clientsettingsdlg.cpp b/src/clientsettingsdlg.cpp index d9f2e6ad11..f1c6af5dbe 100644 --- a/src/clientsettingsdlg.cpp +++ b/src/clientsettingsdlg.cpp @@ -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 ( pClient->GetAudioQuality() ) ); // GUI design (skin) combo box diff --git a/src/clientsettingsdlg.h b/src/clientsettingsdlg.h index 4baf50f5e5..acd584b559 100644 --- a/src/clientsettingsdlg.h +++ b/src/clientsettingsdlg.h @@ -69,7 +69,7 @@ class CClientSettingsDlg : public CBaseDlg, private Ui_CClientSettingsDlgBase void UpdateAudioFaderSlider(); QString GenSndCrdBufferDelayString ( const int iFrameSize, const QString strAddText = "" ); - virtual void showEvent ( QShowEvent* ); + virtual void showEvent ( QShowEvent* ) override; virtual bool eventFilter ( QObject* obj, QEvent* event ) override; CClient* pClient; diff --git a/src/connectdlg.cpp b/src/connectdlg.cpp index 5c16bbc3db..212e35d684 100644 --- a/src/connectdlg.cpp +++ b/src/connectdlg.cpp @@ -736,6 +736,12 @@ void CConnectDlg::UpdateListFilter() bFilterFound = true; } + // search version + if ( pCurListViewItem->text ( LVC_VERSION ).indexOf ( sFilterText, 0, Qt::CaseInsensitive ) >= 0 ) + { + bFilterFound = true; + } + // search children for ( int iCCnt = 0; iCCnt < pCurListViewItem->childCount(); iCCnt++ ) { @@ -1106,7 +1112,7 @@ void CConnectDlg::UpdateDirectoryComboBox() cbxDirectory->clear(); cbxDirectory->addItem ( DirectoryTypeToString ( AT_DEFAULT ) ); cbxDirectory->addItem ( DirectoryTypeToString ( AT_ANY_GENRE2 ) ); - cbxDirectory->addItem ( DirectoryTypeToString ( AT_ANY_GENRE3 ) ); + cbxDirectory->addItem ( DirectoryTypeToString ( AT_ANY_GENRE_ASIA ) ); cbxDirectory->addItem ( DirectoryTypeToString ( AT_GENRE_ROCK ) ); cbxDirectory->addItem ( DirectoryTypeToString ( AT_GENRE_JAZZ ) ); cbxDirectory->addItem ( DirectoryTypeToString ( AT_GENRE_CLASSICAL_FOLK ) ); diff --git a/src/global.h b/src/global.h index 31ce30f5ec..755c447dfb 100644 --- a/src/global.h +++ b/src/global.h @@ -96,22 +96,22 @@ LED bar: lbr #define MAX_DELAY_PANNING_SAMPLES 64 // default server address and port numbers -#define DEFAULT_QOS_NUMBER 128 // CS4 (Quality of Service) -#define DEFAULT_SERVER_ADDRESS "anygenre1.jamulus.io:22124" // default port explicit to avoid unneeded SRV lookup +#define DEFAULT_QOS_NUMBER 128 // CS4 (Quality of Service) +#define DEFAULT_SERVER_ADDRESS "anygenre1.jamulus.app:22124" // default port explicit to avoid unneeded SRV lookup #define DEFAULT_PORT_NUMBER 22124 -#define CENTSERV_ANY_GENRE2 "anygenre2.jamulus.io:22224" -#define CENTSERV_ANY_GENRE3 "anygenre3.jamulus.io:22624" -#define CENTSERV_GENRE_ROCK "rock.jamulus.io:22424" -#define CENTSERV_GENRE_JAZZ "jazz.jamulus.io:22324" -#define CENTSERV_GENRE_CLASSICAL_FOLK "classical.jamulus.io:22524" -#define CENTSERV_GENRE_CHORAL "choral.jamulus.io:22724" +#define DIR_ADDR_ANY_GENRE2 "anygenre2.jamulus.app:22224" +#define DIR_ADDR_ANY_GENRE_ASIA "asia.jamulus.app:22624" +#define DIR_ADDR_GENRE_ROCK "rock.jamulus.app:22424" +#define DIR_ADDR_GENRE_JAZZ "jazz.jamulus.app:22324" +#define DIR_ADDR_GENRE_CLASSICAL_FOLK "classical.jamulus.app:22524" +#define DIR_ADDR_GENRE_CHORAL "choral.jamulus.app:22724" // specify an invalid port to disable the server #define INVALID_PORT -1 // servers to check for new versions -#define UPDATECHECK1_ADDRESS "updatecheck1.jamulus.io" -#define UPDATECHECK2_ADDRESS "updatecheck2.jamulus.io" +#define UPDATECHECK1_ADDRESS "updatecheck1.jamulus.app" +#define UPDATECHECK2_ADDRESS "updatecheck2.jamulus.app" // getting started and software manual URL #define CLIENT_GETTING_STARTED_URL "https://jamulus.io/wiki/Getting-Started" @@ -230,6 +230,9 @@ LED bar: lbr // defines the time interval at which the ping time is updated in the GUI #define PING_UPDATE_TIME_MS 500 // ms +// defines a factor to compensate for larger than ideal jitter buffer sizes for estimated overall delay calculation +#define JITTBUF_COMP_FACTOR 0.7f + // defines the time interval at which the ping time is updated for the server list #define PING_UPDATE_TIME_SERVER_LIST_MS 2500 // ms @@ -270,7 +273,7 @@ LED bar: lbr #define MAX_LEN_SERVER_NAME 20 #define MAX_LEN_IP_ADDRESS 15 #define MAX_LEN_SERVER_CITY 20 -#define MAX_LEN_VERSION_TEXT 30 +#define MAX_LEN_VERSION_TEXT 50 // define Settings tab indexes #define SETTING_TAB_USER 0 diff --git a/src/main.cpp b/src/main.cpp index 98e5ab09ae..a034218f8a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -87,6 +87,7 @@ int main ( int argc, char** argv ) bool bDisconnectAllClientsOnQuit = false; bool bUseDoubleSystemFrameSize = true; // default is 128 samples frame size bool bUseMultithreading = false; + bool bDisableRaw = false; bool bShowAnalyzerConsole = false; bool bMuteStream = false; bool bMuteMeInPersonalMix = false; @@ -478,6 +479,19 @@ int main ( int argc, char** argv ) continue; } + // Disable raw audio feature ------------------------------------------- + if ( GetFlagArgument ( argv, + i, + "--noraw", // no short form + "--noraw" ) ) + { + bDisableRaw = true; + qInfo() << "- raw audio is disabled"; + CommandLineOptions << "--noraw"; + ServerOnlyOptions << "--noraw"; + continue; + } + // Client only: // Connect on startup -------------------------------------------------- @@ -989,6 +1003,7 @@ int main ( int argc, char** argv ) strRecordingDirName, bDisconnectAllClientsOnQuit, bUseDoubleSystemFrameSize, + bDisableRaw, bUseMultithreading, bDisableRecording, bDelayPan, @@ -1115,6 +1130,7 @@ QString UsageArguments ( char** argv ) " -P, --delaypan start with delay panning enabled\n" " -R, --recording set server recording directory; server will record when a session is active by default\n" " --norecord set server not to record by default when recording is configured\n" + " --noraw disable raw audio\n" " -s, --server start Server\n" " --serverbindip IP address the Server will bind to (rather than all)\n" " -T, --multithreading use multithreading to make better use of\n" diff --git a/src/protocol.cpp b/src/protocol.cpp index 47be8bc265..a30534bc72 100644 --- a/src/protocol.cpp +++ b/src/protocol.cpp @@ -235,6 +235,11 @@ MESSAGES (with connection) note: does not have any data -> n = 0 +- PROTMESSID_RAWAUDIO_SUPPORTED: informs client that server supports raw (uncompressed) audio + + note: does not have any data -> n = 0 + + - PROTMESSID_RECORDER_STATE: notifies of changes in the server jam recorder state +--------------+ @@ -813,6 +818,10 @@ void CProtocol::ParseMessageBody ( const CVector& vecbyMesBodyData, con EvaluateSplitMessSupportedMes(); break; + case PROTMESSID_RAWAUDIO_SUPPORTED: + EvaluateRawAudioSupportedMes(); + break; + case PROTMESSID_LICENCE_REQUIRED: EvaluateLicenceRequiredMes ( vecbyMesBodyDataRef ); break; @@ -1520,6 +1529,16 @@ bool CProtocol::EvaluateSplitMessSupportedMes() return false; // no error } +void CProtocol::CreateRawAudioSupportedMes() { CreateAndSendMessage ( PROTMESSID_RAWAUDIO_SUPPORTED, CVector ( 0 ) ); } + +bool CProtocol::EvaluateRawAudioSupportedMes() +{ + // invoke message action + emit RawAudioSupported(); + + return false; // no error +} + void CProtocol::CreateLicenceRequiredMes ( const ELicenceType eLicenceType ) { CVector vecData ( 1 ); // 1 bytes of data diff --git a/src/protocol.h b/src/protocol.h index b76e66607b..03ac9f328f 100644 --- a/src/protocol.h +++ b/src/protocol.h @@ -62,6 +62,7 @@ #define PROTMESSID_RECORDER_STATE 33 // contains the state of the jam recorder (ERecorderState) #define PROTMESSID_REQ_SPLIT_MESS_SUPPORT 34 // request support for split messages #define PROTMESSID_SPLIT_MESS_SUPPORTED 35 // split messages are supported +#define PROTMESSID_RAWAUDIO_SUPPORTED 36 // raw (uncompressed) audio is supported // message IDs of connection less messages (CLM) // DEFINITION -> start at 1000, end at 1999, see IsConnectionLessMessageID @@ -124,6 +125,7 @@ class CProtocol : public QObject void CreateReqNetwTranspPropsMes(); void CreateReqSplitMessSupportMes(); void CreateSplitMessSupportedMes(); + void CreateRawAudioSupportedMes(); void CreateLicenceRequiredMes ( const ELicenceType eLicenceType ); void CreateOpusSupportedMes(); @@ -258,6 +260,7 @@ class CProtocol : public QObject bool EvaluateReqNetwTranspPropsMes(); bool EvaluateReqSplitMessSupportMes(); bool EvaluateSplitMessSupportedMes(); + bool EvaluateRawAudioSupportedMes(); bool EvaluateLicenceRequiredMes ( const CVector& vecData ); bool EvaluateVersionAndOSMes ( const CVector& vecData ); bool EvaluateRecorderStateMes ( const CVector& vecData ); @@ -321,6 +324,7 @@ public slots: void ReqNetTranspProps(); void ReqSplitMessSupport(); void SplitMessSupported(); + void RawAudioSupported(); void LicenceRequired ( ELicenceType eLicenceType ); void VersionAndOSReceived ( COSUtil::EOpSystemType eOSType, QString strVersion ); void RecorderStateReceived ( ERecorderState eRecorderState ); diff --git a/src/res/CLEDBlackBig.png b/src/res/CLEDBlackBig.png index 83b148bdb5..bd1ef9305a 100644 Binary files a/src/res/CLEDBlackBig.png and b/src/res/CLEDBlackBig.png differ diff --git a/src/res/CLEDBlackSrc.png b/src/res/CLEDBlackSrc.png index 7acb70e6c6..dea9569713 100644 Binary files a/src/res/CLEDBlackSrc.png and b/src/res/CLEDBlackSrc.png differ diff --git a/src/res/CLEDGreenSrc.png b/src/res/CLEDGreenSrc.png index 95b0ea139f..e5139e8388 100644 Binary files a/src/res/CLEDGreenSrc.png and b/src/res/CLEDGreenSrc.png differ diff --git a/src/res/CLEDGreySrc.png b/src/res/CLEDGreySrc.png index b3f5d7b015..3a7457ad74 100644 Binary files a/src/res/CLEDGreySrc.png and b/src/res/CLEDGreySrc.png differ diff --git a/src/res/CLEDRedSrc.png b/src/res/CLEDRedSrc.png index 9a2368cc6c..8ff6c9bb42 100644 Binary files a/src/res/CLEDRedSrc.png and b/src/res/CLEDRedSrc.png differ diff --git a/src/res/CLEDYellowSrc.png b/src/res/CLEDYellowSrc.png index 7c643d925b..96b8d9792a 100644 Binary files a/src/res/CLEDYellowSrc.png and b/src/res/CLEDYellowSrc.png differ diff --git a/src/res/HLEDBlackSrc.png b/src/res/HLEDBlackSrc.png index a2b423b0f2..b4781ebd5f 100644 Binary files a/src/res/HLEDBlackSrc.png and b/src/res/HLEDBlackSrc.png differ diff --git a/src/res/HLEDGreenSrc.png b/src/res/HLEDGreenSrc.png index 2e395b919e..e930272e03 100644 Binary files a/src/res/HLEDGreenSrc.png and b/src/res/HLEDGreenSrc.png differ diff --git a/src/res/HLEDGreySrc.png b/src/res/HLEDGreySrc.png index 8cafffbd1c..50532d9093 100644 Binary files a/src/res/HLEDGreySrc.png and b/src/res/HLEDGreySrc.png differ diff --git a/src/res/HLEDRedSrc.png b/src/res/HLEDRedSrc.png index 83e21598a3..841e088e07 100644 Binary files a/src/res/HLEDRedSrc.png and b/src/res/HLEDRedSrc.png differ diff --git a/src/res/HLEDYellowSrc.png b/src/res/HLEDYellowSrc.png index 12ac10b522..5988715bb8 100644 Binary files a/src/res/HLEDYellowSrc.png and b/src/res/HLEDYellowSrc.png differ diff --git a/src/res/flags/flagnone.png b/src/res/flags/flagnone.png index af4134d26b..8a6b7a5305 100644 Binary files a/src/res/flags/flagnone.png and b/src/res/flags/flagnone.png differ diff --git a/src/res/fronticon.png b/src/res/fronticon.png index a90d7ae535..ed5b6e460c 100644 Binary files a/src/res/fronticon.png and b/src/res/fronticon.png differ diff --git a/src/res/fronticonserver.png b/src/res/fronticonserver.png index 87fb5c8eff..d4d24eb855 100644 Binary files a/src/res/fronticonserver.png and b/src/res/fronticonserver.png differ diff --git a/src/res/instruments/clarinet.png b/src/res/instruments/clarinet.png index 623141f054..b12233220f 100644 Binary files a/src/res/instruments/clarinet.png and b/src/res/instruments/clarinet.png differ diff --git a/src/res/instruments/conductor.png b/src/res/instruments/conductor.png index 034fabae05..d37110f3c9 100644 Binary files a/src/res/instruments/conductor.png and b/src/res/instruments/conductor.png differ diff --git a/src/res/instruments/flute.png b/src/res/instruments/flute.png index 6dd3d751a4..df245d7932 100644 Binary files a/src/res/instruments/flute.png and b/src/res/instruments/flute.png differ diff --git a/src/res/instruments/vocaltenor.png b/src/res/instruments/vocaltenor.png index 633477bbb1..9e109f51a3 100644 Binary files a/src/res/instruments/vocaltenor.png and b/src/res/instruments/vocaltenor.png differ diff --git a/src/res/io.jamulus.jamulus.png b/src/res/io.jamulus.jamulus.png index 8444778cb6..338174e73c 100644 Binary files a/src/res/io.jamulus.jamulus.png and b/src/res/io.jamulus.jamulus.png differ diff --git a/src/res/mixerboardbackground.png b/src/res/mixerboardbackground.png index 6759256460..e1d1aadd98 100644 Binary files a/src/res/mixerboardbackground.png and b/src/res/mixerboardbackground.png differ diff --git a/src/server.cpp b/src/server.cpp index a477771fde..d6cd2bf0cc 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -40,6 +40,7 @@ CServer::CServer ( const int iNewMaxNumChan, const QString& strRecordingDirName, const bool bNDisconnectAllClientsOnQuit, const bool bNUseDoubleSystemFrameSize, + const bool bNDisableRaw, const bool bNUseMultithreading, const bool bDisableRecording, const bool bNDelayPan, @@ -49,6 +50,7 @@ CServer::CServer ( const int iNewMaxNumChan, bUseMultithreading ( bNUseMultithreading ), iMaxNumChannels ( iNewMaxNumChan ), iCurNumChannels ( 0 ), + bDisableRaw ( bNDisableRaw ), Socket ( this, iPortNumber, iQosNumber, strServerBindIP, bNEnableIPv6 ), Logging(), iFrameCount ( 0 ), @@ -383,6 +385,12 @@ void CServer::OnNewConnection ( int iChID, int iTotChans, CHostAddress RecHostAd // must be the first message to be sent for a new connection) vecChannels[iChID].CreateClientIDMes ( iChID ); + // if not disabled, inform the client that the server supports raw (uncompressed) audio + if ( !bDisableRaw ) + { + vecChannels[iChID].CreateRawAudioSupportedMes(); + } + // Send an empty channel list in order to force clients to reset their // audio mixer state. This is required to trigger clients to re-send their // gain levels upon reconnecting after server restarts. @@ -885,16 +893,41 @@ void CServer::DecodeReceiveData ( const int iChanCnt, const int iNumClients ) pCurCodedData = nullptr; } - // OPUS decode received data stream - if ( CurOpusDecoder != nullptr ) + // 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 ( sizeof ( int16_t ) * iClientFrameSizeSamples * vecNumAudioChannels[iChanCnt] ) ); + + const int iOffset = iB * SYSTEM_FRAME_SIZE_SAMPLES * vecNumAudioChannels[iChanCnt]; + + if ( !bIsRawAudio ) { - const int iOffset = iB * SYSTEM_FRAME_SIZE_SAMPLES * vecNumAudioChannels[iChanCnt]; - - iUnused = opus_custom_decode ( CurOpusDecoder, - pCurCodedData, - iCeltNumCodedBytes, - &vecvecsData[iChanCnt][iOffset], - iClientFrameSizeSamples ); + // OPUS decode received data stream + if ( CurOpusDecoder != nullptr ) + { + iUnused = opus_custom_decode ( CurOpusDecoder, + pCurCodedData, + iCeltNumCodedBytes, + &vecvecsData[iChanCnt][iOffset], + iClientFrameSizeSamples ); + } + } + else if ( pCurCodedData != nullptr ) + { + // copy received raw data stream + memcpy ( &vecvecsData[iChanCnt][iOffset], pCurCodedData, iCeltNumCodedBytes ); + } + else + { + // lost packet - fill with silence + memset ( &vecvecsData[iChanCnt][iOffset], 0, iCeltNumCodedBytes ); } } @@ -1107,7 +1140,7 @@ void CServer::MixEncodeTransmitData ( const int iChanCnt, const int iNumClients } int iClientFrameSizeSamples = 0; // initialize to avoid a compiler warning - OpusCustomEncoder* pCurOpusEncoder = nullptr; + OpusCustomEncoder* CurOpusEncoder = nullptr; // get current number of CELT coded bytes const int iCeltNumCodedBytes = vecChannels[iCurChanID].GetCeltNumCodedBytes(); @@ -1119,11 +1152,11 @@ void CServer::MixEncodeTransmitData ( const int iChanCnt, const int iNumClients if ( vecNumAudioChannels[iChanCnt] == 1 ) { - pCurOpusEncoder = OpusEncoderMono[iCurChanID]; + CurOpusEncoder = OpusEncoderMono[iCurChanID]; } else { - pCurOpusEncoder = OpusEncoderStereo[iCurChanID]; + CurOpusEncoder = OpusEncoderStereo[iCurChanID]; } } else if ( vecAudioComprType[iChanCnt] == CT_OPUS64 ) @@ -1132,11 +1165,11 @@ void CServer::MixEncodeTransmitData ( const int iChanCnt, const int iNumClients if ( vecNumAudioChannels[iChanCnt] == 1 ) { - pCurOpusEncoder = Opus64EncoderMono[iCurChanID]; + CurOpusEncoder = Opus64EncoderMono[iCurChanID]; } else { - pCurOpusEncoder = Opus64EncoderStereo[iCurChanID]; + CurOpusEncoder = Opus64EncoderStereo[iCurChanID]; } } @@ -1154,25 +1187,40 @@ 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 ( sizeof ( int16_t ) * iClientFrameSizeSamples * vecNumAudioChannels[iChanCnt] ) ) { - //### 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 ( CurOpusEncoder != 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 ( CurOpusEncoder, + 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 ( CurOpusEncoder, + &vecsSendData[iOffset], + iClientFrameSizeSamples, + &vecvecbyCodedData[iChanCnt][0], + iCeltNumCodedBytes ); + // send separate mix to current clients + vecChannels[iCurChanID].PrepAndSendPacket ( &Socket, vecvecbyCodedData[iChanCnt], 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 ); + memcpy ( &vecvecbyCodedData[iChanCnt][0], &vecsSendData[iOffset], iCeltNumCodedBytes ); // send separate mix to current clients vecChannels[iCurChanID].PrepAndSendPacket ( &Socket, vecvecbyCodedData[iChanCnt], iCeltNumCodedBytes ); diff --git a/src/server.h b/src/server.h index cbdbb04373..6a42a8f9b8 100644 --- a/src/server.h +++ b/src/server.h @@ -100,6 +100,7 @@ class CServer : public QObject, public CServerSlots const QString& strRecordingDirName, const bool bNDisconnectAllClientsOnQuit, const bool bNUseDoubleSystemFrameSize, + const bool bNDisableRaw, const bool bNUseMultithreading, const bool bDisableRecording, const bool bNDelayPan, @@ -250,6 +251,9 @@ class CServer : public QObject, public CServerSlots CConvBuf DoubleFrameSizeConvBufIn[MAX_NUM_CHANNELS]; CConvBuf DoubleFrameSizeConvBufOut[MAX_NUM_CHANNELS]; + // needed for disabling raw audio transmission + bool bDisableRaw; + CVector vstrChatColors; CVector vecChanIDsCurConChan; diff --git a/src/serverdlg.cpp b/src/serverdlg.cpp index 330374fed7..8367600bf5 100644 --- a/src/serverdlg.cpp +++ b/src/serverdlg.cpp @@ -295,7 +295,7 @@ CServerDlg::CServerDlg ( CServer* pNServP, CServerSettings* pNSetP, const bool b cbxDirectoryType->addItem ( DirectoryTypeToString ( AT_NONE ) ); cbxDirectoryType->addItem ( DirectoryTypeToString ( AT_DEFAULT ) ); cbxDirectoryType->addItem ( DirectoryTypeToString ( AT_ANY_GENRE2 ) ); - cbxDirectoryType->addItem ( DirectoryTypeToString ( AT_ANY_GENRE3 ) ); + cbxDirectoryType->addItem ( DirectoryTypeToString ( AT_ANY_GENRE_ASIA ) ); cbxDirectoryType->addItem ( DirectoryTypeToString ( AT_GENRE_ROCK ) ); cbxDirectoryType->addItem ( DirectoryTypeToString ( AT_GENRE_JAZZ ) ); cbxDirectoryType->addItem ( DirectoryTypeToString ( AT_GENRE_CLASSICAL_FOLK ) ); diff --git a/src/serverrpc.cpp b/src/serverrpc.cpp index 90af0a4d8b..6ed1b1506e 100644 --- a/src/serverrpc.cpp +++ b/src/serverrpc.cpp @@ -281,7 +281,7 @@ const std::unordered_map { EDirectoryType::AT_NONE, "none" }, { EDirectoryType::AT_DEFAULT, "any_genre_1" }, { EDirectoryType::AT_ANY_GENRE2, "any_genre_2" }, - { EDirectoryType::AT_ANY_GENRE3, "any_genre_3" }, + { EDirectoryType::AT_ANY_GENRE_ASIA, "any_genre_asia" }, { EDirectoryType::AT_GENRE_ROCK, "genre_rock" }, { EDirectoryType::AT_GENRE_JAZZ, "genre_jazz" }, { EDirectoryType::AT_GENRE_CLASSICAL_FOLK, "genre_classical_folk" }, @@ -302,7 +302,7 @@ const std::unordered_map CServerRpc::sumStringToDir { "none", EDirectoryType::AT_NONE }, { "any_genre_1", EDirectoryType::AT_DEFAULT }, { "any_genre_2", EDirectoryType::AT_ANY_GENRE2 }, - { "any_genre_3", EDirectoryType::AT_ANY_GENRE3 }, + { "any_genre_asia", EDirectoryType::AT_ANY_GENRE_ASIA }, { "genre_rock", EDirectoryType::AT_GENRE_ROCK }, { "genre_jazz", EDirectoryType::AT_GENRE_JAZZ }, { "genre_classical_folk", EDirectoryType::AT_GENRE_CLASSICAL_FOLK }, diff --git a/src/settings.cpp b/src/settings.cpp index af594c8bbe..daa2fd5736 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -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 ( iValue ) ); } diff --git a/src/socket.cpp b/src/socket.cpp index f67f37c578..78885226fc 100644 --- a/src/socket.cpp +++ b/src/socket.cpp @@ -129,7 +129,7 @@ void CSocket::Init ( const quint16 iNewPortNumber, const quint16 iNewQosNumber, } #endif -#if !defined( Q_OS_DARWIN ) && !defined( Q_OS_WIN ) +#if !defined( Q_OS_BSD4 ) && !defined( Q_OS_WIN ) // set the QoS for IPv4 as well, as this is a dual-protocol socket if ( setsockopt ( UdpSocket, IPPROTO_IP, IP_TOS, (const char*) &tos, sizeof ( tos ) ) == -1 ) { @@ -272,17 +272,23 @@ CSocket::~CSocket() #endif } -#if defined( Q_OS_DARWIN ) -// sendto_ipv4_with_tos - helper function for macOS to set TOS when sending IPv4 over IPv6 socket +#if defined( Q_OS_BSD4 ) +// sendto_ipv4_with_tos - helper function for macOS and FreeBSD to set TOS when sending IPv4 over IPv6 socket static ssize_t sendto_ipv4_with_tos ( int fd, const void* buf, size_t len, int flags, const struct sockaddr* dest, socklen_t destlen, int tos ) { // For a description of 'struct cmsghdr' and the 'CMSG_xxx' macros, see 'man 3 cmsg' on a Linux machine. // The macOS man pages are less descriptive, but the API is the same, being based on the BSD socket interface. +# if defined( Q_OS_FREEBSD ) + using tos_cmsg_type = unsigned char; +# else + using tos_cmsg_type = int; +# endif + // The cmsg buffer is only set up once (tos doesn't change) so can be static static union { - unsigned char cbuf[CMSG_SPACE ( sizeof ( int ) )]; + unsigned char cbuf[CMSG_SPACE ( sizeof ( tos_cmsg_type ) )]; struct cmsghdr h; } u; static socklen_t clen = 0; @@ -290,26 +296,30 @@ static ssize_t sendto_ipv4_with_tos ( int fd, const void* buf, size_t len, int f if ( clen == 0 ) { // set up the cmsg buffer - memset ( u.cbuf, 0, sizeof ( u.cbuf ) ); + memset ( &u, 0, sizeof ( u ) ); u.h.cmsg_level = IPPROTO_IP; u.h.cmsg_type = IP_TOS; - u.h.cmsg_len = CMSG_LEN ( sizeof ( int ) ); - memcpy ( CMSG_DATA ( &u.h ), &tos, sizeof ( int ) ); - clen = (socklen_t) u.h.cmsg_len; + u.h.cmsg_len = CMSG_LEN ( sizeof ( tos_cmsg_type ) ); + + tos_cmsg_type tosvalue = static_cast ( tos & 0xFF ); + memcpy ( CMSG_DATA ( &u.h ), &tosvalue, sizeof ( tosvalue ) ); + clen = CMSG_SPACE ( sizeof ( tos_cmsg_type ) ); } struct iovec iov; + memset ( &iov, 0, sizeof ( iov ) ); iov.iov_base = const_cast ( buf ); iov.iov_len = len; struct msghdr msg; + memset ( &msg, 0, sizeof ( msg ) ); msg.msg_name = const_cast ( dest ); msg.msg_namelen = destlen; msg.msg_iov = &iov; msg.msg_iovlen = 1; - msg.msg_control = (void*) u.cbuf; + msg.msg_control = u.cbuf; msg.msg_controllen = clen; return sendmsg ( fd, &msg, flags ); @@ -355,8 +365,8 @@ void CSocket::SendPacket ( const CVector& vecbySendBuf, const CHostAddr addr[2] = htonl ( 0xFFFF ); addr[3] = htonl ( HostAddr.InetAddr.toIPv4Address() ); -#if defined( Q_OS_DARWIN ) - // In macOS we need to set TOS explicitly when sending IPv4 over IPv6 socket +#if defined( Q_OS_BSD4 ) + // In macOS and FreeBSD we need to set TOS explicitly when sending IPv4 over IPv6 socket status = sendto_ipv4_with_tos ( UdpSocket, (const char*) &( (CVector) vecbySendBuf )[0], iVecSizeOut, diff --git a/src/sound/coreaudio-mac/sound.h b/src/sound/coreaudio-mac/sound.h index 6ed3990a19..924528dd14 100644 --- a/src/sound/coreaudio-mac/sound.h +++ b/src/sound/coreaudio-mac/sound.h @@ -42,28 +42,28 @@ class CSound : public CSoundBase virtual ~CSound(); - virtual int Init ( const int iNewPrefMonoBufferSize ); - virtual void Start(); - virtual void Stop(); + virtual int Init ( const int iNewPrefMonoBufferSize ) override; + virtual void Start() override; + virtual void Stop() override; // channel selection - virtual int GetNumInputChannels() { return iNumInChanPlusAddChan; } - virtual QString GetInputChannelName ( const int iDiD ) { return sChannelNamesInput[iDiD]; } - virtual void SetLeftInputChannel ( const int iNewChan ); - virtual void SetRightInputChannel ( const int iNewChan ); - virtual int GetLeftInputChannel() { return iSelInputLeftChannel; } - virtual int GetRightInputChannel() { return iSelInputRightChannel; } - - virtual int GetNumOutputChannels() { return iNumOutChan; } - virtual QString GetOutputChannelName ( const int iDiD ) { return sChannelNamesOutput[iDiD]; } - virtual void SetLeftOutputChannel ( const int iNewChan ); - virtual void SetRightOutputChannel ( const int iNewChan ); - virtual int GetLeftOutputChannel() { return iSelOutputLeftChannel; } - virtual int GetRightOutputChannel() { return iSelOutputRightChannel; } + virtual int GetNumInputChannels() override { return iNumInChanPlusAddChan; } + virtual QString GetInputChannelName ( const int iDiD ) override { return sChannelNamesInput[iDiD]; } + virtual void SetLeftInputChannel ( const int iNewChan ) override; + virtual void SetRightInputChannel ( const int iNewChan ) override; + virtual int GetLeftInputChannel() override { return iSelInputLeftChannel; } + virtual int GetRightInputChannel() override { return iSelInputRightChannel; } + + virtual int GetNumOutputChannels() override { return iNumOutChan; } + virtual QString GetOutputChannelName ( const int iDiD ) override { return sChannelNamesOutput[iDiD]; } + virtual void SetLeftOutputChannel ( const int iNewChan ) override; + virtual void SetRightOutputChannel ( const int iNewChan ) override; + virtual int GetLeftOutputChannel() override { return iSelOutputLeftChannel; } + virtual int GetRightOutputChannel() override { return iSelOutputRightChannel; } // MIDI functions - virtual void EnableMIDI ( const bool bEnable ); - virtual bool IsMIDIEnabled() const; + virtual void EnableMIDI ( const bool bEnable ) override; + virtual bool IsMIDIEnabled() const override; virtual QStringList GetMIDIDevNames() override; // these variables/functions should be protected but cannot since we want @@ -97,7 +97,7 @@ class CSound : public CSoundBase CVector vecNumOutBufChan; protected: - virtual QString LoadAndInitializeDriver ( QString strDriverName, bool ); + virtual QString LoadAndInitializeDriver ( QString strDriverName, bool ) override; QString CheckDeviceCapabilities ( const int iDriverIdx ); void UpdateChSelection(); diff --git a/src/threadpool.h b/src/threadpool.h index 51e5d6e134..8619ecfe2d 100644 --- a/src/threadpool.h +++ b/src/threadpool.h @@ -35,13 +35,23 @@ #include #include +// compatibility with C++17 deprecation of result_of +#if __cplusplus >= 201703L +// std::invoke_result_t should be used from C++17 onwards, but is not available in C++14 and earlier +template +using threadpool_result_t = std::invoke_result_t; +#else +// std::result_of should be used on C++14 and earlier, but is deprecated in C++17 +template +using threadpool_result_t = typename std::result_of::type; +#endif + class CThreadPool { public: - CThreadPool() = default; CThreadPool ( size_t ); template - auto enqueue ( F&& f, Args&&... args ) -> std::future::type>; + auto enqueue ( F&& f, Args&&... args ) -> std::future>; ~CThreadPool(); private: @@ -87,9 +97,9 @@ inline CThreadPool::CThreadPool ( size_t threads ) : stop ( false ) // add new work item to the pool template -auto CThreadPool::enqueue ( F&& f, Args&&... args ) -> std::future::type> +auto CThreadPool::enqueue ( F&& f, Args&&... args ) -> std::future> { - using return_type = typename std::result_of::type; + using return_type = threadpool_result_t; auto task = std::make_shared> ( std::bind ( std::forward ( f ), std::forward ( args )... ) ); diff --git a/src/translation/translation_de_DE.ts b/src/translation/translation_de_DE.ts index f9b0440b08..167abc5678 100644 --- a/src/translation/translation_de_DE.ts +++ b/src/translation/translation_de_DE.ts @@ -2588,7 +2588,7 @@ Wir haben Deinen Kanal stummgeschaltet und die Funktion 'Stummschalten&apos Klicke den Button um die momentan ausgewählte Serveradresse aus der Liste von gespeicherten Servern zu löschen. - + Ping Ping diff --git a/src/translation/translation_es_ES.ts b/src/translation/translation_es_ES.ts index 7ce8214eeb..a72c0ad5da 100644 --- a/src/translation/translation_es_ES.ts +++ b/src/translation/translation_es_ES.ts @@ -2588,7 +2588,7 @@ Hemos silenciado tu canal y activado 'Silenciarme Yo'. Por favor resue Haz clic para borrar la dirección de servidor seleccionada y eliminarla de la lista de servidores guardados. - + Ping Ping diff --git a/src/translation/translation_fr_FR.ts b/src/translation/translation_fr_FR.ts index 15e64b6e4a..b47d763a2f 100644 --- a/src/translation/translation_fr_FR.ts +++ b/src/translation/translation_fr_FR.ts @@ -2588,7 +2588,7 @@ Nous avons coupé votre canal et activé "Me silencer". Veuillez d&apo Cliquez sur le bouton pour effacer l'adresse du serveur actuellement sélectionné et la supprimer de la liste des serveurs enregistrés. - + Ping Ping diff --git a/src/translation/translation_it_IT.ts b/src/translation/translation_it_IT.ts index 9f8faec628..adc415e81b 100644 --- a/src/translation/translation_it_IT.ts +++ b/src/translation/translation_it_IT.ts @@ -2589,7 +2589,7 @@ E' stato disattivato l'audio del tuo canale ed inserito il "Disat Fare clic sul pulsante per cancellare l'indirizzo del server attualmente selezionato ed eliminarlo dall'elenco dei server memorizzati. - + Ping Ping diff --git a/src/translation/translation_ja_JP.ts b/src/translation/translation_ja_JP.ts index f1e1c89a46..07daba3ea8 100644 --- a/src/translation/translation_ja_JP.ts +++ b/src/translation/translation_ja_JP.ts @@ -2588,7 +2588,7 @@ We muted your channel and activated 'Mute Myself'. Please solve the fe ボタンをクリックすると、現在選択されているサーバーアドレスがクリアされ、保存済みサーバーリストから削除されます。 - + Ping Ping diff --git a/src/translation/translation_ko_KR.ts b/src/translation/translation_ko_KR.ts index d1da73b48c..c35ecd9fb6 100644 --- a/src/translation/translation_ko_KR.ts +++ b/src/translation/translation_ko_KR.ts @@ -2588,7 +2588,7 @@ We muted your channel and activated 'Mute Myself'. Please solve the fe 버튼을 클릭하면 현재 선택한 서버 주소가 지워지고 저장된 서버 목록에서 삭제됩니다. - + Ping Ping diff --git a/src/translation/translation_nb_NO.ts b/src/translation/translation_nb_NO.ts index 480e5db6f2..f5596c841e 100644 --- a/src/translation/translation_nb_NO.ts +++ b/src/translation/translation_nb_NO.ts @@ -2616,7 +2616,7 @@ We muted your channel and activated 'Mute Myself'. Please solve the fe Filtrer tekst, eller # for tjenere som ikke er tomme - + Ping Svartidsforespørsel diff --git a/src/translation/translation_nl_NL.ts b/src/translation/translation_nl_NL.ts index b1d8511e22..a3bb21ec6d 100644 --- a/src/translation/translation_nl_NL.ts +++ b/src/translation/translation_nl_NL.ts @@ -2588,7 +2588,7 @@ We hebben uw kanaal gedempt en 'Demp mijzelf' geactiveerd. Los eerst h Klik op de knop om het momenteel geselecteerde serveradres te wissen en het uit de lijst met opgeslagen servers te verwijderen. - + Ping Ping diff --git a/src/translation/translation_pl_PL.ts b/src/translation/translation_pl_PL.ts index 65cad815e1..c5e50d9c6b 100644 --- a/src/translation/translation_pl_PL.ts +++ b/src/translation/translation_pl_PL.ts @@ -2588,7 +2588,7 @@ Twój kanał został wyciszony i włączono „Wycisz mnie”. Napraw przyczynę Kliknij przycisk aby wyczyścić wybrany adres serwera i usunąć go z listy przechowywanych serwerów. - + Ping Ping diff --git a/src/translation/translation_pt_BR.ts b/src/translation/translation_pt_BR.ts index 84541ac2dc..d7fb9ad56a 100644 --- a/src/translation/translation_pt_BR.ts +++ b/src/translation/translation_pt_BR.ts @@ -869,7 +869,7 @@ &MIDI Control Settings... - + Configuração de controle &MIDI... @@ -1185,17 +1185,17 @@ Silenciamos seu canal e ativamos 'Silenciar-me'. Resolva o problema de Could not open MIDI port - + Não foi possível abrir a porta MIDI No MIDI devices found. Please connect a MIDI device and try again. - + Nenhum dispositivo MIDI encontrado. Conecte um dispositivo MIDI e tente novamente. Please check your OS configuration. - + Por favor verifique a configuração do seu SO. @@ -1554,117 +1554,117 @@ Silenciamos seu canal e ativamos 'Silenciar-me'. Resolva o problema de Enable/disable MIDI-in port - + Ativar/desativar porta de entrada MIDI MIDI-in port check box - + Caixa de seleção porta de entrada MIDI Pick-up Mode - + Modo Pick-up When enabled, MIDI fader and pan controls will wait until the physical controller position matches the current software value before responding. This prevents sudden jumps when your physical controller is out of sync with the software. - + Quando ativado, o controle MIDI de fader e pan aguardará até que a posição do controlador físico corresponda ao valor atual do software antes de responder. Isso evita saltos repentinos quando o controlador físico estiver dessincronizado com o software. Pick-up Mode check box - + Caixa de seleção de Modo Pick-up Select which MIDI output port to connect to. Jamulus will automatically connect its MIDI input port to the selected device when enabled.You can also use your connection manager of choice to manually change connections. - + Selecione a porta de saída MIDI à qual deseja conectar. Jamulus conectará automaticamente sua porta de entrada MIDI ao dispositivo selecionado quando ativado. Você também pode usar o gerenciador de conexões de sua preferência para alterar as conexões manualmente. Select which MIDI source to connect to. Jamulus will automatically connect its MIDI input port to the selected device when enabled.You can also use Audio MIDI Setup to manually change connections. - + Selecione a fonte MIDI à qual deseja se conectar. Jamulus conectará automaticamente sua porta de entrada MIDI ao dispositivo selecionado quando ativado. Você também pode usar a Configuração de Áudio MIDI para alterar as conexões manualmente. Select which MIDI input device(s) Jamulus should listen to. Select 'All Devices' to receive MIDI from all connected devices, or choose a specific device. - + Selecione quais dispositivos de entrada MIDI o Jamulus deve receber. Selecione "Todos os dispositivos" para receber MIDI de todos os dispositivos conectados ou escolha um dispositivo específico. MIDI input device combo box - + Combo do dispositivo de entrada MIDI MIDI controller settings - + Configurações controlador MIDI There is one global MIDI channel parameter (0-16) and two parameters you can set for each item controlled: First MIDI CC and consecutive CC numbers (count). First set the channel you want Jamulus to listen on (0 for all channels). Then, for each item you want to control (volume fader, pan, solo, mute), set the first MIDI CC (CC number to start from) and number of consecutive CC numbers (count). There is one exception that does not require establishing consecutive CC numbers which is the “Mute Myself” parameter - it only requires a single CC number as it is only applied to one’s own audio stream. - + Há um parâmetro global de canal MIDI (0-16) e dois parâmetros que você pode definir para cada item controlado: o primeiro CC MIDI e a quantidade de CCs consecutivos (contagem). Primeiro, defina o canal que você deseja que o Jamulus receba (0 para todos os canais). Em seguida, para cada item que você deseja controlar (fader de volume, pan, solo, mudo), defina o primeiro CC MIDI (número do CC inicial) e a quantidade de CCs consecutivos (contagem). Há uma exceção que não requer a definição de CCs consecutivos: o parâmetro "Silenciar-me" - ele requer apenas um único número de CC, pois se aplica somente ao seu próprio fluxo de áudio. You can either type in the MIDI CC values or use the "Learn" button: click on "Learn", actuate the fader/knob/button on your MIDI controller, and the MIDI CC number will be detected and saved. - + Você pode digitar os valores MIDI CC ou usar o botão "Aprender": clique em "Aprender", acione o fader/botão/controlador do seu controlador MIDI e o número MIDI CC será detectado e salvo. MIDI channel combo box - + Caixa de combinação canal MIDI Mute Myself MIDI CC number spin box - + Campo MIDI CC Silenciar-me Fader offset spin box - + Campo MIDI CC Fader Pan offset spin box - + Campo MIDI CC Pan Solo offset spin box - + Campo MIDI CC Solo Mute offset spin box - + Campo MIDI CC Mudo Mute Myself MIDI learn button - + Botão aprender MIDI Silenciar-me Fader offset MIDI learn button - + Botão aprender MIDI Fader Pan offset MIDI learn button - + Botão aprender MIDI Pan Solo offset MIDI learn button - + Botão aprender MIDI Solo Mute offset MIDI learn button - + Botão aprender MIDI Mudo @@ -1725,55 +1725,55 @@ Silenciamos seu canal e ativamos 'Silenciar-me'. Resolva o problema de Learn - + Aprender All Devices - + Todos os Dispositivos No device connected - + Nenhum dispositivo conectado MIDI Device Not Found - + Dispositivo MIDI não encontrado The MIDI device "%1" could not be found. Using all available devices instead. - + O dispositivo MIDI "%1" não foi encontrado. Usando todos os dispositivos disponíveis. The MIDI device "%1" is not currently available. Select a different device from the dropdown to connect. - + O dispositivo MIDI "%1" não está disponível. Selecione um dispositivo diferente no menu suspenso para conectar. MIDI Device Connection Failed - + Falha na conexão do dispositivo MIDI Could not connect to MIDI device "%1". Please check your OS configuration. - + Não foi possível conectar ao dispositivo MIDI "%1". Verifique a configuração do seu SO. Could not connect to MIDI device "%1". Please check that the device is available. - + Não foi possível conectar ao dispositivo MIDI "%1". Verifique se o dispositivo está disponível. Listening... - + Ouvindo... @@ -2306,117 +2306,117 @@ Silenciamos seu canal e ativamos 'Silenciar-me'. Resolva o problema de MIDI Control - + Controle MIDI MIDI-in - + Entrada MIDI MIDI Channel - + Canal MIDI 0 (all) - + 0 (todos) 1 - + 1 2 - + 2 3 - + 3 4 - + 4 5 - + 5 6 - + 6 7 - + 7 8 - + 8 9 - + 9 10 - + 10 11 - + 11 12 - + 12 13 - + 13 14 - + 14 15 - + 15 16 - + 16 Pick-up Mode - + Modo Pick-up Mute Myself - + Silenciar-me MIDI CC - + MIDI CC @@ -2425,12 +2425,12 @@ Silenciamos seu canal e ativamos 'Silenciar-me'. Resolva o problema de Learn - + Aprender Fader - + Fader @@ -2438,7 +2438,7 @@ Silenciamos seu canal e ativamos 'Silenciar-me'. Resolva o problema de First MIDI CC - + Primeiro CC MIDI @@ -2446,22 +2446,22 @@ Silenciamos seu canal e ativamos 'Silenciar-me'. Resolva o problema de Count - + Contagem Mute - Mudo + Mudo Solo - Solo + Solo MIDI Device - + Dispositivo MIDI @@ -2592,9 +2592,9 @@ Silenciamos seu canal e ativamos 'Silenciar-me'. Resolva o problema de Clique no botão para limpar o endereço de servidor selecionado e excluí-lo da lista de servidores armazenados. - + Ping - Ping + Ping @@ -2716,7 +2716,7 @@ Silenciamos seu canal e ativamos 'Silenciar-me'. Resolva o problema de P&rivacy policy... - + Política de P&rivacidade... diff --git a/src/translation/translation_pt_PT.ts b/src/translation/translation_pt_PT.ts index 7d407a3b6c..4d325429db 100644 --- a/src/translation/translation_pt_PT.ts +++ b/src/translation/translation_pt_PT.ts @@ -2588,7 +2588,7 @@ Nós silenciamos o seu canal e ativamos 'Silenciar-me'. Por favor, res Clique no botão para limpar o endereço do servidor atualmente selecionado e eliminá-lo da lista dos servidores guardados. - + Ping Ping diff --git a/src/translation/translation_ru_RU.ts b/src/translation/translation_ru_RU.ts index 0baf18843a..faddd57a66 100644 --- a/src/translation/translation_ru_RU.ts +++ b/src/translation/translation_ru_RU.ts @@ -126,7 +126,7 @@ Japanese - + Японский @@ -2586,7 +2586,7 @@ We muted your channel and activated 'Mute Myself'. Please solve the fe - + Ping diff --git a/src/translation/translation_sk_SK.ts b/src/translation/translation_sk_SK.ts index 302c01a5c7..02182e2ab1 100644 --- a/src/translation/translation_sk_SK.ts +++ b/src/translation/translation_sk_SK.ts @@ -2588,7 +2588,7 @@ Stíšili sme váš kanál a aktivovali nastavenia 'Stíšiť ma'. Pro Kliknite na tlačidlo pre vyčistenie aktuálne vybranej adresy servera a jej vymazanie zo zoznamu uložených serverov. - + Ping Ping diff --git a/src/translation/translation_sv_SE.ts b/src/translation/translation_sv_SE.ts index c605947df4..c72c2ca66a 100644 --- a/src/translation/translation_sv_SE.ts +++ b/src/translation/translation_sv_SE.ts @@ -2590,7 +2590,7 @@ Vi stängde av din kanal och aktiverade 'Tysta mig själv'. Vänligen Klicka på knappen för att rensa den för närvarande valda serveradressen och ta bort den från listan över sparade servrar. - + Ping Ping diff --git a/src/translation/translation_zh_CN.ts b/src/translation/translation_zh_CN.ts index 697b836f95..cc0a001793 100644 --- a/src/translation/translation_zh_CN.ts +++ b/src/translation/translation_zh_CN.ts @@ -2590,7 +2590,7 @@ We muted your channel and activated 'Mute Myself'. Please solve the fe 单击该按钮可清除当前选择的服务器地址,并将其从存储的服务器列表中删除。 - + Ping Ping diff --git a/src/util.cpp b/src/util.cpp index 4fd1f3af59..b1221ae339 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -964,17 +964,17 @@ QString NetworkUtil::GetDirectoryAddress ( const EDirectoryType eDirectoryType, case AT_CUSTOM: return strDirectoryAddress; case AT_ANY_GENRE2: - return CENTSERV_ANY_GENRE2; - case AT_ANY_GENRE3: - return CENTSERV_ANY_GENRE3; + return DIR_ADDR_ANY_GENRE2; + case AT_ANY_GENRE_ASIA: + return DIR_ADDR_ANY_GENRE_ASIA; case AT_GENRE_ROCK: - return CENTSERV_GENRE_ROCK; + return DIR_ADDR_GENRE_ROCK; case AT_GENRE_JAZZ: - return CENTSERV_GENRE_JAZZ; + return DIR_ADDR_GENRE_JAZZ; case AT_GENRE_CLASSICAL_FOLK: - return CENTSERV_GENRE_CLASSICAL_FOLK; + return DIR_ADDR_GENRE_CLASSICAL_FOLK; case AT_GENRE_CHORAL: - return CENTSERV_GENRE_CHORAL; + return DIR_ADDR_GENRE_CHORAL; default: return DEFAULT_SERVER_ADDRESS; // AT_DEFAULT } diff --git a/src/util.h b/src/util.h index e83e1f764a..04bf94b0e9 100644 --- a/src/util.h +++ b/src/util.h @@ -36,7 +36,7 @@ # include # include #else -// using mach nanosleep for Linux +// using nanosleep for Linux # include #endif #include @@ -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 }; // Get data status enum -------------------------------------------------------- @@ -582,7 +583,7 @@ enum EDirectoryType AT_NONE = -1, // means not registered, "invalid value" AT_DEFAULT = 0, AT_ANY_GENRE2 = 1, - AT_ANY_GENRE3 = 2, + AT_ANY_GENRE_ASIA = 2, AT_GENRE_ROCK = 3, AT_GENRE_JAZZ = 4, AT_GENRE_CLASSICAL_FOLK = 5, @@ -600,8 +601,8 @@ inline QString DirectoryTypeToString ( EDirectoryType eAddrType ) case AT_ANY_GENRE2: return QCoreApplication::translate ( "CClientSettingsDlg", "Any Genre 2" ); - case AT_ANY_GENRE3: - return QCoreApplication::translate ( "CClientSettingsDlg", "Any Genre 3" ); + case AT_ANY_GENRE_ASIA: + return QCoreApplication::translate ( "CClientSettingsDlg", "Any Genre Asia" ); case AT_GENRE_ROCK: return QCoreApplication::translate ( "CClientSettingsDlg", "Genre Rock" ); @@ -1114,8 +1115,10 @@ class COSUtil return OT_I_OS; #elif defined( Q_OS_ANDROID ) || defined( ANDROID ) return OT_ANDROID; -#else +#elif defined( Q_OS_LINUX ) return OT_LINUX; +#else + return OT_UNIX; #endif } }; diff --git a/windows/deploy_windows.ps1 b/windows/deploy_windows.ps1 index 1d6ddca644..487e02f9fb 100644 --- a/windows/deploy_windows.ps1 +++ b/windows/deploy_windows.ps1 @@ -12,7 +12,7 @@ param ( # The following version pinnings are semi-automatically checked for # updates. Verify .github/workflows/bump-dependencies.yaml when changing those manually: [string] $AsioSDKUrl = "https://download.steinberg.net/sdk_downloads/ASIO-SDK_2.3.4_2025-10-15.zip", - [string] $NsisUrl = "https://downloads.sourceforge.net/project/nsis/NSIS%203/3.11/nsis-3.11.zip", + [string] $NsisUrl = "https://downloads.sourceforge.net/project/nsis/NSIS%203/3.12/nsis-3.12.zip", [string] $BuildOption = "" )