From 8c1deffda77bb045a124e58f8320bd19e196ad1b Mon Sep 17 00:00:00 2001 From: Peter L Jones Date: Wed, 3 Apr 2019 18:12:45 +0100 Subject: [PATCH 01/13] Add recording support with Reaper Project generation Includes the following changes * Initial .gitignore Administrative * Fix up warning message * Not all Windows file systems are case insensitive Bugfixes * (Qt5) Use QCoreApplication for headless Possible solution to get the application to run as a headless server but it loses the nice history graph, so not ideal. * Avoid ESC closing chat Because ESC shouldn't close the chat window. Or the main app window. * Add console logging support for Windows Whilst looking for the headless support, I found this idea for Windows logging. New improved version. This makes far fewer changes. ---- * Add recording support with Reaper Project generation The main feature! * New -r option to enable recording of PCM files and conversion to Reaper RPP with WAV files * New -R option to set the directory in which to create recording sessions You need to specify the -R option, there's no default... so I guess -r and -R could be combined. * New -T option to convert a session directory with PCM files into a Reaper RPP with WAV files You can use -T on "failed" sessions, if the -r option captures the PCMs but the RPP converter doesn't run for some reaon. (It was useful during development, maybe less so once things seem stable.) The recorder is implemented as a new thread with queuing from the main "real time" server thread. When a new client connects or if its audio format changes (e.g. mono to stereo), a new RIFF WAVE file is started. Each frame of decompressed audio for each client written out as LPCM to the file. When the client disconnects, the RIFF WAVE headers are updated to reflect the file length. Once all clients disconnect, the session is considered ended and a Reaper RPP file is written. --- .gitignore | 17 ++ Jamulus.pro | 12 +- src/chatdlg.cpp | 8 + src/chatdlg.h | 2 + src/clientdlg.cpp | 13 + src/clientdlg.h | 3 + src/global.h | 4 + src/main.cpp | 107 +++++++- src/recorder/creaperproject.cpp | 107 ++++++++ src/recorder/creaperproject.h | 70 ++++++ src/recorder/cwavestream.cpp | 121 +++++++++ src/recorder/cwavestream.h | 69 ++++++ src/recorder/jamrecorder.cpp | 418 ++++++++++++++++++++++++++++++++ src/recorder/jamrecorder.h | 156 ++++++++++++ src/server.cpp | 13 + src/server.h | 10 + src/serverdlg.cpp | 8 + src/serverdlg.h | 2 + src/serverlogging.cpp | 16 +- src/util.h | 9 + windows/sound.cpp | 2 +- 21 files changed, 1145 insertions(+), 22 deletions(-) create mode 100644 .gitignore create mode 100755 src/recorder/creaperproject.cpp create mode 100755 src/recorder/creaperproject.h create mode 100755 src/recorder/cwavestream.cpp create mode 100755 src/recorder/cwavestream.h create mode 100755 src/recorder/jamrecorder.cpp create mode 100755 src/recorder/jamrecorder.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..affbd25d --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +.qmake.stash +Jamulus +Makefile +*.pro.user* +**.cppe +**.he +.cproject +.project +.settings +*.user +*.user.* +*.o +moc_*.cpp +ui_*.h +moc_predefs.h +src/res/qrc_resources.cpp +windows/ASIOSDK2 diff --git a/Jamulus.pro b/Jamulus.pro index eb4d1382..83cb9984 100755 --- a/Jamulus.pro +++ b/Jamulus.pro @@ -113,7 +113,7 @@ win32 { !exists(/usr/include/jack/jack.h) { !exists(/usr/local/include/jack/jack.h) { - message(Warning: jack.h was not found at the usual place, maybe the jack dev packet is missing) + message("Warning: jack.h was not found at the usual place, maybe the jack dev packet is missing") } } @@ -170,7 +170,10 @@ HEADERS += src/audiomixerboard.h \ src/soundbase.h \ src/testbench.h \ src/util.h \ - src/analyzerconsole.h + src/analyzerconsole.h \ + src/recorder/jamrecorder.h \ + src/recorder/creaperproject.h \ + src/recorder/cwavestream.h HEADERS_OPUS = libs/opus/include/opus.h \ libs/opus/include/opus_multistream.h \ @@ -264,7 +267,10 @@ SOURCES += src/audiomixerboard.cpp \ src/socket.cpp \ src/soundbase.cpp \ src/util.cpp \ - src/analyzerconsole.cpp + src/analyzerconsole.cpp \ + src/recorder/jamrecorder.cpp \ + src/recorder/creaperproject.cpp \ + src/recorder/cwavestream.cpp SOURCES_OPUS = libs/opus/src/opus.c \ libs/opus/src/opus_decoder.c \ diff --git a/src/chatdlg.cpp b/src/chatdlg.cpp index 270175b4..90b13e1e 100755 --- a/src/chatdlg.cpp +++ b/src/chatdlg.cpp @@ -102,3 +102,11 @@ void CChatDlg::AddChatText ( QString strChatText ) #endif ); } + +void CChatDlg::keyPressEvent(QKeyEvent *e) +{ + if (e->key() != Qt::Key_Escape) + { + QDialog::keyPressEvent(e); + } +} diff --git a/src/chatdlg.h b/src/chatdlg.h index 0a480399..cafbcdfd 100755 --- a/src/chatdlg.h +++ b/src/chatdlg.h @@ -48,6 +48,8 @@ public slots: void OnLocalInputTextTextChanged ( const QString& strNewText ); void OnClearPressed(); + void keyPressEvent(QKeyEvent *e); + signals: void NewLocalInputText ( QString strNewText ); }; diff --git a/src/clientdlg.cpp b/src/clientdlg.cpp index 8a519ed5..4decac1e 100755 --- a/src/clientdlg.cpp +++ b/src/clientdlg.cpp @@ -1200,3 +1200,16 @@ rbtReverbSelR->setStyleSheet ( "" ); // also apply GUI design to child GUI controls MainMixerBoard->SetGUIDesign ( eNewDesign ); } + +void CClientDlg::accept() +{ + close(); +} + +void CClientDlg::keyPressEvent(QKeyEvent *e) +{ + if (e->key() != Qt::Key_Escape) + { + QDialog::keyPressEvent(e); + } +} diff --git a/src/clientdlg.h b/src/clientdlg.h index d53fd003..2c91925c 100755 --- a/src/clientdlg.h +++ b/src/clientdlg.h @@ -205,4 +205,7 @@ public slots: void OnAudioChannelsChanged() { UpdateRevSelection(); } void OnNumClientsChanged ( int iNewNumClients ); void OnNewClientLevelChanged() { MainMixerBoard->iNewClientFaderLevel = pClient->iNewClientFaderLevel; } + + void accept(); + void keyPressEvent(QKeyEvent *e); }; diff --git a/src/global.h b/src/global.h index dad51ece..1d2c2beb 100755 --- a/src/global.h +++ b/src/global.h @@ -50,6 +50,10 @@ LED bar: lbr #if !defined ( GLOBAL_H__3B123453_4344_BB2B_23E7A0D31912__INCLUDED_ ) #define GLOBAL_H__3B123453_4344_BB2B_23E7A0D31912__INCLUDED_ +#if _WIN32 +#define _CRT_SECURE_NO_WARNINGS +#endif + #include #include #include diff --git a/src/main.cpp b/src/main.cpp index 68114b3f..4b7fc354 100755 --- a/src/main.cpp +++ b/src/main.cpp @@ -33,17 +33,41 @@ #include "testbench.h" + +/******************************************************************************\ +* Console logging * +\******************************************************************************/ +// Try for a portable console -------------------------------------------------- + +QTextStream* ConsoleWriterFactory::get() +{ + if (ptsConsole == nullptr) + { +#if _WIN32 + if (!AttachConsole(ATTACH_PARENT_PROCESS)) + { + // Not run from console, dump logging to nowhere + static QString conout; + ptsConsole = new QTextStream ( &conout ); + } + else + { + freopen("CONOUT$", "w", stdout); + ptsConsole = new QTextStream ( stdout ); + } +#else + ptsConsole = new QTextStream ( stdout ); +#endif + } + return ptsConsole; +} + // Implementation ************************************************************** int main ( int argc, char** argv ) { -#ifdef _WIN32 - // no console on windows -> just write in string and dump it - QString strDummySink; - QTextStream tsConsole ( &strDummySink ); -#else - QTextStream tsConsole ( stdout ); -#endif + QTextStream& tsConsole = *((new ConsoleWriterFactory())->get()); + QString strArgument; double rDbleArgument; @@ -56,6 +80,7 @@ int main ( int argc, char** argv ) bool bDisconnectAllClients = false; bool bShowAnalyzerConsole = false; bool bCentServPingServerInList = false; + bool bEnableRecording = false; int iNumServerChannels = DEFAULT_USED_NUM_CHANNELS; int iCtrlMIDIChannel = INVALID_MIDI_CH; quint16 iPortNumber = LLCON_DEFAULT_PORT_NUMBER; @@ -66,6 +91,8 @@ int main ( int argc, char** argv ) QString strServerName = ""; QString strLoggingFileName = ""; QString strHistoryFileName = ""; + QString strRecordingDirName = ""; + QString strSessionDirName = ""; QString strCentralServer = ""; QString strServerInfo = ""; QString strWelcomeMessage = ""; @@ -157,6 +184,18 @@ int main ( int argc, char** argv ) } + // Enable recording at the server -------------------------------------- + if ( GetFlagArgument ( argv, + i, + "-r", + "--enablerecording" ) ) + { + bEnableRecording = true; + tsConsole << "- enabling recording" << endl; + continue; + } + + // Show all registered servers in the server list ---------------------- // Undocumented debugging command line argument: Show all registered // servers in the server list regardless if a ping to the server is @@ -292,6 +331,37 @@ int main ( int argc, char** argv ) } + // Recording directory ------------------------------------------------- + if ( GetStringArgument ( tsConsole, + argc, + argv, + i, + "-R", + "--recordingdirectory", + strArgument ) ) + { + strRecordingDirName = strArgument; + tsConsole << "- recording directory name: " << strRecordingDirName << endl; + continue; + } + + + // Convert a recording session to a Reaper Project --------------------- + if ( GetStringArgument ( tsConsole, + argc, + argv, + i, + "-T", + "--toreaper", + strArgument ) ) + { + bUseGUI = false; + strSessionDirName = strArgument; + tsConsole << "- convert " << strSessionDirName << " to Reaper project (no GUI)" << endl; + continue; + } + + // Central server ------------------------------------------------------ if ( GetStringArgument ( tsConsole, argc, @@ -402,7 +472,15 @@ int main ( int argc, char** argv ) // Application/GUI setup --------------------------------------------------- // Application object - QApplication app ( argc, argv, bUseGUI ); + if (!bUseGUI && !strHistoryFileName.isEmpty()) + { + tsConsole << "Qt5 requires a windowing system to paint a JPEG image; disabling history graph" << endl; + strHistoryFileName = ""; + } + QCoreApplication* _app = bUseGUI + ? new QApplication ( argc, argv ) + : new QCoreApplication ( argc, argv ); +#define app (*_app) #ifdef _WIN32 // set application priority class -> high priority @@ -425,7 +503,11 @@ int main ( int argc, char** argv ) try { - if ( bIsClient ) + if ( !strSessionDirName.isEmpty() ) + { + CJamRecorder::SessionDirToReaper(strSessionDirName); + } + else if ( bIsClient ) { // Client: // actual client object @@ -473,10 +555,11 @@ int main ( int argc, char** argv ) strCentralServer, strServerInfo, strWelcomeMessage, + strRecordingDirName, + bEnableRecording, bCentServPingServerInList, bDisconnectAllClients, eLicenceType ); - if ( bUseGUI ) { // special case for the GUI mode: as the default we want to use @@ -571,7 +654,11 @@ QString UsageArguments ( char **argv ) " [server1 country as QLocale ID]; ...\n" " [server2 address]; ... (server only)\n" " -p, --port local port number (server only)\n" + " -r --enablerecording create recordings of jam sessions (server only)\n" + " -R, --recordingdirectory\n" + " directory to contain recorded jams (server only)\n" " -s, --server start server\n" + " -T, --toreaper create Reaper project from session in named directory\n" " -u, --numchannels maximum number of channels (server only)\n" " -w, --welcomemessage welcome message on connect (server only)\n" " -y, --history enable connection history and set file\n" diff --git a/src/recorder/creaperproject.cpp b/src/recorder/creaperproject.cpp new file mode 100755 index 00000000..9800c15f --- /dev/null +++ b/src/recorder/creaperproject.cpp @@ -0,0 +1,107 @@ +#include "creaperproject.h" + +/** + * @brief operator << Write details of the STrackItem to the QTextStream + * @param os the QTextStream + * @param trackItem the STrackItem + * @return the QTextStream + * + * Note: unused? + */ +QTextStream& operator<<(QTextStream& os, const recorder::STrackItem& trackItem) +{ + os << "_track( " + << "numAudioChannels(" << trackItem.numAudioChannels << ")" + << ", startFrame(" << trackItem.startFrame << ")" + << ", frameCount(" << trackItem.frameCount << ")" + << ", fileName(" << trackItem.fileName << ")" + << " );"; + return os; +} + +/******************************************************************************\ +* recorder methods * +\******************************************************************************/ +using namespace recorder; + +// Reaper Project writer ------------------------------------------------------- + +/** + * @brief CReaperItem::CReaperItem Construct a Reaper RPP "" for a given RIFF WAVE file + * @param name the item name + * @param trackItem the details of where the item is in the track, along with the RIFF WAVE filename + * @param iid the sequential item id + */ +CReaperItem::CReaperItem(const QString& name, const STrackItem& trackItem, const qint32& iid) +{ + QString wavName = trackItem.fileName; // assume RPP in same location... + + QTextStream sOut(&out); + + sOut << " " << endl; + + sOut << " >"; + + sOut.flush(); +} + +/** + * @brief CReaperTrack::CReaperTrack Construct a Reaper RPP "" for a given list of track items + * @param name the track name + * @param iid the sequential track id + * @param items the list of items in the track + */ +CReaperTrack::CReaperTrack(QString name, qint32& iid, QList items) +{ + QTextStream sOut(&out); + + sOut << " "; + + sOut.flush(); +} + +/** + * @brief CReaperProject::CReaperProject Construct a Reaper RPP "" for a given list of tracks + * @param tracks the list of tracks + */ +CReaperProject::CReaperProject(QMap> tracks) +{ + QTextStream sOut(&out); + + sOut << ""; + + sOut.flush(); +} diff --git a/src/recorder/creaperproject.h b/src/recorder/creaperproject.h new file mode 100755 index 00000000..6743ecf1 --- /dev/null +++ b/src/recorder/creaperproject.h @@ -0,0 +1,70 @@ +#ifndef CREAPERPROJECT_H +#define CREAPERPROJECT_H + +#include +#include +#include +#include "util.h" + +#include "cwavestream.h" + +namespace recorder { + +struct STrackItem +{ + STrackItem(int numAudioChannels, qint64 startFrame, qint64 frameCount, QString fileName) : + numAudioChannels(numAudioChannels), + startFrame(startFrame), + frameCount(frameCount), + fileName(fileName) + { + } + int numAudioChannels; + qint64 startFrame; + qint64 frameCount; + QString fileName; +}; + +class CReaperItem : public QObject +{ + Q_OBJECT + +public: + CReaperItem(const QString& name, const STrackItem& trackItem, const qint32& iid); + QString toString() { return out; } + +private: + const QUuid iguid = QUuid::createUuid(); + const QUuid guid = QUuid::createUuid(); + QString out; + + inline QString secondsAt48K(const qint64 frames) { return QString::number(static_cast(frames * SYSTEM_FRAME_SIZE_SAMPLES) / 48000, 'f', 14); } +}; + +class CReaperTrack : public QObject +{ + Q_OBJECT + +public: + CReaperTrack(QString name, qint32 &iid, QList items); + QString toString() { return out; } + +private: + QUuid trackId = QUuid::createUuid(); + QString out; +}; + +class CReaperProject : public QObject +{ + Q_OBJECT + +public: + CReaperProject(QMap > tracks); + QString toString() { return out; } + +private: + QString out; +}; + +} +#endif // CREAPERPROJECT_H diff --git a/src/recorder/cwavestream.cpp b/src/recorder/cwavestream.cpp new file mode 100755 index 00000000..5fc11912 --- /dev/null +++ b/src/recorder/cwavestream.cpp @@ -0,0 +1,121 @@ +#include "cwavestream.h" + +/******************************************************************************\ +* Overrides in global namespace * +\******************************************************************************/ + +/** + * @brief operator << Emit a hdr_riff object to a QDataStream + * @param os a QDataStream + * @param obj a hdr_riff object + * @return the QDataStream passed + */ +recorder::CWaveStream& operator<<(recorder::CWaveStream& os, const recorder::HdrRiff& obj) +{ + (QDataStream&)os << obj.chunkId << obj.chunkSize << obj.format; + return os; +} + +/** + * @brief operator << Emit a fmtSubChunk object to a QDataStream + * @param os a QDataStream + * @param obj a fmtSubChunk object + * @return the QDataStream passed + */ +recorder::CWaveStream& operator<<(recorder::CWaveStream& os, const recorder::FmtSubChunk& obj) +{ + (QDataStream&)os << obj.chunkId + << obj.chunkSize + << obj.audioFormat + << obj.numChannels + << obj.sampleRate + << obj.byteRate + << obj.blockAlign + << obj.bitsPerSample + ; + return os; +} + +/** + * @brief operator << Emit a dataSubChunkHdr object to a QDataStream + * @param os a QDataStream + * @param obj a dataSubChunkHdr object + * @return the QDataStream passed + */ +recorder::CWaveStream& operator<<(recorder::CWaveStream& os, const recorder::DataSubChunkHdr& obj) +{ + (QDataStream&)os << obj.chunkId << obj.chunkSize; + return os; +} + +/******************************************************************************\ +* Implementations of recorder.CWaveStream methods * +\******************************************************************************/ + +using namespace recorder; + +CWaveStream::CWaveStream(const uint16_t numChannels) : + QDataStream(), + numChannels (numChannels), + initialPos (device()->pos()), + initialByteOrder (byteOrder()) +{ + waveStreamHeaders(); +} +CWaveStream::CWaveStream(QIODevice *iod, const uint16_t numChannels) : + QDataStream(iod), + numChannels (numChannels), + initialPos (device()->pos()), + initialByteOrder (byteOrder()) +{ + waveStreamHeaders(); +} +CWaveStream::CWaveStream(QByteArray *iod, QIODevice::OpenMode flags, const uint16_t numChannels) : + QDataStream(iod, flags), + numChannels (numChannels), + initialPos (device()->pos()), + initialByteOrder (byteOrder()) +{ + waveStreamHeaders(); +} +CWaveStream::CWaveStream(const QByteArray &ba, const uint16_t numChannels) : + QDataStream(ba), + numChannels (numChannels), + initialPos (device()->pos()), + initialByteOrder (byteOrder()) +{ + waveStreamHeaders(); +} + +void CWaveStream::waveStreamHeaders() +{ + static const HdrRiff scHdrRiff; + const FmtSubChunk cFmtSubChunk (numChannels); + static const DataSubChunkHdr scDataSubChunkHdr; + + setByteOrder(LittleEndian); + *this << scHdrRiff << cFmtSubChunk << scDataSubChunkHdr; +} + +CWaveStream::~CWaveStream() +{ + static const uint32_t hdrRiffChunkSizeOffset = sizeof(uint32_t); + static const uint32_t dataSubChunkHdrChunkSizeOffset = sizeof(HdrRiff) + sizeof(FmtSubChunk) + sizeof (uint32_t) + sizeof (uint32_t); + + const int64_t currentPos = this->device()->pos(); + const uint32_t fileLength = static_cast(currentPos - initialPos); + + QDataStream& out = static_cast(*this); + + // Overwrite hdr_riff.chunkSize + this->device()->seek(initialPos + hdrRiffChunkSizeOffset); + out << static_cast(fileLength - (hdrRiffChunkSizeOffset + sizeof (uint32_t))); + + // Overwrite dataSubChunkHdr.chunkSize + this->device()->seek(initialPos + dataSubChunkHdrChunkSizeOffset); + out << static_cast(fileLength - (dataSubChunkHdrChunkSizeOffset + sizeof (uint32_t))); + + // and then restore the position and byte order + this->device()->seek(currentPos); + setByteOrder(initialByteOrder); +} diff --git a/src/recorder/cwavestream.h b/src/recorder/cwavestream.h new file mode 100755 index 00000000..64413ec9 --- /dev/null +++ b/src/recorder/cwavestream.h @@ -0,0 +1,69 @@ +#ifndef CWAVESTREAM_H +#define CWAVESTREAM_H + +#include + +namespace recorder { + +class HdrRiff +{ +public: + HdrRiff() {} + + static const uint32_t chunkId = 0x46464952; // RIFF + static const uint32_t chunkSize = 0xffffffff; // (will be overwritten) Size of file in bytes - 8 = size of data + 36 + static const uint32_t format = 0x45564157; // WAVE +}; + +class FmtSubChunk +{ +public: + FmtSubChunk(const uint16_t _numChannels) : + numChannels (_numChannels) + , byteRate (sampleRate * numChannels * bitsPerSample/8) + , blockAlign (numChannels * bitsPerSample/8) + { + } + static const uint32_t chunkId = 0x20746d66; // "fmt " + static const uint32_t chunkSize = 16; // bytes in fmtSubChunk after chunkSize + static const uint16_t audioFormat = 1; // PCM + const uint16_t numChannels; // 1 for mono, 2 for joy... uh, stereo + static const uint32_t sampleRate = 48000; // because it's Jamulus + const uint32_t byteRate; // sampleRate * numChannels * bitsPerSample/8 + const uint16_t blockAlign; // numChannels * bitsPerSample/8 + static const uint16_t bitsPerSample = 16; +}; + +class DataSubChunkHdr +{ +public: + DataSubChunkHdr() {} + + static const uint32_t chunkId = 0x61746164; // "data" + static const uint32_t chunkSize = 0xffffffff; // (will be overwritten) Size of data +}; + +class CWaveStream : public QDataStream +{ +public: + CWaveStream(const uint16_t numChannels); + explicit CWaveStream(QIODevice *iod, const uint16_t numChannels); + CWaveStream(QByteArray *iod, QIODevice::OpenMode flags, const uint16_t numChannels); + CWaveStream(const QByteArray &ba, const uint16_t numChannels); + ~CWaveStream(); + +private: + void waveStreamHeaders(); + + const uint16_t numChannels; + const int64_t initialPos; + const ByteOrder initialByteOrder; +}; + +} + +recorder::CWaveStream& operator<<(recorder::CWaveStream& out, recorder::HdrRiff& hdrRiff); +recorder::CWaveStream& operator<<(recorder::CWaveStream& out, recorder::FmtSubChunk& fmtSubChunk); +recorder::CWaveStream& operator<<(recorder::CWaveStream& out, recorder::DataSubChunkHdr& dataSubChunkHdr); + +#endif // CWAVESTREAM_H diff --git a/src/recorder/jamrecorder.cpp b/src/recorder/jamrecorder.cpp new file mode 100755 index 00000000..43329431 --- /dev/null +++ b/src/recorder/jamrecorder.cpp @@ -0,0 +1,418 @@ +#include "jamrecorder.h" + +using namespace recorder; + +/* ******************************************************************************************************** + * CJamClient + * ********************************************************************************************************/ + +/** + * @brief CJamClient::CJamClient + * @param frame Start frame of the client within the session + * @param numChannels 1 for mono, 2 for stereo + * @param name The client's current name + * @param address IP and Port + * @param recordBaseDir Session recording directory + * + * Creates a file for the raw PCM data and sets up a QDataStream to which to write received frames. + * The data is stored Little Endian. + */ +CJamClient::CJamClient(const qint64 frame, const int _numChannels, const QString name, const CHostAddress address, const QDir recordBaseDir) : + startFrame (frame), + numChannels (static_cast(_numChannels)), + name (name), + address (address) +{ + // At this point we may not have much of a name + QString fileName = ClientName() + "-" + QString::number(frame) + "-" + QString::number(_numChannels); + QString affix = ""; + while (recordBaseDir.exists(fileName + affix + ".wav")) + { + affix = affix.length() == 0 ? "_1" : "_" + QString::number(affix.remove(0, 1).toInt() + 1); + } + fileName = fileName + affix + ".wav"; + + wavFile = new QFile(recordBaseDir.absoluteFilePath(fileName)); + if (!wavFile->open(QFile::OpenMode(QIODevice::OpenModeFlag::ReadWrite))) // need to allow rewriting headers + { + throw new std::runtime_error( ("Could not write to WAV file " + wavFile->fileName()).toStdString() ); + } + out = new CWaveStream(wavFile, numChannels); + + filename = wavFile->fileName(); +} + +/** + * @brief CJamClient::Frame Handle a frame of PCM data from a client connected to the server + * @param _name The client's current name + * @param pcm The PCM data + */ +void CJamClient::Frame(const QString _name, const CVector& pcm) +{ + name = _name; + + for(int i = 0; i < numChannels * SYSTEM_FRAME_SIZE_SAMPLES; i++) + { + *out << pcm[i]; + } + + frameCount++; +} + +/** + * @brief CJamClient::Disconnect Clean up after a disconnected client + */ +void CJamClient::Disconnect() +{ + delete out; + out = nullptr; + + wavFile->close(); + + delete wavFile; + wavFile = nullptr; +} + +/* ******************************************************************************************************** + * CJamSession + * ********************************************************************************************************/ + +/** + * @brief CJamSession::CJamSession Construct a new jam recording session + * @param recordBaseDir The recording base directory + * + * Each session is stored into its own subdirectory of the recording base directory. + */ +CJamSession::CJamSession(QDir recordBaseDir) : + sessionDir (QDir(recordBaseDir.absoluteFilePath("Jam-" + QDateTime().currentDateTimeUtc().toString("yyyyMMdd-HHmmsszzz")))), + currentFrame (0), + vecptrJamClients (MAX_NUM_CHANNELS), + jamClientConnections() +{ + const QFileInfo fi(sessionDir.absolutePath()); + + if (!fi.exists() && !QDir().mkpath(sessionDir.absolutePath())) + { + throw std::runtime_error( (sessionDir.absolutePath() + " does not exist but could not be created").toStdString() ); + } + if (!fi.isDir()) + { + throw std::runtime_error( (sessionDir.absolutePath() + " exists but is not a directory").toStdString() ); + } + if (!fi.isWritable()) + { + throw std::runtime_error( (sessionDir.absolutePath() + " is a directory but cannot be written to").toStdString() ); + } + + // Explicitly set all the pointers to "empty" + vecptrJamClients.fill(nullptr); + + currentFrame = 0; +} + +/** + * @brief CJamSession::DisconnectClient Capture details of the departing client's connection + * @param iChID the channel id of the client that disconnected + */ +void CJamSession::DisconnectClient(int iChID) +{ + vecptrJamClients[iChID]->Disconnect(); + + jamClientConnections.append(new CJamClientConnection(vecptrJamClients[iChID]->NumAudioChannels(), + vecptrJamClients[iChID]->StartFrame(), + vecptrJamClients[iChID]->FrameCount(), + vecptrJamClients[iChID]->ClientName(), + vecptrJamClients[iChID]->FileName())); + + delete vecptrJamClients[iChID]; + vecptrJamClients[iChID] = nullptr; +} + +/** + * @brief CJamSession::Frame Process a frame emited for a client by the server + * @param iChID the client channel id + * @param name the client name + * @param address the client IP and port number + * @param numAudioChannels the client number of audio channels + * @param data the frame data + * + * Manages changes that affect how the recording is stored - i.e. if the number of audio channels changes, we need a new file. + * Files are grouped by IP and port number, so if either of those change for a connection, we also start a new file. + * + * Also manages the overall current frame counter for the session. + */ +void CJamSession::Frame(const int iChID, const QString name, const CHostAddress address, const int numAudioChannels, const CVector data) +{ + if (vecptrJamClients[iChID] == nullptr) + { + // then we have not seen this client this session + vecptrJamClients[iChID] = new CJamClient(currentFrame, numAudioChannels, name, address, sessionDir); + } + else if (numAudioChannels != vecptrJamClients[iChID]->NumAudioChannels() + || address.InetAddr != vecptrJamClients[iChID]->ClientAddress().InetAddr + || address.iPort != vecptrJamClients[iChID]->ClientAddress().iPort) + { + DisconnectClient(iChID); + if (numAudioChannels == 0) + { + vecptrJamClients[iChID] = nullptr; + } + else + { + vecptrJamClients[iChID] = new CJamClient(currentFrame, numAudioChannels, name, address, sessionDir); + } + } + + if (vecptrJamClients[iChID] == nullptr) + { + // Frame allegedly from iChID but unable to establish client details + return; + } + + vecptrJamClients[iChID]->Frame(name, data); + + // If _any_ connected client frame steps past currentFrame, increase currentFrame + if (vecptrJamClients[iChID]->StartFrame() + vecptrJamClients[iChID]->FrameCount() > currentFrame) + { + currentFrame++; + } +} + +/** + * @brief CJamSession::End Clean up any "hanging" clients when the server thinks they all left + */ +void CJamSession::End() +{ + for (int iChID = 0; iChID < vecptrJamClients.size(); iChID++) + { + if (vecptrJamClients[iChID] != nullptr) + { + DisconnectClient(iChID); + vecptrJamClients[iChID] = nullptr; + } + } +} + +/** + * @brief CJamSession::Tracks Retrieve a map of (latest) client name to connection items + * @return a map of (latest) client name to connection items + */ +QMap> CJamSession::Tracks() +{ + QMap> tracks; + + for (int i = 0; i < jamClientConnections.count(); i++ ) + { + STrackItem track ( + jamClientConnections[i]->Format(), + jamClientConnections[i]->StartFrame(), + jamClientConnections[i]->Length(), + jamClientConnections[i]->FileName() + ); + + if (!tracks.contains(jamClientConnections[i]->Name())) + { + tracks.insert(jamClientConnections[i]->Name(), { }); + } + + tracks[jamClientConnections[i]->Name()].append(track); + } + + return tracks; +} + +/** + * @brief CJamSession::TracksFromSessionDir Replica of CJamSession::Tracks but using the directory contents to construct the track item map + * @param sessionDirName the directory name to scan + * @return a map of (latest) client name to connection items + */ +QMap> CJamSession::TracksFromSessionDir(const QString& sessionDirName) +{ + QMap> tracks; + + const QDir sessionDir(sessionDirName); + foreach(auto entry, sessionDir.entryList({ "*.pcm" })) + { + + auto split = entry.split(".")[0].split("-"); + QString name = split[0]; + QString hostPort = split[1]; + QString frame = split[2]; + QString tail = split[3]; //numChannels may have _nnn + QString numChannels = tail.count("_") > 0 ? tail.split("_")[0] : tail; + + QString trackName = name + "-" + hostPort; + if (!tracks.contains(trackName)) + { + tracks.insert(trackName, { }); + } + + QFileInfo fiEntry(sessionDir.absoluteFilePath(entry)); + qint64 length = fiEntry.size() / numChannels.toInt() / SYSTEM_FRAME_SIZE_SAMPLES; + + STrackItem track ( + numChannels.toInt(), + frame.toLongLong(), + length, + sessionDir.absoluteFilePath(entry) + ); + + tracks[trackName].append(track); + } + + return tracks; +} + +/* ******************************************************************************************************** + * CJamRecorder + * ********************************************************************************************************/ + +/** + * @brief CJamRecorder::CJamRecorder Create recording directory, if necessary, and connect signal handlers + * @param server Server object emiting signals + * @param recordingDirName Requested recording directory name + */ +CJamRecorder::CJamRecorder(const CServer* server, const QString recordingDirName) : + recordBaseDir (recordingDirName), + isRecording (false) +{ + const QFileInfo fi(recordBaseDir.absolutePath()); + + if (!fi.exists() && !QDir().mkpath(recordBaseDir.absolutePath())) + { + throw std::runtime_error( (recordBaseDir.absolutePath() + " does not exist but could not be created").toStdString() ); + } + if (!fi.isDir()) + { + throw std::runtime_error( (recordBaseDir.absolutePath() + " exists but is not a directory").toStdString() ); + } + if (!fi.isWritable()) + { + throw std::runtime_error( (recordBaseDir.absolutePath() + " is a directory but cannot be written to").toStdString() ); + } + + QObject::connect((const QObject *)server, SIGNAL ( Stopped() ), + this, SLOT( OnEnd() ), + Qt::ConnectionType::QueuedConnection); + + QObject::connect((const QObject *)server, SIGNAL ( ClientDisconnected(int) ), + this, SLOT( OnDisconnected(int) ), + Qt::ConnectionType::QueuedConnection); + + qRegisterMetaType>(); + QObject::connect((const QObject *)server, SIGNAL ( Frame(const int, const QString, const CHostAddress, const int, const CVector) ), + this, SLOT( OnFrame(const int, const QString, const CHostAddress, const int, const CVector) ), + Qt::ConnectionType::QueuedConnection); +} + +/** + * @brief CJamRecorder::OnStart Start up tasks when the first client connects + */ +void CJamRecorder::OnStart() { + // Ensure any previous cleaning up has been done. + OnEnd(); + + currentSession = new CJamSession(recordBaseDir); + isRecording = true; +} + +/** + * @brief CJamRecorder::OnEnd Finalise the recording and emit the Reaper RPP file + */ +void CJamRecorder::OnEnd() +{ + if (isRecording) + { + isRecording = false; + currentSession->End(); + + QString reaperProjectFileName = currentSession->SessionDir().filePath(currentSession->Name().append(".rpp")); + const QFileInfo fi(reaperProjectFileName); + + if (fi.exists()) + { + tsConsole << "CJamRecorder::OnEnd() - " << fi.absolutePath() << " exists and will not be overwritten." << endl; + } + else + { + QFile outf (reaperProjectFileName); + outf.open(QFile::WriteOnly); + QTextStream out(&outf); + out << CReaperProject(currentSession->Tracks()).toString() << endl; + tsConsole << "Session RPP: " << reaperProjectFileName << endl; + } + + delete currentSession; + currentSession = nullptr; + } +} + +/** + * @brief CJamRecorder::SessionDirToReaper Replica of CJamRecorder::OnEnd() but using the directory contents to construct the CReaperProject object + * @param strSessionDirName + */ +void CJamRecorder::SessionDirToReaper(QString& strSessionDirName) +{ + const QFileInfo fiSessionDir(QDir::cleanPath(strSessionDirName)); + if (!fiSessionDir.exists() || !fiSessionDir.isDir()) + { + throw std::runtime_error( (fiSessionDir.absoluteFilePath() + " does not exist or is not a directory. Aborting.").toStdString() ); + } + + const QDir dSessionDir(fiSessionDir.absoluteFilePath()); + const QString reaperProjectFileName = dSessionDir.absoluteFilePath(fiSessionDir.baseName().append(".rpp")); + const QFileInfo fiRPP(reaperProjectFileName); + if (fiRPP.exists()) + { + throw std::runtime_error( (fiRPP.absoluteFilePath() + " exists and will not be overwritten. Aborting.").toStdString() ); + } + + QFile outf (fiRPP.absoluteFilePath()); + if (!outf.open(QFile::WriteOnly)) { + throw std::runtime_error( (fiRPP.absoluteFilePath() + " could not be written. Aborting.").toStdString() ); + } + QTextStream out(&outf); + + out << CReaperProject(CJamSession::TracksFromSessionDir(fiSessionDir.absoluteFilePath())).toString() << endl; + + (*(new ConsoleWriterFactory())->get()) << "Session RPP: " << reaperProjectFileName << endl; +} + +/** + * @brief CJamRecorder::OnDisconnected Handle disconnection of a client + * @param iChID the client channel id + */ +void CJamRecorder::OnDisconnected(int iChID) +{ + if (!isRecording) + { + tsConsole << "CJamRecorder::OnDisconnected: channel " << iChID << " disconnected but not recording" << endl; + } + if (currentSession == nullptr) + { + tsConsole << "CJamRecorder::OnDisconnected: channel " << iChID << " disconnected but no currentSession" << endl; + return; + } + currentSession->DisconnectClient(iChID); +} + +/** + * @brief CJamRecorder::OnFrame Handle a frame emited for a client by the server + * @param iChID the client channel id + * @param name the client name + * @param address the client IP and port number + * @param numAudioChannels the client number of audio channels + * @param data the frame data + * + * Ensures recording has started. + */ +void CJamRecorder::OnFrame(const int iChID, const QString name, const CHostAddress address, const int numAudioChannels, const CVector data) +{ + // Make sure we are ready + if (!isRecording) + { + OnStart(); + } + + currentSession->Frame(iChID, name, address, numAudioChannels, data); +} diff --git a/src/recorder/jamrecorder.h b/src/recorder/jamrecorder.h new file mode 100755 index 00000000..66274310 --- /dev/null +++ b/src/recorder/jamrecorder.h @@ -0,0 +1,156 @@ +#ifndef JAMRECORDER_H +#define JAMRECORDER_H + +#include +#include +#include + +#include "../util.h" +#include "../channel.h" + +#include "creaperproject.h" +#include "cwavestream.h" + +namespace recorder { + +class CJamClientConnection : public QObject +{ + Q_OBJECT + +public: + CJamClientConnection(const int _numAudioChannels, const qint64 _startFrame, const qint64 _length, const QString _name, const QString _fileName) : + numAudioChannels (_numAudioChannels), + startFrame (_startFrame), + length (_length), + name (_name), + fileName (_fileName) + { + } + + int Format() { return numAudioChannels; } + qint64 StartFrame() { return startFrame; } + qint64 Length() { return length; } + QString Name() { return name; } + QString FileName() { return fileName; } + +private: + const int numAudioChannels; + const qint64 startFrame; + const qint64 length; + const QString name; + const QString fileName; +}; + +class CJamClient : public QObject +{ + Q_OBJECT + +public: + CJamClient(const qint64 frame, const int numChannels, const QString name, const CHostAddress address, const QDir recordBaseDir); + + void Frame(const QString name, const CVector& pcm); + + void Disconnect(); + + qint64 StartFrame() { return startFrame; } + qint64 FrameCount() { return frameCount; } + uint16_t NumAudioChannels() { return numChannels; } + QString ClientName() { return name.leftJustified(4, '_', false).replace(QRegExp("[-.:/\\ ]"), "_") + .append("-") + .append(address.toString(CHostAddress::EStringMode::SM_IP_NO_LAST_BYTE_PORT).replace(QRegExp("[-.:/\\ ]"), "_")) + ; + } + CHostAddress ClientAddress() { return address; } + + QString FileName() { return filename; } + +private: + const qint64 startFrame; + const uint16_t numChannels; + QString name; + const CHostAddress address; + + QString filename; + QFile* wavFile; + QDataStream* out; + qint64 frameCount = 0; +}; + +class CJamSession : public QObject +{ + Q_OBJECT + +public: + + CJamSession(QDir recordBaseDir); + + void Frame(const int iChID, const QString name, const CHostAddress address, const int numAudioChannels, const CVector data); + + void End(); + + QVector Clients() { return vecptrJamClients; } + + QMap> Tracks(); + + QString Name() { return sessionDir.dirName(); } + + const QDir SessionDir() { return sessionDir; } + + void DisconnectClient(int iChID); + + static QMap> TracksFromSessionDir(const QString& name); + +private: + CJamSession(); + + const QDir sessionDir; + + qint64 currentFrame; + QVector vecptrJamClients; + QList jamClientConnections; +}; + +class CJamRecorder : public QThread +{ + Q_OBJECT + +public: + CJamRecorder(const CServer* server, const QString recordingDirName); + + static void SessionDirToReaper(QString& strSessionDirName); + +public slots: + /** + * @brief Raised when first client joins the server, triggering a new recording. + */ + void OnStart(); + + /** + * @brief Raised when last client leaves the server, ending the recording. + */ + void OnEnd(); + + /** + * @brief Raised when an existing client leaves the server. + * @param iChID channel number of client + */ + void OnDisconnected(int iChID); + + /** + * @brief Raised when a frame of data fis available to process + */ + void OnFrame(const int iChID, const QString name, const CHostAddress address, const int numAudioChannels, const CVector data); + +private: + QDir recordBaseDir; + + bool isRecording; + CJamSession* currentSession; + QTextStream& tsConsole = *((new ConsoleWriterFactory())->get()); +}; + +} + +Q_DECLARE_METATYPE(int16_t) +Q_DECLARE_METATYPE(CVector) +#endif // JAMRECORDER_H diff --git a/src/server.cpp b/src/server.cpp index 410b4c59..cabc2e3f 100755 --- a/src/server.cpp +++ b/src/server.cpp @@ -206,6 +206,8 @@ CServer::CServer ( const int iNewMaxNumChan, const QString& strCentralServer, const QString& strServerInfo, const QString& strNewWelcomeMessage, + const QString& strRecordingDirName, + const bool bEnableRecording, const bool bNCentServPingServerInList, const bool bNDisconnectAllClients, const ELicenceType eNLicenceType ) : @@ -357,6 +359,13 @@ CServer::CServer ( const int iNewMaxNumChan, QString().number( static_cast ( iPortNumber ) ) ); } + // Enable jam recording (if requested) + if ( bEnableRecording ) + { + JamRecorder = new CJamRecorder(this, strRecordingDirName); + JamRecorder->start(); + } + // enable all channels (for the server all channel must be enabled the // entire life time of the software) for ( i = 0; i < iMaxNumChannels; i++ ) @@ -739,8 +748,10 @@ JitterMeas.Measure(); // if channel was just disconnected, set flag that connected // client list is sent to all other clients + // and emit the client disconnected signal if ( eGetStat == GS_CHAN_NOW_DISCONNECTED ) { + emit ClientDisconnected(iCurChanID); //? do outside mutex lock? bChannelIsNowDisconnected = true; } @@ -825,6 +836,8 @@ JitterMeas.Measure(); // get number of audio channels of current channel const int iCurNumAudChan = vecNumAudioChannels[i]; + emit Frame(iCurChanID, vecChannels[iCurChanID].GetName(), vecChannels[iCurChanID].GetAddress(), iCurNumAudChan, vecvecsData[i]); + // generate a sparate mix for each channel // actual processing of audio data -> mix ProcessData ( vecvecsData, diff --git a/src/server.h b/src/server.h index 349659e8..4ef49a0e 100755 --- a/src/server.h +++ b/src/server.h @@ -41,6 +41,9 @@ #include "serverlogging.h" #include "serverlist.h" +#include "recorder/jamrecorder.h" +using recorder::CJamRecorder; + /* Definitions ****************************************************************/ // no valid channel number @@ -127,6 +130,8 @@ public: const QString& strCentralServer, const QString& strServerInfo, const QString& strNewWelcomeMessage, + const QString& strRecordingDirName, + const bool bEnableRecording, const bool bNCentServPingServerInList, const bool bNDisconnectAllClients, const ELicenceType eNLicenceType ); @@ -247,6 +252,9 @@ protected: // logging CServerLogging Logging; + // recording thread + CJamRecorder* JamRecorder; + // HTML file server status bool bWriteStatusHTMLFile; QString strServerHTMLFileListName; @@ -268,6 +276,8 @@ protected: signals: void Started(); void Stopped(); + void ClientDisconnected(const int iChID); + void Frame(const int, const QString, const CHostAddress, const int, const CVector); public slots: void OnTimer(); diff --git a/src/serverdlg.cpp b/src/serverdlg.cpp index 37177a15..bd1922e7 100755 --- a/src/serverdlg.cpp +++ b/src/serverdlg.cpp @@ -588,3 +588,11 @@ void CServerDlg::changeEvent ( QEvent* pEvent ) } } } + +void CServerDlg::keyPressEvent(QKeyEvent *e) +{ + if (e->key() != Qt::Key_Escape) + { + QDialog::keyPressEvent(e); + } +} diff --git a/src/serverdlg.h b/src/serverdlg.h index bf40d8d4..63901b30 100755 --- a/src/serverdlg.h +++ b/src/serverdlg.h @@ -97,4 +97,6 @@ public slots: void OnSysTrayMenuHide() { hide(); } void OnSysTrayMenuExit() { close(); } void OnSysTrayActivated ( QSystemTrayIcon::ActivationReason ActReason ); + + void keyPressEvent(QKeyEvent *e); }; diff --git a/src/serverlogging.cpp b/src/serverlogging.cpp index d4a3577c..e5d41c44 100755 --- a/src/serverlogging.cpp +++ b/src/serverlogging.cpp @@ -342,10 +342,10 @@ void CServerLogging::AddNewConnection ( const QHostAddress& ClientInetAddr ) const QString strLogStr = CurTimeDatetoLogString() + ", " + ClientInetAddr.toString() + ", connected"; -#ifndef _WIN32 - QTextStream tsConsoleStream ( stdout ); - tsConsoleStream << strLogStr << endl; // on console -#endif + + QTextStream* tsConsoleStream = (new ConsoleWriterFactory())->get(); + (*tsConsoleStream) << strLogStr << endl; // on console + *this << strLogStr; // in log file // add element to history @@ -357,10 +357,10 @@ void CServerLogging::AddServerStopped() const QString strLogStr = CurTimeDatetoLogString() + ",, server stopped " "-------------------------------------"; -#ifndef _WIN32 - QTextStream tsConsoleStream ( stdout ); - tsConsoleStream << strLogStr << endl; // on console -#endif + + QTextStream* tsConsoleStream = (new ConsoleWriterFactory())->get(); + (*tsConsoleStream) << strLogStr << endl; // on console + *this << strLogStr; // in log file // add element to history and update on server stop diff --git a/src/util.h b/src/util.h index c5b79f17..6710e3c3 100755 --- a/src/util.h +++ b/src/util.h @@ -1275,4 +1275,13 @@ protected: bool bPreviousState; }; +class ConsoleWriterFactory +{ +public: + ConsoleWriterFactory() : ptsConsole ( nullptr ) { } + QTextStream* get(); +private: + QTextStream* ptsConsole; +}; + #endif /* !defined ( UTIL_HOIH934256GEKJH98_3_43445KJIUHF1912__INCLUDED_ ) */ diff --git a/windows/sound.cpp b/windows/sound.cpp index 9650f4a5..93eef040 100755 --- a/windows/sound.cpp +++ b/windows/sound.cpp @@ -25,7 +25,7 @@ * \******************************************************************************/ -#include "Sound.h" +#include "sound.h" /* Implementation *************************************************************/ From 0dbef268a3047ac9f8220d9c0acc9dad33f87118 Mon Sep 17 00:00:00 2001 From: Volker Fischer Date: Wed, 10 Apr 2019 20:00:54 +0200 Subject: [PATCH 02/13] added GPL headers --- Jamulus.pro | 3 --- src/recorder/creaperproject.cpp | 23 +++++++++++++++++++++++ src/recorder/creaperproject.h | 23 +++++++++++++++++++++++ src/recorder/cwavestream.cpp | 23 +++++++++++++++++++++++ src/recorder/cwavestream.h | 23 +++++++++++++++++++++++ src/recorder/jamrecorder.cpp | 23 +++++++++++++++++++++++ src/recorder/jamrecorder.h | 23 +++++++++++++++++++++++ 7 files changed, 138 insertions(+), 3 deletions(-) diff --git a/Jamulus.pro b/Jamulus.pro index 83cb9984..a4f21112 100755 --- a/Jamulus.pro +++ b/Jamulus.pro @@ -123,9 +123,6 @@ win32 { LIBS += -ljack } - # enable to following line to support compilation on the Raspberry Pi - #LIBS += -lrt - # Linux is our source distribution, include sources from other OSs DISTFILES += mac/sound.h \ mac/sound.cpp \ diff --git a/src/recorder/creaperproject.cpp b/src/recorder/creaperproject.cpp index 9800c15f..412f61e1 100755 --- a/src/recorder/creaperproject.cpp +++ b/src/recorder/creaperproject.cpp @@ -1,3 +1,26 @@ +/******************************************************************************\ + * + * Author(s): + * pljones + * + ****************************************************************************** + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later + * version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * +\******************************************************************************/ + #include "creaperproject.h" /** diff --git a/src/recorder/creaperproject.h b/src/recorder/creaperproject.h index 6743ecf1..78a6f05e 100755 --- a/src/recorder/creaperproject.h +++ b/src/recorder/creaperproject.h @@ -1,3 +1,26 @@ +/******************************************************************************\ + * + * Author(s): + * pljones + * + ****************************************************************************** + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later + * version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * +\******************************************************************************/ + #ifndef CREAPERPROJECT_H #define CREAPERPROJECT_H diff --git a/src/recorder/cwavestream.cpp b/src/recorder/cwavestream.cpp index 5fc11912..7f6e7437 100755 --- a/src/recorder/cwavestream.cpp +++ b/src/recorder/cwavestream.cpp @@ -1,3 +1,26 @@ +/******************************************************************************\ + * + * Author(s): + * pljones + * + ****************************************************************************** + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later + * version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * +\******************************************************************************/ + #include "cwavestream.h" /******************************************************************************\ diff --git a/src/recorder/cwavestream.h b/src/recorder/cwavestream.h index 64413ec9..7538e173 100755 --- a/src/recorder/cwavestream.h +++ b/src/recorder/cwavestream.h @@ -1,3 +1,26 @@ +/******************************************************************************\ + * + * Author(s): + * pljones + * + ****************************************************************************** + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later + * version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * +\******************************************************************************/ + #ifndef CWAVESTREAM_H #define CWAVESTREAM_H diff --git a/src/recorder/jamrecorder.cpp b/src/recorder/jamrecorder.cpp index 43329431..14696eaa 100755 --- a/src/recorder/jamrecorder.cpp +++ b/src/recorder/jamrecorder.cpp @@ -1,3 +1,26 @@ +/******************************************************************************\ + * + * Author(s): + * pljones + * + ****************************************************************************** + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later + * version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * +\******************************************************************************/ + #include "jamrecorder.h" using namespace recorder; diff --git a/src/recorder/jamrecorder.h b/src/recorder/jamrecorder.h index 66274310..6e35600b 100755 --- a/src/recorder/jamrecorder.h +++ b/src/recorder/jamrecorder.h @@ -1,3 +1,26 @@ +/******************************************************************************\ + * + * Author(s): + * pljones + * + ****************************************************************************** + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later + * version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * +\******************************************************************************/ + #ifndef JAMRECORDER_H #define JAMRECORDER_H From 30b5833d3d943adbd0a0c100b34464538919d5ba Mon Sep 17 00:00:00 2001 From: Volker Fischer Date: Thu, 11 Apr 2019 22:25:36 +0200 Subject: [PATCH 03/13] - remove unnecessary command line argument (if directory is given, the recording is enabled automatically - jamrecorder is not a pointer in the class but an object now - only call jamrecorder functions if it is enabled --- src/main.cpp | 19 ++----------------- src/recorder/jamrecorder.cpp | 9 +++------ src/recorder/jamrecorder.h | 4 +++- src/server.cpp | 23 ++++++++++++++++++----- src/server.h | 12 ++++++++---- 5 files changed, 34 insertions(+), 33 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 07929d09..03e14981 100755 --- a/src/main.cpp +++ b/src/main.cpp @@ -80,7 +80,6 @@ int main ( int argc, char** argv ) bool bDisconnectAllClients = false; bool bShowAnalyzerConsole = false; bool bCentServPingServerInList = false; - bool bEnableRecording = false; int iNumServerChannels = DEFAULT_USED_NUM_CHANNELS; int iCtrlMIDIChannel = INVALID_MIDI_CH; quint16 iPortNumber = LLCON_DEFAULT_PORT_NUMBER; @@ -184,18 +183,6 @@ int main ( int argc, char** argv ) } - // Enable recording at the server -------------------------------------- - if ( GetFlagArgument ( argv, - i, - "-r", - "--enablerecording" ) ) - { - bEnableRecording = true; - tsConsole << "- enabling recording" << endl; - continue; - } - - // Show all registered servers in the server list ---------------------- // Undocumented debugging command line argument: Show all registered // servers in the server list regardless if a ping to the server is @@ -556,7 +543,6 @@ int main ( int argc, char** argv ) strServerInfo, strWelcomeMessage, strRecordingDirName, - bEnableRecording, bCentServPingServerInList, bDisconnectAllClients, eLicenceType ); @@ -654,9 +640,8 @@ QString UsageArguments ( char **argv ) " [server1 country as QLocale ID]; ...\n" " [server2 address]; ... (server only)\n" " -p, --port local port number (server only)\n" - " -r --enablerecording create recordings of jam sessions (server only)\n" - " -R, --recordingdirectory\n" - " directory to contain recorded jams (server only)\n" + " -R, --recording enables recording and sets directory to contain\n" + " recorded jams (server only)\n" " -s, --server start server\n" " -T, --toreaper create Reaper project from session in named directory\n" " -u, --numchannels maximum number of channels (server only)\n" diff --git a/src/recorder/jamrecorder.cpp b/src/recorder/jamrecorder.cpp index 14696eaa..503e147a 100755 --- a/src/recorder/jamrecorder.cpp +++ b/src/recorder/jamrecorder.cpp @@ -291,13 +291,10 @@ QMap> CJamSession::TracksFromSessionDir(const QString * ********************************************************************************************************/ /** - * @brief CJamRecorder::CJamRecorder Create recording directory, if necessary, and connect signal handlers + * @brief CJamRecorder::Init Create recording directory, if necessary, and connect signal handlers * @param server Server object emiting signals - * @param recordingDirName Requested recording directory name */ -CJamRecorder::CJamRecorder(const CServer* server, const QString recordingDirName) : - recordBaseDir (recordingDirName), - isRecording (false) +void CJamRecorder::Init(const CServer* server) { const QFileInfo fi(recordBaseDir.absolutePath()); @@ -323,7 +320,7 @@ CJamRecorder::CJamRecorder(const CServer* server, const QString recordingDirName Qt::ConnectionType::QueuedConnection); qRegisterMetaType>(); - QObject::connect((const QObject *)server, SIGNAL ( Frame(const int, const QString, const CHostAddress, const int, const CVector) ), + QObject::connect((const QObject *)server, SIGNAL ( AudioFrame(const int, const QString, const CHostAddress, const int, const CVector) ), this, SLOT( OnFrame(const int, const QString, const CHostAddress, const int, const CVector) ), Qt::ConnectionType::QueuedConnection); } diff --git a/src/recorder/jamrecorder.h b/src/recorder/jamrecorder.h index 6e35600b..42443509 100755 --- a/src/recorder/jamrecorder.h +++ b/src/recorder/jamrecorder.h @@ -138,7 +138,9 @@ class CJamRecorder : public QThread Q_OBJECT public: - CJamRecorder(const CServer* server, const QString recordingDirName); + CJamRecorder(const QString recordingDirName) : + recordBaseDir (recordingDirName), isRecording (false) {} + void Init(const CServer* server); static void SessionDirToReaper(QString& strSessionDirName); diff --git a/src/server.cpp b/src/server.cpp index ee6ec754..4437d242 100755 --- a/src/server.cpp +++ b/src/server.cpp @@ -207,12 +207,13 @@ CServer::CServer ( const int iNewMaxNumChan, const QString& strServerInfo, const QString& strNewWelcomeMessage, const QString& strRecordingDirName, - const bool bEnableRecording, const bool bNCentServPingServerInList, const bool bNDisconnectAllClients, const ELicenceType eNLicenceType ) : iMaxNumChannels ( iNewMaxNumChan ), Socket ( this, iPortNumber ), + JamRecorder ( strRecordingDirName ), + bEnableRecording ( !strRecordingDirName.isEmpty() ), bWriteStatusHTMLFile ( false ), ServerListManager ( iPortNumber, strCentralServer, @@ -362,8 +363,8 @@ CServer::CServer ( const int iNewMaxNumChan, // Enable jam recording (if requested) if ( bEnableRecording ) { - JamRecorder = new CJamRecorder(this, strRecordingDirName); - JamRecorder->start(); + JamRecorder.Init ( this ); + JamRecorder.start(); } // enable all channels (for the server all channel must be enabled the @@ -751,7 +752,11 @@ JitterMeas.Measure(); // and emit the client disconnected signal if ( eGetStat == GS_CHAN_NOW_DISCONNECTED ) { - emit ClientDisconnected(iCurChanID); //? do outside mutex lock? + if ( bEnableRecording ) + { + emit ClientDisconnected ( iCurChanID ); // TODO do this outside the mutex lock? + } + bChannelIsNowDisconnected = true; } @@ -836,7 +841,15 @@ JitterMeas.Measure(); // get number of audio channels of current channel const int iCurNumAudChan = vecNumAudioChannels[i]; - emit Frame(iCurChanID, vecChannels[iCurChanID].GetName(), vecChannels[iCurChanID].GetAddress(), iCurNumAudChan, vecvecsData[i]); + // export the audio data for recording purpose + if ( bEnableRecording ) + { + emit AudioFrame ( iCurChanID, + vecChannels[iCurChanID].GetName(), + vecChannels[iCurChanID].GetAddress(), + iCurNumAudChan, + vecvecsData[i] ); + } // generate a sparate mix for each channel // actual processing of audio data -> mix diff --git a/src/server.h b/src/server.h index a14cf45c..92308f39 100755 --- a/src/server.h +++ b/src/server.h @@ -131,7 +131,6 @@ public: const QString& strServerInfo, const QString& strNewWelcomeMessage, const QString& strRecordingDirName, - const bool bEnableRecording, const bool bNCentServPingServerInList, const bool bNDisconnectAllClients, const ELicenceType eNLicenceType ); @@ -253,7 +252,8 @@ protected: CServerLogging Logging; // recording thread - CJamRecorder* JamRecorder; + CJamRecorder JamRecorder; + bool bEnableRecording; // HTML file server status bool bWriteStatusHTMLFile; @@ -276,8 +276,12 @@ protected: signals: void Started(); void Stopped(); - void ClientDisconnected(const int iChID); - void Frame(const int, const QString, const CHostAddress, const int, const CVector); + void ClientDisconnected ( const int iChID ); + void AudioFrame ( const int iChID, + const QString stChName, + const CHostAddress RecHostAddr, + const int iNumAudChan, + const CVector vecsData ); public slots: void OnTimer(); From 8367ecc64ffd5f739960a4ae83778619b351fd60 Mon Sep 17 00:00:00 2001 From: Volker Fischer Date: Fri, 12 Apr 2019 18:02:26 +0200 Subject: [PATCH 04/13] remove -T command line argument since it was only intended for debugging (see pull request from pljones). --- src/main.cpp | 30 ++++-------------------------- 1 file changed, 4 insertions(+), 26 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 03e14981..21ead6e7 100755 --- a/src/main.cpp +++ b/src/main.cpp @@ -41,10 +41,10 @@ QTextStream* ConsoleWriterFactory::get() { - if (ptsConsole == nullptr) + if ( ptsConsole == nullptr ) { #if _WIN32 - if (!AttachConsole(ATTACH_PARENT_PROCESS)) + if ( !AttachConsole ( ATTACH_PARENT_PROCESS ) ) { // Not run from console, dump logging to nowhere static QString conout; @@ -52,7 +52,7 @@ QTextStream* ConsoleWriterFactory::get() } else { - freopen("CONOUT$", "w", stdout); + freopen ( "CONOUT$", "w", stdout ); ptsConsole = new QTextStream ( stdout ); } #else @@ -91,7 +91,6 @@ int main ( int argc, char** argv ) QString strLoggingFileName = ""; QString strHistoryFileName = ""; QString strRecordingDirName = ""; - QString strSessionDirName = ""; QString strCentralServer = ""; QString strServerInfo = ""; QString strWelcomeMessage = ""; @@ -333,22 +332,6 @@ int main ( int argc, char** argv ) } - // Convert a recording session to a Reaper Project --------------------- - if ( GetStringArgument ( tsConsole, - argc, - argv, - i, - "-T", - "--toreaper", - strArgument ) ) - { - bUseGUI = false; - strSessionDirName = strArgument; - tsConsole << "- convert " << strSessionDirName << " to Reaper project (no GUI)" << endl; - continue; - } - - // Central server ------------------------------------------------------ if ( GetStringArgument ( tsConsole, argc, @@ -490,11 +473,7 @@ int main ( int argc, char** argv ) try { - if ( !strSessionDirName.isEmpty() ) - { - CJamRecorder::SessionDirToReaper(strSessionDirName); - } - else if ( bIsClient ) + if ( bIsClient ) { // Client: // actual client object @@ -643,7 +622,6 @@ QString UsageArguments ( char **argv ) " -R, --recording enables recording and sets directory to contain\n" " recorded jams (server only)\n" " -s, --server start server\n" - " -T, --toreaper create Reaper project from session in named directory\n" " -u, --numchannels maximum number of channels (server only)\n" " -w, --welcomemessage welcome message on connect (server only)\n" " -y, --history enable connection history and set file\n" From 05a0ac60a68c60d38a5d0baaf67ed5ed97c2832e Mon Sep 17 00:00:00 2001 From: Volker Fischer Date: Fri, 12 Apr 2019 18:04:28 +0200 Subject: [PATCH 05/13] fixed warnings --- src/main.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 21ead6e7..90460d9e 100755 --- a/src/main.cpp +++ b/src/main.cpp @@ -493,7 +493,7 @@ int main ( int argc, char** argv ) strConnOnStartupAddress, bShowComplRegConnList, bShowAnalyzerConsole, - 0, + nullptr, Qt::Window ); // show dialog @@ -544,7 +544,7 @@ int main ( int argc, char** argv ) CServerDlg ServerDlg ( &Server, &Settings, bStartMinimized, - 0, + nullptr, Qt::Window ); // show dialog (if not the minimized flag is set) @@ -573,11 +573,11 @@ int main ( int argc, char** argv ) // show generic error if ( bUseGUI ) { - QMessageBox::critical ( 0, + QMessageBox::critical ( nullptr, APP_NAME, generr.GetErrorText(), "Quit", - 0 ); + nullptr ); } else { From feaea570f270ac7ac93e0a6ade96fbccdbc39680 Mon Sep 17 00:00:00 2001 From: Volker Fischer Date: Fri, 12 Apr 2019 18:12:22 +0200 Subject: [PATCH 06/13] use the application pointer directly without the need of the define --- src/main.cpp | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 90460d9e..4658b6b5 100755 --- a/src/main.cpp +++ b/src/main.cpp @@ -442,15 +442,14 @@ int main ( int argc, char** argv ) // Application/GUI setup --------------------------------------------------- // Application object - if (!bUseGUI && !strHistoryFileName.isEmpty()) + if ( !bUseGUI && !strHistoryFileName.isEmpty() ) { tsConsole << "Qt5 requires a windowing system to paint a JPEG image; disabling history graph" << endl; strHistoryFileName = ""; } - QCoreApplication* _app = bUseGUI + QCoreApplication* pApp = bUseGUI ? new QApplication ( argc, argv ) : new QCoreApplication ( argc, argv ); -#define app (*_app) #ifdef _WIN32 // set application priority class -> high priority @@ -460,7 +459,7 @@ int main ( int argc, char** argv ) // be located in the install directory of the software by the installer. // Here, we set the path to our application path. QDir ApplDir ( QApplication::applicationDirPath() ); - app.addLibraryPath ( QString ( ApplDir.absolutePath() ) ); + pApp->addLibraryPath ( QString ( ApplDir.absolutePath() ) ); #endif // init resources @@ -498,14 +497,14 @@ int main ( int argc, char** argv ) // show dialog ClientDlg.show(); - app.exec(); + pApp->exec(); } else { // only start application without using the GUI tsConsole << CAboutDlg::GetVersionAndNameStr ( false ) << endl; - app.exec(); + pApp->exec(); } } else @@ -553,7 +552,7 @@ int main ( int argc, char** argv ) ServerDlg.show(); } - app.exec(); + pApp->exec(); } else { @@ -563,7 +562,7 @@ int main ( int argc, char** argv ) // only start application without using the GUI tsConsole << CAboutDlg::GetVersionAndNameStr ( false ) << endl; - app.exec(); + pApp->exec(); } } } From 3348056253d427d507fe78956dda61f308cfff58 Mon Sep 17 00:00:00 2001 From: Volker Fischer Date: Fri, 12 Apr 2019 18:24:35 +0200 Subject: [PATCH 07/13] move ConsoleWriterFactory implementation in util.cpp --- src/main.cpp | 37 ++++--------------------------------- src/util.cpp | 25 +++++++++++++++++++++++++ src/util.h | 22 +++++++++++++--------- 3 files changed, 42 insertions(+), 42 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 4658b6b5..91fea668 100755 --- a/src/main.cpp +++ b/src/main.cpp @@ -31,45 +31,16 @@ #include "serverdlg.h" #include "settings.h" #include "testbench.h" +#include "util.h" - -/******************************************************************************\ -* Console logging * -\******************************************************************************/ -// Try for a portable console -------------------------------------------------- - -QTextStream* ConsoleWriterFactory::get() -{ - if ( ptsConsole == nullptr ) - { -#if _WIN32 - if ( !AttachConsole ( ATTACH_PARENT_PROCESS ) ) - { - // Not run from console, dump logging to nowhere - static QString conout; - ptsConsole = new QTextStream ( &conout ); - } - else - { - freopen ( "CONOUT$", "w", stdout ); - ptsConsole = new QTextStream ( stdout ); - } -#else - ptsConsole = new QTextStream ( stdout ); -#endif - } - return ptsConsole; -} - // Implementation ************************************************************** int main ( int argc, char** argv ) { - QTextStream& tsConsole = *((new ConsoleWriterFactory())->get()); - - QString strArgument; - double rDbleArgument; + QTextStream& tsConsole = *( ( new ConsoleWriterFactory() )->get() ); + QString strArgument; + double rDbleArgument; // initialize all flags and string which might be changed by command line // arguments diff --git a/src/util.cpp b/src/util.cpp index 50160cde..c5aa7801 100755 --- a/src/util.cpp +++ b/src/util.cpp @@ -1209,6 +1209,31 @@ QString CCountyFlagIcons::GetResourceReference ( const QLocale::Country eCountry } +// Console writer factory ------------------------------------------------------ +QTextStream* ConsoleWriterFactory::get() +{ + if ( ptsConsole == nullptr ) + { +#if _WIN32 + if ( !AttachConsole ( ATTACH_PARENT_PROCESS ) ) + { + // Not run from console, dump logging to nowhere + static QString conout; + ptsConsole = new QTextStream ( &conout ); + } + else + { + freopen ( "CONOUT$", "w", stdout ); + ptsConsole = new QTextStream ( stdout ); + } +#else + ptsConsole = new QTextStream ( stdout ); +#endif + } + return ptsConsole; +} + + /******************************************************************************\ * Global Functions Implementation * \******************************************************************************/ diff --git a/src/util.h b/src/util.h index 20a8f09e..5eb2c1fa 100755 --- a/src/util.h +++ b/src/util.h @@ -482,6 +482,19 @@ public slots: }; +// Console writer factory ------------------------------------------------------ +// this class was written by pljones +class ConsoleWriterFactory +{ +public: + ConsoleWriterFactory() : ptsConsole ( nullptr ) { } + QTextStream* get(); + +private: + QTextStream* ptsConsole; +}; + + /******************************************************************************\ * Other Classes/Enums * \******************************************************************************/ @@ -1275,13 +1288,4 @@ protected: bool bPreviousState; }; -class ConsoleWriterFactory -{ -public: - ConsoleWriterFactory() : ptsConsole ( nullptr ) { } - QTextStream* get(); -private: - QTextStream* ptsConsole; -}; - #endif /* !defined ( UTIL_HOIH934256GEKJH98_3_43445KJIUHF1912__INCLUDED_ ) */ From 53fb06b1379cb97f28d6af7dc71b459208956e9b Mon Sep 17 00:00:00 2001 From: Volker Fischer Date: Fri, 12 Apr 2019 18:42:05 +0200 Subject: [PATCH 08/13] "compress" the keyPressEvent a bit :-) --- src/chatdlg.cpp | 8 -------- src/chatdlg.h | 3 ++- src/clientdlg.cpp | 8 -------- src/clientdlg.h | 8 +++++--- src/serverdlg.cpp | 8 -------- src/serverdlg.h | 3 ++- 6 files changed, 9 insertions(+), 29 deletions(-) diff --git a/src/chatdlg.cpp b/src/chatdlg.cpp index 908a3a44..2395026e 100755 --- a/src/chatdlg.cpp +++ b/src/chatdlg.cpp @@ -102,11 +102,3 @@ void CChatDlg::AddChatText ( QString strChatText ) #endif ); } - -void CChatDlg::keyPressEvent(QKeyEvent *e) -{ - if (e->key() != Qt::Key_Escape) - { - QDialog::keyPressEvent(e); - } -} diff --git a/src/chatdlg.h b/src/chatdlg.h index eb2b0a07..87014f25 100755 --- a/src/chatdlg.h +++ b/src/chatdlg.h @@ -48,7 +48,8 @@ public slots: void OnLocalInputTextTextChanged ( const QString& strNewText ); void OnClearPressed(); - void keyPressEvent(QKeyEvent *e); + void keyPressEvent ( QKeyEvent *e ) // block escape key + { if ( e->key() != Qt::Key_Escape ) QDialog::keyPressEvent ( e ); } signals: void NewLocalInputText ( QString strNewText ); diff --git a/src/clientdlg.cpp b/src/clientdlg.cpp index 47144d81..b5348005 100755 --- a/src/clientdlg.cpp +++ b/src/clientdlg.cpp @@ -1205,11 +1205,3 @@ void CClientDlg::accept() { close(); } - -void CClientDlg::keyPressEvent(QKeyEvent *e) -{ - if (e->key() != Qt::Key_Escape) - { - QDialog::keyPressEvent(e); - } -} diff --git a/src/clientdlg.h b/src/clientdlg.h index 28a7b9e7..1f58a81e 100755 --- a/src/clientdlg.h +++ b/src/clientdlg.h @@ -79,8 +79,8 @@ public: const QString& strConnOnStartupAddress, const bool bNewShowComplRegConnList, const bool bShowAnalyzerConsole, - QWidget* parent = 0, - Qt::WindowFlags f = 0 ); + QWidget* parent = nullptr, + Qt::WindowFlags f = nullptr ); protected: void SetGUIDesign ( const EGUIDesign eNewDesign ); @@ -207,5 +207,7 @@ public slots: void OnNewClientLevelChanged() { MainMixerBoard->iNewClientFaderLevel = pClient->iNewClientFaderLevel; } void accept(); - void keyPressEvent(QKeyEvent *e); + + void keyPressEvent ( QKeyEvent *e ) // block escape key + { if ( e->key() != Qt::Key_Escape ) QDialog::keyPressEvent ( e ); } }; diff --git a/src/serverdlg.cpp b/src/serverdlg.cpp index 495fd2d3..9bee3988 100755 --- a/src/serverdlg.cpp +++ b/src/serverdlg.cpp @@ -588,11 +588,3 @@ void CServerDlg::changeEvent ( QEvent* pEvent ) } } } - -void CServerDlg::keyPressEvent(QKeyEvent *e) -{ - if (e->key() != Qt::Key_Escape) - { - QDialog::keyPressEvent(e); - } -} diff --git a/src/serverdlg.h b/src/serverdlg.h index 5b621794..1c8fdc01 100755 --- a/src/serverdlg.h +++ b/src/serverdlg.h @@ -98,5 +98,6 @@ public slots: void OnSysTrayMenuExit() { close(); } void OnSysTrayActivated ( QSystemTrayIcon::ActivationReason ActReason ); - void keyPressEvent(QKeyEvent *e); + void keyPressEvent ( QKeyEvent *e ) // block escape key + { if ( e->key() != Qt::Key_Escape ) QDialog::keyPressEvent ( e ); } }; From 4a86ccda4ff9d2893562148f04e3beede7ae58bc Mon Sep 17 00:00:00 2001 From: Volker Fischer Date: Fri, 12 Apr 2019 18:48:06 +0200 Subject: [PATCH 09/13] I fear a memory leak -> better solution required... --- src/serverlogging.cpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/serverlogging.cpp b/src/serverlogging.cpp index 13afb14e..3a4ebf7e 100755 --- a/src/serverlogging.cpp +++ b/src/serverlogging.cpp @@ -342,10 +342,6 @@ void CServerLogging::AddNewConnection ( const QHostAddress& ClientInetAddr ) const QString strLogStr = CurTimeDatetoLogString() + ", " + ClientInetAddr.toString() + ", connected"; - - QTextStream* tsConsoleStream = (new ConsoleWriterFactory())->get(); - (*tsConsoleStream) << strLogStr << endl; // on console - *this << strLogStr; // in log file // add element to history @@ -357,10 +353,6 @@ void CServerLogging::AddServerStopped() const QString strLogStr = CurTimeDatetoLogString() + ",, server stopped " "-------------------------------------"; - - QTextStream* tsConsoleStream = (new ConsoleWriterFactory())->get(); - (*tsConsoleStream) << strLogStr << endl; // on console - *this << strLogStr; // in log file // add element to history and update on server stop From aa036a08e3f95a486e9396ddee7b6721351c95a9 Mon Sep 17 00:00:00 2001 From: Volker Fischer Date: Fri, 12 Apr 2019 18:48:20 +0200 Subject: [PATCH 10/13] small fixes --- src/global.h | 2 +- src/server.h | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/global.h b/src/global.h index b1d8514a..c0924836 100755 --- a/src/global.h +++ b/src/global.h @@ -51,7 +51,7 @@ LED bar: lbr #define GLOBAL_H__3B123453_4344_BB2B_23E7A0D31912__INCLUDED_ #if _WIN32 -#define _CRT_SECURE_NO_WARNINGS +# define _CRT_SECURE_NO_WARNINGS #endif #include diff --git a/src/server.h b/src/server.h index 92308f39..c313d7c7 100755 --- a/src/server.h +++ b/src/server.h @@ -42,7 +42,6 @@ #include "serverlist.h" #include "recorder/jamrecorder.h" -using recorder::CJamRecorder; /* Definitions ****************************************************************/ @@ -252,7 +251,7 @@ protected: CServerLogging Logging; // recording thread - CJamRecorder JamRecorder; + recorder::CJamRecorder JamRecorder; bool bEnableRecording; // HTML file server status From a6a10d537ebfadd87b6f50cce1e16c82d25f27d3 Mon Sep 17 00:00:00 2001 From: Volker Fischer Date: Fri, 12 Apr 2019 18:49:49 +0200 Subject: [PATCH 11/13] take old code again --- src/serverlogging.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/serverlogging.cpp b/src/serverlogging.cpp index 3a4ebf7e..344a0011 100755 --- a/src/serverlogging.cpp +++ b/src/serverlogging.cpp @@ -342,6 +342,11 @@ void CServerLogging::AddNewConnection ( const QHostAddress& ClientInetAddr ) const QString strLogStr = CurTimeDatetoLogString() + ", " + ClientInetAddr.toString() + ", connected"; +#ifndef _WIN32 + QTextStream tsConsoleStream ( stdout ); + tsConsoleStream << strLogStr << endl; // on console +#endif + *this << strLogStr; // in log file // add element to history @@ -353,6 +358,11 @@ void CServerLogging::AddServerStopped() const QString strLogStr = CurTimeDatetoLogString() + ",, server stopped " "-------------------------------------"; +#ifndef _WIN32 + QTextStream tsConsoleStream ( stdout ); + tsConsoleStream << strLogStr << endl; // on console +#endif + *this << strLogStr; // in log file // add element to history and update on server stop From b1874d193f5bb0b40055aec52e4fca1f32cc367a Mon Sep 17 00:00:00 2001 From: Volker Fischer Date: Fri, 12 Apr 2019 18:55:43 +0200 Subject: [PATCH 12/13] small fixes --- src/clientdlg.cpp | 5 ----- src/clientdlg.h | 2 +- src/server.h | 1 - src/serverlogging.cpp | 2 -- 4 files changed, 1 insertion(+), 9 deletions(-) diff --git a/src/clientdlg.cpp b/src/clientdlg.cpp index b5348005..bd7c10be 100755 --- a/src/clientdlg.cpp +++ b/src/clientdlg.cpp @@ -1200,8 +1200,3 @@ rbtReverbSelR->setStyleSheet ( "" ); // also apply GUI design to child GUI controls MainMixerBoard->SetGUIDesign ( eNewDesign ); } - -void CClientDlg::accept() -{ - close(); -} diff --git a/src/clientdlg.h b/src/clientdlg.h index 1f58a81e..65326f05 100755 --- a/src/clientdlg.h +++ b/src/clientdlg.h @@ -206,7 +206,7 @@ public slots: void OnNumClientsChanged ( int iNewNumClients ); void OnNewClientLevelChanged() { MainMixerBoard->iNewClientFaderLevel = pClient->iNewClientFaderLevel; } - void accept(); + void accept() { close(); } // introduced by pljones void keyPressEvent ( QKeyEvent *e ) // block escape key { if ( e->key() != Qt::Key_Escape ) QDialog::keyPressEvent ( e ); } diff --git a/src/server.h b/src/server.h index c313d7c7..76dfb215 100755 --- a/src/server.h +++ b/src/server.h @@ -40,7 +40,6 @@ #include "util.h" #include "serverlogging.h" #include "serverlist.h" - #include "recorder/jamrecorder.h" diff --git a/src/serverlogging.cpp b/src/serverlogging.cpp index 344a0011..8876c099 100755 --- a/src/serverlogging.cpp +++ b/src/serverlogging.cpp @@ -346,7 +346,6 @@ void CServerLogging::AddNewConnection ( const QHostAddress& ClientInetAddr ) QTextStream tsConsoleStream ( stdout ); tsConsoleStream << strLogStr << endl; // on console #endif - *this << strLogStr; // in log file // add element to history @@ -362,7 +361,6 @@ void CServerLogging::AddServerStopped() QTextStream tsConsoleStream ( stdout ); tsConsoleStream << strLogStr << endl; // on console #endif - *this << strLogStr; // in log file // add element to history and update on server stop From 0d9c98f9be1f135570a3f94a659dc0f5096560fd Mon Sep 17 00:00:00 2001 From: Volker Fischer Date: Fri, 12 Apr 2019 19:01:21 +0200 Subject: [PATCH 13/13] mention pljones in the About dialog and README --- README | 2 ++ src/util.cpp | 1 + 2 files changed, 3 insertions(+) diff --git a/README b/README index 4f696650..c95eea89 100755 --- a/README +++ b/README @@ -62,3 +62,5 @@ source: - Some pixmaps are from the Open Clip Art Library (OCAL): http://openclipart.org + +- Server wave recording, coded by pljones diff --git a/src/util.cpp b/src/util.cpp index c5aa7801..5430bd8f 100755 --- a/src/util.cpp +++ b/src/util.cpp @@ -385,6 +385,7 @@ CAboutDlg::CAboutDlg ( QWidget* parent ) : QDialog ( parent ) "http://openclipart.org" "
  • Country flag icons from Mark James: " "http://www.famfamfam.com
  • " + "
  • Server wave recording, coded by pljones
  • " "" "
    ");