Merge branch 'doc-info' of https://github.com/ElectricKeet/furnace into doc-info

This commit is contained in:
Electric Keet 2023-06-30 20:41:39 -07:00
commit ab67d30132
18 changed files with 189 additions and 99 deletions

View File

@ -3,14 +3,10 @@
- tutorial?
- ease-of-use improvements... ideas:
- preset compat flags
- setting to toggle the Choose a System screen on new project
- maybe reduced set of presets for the sake of simplicity
- a more preferable highlight/drag system
- some speed/intuitive workflow improvements that go a long way
- Had a hard time finding the docs on github and in Furnace's folder.
- make .pdf manual out of doc/
- you're going too slow; please run
- break compatibility if it relieves complexity
- ins/wave/sample organization (folders and all)
- multi-key binds
- bug fixes

BIN
demos/gameboy/finger.fur Normal file

Binary file not shown.

Binary file not shown.

BIN
demos/x16/keygen19.fur Normal file

Binary file not shown.

View File

@ -19,4 +19,4 @@ furthermore, it has some PCM and LFO!
- when LFO is enabled, channel 2 is muted and its output is passed to channel 1's frequency.
- `13xx`: **set LFO speed.**
- `17xx`: **toggle PCM mode.**
- _this effect is here for compatibility reasons;_ it is otherwise recommended to use Sample type instruments (which automatically enable PCM mode when used).
- _this effect is here for compatibility reasons_; it is otherwise recommended to use Sample type instruments (which automatically enable PCM mode when used).

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -160,6 +160,7 @@ bool TAMidiInRtMidi::quit() {
bool TAMidiOutRtMidi::send(const TAMidiMessage& what) {
if (!isOpen) return false;
if (!isWorking) return false;
if (what.type<0x80) return false;
size_t len=0;
switch (what.type&0xf0) {
@ -190,6 +191,7 @@ bool TAMidiOutRtMidi::send(const TAMidiMessage& what) {
port->sendMessage(what.sysExData.get(),len);
} catch (RtMidiError& e) {
logE("MIDI output error! %s",e.what());
isWorking=false;
return false;
}
return true;
@ -209,6 +211,7 @@ bool TAMidiOutRtMidi::send(const TAMidiMessage& what) {
port->sendMessage((const unsigned char*)&what.type,len);
} catch (RtMidiError& e) {
logE("MIDI output error! %s",e.what());
isWorking=false;
return false;
}
return true;
@ -237,17 +240,20 @@ bool TAMidiOutRtMidi::openDevice(String name) {
}
isOpen=portOpen;
if (!portOpen) logW("could not find MIDI out device...");
isWorking=true;
return portOpen;
} catch (RtMidiError& e) {
logW("could not open MIDI out device! %s",e.what());
return false;
}
isWorking=true;
return true;
}
bool TAMidiOutRtMidi::closeDevice() {
if (port==NULL) return false;
if (!isOpen) return false;
isWorking=false;
try {
port->closePort();
} catch (RtMidiError& e) {

View File

@ -38,7 +38,7 @@ class TAMidiInRtMidi: public TAMidiIn {
class TAMidiOutRtMidi: public TAMidiOut {
RtMidiOut* port;
bool isOpen;
bool isOpen, isWorking;
public:
bool send(const TAMidiMessage& what);
bool isDeviceOpen();
@ -49,5 +49,6 @@ class TAMidiOutRtMidi: public TAMidiOut {
bool init();
TAMidiOutRtMidi():
port(NULL),
isOpen(false) {}
isOpen(false),
isWorking(false) {}
};

View File

@ -423,7 +423,7 @@ const void* DivPlatformSegaPCM::getSampleMem(int index) {
}
size_t DivPlatformSegaPCM::getSampleMemCapacity(int index) {
return index == 0 ? 16777216 : 0;
return index == 0 ? 2097152 : 0;
}
size_t DivPlatformSegaPCM::getSampleMemUsage(int index) {
@ -465,7 +465,7 @@ void DivPlatformSegaPCM::reset() {
void DivPlatformSegaPCM::renderSamples(int sysID) {
size_t memPos=0;
memset(sampleMem,0,16777216);
memset(sampleMem,0,2097152);
memset(sampleLoaded,0,256*sizeof(bool));
memset(sampleOffSegaPCM,0,256*sizeof(unsigned int));
memset(sampleEndSegaPCM,0,256);
@ -482,7 +482,7 @@ void DivPlatformSegaPCM::renderSamples(int sysID) {
}
logV("- sample %d will be at %x with length %x",i,memPos,alignedSize);
sampleLoaded[i]=true;
if (memPos>=16777216) break;
if (memPos>=2097152) break;
sampleOffSegaPCM[i]=memPos;
for (unsigned int j=0; j<alignedSize; j++) {
if (j>=sample->samples) {
@ -491,10 +491,10 @@ void DivPlatformSegaPCM::renderSamples(int sysID) {
sampleMem[memPos++]=((unsigned char)sample->data8[j]+0x80);
}
sampleEndSegaPCM[i]=((memPos+0xff)>>8)-1;
if (memPos>=16777216) break;
if (memPos>=2097152) break;
}
logV(" and it ends in %d",sampleEndSegaPCM[i]);
if (memPos>=16777216) break;
if (memPos>=2097152) break;
}
sampleMemLen=memPos;
}
@ -522,10 +522,10 @@ int DivPlatformSegaPCM::init(DivEngine* p, int channels, int sugRate, const DivC
isMuted[i]=false;
oscBuf[i]=new DivDispatchOscBuffer;
}
sampleMem=new unsigned char[16777216];
sampleMem=new unsigned char[2097152];
pcm.set_bank(segapcm_device::BANK_12M|segapcm_device::BANK_MASKF8);
pcm.set_read([this](unsigned int addr) -> unsigned char {
return sampleMem[addr&0xffffff];
return sampleMem[addr&0x1fffff];
});
setFlags(flags);
reset();

View File

@ -1691,6 +1691,10 @@ void DivEngine::runMidiTime(int totalCycles) {
}
void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsigned int size) {
if (!size) {
logW("nextBuf called with size 0!");
return;
}
lastLoopPos=-1;
if (out!=NULL) {

View File

@ -497,6 +497,15 @@ bool FurnaceGUI::InvCheckbox(const char* label, bool* value) {
return false;
}
void FurnaceGUI::sameLineMaybe(float width) {
if (width<0.0f) width=ImGui::GetFrameHeight();
logV("sameLineMaybe: %f %f",ImGui::GetContentRegionAvail().x,width);
ImGui::SameLine();
if (ImGui::GetContentRegionAvail().x<width) ImGui::NewLine();
}
const char* FurnaceGUI::getSystemName(DivSystem which) {
/*
if (settings.chipNames) {
@ -5076,7 +5085,24 @@ bool FurnaceGUI::loop() {
newSongQuery="";
newSongFirstFrame=true;
displayNew=false;
ImGui::OpenPopup("New Song");
if (settings.newSongBehavior==1) {
e->createNewFromDefaults();
undoHist.clear();
redoHist.clear();
curFileName="";
modified=false;
curNibble=false;
orderNibble=false;
orderCursor=-1;
samplePos=0;
updateSampleTex=true;
selStart=SelectionPoint();
selEnd=SelectionPoint();
cursor=SelectionPoint();
updateWindowTitle();
} else {
ImGui::OpenPopup("New Song");
}
}
if (displayEditString) {

View File

@ -1508,6 +1508,7 @@ class FurnaceGUI {
int renderClearPos;
int insertBehavior;
int pullDeleteRow;
int newSongBehavior;
unsigned int maxUndoSteps;
String mainFontPath;
String patFontPath;
@ -1659,6 +1660,7 @@ class FurnaceGUI {
renderClearPos(0),
insertBehavior(1),
pullDeleteRow(1),
newSongBehavior(0),
maxUndoSteps(100),
mainFontPath(""),
patFontPath(""),
@ -2089,6 +2091,8 @@ class FurnaceGUI {
void pushWarningColor(bool warnCond, bool errorCond=false);
void popWarningColor();
void sameLineMaybe(float width=-1.0f);
float calcBPM(const DivGroovePattern& speeds, float hz, int vN, int vD);
void patternRow(int i, bool isPlaying, float lineHeight, int chans, int ord, const DivPattern** patCache, bool inhibitSel);

View File

@ -59,79 +59,79 @@ const char* opllVariants[4]={
const char* opllInsNames[4][17]={
/* YM2413 */ {
"User",
"Violin",
"Guitar",
"Piano",
"Flute",
"Clarinet",
"Oboe",
"Trumpet",
"Organ",
"Horn",
"Synth",
"Harpsichord",
"Vibraphone",
"Synth Bass",
"Acoustic Bass",
"Electric Guitar",
"1. Violin",
"2. Guitar",
"3. Piano",
"4. Flute",
"5. Clarinet",
"6. Oboe",
"7. Trumpet",
"8. Organ",
"9. Horn",
"10. Synth",
"11. Harpsichord",
"12. Vibraphone",
"13. Synth Bass",
"14. Acoustic Bass",
"15. Electric Guitar",
"Drums"
},
/* YMF281 */ {
"User",
"Electric String",
"Bow wow",
"Electric Guitar",
"Organ",
"Clarinet",
"Saxophone",
"Trumpet",
"Street Organ",
"Synth Brass",
"Electric Piano",
"Bass",
"Vibraphone",
"Chime",
"Tom Tom II",
"Noise",
"1. Electric String",
"2. Bow wow",
"3. Electric Guitar",
"4. Organ",
"5. Clarinet",
"6. Saxophone",
"7. Trumpet",
"8. Street Organ",
"9. Synth Brass",
"10. Electric Piano",
"11. Bass",
"12. Vibraphone",
"13. Chime",
"14. Tom Tom II",
"15. Noise",
"Drums"
},
/* YM2423 */ {
"User",
"Strings",
"Guitar",
"Electric Guitar",
"Electric Piano",
"Flute",
"Marimba",
"Trumpet",
"Harmonica",
"Tuba",
"Synth Brass",
"Short Saw",
"Vibraphone",
"Electric Guitar 2",
"Synth Bass",
"Sitar",
"1. Strings",
"2. Guitar",
"3. Electric Guitar",
"4. Electric Piano",
"5. Flute",
"6. Marimba",
"7. Trumpet",
"8. Harmonica",
"9. Tuba",
"10. Synth Brass",
"11. Short Saw",
"12. Vibraphone",
"13. Electric Guitar 2",
"14. Synth Bass",
"15. Sitar",
"Drums"
},
// stolen from FamiTracker
/* VRC7 */ {
"User",
"Bell",
"Guitar",
"Piano",
"Flute",
"Clarinet",
"Rattling Bell",
"Trumpet",
"Reed Organ",
"Soft Bell",
"Xylophone",
"Vibraphone",
"Brass",
"Bass Guitar",
"Synth",
"Chorus",
"1. Bell",
"2. Guitar",
"3. Piano",
"4. Flute",
"5. Clarinet",
"6. Rattling Bell",
"7. Trumpet",
"8. Reed Organ",
"9. Soft Bell",
"10. Xylophone",
"11. Vibraphone",
"12. Brass",
"13. Bass Guitar",
"14. Synth",
"15. Chorus",
"Drums"
}
};

View File

@ -200,6 +200,9 @@ void FurnaceGUI::drawSampleEdit() {
SAMPLE_WARN(warnLoopPos,"QSound: loop cannot be longer than 32767 samples");
}
}
if (sample->samples>65535) {
SAMPLE_WARN(warnLength,"QSound: maximum sample length is 65535");
}
break;
case DIV_SYSTEM_NES:
if (sample->loop) {
@ -207,11 +210,17 @@ void FurnaceGUI::drawSampleEdit() {
SAMPLE_WARN(warnLoopPos,"NES: loop point ignored on DPCM (may only loop entire sample)");
}
}
if (sample->samples>32648) {
SAMPLE_WARN(warnLength,"NES: maximum DPCM sample length is 32648");
}
break;
case DIV_SYSTEM_X1_010:
if (sample->loop) {
SAMPLE_WARN(warnLoop,"X1-010: samples can't loop");
}
if (sample->samples>131072) {
SAMPLE_WARN(warnLength,"X1-010: maximum sample length is 131072");
}
break;
case DIV_SYSTEM_GA20:
if (sample->loop) {
@ -235,9 +244,12 @@ void FurnaceGUI::drawSampleEdit() {
if (sample->loop) {
SAMPLE_WARN(warnLoop,"YM2610: ADPCM-A samples can't loop");
if (sample->loopStart!=0 || sample->loopEnd!=(int)(sample->samples)) {
SAMPLE_WARN(warnLoopPos,"YM2608: loop point ignored on ADPCM-B (may only loop entire sample)");
SAMPLE_WARN(warnLoopPos,"YM2610: loop point ignored on ADPCM-B (may only loop entire sample)");
}
}
if (sample->samples>2097152) {
SAMPLE_WARN(warnLength,"YM2610: maximum ADPCM-A sample length is 2097152");
}
break;
case DIV_SYSTEM_AMIGA:
if (sample->loop) {
@ -245,10 +257,28 @@ void FurnaceGUI::drawSampleEdit() {
SAMPLE_WARN(warnLoopPos,"Amiga: loop must be a multiple of 2");
}
}
if (sample->samples>131070) {
SAMPLE_WARN(warnLength,"Amiga: maximum sample length is 131070");
}
break;
case DIV_SYSTEM_SEGAPCM:
case DIV_SYSTEM_SEGAPCM_COMPAT:
if (sample->samples>65280) {
SAMPLE_WARN(warnLength,"SegaPCM: maximum sample length is 65280");
}
break;
default:
break;
}
if (e->song.system[i]!=DIV_SYSTEM_PCM_DAC) {
if (e->song.system[i]==DIV_SYSTEM_ES5506) {
if (sample->loopMode==DIV_SAMPLE_LOOP_BACKWARD) {
SAMPLE_WARN(warnLoopMode,"ES5506: backward loop mode isn't supported");
}
} else if (sample->loopMode!=DIV_SAMPLE_LOOP_FORWARD) {
SAMPLE_WARN(warnLoopMode,"backward/ping-pong only supported in Generic PCM DAC\nping-pong also on ES5506");
}
}
// chips grid
DivDispatch* dispatch=e->getDispatch(i);
@ -514,6 +544,9 @@ void FurnaceGUI::drawSampleEdit() {
}
ImGui::EndCombo();
}
if (ImGui::IsItemHovered() && !warnLoopMode.empty()) {
ImGui::SetTooltip("%s",warnLoopMode.c_str());
}
popWarningColor();
pushWarningColor(!warnLoopPos.empty());
@ -682,7 +715,7 @@ void FurnaceGUI::drawSampleEdit() {
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Edit mode: Select");
}
ImGui::SameLine();
sameLineMaybe();
pushToggleColors(sampleDragMode);
if (ImGui::Button(ICON_FA_PENCIL "##SDraw")) {
sampleDragMode=true;
@ -692,9 +725,9 @@ void FurnaceGUI::drawSampleEdit() {
ImGui::SetTooltip("Edit mode: Draw");
}
ImGui::BeginDisabled(sample->depth!=DIV_SAMPLE_DEPTH_8BIT && sample->depth!=DIV_SAMPLE_DEPTH_16BIT);
ImGui::SameLine();
sameLineMaybe();
ImGui::Dummy(ImVec2(4.0*dpiScale,dpiScale));
ImGui::SameLine();
sameLineMaybe();
ImGui::Button(ICON_FA_ARROWS_H "##SResize");
if (ImGui::IsItemClicked()) {
resizeSize=sample->samples;
@ -729,7 +762,7 @@ void FurnaceGUI::drawSampleEdit() {
} else {
resizeSize=sample->samples;
}
ImGui::SameLine();
sameLineMaybe();
ImGui::Button(ICON_FA_EXPAND "##SResample");
if (ImGui::IsItemClicked()) {
resampleTarget=targetRate;
@ -786,14 +819,14 @@ void FurnaceGUI::drawSampleEdit() {
}
ImGui::SameLine();
ImGui::Dummy(ImVec2(4.0*dpiScale,dpiScale));
ImGui::SameLine();
sameLineMaybe();
if (ImGui::Button(ICON_FA_UNDO "##SUndo")) {
doUndoSample();
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Undo");
}
ImGui::SameLine();
sameLineMaybe();
if (ImGui::Button(ICON_FA_REPEAT "##SRedo")) {
doRedoSample();
}
@ -802,7 +835,7 @@ void FurnaceGUI::drawSampleEdit() {
}
ImGui::SameLine();
ImGui::Dummy(ImVec2(4.0*dpiScale,dpiScale));
ImGui::SameLine();
sameLineMaybe();
ImGui::Button(ICON_FA_VOLUME_UP "##SAmplify");
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Amplify");
@ -850,28 +883,28 @@ void FurnaceGUI::drawSampleEdit() {
}
ImGui::EndPopup();
}
ImGui::SameLine();
sameLineMaybe();
if (ImGui::Button(ICON_FA_ARROWS_V "##SNormalize")) {
doAction(GUI_ACTION_SAMPLE_NORMALIZE);
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Normalize");
}
ImGui::SameLine();
sameLineMaybe();
if (ImGui::Button(ICON_FA_ARROW_UP "##SFadeIn")) {
doAction(GUI_ACTION_SAMPLE_FADE_IN);
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Fade in");
}
ImGui::SameLine();
sameLineMaybe();
if (ImGui::Button(ICON_FA_ARROW_DOWN "##SFadeOut")) {
doAction(GUI_ACTION_SAMPLE_FADE_OUT);
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Fade out");
}
ImGui::SameLine();
sameLineMaybe();
ImGui::Button(ICON_FA_ADJUST "##SInsertSilence");
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Insert silence");
@ -902,21 +935,21 @@ void FurnaceGUI::drawSampleEdit() {
}
ImGui::EndPopup();
}
ImGui::SameLine();
sameLineMaybe();
if (ImGui::Button(ICON_FA_ERASER "##SSilence")) {
doAction(GUI_ACTION_SAMPLE_SILENCE);
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Apply silence");
}
ImGui::SameLine();
sameLineMaybe();
if (ImGui::Button(ICON_FA_TIMES "##SDelete")) {
doAction(GUI_ACTION_SAMPLE_DELETE);
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Delete");
}
ImGui::SameLine();
sameLineMaybe();
if (ImGui::Button(ICON_FA_CROP "##STrim")) {
doAction(GUI_ACTION_SAMPLE_TRIM);
}
@ -925,28 +958,28 @@ void FurnaceGUI::drawSampleEdit() {
}
ImGui::SameLine();
ImGui::Dummy(ImVec2(4.0*dpiScale,dpiScale));
ImGui::SameLine();
sameLineMaybe();
if (ImGui::Button(ICON_FA_BACKWARD "##SReverse")) {
doAction(GUI_ACTION_SAMPLE_REVERSE);
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Reverse");
}
ImGui::SameLine();
sameLineMaybe();
if (ImGui::Button(ICON_FA_SORT_AMOUNT_ASC "##SInvert")) {
doAction(GUI_ACTION_SAMPLE_INVERT);
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Invert");
}
ImGui::SameLine();
sameLineMaybe();
if (ImGui::Button(ICON_FA_LEVEL_DOWN "##SSign")) {
doAction(GUI_ACTION_SAMPLE_SIGN);
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Signed/unsigned exchange");
}
ImGui::SameLine();
sameLineMaybe();
ImGui::Button(ICON_FA_INDUSTRY "##SFilter");
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Apply filter");
@ -1062,21 +1095,21 @@ void FurnaceGUI::drawSampleEdit() {
ImGui::EndDisabled();
ImGui::SameLine();
ImGui::Dummy(ImVec2(4.0*dpiScale,dpiScale));
ImGui::SameLine();
sameLineMaybe();
if (ImGui::Button(ICON_FA_PLAY "##PreviewSample")) {
e->previewSample(curSample);
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Preview sample");
}
ImGui::SameLine();
sameLineMaybe();
if (ImGui::Button(ICON_FA_STOP "##StopSample")) {
e->stopSamplePreview();
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Stop sample preview");
}
ImGui::SameLine();
sameLineMaybe();
if (ImGui::Button(ICON_FA_UPLOAD "##MakeIns")) {
doAction(GUI_ACTION_SAMPLE_MAKE_INS);
}
@ -1084,7 +1117,7 @@ void FurnaceGUI::drawSampleEdit() {
ImGui::SetTooltip("Create instrument from sample");
}
ImGui::SameLine();
sameLineMaybe(ImGui::CalcTextSize("Zoom").x+150.0f*dpiScale+ImGui::CalcTextSize("100%").x);
double zoomPercent=100.0/sampleZoom;
bool checkZoomLimit=false;
ImGui::Text("Zoom");
@ -1702,7 +1735,16 @@ void FurnaceGUI::drawSampleEdit() {
ImGui::TableNextColumn();
ImGui::TextUnformatted(statusBar2.c_str());
ImGui::TableNextColumn();
ImGui::TextUnformatted(statusBar3.c_str());
if (!warnLength.empty()) {
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_WARNING]);
ImGui::TextUnformatted(statusBar3.c_str());
ImGui::PopStyleColor();
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("%s",warnLength.c_str());
}
} else {
ImGui::TextUnformatted(statusBar3.c_str());
}
ImGui::EndTable();
}

View File

@ -508,6 +508,14 @@ void FurnaceGUI::drawSettings() {
settings.alwaysPlayIntro=3;
}
ImGui::Text("When creating new song:");
if (ImGui::RadioButton("Display system preset selector##NSB0",settings.newSongBehavior==0)) {
settings.newSongBehavior=0;
}
if (ImGui::RadioButton("Start with initial system##NSB1",settings.newSongBehavior==1)) {
settings.newSongBehavior=1;
}
ImGui::Separator();
if (CWSliderFloat("Double-click time (seconds)",&settings.doubleClickTime,0.02,1.0,"%.2f")) {
@ -2757,6 +2765,7 @@ void FurnaceGUI::syncSettings() {
settings.renderClearPos=e->getConfInt("renderClearPos",0);
settings.insertBehavior=e->getConfInt("insertBehavior",1);
settings.pullDeleteRow=e->getConfInt("pullDeleteRow",1);
settings.newSongBehavior=e->getConfInt("newSongBehavior",0);
clampSetting(settings.mainFontSize,2,96);
clampSetting(settings.patFontSize,2,96);
@ -2882,6 +2891,7 @@ void FurnaceGUI::syncSettings() {
clampSetting(settings.renderClearPos,0,1);
clampSetting(settings.insertBehavior,0,1);
clampSetting(settings.pullDeleteRow,0,1);
clampSetting(settings.newSongBehavior,0,1);
if (settings.exportLoops<0.0) settings.exportLoops=0.0;
if (settings.exportFadeOut<0.0) settings.exportFadeOut=0.0;
@ -3103,6 +3113,7 @@ void FurnaceGUI::commitSettings() {
e->setConf("renderClearPos",settings.renderClearPos);
e->setConf("insertBehavior",settings.insertBehavior);
e->setConf("pullDeleteRow",settings.pullDeleteRow);
e->setConf("newSongBehavior",settings.newSongBehavior);
// colors
for (int i=0; i<GUI_COLOR_MAX; i++) {