/*
    PsyTexx: mod.c. Mod load/save/play functions.
    Copyright (C) 2004  Zolotov Alexandr

    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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/
//*** Contact info: Zolotov Alexandr (NightRadio project)
//***               Ekaterinburg. Russia.
//***               Email: observer_page@mail.ru
//***                      warmplace@warmplace.ru
//***                      nightradio@knoppix.ru
//***               WWW: warmplace.ru

#include "PceNativeCall.h"
#include "modplay.h"
#include "finetune.h"

#define BUFSIZE 4096

//************************************************
//********* LARGE MEMORY FUNCTIONS ***************
//************************************************
#define USE_STORAGE
#define memNewChunkFlagNonMovable    0x0200
#define memNewChunkFlagAllowLarge    0x1000  // this is not in the sdk *g*
SysAppInfoPtr ai1, ai2, appInfo;
SysAppInfoPtr SysGetAppInfo(SysAppInfoPtr *rootAppPP,
                            SysAppInfoPtr *actionCodeAppPP)
                            SYS_TRAP(sysTrapSysUIBusy);
void* mem_new( unsigned long size )
{
	unsigned short ownID;
#ifdef USE_STORAGE
	unsigned long heap = 1;
#else
	unsigned long heap = 0;
#endif
	ownID = appInfo->memOwnerID;
	return MemChunkNew( MemHeapID( 0, heap ), size, ownID | memNewChunkFlagNonMovable | memNewChunkFlagAllowLarge );
}
void mem_free( void *ptr )
{
    MemPtrFree(ptr);
}
void mem_on(void)
{
#ifdef USE_STORAGE
    MemSemaphoreRelease(1);
#endif
}
void mem_off(void)
{
#ifdef USE_STORAGE
    MemSemaphoreReserve(1);
#endif
}

//************************************************
//********* NEW ADDITION :) **********************
//************************************************
ulong CHANNELS = 4;
#define CHANNELS_MAX 12
#define PAT_SIZE (ulong)(CHANNELS*4*64)
void print(long num){
	char str[20];
	StrPrintF(str,"%ld",num);
	WinDrawChars(str,10,0,0);
	FrmAlert(Delete);
}

uchar sin_tab[256] =
  {
0, 3, 6, 9, 12, 15, 18, 21, 25, 28, 31, 34, 37, 40, 43, 46,
49, 52, 56, 59, 62, 65, 68, 71, 74, 77, 80, 83, 86, 89, 92, 95, 
97, 100, 103, 106, 109, 112, 115, 117, 120, 123, 126, 128, 131, 134, 136, 139, 
142, 144, 147, 149, 152, 154, 157, 159, 162, 164, 167, 169, 171, 174, 176, 178, 
180, 183, 185, 187, 189, 191, 193, 195, 197, 199, 201, 203, 205, 207, 209, 211, 
212, 214, 216, 217, 219, 221, 222, 224, 225, 227, 228, 229, 231, 232, 233, 235, 
236, 237, 238, 239, 240, 242, 243, 243, 244, 245, 246, 247, 248, 249, 249, 250, 
251, 251, 252, 252, 253, 253, 254, 254, 254, 255, 255, 255, 255, 255, 255, 255, 
255, 255, 255, 255, 255, 255, 255, 255, 254, 254, 254, 253, 253, 252, 252, 251, 
251, 250, 249, 249, 248, 247, 246, 245, 245, 244, 243, 242, 241, 240, 238, 237, 
236, 235, 234, 232, 231, 230, 228, 227, 225, 224, 222, 221, 219, 218, 216, 214, 
213, 211, 209, 207, 205, 203, 201, 200, 198, 196, 194, 191, 189, 187, 185, 183, 
181, 179, 176, 174, 172, 169, 167, 165, 162, 160, 157, 155, 152, 150, 147, 145, 
142, 139, 137, 134, 131, 129, 126, 123, 120, 118, 115, 112, 109, 106, 104, 101, 
98, 95, 92, 89, 86, 83, 80, 77, 74, 71, 68, 65, 62, 59, 56, 53, 
50, 47, 44, 41, 37, 34, 31, 28, 25, 22, 19, 16, 12, 9, 6, 3
  };
//************************************************
//************************************************
//************************************************

typedef struct {
   long file_is_open; //For WIN32 
   long out_file;     //For WIN32

   char* resonance_buffer; //256 bytes
   
   void* record_buf;
   long record_pos;

   void* echo_buffer;
   long echo_size;
   long echo_pointer;
   long echo_flange_delay;
   long echo_vol;

   void* reverb_offset;
   long reverb_number;

   long win32_frameCount;
   void* win32_buffer;

   char *rec_pointer;
   long rec_count;
   long rec_add;

   char *_arm_code;
   channel *_channels;
   long _patternticks;
   long _onetick;
   ulong _sp;
   ulong _speed;
   ulong _bpm;
   note **_patterndata;
   note **_patterndata2;
   module *_song;
   module *_song2;
   char *_buffer;
   channel *_one;
   uint *_fine;
   uint *_fine2;
   ulong _sampleticksconst;
   char *_pp;
   char **_sampledata;
   char **_sampledata2;
   long i2, size;
   ulong _update;
   uint _tablepos;
   uint _patternpos;
   UInt16 _num_of_mods;
   uint _playrate;
   uint _buffer_size;
   uint CHANNELS;
   uint VOLUME;
   char _scope[60];
   char _status;
} user_info;
user_info mod_info;

void effect(channel *, user_info *);
void playnote(uint,uint,channel*,ulong);
void slidenote(uint,uint,channel*);
void fillbuffer(char *, user_info *);
void worknote(note *, channel *, user_info *);
void start_clear(void); //clear output before start playing.
void convert_song_for_ARM(void);

MemHandle armH;
char *arm_code;

char model=0;  //0 - PalmOS3; 1 - PalmOS4..5
char status=0; //0-pause; 1-one sample mode; 2-play; 3-one pattern play;
void set_volume(long); //64=normal; 128=x2; 256=x4;
uint VOLUME = 64;
uint ECHO = 100;
uint ECHOLEN = 24;
uchar CLASSIC = 0;
uchar reverb_offset[10];
long reverb_number = 10;
ulong one_sample_limit = 65000; //Limit for one sample playing. It's useless thing, when we want to play selected sample area.

module  song;
module  song2;           //(ARM)
MemHandle songH;
char *  sampledata[31];
char *  sampledata2[31]; //(ARM)
note *  patterndata[128]; //Array of pointers to patterns.
MemHandle patterndataH[128]; //Handles
note *  patterndata2[128]; //Array of pointers to patterns (ARM).
char    buffer[256]; //Main sound buffer.

uint    maxp; //maximal pattern number in song;
uint    playrate;
uint    tablepos;
uint    patternpos;
ulong   patternticks;
ulong   patternticksaim;
ulong   sampleticksconst;
channel channels[CHANNELS_MAX];

channel one[2]; //One channel mode.

ulong bpm,onetick,speed,sp;

Err error;
LocalID oldID2,oldID,ID;
UInt16 num_of_mods,card;
DmSearchStateType state;
char mod_list[64*32]; //64 names of MODs.
DmOpenRef modDB;
MemHandle modREC[34];
char     *modH[34];
long aa,bb,cc,p;
char pp[1900]; //Table of periods.


void tableinit(void)
{
	int r;
	int s;
	pp[1712]=0;pp[1616]=1;pp[1524]=2;pp[1440]=3;pp[1356]=4;pp[1280]=5;
	pp[1208]=6;pp[1140]=7;pp[1076]=8;pp[1016]=9;pp[960]=10;pp[906]=11;
	pp[856]=12;pp[808]=13;pp[762]=14;pp[720]=15;pp[678]=16;pp[640]=17;
	pp[604]=18;pp[570]=19;pp[538]=20;pp[508]=21;pp[480]=22;pp[453]=23;
	pp[428]=24;pp[404]=25;pp[381]=26;pp[360]=27;pp[339]=28;pp[320]=29;
	pp[302]=30;pp[285]=31;pp[269]=32;pp[254]=33;pp[240]=34;pp[226]=35;
	pp[214]=36;pp[202]=37;pp[190]=38;pp[180]=39;pp[170]=40;pp[160]=41;
	pp[151]=42;pp[143]=43;pp[135]=44;pp[127]=45;pp[120]=46;pp[113]=47;
	pp[107]=48;pp[101]=49;pp[95]=50;pp[90]=51;pp[85]=52;pp[80]=53;
	pp[75]=54;pp[71]=55;pp[67]=56;pp[63]=57;pp[60]=58;pp[56]=59;
	for( r = 0; r < reverb_number; r++ )
	{
		reverb_offset[r] = (uchar)SysRandom(0);
		reverb_offset[r] >>= 1;
		reverb_offset[r] <<= 1;
	}
	//Sin table:
	for( r = 0; r < 256; r++ )
	{
		s = sin_tab[ r ];
		s -= 128;
		sin_tab[ r ] = (char)s;
	}
}


Err newsearch(void)
{
  error=DmGetNextDatabaseByTypeCreator(1,
                                       &state,
                                       0x4D4F4420,
                                       0x5A554C55,
                                       0,
                                       &card,
                                       &ID);
  return error;
}
Err nextfile(void)
{
  error=DmGetNextDatabaseByTypeCreator(0,
                                       &state,
                                       0x4D4F4420,
                                       0x5A554C55,
                                       0,
                                       &card,
                                       &ID);
  if(error==dmErrCantFind){DmGetNextDatabaseByTypeCreator(1,
                                       &state,
                                       0x4D4F4420,
                                       0x5A554C55,
                                       0,
                                       &card,
                                       &ID);
                          }
  return error;
}
void getmodlist(void)
{
  oldID=ID;
  num_of_mods=1;
  if(newsearch()!=dmErrCantFind){
    DmDatabaseInfo(0,ID,mod_list+((num_of_mods-1)<<5),0,0,0,0,0,0,0,0,0,0);
    snova:
    if(nextfile()!=dmErrCantFind){
      num_of_mods++;
      DmDatabaseInfo(0,ID,mod_list+((num_of_mods-1)<<5),0,0,0,0,0,0,0,0,0,0);
      goto snova;
    }
    ID=oldID;
  }else{num_of_mods=0;}
}


void channels_setup(void)
{
		long cc;
		long vol;
		//channels setup:
		CHANNELS=4;
		if(song.signature[0]=='6') CHANNELS=6;
		if(song.signature[0]=='8') CHANNELS=8;
		if(song.signature[0]=='O') CHANNELS=8;
		if(song.signature[0]=='C') CHANNELS=12;
		if(song.signature[0]=='X') CHANNELS=16;
        mod_info.CHANNELS = ByteSwap16(CHANNELS);
		for( vol = 32, cc = 0; cc < CHANNELS>>1; cc ++, vol += (64/CHANNELS) )
		{
			channels[cc].global_volume_l = ByteSwap16( 64 );
			channels[cc].global_volume_r = ByteSwap16( vol );
		}
		for( vol = 64, cc = CHANNELS>>1; cc < CHANNELS; cc ++, vol -= (64/CHANNELS) )
		{
			channels[cc].global_volume_l = ByteSwap16( vol );
			channels[cc].global_volume_r = ByteSwap16( 64 );
		}
		//==============
}


int
modload(void)
{
        uint    i, j, max;
        ulong   a, b, c;
        sample  *sptr;

if(num_of_mods!=0){
        modDB=DmOpenDatabase(0,ID,dmModeReadWrite);oldID2=ID;
if(modDB!=0){
        for(a=0;a<34;a++){
          DmReleaseRecord(modDB,a,0);
          modREC[a]=DmGetRecord(modDB,a);
          modH[a]=MemHandleLock(modREC[a]);
        }

        p=0;MemMove(&(song.title),modH[1],1084); //load header
		
		channels_setup();

		maxp=0;for(i=0;i<128;i++){
          if(song.patterntable[i]>maxp){maxp=song.patterntable[i];}
        }if(maxp>63){maxp=63;}
        
		p=0;for(i=0;i<64;i++){
			mem_off();
			if(i<=maxp) MemMove(patterndata[i],modH[2]+p,PAT_SIZE);
			mem_on();
			p+=PAT_SIZE;
		}
        for (i = 0, sptr = song.samples; i < 31; i++, sptr++) {
                sptr->length *= 2;
                sptr->reppnt *= 2;
                sptr->replen *= 2;
                if (sptr->length == 0) continue;
                sampledata[i] = modH[i+3];
                sampledata2[i] = ByteSwap32(sampledata[i]);
                if (sptr->replen + sptr->reppnt > sptr->length)
                        sptr->replen = sptr->length - sptr->reppnt;
        }

		ECHO = song.title[19];
		ECHOLEN = song.title[18];
		if( ECHOLEN > 24 ) ECHO = 24;
		if( ECHOLEN == 0 ) ECHOLEN = 24;
		if( ECHO == 0 ) ECHO = 100;

        return 1;
}}
	return 0;
}

void modclose(void)
{
  if(num_of_mods!=0){
    for(aa=0;aa<34;aa++){
      MemHandleUnlock(modREC[aa]);
    }
    DmCloseDatabase(modDB);
  }
}

void save_current_mod(void)
{
  long ptt;
  MemHandle newp;
  char *newpH;
  if(num_of_mods!=0){
	song.title[19] = ECHO;
	song.title[18] = ECHOLEN;
    for(aa=0;aa<31;aa++){
      song.samples[aa].length /= 2;
      song.samples[aa].reppnt /= 2;
      song.samples[aa].replen /= 2;
    }
    maxp=0;
    for(ptt=0;ptt<song.length;ptt++){      //Search for max number of pattern.
      if(song.patterntable[ptt]>maxp){maxp=song.patterntable[ptt];}
    }
	for(ptt=song.length;ptt<128;ptt++) song.patterntable[ptt] = 0;
    DmWrite(modH[1],0,&(song.title),1084); //Save MOD information.
    for(aa=0;aa<34;aa++){                  //Close all records.
      MemHandleUnlock(modREC[aa]);
    }
    newp=DmResizeRecord(modDB,2,(maxp+1)*PAT_SIZE); //Resize record with patterns.
    newpH=MemHandleLock(newp);
	//write patterns:
	ptt=0;
	for(p=0;p<maxp+1;p++){
		DmWrite(newpH,ptt,patterndata[p],PAT_SIZE);
		ptt+=PAT_SIZE;
	}
	//==============
    MemHandleUnlock(newp);
    DmCloseDatabase(modDB);
    ID=oldID2;
	//DmSetDatabaseInfo(0,ID,0,dmHdrAttrBackup,0,0,0,0,0,0,0,0,0);
	modload();
  }
}

int
modinit(int frequency)
{
        mod_info._playrate = frequency;
        return mod_info._playrate;
}

void start_clear(void)
{
  int a;
  if(status<2) {
  for(a=0;a<CHANNELS;a++) {
	  channels[a].volume = 0; channels[a].period = 1; channels[a].echo_volume = 0;
	  channels[a].pitch = 0;
	  channels[a].spec_effects = 0;
	  channels[a].cutoff = 255;
	  channels[a].cold_volume = 0;
	  channels[a].res_volume = ByteSwap32(1);
	  channels[a].res_ptr = 0;
	  channels[a].res_delta = ByteSwap32(255<<15);
	  channels[a].wah_offset = ByteSwap32(256<<16);
	  channels[a].wah_amp = ByteSwap32(1500);
	  channels[a].wah_add = ByteSwap32(-1500);
	  channels[a].p_delta=0;
	  channels[a].v_up=0;
	  channels[a].v_down=0;
	  channels[a].anticlick = 0;
	  channels[a].anticlick_start = 0;
	  channels[a].previous_value = 0;
  }
  for(a=0;a<2;a++) {
      one[0].echo_volume = 0;
      one[0].pitch = 0;
      one[0].spec_effects = 0;
      one[0].cutoff = 255;
      one[a].cold_volume = 0;
      one[a].res_volume = ByteSwap32(1);
      one[a].res_ptr = 0;
      one[a].res_delta = ByteSwap32(255<<15);
	  one[a].wah_offset = ByteSwap32(256<<16);
	  one[a].wah_amp = ByteSwap32(1500);
	  one[a].wah_add = ByteSwap32(-1500);
	  one[a].p_delta=0;
	  one[a].v_up=0;
	  one[a].v_down=0;
	  one[a].anticlick = 0;
	  one[a].anticlick_start = 0;
	  one[a].previous_value = 0;
  }
  convert_song_for_ARM();
  }
}

void set_volume(long mod_volume)
{
	VOLUME = mod_volume;
	mod_info.VOLUME = ByteSwap16(mod_volume);
}

void mod_set_freq(int frequency)
{
        modinit(frequency);
        mod_info._onetick=(((ulong)mod_info._playrate*25)<<8)/(mod_info._bpm*10);
        mod_info._patternticks = mod_info._onetick + 1;
        mod_info._sampleticksconst = (3546894UL / mod_info._playrate)<<16;
        mod_info._patternpos = 0;
        start_clear();
}

int modplay(int frequency)
{
        modinit(frequency);
        mod_info._tablepos = 0;
        mod_info._patternpos = 0;
        mod_info._bpm=125;mod_info._onetick=(((ulong)mod_info._playrate*25)<<8)/(mod_info._bpm*10);
        mod_info._patternticks = mod_info._onetick + 1;
        mod_info._speed=6;mod_info._sp=2;
        mod_info._sampleticksconst = (3546894UL / mod_info._playrate)<<16;
        start_clear();
        return 1;
}

void
fillbuffer(char * buffer, user_info *U)
{
}

void worknote(note *nptr, channel *cptr, user_info *U)
{
}

void playnote(uint note_num,uint samplenum,channel *cptr,ulong start_pnt)
{
    uint period, effect, a;
	uchar* s_info;
    cptr->sampdata = sampledata[samplenum];

	//Set spec-effects:=======
	s_info = song.samples[ samplenum ].name;
	cptr->spec_effects = 0;
	if( s_info[ 0 ] == '@' )
	{
		for(a=1;a<20;a++)
		{
			if( s_info[ a ] == '@' ) break;
			switch( s_info[ a ] )
			{
				case 'A':
				cptr->spec_effects |= SF_ANTICLICK;
				break;
				case 'S':
				cptr->spec_effects |= SF_SPORTAMENTO;
				break;
				case 'D':
				cptr->spec_effects |= SF_DISTORTION;
				break;
				case 'C':
				cptr->spec_effects |= SF_CUTOFF;
				break;
				case 'R':
				cptr->spec_effects |= SF_RESONANCE;
				break;
				case 'W':
				cptr->spec_effects |= SF_WAHWAH;
				break;
			}
		}
		cptr->spec_effects = ByteSwap32( cptr->spec_effects );
	}
	//========================
	
	if( song.samples[samplenum].name[21] < 32 )
		cptr->echo_volume = song.samples[samplenum].name[21];
	else cptr->echo_volume = 0;
    cptr->length = song.samples[samplenum].length;
    cptr->reppnt = song.samples[samplenum].reppnt;
    cptr->replen = song.samples[samplenum].replen;

	if( start_pnt < cptr->length && one_sample_limit < cptr->length )
	if( cptr->replen ) 
	{
		if( start_pnt > cptr->reppnt ) {
			cptr->replen = 0;
			cptr->length = one_sample_limit; 
		}	
		if( one_sample_limit < cptr->length ) { 
			if( one_sample_limit < cptr->reppnt + cptr->replen ) {
				cptr->replen = 0; cptr->length = one_sample_limit; 
			}
		}
	}
	else
	{
		//No loop:
		if( one_sample_limit < cptr->length ) 
		{ 
			cptr->length = one_sample_limit; 
		}
	}
    
	cptr->volume = song.samples[samplenum].volume;
    period=fine[song.samples[samplenum].finetune][note_num];
    cptr->delta = mod_info._sampleticksconst/period;
    cptr->period = period;
	if( start_pnt >= cptr->length ) start_pnt = 0;
    cptr->ticks = start_pnt << 16;
    if(cptr->replen>2){cptr->rep = cptr->reppnt+cptr->replen;}
                  else{cptr->rep = cptr->length;}
}

void slidenote(uint note_num,uint samplenum,channel *cptr)
{
    uint period, effect;
    cptr->sampdata = sampledata[samplenum];
	if( song.samples[samplenum].name[21] < 32 )
		cptr->echo_volume = song.samples[samplenum].name[21];
	else cptr->echo_volume = 0;
    /*cptr->length = song.samples[samplenum].length;
    cptr->reppnt = song.samples[samplenum].reppnt;
    cptr->replen = song.samples[samplenum].replen;
	
	if( start_pnt > cptr->reppnt + cptr->replen ) cptr->replen = 0;
	if( one_sample_limit < cptr->length ) { 
		if( one_sample_limit < cptr->reppnt + cptr->replen ) {
			cptr->replen = 0; cptr->length = one_sample_limit; 
		}
	}*/
    
	cptr->volume = song.samples[samplenum].volume;
    period=fine[song.samples[samplenum].finetune][note_num];
    cptr->delta = mod_info._sampleticksconst/period;
    cptr->period = period;
    if(cptr->replen>2){cptr->rep = cptr->reppnt+cptr->replen;}
                  else{cptr->rep = cptr->length;}
}

