godot/modules/chibi/cp_loader_xm.cpp

727 lines
20 KiB
C++

/*************************************************************************/
/* cp_loader_xm.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#include "cp_loader_xm.h"
#include "cp_tables.h"
#define ABORT_LOAD \
{ \
file->close(); \
return FILE_CORRUPTED; \
}
CPLoader::Error CPLoader_XM::load_song(const char *p_file, CPSong *p_song, bool p_sampleset) {
song = p_song;
if (file->open(p_file, CPFileAccessWrapper::READ)) {
return FILE_CANNOT_OPEN;
};
/**************************************
LOAD HEADER
***************************************/
file->get_byte_array(header.idtext, 17);
header.idtext[17] = 0;
file->get_byte_array(header.songname, 20);
header.songname[20] = 0;
header.hex1a = file->get_byte();
if (header.hex1a != 0x1A) { //XM "magic" byte.. this sucks :)
file->close();
return FILE_UNRECOGNIZED;
}
//magic byte sucks, but can't do much about it..
song->reset(); //must reset the song
song->set_name((const char *)header.songname);
file->get_byte_array(header.trackername, 20);
header.trackername[20] = 0;
header.version = file->get_word();
header.headersize = file->get_dword();
header.songlength = file->get_word();
header.restart_pos = file->get_word();
header.channels_used = file->get_word();
header.patterns_used = file->get_word();
header.instruments_used = file->get_word();
song->set_linear_slides(file->get_word());
song->set_speed(file->get_word());
song->set_tempo(file->get_word());
song->set_instruments(true);
file->get_byte_array(header.orderlist, 256);
for (int i = 0; i < header.songlength; i++) {
if (i > 199)
break;
song->set_order(i, header.orderlist[i]);
}
/**************************************
LOAD PATTERNS
***************************************/
for (int i = 0; i < header.patterns_used; i++) {
uint32_t aux, rows;
aux = file->get_dword(); //length
aux = file->get_byte(); //packing type
rows = aux = file->get_word(); //rows!
song->get_pattern(i)->set_length(aux);
aux = file->get_word(); //packed size
if (aux == 0)
continue;
//unpaaack!
for (int j = 0; j < (int)rows; j++)
for (int k = 0; k < header.channels_used; k++) {
CPNote aux_note;
uint8_t aux_byte;
//uint8_t field;
aux_byte = file->get_byte(); //packing type
if (!(aux_byte & 0x80)) {
aux_note.note = aux_byte;
aux_byte = 0xFE; //if bit 7 not set, read all of them except the note
}
if (aux_byte & 1) aux_note.note = file->get_byte();
if (aux_byte & 2) aux_note.instrument = file->get_byte();
if (aux_byte & 4) aux_note.volume = file->get_byte();
if (aux_byte & 8) aux_note.command = file->get_byte();
if (aux_byte & 16) aux_note.parameter = file->get_byte();
if (aux_note.note != CPNote::EMPTY) {
if (aux_note.note == 97)
aux_note.note = CPNote::OFF;
else {
aux_note.note += 11; //octave minus one (XM C-0 is 1, not zero )
}
}
if (aux_note.instrument != CPNote::EMPTY) {
if ((aux_note.instrument > 0) && (aux_note.instrument < 100))
aux_note.instrument--;
else
aux_note.instrument = CPNote::EMPTY;
}
if (aux_note.volume != CPNote::EMPTY) {
if (aux_note.volume < 0x10) {
} else if (aux_note.volume < 0x50) {
aux_note.volume -= 0x10;
} else if (aux_note.volume < 0x60) {
//
aux_note.volume = CPNote::EMPTY;
} else if (aux_note.volume < 0x70) {
//60 -- volume slide down
aux_note.volume -= 0x60;
if (aux_note.volume > 9) aux_note.volume = 9;
aux_note.volume += 95;
} else if (aux_note.volume < 0x80) {
//70 -- volume slide up
aux_note.volume -= 0x70;
if (aux_note.volume > 9) aux_note.volume = 9;
aux_note.volume += 85;
} else if (aux_note.volume < 0x90) {
//80 -- fine volume slide down
aux_note.volume -= 0x80;
if (aux_note.volume > 9) aux_note.volume = 9;
aux_note.volume += 75;
} else if (aux_note.volume < 0xA0) {
//9 -- fine volume slide up
aux_note.volume -= 0x90;
if (aux_note.volume > 9) aux_note.volume = 9;
aux_note.volume += 65;
} else if (aux_note.volume < 0xB0) {
//A -- set vibrato speed
aux_note.volume = CPNote::EMPTY;
} else if (aux_note.volume < 0xC0) {
//B -- vibrato
aux_note.volume -= 0xB0;
if (aux_note.volume > 9) aux_note.volume = 9;
aux_note.volume += 203;
} else if (aux_note.volume < 0xD0) {
//C -- set panning
int aux = aux_note.volume -= 0xC0;
aux = aux * 65 / 0xF;
aux_note.volume = 128 + aux;
} else if (aux_note.volume < 0xE0) {
aux_note.volume = CPNote::EMPTY;
} else if (aux_note.volume < 0xF0) {
aux_note.volume = CPNote::EMPTY;
} else {
//F -- tone porta
aux_note.volume -= 0xF0;
aux_note.volume *= 9;
aux_note.volume /= 0xF;
aux_note.volume += 193;
}
}
if (aux_note.command != CPNote::EMPTY) {
switch (aux_note.command) {
case 0x0:
aux_note.command = 'J' - 'A';
break;
case 0x1:
aux_note.command = 'F' - 'A';
break;
case 0x2:
aux_note.command = 'E' - 'A';
break;
case 0x3:
aux_note.command = 'G' - 'A';
break;
case 0x4:
aux_note.command = 'H' - 'A';
break;
case 0x5:
aux_note.command = 'L' - 'A';
break;
case 0x6:
aux_note.command = 'K' - 'A';
break;
case 0x7:
aux_note.command = 'R' - 'A';
break;
case 0x8:
aux_note.command = 'X' - 'A';
break;
case 0x9:
aux_note.command = 'O' - 'A';
break;
case 0xa:
aux_note.command = 'D' - 'A';
break;
case 0xb:
aux_note.command = 'B' - 'A';
break;
case 0xc:
//printf("XM Import: Warning! effect C (set volume) not implemented!\n");
break;
case 0xd:
aux_note.command = 'C' - 'A';
break;
case 0xe: /* Extended effects */
aux_note.command = 'S' - 'A';
switch (aux_note.parameter >> 4) {
case 0x1: /* XM fine porta up */
if (!(aux_note.parameter & 0xF)) {
aux_note.command = CPNote::EMPTY;
aux_note.parameter = 0;
break;
}
aux_note.command = 'F' - 'A';
aux_note.parameter = 0xF0 | (aux_note.parameter & 0xF);
break;
case 0x2: /* XM fine porta down */
if (!(aux_note.parameter & 0xF)) {
aux_note.command = CPNote::EMPTY;
aux_note.parameter = 0;
break;
}
aux_note.command = 'E' - 'A';
aux_note.parameter = 0xF0 | (aux_note.parameter & 0xF);
break;
case 0xa: /* XM fine volume up */
if (!(aux_note.parameter & 0xF)) {
aux_note.command = CPNote::EMPTY;
aux_note.parameter = 0;
break;
}
aux_note.command = 'D' - 'A';
aux_note.parameter = 0x0F | ((aux_note.parameter & 0xF) << 4);
break;
case 0xb: /* XM fine volume down */
if (!(aux_note.parameter & 0xF)) {
aux_note.command = CPNote::EMPTY;
aux_note.parameter = 0;
break;
}
aux_note.command = 'D' - 'A';
aux_note.parameter = 0xF0 | (aux_note.parameter & 0xF);
break;
case 0x9: /* XM fine volume down */
if (!(aux_note.parameter & 0xF)) {
aux_note.command = CPNote::EMPTY;
aux_note.parameter = 0;
break;
}
aux_note.command = 'Q' - 'A';
aux_note.parameter = 0x00 | (aux_note.parameter & 0xF);
break;
case 0xc: //notecut
aux_note.parameter = 0xC0 | (aux_note.parameter & 0xF);
break;
case 0xd: //notedelay
aux_note.parameter = 0xD0 | (aux_note.parameter & 0xF);
break;
case 0xe: //patterndelay
aux_note.parameter = 0xE0 | (aux_note.parameter & 0xF);
break;
}
break;
case 0xf:
if (aux_note.parameter < 32) {
aux_note.command = 'A' - 'A';
} else {
aux_note.command = 'T' - 'A';
}
break;
case 'G' - 55:
aux_note.command = 'V' - 'A';
break;
case 'H' - 55:
aux_note.command = 'W' - 'A';
break;
case 'K' - 55:
if (aux_note.note != CPNote::EMPTY) break;
aux_note.note = CPNote::OFF;
break;
case 'P' - 55:
aux_note.command = 'P' - 'A';
break;
case 'R' - 55:
aux_note.command = 'Q' - 'A';
break;
case 'T' - 55:
aux_note.command = 'I' - 'A';
break;
default: {
aux_note.command = CPNote::EMPTY;
}
}
}
song->get_pattern(i)->set_note(k, j, aux_note);
}
}
/**************************************
LOAD INSTRUMENTS!
***************************************/
for (int i = 0; i < header.instruments_used; i++) {
uint32_t aux;
int sampnum;
CPInstrument &instrument = *song->get_instrument(i);
uint32_t cpos = file->get_pos();
//printf("pos is %i\n",cpos);
/* +4 */ uint32_t hsize = file->get_dword(); //header length
char instrname[23];
instrname[22] = 0;
file->get_byte_array((uint8_t *)instrname, 22);
//XM_LOAD_DEBUG printf("name is %s\n",instrname);
/* +27 */ aux = file->get_byte(); //byte that must be ignored
//XM_LOAD_DEBUG printf("header size is %i\n",hsize);
/* +29 */ sampnum = file->get_word();
//XM_LOAD_DEBUG printf("samples %i\n",sampnum);
instrument.set_name(instrname);
// printf("Header Len: %i, CPInstrument %i, %i samples , name: s,\n",hsize,i,sampnum,instrname);
if (sampnum == 0) {
//aux=file->get_dword(); //Why is this for? -- for nothing, skipped
if (hsize) {
file->seek(cpos + hsize); //skip header if size has been specified
}
continue;
}
/* +33 */ file->get_dword();
if (Error result = load_instrument_internal(&instrument, false, cpos, hsize, sampnum)) {
CP_PRINTERR("Error loading instrument");
file->close();
return result;
}
}
//
file->close();
return FILE_OK;
}
CPLoader::Error CPLoader_XM::load_instrument_internal(CPInstrument *p_instr, bool p_xi, int p_cpos, int p_hsize, int p_sampnum) {
int sampnum;
uint32_t aux;
uint8_t notenumb[96];
uint16_t panenv[24], volenv[24];
int volpoints, panpoints;
int vol_loop_begin, vol_loop_end, vol_sustain_loop;
int pan_loop_begin, pan_loop_end, pan_sustain_loop;
char instrname[23];
int sample_index[16] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; //-1 means no index!
instrname[22] = 0;
/* +129 */ file->get_byte_array((uint8_t *)notenumb, 96);
for (int j = 0; j < 24; j++) {
volenv[j] = file->get_word();
}
for (int j = 0; j < 24; j++) {
panenv[j] = file->get_word();
}
/* +177 */
/* +225 */
/* +226 */ volpoints = file->get_byte();
/* +227 */ panpoints = file->get_byte();
/* +230 */ vol_sustain_loop = file->get_byte();
/* +228 */ vol_loop_begin = file->get_byte();
/* +229 */ vol_loop_end = file->get_byte();
//XM_LOAD_DEBUG printf("1- volpoints: %i, panpoints: %i, susloop: %i, loop begin: %i, loop end %i\n",volpoints,panpoints,vol_sustain_loop,vol_loop_begin,vol_loop_end);
pan_sustain_loop = file->get_byte();
/* +231 */ pan_loop_begin = file->get_byte();
/* +232 */ pan_loop_end = file->get_byte();
/* +234 */ aux = file->get_byte();
p_instr->get_volume_envelope()->reset();
p_instr->get_volume_envelope()->set_enabled(aux & 1);
p_instr->get_volume_envelope()->set_sustain_loop_enabled((aux & 2) ? true : false);
p_instr->get_volume_envelope()->set_loop_enabled((aux & 4) ? true : false);
/* +235 */ aux = file->get_byte();
p_instr->get_pan_envelope()->reset();
p_instr->get_pan_envelope()->set_enabled(aux & 1);
p_instr->get_pan_envelope()->set_sustain_loop_enabled((aux & 2) ? true : false);
p_instr->get_pan_envelope()->set_loop_enabled((aux & 4) ? true : false);
/* +239 */ aux = file->get_dword(); // sadly, cant use those
/* +241 */ p_instr->set_volume_fadeout(file->get_word() >> 4);
/* +243 */ aux = file->get_word(); // reserved!
for (int j = 0; j < volpoints; j++) {
int ofs = volenv[j * 2];
int val = volenv[j * 2 + 1];
p_instr->get_volume_envelope()->add_position(ofs, val);
}
//make sure minimum is 2
while (p_instr->get_volume_envelope()->get_node_count() < 2) {
p_instr->get_volume_envelope()->add_position(p_instr->get_volume_envelope()->get_node_count() * 20, 64);
}
for (int j = 0; j < panpoints; j++) {
int ofs = panenv[j * 2];
int val = panenv[j * 2 + 1];
p_instr->get_pan_envelope()->add_position(ofs, val - 32);
}
//make sure minimum is 2
while (p_instr->get_pan_envelope()->get_node_count() < 2) {
p_instr->get_pan_envelope()->add_position(p_instr->get_pan_envelope()->get_node_count() * 20, 0);
}
p_instr->get_volume_envelope()->set_loop_begin(vol_loop_begin);
p_instr->get_volume_envelope()->set_loop_end(vol_loop_end);
p_instr->get_volume_envelope()->set_sustain_loop_end(vol_sustain_loop);
p_instr->get_volume_envelope()->set_sustain_loop_begin(vol_sustain_loop);
p_instr->get_pan_envelope()->set_loop_begin(pan_loop_begin);
p_instr->get_pan_envelope()->set_loop_end(pan_loop_end);
p_instr->get_pan_envelope()->set_sustain_loop_end(pan_sustain_loop);
p_instr->get_pan_envelope()->set_sustain_loop_begin(pan_sustain_loop);
if (!p_xi) {
if ((file->get_pos() - p_cpos) < p_hsize) {
uint8_t junkbuster[500];
//printf("extra junk XM instrument in header! hsize is %i, extra junk: %i\n",p_hsize,(file->get_pos()-p_cpos));
//printf("extra: %i\n",p_hsize-(file->get_pos()-p_cpos));
file->get_byte_array((uint8_t *)junkbuster, p_hsize - (file->get_pos() - p_cpos));
}
sampnum = p_sampnum;
} else {
uint8_t junkbuster[500];
file->get_byte_array((uint8_t *)junkbuster, 20); //14 bytes?
sampnum = file->get_word();
}
CPSampleManager *sm = CPSampleManager::get_singleton();
/*SAMPLE!!*/
for (int j = 0; j < sampnum; j++) {
if (j > 16) ABORT_LOAD;
int s_idx = -1;
for (int s = 0; s < CPSong::MAX_SAMPLES; s++) {
if (song->get_sample(s)->get_sample_data().is_null()) {
//empty sample!
s_idx = s;
break;
}
}
if (s_idx == -1) ABORT_LOAD;
//printf("free sample: %i\n",s_idx);
CPSample &sample = *song->get_sample(s_idx);
int sample_size = file->get_dword();
int tmp_loop_begin = file->get_dword();
int tmp_loop_end = file->get_dword();
sample.set_default_volume(file->get_byte());
uint8_t ftb = file->get_byte();
int8_t *fts = (int8_t *)&ftb;
int finetune = *fts;
uint32_t flags = file->get_byte();
if (flags & 16) { // is 16 bits.. at flag 16.. fun :)
tmp_loop_end /= 2;
tmp_loop_begin /= 2;
sample_size /= 2;
}
CPSample_ID sample_data = sm->create(flags & 16, false, sample_size);
sample.set_sample_data(sample_data);
sm->set_loop_begin(sample_data, tmp_loop_begin);
sm->set_loop_end(sample_data, tmp_loop_end + tmp_loop_begin);
sm->set_loop_type(sample_data, (flags & 3) ? ((flags & 2) ? CP_LOOP_BIDI : CP_LOOP_FORWARD) : CP_LOOP_NONE);
sample.set_pan_enabled(true);
sample.set_pan(file->get_byte() * 64 / 255);
uint8_t noteb = file->get_byte();
int8_t *notes = (int8_t *)&noteb;
int note_offset = *notes;
note_offset += 48;
//note_offset+=60;
//int linear_period=10*12*16*4 - (note_offset)*16*4 - finetune/2;
//int freq=(int)(8363*pow(2.0,(double)(6*12*16*4 - linear_period) / (double)(12*16*4)));
//sm->set_c5_freq( sample_data, freq);
sm->set_c5_freq(sample_data, CPTables::get_linear_frequency(CPTables::get_linear_period(note_offset << 1, finetune)));
//printf("NOTE %i,fine %i\n",note_offset,finetune);
char auxb;
auxb = file->get_byte(); //reserved?
file->get_byte_array((uint8_t *)instrname, 22);
sample.set_name(instrname);
sample_index[j] = s_idx;
}
/*SAMPLE __DATA__!!*/
for (int j = 0; j < sampnum; j++) {
if (sample_index[j] == -1) continue;
CPSample *sample = song->get_sample(sample_index[j]);
CPSample_ID sid = sample->get_sample_data();
sm->lock_data(sid);
void *dataptr = sm->get_data(sid);
if (sm->is_16bits(sid)) {
int16_t old = 0;
for (int k = 0; k < sm->get_size(sid); k++) {
int16_t newsample;
int16_t sampleval = file->get_word();
newsample = sampleval + old;
old = newsample;
((int16_t *)dataptr)[k] = newsample;
//sm->set_data( sid, k, newsample );
}
} else {
int8_t old = 0;
for (int k = 0; k < sm->get_size(sid); k++) {
int8_t newsample;
int8_t sampleval = file->get_byte();
newsample = sampleval + old;
old = newsample;
((int8_t *)dataptr)[k] = newsample;
//sm->set_data( sid, k, (int16_t)newsample << 8 );
}
}
sm->unlock_data(sid);
}
for (int j = 0; j < 96; j++) {
int val = notenumb[j];
if ((val < 0) || (val > 15))
continue;
else
val = sample_index[val];
if (val == -1) continue;
p_instr->set_sample_number(12 + j, val);
}
return FILE_OK;
}
CPLoader::Error CPLoader_XM::load_sample(const char *p_file, CPSample *p_sample) {
return FILE_UNRECOGNIZED;
}
/* Compute CPInstrument Info */
CPLoader::Error CPLoader_XM::load_instrument(const char *p_file, CPSong *p_song, int p_instr_idx) {
if (file->open(p_file, CPFileAccessWrapper::READ)) return FILE_CANNOT_OPEN;
//int i;
song = p_song;
CPInstrument &instr = *p_song->get_instrument(p_instr_idx);
int aux;
char buffer[500];
file->get_byte_array((uint8_t *)buffer, 0x15);
buffer[8] = 0;
if (buffer[0] != 'E' ||
buffer[1] != 'x' ||
buffer[2] != 't' ||
buffer[3] != 'e' ||
buffer[4] != 'n' ||
buffer[5] != 'd' ||
buffer[6] != 'e' ||
buffer[7] != 'd') {
file->close();
return FILE_UNRECOGNIZED;
}
file->get_byte_array((uint8_t *)buffer, 0x16);
buffer[0x16] = 0;
instr.set_name(buffer);
aux = file->get_byte(); //says ignore ti
/*if(aux!=0x1a) { I'm not sure. this is supposed to be ignored...
file->close();
return FILE_UNRECOGNIZED;
} */
file->get_byte_array((uint8_t *)buffer, 0x14); //somethingaboutthename
aux = file->get_word(); //version or blahblah
if (load_instrument_internal(&instr, true, 0, 0)) {
file->close();
return FILE_CORRUPTED;
}
file->close(); //ook, we got it..
return FILE_OK;
}
CPLoader_XM::CPLoader_XM(CPFileAccessWrapper *p_file) {
file = p_file;
}
CPLoader_XM::~CPLoader_XM() {
}