To: <alsa-devel@xxxxxxxx>
Subject: [PATCH] alsa-tools: us428control enhancements
From: "Michael Bourgeous" <sts.nitrogen@xxxxxxxx>
Date: Fri, 7 Sep 2007 16:18:49 -0600

Hello,

Here's a patch against us428control in the latest alsa-tools that adds
the following enhancements:

- Master fader and Input Monitor faders use a logarithmic scale, like
  in Windows.
- When the Master fader is all the way down, the sound stops instead
  of just getting really quiet.
- Input Monitor pan knob now requires 1 revolution instead of 10 to
  pan from left to right, like in Windows.
- LEDs are reset to a known state when us428control starts.
- A new command line option "-g" to allow the gain sliders to go above
  0dB gain, which may result in clipping.

Mike Bourgeous

--- us428control.0/Cus428State.cc	2007-02-26 02:24:15.000000000 -0700
+++ us428control.1/Cus428State.cc	2007-09-07 15:15:22.000000000 -0600
@@ -45,8 +45,11 @@
 void Cus428State::InitDevice(void)
 {
 	if (us428ctls_sharedmem->CtlSnapShotLast >= 0)
 		SliderChangedTo(eFaderM, ((unsigned char*)(us428ctls_sharedmem->CtlSnapShot + us428ctls_sharedmem->CtlSnapShotLast))[eFaderM]);
+
+	// Reset the LEDs to the state known by us428control
+	LightSend();
 }
 
 int Cus428State::LightSend()
 {
@@ -349,9 +352,13 @@
 		while( index < 4 && (1 << index) !=  Light[0].Value)
 			index++;
 		if (index >= 4)
 			return;
-		Volume[index].PanTo(Diff, us428_ctls->Knob(eK_SET));
+
+		// The pan encoder has 24 detents.  A multiple of 13 allows
+		// the pan to go from full-left to full-right in 20 clicks
+		// instead of 255 clicks (over 10 full turns!).
+		Volume[index].PanTo(Diff * 13, us428_ctls->Knob(eK_SET));
 		if (!LightIs(eL_Mute0 + index))
 			SendVolume(Volume[index]);
 		return;
 	}

--- us428control.0/us428control.cc	2007-02-26 02:24:15.000000000 -0700
+++ us428control.1/us428control.cc	2007-09-07 15:19:39.000000000 -0600
@@ -43,8 +43,10 @@
 #define SND_CARDS	8
 
 int verbose = 1;
 
+int positive_gain = 0;
+
 Cus428Midi Midi;
 
 
 static void error(const char *fmt, ...)
@@ -62,10 +64,11 @@
 static void usage(void)
 {
 	printf("Tascam US-428 Control\n");
 	printf("version %s\n", VERSION);
-	printf("usage: "PROGNAME" [-v verbosity_level 0..2] [-c card] [-D device] [-u usb-device] [-m mode]\n");
+	printf("usage: "PROGNAME" [-v verbosity_level 0..3] [-c card] [-D device] [-u usb-device] [-m mode] [-g]\n");
 	printf("mode is one of (us224, us428, mixxx)\n");
+	printf("The -g option allows the Input Monitor and Master gains to go above 0dB\n");
 }
 /*
  * check the name id of the given hwdep handle
  */
@@ -170,9 +173,9 @@
 	char	*device_name = NULL,
 		*usb_device_name = getenv("DEVICE");
 	char name[64];
 