void effect(channel * cptr, user_info *U)
{
  if(cptr->p_delta!=0){
    cptr->period+=cptr->p_delta;
    if(cptr->period>=cptr->p_up){cptr->period=cptr->p_up;cptr->p_delta=0;}
    if(cptr->period<=cptr->p_down){cptr->period=cptr->p_down;cptr->p_delta=0;}
  if(cptr->period!=0){
    cptr->delta = U->_sampleticksconst/cptr->period;
  }}
}

void convert_song_for_ARM(void)
{
   int i;
   //char str[20];
   //StrPrintF(str,"%ld-%ld",(long)song.length,(long)song2.length);
   //WinDrawChars(str,20,10,10);
   MemMove(&song2,&song,1084);
   for(i=0;i<31;i++){
     song2._samples[i]=ByteSwap32(&song2.samples+(i*30));
     song2.samples[i].length=ByteSwap16(song2.samples[i].length);
     song2.samples[i].reppnt=ByteSwap16(song2.samples[i].reppnt);
     song2.samples[i].replen=ByteSwap16(song2.samples[i].replen);
   }
}

void echo_setup( void* buffer, long echo_volume, long size_in_ticks )
{
	if( CLASSIC == 0 ) 
	{
		mod_info.echo_buffer = (void*)ByteSwap32( buffer );
		mod_info.echo_size = ByteSwap32( size_in_ticks );
		mod_info.echo_vol = ByteSwap32( echo_volume );
		song.title[19] = (uchar)echo_volume;
		song.title[18] = (uchar)size_in_ticks;
		mod_info.echo_pointer = 0;
	}
	else
	{
		mod_info.echo_buffer = 0;
	}
}

