alsa-devel.alsa-project.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 1/1] ALSA: usb-audio: add support for Akai MPD16
@ 2010-05-17 22:48 wdev
  2010-05-18  0:38 ` Daniel Mack
  0 siblings, 1 reply; 13+ messages in thread
From: wdev @ 2010-05-17 22:48 UTC (permalink / raw)
  To: patch; +Cc: alsa-devel, Krzysztof Foltman

From: Krzysztof Foltman <wdev@foltman.com>

The decoding routine is based on own reverse-engineering. It seems to
recognize all the messages that MPD16 sends during normal operation
(that is, via standard MIDI messages).

Configuration (changing pad sensitivity, slider controller and MIDI
notes) is not supported in this version.

Signed-off-by: Krzysztof Foltman <wdev@foltman.com>

diff --git a/sound/usb/midi.c b/sound/usb/midi.c
index 2c1558c..57d642a 100644
--- a/sound/usb/midi.c
+++ b/sound/usb/midi.c
@@ -645,6 +645,34 @@ static struct usb_protocol_ops snd_usbmidi_cme_ops = {
 };
 
 /*
+ * AKAI MPD16 protocol: one or more chunks consisting of first byte of 
+ * (0x20 | msg_len) and then a MIDI message (msg_len bytes long)
+ * 
+ * Messages sent:
+ * 21 FE (active sense)
+ * 23 90 xx xx (note on)
+ * 23 Ax xx xx (polyphonic pressure)
+ * 23 Bx xx xx (control change)
+ */
+static void snd_usbmidi_akai_input(struct snd_usb_midi_in_endpoint *ep,
+				   uint8_t *buffer, int buffer_length)
+{
+	unsigned int pos = 0;
+	while (pos < (unsigned)buffer_length && (buffer[pos] & 0xF8) == 0x20)
+	{
+		int msg_len = buffer[pos] & 0x0f;
+		snd_usbmidi_input_data(ep, 0, &buffer[pos + 1], msg_len);
+		pos += 1 + msg_len;
+	}
+}
+
+static struct usb_protocol_ops snd_usbmidi_akai_ops = {
+	.input = snd_usbmidi_akai_input,
+	.output = snd_usbmidi_standard_output,
+	.output_packet = snd_usbmidi_output_standard_packet,
+};
+
+/*
  * Novation USB MIDI protocol: number of data bytes is in the first byte
  * (when receiving) (+1!) or in the second byte (when sending); data begins
  * at the third byte.
@@ -1434,6 +1462,8 @@ static struct port_info {
 	EXTERNAL_PORT(0x086a, 0x0001, 8, "%s Broadcast"),
 	EXTERNAL_PORT(0x086a, 0x0002, 8, "%s Broadcast"),
 	EXTERNAL_PORT(0x086a, 0x0003, 4, "%s Broadcast"),
+	/* Akai MPD16 */
+	EXTERNAL_PORT(0x09e8, 0x0062, 0, "%s MIDI"),
 	/* Access Music Virus TI */
 	EXTERNAL_PORT(0x133e, 0x0815, 0, "%s MIDI"),
 	PORT_INFO(0x133e, 0x0815, 1, "%s Synth", 0,
@@ -2035,6 +2065,13 @@ int snd_usbmidi_create(struct snd_card *card,
 		umidi->usb_protocol_ops = &snd_usbmidi_cme_ops;
 		err = snd_usbmidi_detect_per_port_endpoints(umidi, endpoints);
 		break;
+	case QUIRK_MIDI_AKAI:
+		umidi->usb_protocol_ops = &snd_usbmidi_akai_ops;
+		err = snd_usbmidi_detect_per_port_endpoints(umidi, endpoints);
+		/* endpoint 0 does not carry MIDI data */
+		endpoints[0].out_cables = 0;
+		endpoints[0].in_cables = 0;
+		break;
 	default:
 		snd_printd(KERN_ERR "invalid quirk type %d\n", quirk->type);
 		err = -ENXIO;
diff --git a/sound/usb/midi.h b/sound/usb/midi.h
index 2089ec9..2fca80b 100644
--- a/sound/usb/midi.h
+++ b/sound/usb/midi.h
@@ -37,6 +37,8 @@ struct snd_usb_midi_endpoint_info {
 
 /* for QUIRK_MIDI_CME, data is NULL */
 
+/* for QUIRK_MIDI_AKAI, data is NULL */
+
 int snd_usbmidi_create(struct snd_card *card,
 		       struct usb_interface *iface,
 		       struct list_head *midi_list,
diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h
index 91ddef3..f8797f6 100644
--- a/sound/usb/quirks-table.h
+++ b/sound/usb/quirks-table.h
@@ -1973,6 +1973,17 @@ YAMAHA_DEVICE(0x7010, "UB99"),
 	}
 },
 
+/* AKAI devices */
+{
+	USB_DEVICE(0x09e8, 0x0062),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "AKAI",
+		.product_name = "MPD16",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_AKAI,
+	}
+},
+
 /* TerraTec devices */
 {
 	USB_DEVICE_VENDOR_SPEC(0x0ccd, 0x0012),
diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c
index 136e5b4..b45e54c 100644
--- a/sound/usb/quirks.c
+++ b/sound/usb/quirks.c
@@ -289,6 +289,7 @@ int snd_usb_create_quirk(struct snd_usb_audio *chip,
 		[QUIRK_MIDI_FASTLANE] = create_any_midi_quirk,
 		[QUIRK_MIDI_EMAGIC] = create_any_midi_quirk,
 		[QUIRK_MIDI_CME] = create_any_midi_quirk,
+		[QUIRK_MIDI_AKAI] = create_any_midi_quirk,
 		[QUIRK_AUDIO_STANDARD_INTERFACE] = create_standard_audio_quirk,
 		[QUIRK_AUDIO_FIXED_ENDPOINT] = create_fixed_stream_quirk,
 		[QUIRK_AUDIO_EDIROL_UAXX] = create_uaxx_quirk,
diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h
index d679e72..06ebf24 100644
--- a/sound/usb/usbaudio.h
+++ b/sound/usb/usbaudio.h
@@ -74,6 +74,7 @@ enum quirk_type {
 	QUIRK_MIDI_FASTLANE,
 	QUIRK_MIDI_EMAGIC,
 	QUIRK_MIDI_CME,
+	QUIRK_MIDI_AKAI,
 	QUIRK_MIDI_US122L,
 	QUIRK_AUDIO_STANDARD_INTERFACE,
 	QUIRK_AUDIO_FIXED_ENDPOINT,
-- 
1.7.0.4

^ permalink raw reply related	[flat|nested] 13+ messages in thread

* Re: [PATCH 1/1] ALSA: usb-audio: add support for Akai MPD16
  2010-05-17 22:48 [PATCH 1/1] ALSA: usb-audio: add support for Akai MPD16 wdev
@ 2010-05-18  0:38 ` Daniel Mack
  2010-05-18 18:36   ` Krzysztof Foltman
  0 siblings, 1 reply; 13+ messages in thread
From: Daniel Mack @ 2010-05-18  0:38 UTC (permalink / raw)
  To: wdev; +Cc: alsa-devel, patch

On Mon, May 17, 2010 at 11:48:15PM +0100, wdev@foltman.com wrote:
> The decoding routine is based on own reverse-engineering. It seems to
> recognize all the messages that MPD16 sends during normal operation
> (that is, via standard MIDI messages).
> 
> Configuration (changing pad sensitivity, slider controller and MIDI
> notes) is not supported in this version.
> 
> Signed-off-by: Krzysztof Foltman <wdev@foltman.com>
> 
> diff --git a/sound/usb/midi.c b/sound/usb/midi.c
> index 2c1558c..57d642a 100644
> --- a/sound/usb/midi.c
> +++ b/sound/usb/midi.c
> @@ -645,6 +645,34 @@ static struct usb_protocol_ops snd_usbmidi_cme_ops = {
>  };
>  
>  /*
> + * AKAI MPD16 protocol: one or more chunks consisting of first byte of 
> + * (0x20 | msg_len) and then a MIDI message (msg_len bytes long)
> + * 
> + * Messages sent:
> + * 21 FE (active sense)
> + * 23 90 xx xx (note on)
> + * 23 Ax xx xx (polyphonic pressure)
> + * 23 Bx xx xx (control change)
> + */
> +static void snd_usbmidi_akai_input(struct snd_usb_midi_in_endpoint *ep,
> +				   uint8_t *buffer, int buffer_length)
> +{
> +	unsigned int pos = 0;
> +	while (pos < (unsigned)buffer_length && (buffer[pos] & 0xF8) == 0x20)
> +	{
> +		int msg_len = buffer[pos] & 0x0f;
> +		snd_usbmidi_input_data(ep, 0, &buffer[pos + 1], msg_len);
> +		pos += 1 + msg_len;
> +	}
> +}

Just a minor coding style flaw here for the curly brackets. See
Documentation/CodingStyle or let scripts/checkpatch.pl annoy you :)

FWIW, I think the block above could also be a for-loop, but that's
certainly a matter of taste.

Thanks,
Daniel

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH 1/1] ALSA: usb-audio: add support for Akai MPD16
  2010-05-18  0:38 ` Daniel Mack
