add pipe audio output

also add ability to disable CLI control/status completely
This commit is contained in:
tildearrow 2024-04-23 04:38:08 -05:00
parent d41eeb02be
commit c9309834ce
12 changed files with 261 additions and 16 deletions

View file

@ -412,6 +412,7 @@ endif()
set(AUDIO_SOURCES
src/audio/abstract.cpp
src/audio/midi.cpp
src/audio/pipe.cpp
)
if (USE_SDL2)

View file

@ -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
View 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
View 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) {}
};

View file

@ -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;

View file

@ -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();

View file

@ -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;

View file

@ -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),

View file

@ -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;

View file

@ -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) {

View file

@ -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!");

View file

@ -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