/* * Sega System 32 Multi/Model 1/Model 2 custom PCM chip (315-5560) emulation. * * by Miguel Angel Horna (ElSemi) for Model 2 Emulator and MAME. * Information by R.Belmont and the YMF278B (OPL4) manual. * * voice registers: * 0: Pan * 1: Index of sample * 2: LSB of pitch (low 2 bits seem unused so) * 3: MSB of pitch (ooooppppppppppxx) (o=octave (4 bit signed), p=pitch (10 bits), x=unused? * 4: voice control: top bit = 1 for key on, 0 for key off * 5: bit 0: 0: interpolate volume changes, 1: direct set volume, bits 1-7 = volume attenuate (0=max, 7f=min) * 6: LFO frequency + Phase LFO depth * 7: Amplitude LFO size * * The first sample ROM contains a variable length table with 12 * bytes per instrument/sample. This is very similar to the YMF278B. * * The first 3 bytes are the offset into the file (big endian). * The next 2 are the loop start offset into the file (big endian) * The next 2 are the 2's complement of the total sample size (big endian) * The next byte is LFO freq + depth (copied to reg 6 ?) * The next 3 are envelope params (Attack, Decay1 and 2, sustain level, release, Key Rate Scaling) * The next byte is Amplitude LFO size (copied to reg 7 ?) * * TODO * - The YM278B manual states that the chip supports 512 instruments. The MultiPCM probably supports them * too but the high bit position is unknown (probably reg 2 low bit). Any game use more than 256? * */ //#include "emu.h" //#include "streams.h" #include "mamedef.h" #include #include #include #include "multipcm.h" #ifndef NULL #define NULL ((void *)0) #endif //???? #define MULTIPCM_CLOCKDIV (180.0) struct _Sample { unsigned int Start; unsigned int Loop; unsigned int End; unsigned char AR,DR1,DR2,DL,RR; unsigned char KRS; unsigned char LFOVIB; unsigned char AM; }; typedef enum {ATTACK,DECAY1,DECAY2,RELEASE} _STATE; struct _EG { int volume; // _STATE state; int step; //step vals int AR; //Attack int D1R; //Decay1 int D2R; //Decay2 int RR; //Release int DL; //Decay level }; struct _LFO { unsigned short phase; UINT32 phase_step; int *table; int *scale; }; struct _SLOT { unsigned char Num; unsigned char Regs[8]; int Playing; struct _Sample *Sample; unsigned int Base; unsigned int offset; unsigned int step; unsigned int Pan,TL; unsigned int DstTL; int TLStep; signed int Prev; struct _EG EG; struct _LFO PLFO; //Phase lfo struct _LFO ALFO; //AM lfo UINT8 Muted; }; typedef struct _MultiPCM MultiPCM; struct _MultiPCM { //sound_stream * stream; struct _Sample Samples[0x200]; //Max 512 samples struct _SLOT Slots[28]; unsigned int CurSlot; unsigned int Address; unsigned int BankR,BankL; float Rate; UINT32 ROMMask; UINT32 ROMSize; INT8 *ROM; //I include these in the chip because they depend on the chip clock unsigned int ARStep[0x40],DRStep[0x40]; //Envelope step table unsigned int FNS_Table[0x400]; //Frequency step table }; static UINT8 IsInit = 0x00; static signed int LPANTABLE[0x800],RPANTABLE[0x800]; #define FIX(v) ((UINT32) ((float) (1<type() == MULTIPCM); return (MultiPCM *)downcast(device)->token(); }*/ /******************************* ENVELOPE SECTION *******************************/ //Times are based on a 44100Hz timebase. It's adjusted to the actual sampling rate on startup static const double BaseTimes[64]={0,0,0,0,6222.95,4978.37,4148.66,3556.01,3111.47,2489.21,2074.33,1778.00,1555.74,1244.63,1037.19,889.02, 777.87,622.31,518.59,444.54,388.93,311.16,259.32,222.27,194.47,155.60,129.66,111.16,97.23,77.82,64.85,55.60, 48.62,38.91,32.43,27.80,24.31,19.46,16.24,13.92,12.15,9.75,8.12,6.98,6.08,4.90,4.08,3.49, 3.04,2.49,2.13,1.90,1.72,1.41,1.18,1.04,0.91,0.73,0.59,0.50,0.45,0.45,0.45,0.45}; #define AR2DR 14.32833 static signed int lin2expvol[0x400]; static int TLSteps[2]; #define EG_SHIFT 16 static int EG_Update(struct _SLOT *slot) { switch(slot->EG.state) { case ATTACK: slot->EG.volume+=slot->EG.AR; if(slot->EG.volume>=(0x3ff<EG.state=DECAY1; if(slot->EG.D1R>=(0x400<EG.state=DECAY2; slot->EG.volume=0x3ff<EG.volume-=slot->EG.D1R; if(slot->EG.volume<=0) slot->EG.volume=0; if(slot->EG.volume>>EG_SHIFT<=(slot->EG.DL<<(10-4))) slot->EG.state=DECAY2; break; case DECAY2: slot->EG.volume-=slot->EG.D2R; if(slot->EG.volume<=0) slot->EG.volume=0; break; case RELEASE: slot->EG.volume-=slot->EG.RR; if(slot->EG.volume<=0) { slot->EG.volume=0; slot->Playing=0; } break; default: return 1<EG.volume>>EG_SHIFT]; } static unsigned int Get_RATE(unsigned int *Steps,unsigned int rate,unsigned int val) { int r=4*val+rate; if(val==0) return Steps[0]; if(val==0xf) return Steps[0x3f]; if(r>0x3f) r=0x3f; return Steps[r]; } static void EG_Calc(MultiPCM *ptChip,struct _SLOT *slot) { int octave=((slot->Regs[3]>>4)-1)&0xf; int rate; if(octave&8) octave=octave-16; if(slot->Sample->KRS!=0xf) rate=(octave+slot->Sample->KRS)*2+((slot->Regs[3]>>3)&1); else rate=0; slot->EG.AR=Get_RATE(ptChip->ARStep,rate,slot->Sample->AR); slot->EG.D1R=Get_RATE(ptChip->DRStep,rate,slot->Sample->DR1); slot->EG.D2R=Get_RATE(ptChip->DRStep,rate,slot->Sample->DR2); slot->EG.RR=Get_RATE(ptChip->DRStep,rate,slot->Sample->RR); slot->EG.DL=0xf-slot->Sample->DL; } /***************************** LFO SECTION *****************************/ #define LFO_SHIFT 8 #define LFIX(v) ((unsigned int) ((float) (1<phase+=LFO->phase_step; p=LFO->table[(LFO->phase>>LFO_SHIFT)&0xff]; p=LFO->scale[p+128]; return p<<(SHIFT-LFO_SHIFT); } INLINE signed int ALFO_Step(struct _LFO *LFO) { int p; LFO->phase+=LFO->phase_step; p=LFO->table[(LFO->phase>>LFO_SHIFT)&0xff]; p=LFO->scale[p]; return p<<(SHIFT-LFO_SHIFT); } static void LFO_ComputeStep(MultiPCM *ptChip,struct _LFO *LFO,UINT32 LFOF,UINT32 LFOS,int ALFO) { float step=(float) LFOFreq[LFOF]*256.0/(float) ptChip->Rate; LFO->phase_step=(unsigned int) ((float) (1<table=ALFO_TRI; LFO->scale=ASCALES[LFOS]; } else { LFO->table=PLFO_TRI; LFO->scale=PSCALES[LFOS]; } } static void WriteSlot(MultiPCM *ptChip,struct _SLOT *slot,int reg,unsigned char data) { slot->Regs[reg]=data; switch(reg) { case 0: //PANPOT slot->Pan=(data>>4)&0xf; break; case 1: //Sample //according to YMF278 sample write causes some base params written to the regs (envelope+lfos) //the game should never change the sample while playing. { struct _Sample *Sample=ptChip->Samples+slot->Regs[1]; WriteSlot(ptChip,slot,6,Sample->LFOVIB); WriteSlot(ptChip,slot,7,Sample->AM); } break; case 2: //Pitch case 3: { unsigned int oct=((slot->Regs[3]>>4)-1)&0xf; unsigned int pitch=((slot->Regs[3]&0xf)<<6)|(slot->Regs[2]>>2); pitch=ptChip->FNS_Table[pitch]; if(oct&0x8) pitch>>=(16-oct); else pitch<<=oct; slot->step=pitch/ptChip->Rate; } break; case 4: //KeyOn/Off (and more?) { if(data&0x80) //KeyOn { slot->Sample=ptChip->Samples+slot->Regs[1]; slot->Playing=1; slot->Base=slot->Sample->Start; slot->offset=0; slot->Prev=0; slot->TL=slot->DstTL<EG.state=ATTACK; slot->EG.volume=0; if(slot->Base>=0x100000) { if(slot->Pan&8) slot->Base=(slot->Base&0xfffff)|(ptChip->BankL); else slot->Base=(slot->Base&0xfffff)|(ptChip->BankR); } } else { if(slot->Playing) { if(slot->Sample->RR!=0xf) slot->EG.state=RELEASE; else slot->Playing=0; } } } break; case 5: //TL+Interpolation { slot->DstTL=(data>>1)&0x7f; if(!(data&1)) //Interpolate TL { if((slot->TL>>SHIFT)>slot->DstTL) slot->TLStep=TLSteps[0]; //decrease else slot->TLStep=TLSteps[1]; //increase } else slot->TL=slot->DstTL<PLFO),(slot->Regs[6]>>3)&7,slot->Regs[6]&7,0); LFO_ComputeStep(ptChip,&(slot->ALFO),(slot->Regs[6]>>3)&7,slot->Regs[7]&7,1); } } break; case 7: //ALFO { if(data) { LFO_ComputeStep(ptChip,&(slot->PLFO),(slot->Regs[6]>>3)&7,slot->Regs[6]&7,0); LFO_ComputeStep(ptChip,&(slot->ALFO),(slot->Regs[6]>>3)&7,slot->Regs[7]&7,1); } } break; } } //static STREAM_UPDATE( MultiPCM_update ) void MultiPCM_update(void *param, stream_sample_t **outputs, int samples) { MultiPCM *ptChip = (MultiPCM *)param; stream_sample_t *datap[2]; int i,sl; datap[0] = outputs[0]; datap[1] = outputs[1]; memset(datap[0], 0, sizeof(*datap[0])*samples); memset(datap[1], 0, sizeof(*datap[1])*samples); for(i=0;iSlots+sl; if(slot->Playing && ! slot->Muted) { unsigned int vol=(slot->TL>>SHIFT)|(slot->Pan<<7); unsigned int adr=slot->offset>>SHIFT; signed int sample; unsigned int step=slot->step; signed int csample=(signed short) (ptChip->ROM[(slot->Base+adr) & ptChip->ROMMask]<<8); signed int fpart=slot->offset&((1<Prev*((1<>SHIFT; if(slot->Regs[6]&7) //Vibrato enabled { step=step*PLFO_Step(&(slot->PLFO)); step>>=SHIFT; } slot->offset+=step; if(slot->offset>=(slot->Sample->End<offset=slot->Sample->Loop<offset>>SHIFT)) { slot->Prev=csample; } if((slot->TL>>SHIFT)!=slot->DstTL) slot->TL+=slot->TLStep; if(slot->Regs[7]&7) //Tremolo enabled { sample=sample*ALFO_Step(&(slot->ALFO)); sample>>=SHIFT; } sample=(sample*EG_Update(slot))>>10; smpl+=(LPANTABLE[vol]*sample)>>SHIFT; smpr+=(RPANTABLE[vol]*sample)>>SHIFT; } } /*#define ICLIP16(x) (x<-32768)?-32768:((x>32767)?32767:x) datap[0][i]=ICLIP16(smpl); datap[1][i]=ICLIP16(smpr);*/ datap[0][i] = smpl; datap[1][i] = smpr; } } //READ8_DEVICE_HANDLER( multipcm_r ) UINT8 multipcm_r(void *_info, offs_t offset) { // MultiPCM *ptChip = get_safe_token(device); // MultiPCM *ptChip = &MultiPCMData[ChipID]; return 0; } //static DEVICE_START( multipcm ) int device_start_multipcm(void **_info, int clock) { //MultiPCM *ptChip = get_safe_token(device); MultiPCM *ptChip; int i; ptChip = (MultiPCM *) calloc(1, sizeof(MultiPCM)); *_info = (void *) ptChip; //ptChip->ROM=*device->region(); ptChip->ROMMask = 0x00; ptChip->ROMSize = 0x00; ptChip->ROM = NULL; //ptChip->Rate=(float) device->clock() / MULTIPCM_CLOCKDIV; ptChip->Rate=(float) clock / MULTIPCM_CLOCKDIV; //ptChip->stream = stream_create(device, 0, 2, ptChip->Rate, ptChip, MultiPCM_update); if (! IsInit) { //Volume+pan table for(i=0;i<0x800;++i) { float SegaDB=0; float TL; float LPAN,RPAN; unsigned char iTL=i&0x7f; unsigned char iPAN=(i>>7)&0xf; SegaDB=(float) iTL*(-24.0)/(float) 0x40; TL=pow(10.0,SegaDB/20.0); if(iPAN==0x8) { LPAN=RPAN=0.0; } else if(iPAN==0x0) { LPAN=RPAN=1.0; } else if(iPAN&0x8) { LPAN=1.0; iPAN=0x10-iPAN; SegaDB=(float) iPAN*(-12.0)/(float) 0x4; RPAN=pow(10.0,SegaDB/20.0); if((iPAN&0x7)==7) RPAN=0.0; } else { RPAN=1.0; SegaDB=(float) iPAN*(-12.0)/(float) 0x4; LPAN=pow(10.0,SegaDB/20.0); if((iPAN&0x7)==7) LPAN=0.0; } TL/=4.0; LPANTABLE[i]=FIX((LPAN*TL)); RPANTABLE[i]=FIX((RPAN*TL)); } IsInit = 0x01; } //Pitch steps for(i=0;i<0x400;++i) { float fcent=ptChip->Rate*(1024.0+(float) i)/1024.0; ptChip->FNS_Table[i]=(unsigned int ) ((float) (1<ARStep[i]=(float) (0x400<DRStep[i]=(float) (0x400<ARStep[0]=ptChip->ARStep[1]=ptChip->ARStep[2]=ptChip->ARStep[3]=0; ptChip->ARStep[0x3f]=0x400<DRStep[0]=ptChip->DRStep[1]=ptChip->DRStep[2]=ptChip->DRStep[3]=0; //TL Interpolation steps //lower TLSteps[0]=-(float) (0x80<exponential ramps for(i=0;i<0x400;++i) { float db=-(96.0-(96.0*(float) i/(float) 0x400)); lin2expvol[i]=pow(10.0,db/20.0)*(float) (1<ROM+i*12; ptChip->Samples[i].Start=(ptSample[0]<<16)|(ptSample[1]<<8)|(ptSample[2]<<0); ptChip->Samples[i].Loop=(ptSample[3]<<8)|(ptSample[4]<<0); ptChip->Samples[i].End=0xffff-((ptSample[5]<<8)|(ptSample[6]<<0)); ptChip->Samples[i].LFOVIB=ptSample[7]; ptChip->Samples[i].DR1=ptSample[8]&0xf; ptChip->Samples[i].AR=(ptSample[8]>>4)&0xf; ptChip->Samples[i].DR2=ptSample[9]&0xf; ptChip->Samples[i].DL=(ptSample[9]>>4)&0xf; ptChip->Samples[i].RR=ptSample[10]&0xf; ptChip->Samples[i].KRS=(ptSample[10]>>4)&0xf; ptChip->Samples[i].AM=ptSample[11]; }*/ /*state_save_register_device_item(device, 0, ptChip->CurSlot); state_save_register_device_item(device, 0, ptChip->Address); state_save_register_device_item(device, 0, ptChip->BankL); state_save_register_device_item(device, 0, ptChip->BankR);*/ // reset is done via DEVICE_RESET /*for(i=0;i<28;++i) { ptChip->Slots[i].Num=i; ptChip->Slots[i].Playing=0; state_save_register_device_item(device, i, ptChip->Slots[i].Num); state_save_register_device_item_array(device, i, ptChip->Slots[i].Regs); state_save_register_device_item(device, i, ptChip->Slots[i].Playing); state_save_register_device_item(device, i, ptChip->Slots[i].Base); state_save_register_device_item(device, i, ptChip->Slots[i].offset); state_save_register_device_item(device, i, ptChip->Slots[i].step); state_save_register_device_item(device, i, ptChip->Slots[i].Pan); state_save_register_device_item(device, i, ptChip->Slots[i].TL); state_save_register_device_item(device, i, ptChip->Slots[i].DstTL); state_save_register_device_item(device, i, ptChip->Slots[i].TLStep); state_save_register_device_item(device, i, ptChip->Slots[i].Prev); state_save_register_device_item(device, i, ptChip->Slots[i].EG.volume); state_save_register_device_item(device, i, ptChip->Slots[i].EG.state); state_save_register_device_item(device, i, ptChip->Slots[i].EG.step); state_save_register_device_item(device, i, ptChip->Slots[i].EG.AR); state_save_register_device_item(device, i, ptChip->Slots[i].EG.D1R); state_save_register_device_item(device, i, ptChip->Slots[i].EG.D2R); state_save_register_device_item(device, i, ptChip->Slots[i].EG.RR); state_save_register_device_item(device, i, ptChip->Slots[i].EG.DL); state_save_register_device_item(device, i, ptChip->Slots[i].PLFO.phase); state_save_register_device_item(device, i, ptChip->Slots[i].PLFO.phase_step); state_save_register_device_item(device, i, ptChip->Slots[i].ALFO.phase); state_save_register_device_item(device, i, ptChip->Slots[i].ALFO.phase_step); }*/ LFO_Init(); multipcm_set_bank(ptChip, 0x00, 0x00); return (int)(ptChip->Rate + 0.5); } void device_stop_multipcm(void *_info) { MultiPCM *ptChip = (MultiPCM *)_info; free(ptChip->ROM); ptChip->ROM = NULL; free(ptChip); return; } void device_reset_multipcm(void *_info) { MultiPCM *ptChip = (MultiPCM *)_info; int i; for(i=0;i<28;++i) { ptChip->Slots[i].Num=i; ptChip->Slots[i].Playing=0; } return; } //WRITE8_DEVICE_HANDLER( multipcm_w ) void multipcm_w(void *_info, offs_t offset, UINT8 data) { //MultiPCM *ptChip = get_safe_token(device); MultiPCM *ptChip = (MultiPCM *)_info; switch(offset) { case 0: //Data write WriteSlot(ptChip,ptChip->Slots+ptChip->CurSlot,ptChip->Address,data); break; case 1: ptChip->CurSlot=val2chan[data&0x1f]; break; case 2: ptChip->Address=(data>7)?7:data; break; } /*ptChip->CurSlot = val2chan[(offset >> 3) & 0x1F]; ptChip->Address = offset & 0x07; WriteSlot(ptChip, ptChip->Slots + ptChip->CurSlot, ptChip->Address, data);*/ } /* MAME/M1 access functions */ //void multipcm_set_bank(running_device *device, UINT32 leftoffs, UINT32 rightoffs) void multipcm_set_bank(void *_info, UINT32 leftoffs, UINT32 rightoffs) { //MultiPCM *ptChip = get_safe_token(device); MultiPCM *ptChip = (MultiPCM *)_info; ptChip->BankL = leftoffs; ptChip->BankR = rightoffs; } void multipcm_bank_write(void *_info, UINT8 offset, UINT16 data) { MultiPCM *ptChip = (MultiPCM *)_info; if (offset & 0x01) ptChip->BankL = data << 16; if (offset & 0x02) ptChip->BankR = data << 16; return; } void multipcm_write_rom(void *_info, offs_t ROMSize, offs_t DataStart, offs_t DataLength, const UINT8* ROMData) { MultiPCM *ptChip = (MultiPCM *)_info; UINT16 CurSmpl; struct _Sample* TempSmpl; UINT8* ptSample; if (ptChip->ROMSize != ROMSize) { ptChip->ROM = (INT8*)realloc(ptChip->ROM, ROMSize); ptChip->ROMSize = ROMSize; for (ptChip->ROMMask = 1; ptChip->ROMMask < ROMSize; ptChip->ROMMask <<= 1) ; ptChip->ROMMask --; memset(ptChip->ROM, 0xFF, ROMSize); } if (DataStart > ROMSize) return; if (DataStart + DataLength > ROMSize) DataLength = ROMSize - DataStart; memcpy(ptChip->ROM + DataStart, ROMData, DataLength); if (DataStart < 0x200 * 12) { for (CurSmpl = 0; CurSmpl < 512; CurSmpl ++) { TempSmpl = &ptChip->Samples[CurSmpl]; ptSample = (UINT8*)ptChip->ROM + CurSmpl * 12; TempSmpl->Start = (ptSample[0]<<16)|(ptSample[1]<<8)|(ptSample[2]<<0); TempSmpl->Loop = (ptSample[3]<<8)|(ptSample[4]<<0); TempSmpl->End = 0xffff-((ptSample[5]<<8)|(ptSample[6]<<0)); TempSmpl->LFOVIB = ptSample[7]; TempSmpl->DR1 = ptSample[8]&0xf; TempSmpl->AR = (ptSample[8]>>4)&0xf; TempSmpl->DR2 = ptSample[9]&0xf; TempSmpl->DL = (ptSample[9]>>4)&0xf; TempSmpl->RR = ptSample[10]&0xf; TempSmpl->KRS = (ptSample[10]>>4)&0xf; TempSmpl->AM = ptSample[11]; } } return; } void multipcm_set_mute_mask(void *_info, UINT32 MuteMask) { MultiPCM* ptChip = (MultiPCM *)_info; UINT8 CurChn; for (CurChn = 0; CurChn < 28; CurChn ++) ptChip->Slots[CurChn].Muted = (MuteMask >> CurChn) & 0x01; return; } #if 0 // for debugging only UINT8 multipcm_get_channels(UINT8 ChipID, UINT32* ChannelMask) { MultiPCM* ptChip = &MultiPCMData[ChipID]; UINT8 CurChn; UINT8 UsedChns; UINT32 ChnMask; ChnMask = 0x00000000; UsedChns = 0x00; for (CurChn = 0; CurChn < 28; CurChn ++) { if (ptChip->Slots[CurChn].Playing) { ChnMask |= (1 << CurChn); UsedChns ++; } } if (ChannelMask != NULL) *ChannelMask = ChnMask; return UsedChns; } #endif /************************************************************************** * Generic get_info **************************************************************************/ /*DEVICE_GET_INFO( multipcm ) { switch (state) { // --- the following bits of info are returned as 64-bit signed integers --- case DEVINFO_INT_TOKEN_BYTES: info->i = sizeof(MultiPCM); break; // --- the following bits of info are returned as pointers to data or functions --- case DEVINFO_FCT_START: info->start = DEVICE_START_NAME( multipcm ); break; case DEVINFO_FCT_STOP: // Nothing break; case DEVINFO_FCT_RESET: // Nothing break; // --- the following bits of info are returned as NULL-terminated strings --- case DEVINFO_STR_NAME: strcpy(info->s, "Sega/Yamaha 315-5560"); break; case DEVINFO_STR_FAMILY: strcpy(info->s, "Sega custom"); break; case DEVINFO_STR_VERSION: strcpy(info->s, "2.0"); break; case DEVINFO_STR_SOURCE_FILE: strcpy(info->s, __FILE__); break; case DEVINFO_STR_CREDITS: strcpy(info->s, "Copyright Nicola Salmoria and the MAME Team"); break; } }*/ //DEFINE_LEGACY_SOUND_DEVICE(MULTIPCM, multipcm);