@ 2010-05-18 18:36   ` Krzysztof Foltman
  0 siblings, 0 replies; 13+ messages in thread
From: Krzysztof Foltman @ 2010-05-18 18:36 UTC (permalink / raw)
  To: alsa-devel

On 05/18/2010 01:38 AM, Daniel Mack wrote:

>  Just a minor coding style flaw here for the curly brackets. See
>  Documentation/CodingStyle or let scripts/checkpatch.pl annoy you :)

Fixed, I'll resend it ASAP. The awkward part is that checkpatch.pl also 
complains about the use of space after the ampersand in quirks-table.h, 
where it seems to be a common practice. The CodingStyle document does 
mention unary &, but it doesn't mention this sort of initialization syntax.

>  FWIW, I think the block above could also be a for-loop, but that's
>  certainly a matter of taste.

I wouldn't use a for statement with this longish condition - it would no 
more readable than separate init/condition/increment with a while loop.

K.

^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH 1/1] ALSA: usb-audio: add support for Akai MPD16
@ 2010-05-18 18:37 Krzysztof Foltman
  2010-05-19  6:59 ` Clemens Ladisch
  0 siblings, 1 reply; 13+ messages in thread
From: Krzysztof Foltman @ 2010-05-18 18:37 UTC (permalink / raw)
  To: patch; +Cc: alsa-devel, Krzysztof Foltman

The decoding routine is based on own reverse-engineering. It seems to
recognize all the messages that MPD16 sends during normal operation
(that is, via standard MIDI messages).

Configuration (changing pad sensitivity, slider controller and MIDI
notes) is not supported in this version.

Signed-off-by: Krzysztof Foltman <wdev@foltman.com>

