add iPod and GRUB bootloader beeper tune export (#2441)

This commit is contained in:
AnnoyedArt1256 2025-06-15 01:29:59 +03:00 committed by GitHub
parent 24b629c73c
commit 055266090a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 522 additions and 0 deletions

View file

@ -822,6 +822,8 @@ src/engine/export/amigaValidation.cpp
src/engine/export/sapr.cpp
src/engine/export/tiuna.cpp
src/engine/export/zsm.cpp
src/engine/export/ipod.cpp
src/engine/export/grub.cpp
src/engine/effect/abstract.cpp
src/engine/effect/dummy.cpp

View file

@ -675,6 +675,8 @@ class DivEngine {
friend class DivExportSAPR;
friend class DivExportTiuna;
friend class DivExportZSM;
friend class DivExportiPod;
friend class DivExportGRUB;
public:
DivSong song;

View file

@ -23,6 +23,8 @@
#include "export/sapr.h"
#include "export/tiuna.h"
#include "export/zsm.h"
#include "export/ipod.h"
#include "export/grub.h"
DivROMExport* DivEngine::buildROM(DivROMExportOptions sys) {
DivROMExport* exporter=NULL;
@ -39,6 +41,12 @@ DivROMExport* DivEngine::buildROM(DivROMExportOptions sys) {
case DIV_ROM_SAP_R:
exporter=new DivExportSAPR;
break;
case DIV_ROM_IPOD:
exporter=new DivExportiPod;
break;
case DIV_ROM_GRUB:
exporter=new DivExportGRUB;
break;
default:
exporter=new DivROMExport;
break;

View file

@ -32,6 +32,8 @@ enum DivROMExportOptions {
DIV_ROM_ZSM,
DIV_ROM_TIUNA,
DIV_ROM_SAP_R,
DIV_ROM_IPOD,
DIV_ROM_GRUB,
DIV_ROM_MAX
};

204
src/engine/export/grub.cpp Normal file
View file

@ -0,0 +1,204 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2025 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 "grub.h"
#include "../engine.h"
#include "../ta-log.h"
#include <fmt/printf.h>
#include <array>
#include <vector>
void DivExportGRUB::run() {
bool grubExportBin=conf.getBool("exportBin",false);
int BEEPER=-1;
int IGNORED=0;
// Locate system index.
for (int i=0; i<e->song.systemLen; i++) {
if (e->song.system[i] == DIV_SYSTEM_PCSPKR) {
if (BEEPER>=0) {
IGNORED++;
logAppendf("Ignoring duplicate Beeper id %d",i);
break;
}
BEEPER=i;
logAppendf("PC Speaker detected as chip id %d",i);
break;
} else {
IGNORED++;
logAppendf("Ignoring chip id %d, system id %d",i,(int)e->song.system[i]);
break;
}
}
if (BEEPER<0) {
logAppendf("ERROR: Could not find PC Speaker/Beeper");
failed=true;
running=false;
return;
}
if (IGNORED>0) {
logAppendf("WARNING: iPod .tone export ignoring %d unsupported system%c",IGNORED,IGNORED>1?'s':' ');
}
size_t tickCount=0;
e->stop();
e->repeatPattern=false;
e->setOrder(0);
logAppend("playing and logging register writes...");
int oldFreq = 0;
int freq = 0;
e->synchronizedSoft([&]() {
double origRate = e->got.rate;
double rate = MIN(e->curSubSong->hz,1000.0);
logAppendf("export rate is %d hz",(int)rate);
int tempo = (int)(60000.0/(1000.0/rate));
e->got.rate=rate;
// Determine loop point.
int loopOrder=0;
int loopRow=0;
int loopEnd=0;
e->walkSong(loopOrder,loopRow,loopEnd);
logAppendf("loop point: %d %d",loopOrder,loopRow);
e->warnings="";
auto w = new SafeWriter;
w->init();
// Reset the playback state.
e->curOrder=0;
e->freelance=false;
e->playing=false;
e->extValuePresent=false;
e->remainingLoops=-1;
e->disCont[BEEPER].dispatch->toggleRegisterDump(true);
// Prepare to write song data.
e->playSub(false);
bool done=false;
logAppend("writing data...");
progress[0].amount=0.15f;
int wait_tempo = 0;
if (grubExportBin)
w->writeI(tempo); // write tempo
else
w->writeText(fmt::sprintf("%d",tempo)); // write tempo
while (!done) {
if (e->nextTick(false,true) || !e->playing) {
done=true;
}
// get register dumps
uint8_t* regPool = e->disCont[BEEPER].dispatch->getRegisterPool();
int chipClock = e->disCont[BEEPER].dispatch->chipClock;
freq = (int)(regPool[0]|(regPool[1]<<8));
if (freq > 0) freq = chipClock/freq;
// write wait
tickCount++;
int totalWait=e->cycles;
if (totalWait>0 && !done) {
while (totalWait) {
wait_tempo++;
if (freq != oldFreq || wait_tempo == 65535) {
if (grubExportBin) {
w->writeS(oldFreq); // pitch
w->writeS(wait_tempo); // duration
} else {
w->writeText(fmt::sprintf(" %d %d", oldFreq, wait_tempo));
}
oldFreq = freq;
wait_tempo = 0;
}
totalWait--;
tickCount++;
}
}
}
if (!grubExportBin) w->writeText(fmt::sprintf("\n")); // end song
// end of song
// done - close out.
e->got.rate=origRate;
e->disCont[BEEPER].dispatch->getRegisterWrites().clear();
e->disCont[BEEPER].dispatch->toggleRegisterDump(false);
e->remainingLoops=-1;
e->playing=false;
e->freelance=false;
e->extValuePresent=false;
output.push_back(DivROMExportOutput(grubExportBin?"export.bin":"export.txt",w));
});
progress[0].amount=1.0f;
logAppend("finished!");
running=false;
}
bool DivExportGRUB::go(DivEngine* eng) {
progress[0].name="Progress";
progress[0].amount=0.0f;
e=eng;
running=true;
failed=false;
mustAbort=false;
exportThread=new std::thread(&DivExportGRUB::run,this);
return true;
}
void DivExportGRUB::wait() {
if (exportThread!=NULL) {
logV("waiting for export thread...");
exportThread->join();
delete exportThread;
}
}
void DivExportGRUB::abort() {
mustAbort=true;
wait();
}
bool DivExportGRUB::isRunning() {
return running;
}
bool DivExportGRUB::hasFailed() {
return failed;
}
DivROMExportProgress DivExportGRUB::getProgress(int index) {
if (index<0 || index>1) return progress[1];
return progress[index];
}

38
src/engine/export/grub.h Normal file
View file

@ -0,0 +1,38 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2025 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 "../export.h"
#include <thread>
class DivExportGRUB: public DivROMExport {
DivEngine* e;
std::thread* exportThread;
DivROMExportProgress progress[2];
bool running, failed, mustAbort;
void run();
public:
bool go(DivEngine* e);
bool isRunning();
bool hasFailed();
void abort();
void wait();
DivROMExportProgress getProgress(int index=0);
~DivExportGRUB() {}
};

194
src/engine/export/ipod.cpp Normal file
View file

@ -0,0 +1,194 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2025 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.
*/
// thanks asiekierka! (I used your SAP-R export code as a base for this)
#include "ipod.h"
#include "../engine.h"
#include "../ta-log.h"
#include <fmt/printf.h>
#include <array>
#include <vector>
void DivExportiPod::run() {
int BEEPER=-1;
int IGNORED=0;
// Locate system index.
for (int i=0; i<e->song.systemLen; i++) {
if (e->song.system[i] == DIV_SYSTEM_PCSPKR) {
if (BEEPER>=0) {
IGNORED++;
logAppendf("Ignoring duplicate Beeper id %d",i);
break;
}
BEEPER=i;
logAppendf("PC Speaker detected as chip id %d",i);
break;
} else {
IGNORED++;
logAppendf("Ignoring chip id %d, system id %d",i,(int)e->song.system[i]);
break;
}
}
if (BEEPER<0) {
logAppendf("ERROR: Could not find PC Speaker/Beeper");
failed=true;
running=false;
return;
}
if (IGNORED>0) {
logAppendf("WARNING: iPod .tone export ignoring %d unsupported system%c",IGNORED,IGNORED>1?'s':' ');
}
size_t tickCount=0;
double rate = 1000.0;
e->stop();
e->repeatPattern=false;
e->setOrder(0);
logAppend("playing and logging register writes...");
int oldFreq = 0;
int freq = 0;
e->synchronizedSoft([&]() {
double origRate = e->got.rate;
e->got.rate=rate;
// Determine loop point.
int loopOrder=0;
int loopRow=0;
int loopEnd=0;
e->walkSong(loopOrder,loopRow,loopEnd);
logAppendf("loop point: %d %d",loopOrder,loopRow);
e->warnings="";
auto w = new SafeWriter;
w->init();
w->writeText(fmt::sprintf("%s\n", e->song.name));
// Reset the playback state.
e->curOrder=0;
e->freelance=false;
e->playing=false;
e->extValuePresent=false;
e->remainingLoops=-1;
e->disCont[BEEPER].dispatch->toggleRegisterDump(true);
// Prepare to write song data.
e->playSub(false);
bool done=false;
logAppend("writing data...");
progress[0].amount=0.15f;
int wait_ms = 0;
while (!done) {
if (e->nextTick(false,true) || !e->playing) {
done=true;
}
// get register dumps
uint8_t* regPool = e->disCont[BEEPER].dispatch->getRegisterPool();
int chipClock = e->disCont[BEEPER].dispatch->chipClock;
freq = (int)(regPool[0]|(regPool[1]<<8));
if (freq > 0) freq = chipClock/freq;
// write wait
tickCount++;
int totalWait=e->cycles;
if (totalWait>0 && !done) {
while (totalWait) {
wait_ms++;
if (freq != oldFreq) {
w->writeText(fmt::sprintf("%d %d\n", oldFreq, wait_ms));
oldFreq = freq;
wait_ms = 0;
}
totalWait--;
tickCount++;
}
}
}
// end of song
// done - close out.
e->got.rate=origRate;
e->disCont[BEEPER].dispatch->getRegisterWrites().clear();
e->disCont[BEEPER].dispatch->toggleRegisterDump(false);
e->remainingLoops=-1;
e->playing=false;
e->freelance=false;
e->extValuePresent=false;
output.push_back(DivROMExportOutput("export.tone",w));
});
progress[0].amount=1.0f;
logAppend("finished!");
running=false;
}
bool DivExportiPod::go(DivEngine* eng) {
progress[0].name="Progress";
progress[0].amount=0.0f;
e=eng;
running=true;
failed=false;
mustAbort=false;
exportThread=new std::thread(&DivExportiPod::run,this);
return true;
}
void DivExportiPod::wait() {
if (exportThread!=NULL) {
logV("waiting for export thread...");
exportThread->join();
delete exportThread;
}
}
void DivExportiPod::abort() {
mustAbort=true;
wait();
}
bool DivExportiPod::isRunning() {
return running;
}
bool DivExportiPod::hasFailed() {
return failed;
}
DivROMExportProgress DivExportiPod::getProgress(int index) {
if (index<0 || index>1) return progress[1];
return progress[index];
}

38
src/engine/export/ipod.h Normal file
View file

@ -0,0 +1,38 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2025 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 "../export.h"
#include <thread>
class DivExportiPod: public DivROMExport {
DivEngine* e;
std::thread* exportThread;
DivROMExportProgress progress[2];
bool running, failed, mustAbort;
void run();
public:
bool go(DivEngine* e);
bool isRunning();
bool hasFailed();
void abort();
void wait();
DivROMExportProgress getProgress(int index=0);
~DivExportiPod() {}
};

View file

@ -119,4 +119,27 @@ void DivEngine::registerROMExports() {
},
false, DIV_REQPOL_EXACT
);
romExportDefs[DIV_ROM_IPOD]=new DivROMExportDef(
"iPod .tone alarm", "AArt1256",
"iPod Beeper (.tone) Alarm export\n"
"for playback, you can drag the resulting file\n"
"into iPod_Control/Tones to your iPod IN DISK MODE",
"alarm tone files", ".tone",
{
DIV_SYSTEM_PCSPKR
},
false, DIV_REQPOL_ANY
);
romExportDefs[DIV_ROM_GRUB]=new DivROMExportDef(
"GRUB_INIT_TUNE", "AArt1256",
"GRUB_INIT_TUNE export\n"
"for use with the GRUB bootloader using the \"play\" command",
"Text/Binary files", NULL,
{
DIV_SYSTEM_PCSPKR
},
false, DIV_REQPOL_ANY
);
}

View file

@ -34,6 +34,7 @@ const char* aboutLine[]={
"",
_N("-- program --"),
"tildearrow",
"AArt1256",
_N("A M 4 N (intro tune)"),
"Adam Lederer",
"akumanatt",

View file

@ -371,6 +371,16 @@ void FurnaceGUI::drawExportROM(bool onWindow) {
}
break;
}
case DIV_ROM_GRUB: {
bool grubExportBin=romConfig.getBool("exportBin",false);
if (ImGui::Checkbox(_("export binary file"),&grubExportBin)) {
altered=true;
}
if (altered) {
romConfig.set("exportBin",grubExportBin);
}
break;
}
case DIV_ROM_ABSTRACT:
ImGui::TextWrapped("%s",_("select a target from the menu at the top of this dialog."));
break;