-	while ((c = getopt(argc, argv, "c:D:u:v:m:")) != -1) {
+	while ((c = getopt(argc, argv, "c:D:u:v:m:g")) != -1) {
 		switch (c) {
 		case 'c':
 			card = atoi(optarg);
 			break;
@@ -194,8 +197,12 @@
 			else
 			if (!strcmp(optarg, "mixxx"))
 				mode = 1;
 			break;
+		case 'g':
+			// Allow positive gain on sliders (which may result in clipping)
+			positive_gain = 1;
+			break;
 		default:
 			usage();
 			return 1;
 		}

--- us428control.0/usbus428ctldefs.h	2007-02-26 02:24:15.000000000 -0700
+++ us428control.1/usbus428ctldefs.h	2007-09-07 15:16:36.000000000 -0600
@@ -19,9 +21,12 @@
  */
 
 #ifdef __cplusplus
 #include <string.h>
+#include <math.h>
+#include <stdlib.h>
 extern int verbose;
+extern int positive_gain;
 #endif
 
 enum E_In84 {
 	eFader0 = 0,
@@ -98,26 +103,94 @@
 	}
 	int Scale(){return 0x40;}
 
 	void calculate() {
-		int lPan = (int)Pan * Slider / 0x80;
-		int ValL = (Slider - lPan) * Scale();
-		LH = ValL >> 8;
-		LL = ValL;
-		int ValR = (Slider + lPan) * Scale();
-		RH = ValR >> 8;
-		RL = ValR;
+		int Val;
+		int ScaledL, ScaledR;
+		float PanLeft, PanRight;
+		float Norm;
+		float dBL, dBR;
+
+		// Subtract 1 because the sliders usually don't reach 0 due to sampling inaccuracy
+		Val = (Slider - 1);
+
+		if(Val < 0) {
+			Val = 0;
+		}
+
+		// This is a balance knob-style pan law
+		PanLeft = (float)(Pan + 127) / 127.0;
+		PanRight = 2.0 - PanLeft;
+
+		if(PanLeft < 0.0) {
+			PanLeft = 0.0;
+		}
+		if(PanLeft > 1.0) {
+			PanLeft = 1.0;
+		}
+		if(PanRight < 0.0) {
+			PanRight = 0.0;
+		}
+		if(PanRight > 1.0) {
+			PanRight = 1.0;
+		}
+
+		Norm = (float)Val / 254.0;
+
+		if(positive_gain) {
+			// -55dB to +12dB
+			dBL = 67.0 * (Norm - 1.0) + 12.0;
+			dBR = 67.0 * (Norm - 1.0) + 12.0;
+		} else {
+			// -55dB to 0dB
+			dBL = 55.0 * (Norm - 1.0);
+			dBR = 55.0 * (Norm - 1.0);
+		}
+
+		// 16384 == 0dB (unity) gain
+		ScaledL = 16384.0 * pow(10.0, dBL / 20.0) * PanLeft;
+		ScaledR = 16384.0 * pow(10.0, dBR / 20.0) * PanRight;
+
+		// Make sure the sound stops completely when the slider is at the bottom
+		if(ScaledL < 30) {
+			ScaledL = 0;
+		}
+		if(ScaledR < 30) {
+			ScaledR = 0;
+		}
+
+		// Prevent overflow of the 16-bit gain value
+		if(positive_gain) {
+			if(ScaledL > 65535) {
+				ScaledL = 65535;
+			}
+			if(ScaledR > 65535) {
+				ScaledR = 65535;
+			}
+		} else {
+			if(ScaledL > 16384) {
+				ScaledL = 16384;
+			}
+			if(ScaledR > 16384) {
+				ScaledR = 16384;
+			}
+		}
+
+		LH = ScaledL >> 8;
+		LL = ScaledL;
+		RH = ScaledR >> 8;
+		RL = ScaledR;
 		if (2 < verbose)
-			printf("S=% 3i, P=% 3i, lP=% 3i, VL=%05i, VR=%05i\n", (int)Slider, (int)Pan, (int)lPan, ValL, ValR);
+			printf("Slider=% 3i, Pan=% 4i, Val=% 3i, dBL=% 6.2f, dBR=% 6.2f, ScaledL=%05i, ScaledR=%05i\n", (int)Slider, (int)Pan, Val, dBL, dBR, ScaledL, ScaledR);
 	}
 
 	void SetTo(unsigned char _Channel, int RawValue){
 		Slider = RawValue;
 		Channel = eFaderM == _Channel ? 4 : _Channel;
 		calculate();
 	}
 	void PanTo(int RawValue, bool Grob) {
-		int NewPan;
+		int NewPan = 0;
 		if (Grob) {
 			static int GrobVals[] = {-128, -64, 0, 64, 127};
 			int i = 4;
 			while (i >= 0 && GrobVals[i] > Pan)
@@ -134,10 +207,24 @@
 
 		} else {
 			NewPan = Pan + RawValue;
 		}
+		
+		// Since the step is now 13, make sure 0 can still be reached
+		if (abs(NewPan) < 7) {
+			NewPan = 0;
+		}
+
+		// If we went past the limit, but we weren't at the limit, clamp to the limit
+		if (NewPan < -128 && Pan > -128)
+			NewPan = -128;
+		else if (NewPan > 127 && Pan < 127)
+			NewPan = 127;
+
+		// If we were already at the limit, then clamping didn't occur above, so ignore the new value
 		if (NewPan < -128  ||  NewPan > 127)
 			return;
+
 		Pan = NewPan;
 		calculate();
 	}
 #endif
