Skip to content

add iPod and GRUB bootloader beeper tune export #2441

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -814,6 +814,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
Expand Down
2 changes: 2 additions & 0 deletions src/engine/engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -675,6 +675,8 @@ class DivEngine {
friend class DivExportSAPR;
friend class DivExportTiuna;
friend class DivExportZSM;
friend class DivExportiPod;
friend class DivExportGRUB;

public:
DivSong song;
Expand Down
8 changes: 8 additions & 0 deletions src/engine/export.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions src/engine/export.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
};
Expand Down
204 changes: 204 additions & 0 deletions src/engine/export/grub.cpp
Original file line number Diff line number Diff line change
@@ -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 changes: 38 additions & 0 deletions src/engine/export/grub.h
Original file line number Diff line number Diff line change
@@ -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() {}
};
Loading