Previous Thread
Next Thread
Print Thread
Page 12 of 55 1 2 10 11 12 13 14 54 55
Joined: Oct 2006
Posts: 1,017
Likes: 21
S
Very Senior Member
Offline
Very Senior Member
S
Joined: Oct 2006
Posts: 1,017
Likes: 21
Originally Posted by R. Belmont
Deunan writes gorgeous code


I've been lovin' his blog, too. A fun read, and I'm much happier with the more frequent English posts, too.
http://dknute.livejournal.com/
smile

Joined: Mar 2001
Posts: 17,215
Likes: 234
R
Very Senior Member
OP Offline
Very Senior Member
R
Joined: Mar 2001
Posts: 17,215
Likes: 234
New developer-only SDK 1.4.0a2:

http://rbelmont.mameworld.info/aosdk_140a2.zip

- Using Deunan Knute's ARM7 so DISDL/DIPAN are written properly
- Reenabled use of DISDL/DIPAN
- Tried to fix 16-bit samples. Failed smirk

Reminder: do not download this if you want to just listen to music - this is for folks who want to contribute to the AICA emulation only right now.

Joined: Sep 2007
Posts: 56
K
Member
Offline
Member
K
Joined: Sep 2007
Posts: 56
And here are my latest updates:

Code
diff -Nru aosdk_base/eng_dsf/aica.c aosdk/eng_dsf/aica.c
--- aosdk_base/eng_dsf/aica.c	2008-02-07 21:46:42.000000000 -0800
+++ aosdk/eng_dsf/aica.c	2008-02-07 20:41:31.000000000 -0800
@@ -69,14 +69,14 @@
 #define ALFOWS(slot)		((slot->udata.data[0x1c/2]>>0x3)&0x0003)
 #define ALFOS(slot)		((slot->udata.data[0x1c/2]>>0x0)&0x0007)
 
-#define ISEL(slot)		((slot->udata.data[0x20/2]>>0x4)&0x000F)
-#define IMXL(slot)		((slot->udata.data[0x20/2]>>0x0)&0x000F)
+#define ISEL(slot)		((slot->udata.data[0x20/2]>>0x0)&0x000F)
+#define IMXL(slot)		((slot->udata.data[0x20/2]>>0x4)&0x000F)
 
 #define DISDL(slot)		((slot->udata.data[0x24/2]>>0x8)&0x000F)
 #define DIPAN(slot)		((slot->udata.data[0x24/2]>>0x0)&0x001F)
 
-#define EFSDL(slot)		((AICA->EFSPAN[slot/2]>>8)&0x000f)
-#define EFPAN(slot)		((AICA->EFSPAN[slot/2]>>0)&0x001f) 
+#define EFSDL(slot)		((AICA->EFSPAN[slot*4]>>8)&0x000f)
+#define EFPAN(slot)		((AICA->EFSPAN[slot*4]>>0)&0x001f) 
 
 //Envelope times in ms
 static const double ARTimes[64]={100000/*infinity*/,100000/*infinity*/,8100.0,6900.0,6000.0,4800.0,4000.0,3400.0,3000.0,2400.0,2000.0,1700.0,1500.0,
@@ -116,6 +116,7 @@
 	} udata;
 	UINT8 active;	//this slot is currently playing
 	UINT8 *base;		//samples base address
+	UINT32 prv_addr;    // previous play address (for ADPCM)
 	UINT32 cur_addr;	//current play address (24.8)
 	UINT32 nxt_addr;	//next play address
 	UINT32 step;		//pitch step (24.8)
@@ -124,18 +125,21 @@
 	struct _LFO PLFO;		//Phase LFO
 	struct _LFO ALFO;		//Amplitude LFO
 	int slot;
-	int cur_sample;    //current ADPCM sample
-	int nxt_sample;    //next ADPCM sample
-	int cur_quant;     //current ADPCM step
-	int nxt_quant;     //next ADPCM step
-	int do_adpcm;      //do ADPCM decoding
+	int cur_sample;       //current ADPCM sample
+	int nxt_sample;       //next ADPCM sample
+	int cur_quant;        //current ADPCM step
+	int nxt_quant;        //next ADPCM step
+	int sample_lsa;       // current ADPCM sample at loop start
+	int quant_lsa;        // current ADPCM step at loop start
+	int do_adpcm;         // do ADPCM decoding - number of iterations
+	int loop_adpcm;       // ADPCM sampler has passed LSA
 };
 
 
 #define MEM4B(aica)		((aica->udata.data[0]>>0x0)&0x0200)
 #define DAC18B(aica)		((aica->udata.data[0]>>0x0)&0x0100)
 #define MVOL(aica)		((aica->udata.data[0]>>0x0)&0x000F)
