ALSA: emu10k1: enable bit-exact playback, part 2: voice attenuation
authorOswald Buddenhagen <oswald.buddenhagen@gmx.de>
Sun, 14 May 2023 17:03:23 +0000 (19:03 +0200)
committerTakashi Iwai <tiwai@suse.de>
Tue, 16 May 2023 09:11:04 +0000 (11:11 +0200)
The voice volume is a raw fractional multiplier that can't actually
represent 1.0. To still enable real pass-through, we now set the volume
to 0.5 (which results in no loss of precision, as the FX bus provides
fractional values) and scale up the samples in DSP code.

To maintain backwards compatibility with existing configuration files,
we rescale the values in the mixer controls. The range is extended
upwards from 0xffff to 0x1fffd, which actually introduces the
possibility of specifying an amplification.

There is still a minor incompatibility with user space, namely if
someone loaded custom DSP code. They'll just get half the volume, so
this doesn't seem like a big deal.

Signed-off-by: Oswald Buddenhagen <oswald.buddenhagen@gmx.de>
Link: https://lore.kernel.org/r/20230514170323.3408834-8-oswald.buddenhagen@gmx.de
Signed-off-by: Takashi Iwai <tiwai@suse.de>
Documentation/sound/cards/audigy-mixer.rst
Documentation/sound/cards/sb-live-mixer.rst
include/sound/emu10k1.h
sound/pci/emu10k1/emufx.c
sound/pci/emu10k1/emumixer.c
sound/pci/emu10k1/emupcm.c

index aa17645..e02dd89 100644 (file)
@@ -227,7 +227,7 @@ PCM stream related controls
 
 name='EMU10K1 PCM Volume',index 0-31
 ------------------------------------
