basic export implemented (still need to implement TIA and samples)

This commit is contained in:
AArt1256 2026-01-22 16:40:25 +01:00
parent 79902f472f
commit 68979270ad
7 changed files with 367 additions and 0 deletions

View file

@ -896,6 +896,7 @@ src/engine/export/tiuna.cpp
src/engine/export/zsm.cpp
src/engine/export/ipod.cpp
src/engine/export/grub.cpp
src/engine/export/saxotone.cpp
src/engine/effect/abstract.cpp
src/engine/effect/dummy.cpp

View file

@ -585,6 +585,7 @@ class DivEngine {
friend class DivExportZSM;
friend class DivExportiPod;
friend class DivExportGRUB;
friend class DivExportSaxotone;
public:
DivSong song;

View file

@ -25,6 +25,7 @@
#include "export/zsm.h"
#include "export/ipod.h"
#include "export/grub.h"
#include "export/saxotone.h"
DivROMExport* DivEngine::buildROM(DivROMExportOptions sys) {
DivROMExport* exporter=NULL;
@ -47,6 +48,9 @@ DivROMExport* DivEngine::buildROM(DivROMExportOptions sys) {
case DIV_ROM_GRUB:
exporter=new DivExportGRUB;
break;
case DIV_ROM_SAXOTONE:
exporter=new DivExportSaxotone;
break;
default:
exporter=new DivROMExport;
break;

View file

@ -34,6 +34,7 @@ enum DivROMExportOptions {
DIV_ROM_SAP_R,
DIV_ROM_IPOD,
DIV_ROM_GRUB,
DIV_ROM_SAXOTONE,
DIV_ROM_MAX
};

View file

@ -0,0 +1,311 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2026 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 "saxotone.h"
#include "../engine.h"
#include "../ta-log.h"
#include <fmt/printf.h>
#include <array>
#include <vector>
void saxotone_write_table_lo(SafeWriter *w, unsigned int *offs, size_t off_amt) {
w->writeText(".byte ");
for (size_t i = 0; i < off_amt; i++) {
w->writeText(fmt::sprintf("<($%04x+sample_dir)%c",offs[i],i==(off_amt-1)?'\n':','));
}
}
void saxotone_write_table_hi(SafeWriter *w, unsigned int *offs, size_t off_amt) {
w->writeText(".byte ");
for (size_t i = 0; i < off_amt; i++) {
w->writeText(fmt::sprintf("<($%04x+sample_dir)%c",offs[i],i==(off_amt-1)?'\n':','));
}
}
void DivExportSaxotone::run() {
int SAXOTONE=-1;
int TIA=-1;
int IGNORED=0;
// Locate system index.
for (int i=0; i<e->song.systemLen; i++) {
if (e->song.system[i] == DIV_SYSTEM_SAXOTONE) {
if (SAXOTONE>=0) {
IGNORED++;
logAppendf("Ignoring duplicate Saxotone id %d",i);
continue;
}
SAXOTONE=i;
logAppendf("Saxotone detected as chip id %d",i);
continue;
} else if (e->song.system[i] == DIV_SYSTEM_TIA) {
if (TIA>=0) {
IGNORED++;
logAppendf("Ignoring duplicate TIA id %d",i);
continue;
}
TIA=i;
logAppendf("TIA detected as chip id %d",i);
continue;
} else {
IGNORED++;
logAppendf("Ignoring chip id %d, system id %d",i,(int)e->song.system[i]);
continue;
}
}
if (SAXOTONE<0) {
logAppendf("ERROR: Could not find Saxotone");
failed=true;
running=false;
return;
}
if (TIA<0) {
logAppendf("ERROR: Could not find TIA");
failed=true;
running=false;
return;
}
if (IGNORED>0) {
logAppendf("WARNING: ASM export ignoring %d unsupported system%c",IGNORED,IGNORED>1?'s':' ');
}
e->stop();
e->setOrder(0);
logAppend("playing and logging register writes...");
e->synchronizedSoft([&]() {
e->warnings="";
auto w = new SafeWriter;
w->init();
e->disCont[SAXOTONE].dispatch->renderSamples(0);
const DivMemoryComposition *memCompo = e->disCont[SAXOTONE].dispatch->getMemCompo(0);
unsigned int wtOff[256];
unsigned int sampOff[256];
unsigned int sampEnd[256];
int wtOff_ind = 0;
int samp_ind = 0;
for (const DivMemoryEntry& k: memCompo->entries) {
if (k.type == DIV_MEMORY_WAVE_STATIC) {
wtOff[wtOff_ind++] = k.begin;
} else if (k.type == DIV_MEMORY_SAMPLE) {
sampOff[samp_ind] = k.begin;
sampEnd[samp_ind] = k.end;
samp_ind++;
}
}
// wavetab O
// samptab O
// samptabend O
// instab/wav O
// freqtab O
// ords(0-4) ?
// ordswait X
int wave_amt = e->song.waveLen;
int samp_amt = e->song.sampleLen;
int ins_amt = e->song.insLen;
// wavetable pointers
w->writeText(".page $100\n");
w->writeText("wavetablo\n");
saxotone_write_table_lo(w, wtOff, wave_amt);
w->writeText("wavetabhi\n");
saxotone_write_table_hi(w, wtOff, wave_amt);
w->writeText(".endpage\n\n");
// sample pointers
w->writeText("samptablo\n");
saxotone_write_table_lo(w, sampOff, samp_amt);
w->writeText("samptabhi\n");
saxotone_write_table_hi(w, sampOff, samp_amt);
w->writeText("samptabend\n");
saxotone_write_table_hi(w, sampEnd, samp_amt);
w->writeText("\n");
// waveform MACRO pointers
w->writeText("instablo\n");
w->writeText(".byte ");
for (int i = 0; i < ins_amt; i++) {
w->writeText(fmt::sprintf("<(inswav_%d)%c",i,i==(ins_amt-1)?'\n':','));
}
w->writeText("instabhi\n");
w->writeText(".byte ");
for (int i = 0; i < ins_amt; i++) {
w->writeText(fmt::sprintf(">(inswav_%d)%c",i,i==(ins_amt-1)?'\n':','));
}
w->writeText("\n");
for (int i = 0; i < ins_amt; i++) {
w->writeText(fmt::sprintf("inswav_%d\n.byte ",i));
unsigned char wave_len = e->song.ins[i]->std.waveMacro.len;
unsigned char wave_loop = e->song.ins[i]->std.waveMacro.loop;
// if one-shot, set the loop point the last wave macro position
if (wave_loop == 0xFF) wave_loop = wave_len-1;
for (int x = 0; x < wave_len; x++) {
w->writeText(fmt::sprintf("$%02x,", e->song.ins[i]->std.waveMacro.val[x]+1));
}
w->writeText(fmt::sprintf("$00,$%02x\n", wave_loop)); // loop
}
// get wave length (for the freq table)
int waveWidth;
switch(e->song.systemFlags[SAXOTONE].getInt("waveWidth",1)) {
case 0: waveWidth=16; break;
case 2: waveWidth=64; break;
case 3: waveWidth=128; break;
case 4: waveWidth=256; break;
default: waveWidth=32; break;
}
int CHIP_FREQBASE = 32*76*waveWidth;
unsigned short note_freq[256];
int used_note_index[256];
for (int i = 0; i < 256; i++) used_note_index[i] = -1;
unsigned char note_ind = 0;
int ord_len = e->curSubSong->ordersLen;
// order pointers
// TOOD: what to do for the TIA channel?
for (int ch = 0; ch < 4; ch++) {
int pat_amt = 0;
for (int pat = 0; pat < ord_len; pat++) {
if (e->curSubSong->orders.ord[ch][pat] >= pat_amt)
pat_amt = e->curSubSong->orders.ord[ch][pat];
}
pat_amt++;
w->writeText(fmt::sprintf("ord%dlo\n",ch));
w->writeText(".byte ");
for (int i = 0; i < ord_len; i++) {
w->writeText(fmt::sprintf("<(ord%d_%02x)%c",ch,i,i==(ord_len-1)?'\n':','));
}
w->writeText(fmt::sprintf("ord%dhi\n",ch));
w->writeText(".byte ");
for (int i = 0; i < ord_len; i++) {
w->writeText(fmt::sprintf(">(ord%d_%02x)%c",ch,i,i==(ord_len-1)?'\n':','));
}
w->writeText("\n");
// get the OPTMIZED frequency table and pattern data
for (int pat = 0; pat < pat_amt; pat++) {
w->writeText(fmt::sprintf("ord%d_%02x\n",ch,pat));
for (int row = 0; row < e->curSubSong->patLen; row++) {
short *cur_row = e->curSubSong->pat[ch].getPattern(pat, false)->newData[row];
short note = cur_row[DIV_PAT_NOTE];
if (note >= 0 && note < 180) {
if (used_note_index[note] == -1) {
// note from C-(-5) to B-9
// NOTE_FREQUENCY(parent->calcArp(chan[i].note,chan[i].std.arp.val));
double baseFreq = e->calcBaseFreq(e->disCont[SAXOTONE].dispatch->chipClock,
CHIP_FREQBASE,
note-60,
false);
// parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,false,2,chan[i].pitch2,chipClock,CHIP_FREQBASE);
int freq = e->calcFreq(baseFreq,0,0,false,false,2,0,e->disCont[SAXOTONE].dispatch->chipClock,CHIP_FREQBASE);
printf("%d\n",freq);
if (freq<0) freq=0;
if (freq>((int)waveWidth<<7)) freq=(int)waveWidth<<7;
note_freq[note_ind] = (unsigned short)freq;
used_note_index[note] = note_ind;
note_ind++;
}
note = used_note_index[note]+1;
} else {
note = 0;
}
// get instrument byte
unsigned char inst = cur_row[DIV_PAT_INS];
if (inst == -1) inst = 0;
else inst++;
// write the note and instrument bytes
w->writeText(fmt::sprintf(" .byte $%02x,$%02x\n", inst, note));
}
}
}
// write the OPTIMIZED frequency table
w->writeText("freqtablo\n");
w->writeText(".byte ");
for (int i = 0; i < note_ind; i++) {
w->writeText(fmt::sprintf("<($%04x)%c",note_freq[i],i==(note_ind-1)?'\n':','));
}
w->writeText("freqtabhi\n");
w->writeText(".byte ");
for (int i = 0; i < note_ind; i++) {
w->writeText(fmt::sprintf(">($%04x)%c",note_freq[i],i==(note_ind-1)?'\n':','));
}
w->writeText("\n");
output.push_back(DivROMExportOutput("music.asm",w));
});
logAppend("writing data...");
progress[0].amount=0.95f;
progress[0].amount=1.0f;
logAppend("finished!");
running=false;
}
bool DivExportSaxotone::go(DivEngine* eng) {
progress[0].name="Progress";
progress[0].amount=0.0f;
e=eng;
running=true;
failed=false;
mustAbort=false;
exportThread=new std::thread(&DivExportSaxotone::run,this);
return true;
}
void DivExportSaxotone::wait() {
if (exportThread!=NULL) {
logV("waiting for export thread...");
exportThread->join();
delete exportThread;
}
}
void DivExportSaxotone::abort() {
mustAbort=true;
wait();
}
bool DivExportSaxotone::isRunning() {
return running;
}
bool DivExportSaxotone::hasFailed() {
return failed;
}
DivROMExportProgress DivExportSaxotone::getProgress(int index) {
if (index<0 || index>1) return progress[1];
return progress[index];
}

View file

@ -0,0 +1,38 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2026 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 DivExportSaxotone: 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);
~DivExportSaxotone() {}
};

View file

@ -142,4 +142,15 @@ void DivEngine::registerROMExports() {
},
false, DIV_REQPOL_ANY
);
romExportDefs[DIV_ROM_SAXOTONE]=new DivROMExportDef(
"Saxotone", "Natt & AArt1256",
"Saxotone export\n"
"for use with the Saxotone Atari 2600 beeper engine",
"Text/Binary files", NULL,
{
DIV_SYSTEM_SAXOTONE, DIV_SYSTEM_TIA
},
false, DIV_REQPOL_ANY
);
}