void create_user_info(void)
{
	mod_info.resonance_buffer = (char*)ByteSwap32( sin_tab );
	mod_info.reverb_offset = (void*)ByteSwap32( reverb_offset );
	mod_info.reverb_number = ByteSwap32( reverb_number );
   mod_info.record_pos = ByteSwap32( 61000 );
   mod_info.rec_pointer = 0;
   mod_info.rec_count = 0;
   mod_info.rec_add = 0;
   mod_info._arm_code=arm_code;
   mod_info._channels=channels;
   mod_info._patternticks=patternticks;
   mod_info._onetick=onetick;
   mod_info._sp=sp;
   mod_info._speed=speed;
   mod_info._bpm=bpm;
   mod_info._patterndata=patterndata;
   mod_info._patterndata2=ByteSwap32(patterndata2);
   mod_info._song=&song;
   mod_info._song2=ByteSwap32(&song2);
   mod_info._tablepos=tablepos;
   mod_info._patternpos=patternpos;
   mod_info._status=status;
   mod_info._one=one;
   mod_info._fine=fine;
   mod_info._fine2=fine2;
   mod_info._sampleticksconst=sampleticksconst;
   mod_info._pp=pp;
   mod_info._sampledata=sampledata;
   mod_info._sampledata2=ByteSwap32(sampledata2);
   mod_info._num_of_mods=num_of_mods;
   mod_info._playrate=playrate;
   mod_info.CHANNELS = ByteSwap16(CHANNELS);
   mod_info.VOLUME = ByteSwap16(VOLUME);
}