-Channel volume attenuation in range 0-0xffff. The maximum value (no
+Channel volume attenuation in range 0-0x1fffd. The middle value (no
 attenuation) is default. The channel mapping for three values is
 as follows:
 
index 8198866..4dd9bfe 100644 (file)
@@ -258,7 +258,7 @@ PCM stream related controls
 
 ``name='EMU10K1 PCM Volume',index 0-31``
 ----------------------------------------
-Channel volume attenuation in range 0-0xffff. The maximum value (no
+Channel volume attenuation in range 0-0x1fffd. The middle value (no
 attenuation) is default. The channel mapping for three values is
 as follows:
 
index 8e27f70..7bcb1a2 100644 (file)
@@ -415,6 +415,7 @@ SUB_REG(PTRX, PITCHTARGET,  0xffff0000)     /* Pitch target of specified channel                    */
 SUB_REG(PTRX, FXSENDAMOUNT_A,  0x0000ff00)     /* Linear level of channel output sent to FX send bus A */
 SUB_REG(PTRX, FXSENDAMOUNT_B,  0x000000ff)     /* Linear level of channel output sent to FX send bus B */
 
+// Note: the volumes are raw multpliers, so real 100% is impossible.
 #define CVCF                   0x02            /* Current volume and filter cutoff register            */
 SUB_REG(CVCF, CURRENTVOL,      0xffff0000)     /* Current linear volume of specified channel           */
 SUB_REG(CVCF, CURRENTFILTER,   0x0000ffff)     /* Current filter cutoff frequency of specified channel */
@@ -1477,6 +1478,8 @@ struct snd_emu10k1_pcm_mixer {
        /* mono, left, right x 8 sends (4 on emu10k1) */
        unsigned char send_routing[3][8];
        unsigned char send_volume[3][8];
+       // 0x8000 is neutral. The mixer code rescales it to 0xffff to maintain
+       // backwards compatibility with user space.
        unsigned short attn[3];
        struct snd_emu10k1_pcm *epcm;
 };
index 4c9d67e..f64b2b4 100644 (file)
@@ -1361,7 +1361,13 @@ static int _snd_emu10k1_audigy_init_efx(struct snd_emu10k1 *emu)
        A_OP(icode, &ptr, iMAC1, A_GPR(capture+1), A_GPR(capture+1), A_GPR(gpr+1), A_FXBUS(FXBUS_MIDI_RIGHT));
        snd_emu10k1_init_stereo_control(&controls[nctl++], "Synth Capture Volume", gpr, 0);
        gpr += 2;
-      
+
+       // We need to double the volume, as we configure the voices for half volume,
+       // which is necessary for bit-identical reproduction.
+       { static_assert(stereo_mix == playback + SND_EMU10K1_PLAYBACK_CHANNELS); }
+       for (z = 0; z < SND_EMU10K1_PLAYBACK_CHANNELS + 2; z++)
+               A_OP(icode, &ptr, iACC3, A_GPR(playback + z), A_GPR(playback + z), A_GPR(playback + z), A_C_00000000);
+
        /*
         * inputs
         */
@@ -1826,18 +1832,18 @@ static int _snd_emu10k1_init_efx(struct snd_emu10k1 *emu)
        /*
         *  Process FX Buses
         */
-       OP(icode, &ptr, iMACINT0, GPR(0), C_00000000, FXBUS(FXBUS_PCM_LEFT), C_00000004);
-       OP(icode, &ptr, iMACINT0, GPR(1), C_00000000, FXBUS(FXBUS_PCM_RIGHT), C_00000004);
-       OP(icode, &ptr, iMACINT0, GPR(2), C_00000000, FXBUS(FXBUS_MIDI_LEFT), C_00000004);
-       OP(icode, &ptr, iMACINT0, GPR(3), C_00000000, FXBUS(FXBUS_MIDI_RIGHT), C_00000004);
-       OP(icode, &ptr, iMACINT0, GPR(4), C_00000000, FXBUS(FXBUS_PCM_LEFT_REAR), C_00000004);
-       OP(icode, &ptr, iMACINT0, GPR(5), C_00000000, FXBUS(FXBUS_PCM_RIGHT_REAR), C_00000004);
-       OP(icode, &ptr, iMACINT0, GPR(6), C_00000000, FXBUS(FXBUS_PCM_CENTER), C_00000004);
-       OP(icode, &ptr, iMACINT0, GPR(7), C_00000000, FXBUS(FXBUS_PCM_LFE), C_00000004);
+       OP(icode, &ptr, iMACINT0, GPR(0), C_00000000, FXBUS(FXBUS_PCM_LEFT), C_00000008);
+       OP(icode, &ptr, iMACINT0, GPR(1), C_00000000, FXBUS(FXBUS_PCM_RIGHT), C_00000008);
+       OP(icode, &ptr, iMACINT0, GPR(2), C_00000000, FXBUS(FXBUS_MIDI_LEFT), C_00000008);
+       OP(icode, &ptr, iMACINT0, GPR(3), C_00000000, FXBUS(FXBUS_MIDI_RIGHT), C_00000008);
+       OP(icode, &ptr, iMACINT0, GPR(4), C_00000000, FXBUS(FXBUS_PCM_LEFT_REAR), C_00000008);
+       OP(icode, &ptr, iMACINT0, GPR(5), C_00000000, FXBUS(FXBUS_PCM_RIGHT_REAR), C_00000008);
+       OP(icode, &ptr, iMACINT0, GPR(6), C_00000000, FXBUS(FXBUS_PCM_CENTER), C_00000008);
+       OP(icode, &ptr, iMACINT0, GPR(7), C_00000000, FXBUS(FXBUS_PCM_LFE), C_00000008);
        OP(icode, &ptr, iMACINT0, GPR(8), C_00000000, C_00000000, C_00000000);  /* S/PDIF left */
        OP(icode, &ptr, iMACINT0, GPR(9), C_00000000, C_00000000, C_00000000);  /* S/PDIF right */
-       OP(icode, &ptr, iMACINT0, GPR(10), C_00000000, FXBUS(FXBUS_PCM_LEFT_FRONT), C_00000004);
-       OP(icode, &ptr, iMACINT0, GPR(11), C_00000000, FXBUS(FXBUS_PCM_RIGHT_FRONT), C_00000004);
+       OP(icode, &ptr, iMACINT0, GPR(10), C_00000000, FXBUS(FXBUS_PCM_LEFT_FRONT), C_00000008);
+       OP(icode, &ptr, iMACINT0, GPR(11), C_00000000, FXBUS(FXBUS_PCM_RIGHT_FRONT), C_00000008);
 
        /* Raw S/PDIF PCM */
        ipcm->substream = 0;
@@ -1931,7 +1937,7 @@ static int _snd_emu10k1_init_efx(struct snd_emu10k1 *emu)
        
        /* Wave Center/LFE Playback Volume */
        OP(icode, &ptr, iACC3, GPR(tmp + 0), FXBUS(FXBUS_PCM_LEFT), FXBUS(FXBUS_PCM_RIGHT), C_00000000);
-       OP(icode, &ptr, iMACINT0, GPR(tmp + 0), C_00000000, GPR(tmp + 0), C_00000002);
+       OP(icode, &ptr, iMACINT0, GPR(tmp + 0), C_00000000, GPR(tmp + 0), C_00000004);
        VOLUME(icode, &ptr, playback + 4, tmp + 0, gpr);
        snd_emu10k1_init_mono_control(controls + i++, "Wave Center Playback Volume", gpr++, 0);
        VOLUME(icode, &ptr, playback + 5, tmp + 0, gpr);
index 48f0d3f..9fa4bc8 100644 (file)
@@ -1352,7 +1352,7 @@ static int snd_emu10k1_attn_info(struct snd_kcontrol *kcontrol, struct snd_ctl_e
        uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
        uinfo->count = 3;
        uinfo->value.integer.min = 0;
-       uinfo->value.integer.max = 0xffff;
+       uinfo->value.integer.max = 0x1fffd;
        return 0;
 }
 
@@ -1365,7 +1365,7 @@ static int snd_emu10k1_attn_get(struct snd_kcontrol *kcontrol,
        int idx;
 
        for (idx = 0; idx < 3; idx++)
-               ucontrol->value.integer.value[idx] = mix->attn[idx];
+               ucontrol->value.integer.value[idx] = mix->attn[idx] * 0xffffU / 0x8000U;
        return 0;
 }
 
@@ -1380,7 +1380,8 @@ static int snd_emu10k1_attn_put(struct snd_kcontrol *kcontrol,
 
        spin_lock_irqsave(&emu->reg_lock, flags);
        for (idx = 0; idx < 3; idx++) {
-               val = ucontrol->value.integer.value[idx] & 0xffff;
+               unsigned uval = ucontrol->value.integer.value[idx] & 0x1ffff;
+               val = uval * 0x8000U / 0xffffU;
                if (mix->attn[idx] != val) {
                        mix->attn[idx] = val;
                        change = 1;
@@ -1547,7 +1548,7 @@ static int snd_emu10k1_efx_attn_info(struct snd_kcontrol *kcontrol, struct snd_c
        uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
        uinfo->count = 1;
        uinfo->value.integer.min = 0;
-       uinfo->value.integer.max = 0xffff;
+       uinfo->value.integer.max = 0x1fffd;
        return 0;
 }
 
@@ -1558,7 +1559,7 @@ static int snd_emu10k1_efx_attn_get(struct snd_kcontrol *kcontrol,
        struct snd_emu10k1_pcm_mixer *mix =
                &emu->efx_pcm_mixer[snd_ctl_get_ioffidx(kcontrol, &ucontrol->id)];
 
-       ucontrol->value.integer.value[0] = mix->attn[0];
+       ucontrol->value.integer.value[0] = mix->attn[0] * 0xffffU / 0x8000U;
        return 0;
 }
 
@@ -1570,9 +1571,11 @@ static int snd_emu10k1_efx_attn_put(struct snd_kcontrol *kcontrol,
        int ch = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
        struct snd_emu10k1_pcm_mixer *mix = &emu->efx_pcm_mixer[ch];
        int change = 0, val;
+       unsigned uval;
 
        spin_lock_irqsave(&emu->reg_lock, flags);
-       val = ucontrol->value.integer.value[0] & 0xffff;
+       uval = ucontrol->value.integer.value[0] & 0x1ffff;
+       val = uval * 0x8000U / 0xffffU;
        if (mix->attn[0] != val) {
                mix->attn[0] = val;
                change = 1;
index 5ed404e..6e6d310 100644 (file)
@@ -1049,7 +1049,7 @@ static int snd_emu10k1_efx_playback_open(struct snd_pcm_substream *substream)
                mix->send_routing[0][0] = i;
                memset(&mix->send_volume, 0, sizeof(mix->send_volume));
                mix->send_volume[0][0] = 255;
-               mix->attn[0] = 0xffff;
+               mix->attn[0] = 0x8000;
                mix->epcm = epcm;
                snd_emu10k1_pcm_efx_mixer_notify(emu, i, 1);
        }
@@ -1098,7 +1098,7 @@ static int snd_emu10k1_playback_open(struct snd_pcm_substream *substream)
        memset(&mix->send_volume, 0, sizeof(mix->send_volume));
        mix->send_volume[0][0] = mix->send_volume[0][1] =
        mix->send_volume[1][0] = mix->send_volume[2][1] = 255;
-       mix->attn[0] = mix->attn[1] = mix->attn[2] = 0xffff;
+       mix->attn[0] = mix->attn[1] = mix->attn[2] = 0x8000;
        mix->epcm = epcm;
        snd_emu10k1_pcm_mixer_notify(emu, substream->number, 1);
        return 0;