From c9309834ce16238ec91838bb74f6702b9617b0ea Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 23 Apr 2024 04:38:08 -0500 Subject: [PATCH] add pipe audio output also add ability to disable CLI control/status completely --- CMakeLists.txt | 1 + extern/rtmidi/RtMidi.cpp | 4 +- src/audio/pipe.cpp | 138 +++++++++++++++++++++++++++++++++++++++ src/audio/pipe.h | 38 +++++++++++ src/cli/cli.cpp | 24 +++++++ src/cli/cli.h | 4 ++ src/engine/engine.cpp | 7 +- src/engine/engine.h | 5 +- src/engine/playback.cpp | 2 +- src/log.cpp | 19 ++++-- src/main.cpp | 32 +++++++-- src/ta-log.h | 3 +- 12 files changed, 261 insertions(+), 16 deletions(-) create mode 100644 src/audio/pipe.cpp create mode 100644 src/audio/pipe.h diff --git a/CMakeLists.txt b/CMakeLists.txt index cae73dce8..8248a71bb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -412,6 +412,7 @@ endif() set(AUDIO_SOURCES src/audio/abstract.cpp src/audio/midi.cpp +src/audio/pipe.cpp ) if (USE_SDL2) diff --git a/extern/rtmidi/RtMidi.cpp b/extern/rtmidi/RtMidi.cpp index b54da7e1d..3984c5928 100644 --- a/extern/rtmidi/RtMidi.cpp +++ b/extern/rtmidi/RtMidi.cpp @@ -1718,14 +1718,14 @@ static void *alsaMidiHandler( void *ptr ) case SND_SEQ_EVENT_PORT_SUBSCRIBED: #if defined(__RTMIDI_DEBUG__) - std::cout << "MidiInAlsa::alsaMidiHandler: port connection made!\n"; + std::cerr << "MidiInAlsa::alsaMidiHandler: port connection made!\n"; #endif break; case SND_SEQ_EVENT_PORT_UNSUBSCRIBED: #if defined(__RTMIDI_DEBUG__) std::cerr << "MidiInAlsa::alsaMidiHandler: port connection has closed!\n"; - std::cout << "sender = " << (int) ev->data.connect.sender.client << ":" + std::cerr << "sender = " << (int) ev->data.connect.sender.client << ":" << (int) ev->data.connect.sender.port << ", dest = " << (int) ev->data.connect.dest.client << ":" << (int) ev->data.connect.dest.port diff --git a/src/audio/pipe.cpp b/src/audio/pipe.cpp new file mode 100644 index 000000000..888654fd5 --- /dev/null +++ b/src/audio/pipe.cpp @@ -0,0 +1,138 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2024 tildearrow and contributors + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include "../ta-log.h" +#include "pipe.h" + +void taPipeThread(void* inst) { + TAAudioPipe* in=(TAAudioPipe*)inst; + in->runThread(); +} + +void TAAudioPipe::runThread() { + while (running) { + onProcess((unsigned char*)sbuf,desc.bufsize); + } +} + +void TAAudioPipe::onProcess(unsigned char* buf, int nframes) { + if (audioProcCallback!=NULL) { + if (midiIn!=NULL) midiIn->gather(); + audioProcCallback(audioProcCallbackUser,inBufs,outBufs,desc.inChans,desc.outChans,desc.bufsize); + } + short* sb=(short*)buf; + + if (sb==NULL) return; + + for (size_t j=0; j1.0) outBufs[i][j]=1.0; + sb[j*desc.outChans+i]=outBufs[i][j]*32767.0; + } + } + + fwrite(sb,2,desc.bufsize*desc.outChans,stdout); + fflush(stdout); +} + +void* TAAudioPipe::getContext() { + return NULL; +} + +bool TAAudioPipe::quit() { + if (!initialized) return false; + + if (running) { + running=false; + if (outThread) { + outThread->join(); + delete outThread; + outThread=NULL; + } + } + + for (int i=0; ijoin(); + delete outThread; + outThread=NULL; + } + } + + return running; +} + +std::vector TAAudioPipe::listAudioDevices() { + std::vector ret; + + ret.push_back("stdout"); + + return ret; +} + +bool TAAudioPipe::init(TAAudioDesc& request, TAAudioDesc& response) { + if (initialized) { + logE("audio already initialized"); + return false; + } + + desc=request; + desc.outFormat=TA_AUDIO_FORMAT_F32; + response=desc; + + logV("opening stdout for audio..."); + + if (desc.outChans>0) { + outBufs=new float*[desc.outChans]; + for (int i=0; i + +class TAAudioPipe: public TAAudio { + std::thread* outThread; + short* sbuf; + + public: + void runThread(); + void onProcess(unsigned char* buf, int nframes); + + void* getContext(); + bool quit(); + bool setRun(bool run); + std::vector listAudioDevices(); + bool init(TAAudioDesc& request, TAAudioDesc& response); + TAAudioPipe(): + sbuf(NULL) {} +}; diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index a2e9a1ca9..353e335a0 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -28,11 +28,30 @@ static void handleTerm(int) { } #endif +void FurnaceCLI::noStatus() { + disableStatus=true; +} + +void FurnaceCLI::noControls() { + disableControls=true; +} + void FurnaceCLI::bindEngine(DivEngine* eng) { e=eng; } bool FurnaceCLI::loop() { + if (disableControls) { + while (!cliQuit) { +#ifdef _WIN32 + Sleep(1000); +#else + pause(); +#endif + } + return true; + } + bool escape=false; bool escapeSecondStage=false; while (!cliQuit) { @@ -98,6 +117,7 @@ bool FurnaceCLI::loop() { } bool FurnaceCLI::finish() { + if (disableControls) return true; #ifdef _WIN32 #else if (tcsetattr(0,TCSAFLUSH,&termpropold)!=0) { @@ -112,6 +132,8 @@ bool FurnaceCLI::finish() { // blatantly copied from tildearrow/tfmxplay bool FurnaceCLI::init() { #ifdef _WIN32 + if (disableControls) return true; + winin=GetStdHandle(STD_INPUT_HANDLE); winout=GetStdHandle(STD_OUTPUT_HANDLE); int termprop=0; @@ -128,6 +150,8 @@ bool FurnaceCLI::init() { intsa.sa_handler=handleTerm; sigaction(SIGINT,&intsa,NULL); + if (disableControls) return true; + if (tcgetattr(0,&termprop)!=0) { logE("could not get console attributes!"); return false; diff --git a/src/cli/cli.h b/src/cli/cli.h index fbb88567a..621ce1f26 100644 --- a/src/cli/cli.h +++ b/src/cli/cli.h @@ -35,6 +35,8 @@ class FurnaceCLI { DivEngine* e; + bool disableStatus; + bool disableControls; #ifdef _WIN32 HANDLE winin; @@ -46,6 +48,8 @@ class FurnaceCLI { #endif public: + void noStatus(); + void noControls(); void bindEngine(DivEngine* eng); bool loop(); bool finish(); diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 05387fd8f..b2a4b4428 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -36,6 +36,7 @@ #ifdef HAVE_PA #include "../audio/pa.h" #endif +#include "../audio/pipe.h" #include #include #include @@ -3522,8 +3523,9 @@ void DivEngine::setSamplePreviewVol(float vol) { previewVol=vol; } -void DivEngine::setConsoleMode(bool enable) { +void DivEngine::setConsoleMode(bool enable, bool statusOut) { consoleMode=enable; + disableStatusOut=!statusOut; } bool DivEngine::switchMaster(bool full) { @@ -3800,6 +3802,9 @@ bool DivEngine::initAudioBackend() { output=new TAAudio; #endif break; + case DIV_AUDIO_PIPE: + output=new TAAudioPipe; + break; case DIV_AUDIO_DUMMY: output=new TAAudio; break; diff --git a/src/engine/engine.h b/src/engine/engine.h index 9a7b45ddb..06d7e57d3 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -72,6 +72,7 @@ enum DivAudioEngines { DIV_AUDIO_JACK=0, DIV_AUDIO_SDL=1, DIV_AUDIO_PORTAUDIO=2, + DIV_AUDIO_PIPE=3, DIV_AUDIO_NULL=126, DIV_AUDIO_DUMMY=127 @@ -405,6 +406,7 @@ class DivEngine { bool shallStop, shallStopSched; bool endOfSong; bool consoleMode; + bool disableStatusOut; bool extValuePresent; bool repeatPattern; bool metronome; @@ -1099,7 +1101,7 @@ class DivEngine { void rescanMidiDevices(); // set the console mode. - void setConsoleMode(bool enable); + void setConsoleMode(bool enable, bool statusOut=true); // get metronome bool getMetronome(); @@ -1282,6 +1284,7 @@ class DivEngine { shallStopSched(false), endOfSong(false), consoleMode(false), + disableStatusOut(false), extValuePresent(false), repeatPattern(false), metronome(false), diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 82d07d636..38187cfe7 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -1722,7 +1722,7 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { } } - if (consoleMode && subticks<=1 && !skipping) fprintf(stderr,"\x1b[2K> %d:%.2d:%.2d.%.2d %.2x/%.2x:%.3d/%.3d %4dcmd/s\x1b[G",totalSeconds/3600,(totalSeconds/60)%60,totalSeconds%60,totalTicks/10000,curOrder,curSubSong->ordersLen,curRow,curSubSong->patLen,cmdsPerSecond); + if (consoleMode && !disableStatusOut && subticks<=1 && !skipping) fprintf(stderr,"\x1b[2K> %d:%.2d:%.2d.%.2d %.2x/%.2x:%.3d/%.3d %4dcmd/s\x1b[G",totalSeconds/3600,(totalSeconds/60)%60,totalSeconds%60,totalTicks/10000,curOrder,curSubSong->ordersLen,curRow,curSubSong->patLen,cmdsPerSecond); } if (haltOn==DIV_HALT_TICK) halted=true; diff --git a/src/log.cpp b/src/log.cpp index 3c6149fd3..1d006f93f 100644 --- a/src/log.cpp +++ b/src/log.cpp @@ -32,6 +32,7 @@ int logLevel=LOGLEVEL_TRACE; int logLevel=LOGLEVEL_TRACE; // until done #endif +FILE* logOut; FILE* logFile; char* logFileBuf; char* logFileWriteBuf; @@ -128,20 +129,22 @@ int writeLog(int level, const char* msg, fmt::printf_args args) { if (logLevel lock(logFileLock); while (true) { diff --git a/src/main.cpp b/src/main.cpp index e91e302ca..63a026e28 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -71,6 +71,9 @@ bool consoleMode=false; bool consoleMode=true; #endif +bool consoleNoStatus=false; +bool consoleNoControls=false; + bool displayEngineFailError=false; bool vgmOutDirect=false; @@ -105,8 +108,11 @@ TAParamResult pAudio(String val) { e.setAudio(DIV_AUDIO_SDL); } else if (val=="portaudio") { e.setAudio(DIV_AUDIO_PORTAUDIO); + } else if (val=="pipe") { + e.setAudio(DIV_AUDIO_PIPE); + changeLogOutput(stderr); } else { - logE("invalid value for audio engine! valid values are: jack, sdl, portaudio."); + logE("invalid value for audio engine! valid values are: jack, sdl, portaudio, pipe."); return TA_PARAM_ERROR; } return TA_PARAM_SUCCESS; @@ -131,6 +137,16 @@ TAParamResult pConsole(String val) { return TA_PARAM_SUCCESS; } +TAParamResult pNoStatus(String val) { + consoleNoStatus=true; + return TA_PARAM_SUCCESS; +} + +TAParamResult pNoControls(String val) { + consoleNoControls=true; + return TA_PARAM_SUCCESS; +} + TAParamResult pSafeMode(String val) { #ifdef HAVE_GUI safeMode=true; @@ -365,7 +381,7 @@ bool needsValue(String param) { void initParams() { params.push_back(TAParam("h","help",false,pHelp,"","display this help")); - params.push_back(TAParam("a","audio",true,pAudio,"jack|sdl|portaudio","set audio engine (SDL by default)")); + params.push_back(TAParam("a","audio",true,pAudio,"jack|sdl|portaudio|pipe","set audio engine (SDL by default)")); params.push_back(TAParam("o","output",true,pOutput,"","output audio to file")); params.push_back(TAParam("O","vgmout",true,pVGMOut,"","output .vgm data")); params.push_back(TAParam("D","direct",false,pDirect,"","set VGM export direct stream mode")); @@ -375,6 +391,8 @@ void initParams() { params.push_back(TAParam("v","view",true,pView,"pattern|commands|nothing","set visualization (nothing by default)")); params.push_back(TAParam("i","info",false,pInfo,"","get info about a song")); params.push_back(TAParam("c","console",false,pConsole,"","enable console mode")); + params.push_back(TAParam("n","nostatus",false,pNoStatus,"","disable playback status in console mode")); + params.push_back(TAParam("N","nocontrols",false,pNoControls,"","disable standard input controls in console mode")); params.push_back(TAParam("l","loops",true,pLoops,"","set number of loops (-1 means loop forever)")); params.push_back(TAParam("s","subsong",true,pSubSong,"","set sub-song")); @@ -436,7 +454,7 @@ int main(int argc, char** argv) { srand(time(NULL)); - initLog(); + initLog(stdout); #ifdef _WIN32 // set DPI awareness HMODULE shcore=LoadLibraryW(L"shcore.dll"); @@ -513,7 +531,7 @@ int main(int argc, char** argv) { } } - e.setConsoleMode(consoleMode); + e.setConsoleMode(consoleMode,!consoleNoStatus); #ifdef _WIN32 if (consoleMode) { @@ -699,6 +717,12 @@ int main(int argc, char** argv) { if (consoleMode) { bool cliSuccess=false; + if (consoleNoStatus) { + cli.noStatus(); + } + if (consoleNoControls) { + cli.noControls(); + } cli.bindEngine(&e); if (!cli.init()) { reportError("error while starting CLI!"); diff --git a/src/ta-log.h b/src/ta-log.h index 43aecaafb..4f88a7c88 100644 --- a/src/ta-log.h +++ b/src/ta-log.h @@ -78,7 +78,8 @@ template int logE(const char* msg, const T&... args) { return writeLog(LOGLEVEL_ERROR,msg,fmt::make_printf_args(args...)); } -void initLog(); +void initLog(FILE* where); +void changeLogOutput(FILE* where); bool startLogFile(const char* path); bool finishLogFile(); #endif