-#define RBL(aica)		((aica->udata.data[2]>>0x13)&0x0003)
+#define RBL(aica)		((aica->udata.data[2]>>0xD)&0x0003)
 #define RBP(aica)		((aica->udata.data[2]>>0x0)&0x0fff)
 #define MOFULL(aica)   		((aica->udata.data[4]>>0x0)&0x1000)
 #define MOEMPTY(aica)		((aica->udata.data[4]>>0x0)&0x0800)
@@ -415,7 +419,7 @@
 
 	slot->active=1;
 	slot->Backwards=0;
-	slot->cur_addr=0; slot->nxt_addr=1<<SHIFT;
+	slot->cur_addr=0; slot->nxt_addr=1<<SHIFT; slot->prv_addr=-1;
 	start_offset = SA(slot);	// AICA can play 16-bit samples from any boundry
 	slot->base=&AICA->AICARAM[start_offset];
 	slot->step=AICA_Step(slot);
@@ -427,6 +431,7 @@
 	if (PCMS(slot) >= 2)
 	{
 		slot->do_adpcm=1;
+		slot->loop_adpcm=0;
 		InitADPCM(&(slot->cur_sample), &(slot->cur_quant));
 		InitADPCM(&(slot->nxt_sample), &(slot->nxt_quant));
 	}
@@ -445,7 +450,7 @@
 	{
 		slot->active=0;
 	}
-	slot->udata.data[0]&=~0x800;
+	slot->udata.data[0]&=~0x4000;
 }
 
 #define log_base_2(n) (log((float) n)/log((float) 2))
@@ -622,7 +627,7 @@
 						}
 					}
 				}
-				slot->udata.data[0]&=~0x1000;
+				slot->udata.data[0]&=~0x8000;
 			}
 			break;
 		case 0x18:
@@ -657,9 +662,9 @@
 					AICA->DSP.RBL=8*1024;
 				else if(v==1)
 					AICA->DSP.RBL=16*1024;
-				if(v==2)
+				else if(v==2)
 					AICA->DSP.RBL=32*1024;
-				if(v==3)
+				else if(v==3)
 					AICA->DSP.RBL=64*1024;
 			}
 			break;
