diff src/adplug/core/protrack.cxx @ 952:87666f9bf6d0 trunk

[svn] Upstream commit "Vastly enhanced generic Protracker player and modified loaders accordingly. Copl now supports a getchip() method. A2M loader enhanced for OPL3 features." manually applied by decoding the actual changes from an ocean of whitespace damage. It compiles, but do test it.
author chainsaw
date Fri, 13 Apr 2007 09:09:50 -0700
parents 3da1b8942b8b
children 4709ce4e209e
line wrap: on
line diff
--- a/src/adplug/core/protrack.cxx	Fri Apr 13 08:07:32 2007 -0700
+++ b/src/adplug/core/protrack.cxx	Fri Apr 13 09:09:50 2007 -0700
@@ -1,6 +1,6 @@
 /*
  * Adplug - Replayer for many OPL2/OPL3 audio file formats.
- * Copyright (C) 1999 - 2006 Simon Peter, <dn.tlp@gmx.net>, et al.
+ * Copyright (C) 1999 - 2007 Simon Peter, <dn.tlp@gmx.net>, et al.
  * 
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -21,7 +21,7 @@
  * NOTES:
  * This is a generic Protracker-based formats player. It offers all Protracker
  * features, plus a good set of extensions to be compatible to other Protracker
- * derivatives. It is derived from the original SA2 player by me. If you got a
+ * derivatives. It is derived from the former SA2 player. If you got a
  * Protracker-like format, this is most certainly the player you want to use.
  */
 
@@ -43,7 +43,8 @@
 
 CmodPlayer::CmodPlayer(Copl *newopl)
   : CPlayer(newopl), inst(0), order(0), arplist(0), arpcmd(0), initspeed(6),
