Skip to contentSkip to conversation

emu10k1 multichannel PCM - still not quite working

PATCH In review Lee Revell 1 participant 2 messages Oct 8, 2004 – Oct 23, 2004

lore
Share & tools
lore

Lee Revell

author
#0 lore raw

PATCH 1/1 emu10k1 multichannel PCM - still not quite working

Here is my first attempt at adding multichannel PCM playback support to the emu10k1. It does not quite seem to work and I am not sure why. This has 16 non-interleaved channels. Since the playback interrupt is driven by the capture, we call snd_pcm_period_elapsed in the efx capture interrupt handler. Since playback and capture are synced, first 16 channels must be enabled for recording using the EFX voices mask control.

I start JACK like so:

jackd -v -R -d alsa -C hw:0,2 -P hw:0,3 -S -p 32

and it correctly opens 16 playback and 16 capture channels. However, the pointer callback and the interrupt handler only get called once; the jackd watchdog thread kills jackd eventually. Looks like only the first period is handled, then it hangs. I added some printks:

Oct  6 14:54:23 krustophenia kernel: in snd_emu10k1_efx_playback_prepare - start_addr 0x1000 end_addr 0x1800
Oct  6 14:54:23 krustophenia kernel: init voice - master 1 extra 0 start_addr 0x800 end_addr 0x840
Oct  6 14:54:23 krustophenia kernel: init voice - master 0 extra 0 start_addr 0x840 end_addr 0x880
Oct  6 14:54:23 krustophenia kernel: init voice - master 0 extra 0 start_addr 0x880 end_addr 0x8c0
Oct  6 14:54:23 krustophenia kernel: init voice - master 0 extra 0 start_addr 0x8c0 end_addr 0x900
Oct  6 14:54:23 krustophenia kernel: init voice - master 0 extra 0 start_addr 0x900 end_addr 0x940
Oct  6 14:54:23 krustophenia kernel: init voice - master 0 extra 0 start_addr 0x940 end_addr 0x980
Oct  6 14:54:23 krustophenia kernel: init voice - master 0 extra 0 start_addr 0x980 end_addr 0x9c0
Oct  6 14:54:23 krustophenia kernel: init voice - master 0 extra 0 start_addr 0x9c0 end_addr 0xa00
Oct  6 14:54:23 krustophenia kernel: init voice - master 0 extra 0 start_addr 0xa00 end_addr 0xa40
Oct  6 14:54:23 krustophenia kernel: init voice - master 0 extra 0 start_addr 0xa40 end_addr 0xa80
Oct  6 14:54:23 krustophenia kernel: init voice - master 0 extra 0 start_addr 0xa80 end_addr 0xac0
Oct  6 14:54:23 krustophenia kernel: init voice - master 0 extra 0 start_addr 0xac0 end_addr 0xb00
Oct  6 14:54:23 krustophenia kernel: init voice - master 0 extra 0 start_addr 0xb00 end_addr 0xb40
Oct  6 14:54:23 krustophenia kernel: init voice - master 0 extra 0 start_addr 0xb40 end_addr 0xb80
Oct  6 14:54:23 krustophenia kernel: init voice - master 0 extra 0 start_addr 0xb80 end_addr 0xbc0
Oct  6 14:54:23 krustophenia kernel: init voice - master 0 extra 0 start_addr 0xbc0 end_addr 0xc00
Oct  6 14:54:23 krustophenia kernel: ptr = 0x83c, ccca_start_addr = 0x83c
Oct  6 14:54:23 krustophenia kernel: ptr = 0x0, buffer_size = 0x40, period_size = 0x20
Oct  6 14:54:23 krustophenia kernel: in capture efx irq handler, updating playback stream

The first "ptr" is the one read from the hardware in the pointer callback, the second is the pointer we return.

The patch is attached. It is definitely incomplete but AFAICS it should work at this point.

Lee

Index: alsa-kernel/include/emu10k1.h
===================================================================
RCS file: /cvsroot/alsa/alsa-kernel/include/emu10k1.h,v retrieving revision 1.46