diff --git a/sound/usb/midi.c b/sound/usb/midi.c
index 2c1558c..cce5dbe 100644
--- a/sound/usb/midi.c
+++ b/sound/usb/midi.c
@@ -645,6 +645,33 @@ static struct usb_protocol_ops snd_usbmidi_cme_ops = {
 };
 
 /*
+ * AKAI MPD16 protocol: one or more chunks consisting of first byte of
+ * (0x20 | msg_len) and then a MIDI message (msg_len bytes long)
+ *
+ * Messages sent:
+ * 21 FE (active sense)
+ * 23 90 xx xx (note on)
+ * 23 Ax xx xx (polyphonic pressure)
+ * 23 Bx xx xx (control change)
+ */
+static void snd_usbmidi_akai_input(struct snd_usb_midi_in_endpoint *ep,
+				   uint8_t *buffer, int buffer_length)
+{
+	unsigned int pos = 0;
+	while (pos < (unsigned)buffer_length && (buffer[pos] & 0xF8) == 0x20) {
+		int msg_len = buffer[pos] & 0x0f;
+		snd_usbmidi_input_data(ep, 0, &buffer[pos + 1], msg_len);
+		pos += 1 + msg_len;
+	}
+}
+
+static struct usb_protocol_ops snd_usbmidi_akai_ops = {
+	.input = snd_usbmidi_akai_input,
+	.output = snd_usbmidi_standard_output,
+	.output_packet = snd_usbmidi_output_standard_packet,
+};
+
+/*
  * Novation USB MIDI protocol: number of data bytes is in the first byte
  * (when receiving) (+1!) or in the second byte (when sending); data begins
  * at the third byte.
@@ -1434,6 +1461,8 @@ static struct port_info {
 	EXTERNAL_PORT(0x086a, 0x0001, 8, "%s Broadcast"),
 	EXTERNAL_PORT(0x086a, 0x0002, 8, "%s Broadcast"),
 	EXTERNAL_PORT(0x086a, 0x0003, 4, "%s Broadcast"),
+	/* Akai MPD16 */
+	EXTERNAL_PORT(0x09e8, 0x0062, 0, "%s MIDI"),
 	/* Access Music Virus TI */
 	EXTERNAL_PORT(0x133e, 0x0815, 0, "%s MIDI"),
 	PORT_INFO(0x133e, 0x0815, 1, "%s Synth", 0,
@@ -2035,6 +2064,13 @@ int snd_usbmidi_create(struct snd_card *card,
 		umidi->usb_protocol_ops = &snd_usbmidi_cme_ops;
 		err = snd_usbmidi_detect_per_port_endpoints(umidi, endpoints);
 		break;
+	case QUIRK_MIDI_AKAI:
+		umidi->usb_protocol_ops = &snd_usbmidi_akai_ops;
+		err = snd_usbmidi_detect_per_port_endpoints(umidi, endpoints);
+		/* endpoint 0 does not carry MIDI data */
+		endpoints[0].out_cables = 0;
+		endpoints[0].in_cables = 0;
+		break;
 	default:
 		snd_printd(KERN_ERR "invalid quirk type %d\n", quirk->type);
 		err = -ENXIO;
diff --git a/sound/usb/midi.h b/sound/usb/midi.h
index 2089ec9..2fca80b 100644
--- a/sound/usb/midi.h
+++ b/sound/usb/midi.h
@@ -37,6 +37,8 @@ struct snd_usb_midi_endpoint_info {
 
 /* for QUIRK_MIDI_CME, data is NULL */
 
+/* for QUIRK_MIDI_AKAI, data is NULL */
+
 int snd_usbmidi_create(struct snd_card *card,
 		       struct usb_interface *iface,
 		       struct list_head *midi_list,
diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h
index 91ddef3..7ecad05 100644
--- a/sound/usb/quirks-table.h
+++ b/sound/usb/quirks-table.h
@@ -1973,6 +1973,17 @@ YAMAHA_DEVICE(0x7010, "UB99"),
 	}
 },
 
+/* AKAI devices */
+{
+	USB_DEVICE(0x09e8, 0x0062),
+	.driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) {
+		.vendor_name = "AKAI",
+		.product_name = "MPD16",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_AKAI,
+	}
+},
+
 /* TerraTec devices */
 {
 	USB_DEVICE_VENDOR_SPEC(0x0ccd, 0x0012),
diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c
index 136e5b4..b45e54c 100644
--- a/sound/usb/quirks.c
+++ b/sound/usb/quirks.c
@@ -289,6 +289,7 @@ int snd_usb_create_quirk(struct snd_usb_audio *chip,
 		[QUIRK_MIDI_FASTLANE] = create_any_midi_quirk,
 		[QUIRK_MIDI_EMAGIC] = create_any_midi_quirk,
 		[QUIRK_MIDI_CME] = create_any_midi_quirk,
+		[QUIRK_MIDI_AKAI] = create_any_midi_quirk,
 		[QUIRK_AUDIO_STANDARD_INTERFACE] = create_standard_audio_quirk,
 		[QUIRK_AUDIO_FIXED_ENDPOINT] = create_fixed_stream_quirk,
 		[QUIRK_AUDIO_EDIROL_UAXX] = create_uaxx_quirk,
diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h
index d679e72..06ebf24 100644
--- a/sound/usb/usbaudio.h
+++ b/sound/usb/usbaudio.h
@@ -74,6 +74,7 @@ enum quirk_type {
 	QUIRK_MIDI_FASTLANE,
 	QUIRK_MIDI_EMAGIC,
 	QUIRK_MIDI_CME,
+	QUIRK_MIDI_AKAI,
 	QUIRK_MIDI_US122L,
 	QUIRK_AUDIO_STANDARD_INTERFACE,
 	QUIRK_AUDIO_FIXED_ENDPOINT,
-- 
1.7.0.4

^ permalink raw reply related	[flat|nested] 13+ messages in thread

* Re: [PATCH 1/1] ALSA: usb-audio: add support for Akai MPD16
  2010-05-18 18:37 Krzysztof Foltman
@ 2010-05-19  6:59 ` Clemens Ladisch
  2010-05-19 20:31   ` Krzysztof Foltman
  0 siblings, 1 reply; 13+ messages in thread
From: Clemens Ladisch @ 2010-05-19  6:59 UTC (permalink / raw)
  To: Krzysztof Foltman; +Cc: alsa-devel

Krzysztof Foltman wrote:
> + * AKAI MPD16 protocol: one or more chunks consisting of first byte of
> + * (0x20 | msg_len) and then a MIDI message (msg_len bytes long)
> + *
> + * Messages sent:
> + * 21 FE (active sense)
> + * 23 90 xx xx (note on)
> + * 23 Ax xx xx (polyphonic pressure)
> + * 23 Bx xx xx (control change)
> + */
> +static void snd_usbmidi_akai_input(struct snd_usb_midi_in_endpoint *ep,
> +				   uint8_t *buffer, int buffer_length)
> +{
> +	unsigned int pos = 0;
> +	while (pos < (unsigned)buffer_length && (buffer[pos] & 0xF8) == 0x20) {
> +		int msg_len = buffer[pos] & 0x0f;
> +		snd_usbmidi_input_data(ep, 0, &buffer[pos + 1], msg_len);

This might overflow if the buffer ends with a 2x byte.

> +static struct usb_protocol_ops snd_usbmidi_akai_ops = {
> +	.input = snd_usbmidi_akai_input,
> +	.output = snd_usbmidi_standard_output,
> +	.output_packet = snd_usbmidi_output_standard_packet,

Assuming that this device doesn't have any output ports, please add a
comment that this isn't the actual output protocol.

> checkpatch.pl also complains about the use of space after the
> ampersand in quirks-table.h, where it seems to be a common practice.

I'd value consistency over some stupid script, but this issue isn't
important enough to worry over.


Overall, the patch looks fine.


Regards,
Clemens

^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH 1/1] ALSA: usb-audio: add support for Akai MPD16
@ 2010-05-19 20:06 Krzysztof Foltman
  2010-05-20  7:26 ` Clemens Ladisch
  0 siblings, 1 reply; 13+ messages in thread
From: Krzysztof Foltman @ 2010-05-19 20:06 UTC (permalink / raw)
  To: patch; +Cc: alsa-devel, Krzysztof Foltman

The decoding/encoding is based on own reverse-engineering. Both control and
data ports are handled. Writing to control port supports SysEx events only,
as this is the only type of messages that MPD16 recognizes.

Signed-off-by: Krzysztof Foltman <wdev@foltman.com>

diff --git a/sound/usb/midi.c b/sound/usb/midi.c
index 2c1558c..d669feb 100644
--- a/sound/usb/midi.c
+++ b/sound/usb/midi.c
@@ -434,7 +434,7 @@ static void snd_usbmidi_maudio_broken_running_status_input(
 			u8 cin = buffer[i] & 0x0f;
 			struct usbmidi_in_port *port = &ep->ports[cable];
 			int length;
-			
+
 			length = snd_usbmidi_cin_length[cin];
 			if (cin == 0xf && buffer[i + 1] >= 0xf8)
 				; /* realtime msg: no running status change */
@@ -628,13 +628,13 @@ static struct usb_protocol_ops snd_usbmidi_standard_ops = {
 
 static struct usb_protocol_ops snd_usbmidi_midiman_ops = {
 	.input = snd_usbmidi_midiman_input,
-	.output = snd_usbmidi_standard_output, 
+	.output = snd_usbmidi_standard_output,
 	.output_packet = snd_usbmidi_output_midiman_packet,
 };
 
 static struct usb_protocol_ops snd_usbmidi_maudio_broken_running_status_ops = {
 	.input = snd_usbmidi_maudio_broken_running_status_input,
-	.output = snd_usbmidi_standard_output, 
+	.output = snd_usbmidi_standard_output,
 	.output_packet = snd_usbmidi_output_standard_packet,
 };
 
@@ -645,6 +645,110 @@ static struct usb_protocol_ops snd_usbmidi_cme_ops = {
 };
 
 /*
+ * AKAI MPD16 protocol:
+ *
+ * For control port (endpoint 1):
+ * ==============================
+ * One or more chunks consisting of first byte of (0x10 | msg_len) and then a
+ * SysEx message (msg_len=9 bytes long):
+ *
+ *     F0 47 62 60 aa bb cc xx F7 (xx = (aa + bb + cc) & 0x7F)
+ *
+ * aa bb cc
+ * --------
+ * 10 01 7E  edit mode on?
+ * 10 01 00  edit mode off?
+ * 10 04 7F  select bank 1
+ * 10 04 00  select bank 0
+ * 11 pp nn  set pad pp to send note nn
+ * 12 pp ss  set sensitivity on pad pp to ss
+ * 13 00 cc  set slider CC number to cc
+ * 14 00 cc  set MIDI channel to cc
+ * 15 01 pp  request note number for pad pp (returns 20 pp nn)
+ * 15 02 pp  request sensitivity for pad pp (returns 21 pp ss)
+ * 15 03 00  request slider CC number (returns 22 00 cc)
+ * 15 05 00  request MIDI channel number (returns 23 00 cc)
+ *
+ * For data port (endpoint 2):
+ * ===========================
+ * One or more chunks consisting of first byte of (0x20 | msg_len) and then a
+ * MIDI message (msg_len bytes long)
+ *
+ * Messages sent:
+ * 21 FE (active sense)
+ * 23 90 xx xx (note on)
+ * 23 Ax xx xx (polyphonic pressure)
+ * 23 Bx xx xx (control change)
+ */
+static void snd_usbmidi_akai_input(struct snd_usb_midi_in_endpoint *ep,
+				   uint8_t *buffer, int buffer_length)
+{
+	unsigned int pos = 0;
+	unsigned int len = (unsigned int)buffer_length;
+	while (pos < len) {
+		int port = (buffer[pos] >> 4) - 1;
+		unsigned int msg_len = buffer[pos] & 0x0f;
+		pos++;
+		if (pos + msg_len <= len && (port == 0 || port == 1))
+			snd_usbmidi_input_data(ep, 0, &buffer[pos], msg_len);
+		pos += msg_len;
+	}
+}
+
+static void snd_usbmidi_akai_output(struct snd_usb_midi_out_endpoint *ep,
+				    struct urb *urb)
+{
+	uint8_t *msg;
+	uint8_t *plen = &ep->ports[0].data[0]; /* length so far */
+	int count;
+	uint8_t tmp;
+
+	if (!ep->ports[0].active)
+		return;
+
+	msg = urb->transfer_buffer + urb->transfer_buffer_length;
+
+	/* only try adding more data when there's space for at least 1 SysEx */
+	while (urb->transfer_buffer_length + 16 < ep->max_transfer) {
+		count = snd_rawmidi_transmit(ep->ports[0].substream, &tmp, 1);
+		if (count < 1) {
+			ep->ports[0].active = 0;
+			return;
+		}
+		if (ep->ports[0].state == STATE_UNKNOWN) {
+			/* only accept SysEx'es, skip everything else */
+			if (tmp != 0xF0)
+				continue;
+			/* start accumulating SysEx data */
+			*plen = 0x00;
+			ep->ports[0].state = STATE_SYSEX_0;
+		}
+		if (*plen > 14) {
+			/* SysEx too long, drop it and start over */
+			ep->ports[0].state = STATE_UNKNOWN;
+			continue;
+		}
+		msg[++(*plen)] = tmp;
+		if (tmp == 0xF7) {
+			/* add total size (w/hdr) to length-to-transfer */
+			urb->transfer_buffer_length += 1 + *plen;
+			/* set port number nibble in the header */
+			msg[0] = 0x10 | *plen;
+			msg += 1 + *plen;
+
+			/* go back to waiting for SysEx */
+			ep->ports[0].state = STATE_UNKNOWN;
+			continue;
+		}
+	}
+}
+
+static struct usb_protocol_ops snd_usbmidi_akai_ops = {
+	.input = snd_usbmidi_akai_input,
+	.output = snd_usbmidi_akai_output,
+};
+
+/*
  * Novation USB MIDI protocol: number of data bytes is in the first byte
  * (when receiving) (+1!) or in the second byte (when sending); data begins
  * at the third byte.
@@ -1434,6 +1538,9 @@ static struct port_info {
 	EXTERNAL_PORT(0x086a, 0x0001, 8, "%s Broadcast"),
 	EXTERNAL_PORT(0x086a, 0x0002, 8, "%s Broadcast"),
 	EXTERNAL_PORT(0x086a, 0x0003, 4, "%s Broadcast"),
+	/* Akai MPD16 */
+	CONTROL_PORT(0x09e8, 0x0062, 0, "%s Control"),
+	EXTERNAL_PORT(0x09e8, 0x0062, 1, "%s MIDI"),
 	/* Access Music Virus TI */
 	EXTERNAL_PORT(0x133e, 0x0815, 0, "%s MIDI"),
 	PORT_INFO(0x133e, 0x0815, 1, "%s Synth", 0,
@@ -1707,7 +1814,7 @@ static int snd_usbmidi_detect_endpoints(struct snd_usb_midi* umidi,
 		snd_usbmidi_switch_roland_altsetting(umidi);
 
 	if (endpoint[0].out_ep || endpoint[0].in_ep)
-		return 0;	
+		return 0;
 
 	intf = umidi->iface;
 	if (!intf || intf->num_altsetting < 1)
@@ -1745,7 +1852,7 @@ static int snd_usbmidi_detect_per_port_endpoints(struct snd_usb_midi* umidi,
 						 struct snd_usb_midi_endpoint_info* endpoints)
 {
 	int err, i;
-	
+
 	err = snd_usbmidi_detect_endpoints(umidi, endpoints, MIDI_MAX_ENDPOINTS);
 	for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) {
 		if (endpoints[i].out_ep)
@@ -2035,6 +2142,12 @@ int snd_usbmidi_create(struct snd_card *card,
 		umidi->usb_protocol_ops = &snd_usbmidi_cme_ops;
 		err = snd_usbmidi_detect_per_port_endpoints(umidi, endpoints);
 		break;
+	case QUIRK_MIDI_AKAI:
+		umidi->usb_protocol_ops = &snd_usbmidi_akai_ops;
+		err = snd_usbmidi_detect_per_port_endpoints(umidi, endpoints);
+		/* endpoint 1 is input-only */
+		endpoints[1].out_cables = 0;
+		break;
 	default:
 		snd_printd(KERN_ERR "invalid quirk type %d\n", quirk->type);
 		err = -ENXIO;
diff --git a/sound/usb/midi.h b/sound/usb/midi.h
index 2089ec9..2fca80b 100644
--- a/sound/usb/midi.h
+++ b/sound/usb/midi.h
@@ -37,6 +37,8 @@ struct snd_usb_midi_endpoint_info {
 
 /* for QUIRK_MIDI_CME, data is NULL */
 
+/* for QUIRK_MIDI_AKAI, data is NULL */
+
 int snd_usbmidi_create(struct snd_card *card,
 		       struct usb_interface *iface,
 		       struct list_head *midi_list,
diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h
index 91ddef3..f8797f6 100644
--- a/sound/usb/quirks-table.h
+++ b/sound/usb/quirks-table.h
@@ -1973,6 +1973,17 @@ YAMAHA_DEVICE(0x7010, "UB99"),
 	}
 },
 
+/* AKAI devices */
+{
+	USB_DEVICE(0x09e8, 0x0062),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "AKAI",
+		.product_name = "MPD16",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_AKAI,
+	}
+},
+
 /* TerraTec devices */
 {
 	USB_DEVICE_VENDOR_SPEC(0x0ccd, 0x0012),
diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c
index 136e5b4..b45e54c 100644
--- a/sound/usb/quirks.c
+++ b/sound/usb/quirks.c
@@ -289,6 +289,7 @@ int snd_usb_create_quirk(struct snd_usb_audio *chip,
 		[QUIRK_MIDI_FASTLANE] = create_any_midi_quirk,
 		[QUIRK_MIDI_EMAGIC] = create_any_midi_quirk,
 		[QUIRK_MIDI_CME] = create_any_midi_quirk,
+		[QUIRK_MIDI_AKAI] = create_any_midi_quirk,
 		[QUIRK_AUDIO_STANDARD_INTERFACE] = create_standard_audio_quirk,
 		[QUIRK_AUDIO_FIXED_ENDPOINT] = create_fixed_stream_quirk,
 		[QUIRK_AUDIO_EDIROL_UAXX] = create_uaxx_quirk,
diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h
index d679e72..06ebf24 100644
--- a/sound/usb/usbaudio.h
+++ b/sound/usb/usbaudio.h
@@ -74,6 +74,7 @@ enum quirk_type {
 	QUIRK_MIDI_FASTLANE,
 	QUIRK_MIDI_EMAGIC,
 	QUIRK_MIDI_CME,
+	QUIRK_MIDI_AKAI,
 	QUIRK_MIDI_US122L,
 	QUIRK_AUDIO_STANDARD_INTERFACE,
 	QUIRK_AUDIO_FIXED_ENDPOINT,
-- 
1.7.0.4

^ permalink raw reply related	[flat|nested] 13+ messages in thread

* Re: [PATCH 1/1] ALSA: usb-audio: add support for Akai MPD16
  2010-05-19  6:59 ` Clemens Ladisch
@ 2010-05-19 20:31   ` Krzysztof Foltman
  0 siblings, 0 replies; 13+ messages in thread
From: Krzysztof Foltman @ 2010-05-19 20:31 UTC (permalink / raw)
  To: alsa-devel

On 05/19/2010 07:59 AM, Clemens Ladisch wrote:

>  This might overflow if the buffer ends with a 2x byte.

True. I rewrote that part now, using better sanity checking (hopefully). 
Also, the control endpoint uses 0x1x instead of 0x2x and the length is 
9, so the mask 0xF8 was wrong too.

>  Assuming that this device doesn't have any output ports, please add
>  a comment that this isn't the actual output protocol.

In the most recent version, I've added output support (for control port 
only, as the device does not seem to handle input on its data port 
endpoint). It's a little bit hairy, but seems to do the job. I've 
checked it with amidi, both input and output - even when I deliberately 
added junk before actual sysex data, or when I put several SysEx 
messages in one hex string. The device seems to require that individual 
messages are entirely contained within a single USB packet, which 
complicates things a little bit.

Thanks!

K.

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH 1/1] ALSA: usb-audio: add support for Akai MPD16
  2010-05-19 20:06 Krzysztof Foltman
@ 2010-05-20  7:26 ` Clemens Ladisch
  2010-05-20  7:49   ` Krzysztof Foltman
  0 siblings, 1 reply; 13+ messages in thread
From: Clemens Ladisch @ 2010-05-20  7:26 UTC (permalink / raw)
  To: Krzysztof Foltman; +Cc: alsa-devel

Krzysztof Foltman wrote:
> +		int port = (buffer[pos] >> 4) - 1;
> +		if (... && (port == 0 || port == 1))

Not that it really matters, but if the port variable were unsigned, you
could just check for "port <= 1".

> +static void snd_usbmidi_akai_output(struct snd_usb_midi_out_endpoint *ep,
> +				    struct urb *urb)
> ...
> +	/* only try adding more data when there's space for at least 1 SysEx */
> +	while (urb->transfer_buffer_length + 16 < ep->max_transfer) {
> +		count = snd_rawmidi_transmit(ep->ports[0].substream, &tmp, 1);
> +		if (count < 1) {
> +			ep->ports[0].active = 0;
> +			return;
> +		}

It is possible that ALSA's buffer does not yet contain the entire SysEx
message; in this case, the driver would send off the partial message
without the 0x10 in the first byte.

I think the only solution for that is to use snd_rawmidi_transmit_peek,
and for a SysEx, waiting until the entire message is available before
copying it to the transfer_buffer and calling transmit_ack.

> +	/* Akai MPD16 */
> +	CONTROL_PORT(0x09e8, 0x0062, 0, "%s Control"),
> +	EXTERNAL_PORT(0x09e8, 0x0062, 1, "%s MIDI"),

This MIDI port isn't an external port (which would imply that any random
device could be connected to it).  There is no macro for internal ports;
just use PORT_INFO(... _MIDI_GENERIC | _HARDWARE).


Regards,
Clemens

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH 1/1] ALSA: usb-audio: add support for Akai MPD16
  2010-05-20  7:26 ` Clemens Ladisch
@ 2010-05-20  7:49   ` Krzysztof Foltman
  2010-05-20  9:21     ` Clemens Ladisch
  0 siblings, 1 reply; 13+ messages in thread
From: Krzysztof Foltman @ 2010-05-20  7:49 UTC (permalink / raw)
  To: alsa-devel

On 05/20/2010 08:26 AM, Clemens Ladisch wrote:

>  Not that it really matters, but if the port variable were unsigned,
>  you could just check for "port <= 1".

True.

>  It is possible that ALSA's buffer does not yet contain the entire
>  SysEx message; in this case, the driver would send off the partial
>  message without the 0x10 in the first byte.

In that case, the length wouldn't be updated to include the partial 
message, so it would send all the previous messages in the transmit 
buffer, but not the new (incomplete) message, and it would lose the 
incomplete message (because the code would then expect the message to 
end up at offset 0 instead of transfer_buffer_length, which isn't 
happening).

Anyway, the only correct solution is - as you say - to keep peeking 
until skippable content or a full valid SysEx is found. There are still 
some nasty corner cases here, too (for example, a SysEx that's longer 
than accepted maximum), but it should work. I'll try to implement it 
that way.

>  This MIDI port isn't an external port (which would imply that any
>  random device could be connected to it).  There is no macro for
>  internal ports; just use PORT_INFO(... _MIDI_GENERIC | _HARDWARE).

True. It isn't the other end of a MIDI socket, it's a USB version of the 
MIDI output of the device.

What about the oversized comment block about MPD16 SysEx messages - 
should it remain in the source file, or should I move it to a text file 
or remove it altogether?

Thanks  for the comments/ideas!

K.

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH 1/1] ALSA: usb-audio: add support for Akai MPD16
  2010-05-20  7:49   ` Krzysztof Foltman
@ 2010-05-20  9:21     ` Clemens Ladisch
  2010-05-25  9:07       ` Krzysztof Foltman
  0 siblings, 1 reply; 13+ messages in thread
From: Clemens Ladisch @ 2010-05-20  9:21 UTC (permalink / raw)
  To: Krzysztof Foltman; +Cc: alsa-devel

Krzysztof Foltman wrote:
> What about the oversized comment block about MPD16 SysEx messages -
> should it remain in the source file, or should I move it to a text file
> or remove it altogether?

The comments in this file are usually only about the protocol itself,
because this is what the driver needs to know.  The fact that all
SysEx's have a known limit of 9 bytes is interesting for the driver,
but the contents of the SysEx messages are important only to programs
that want to control the MPD16; I'm not sure if this even belongs into
the kernel.  (In theory, this comment belongs into that control
application.)


Regards,
Clemens

^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH 1/1] ALSA: usb-audio: add support for Akai MPD16
@ 2010-05-20 19:31 Krzysztof Foltman
  0 siblings, 0 replies; 13+ messages in thread
From: Krzysztof Foltman @ 2010-05-20 19:31 UTC (permalink / raw)
  To: patch; +Cc: alsa-devel, Krzysztof Foltman

The decoding/encoding is based on own reverse-engineering. Both control and
data ports are handled. Writing to control port supports SysEx events only,
as this is the only type of messages that MPD16 recognizes.

Signed-off-by: Krzysztof Foltman <wdev@foltman.com>

diff --git a/sound/usb/midi.c b/sound/usb/midi.c
index 2c1558c..9c23d89 100644
--- a/sound/usb/midi.c
+++ b/sound/usb/midi.c
@@ -645,6 +645,105 @@ static struct usb_protocol_ops snd_usbmidi_cme_ops = {
 };
 
 /*
+ * AKAI MPD16 protocol:
+ *
+ * For control port (endpoint 1):
+ * ==============================
+ * One or more chunks consisting of first byte of (0x10 | msg_len) and then a
+ * SysEx message (msg_len=9 bytes long).
+ *
+ * For data port (endpoint 2):
+ * ===========================
+ * One or more chunks consisting of first byte of (0x20 | msg_len) and then a
+ * MIDI message (msg_len bytes long)
+ *
+ * Messages sent: Active Sense, Note On, Poly Pressure, Control Change.
+ */
+static void snd_usbmidi_akai_input(struct snd_usb_midi_in_endpoint *ep,
+				   uint8_t *buffer, int buffer_length)
+{
+	unsigned int pos = 0;
+	unsigned int len = (unsigned int)buffer_length;
+	while (pos < len) {
+		unsigned int port = (buffer[pos] >> 4) - 1;
+		unsigned int msg_len = buffer[pos] & 0x0f;
+		pos++;
+		if (pos + msg_len <= len && port < 2)
+			snd_usbmidi_input_data(ep, 0, &buffer[pos], msg_len);
+		pos += msg_len;
+	}
+}
+
+#define MAX_AKAI_SYSEX_LEN 9
+
+static void snd_usbmidi_akai_output(struct snd_usb_midi_out_endpoint *ep,
+				    struct urb *urb)
+{
+	uint8_t *msg;
+	int pos, end, count, buf_end;
+	uint8_t tmp[MAX_AKAI_SYSEX_LEN];
+	struct snd_rawmidi_substream *substream = ep->ports[0].substream;
+
+	if (!ep->ports[0].active)
+		return;
+
+	msg = urb->transfer_buffer + urb->transfer_buffer_length;
+	buf_end = ep->max_transfer - MAX_AKAI_SYSEX_LEN - 1;
+
+	/* only try adding more data when there's space for at least 1 SysEx */
+	while (urb->transfer_buffer_length < buf_end) {
+		count = snd_rawmidi_transmit_peek(substream,
+						  tmp, MAX_AKAI_SYSEX_LEN);
+		if (!count) {
+			ep->ports[0].active = 0;
+			return;
+		}
+		/* try to skip non-SysEx data */
+		for (pos = 0; pos < count && tmp[pos] != 0xF0; pos++)
+			;
+
+		if (pos > 0) {
+			snd_rawmidi_transmit_ack(substream, pos);
+			continue;
+		}
+
+		/* look for the start or end marker */
+		for (end = 1; end < count && tmp[end] < 0xF0; end++)
+			;
+
+		/* next SysEx started before the end of current one */
+		if (end < count && tmp[end] == 0xF0) {
+			/* it's incomplete - drop it */
+			snd_rawmidi_transmit_ack(substream, end);
+			continue;
+		}
+		/* SysEx complete */
+		if (end < count && tmp[end] == 0xF7) {
+			/* queue it, ack it, and get the next one */
+			count = end + 1;
+			msg[0] = 0x10 | count;
+			memcpy(&msg[1], tmp, count);
+			snd_rawmidi_transmit_ack(substream, count);
+			urb->transfer_buffer_length += count + 1;
+			msg += count + 1;
+			continue;
+		}
+		/* less than 9 bytes and no end byte - wait for more */
+		if (count < MAX_AKAI_SYSEX_LEN) {
+			ep->ports[0].active = 0;
+			return;
+		}
+		/* 9 bytes and no end marker in sight - malformed, skip it */
+		snd_rawmidi_transmit_ack(substream, count);
+	}
+}
+
+static struct usb_protocol_ops snd_usbmidi_akai_ops = {
+	.input = snd_usbmidi_akai_input,
+	.output = snd_usbmidi_akai_output,
+};
+
+/*
  * Novation USB MIDI protocol: number of data bytes is in the first byte
  * (when receiving) (+1!) or in the second byte (when sending); data begins
  * at the third byte.
@@ -1434,6 +1533,11 @@ static struct port_info {
 	EXTERNAL_PORT(0x086a, 0x0001, 8, "%s Broadcast"),
 	EXTERNAL_PORT(0x086a, 0x0002, 8, "%s Broadcast"),
 	EXTERNAL_PORT(0x086a, 0x0003, 4, "%s Broadcast"),
+	/* Akai MPD16 */
+	CONTROL_PORT(0x09e8, 0x0062, 0, "%s Control"),
+	PORT_INFO(0x09e8, 0x0062, 1, "%s MIDI", 0,
+		SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC |
+		SNDRV_SEQ_PORT_TYPE_HARDWARE),
 	/* Access Music Virus TI */
 	EXTERNAL_PORT(0x133e, 0x0815, 0, "%s MIDI"),
 	PORT_INFO(0x133e, 0x0815, 1, "%s Synth", 0,
@@ -2035,6 +2139,12 @@ int snd_usbmidi_create(struct snd_card *card,
 		umidi->usb_protocol_ops = &snd_usbmidi_cme_ops;
 		err = snd_usbmidi_detect_per_port_endpoints(umidi, endpoints);
 		break;
+	case QUIRK_MIDI_AKAI:
+		umidi->usb_protocol_ops = &snd_usbmidi_akai_ops;
+		err = snd_usbmidi_detect_per_port_endpoints(umidi, endpoints);
+		/* endpoint 1 is input-only */
+		endpoints[1].out_cables = 0;
+		break;
 	default:
 		snd_printd(KERN_ERR "invalid quirk type %d\n", quirk->type);
 		err = -ENXIO;
diff --git a/sound/usb/midi.h b/sound/usb/midi.h
index 2089ec9..2fca80b 100644
--- a/sound/usb/midi.h
+++ b/sound/usb/midi.h
@@ -37,6 +37,8 @@ struct snd_usb_midi_endpoint_info {
 
 /* for QUIRK_MIDI_CME, data is NULL */
 
+/* for QUIRK_MIDI_AKAI, data is NULL */
+
 int snd_usbmidi_create(struct snd_card *card,
 		       struct usb_interface *iface,
 		       struct list_head *midi_list,
diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h
index 91ddef3..f8797f6 100644
--- a/sound/usb/quirks-table.h
+++ b/sound/usb/quirks-table.h
@@ -1973,6 +1973,17 @@ YAMAHA_DEVICE(0x7010, "UB99"),
 	}
 },
 
+/* AKAI devices */
+{
+	USB_DEVICE(0x09e8, 0x0062),
+	.driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+		.vendor_name = "AKAI",
+		.product_name = "MPD16",
+		.ifnum = 0,
+		.type = QUIRK_MIDI_AKAI,
+	}
+},
+
 /* TerraTec devices */
 {
 	USB_DEVICE_VENDOR_SPEC(0x0ccd, 0x0012),
diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c
index 136e5b4..b45e54c 100644
--- a/sound/usb/quirks.c
+++ b/sound/usb/quirks.c
@@ -289,6 +289,7 @@ int snd_usb_create_quirk(struct snd_usb_audio *chip,
 		[QUIRK_MIDI_FASTLANE] = create_any_midi_quirk,
 		[QUIRK_MIDI_EMAGIC] = create_any_midi_quirk,
 		[QUIRK_MIDI_CME] = create_any_midi_quirk,
+		[QUIRK_MIDI_AKAI] = create_any_midi_quirk,
 		[QUIRK_AUDIO_STANDARD_INTERFACE] = create_standard_audio_quirk,
 		[QUIRK_AUDIO_FIXED_ENDPOINT] = create_fixed_stream_quirk,
 		[QUIRK_AUDIO_EDIROL_UAXX] = create_uaxx_quirk,
diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h
index d679e72..06ebf24 100644
--- a/sound/usb/usbaudio.h
+++ b/sound/usb/usbaudio.h
@@ -74,6 +74,7 @@ enum quirk_type {
 	QUIRK_MIDI_FASTLANE,
 	QUIRK_MIDI_EMAGIC,
 	QUIRK_MIDI_CME,
+	QUIRK_MIDI_AKAI,
 	QUIRK_MIDI_US122L,
 	QUIRK_AUDIO_STANDARD_INTERFACE,
 	QUIRK_AUDIO_FIXED_ENDPOINT,
-- 
1.7.0.4

^ permalink raw reply related	[flat|nested] 13+ messages in thread

* Re: [PATCH 1/1] ALSA: usb-audio: add support for Akai MPD16
  2010-05-20  9:21     ` Clemens Ladisch
@ 2010-05-25  9:07       ` Krzysztof Foltman
  2010-05-25  9:39         ` Daniel Mack
  0 siblings, 1 reply; 13+ messages in thread
From: Krzysztof Foltman @ 2010-05-25  9:07 UTC (permalink / raw)
  To: Clemens Ladisch; +Cc: ALSA Development Mailing List

On 20/05/10 10:21, Clemens Ladisch wrote:

> The comments in this file are usually only about the protocol itself,
> because this is what the driver needs to know.

Okay. Any comments about the 20 May version of the patch? It has the 
output routine almost completely rewritten, and the comments are limited 
to essential stuff (anyone can now find the previous version with 
protocol description by googling mpd16 protocol, so it should be OK).

Thanks,
Krzysztof

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH 1/1] ALSA: usb-audio: add support for Akai MPD16
  2010-05-25  9:07       ` Krzysztof Foltman
@ 2010-05-25  9:39         ` Daniel Mack
  0 siblings, 0 replies; 13+ messages in thread
From: Daniel Mack @ 2010-05-25  9:39 UTC (permalink / raw)
  To: Krzysztof Foltman; +Cc: ALSA Development Mailing List, Clemens Ladisch

On Tue, May 25, 2010 at 10:07:18AM +0100, Krzysztof Foltman wrote:
> On 20/05/10 10:21, Clemens Ladisch wrote:
> 
> > The comments in this file are usually only about the protocol itself,
> > because this is what the driver needs to know.
> 
> Okay. Any comments about the 20 May version of the patch? It has the 
> output routine almost completely rewritten, and the comments are limited 
> to essential stuff (anyone can now find the previous version with 
> protocol description by googling mpd16 protocol, so it should be OK).

It's commited already, see

  http://git.kernel.org/?p=linux/kernel/git/tiwai/sound-2.6.git;a=summary

Daniel

^ permalink raw reply	[flat|nested] 13+ messages in thread

end of thread, other threads:[~2010-05-25  9:40 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2010-05-17 22:48 [PATCH 1/1] ALSA: usb-audio: add support for Akai MPD16 wdev
2010-05-18  0:38 ` Daniel Mack
2010-05-18 18:36   ` Krzysztof Foltman
  -- strict thread matches above, loose matches on Subject: below --
2010-05-18 18:37 Krzysztof Foltman
2010-05-19  6:59 ` Clemens Ladisch
2010-05-19 20:31   ` Krzysztof Foltman
2010-05-19 20:06 Krzysztof Foltman
2010-05-20  7:26 ` Clemens Ladisch
2010-05-20  7:49   ` Krzysztof Foltman
2010-05-20  9:21     ` Clemens Ladisch
2010-05-25  9:07       ` Krzysztof Foltman
2010-05-25  9:39         ` Daniel Mack
2010-05-20 19:31 Krzysztof Foltman

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).