-    activechan(0xffff), flags(Standard), nop(0), nrows(0), npats(0), nchans(0)
+    nop(0), activechan(0xffffffff), flags(Standard), curchip(opl->getchip()),
+    nrows(0), npats(0), nchans(0)
 {
   realloc_order(128);
   realloc_patterns(64, 64, 9);
@@ -58,16 +59,18 @@
 
 bool CmodPlayer::update()
 {
-	unsigned char pattbreak=0,donote;		// remember vars
-	unsigned char pattnr,chan,info1,info2,info;	// cache vars
-	unsigned short track;
-	unsigned long row;
+  unsigned char		pattbreak=0, donote, pattnr, chan, oplchan, info1,
+    info2, info, pattern_delay;
+  unsigned short	track;
+  unsigned long		row;
 
 	if(!speed)		// song full stop
 		return !songend;
 
 	// effect handling (timer dependant)
-	for(chan=0;chan<nchans;chan++) {
+  for(chan = 0; chan < nchans; chan++) {
+    oplchan = set_opl_chip(chan);
+
 		if(arplist && arpcmd && inst[channel[chan].inst].arpstart)	// special arpeggio
 			if(channel[chan].arpspdcnt)
 				channel[chan].arpspdcnt--;
@@ -84,11 +87,11 @@
 					case 254: channel[chan].arppos = arplist[channel[chan].arppos]; break; // arpeggio loop
 					default: if(arpcmd[channel[chan].arppos]) {
 								if(arpcmd[channel[chan].arppos] / 10)
-									opl->write(0xe3 + op_table[chan], arpcmd[channel[chan].arppos] / 10 - 1);
+	      opl->write(0xe3 + op_table[oplchan], arpcmd[channel[chan].arppos] / 10 - 1);
 								if(arpcmd[channel[chan].arppos] % 10)
-									opl->write(0xe0 + op_table[chan], (arpcmd[channel[chan].arppos] % 10) - 1);
+	      opl->write(0xe0 + op_table[oplchan], (arpcmd[channel[chan].arppos] % 10) - 1);
 								if(arpcmd[channel[chan].arppos] < 10)	// ?????
-									opl->write(0xe0 + op_table[chan], arpcmd[channel[chan].arppos] - 1);
+	      opl->write(0xe0 + op_table[oplchan], arpcmd[channel[chan].arppos] - 1);
 							 }
 					}
 					if(arpcmd[channel[chan].arppos] != 252) {
@@ -185,19 +188,19 @@
 	}
 
 	// arrangement handling
-	if(ord >= length) {
-		songend = 1;				// set end-flag
-		ord = restartpos;
-	}
+  if(!resolve_order()) return !songend;
 	pattnr = order[ord];
 
         if(!rw) AdPlug_LogWrite("\nCmodPlayer::update(): Pattern: %d, Order: %d\n", pattnr, ord);
         AdPlug_LogWrite("CmodPlayer::update():%3d|", rw);
 
 	// play row
+  pattern_delay = 0;
 	row = rw;
-	for(chan=0;chan<nchans;chan++) {
-		if(!(activechan >> (15 - chan)) & 1) {	// channel active?
+  for(chan = 0; chan < nchans; chan++) {
+    oplchan = set_opl_chip(chan);
+
+    if(!(activechan >> (31 - chan)) & 1) {	// channel active?
                   AdPlug_LogWrite("N/A|");
 		  continue;
 		}
@@ -246,30 +249,36 @@
 		else
 			info = (channel[chan].info1 << 4) + channel[chan].info2;
 		switch(channel[chan].fx) {
-		case 3:	if(tracks[track][row].note) {					// tone portamento
-					if(tracks[track][row].note < 13)
-						channel[chan].nextfreq = notetable[tracks[track][row].note - 1];
-					else
-						if(tracks[track][row].note % 12 > 0)
-							channel[chan].nextfreq = notetable[(tracks[track][row].note % 12) - 1];
+    case 3: // tone portamento
+      if(tracks[track][row].note) {
+	if(tracks[track][row].note < 13)
+	  channel[chan].nextfreq = notetable[tracks[track][row].note - 1];
 						else
-							channel[chan].nextfreq = notetable[11];
-					channel[chan].nextoct = (tracks[track][row].note - 1) / 12;
-					if(tracks[track][row].note == 127) {	// handle key off
-						channel[chan].nextfreq = channel[chan].freq;
-						channel[chan].nextoct = channel[chan].oct;
+	  if(tracks[track][row].note % 12 > 0)
+	    channel[chan].nextfreq = notetable[(tracks[track][row].note % 12) - 1];
+	  else
+	    channel[chan].nextfreq = notetable[11];
+	channel[chan].nextoct = (tracks[track][row].note - 1) / 12;
+	if(tracks[track][row].note == 127) {	// handle key off
+	  channel[chan].nextfreq = channel[chan].freq;
+	  channel[chan].nextoct = channel[chan].oct;
+	}
 					}
-				}
 				if(info)		// remember vars
 					channel[chan].portainfo = info;
 				break;
-		case 4: if(info) {										// vibrato (remember vars)
-					channel[chan].vibinfo1 = info1;
-					channel[chan].vibinfo2 = info2;
-				}
+
+    case 4: // vibrato (remember vars)
+      if(info) {
+	channel[chan].vibinfo1 = info1;
+	channel[chan].vibinfo2 = info2;
+      }
 				break;
+
 		case 7: tempo = info; break;							// set tempo
+
 		case 8: channel[chan].key = 0; setfreq(chan); break;	// release sustaining note
+
 		case 9: // set carrier/modulator volume
 				if(info1)
 					channel[chan].vol1 = info1 * 7;
@@ -277,7 +286,10 @@
 					channel[chan].vol2 = info2 * 7;
 				setvolume(chan);
 				break;
-		case 11: pattbreak = 1; rw = 0; if(info < ord) songend = 1; ord = info; break; // position jump
+
+    case 11: // position jump
+      pattbreak = 1; rw = 0; if(info < ord) songend = 1; ord = info; break;
+
 		case 12: // set volume
 				channel[chan].vol1 = info;
 				channel[chan].vol2 = info;
@@ -287,35 +299,54 @@
 					channel[chan].vol2 = 63;
 				setvolume(chan);
 				break;
-		case 13: if(!pattbreak) { pattbreak = 1; rw = info; ord++; } break;	// pattern break
+
+    case 13: // pattern break
+      if(!pattbreak) { pattbreak = 1; rw = info; ord++; } break;
+
 		case 14: // extended command
 				switch(info1) {
-				case 0: if(info2)								// define cell-tremolo
-							regbd |= 128;
-						else
-							regbd &= 127;
+      case 0: // define cell-tremolo
+	if(info2)
+	  regbd |= 128;
+	else
+	  regbd &= 127;
 						opl->write(0xbd,regbd);
 						break;
-				case 1: if(info2)								// define cell-vibrato
-							regbd |= 64;
-						else
-							regbd &= 191;
+
+      case 1: // define cell-vibrato
+	if(info2)
+	  regbd |= 64;
+	else
+	  regbd &= 191;
 						opl->write(0xbd,regbd);
 						break;
-				case 4: vol_up_alt(chan,info2);					// increase volume fine
+
+      case 4: // increase volume fine
+	vol_up_alt(chan,info2);
 						setvolume(chan);
 						break;
-				case 5: vol_down_alt(chan,info2);				// decrease volume fine
+
+      case 5: // decrease volume fine
+	vol_down_alt(chan,info2);
 						setvolume(chan);
 						break;
-				case 6: slide_up(chan,info2);					// manual slide up
+
+      case 6: // manual slide up
+	slide_up(chan,info2);
 						setfreq(chan);
 						break;
-				case 7: slide_down(chan,info2);					// manual slide down
+
+      case 7: // manual slide down
+	slide_down(chan,info2);
 						setfreq(chan);
 						break;
+
+      case 8: // pattern delay (rows)
+	pattern_delay = info2 * speed;
+	break;
 				}
 				break;
+
 		case 15: // SA2 set speed
 			if(info <= 0x1f)
 				speed = info;
@@ -324,6 +355,7 @@
 			if(!info)
 				songend = 1;
 			break;
+
 		case 17: // alternate set volume
 			channel[chan].vol1 = info;
 			if(channel[chan].vol1 > 63)
@@ -336,15 +368,18 @@
 
 			setvolume(chan);
 			break;
+
 		case 18: // AMD set speed
 			if(info <= 31 && info > 0)
 				speed = info;
 			if(info > 31 || !info)
 				tempo = info;
 			break;
+
 		case 19: // RAD/A2M set speed
 			speed = (info ? info : info + 1);
 			break;
+
 		case 21: // set modulator volume
 			if(info <= 63)
 				channel[chan].vol2 = info;
@@ -352,6 +387,7 @@
 				channel[chan].vol2 = 63;
 			setvolume(chan);
 			break;
+
 		case 22: // set carrier volume
 			if(info <= 63)
 				channel[chan].vol1 = info;
@@ -359,35 +395,45 @@
 				channel[chan].vol1 = 63;
 			setvolume(chan);
 			break;
+
 		case 23: // fine frequency slide up
 			slide_up(chan,info);
 			setfreq(chan);
 			break;
+
 		case 24: // fine frequency slide down
 			slide_down(chan,info);
 			setfreq(chan);
 			break;
+
 		case 25: // set carrier/modulator waveform
 			if(info1 != 0x0f)
-				opl->write(0xe3 + op_table[chan],info1);
+	opl->write(0xe3 + op_table[oplchan],info1);
 			if(info2 != 0x0f)
-				opl->write(0xe0 + op_table[chan],info2);
+	opl->write(0xe0 + op_table[oplchan],info2);
 			break;
+
 		case 27: // set chip tremolo/vibrato
-			if (info1)
+      if(info1)
 				regbd |= 128;
 			else
 				regbd &= 127;
-			if (info2)
+      if(info2)
 				regbd |= 64;
 			else
 				regbd &= 191;
 			opl->write(0xbd,regbd);
 			break;
+
+    case 29: // pattern delay (frames)
+      pattern_delay = info;
+      break;
 		}
 	}
 
-	del = speed - 1;	// speed compensation
+  // speed compensation
+  del = speed - 1 + pattern_delay;
+
 	if(!pattbreak) {	// next row (only if no manual advance)
 		rw++;
 		if(rw >= nrows) {
@@ -395,16 +441,51 @@
 			ord++;
 		}
 	}
+
+  resolve_order();	// so we can report songend right away
+  AdPlug_LogWrite("\n");
+  return !songend;
+}
+
+unsigned char CmodPlayer::set_opl_chip(unsigned char chan)
+  /*
+   * Sets OPL chip according to channel number. Channels 0-8 are on first chip,
+   * channels 9-17 are on second chip. Returns corresponding OPL channel
+   * number.
+   */
+{
+  int newchip = chan < 9 ? 0 : 1;
+
+  if(newchip != curchip) {
+    opl->setchip(newchip);
+    curchip = newchip;
+  }
+
+  return chan % 9;
+}
+
+bool CmodPlayer::resolve_order()
+  /*
+   * Resolves current orderlist entry, checking for jumps and loops.
+   *
+   * Returns true on correct processing, false if immediate recursive loop
+   * has been detected.
+   */
+{
 	if(ord < length) {
-	  if(order[ord] >= JUMPMARKER) {	// jump to order
-	    ord = order[ord] - JUMPMARKER;
-	    songend = 1;
+    while(order[ord] >= JUMPMARKER) {	// jump to order
+      unsigned long neword = order[ord] - JUMPMARKER;
+
+      if(neword <= ord) songend = 1;
+      if(neword == ord) return false;
+      ord = neword;
 	  }
-	} else
+  } else {
 	  songend = 1;
+    ord = restartpos;
+  }
 
-        AdPlug_LogWrite("\n");
-	return !songend;
+  return true;
 }
 
 void CmodPlayer::rewind(int subsong)
@@ -424,7 +505,20 @@
       nop = (order[i] > nop ? order[i] : nop);
 
   opl->init();				// Reset OPL chip
-  opl->write(1,32);			// Go to ym3812 mode
+  opl->write(1, 32);	// Go to ym3812 mode
+
+  // Enable OPL3 extensions if flagged
+  if(flags & Opl3) {
+    opl->setchip(1);
+    opl->write(1, 32);
+    opl->write(5, 1);
+    opl->setchip(0);
+  }
+
+  // Enable tremolo/vibrato depth if flagged
+  if(flags & Tremolo) regbd |= 128;
+  if(flags & Vibrato) regbd |= 64;
+  if(regbd) opl->write(0xbd, regbd);
 }
 
 float CmodPlayer::getrefresh()
@@ -520,38 +614,44 @@
 
 void CmodPlayer::setvolume(unsigned char chan)
 {
-	if (flags & Faust)
+  unsigned char oplchan = set_opl_chip(chan);
+
+  if(flags & Faust)
         	setvolume_alt(chan);
 	else {
-		opl->write(0x40 + op_table[chan], 63-channel[chan].vol2 + (inst[channel[chan].inst].data[9] & 192));
-		opl->write(0x43 + op_table[chan], 63-channel[chan].vol1 + (inst[channel[chan].inst].data[10] & 192));
+    opl->write(0x40 + op_table[oplchan], 63-channel[chan].vol2 + (inst[channel[chan].inst].data[9] & 192));
+    opl->write(0x43 + op_table[oplchan], 63-channel[chan].vol1 + (inst[channel[chan].inst].data[10] & 192));
 	}
 }
 
 void CmodPlayer::setvolume_alt(unsigned char chan)
 {
+  unsigned char oplchan = set_opl_chip(chan);
     unsigned char ivol2 = inst[channel[chan].inst].data[9] & 63;
     unsigned char ivol1 = inst[channel[chan].inst].data[10] & 63;
 
-    opl->write(0x40 + op_table[chan], (((63 - channel[chan].vol2 & 63) + ivol2) >> 1) + (inst[channel[chan].inst].data[9] & 192));
-    opl->write(0x43 + op_table[chan], (((63 - channel[chan].vol1 & 63) + ivol1) >> 1) + (inst[channel[chan].inst].data[10] & 192));
+  opl->write(0x40 + op_table[oplchan], (((63 - channel[chan].vol2 & 63) + ivol2) >> 1) + (inst[channel[chan].inst].data[9] & 192));
+  opl->write(0x43 + op_table[oplchan], (((63 - channel[chan].vol1 & 63) + ivol1) >> 1) + (inst[channel[chan].inst].data[10] & 192));
 }
 
 void CmodPlayer::setfreq(unsigned char chan)
 {
-	opl->write(0xa0 + chan, channel[chan].freq & 255);
+  unsigned char oplchan = set_opl_chip(chan);
+
+  opl->write(0xa0 + oplchan, channel[chan].freq & 255);
 	if(channel[chan].key)
-		opl->write(0xb0 + chan, ((channel[chan].freq & 768) >> 8) + (channel[chan].oct << 2) | 32);
+    opl->write(0xb0 + oplchan, ((channel[chan].freq & 768) >> 8) + (channel[chan].oct << 2) | 32);
 	else
-		opl->write(0xb0 + chan, ((channel[chan].freq & 768) >> 8) + (channel[chan].oct << 2));
+    opl->write(0xb0 + oplchan, ((channel[chan].freq & 768) >> 8) + (channel[chan].oct << 2));
 }
 
 void CmodPlayer::playnote(unsigned char chan)
 {
-	unsigned char op = op_table[chan], insnr = channel[chan].inst;
+  unsigned char oplchan = set_opl_chip(chan);
+  unsigned char op = op_table[oplchan], insnr = channel[chan].inst;
 
 	if(!(flags & NoKeyOn))
-	  opl->write(0xb0 + chan, 0);	// stop old note
+    opl->write(0xb0 + oplchan, 0);	// stop old note
 
 	// set instrument data
 	opl->write(0x20 + op, inst[insnr].data[1]);
@@ -562,7 +662,7 @@
 	opl->write(0x83 + op, inst[insnr].data[6]);
 	opl->write(0xe0 + op, inst[insnr].data[7]);
 	opl->write(0xe3 + op, inst[insnr].data[8]);
-	opl->write(0xc0 + chan, inst[insnr].data[0]);
+  opl->write(0xc0 + oplchan, inst[insnr].data[0]);
 	opl->write(0xbd, inst[insnr].misc);	// set misc. register
 
 	// set frequency, volume & play