Merge branch 'master' into preset1

This commit is contained in:
tildearrow 2022-06-17 20:40:09 -05:00
commit 39fa3d7d85
30 changed files with 3059 additions and 48 deletions

View file

@ -92,10 +92,12 @@ bool TAMidiOut::closeDevice() {
}
std::vector<String> TAMidiIn::listDevices() {
logW("attempting to list devices of abstract TAMidiIn!");
return std::vector<String>();
}
std::vector<String> TAMidiOut::listDevices() {
logW("attempting to list devices of abstract TAMidiOut!");
return std::vector<String>();
}

View file

@ -2067,6 +2067,8 @@ int DivEngine::addSample() {
sample->name=fmt::sprintf("Sample %d",sampleCount);
song.sample.push_back(sample);
song.sampleLen=sampleCount+1;
sPreview.sample=-1;
sPreview.pos=0;
saveLock.unlock();
renderSamples();
BUSY_END;
@ -2183,6 +2185,7 @@ int DivEngine::addSampleFromFile(const char* path) {
return -1;
#else
SF_INFO si;
memset(&si,0,sizeof(SF_INFO));
SNDFILE* f=sf_open(path,SFM_READ,&si);
if (f==NULL) {
BUSY_END;
@ -2200,8 +2203,22 @@ int DivEngine::addSampleFromFile(const char* path) {
BUSY_END;
return -1;
}
short* buf=new short[si.channels*si.frames];
if (sf_readf_short(f,buf,si.frames)!=si.frames) {
void* buf=NULL;
sf_count_t sampleLen=sizeof(short);
if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_PCM_U8) {
logD("sample is 8-bit unsigned");
buf=new unsigned char[si.channels*si.frames];
sampleLen=sizeof(unsigned char);
} else if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_FLOAT) {
logD("sample is 32-bit float");
buf=new float[si.channels*si.frames];
sampleLen=sizeof(float);
} else {
logD("sample is 16-bit signed");
buf=new short[si.channels*si.frames];
sampleLen=sizeof(short);
}
if (sf_read_raw(f,buf,si.frames*si.channels*sampleLen)!=(si.frames*si.channels*sampleLen)) {
logW("sample read size mismatch!");
}
DivSample* sample=new DivSample;
@ -2215,19 +2232,41 @@ int DivEngine::addSampleFromFile(const char* path) {
sample->depth=16;
}
sample->init(si.frames);
for (int i=0; i<si.frames*si.channels; i+=si.channels) {
int averaged=0;
for (int j=0; j<si.channels; j++) {
averaged+=buf[i+j];
if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_PCM_U8) {
for (int i=0; i<si.frames*si.channels; i+=si.channels) {
int averaged=0;
for (int j=0; j<si.channels; j++) {
averaged+=((int)((unsigned char*)buf)[i+j])-128;
}
averaged/=si.channels;
sample->data8[index++]=averaged;
}
averaged/=si.channels;
if (((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_PCM_U8)) {
sample->data8[index++]=averaged>>8;
} else {
delete[] (unsigned char*)buf;
} else if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_FLOAT) {
for (int i=0; i<si.frames*si.channels; i+=si.channels) {
float averaged=0.0f;
for (int j=0; j<si.channels; j++) {
averaged+=((float*)buf)[i+j];
}
averaged/=si.channels;
averaged*=32767.0;
if (averaged<-32768.0) averaged=-32768.0;
if (averaged>32767.0) averaged=32767.0;
sample->data16[index++]=averaged;
}
delete[] (float*)buf;
} else {
for (int i=0; i<si.frames*si.channels; i+=si.channels) {
int averaged=0;
for (int j=0; j<si.channels; j++) {
averaged+=((short*)buf)[i+j];
}
averaged/=si.channels;
sample->data16[index++]=averaged;
}
delete[] (short*)buf;
}
delete[] buf;
sample->rate=si.samplerate;
if (sample->rate<4000) sample->rate=4000;
if (sample->rate>96000) sample->rate=96000;
@ -2265,6 +2304,8 @@ int DivEngine::addSampleFromFile(const char* path) {
void DivEngine::delSample(int index) {
BUSY_BEGIN;
sPreview.sample=-1;
sPreview.pos=0;
saveLock.lock();
if (index>=0 && index<(int)song.sample.size()) {
delete song.sample[index];
@ -2479,6 +2520,8 @@ bool DivEngine::moveWaveUp(int which) {
bool DivEngine::moveSampleUp(int which) {
if (which<1 || which>=(int)song.sample.size()) return false;
BUSY_BEGIN;
sPreview.sample=-1;
sPreview.pos=0;
DivSample* prev=song.sample[which];
saveLock.lock();
song.sample[which]=song.sample[which-1];
@ -2516,6 +2559,8 @@ bool DivEngine::moveWaveDown(int which) {
bool DivEngine::moveSampleDown(int which) {
if (which<0 || which>=((int)song.sample.size())-1) return false;
BUSY_BEGIN;
sPreview.sample=-1;
sPreview.pos=0;
DivSample* prev=song.sample[which];
saveLock.lock();
song.sample[which]=song.sample[which+1];
@ -2980,6 +3025,8 @@ bool DivEngine::initAudioBackend() {
if (!output->midiIn->openDevice(inName)) {
logW("could not open MIDI input device!");
}
} else {
logV("no MIDI input device selected.");
}
}
if (output->midiOut) {
@ -2990,6 +3037,8 @@ bool DivEngine::initAudioBackend() {
if (!output->midiOut->openDevice(outName)) {
logW("could not open MIDI output device!");
}
} else {
logV("no MIDI output device selected.");
}
}

View file

@ -65,7 +65,7 @@ const char** DivPlatformNES::getRegisterSheet() {
const char* DivPlatformNES::getEffectName(unsigned char effect) {
switch (effect) {
case 0x11:
return "Write to delta modulation counter (0 to 7F)";
return "11xx: Write to delta modulation counter (0 to 7F)";
break;
case 0x12:
return "12xx: Set duty cycle/noise mode (pulse: 0 to 3; noise: 0 or 1)";

View file

@ -51,7 +51,14 @@ bool DivSample::save(const char* path) {
si.channels=1;
si.samplerate=rate;
si.format=SF_FORMAT_PCM_16|SF_FORMAT_WAV;
switch (depth) {
case 8: // 8-bit
si.format=SF_FORMAT_PCM_U8|SF_FORMAT_WAV;
break;
default: // 16-bit
si.format=SF_FORMAT_PCM_16|SF_FORMAT_WAV;
break;
}
f=sf_open(path,SFM_WRITE,&si);
@ -77,7 +84,21 @@ bool DivSample::save(const char* path) {
}
sf_command(f, SFC_SET_INSTRUMENT, &inst, sizeof(inst));
sf_writef_short(f,data16,samples);
switch (depth) {
case 8: {
// convert from signed to unsigned
unsigned char* buf=new unsigned char[length8];
for (size_t i=0; i<length8; i++) {
buf[i]=data8[i]^0x80;
}
sf_write_raw(f,buf,length8);
delete[] buf;
break;
}
default:
sf_write_raw(f,data16,length16);
break;
}
sf_close(f);

View file

@ -953,13 +953,15 @@ void FurnaceGUI::doUndo() {
DivPattern* p=e->curPat[i.chan].getPattern(i.pat,true);
p->data[i.row][i.col]=i.oldVal;
}
if (!e->isPlaying() || !followPattern) {
cursor=us.cursor;
selStart=us.selStart;
selEnd=us.selEnd;
curNibble=us.nibble;
updateScroll(cursor.y);
setOrder(us.order);
if (us.type!=GUI_UNDO_REPLACE) {
if (!e->isPlaying() || !followPattern) {
cursor=us.cursor;
selStart=us.selStart;
selEnd=us.selEnd;
curNibble=us.nibble;
updateScroll(cursor.y);
setOrder(us.order);
}
}
break;
}
@ -1002,13 +1004,15 @@ void FurnaceGUI::doRedo() {
DivPattern* p=e->curPat[i.chan].getPattern(i.pat,true);
p->data[i.row][i.col]=i.newVal;
}
if (!e->isPlaying()) {
cursor=us.cursor;
selStart=us.selStart;
selEnd=us.selEnd;
curNibble=us.nibble;
updateScroll(cursor.y);
setOrder(us.order);
if (us.type!=GUI_UNDO_REPLACE) {
if (!e->isPlaying() || !followPattern) {
cursor=us.cursor;
selStart=us.selStart;
selEnd=us.selEnd;
curNibble=us.nibble;
updateScroll(cursor.y);
setOrder(us.order);
}
}
break;

View file

@ -2,7 +2,62 @@
#include "ImGuiFileDialog.h"
#include "../ta-log.h"
#ifdef USE_NFD
#include <nfd.h>
#else
#include "../../extern/pfd-fixed/portable-file-dialogs.h"
#endif
#ifdef USE_NFD
struct NFDState {
bool isSave;
String header;
std::vector<String> filter;
String path;
FileDialogSelectCallback clickCallback;
NFDState(bool save, String h, std::vector<String> filt, String pa, FileDialogSelectCallback cc):
isSave(save),
header(h),
filter(filt),
path(pa),
clickCallback(cc) {
}
};
// TODO: filter
void _nfdThread(const NFDState state, std::atomic<bool>* ok, String* result) {
nfdchar_t* out=NULL;
nfdresult_t ret=NFD_CANCEL;
if (state.isSave) {
ret=NFD_SaveDialog(NULL,state.path.c_str(),&out);
} else {
ret=NFD_OpenDialog(NULL,state.path.c_str(),&out);
}
switch (ret) {
case NFD_OKAY:
if (out!=NULL) {
(*result)=out;
} else {
(*result)="";
}
break;
case NFD_CANCEL:
(*result)="";
break;
case NFD_ERROR:
(*result)="";
logE("NFD error! %s\n",NFD_GetError());
break;
default:
logE("NFD unknown return code %d!\n",ret);
(*result)="";
break;
}
(*ok)=true;
}
#endif
bool FurnaceGUIFileDialog::openLoad(String header, std::vector<String> filter, const char* noSysFilter, String path, double dpiScale, FileDialogSelectCallback clickCallback) {
if (opened) return false;
@ -10,7 +65,12 @@ bool FurnaceGUIFileDialog::openLoad(String header, std::vector<String> filter, c
curPath=path;
logD("opening load file dialog with curPath %s",curPath.c_str());
if (sysDialog) {
#ifdef USE_NFD
dialogOK=false;
dialogO=new std::thread(_nfdThread,NFDState(false,header,filter,path,clickCallback),&dialogOK,&nfdResult);
#else
dialogO=new pfd::open_file(header,path,filter);
#endif
} else {
ImGuiFileDialog::Instance()->DpiScale=dpiScale;
ImGuiFileDialog::Instance()->OpenModal("FileDialog",header,noSysFilter,path,1,nullptr,0,clickCallback);
@ -25,7 +85,12 @@ bool FurnaceGUIFileDialog::openSave(String header, std::vector<String> filter, c
curPath=path;
logD("opening save file dialog with curPath %s",curPath.c_str());
if (sysDialog) {
#ifdef USE_NFD
dialogOK=false;
dialogS=new std::thread(_nfdThread,NFDState(true,header,filter,path,NULL),&dialogOK,&nfdResult);
#else
dialogS=new pfd::save_file(header,path,filter);
#endif
} else {
ImGuiFileDialog::Instance()->DpiScale=dpiScale;
ImGuiFileDialog::Instance()->OpenModal("FileDialog",header,noSysFilter,path,1,nullptr,ImGuiFileDialogFlags_ConfirmOverwrite);
@ -46,15 +111,24 @@ void FurnaceGUIFileDialog::close() {
if (sysDialog) {
if (saving) {
if (dialogS!=NULL) {
#ifdef USE_NFD
dialogS->join();
#endif
delete dialogS;
dialogS=NULL;
}
} else {
if (dialogO!=NULL) {
#ifdef USE_NFD
dialogO->join();
#endif
delete dialogO;
dialogO=NULL;
}
}
#ifdef USE_NFD
dialogOK=false;
#endif
} else {
ImGuiFileDialog::Instance()->Close();
}
@ -63,6 +137,15 @@ void FurnaceGUIFileDialog::close() {
bool FurnaceGUIFileDialog::render(const ImVec2& min, const ImVec2& max) {
if (sysDialog) {
#ifdef USE_NFD
if (dialogOK) {
fileName=nfdResult;
logD("returning %s",fileName.c_str());
dialogOK=false;
return true;
}
return false;
#else
if (saving) {
if (dialogS!=NULL) {
if (dialogS->ready(0)) {
@ -90,6 +173,7 @@ bool FurnaceGUIFileDialog::render(const ImVec2& min, const ImVec2& max) {
}
}
return false;
#endif
} else {
return ImGuiFileDialog::Instance()->Display("FileDialog",ImGuiWindowFlags_NoCollapse|ImGuiWindowFlags_NoMove,min,max);
}

View file

@ -3,10 +3,19 @@
#include <functional>
#include <vector>
#if defined(_WIN32) || defined(__APPLE__)
#define USE_NFD
#endif
#ifdef USE_NFD
#include <atomic>
#include <thread>
#else
namespace pfd {
class open_file;
class save_file;
}
#endif
typedef std::function<void(const char*)> FileDialogSelectCallback;
@ -16,8 +25,15 @@ class FurnaceGUIFileDialog {
bool saving;
String curPath;
String fileName;
#ifdef USE_NFD
std::thread* dialogO;
std::thread* dialogS;
std::atomic<bool> dialogOK;
String nfdResult;
#else
pfd::open_file* dialogO;
pfd::save_file* dialogS;
#endif
public:
bool openLoad(String header, std::vector<String> filter, const char* noSysFilter, String path, double dpiScale, FileDialogSelectCallback clickCallback=NULL);
bool openSave(String header, std::vector<String> filter, const char* noSysFilter, String path, double dpiScale);

View file

@ -43,6 +43,7 @@ int queryNote(int note, int octave) {
}
bool checkCondition(int mode, int arg, int argMax, int val, bool noteMode=false) {
const int emptyVal=noteMode?-61:-1;
switch (mode) {
case GUI_QUERY_IGNORE:
return true;
@ -51,19 +52,19 @@ bool checkCondition(int mode, int arg, int argMax, int val, bool noteMode=false)
return (val==arg);
break;
case GUI_QUERY_MATCH_NOT:
return (val!=-1 && val!=arg);
return (val!=emptyVal && val!=arg);
break;
case GUI_QUERY_RANGE:
return (val>=arg && val<=argMax);
break;
case GUI_QUERY_RANGE_NOT:
return (val!=-1 && (val<arg || val>argMax) && (!noteMode || val<120));
return (val!=emptyVal && (val<arg || val>argMax) && (!noteMode || val<120));
break;
case GUI_QUERY_ANY:
return (val!=-1);
return (val!=emptyVal);
break;
case GUI_QUERY_NONE:
return (val==-1);
return (val==emptyVal);
break;
}
return false;
@ -189,12 +190,6 @@ void FurnaceGUI::doFind() {
queryViewingResults=true;
}
/* issues with the find and replace function:
- doesn't mark the module as modified
- can't undo
- replace notes to anything starting from C-0 to lower notes will have an octave higher, so set it to replace to C-0 it will becom C-1, b_1 will become B-0 and so on
*/
void FurnaceGUI::doReplace() {
doFind();
queryViewingResults=false;
@ -202,6 +197,12 @@ void FurnaceGUI::doReplace() {
bool* touched[DIV_MAX_CHANS];
memset(touched,0,DIV_MAX_CHANS*sizeof(bool*));
UndoStep us;
us.type=GUI_UNDO_REPLACE;
short prevVal[32];
memset(prevVal,0,32*sizeof(short));
for (FurnaceGUIQueryResult& i: curQueryResults) {
int patIndex=e->song.subsong[i.subsong]->orders.ord[i.x][i.order];
DivPattern* p=e->song.subsong[i.subsong]->pat[i.x].getPattern(patIndex,true);
@ -211,6 +212,9 @@ void FurnaceGUI::doReplace() {
}
if (touched[i.x][(patIndex<<8)|i.y]) continue;
touched[i.x][(patIndex<<8)|i.y]=true;
memcpy(prevVal,p->data[i.y],32*sizeof(short));
if (queryReplaceNoteDo) {
switch (queryReplaceNoteMode) {
case GUI_QUERY_REPLACE_SET:
@ -241,8 +245,11 @@ void FurnaceGUI::doReplace() {
if (note>119) note=119;
p->data[i.y][0]=(note+60)%12;
if (p->data[i.y][0]==0) p->data[i.y][0]=12;
p->data[i.y][1]=(unsigned char)((note-1)/12);
p->data[i.y][1]=(unsigned char)(((note+60)/12)-5);
if (p->data[i.y][0]==0) {
p->data[i.y][0]=12;
p->data[i.y][1]=(unsigned char)(p->data[i.y][1]-1);
}
}
}
break;
@ -258,8 +265,11 @@ void FurnaceGUI::doReplace() {
}
p->data[i.y][0]=(note+60)%12;
if (p->data[i.y][0]==0) p->data[i.y][0]=12;
p->data[i.y][1]=(unsigned char)((note-1)/12);
p->data[i.y][1]=(unsigned char)(((note+60)/12)-5);
if (p->data[i.y][0]==0) {
p->data[i.y][0]=12;
p->data[i.y][1]=(unsigned char)(p->data[i.y][1]-1);
}
}
}
break;
@ -400,6 +410,13 @@ void FurnaceGUI::doReplace() {
}
}
}
// issue undo step
for (int j=0; j<32; j++) {
if (p->data[i.y][j]!=prevVal[j]) {
us.pat.push_back(UndoPatternData(i.subsong,i.x,patIndex,i.y,j,prevVal[j],p->data[i.y][j]));
}
}
}
for (int i=0; i<DIV_MAX_CHANS; i++) {
@ -409,6 +426,13 @@ void FurnaceGUI::doReplace() {
if (!curQueryResults.empty()) {
MARK_MODIFIED;
}
if (!us.pat.empty()) {
printf("pusher\n");
undoHist.push_back(us);
redoHist.clear();
if (undoHist.size()>settings.maxUndoSteps) undoHist.pop_front();
}
}
#define FIRST_VISIBLE(x) (x==GUI_QUERY_MATCH || x==GUI_QUERY_MATCH_NOT || x==GUI_QUERY_RANGE || x==GUI_QUERY_RANGE_NOT)

View file

@ -765,16 +765,16 @@ void FurnaceGUI::drawSampleEdit() {
float highP=sampleFilterH*100.0f;
float resP=sampleFilterRes*100.0f;
ImGui::Text("Cutoff:");
if (ImGui::SliderFloat("From",&sampleFilterCutStart,0.0f,sample->rate*0.5,"%.0fHz")) {
if (CWSliderFloat("From",&sampleFilterCutStart,0.0f,sample->rate*0.5,"%.0fHz")) {
if (sampleFilterCutStart<0.0) sampleFilterCutStart=0.0;
if (sampleFilterCutStart>sample->rate*0.5) sampleFilterCutStart=sample->rate*0.5;
}
if (ImGui::SliderFloat("To",&sampleFilterCutEnd,0.0f,sample->rate*0.5,"%.0fHz")) {
if (CWSliderFloat("To",&sampleFilterCutEnd,0.0f,sample->rate*0.5,"%.0fHz")) {
if (sampleFilterCutEnd<0.0) sampleFilterCutEnd=0.0;
if (sampleFilterCutEnd>sample->rate*0.5) sampleFilterCutEnd=sample->rate*0.5;
}
ImGui::Separator();
if (ImGui::SliderFloat("Resonance",&resP,0.0f,99.0f,"%.1f%%")) {
if (CWSliderFloat("Resonance",&resP,0.0f,99.0f,"%.1f%%")) {
sampleFilterRes=resP/100.0f;
if (sampleFilterRes<0.0f) sampleFilterRes=0.0f;
if (sampleFilterRes>0.99f) sampleFilterRes=0.99f;
@ -793,17 +793,17 @@ void FurnaceGUI::drawSampleEdit() {
sampleFilterPower=3;
}
ImGui::Separator();
if (ImGui::SliderFloat("Low-pass",&lowP,0.0f,100.0f,"%.1f%%")) {
if (CWSliderFloat("Low-pass",&lowP,0.0f,100.0f,"%.1f%%")) {
sampleFilterL=lowP/100.0f;
if (sampleFilterL<0.0f) sampleFilterL=0.0f;
if (sampleFilterL>1.0f) sampleFilterL=1.0f;
}
if (ImGui::SliderFloat("Band-pass",&bandP,0.0f,100.0f,"%.1f%%")) {
if (CWSliderFloat("Band-pass",&bandP,0.0f,100.0f,"%.1f%%")) {
sampleFilterB=bandP/100.0f;
if (sampleFilterB<0.0f) sampleFilterB=0.0f;
if (sampleFilterB>1.0f) sampleFilterB=1.0f;
}
if (ImGui::SliderFloat("High-pass",&highP,0.0f,100.0f,"%.1f%%")) {
if (CWSliderFloat("High-pass",&highP,0.0f,100.0f,"%.1f%%")) {
sampleFilterH=highP/100.0f;
if (sampleFilterH<0.0f) sampleFilterH=0.0f;
if (sampleFilterH>1.0f) sampleFilterH=1.0f;