@@ -857,7 +862,11 @@
 	}
 	else if(addr<0x3000)
 	{
-		if (addr < 0x28be)
+		if (addr <= 0x2044)
+		{
+			v = AICA->EFSPAN[addr&0x7f];
+		}
+		else if (addr < 0x28be)
 		{
 			AICA_UpdateRegR(AICA, addr&0xff);
 			v= *((unsigned short *) (AICA->udata.datab+((addr&0xff))));
@@ -925,6 +934,8 @@
 	UINT32 addr1,addr2,addr_select;                                   // current and next sample addresses
 	UINT32 *addr[2]      = {&addr1, &addr2};                          // used for linear interpolation
 	UINT32 *slot_addr[2] = {&(slot->cur_addr), &(slot->nxt_addr)};    //
+	int    *adpcm_sample[2] = {&(slot->cur_sample), &(slot->nxt_sample)};
+	int    *adpcm_quant[2]  = {&(slot->cur_quant), &(slot->nxt_quant)};
 
 	if(SSCTL(slot)!=0)	//no FM or noise yet
 		return 0;
@@ -942,8 +953,8 @@
 	}
 	else if(PCMS(slot) == 0) 
 	{
-		addr1=(slot->cur_addr>>(SHIFT-1));
-		addr2=(slot->nxt_addr>>(SHIFT-1));
+		addr1=(slot->cur_addr>>(SHIFT-1))&0x1ffffe;
+		addr2=(slot->nxt_addr>>(SHIFT-1))&0x1ffffe;
 	}
 	else
 	{
@@ -963,11 +974,11 @@
 	}
 	else if (PCMS(slot) == 0)	//16 bit signed
 	{
-		UINT8 *p1=(UINT8 *) (AICA->AICARAM+((SA(slot)+addr1)&0x1fffff));
-		UINT8 *p2=(UINT8 *) (AICA->AICARAM+((SA(slot)+addr2)&0x1fffff));
+		INT16 *p1=(signed short *) (AICA->AICARAM+((SA(slot)+addr1)&0x1fffff));
+		INT16 *p2=(signed short *) (AICA->AICARAM+((SA(slot)+addr2)&0x1fffff));
 		INT32 s;
 		INT32 fpart=slot->cur_addr&((1<<SHIFT)-1);
-		s=(int) ((INT16)(p1[1] | (p1[0]<<8)))*((1<<SHIFT)-fpart)+(int) ((INT16)(p2[1] | (p2[0]<<8)))*fpart;
+		s=(int) LE16(p1[0])*((1<<SHIFT)-fpart)+(int) LE16(p2[0])*fpart;
 		sample=(s>>SHIFT);
 	}
 	else	// 4-bit ADPCM
@@ -976,13 +987,19 @@
 		UINT8 *p2=(unsigned char *) (AICA->AICARAM+((SA(slot)+(addr2>>1))&0x1fffff));
 		INT32 s;
 		INT32 fpart=slot->cur_addr&((1<<SHIFT)-1);
-		if (slot->do_adpcm)
+		UINT32 addr=slot->prv_addr>>SHIFT;
+		while (slot->do_adpcm--)
+		{
+			int shift1,delta1;
+			addr += 1;
+			shift1 = 4*((addr&1)^1);
+			delta1 = (*p1>>shift1)&0xF;
+//			printf("DEBUG: addr %04X sample %+06d delta %01X quant %05d\n",addr,slot->cur_sample,delta1,slot->cur_quant);
+			DecodeADPCM(&(slot->cur_sample),delta1,&(slot->cur_quant));
+		}
 		{
-			int shift1 = 4*((addr1&1)^1);
 			int shift2 = 4*((addr2&1)^1);
-			int delta1 = (*p1>>shift1)&0xF;
 			int delta2 = (*p2>>shift2)&0xF;
-			DecodeADPCM(&(slot->cur_sample),delta1,&(slot->cur_quant));
 			slot->nxt_sample=slot->cur_sample;
 			slot->nxt_quant=slot->cur_quant;
 			DecodeADPCM(&(slot->nxt_sample),delta2,&(slot->nxt_quant));
@@ -992,21 +1009,27 @@
 	}
 
 	// Only do an ADPCM decode when crossing a whole-address boundary
-	if(((slot->cur_addr+step)>>SHIFT)>((slot->cur_addr)>>SHIFT)) slot->do_adpcm=1;
-	else slot->do_adpcm=0;
+	slot->do_adpcm = ((slot->cur_addr+step)>>SHIFT)-((slot->cur_addr)>>SHIFT);
 	
+	slot->prv_addr=slot->cur_addr;
 	slot->cur_addr+=step;
 	slot->nxt_addr=slot->cur_addr+(1<<SHIFT);
-	
+
 	addr1=slot->cur_addr>>SHIFT;
 	addr2=slot->nxt_addr>>SHIFT;
-	
+
 	if(addr1>=LSA(slot))
 	{
 		if(LPSLNK(slot) && slot->EG.state==ATTACK)
 			slot->EG.state = DECAY1;
+		if(PCMS(slot)==2 && !(slot->loop_adpcm))
+		{
+			slot->sample_lsa = slot->cur_sample;
+			slot->quant_lsa = slot->cur_quant;
+			slot->loop_adpcm = 1;
+		}
 	}
-	
+
 	for (addr_select=0;addr_select<2;addr_select++)
 	{
 		INT32 rem_addr;
@@ -1024,6 +1047,11 @@
 			{
 				rem_addr = *slot_addr[addr_select] - (LEA(slot)<<SHIFT);
 				*slot_addr[addr_select]=(LSA(slot)<<SHIFT) + rem_addr;
+				if(PCMS(slot)==2 && addr_select==0)
+				{
+					*adpcm_sample[addr_select] = slot->sample_lsa;
+					*adpcm_quant[addr_select] = slot->quant_lsa;
+				}
 			}
 			break;
 		}
@@ -1047,7 +1075,6 @@
 {
 	INT16 *bufr,*bufl;
 	int sl, s, i;
-	struct _SLOT *s2 = &AICA->Slots[39];
 
 	bufr=bufferr;
 	bufl=bufferl;
@@ -1064,7 +1091,7 @@
 			if(AICA->Slots[sl].active)
 			{
 				struct _SLOT *slot=AICA->Slots+sl;
-				unsigned short Enc;
+				unsigned int Enc;
 				signed int sample;
 
 				sample=AICA_UpdateSlot(AICA, slot);
@@ -1081,14 +1108,14 @@
 			AICA->BUFPTR&=63;
 		}
 
-#if 0 	// actually works somewhat, but not yet
+#if 1
 		AICADSP_Step(&AICA->DSP);
 
 		for(i=0;i<16;++i)
 		{
 			if(EFSDL(i))
 			{
-				unsigned short Enc=((EFPAN(i))<<0x8)|((EFSDL(i))<<0xd);
+				unsigned int Enc=((EFPAN(i))<<0x8)|((EFSDL(i))<<0xd);
 				smpl+=(AICA->DSP.EFREG[i]*AICA->LPANTABLE[Enc])>>SHIFT;
 				smpr+=(AICA->DSP.EFREG[i]*AICA->RPANTABLE[Enc])>>SHIFT;
 			}
diff -Nru aosdk_base/eng_dsf/aicadsp.c aosdk/eng_dsf/aicadsp.c
--- aosdk_base/eng_dsf/aicadsp.c	2008-02-07 21:44:10.000000000 -0800
+++ aosdk/eng_dsf/aicadsp.c	2008-02-07 20:37:30.000000000 -0800
@@ -86,41 +86,41 @@
 #endif
 	for(step=0;step</*128*/DSP->LastStep;++step)
 	{
-		UINT16 *IPtr=DSP->MPRO+step*4;
-
+		UINT16 *IPtr=DSP->MPRO+step*8;
+				
 //      if(IPtr[0]==0 && IPtr[1]==0 && IPtr[2]==0 && IPtr[3]==0)
 //          break;
 
-		UINT32 TRA=(IPtr[0]>>8)&0x7F;
-		UINT32 TWT=(IPtr[0]>>7)&0x01;
-		UINT32 TWA=(IPtr[0]>>0)&0x7F;
-
-		UINT32 XSEL=(IPtr[1]>>15)&0x01;
-		UINT32 YSEL=(IPtr[1]>>13)&0x03;
-		UINT32 IRA=(IPtr[1]>>6)&0x3F;
-		UINT32 IWT=(IPtr[1]>>5)&0x01;
-		UINT32 IWA=(IPtr[1]>>0)&0x1F;
-
-		UINT32 TABLE=(IPtr[2]>>15)&0x01;
-		UINT32 MWT=(IPtr[2]>>14)&0x01;
-		UINT32 MRD=(IPtr[2]>>13)&0x01;
-		UINT32 EWT=(IPtr[2]>>12)&0x01;
-		UINT32 EWA=(IPtr[2]>>8)&0x0F;
-		UINT32 ADRL=(IPtr[2]>>7)&0x01;
-		UINT32 FRCL=(IPtr[2]>>6)&0x01;
-		UINT32 SHIFT=(IPtr[2]>>4)&0x03;
-		UINT32 YRL=(IPtr[2]>>3)&0x01;
-		UINT32 NEGB=(IPtr[2]>>2)&0x01;
-		UINT32 ZERO=(IPtr[2]>>1)&0x01;
-		UINT32 BSEL=(IPtr[2]>>0)&0x01;
-
-		UINT32 NOFL=(IPtr[3]>>15)&1;		//????
-		UINT32 COEF=(IPtr[3]>>9)&0x3f;
-
-		UINT32 MASA=(IPtr[3]>>2)&0x1f;	//???
-		UINT32 ADREB=(IPtr[3]>>1)&0x1;
-		UINT32 NXADR=(IPtr[3]>>0)&0x1;
-
+		UINT32 TRA=(IPtr[0]>>9)&0x7F;
+		UINT32 TWT=(IPtr[0]>>8)&0x01;
+		UINT32 TWA=(IPtr[0]>>1)&0x7F;
+
+		UINT32 XSEL=(IPtr[2]>>15)&0x01;
+		UINT32 YSEL=(IPtr[2]>>13)&0x03;
+		UINT32 IRA=(IPtr[2]>>7)&0x3F;
+		UINT32 IWT=(IPtr[2]>>6)&0x01;
+		UINT32 IWA=(IPtr[2]>>1)&0x1F;
+
+		UINT32 TABLE=(IPtr[4]>>15)&0x01;
+		UINT32 MWT=(IPtr[4]>>14)&0x01;
+		UINT32 MRD=(IPtr[4]>>13)&0x01;
+		UINT32 EWT=(IPtr[4]>>12)&0x01;
+		UINT32 EWA=(IPtr[4]>>8)&0x0F;
+		UINT32 ADRL=(IPtr[4]>>7)&0x01;
+		UINT32 FRCL=(IPtr[4]>>6)&0x01;
+		UINT32 SHIFT=(IPtr[4]>>4)&0x03;
+		UINT32 YRL=(IPtr[4]>>3)&0x01;
+		UINT32 NEGB=(IPtr[4]>>2)&0x01;
+		UINT32 ZERO=(IPtr[4]>>1)&0x01;
+		UINT32 BSEL=(IPtr[4]>>0)&0x01;
+
+		UINT32 NOFL=(IPtr[6]>>15)&1;		//????
+		UINT32 COEF=step;
+
+		UINT32 MASA=(IPtr[6]>>9)&0x1f;	//???
+		UINT32 ADREB=(IPtr[6]>>8)&0x1;
+		UINT32 NXADR=(IPtr[6]>>7)&0x1;
+		
 		INT64 v;
 
 		//operations are done at 24 bit precision
@@ -208,7 +208,7 @@
 		if(YSEL==0)
 			Y=FRC_REG;
 		else if(YSEL==1)
-			Y=DSP->COEF[COEF]>>3;	//COEF is 16 bits
+			Y=DSP->COEF[COEF<<1]>>3;	//COEF is 16 bits
 		else if(YSEL==2)
 			Y=(Y_REG>>11)&0x1FFF;
 		else if(YSEL==3)
@@ -276,7 +276,7 @@
 		if(MRD || MWT)
 		//if(0)
 		{
-			ADDR=DSP->MADRS[MASA];
+			ADDR=DSP->MADRS[MASA<<1];
 			if(!TABLE)
 				ADDR+=DSP->DEC;
 			if(ADREB)
@@ -290,7 +290,7 @@
 			//ADDR<<=1;
 			//ADDR+=DSP->RBP<<13;
 			//MEMVAL=DSP->AICARAM[ADDR>>1];
-			ADDR+=DSP->RBP<<12;
+			ADDR+=DSP->RBP<<10;
 			if(MRD && (step&1))	//memory only allowed on odd? DoA inserts NOPs on even
 			{
 				if(NOFL)
@@ -339,9 +339,9 @@
 	DSP->Stopped=0;
 	for(i=127;i>=0;--i)
 	{
-		UINT16 *IPtr=DSP->MPRO+i*4;
+		UINT16 *IPtr=DSP->MPRO+i*8;
 
-		if(IPtr[0]!=0 || IPtr[1]!=0 || IPtr[2]!=0 || IPtr[3]!=0)
+		if(IPtr[0]!=0 || IPtr[2]!=0 || IPtr[4]!=0 || IPtr[6]!=0)
 			break;
 	}
 	DSP->LastStep=i+1;
diff -Nru aosdk_base/eng_dsf/aicadsp.h aosdk/eng_dsf/aicadsp.h
--- aosdk_base/eng_dsf/aicadsp.h	2008-02-07 21:44:10.000000000 -0800
+++ aosdk/eng_dsf/aicadsp.h	2008-02-07 20:37:52.000000000 -0800
@@ -12,9 +12,9 @@
 
 //context
 
-	INT16 COEF[128];		//16 bit signed
-	UINT16 MADRS[64];	//offsets (in words), 16 bit
-	UINT16 MPRO[128*4*2];	//128 steps 64 bit
+	INT16 COEF[128*2];		//16 bit signed
+	UINT16 MADRS[64*2];	//offsets (in words), 16 bit
+	UINT16 MPRO[128*4*2*2];	//128 steps 64 bit
 	INT32 TEMP[128];	//TEMP regs,24 bit signed
 	INT32 MEMS[32];	//MEMS regs,24 bit signed
 	UINT32 DEC;

- Fixed 16-bit samples smile
- Swapped IMXL/ISEL (thanks Deunan!)
- Made all of the necessary changes (or so I think) to get the DSP working
- Changed LPANTABLE/RPANTABLE lookup to use int instead of short (since we're dealing with 0x20000 values now).
- Changed the ADPCM decoding loop so that it always operates a single step at a time. ADPCM still saturates far too much - still some work to be done here.

Joined: Dec 1969
Posts: 920
Likes: 3
R
Senior Member
Offline
Senior Member
R
Joined: Dec 1969
Posts: 920
Likes: 3
Yegads. Deunan's code really is nice. I assume Makaron is DirectX based? If not I'd love to do a Mac port...

Joined: Feb 2008
Posts: 107
D
Senior Member
Offline
Senior Member
D
Joined: Feb 2008
Posts: 107
If I may have a suggestion: AICA should be addressing samples, not bytes. Change it and most of your code will become universal - as only the actual fetch from memory will have to convert sample number to data address.

As for ADPCM - this approach will never work. It might for non-looped samples when the sound pitch matches the sound's own, but that's about it. I'm not so sure current skipping is correct for 16-bit cases as well. Things to note:
- ADPCM always starts with lowest nible, then moves to high, then to next byte
- If the sound pitch is changed, the step will not be one. ADPCM decoder still needs to refresh it's internal state for _every_ sample on it's way to current one. That also means you need to implement proper skipping for step < 1 - this will be easier if you follow my first suggestion.
- I understand you're doing interpolation between two consecutive samples. Be careful with that on ADPCM. I'd disable it for now for those samples.
- And there's the loop processing smile But one thing at a time.

Joined: Mar 2001
Posts: 17,215
Likes: 234
R
Very Senior Member
OP Offline
Very Senior Member
R
Joined: Mar 2001
Posts: 17,215
Likes: 234
1.4.0a3 (still developer only):

http://rbelmont.mameworld.info/aosdk_140a3.zip

Includes kingshriek's patch above plus I rewrote ADPCM (including Deunan's hint that the samples go 10 32 54 76 instead of 01 23 45 67). Things still get crunchy due to DC offset drift (it's quite noticeable in AO with the 'scope) so it may be our actual decode constants aren't quite right, but in general it sounds far better.

For end users following along at home, you can hear the current code playing Sakura Taisen 4 here:

http://rbelmont.mameworld.info/sakutai4.mp3

Joined: Feb 2008
Posts: 107
D
Senior Member
Offline
Senior Member
D
Joined: Feb 2008
Posts: 107
Out of curiosity, where did you get that tune? And is it the one that runs on the "Press START" screen?

Joined: Mar 2001
Posts: 17,215
Likes: 234
R
Very Senior Member
OP Offline
Very Senior Member
R
Joined: Mar 2001
Posts: 17,215
Likes: 234
It's from kingshriek's rip page. I don't know where in game it plays, but it's the same song as the title screen tune on "Hanagumi Taisen Columns - Sakura Wars" in MAME. The metadata says it's "Declaration! Imperial Floral Assault Group - Final Chapter (Imperial Castle Version)", which doesn't sound very title-screen-y to me.

Joined: Feb 2008
Posts: 107
D
Senior Member
Offline
Senior Member
D
Joined: Feb 2008
Posts: 107
Ha! What a small world we live in smile
Anyway, the new ADPCM looks so much better. But if I read this right it'll still misbehave on the very last sample. In case there's a loop it will try to fetch one more byte over LEA rather then look at the first one again. I still say ADPCM + interpolation = tricky. And that's just one of the caveats.

Joined: Mar 2001
Posts: 17,215
Likes: 234
R
Very Senior Member
OP Offline
Very Senior Member
R
Joined: Mar 2001
Posts: 17,215
Likes: 234
Well, it's not trying to interpolate ADPCM at the moment anyway, although I didn't pay a lot of attention to the looping. Is our basic ADPCM decode correct to your knowledge? I got it from ElSemi but I have no real way to validate it smile

Page 12 of 55 1 2 10 11 12 13 14 54 55

Moderated by  R. Belmont, Richard Bannister 

Link Copied to Clipboard
Who's Online Now
2 members (Kale, 1 invisible), 233 guests, and 1 robot.
Key: Admin, Global Mod, Mod
ShoutChat
Comment Guidelines: Do post respectful and insightful comments. Don't flame, hate, spam.
Forum Statistics
Forums9
Topics9,320
Posts121,923
Members5,074
Most Online1,283
Dec 21st, 2022
Our Sponsor
These forums are sponsored by Superior Solitaire, an ad-free card game collection for macOS and iOS. Download it today!

Superior Solitaire
Forum hosted by www.retrogamesformac.com