/*************************************************************************/ /* cp_loader_xm.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* http://www.godotengine.org */ /*************************************************************************/ /* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ /* */ /* 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;i199) break; song->set_order(i,header.orderlist[i]); } /************************************** LOAD PATTERNS ***************************************/ for (int i=0;iget_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;kget_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;iget_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;jget_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;jget_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)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;j16) ABORT_LOAD; int s_idx=-1; for (int s=0;sget_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*)¬eb; 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;jget_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;kget_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;kget_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(){ }