// license:BSD-3-Clause // copyright-holders:Miguel Angel Horna /* * 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 "multipcm.h" //???? #define MULTIPCM_CLOCKDIV (180.0f) ALLOW_SAVE_TYPE(STATE); // allow save_item on a non-fundamental type static signed int LPANTABLE[0x800],RPANTABLE[0x800]; #define FIX(v) ((UINT32) ((float) (1<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]; } void multipcm_device::EG_Calc(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(m_ARStep,rate,slot->Sample->AR); slot->EG.D1R=Get_RATE(m_DRStep,rate,slot->Sample->DR1); slot->EG.D2R=Get_RATE(m_DRStep,rate,slot->Sample->DR2); slot->EG.RR=Get_RATE(m_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(LFO_t *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); } void multipcm_device::LFO_ComputeStep(LFO_t *LFO,UINT32 LFOF,UINT32 LFOS,int ALFO) { float step=(float) LFOFreq[LFOF]*256.0f/(float) m_Rate; LFO->phase_step=(unsigned int) ((float) (1<table=ALFO_TRI; LFO->scale=ASCALES[LFOS]; } else { LFO->table=PLFO_TRI; LFO->scale=PSCALES[LFOS]; } } void multipcm_device::WriteSlot(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. { Sample_t *Sample=m_Samples+slot->Regs[1]; WriteSlot(slot,6,Sample->LFOVIB); WriteSlot(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=m_FNS_Table[pitch]; if(oct&0x8) pitch>>=(16-oct); else pitch<<=oct; slot->step=pitch/m_Rate; } break; case 4: //KeyOn/Off (and more?) { if(data&0x80) //KeyOn { slot->Sample=m_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)|(m_BankL); else slot->Base=(slot->Base&0xfffff)|(m_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(&(slot->ALFO),(slot->Regs[6]>>3)&7,slot->Regs[7]&7,1); } } break; case 7: //ALFO { if(data) { LFO_ComputeStep(&(slot->PLFO),(slot->Regs[6]>>3)&7,slot->Regs[6]&7,0); LFO_ComputeStep(&(slot->ALFO),(slot->Regs[6]>>3)&7,slot->Regs[7]&7,1); } } break; } } READ8_MEMBER( multipcm_device::read ) { return 0; } WRITE8_MEMBER( multipcm_device::write ) { switch(offset) { case 0: //Data write WriteSlot(m_Slots+m_CurSlot,m_Address,data); break; case 1: m_CurSlot=val2chan[data&0x1f]; break; case 2: m_Address=(data>7)?7:data; break; } } /* MAME/M1 access functions */ void multipcm_device::set_bank(UINT32 leftoffs, UINT32 rightoffs) { m_BankL = leftoffs; m_BankR = rightoffs; } const device_type MULTIPCM = &device_creator; // default address map static ADDRESS_MAP_START( multipcm, AS_0, 8, multipcm_device ) AM_RANGE(0x000000, 0x3fffff) AM_ROM ADDRESS_MAP_END multipcm_device::multipcm_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) : device_t(mconfig, MULTIPCM, "Sega/Yamaha 315-5560", tag, owner, clock, "multipcm", __FILE__), device_sound_interface(mconfig, *this), device_memory_interface(mconfig, *this), m_space_config("mpcm_samples", ENDIANNESS_LITTLE, 8, 24, 0, NULL), m_stream(NULL), //m_Samples(0x200), //m_Slots[28], m_CurSlot(0), m_Address(0), m_BankR(0), m_BankL(0), m_Rate(0) //m_ARStep(0), //m_DRStep(0), //m_FNS_Table(0) { m_address_map[0] = *ADDRESS_MAP_NAME(multipcm); } //------------------------------------------------- // memory_space_config - return a description of // any address spaces owned by this device //------------------------------------------------- const address_space_config *multipcm_device::memory_space_config(address_spacenum spacenum) const { return (spacenum == 0) ? &m_space_config : NULL; } //------------------------------------------------- // device_config_complete - perform any // operations now that the configuration is // complete //------------------------------------------------- void multipcm_device::device_config_complete() { } //------------------------------------------------- // device_start - device-specific startup //------------------------------------------------- void multipcm_device::device_start() { int i; // find our direct access m_direct = &space().direct(); m_Rate=(float) clock() / MULTIPCM_CLOCKDIV; m_stream = machine().sound().stream_alloc(*this, 0, 2, m_Rate); //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.0f)/(float) 0x40; TL=powf(10.0f,SegaDB/20.0f); 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.0f)/(float) 0x4; RPAN=pow(10.0f,SegaDB/20.0f); if((iPAN&0x7)==7) RPAN=0.0; } else { RPAN=1.0; SegaDB=(float) iPAN*(-12.0f)/(float) 0x4; LPAN=pow(10.0f,SegaDB/20.0f); if((iPAN&0x7)==7) LPAN=0.0; } TL/=4.0f; LPANTABLE[i]=FIX((LPAN*TL)); RPANTABLE[i]=FIX((RPAN*TL)); } //Pitch steps for(i=0;i<0x400;++i) { float fcent=m_Rate*(1024.0f+(float) i)/1024.0f; m_FNS_Table[i]=(unsigned int ) ((float) (1<exponential ramps for(i=0;i<0x400;++i) { float db=-(96.0f-(96.0f*(float) i/(float) 0x400)); lin2expvol[i]=powf(10.0f,db/20.0f)*(float) (1<read_raw_byte((i*12) + j); } m_Samples[i].Start=(ptSample[0]<<16)|(ptSample[1]<<8)|(ptSample[2]<<0); m_Samples[i].Loop=(ptSample[3]<<8)|(ptSample[4]<<0); m_Samples[i].End=0xffff-((ptSample[5]<<8)|(ptSample[6]<<0)); m_Samples[i].LFOVIB=ptSample[7]; m_Samples[i].DR1=ptSample[8]&0xf; m_Samples[i].AR=(ptSample[8]>>4)&0xf; m_Samples[i].DR2=ptSample[9]&0xf; m_Samples[i].DL=(ptSample[9]>>4)&0xf; m_Samples[i].RR=ptSample[10]&0xf; m_Samples[i].KRS=(ptSample[10]>>4)&0xf; m_Samples[i].AM=ptSample[11]; } save_item(NAME(m_CurSlot)); save_item(NAME(m_Address)); save_item(NAME(m_BankL)); save_item(NAME(m_BankR)); for(i=0;i<28;++i) { m_Slots[i].Num=i; m_Slots[i].Playing=0; save_item(NAME(m_Slots[i].Num), i); save_item(NAME(m_Slots[i].Regs), i); save_item(NAME(m_Slots[i].Playing), i); save_item(NAME(m_Slots[i].Base), i); save_item(NAME(m_Slots[i].offset), i); save_item(NAME(m_Slots[i].step), i); save_item(NAME(m_Slots[i].Pan), i); save_item(NAME(m_Slots[i].TL), i); save_item(NAME(m_Slots[i].DstTL), i); save_item(NAME(m_Slots[i].TLStep), i); save_item(NAME(m_Slots[i].Prev), i); save_item(NAME(m_Slots[i].EG.volume), i); save_item(NAME(m_Slots[i].EG.state), i); save_item(NAME(m_Slots[i].EG.step), i); save_item(NAME(m_Slots[i].EG.AR), i); save_item(NAME(m_Slots[i].EG.D1R), i); save_item(NAME(m_Slots[i].EG.D2R), i); save_item(NAME(m_Slots[i].EG.RR), i); save_item(NAME(m_Slots[i].EG.DL), i); save_item(NAME(m_Slots[i].PLFO.phase), i); save_item(NAME(m_Slots[i].PLFO.phase_step), i); save_item(NAME(m_Slots[i].ALFO.phase), i); save_item(NAME(m_Slots[i].ALFO.phase_step), i); } LFO_Init(); } //------------------------------------------------- // sound_stream_update - handle a stream update //------------------------------------------------- void multipcm_device::sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples) { 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;iPlaying) { 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) (m_direct->read_raw_byte(slot->Base+adr)<<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); } }