include/emu10k1.h +6 -1
diff -u -r1.46 emu10k1.h
@@ -781,6 +781,7 @@
 typedef struct _snd_emu10k1_pcm emu10k1_pcm_t;
 
 typedef enum {
+	EMU10K1_EFX,
 	EMU10K1_PCM,
 	EMU10K1_SYNTH,
 	EMU10K1_MIDI
@@ -792,6 +793,7 @@
 	int use: 1,
 	    pcm: 1,
 	    synth: 1,
+	    efx: 1,
 	    midi: 1;
 	void (*interrupt)(emu10k1_t *emu, emu10k1_voice_t *pvoice);
 
@@ -800,6 +802,7 @@
 
 typedef enum {
 	PLAYBACK_EMUVOICE,
+	PLAYBACK_EFX,
 	CAPTURE_AC97ADC,
 	CAPTURE_AC97MIC,
 	CAPTURE_EFX
@@ -809,7 +812,7 @@
 	emu10k1_t *emu;
 	snd_emu10k1_pcm_type_t type;
 	snd_pcm_substream_t *substream;
-	emu10k1_voice_t *voices[2];
+	emu10k1_voice_t *voices[16];
 	emu10k1_voice_t *extra;
 	unsigned short running;
 	unsigned short first_ptr;
@@ -999,6 +1002,7 @@
 	snd_pcm_substream_t *pcm_capture_substream;
 	snd_pcm_substream_t *pcm_capture_mic_substream;
 	snd_pcm_substream_t *pcm_capture_efx_substream;
+	snd_pcm_substream_t *pcm_playback_efx_substream;
 
 	emu10k1_midi_t midi;
 	emu10k1_midi_t midi2; /* for audigy */
@@ -1017,6 +1021,7 @@
 int snd_emu10k1_pcm(emu10k1_t * emu, int device, snd_pcm_t ** rpcm);
 int snd_emu10k1_pcm_mic(emu10k1_t * emu, int device, snd_pcm_t ** rpcm);
 int snd_emu10k1_pcm_efx(emu10k1_t * emu, int device, snd_pcm_t ** rpcm);
+int snd_emu10k1_pcm_multi(emu10k1_t * emu, int device, snd_pcm_t ** rpcm);
 int snd_emu10k1_fx8010_pcm(emu10k1_t * emu, int device, snd_pcm_t ** rpcm);
 int snd_emu10k1_mixer(emu10k1_t * emu);
 int snd_emu10k1_fx8010_new(emu10k1_t *emu, int device, snd_hwdep_t ** rhwdep);

Index: alsa-kernel/pci/emu10k1/emu10k1.c
===================================================================
RCS file: /cvsroot/alsa/alsa-kernel/pci/emu10k1/emu10k1.c,v retrieving revision 1.27

pci/emu10k1/emu10k1.c +4 -0
diff -u -r1.27 emu10k1.c
@@ -121,6 +121,10 @@
 		snd_card_free(card);
 		return err;
 	}		
+	if ((err = snd_emu10k1_pcm_multi(emu, 3, NULL)) < 0) {
+		snd_card_free(card);
+		return err;
+	}		
 	if ((err = snd_emu10k1_mixer(emu)) < 0) {
 		snd_card_free(card);
 		return err;

Index: alsa-kernel/pci/emu10k1/emupcm.c
===================================================================
RCS file: /cvsroot/alsa/alsa-kernel/pci/emu10k1/emupcm.c,v retrieving revision 1.32

pci/emu10k1/emupcm.c +243 -7

355 lines not on this page · raw message

Index: alsa-kernel/pci/emu10k1/voice.c
===================================================================
RCS file: /cvsroot/alsa/alsa-kernel/pci/emu10k1/voice.c,v retrieving revision 1.5

pci/emu10k1/voice.c +24 -9
diff -u -r1.5 voice.c
@@ -30,13 +30,26 @@
 #include <sound/core.h>
 #include <sound/emu10k1.h>
 
-static int voice_alloc(emu10k1_t *emu, emu10k1_voice_type_t type, int pair, emu10k1_voice_t **rvoice)
+static int voice_alloc(emu10k1_t *emu, emu10k1_voice_type_t type, int number, emu10k1_voice_t **rvoice)
 {
 	emu10k1_voice_t *voice, *voice2;
-	int idx;
-
 	*rvoice = NULL;
-	for (idx = 0; idx < 64; idx += pair ? 2 : 1) {
+	int idx,pair;
+	pair = ( number == 2 ) ? 1 : 0;
+	if (type == EMU10K1_EFX) { /* multichannel pcm */
+	    for (idx = 0; idx < 16; idx++) {
+		voice = &emu->voices[idx];
+		printk("voice alloc - %i, %i of %i\n", voice->number, idx, number);
+		if (voice->use) /* error, nothing else should touch the 1st 16 voices */
+		    return -ENOMEM;
+		voice->use = 1;
+		voice->efx = 1;
+	    }
+	    *rvoice = &emu->voices[0];
+	    return 0;
+	}
+		
+	for (idx = 16; idx < 64; idx += pair ? 2 : 1) {
 		voice = &emu->voices[idx];
 		voice2 = pair ? &emu->voices[idx+1] : NULL;
 		if (voice->use || (voice2 && voice2->use))
@@ -56,8 +69,10 @@
 		case EMU10K1_MIDI:
 			voice->midi = 1;
 			break;
+		case EMU10K1_EFX:
+			break;
 		}
-		// printk("voice alloc - %i, pair = %i\n", voice->number, pair);
+		printk("voice alloc - %i, number = %i\n", voice->number, number);
 		*rvoice = voice;
 		return 0;
 	}
@@ -70,12 +85,12 @@
 	int result;
 
 	snd_assert(rvoice != NULL, return -EINVAL);
-	snd_assert(!pair || type == EMU10K1_PCM, return -EINVAL);
+	snd_assert(!pair || type == EMU10K1_PCM || type==EMU10K1_EFX, return -EINVAL);
 
 	spin_lock_irqsave(&emu->voice_lock, flags);
 	for (;;) {
 		result = voice_alloc(emu, type, pair, rvoice);
-		if (result == 0 || type != EMU10K1_PCM)
+		if (result == 0 || type == EMU10K1_SYNTH || type == EMU10K1_MIDI)
 			break;
 
 		/* free a voice from synth */
@@ -84,7 +99,7 @@
 			if (result >= 0) {
 				emu10k1_voice_t *pvoice = &emu->voices[result];
 				pvoice->interrupt = NULL;
-				pvoice->use = pvoice->pcm = pvoice->synth = pvoice->midi = 0;
+				pvoice->use = pvoice->pcm = pvoice->synth = pvoice->midi = pvoice->efx = 0;
 				pvoice->epcm = NULL;
 			}
 		}
@@ -103,7 +118,7 @@
 	snd_assert(pvoice != NULL, return -EINVAL);
 	spin_lock_irqsave(&emu->voice_lock, flags);
 	pvoice->interrupt = NULL;
-	pvoice->use = pvoice->pcm = pvoice->synth = pvoice->midi = 0;
+	pvoice->use = pvoice->pcm = pvoice->synth = pvoice->midi = pvoice->efx = 0;
 	pvoice->epcm = NULL;
 	snd_emu10k1_voice_init(emu, pvoice->number);
 	spin_unlock_irqrestore(&emu->voice_lock, flags);
author
#1 lore raw
Quoted 2 lines · Lee Revell

Here is my first attempt at adding multichannel PCM playback support to the emu10k1

...

Quoted 4 lines

it correctly opens 16 playback and 16 capture channels. However, the pointer callback and the interrupt handler only get called once; the jackd watchdog thread kills jackd eventually. Looks like only the first period is handled, then it hangs.

OK, I think I might have figured this out. It is important that the playback stream be started before the capture. If the capture stream is started first, the playback pointer will not have reached the period boundary by the time we call period_elapsed on the playback stream inside the capture interrupt handler. This will cause the ALSA middle layer to wake up the playback stream prematurely, before there is enough space in the buffer.

Also, I think I found a way to start and stop up to 32 playback voices in sync. Currently in the playback trigger callback we start the voices one at a time:

        case SNDRV_PCM_TRIGGER_START:
	[...]
        case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
                snd_emu10k1_playback_trigger_voice(emu, epcm->voices[0], 1, 0);
                snd_emu10k1_playback_trigger_voice(emu, epcm->voices[1], 0, 0);
                snd_emu10k1_playback_trigger_voice(emu, epcm->extra, 1, 1);
                epcm->running = 1;

We prepare the voice, apply the mixer settings in playback_trigger_voice:

        if (evoice == NULL)     /* skip second voice for mono */
                return;
        substream = evoice->epcm->substream;
        runtime = substream->runtime;
        mix = &emu->pcm_mixer[substream->number];
        voice = evoice->number;
        pitch = snd_emu10k1_rate_to_pitch(runtime->rate) >> 8;
        pitch_target = emu10k1_calc_pitch_target(runtime->rate);
        attn = extra ? 0 : 0x00ff;
        tmp = runtime->channels == 2 ? (master ? 1 : 2) : 0;
        snd_emu10k1_ptr_write(emu, IFATN, voice, attn);
        snd_emu10k1_ptr_write(emu, VTFT, voice, (mix->attn[tmp] << 16) | 0xffff);
        snd_emu10k1_ptr_write(emu, CVCF, voice, (mix->attn[tmp] << 16) | 0xffff);
        snd_emu10k1_voice_clear_loop_stop(emu, voice);
        if (extra)
                snd_emu10k1_voice_intr_enable(emu, voice);

Then snd_emu10k1_voice_clear_loop_stop() actually starts the DMA transfer:

        /* voice interrupt */
        if (voicenum >= 32) {
                outl(SOLEH << 16, emu->port + PTR);
                sol = inl(emu->port + DATA);
                sol &= ~(1 << (voicenum - 32));
        } else {
                outl(SOLEL << 16, emu->port + PTR);
                sol = inl(emu->port + DATA);
                sol &= ~(1 << voicenum);
        }
        outl(sol, emu->port + DATA);

SOLEL (Stop On Loop Enable Low) and SOLEH (Stop On Loop Enable High) are not per-channel registers, but two 32-bit masks that correspond to the 64 total voices. But the current code only flips one bit at a time. If we change snd_emu10k1_voice_clear_loop_stop to accept a 64 bit mask as its argument, for up to a 32 channel PCM we preparing the voices sequentially, then start them all in perfect sync with a single PCI write.

This change would also benefit the standard PCM playback, because we currently start the main voice(s) and the extra voice sequentially. So by the time the channel loop interrupt fires, the main voice is 1-2 samples ahead. By triggering them simultaneously, we have 20-40 usecs more headroom.

Patch forthcoming...

Lee

-------------------------------------------------------
This SF.net email is sponsored by: IT Product Guide on ITManagersJournal Use IT products in your business? Tell us what you think of them. Give us Your Opinions, Get Free ThinkGeek Gift Certificates! Click to find out more
http://productguide.itmanagersjournal.com/guidepromo.tmpl