add pipe audio output
also add ability to disable CLI control/status completely
This commit is contained in:
parent
d41eeb02be
commit
c9309834ce
|
@ -412,6 +412,7 @@ endif()
|
|||
set(AUDIO_SOURCES
|
||||
src/audio/abstract.cpp
|
||||
src/audio/midi.cpp
|
||||
src/audio/pipe.cpp
|
||||
)
|
||||
|
||||
if (USE_SDL2)
|
||||
|
|
4
extern/rtmidi/RtMidi.cpp
vendored
4
extern/rtmidi/RtMidi.cpp
vendored
|
@ -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
|
||||
|
|
138
src/audio/pipe.cpp
Normal file
138
src/audio/pipe.cpp
Normal file
|
@ -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 <string.h>
|
||||
#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; j<desc.bufsize; j++) {
|
||||
for (size_t i=0; i<desc.outChans; i++) {
|
||||
if (outBufs[i][j]<-1.0) outBufs[i][j]=-1.0;
|
||||
if (outBufs[i][j]>1.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; i<desc.outChans; i++) {
|
||||
delete[] outBufs[i];
|
||||
}
|
||||
|
||||
delete[] outBufs;
|
||||
|
||||
if (sbuf) {
|
||||
delete[] sbuf;
|
||||
sbuf=NULL;
|
||||
}
|
||||
|
||||
initialized=false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TAAudioPipe::setRun(bool run) {
|
||||
if (!initialized) return false;
|
||||
|
||||
if (running!=run) {
|
||||
running=run;
|
||||
if (running) {
|
||||
outThread=new std::thread(taPipeThread,this);
|
||||
} else if (outThread) {
|
||||
outThread->join();
|
||||
delete outThread;
|
||||
outThread=NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return running;
|
||||
}
|
||||
|
||||
std::vector<String> TAAudioPipe::listAudioDevices() {
|
||||
std::vector<String> 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<desc.outChans; i++) {
|
||||
outBufs[i]=new float[desc.bufsize];
|
||||
}
|
||||
|
||||
sbuf=new short[desc.bufsize*desc.outChans];
|
||||
} else {
|
||||
sbuf=NULL;
|
||||
}
|
||||
|
||||
response=desc;
|
||||
initialized=true;
|
||||
return true;
|
||||
}
|
38
src/audio/pipe.h
Normal file
38
src/audio/pipe.h
Normal file
|
@ -0,0 +1,38 @@
|
|||
/**
|
||||
* 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 "taAudio.h"
|
||||
#include <thread>
|
||||
|
||||
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<String> listAudioDevices();
|
||||
bool init(TAAudioDesc& request, TAAudioDesc& response);
|
||||
TAAudioPipe():
|
||||
sbuf(NULL) {}
|
||||
};
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
#ifdef HAVE_PA
|
||||
#include "../audio/pa.h"
|
||||
#endif
|
||||
#include "../audio/pipe.h"
|
||||
#include <math.h>
|
||||
#include <float.h>
|
||||
#include <fmt/printf.h>
|
||||
|
@ -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;
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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;
|
||||
|
|
19
src/log.cpp
19
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<level) return 0;
|
||||
switch (level) {
|
||||
case LOGLEVEL_ERROR:
|
||||
return fmt::printf("\x1b[1;31m[ERROR]\x1b[m %s\n",logEntries[pos].text);
|
||||
return fmt::fprintf(logOut,"\x1b[1;31m[ERROR]\x1b[m %s\n",logEntries[pos].text);
|
||||
case LOGLEVEL_WARN:
|
||||
return fmt::printf("\x1b[1;33m[warning]\x1b[m %s\n",logEntries[pos].text);
|
||||
return fmt::fprintf(logOut,"\x1b[1;33m[warning]\x1b[m %s\n",logEntries[pos].text);
|
||||
case LOGLEVEL_INFO:
|
||||
return fmt::printf("\x1b[1;32m[info]\x1b[m %s\n",logEntries[pos].text);
|
||||
return fmt::fprintf(logOut,"\x1b[1;32m[info]\x1b[m %s\n",logEntries[pos].text);
|
||||
case LOGLEVEL_DEBUG:
|
||||
return fmt::printf("\x1b[1;34m[debug]\x1b[m %s\n",logEntries[pos].text);
|
||||
return fmt::fprintf(logOut,"\x1b[1;34m[debug]\x1b[m %s\n",logEntries[pos].text);
|
||||
case LOGLEVEL_TRACE:
|
||||
return fmt::printf("\x1b[1;37m[trace]\x1b[m %s\n",logEntries[pos].text);
|
||||
return fmt::fprintf(logOut,"\x1b[1;37m[trace]\x1b[m %s\n",logEntries[pos].text);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void initLog() {
|
||||
void initLog(FILE* where) {
|
||||
logOut=where;
|
||||
|
||||
// initialize coloring on Windows
|
||||
#ifdef _WIN32
|
||||
HANDLE winout=GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
|
@ -161,6 +164,10 @@ void initLog() {
|
|||
logFileAvail=false;
|
||||
}
|
||||
|
||||
void changeLogOutput(FILE* where) {
|
||||
logOut=where;
|
||||
}
|
||||
|
||||
void _logFileThread() {
|
||||
std::unique_lock<std::mutex> lock(logFileLock);
|
||||
while (true) {
|
||||
|
|
32
src/main.cpp
32
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,"<filename>","output audio to file"));
|
||||
params.push_back(TAParam("O","vgmout",true,pVGMOut,"<filename>","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,"<count>","set number of loops (-1 means loop forever)"));
|
||||
params.push_back(TAParam("s","subsong",true,pSubSong,"<number>","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!");
|
||||
|
|
|
@ -78,7 +78,8 @@ template<typename... T> 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
|
||||
|
|
Loading…
Reference in a new issue