Skip to content

Commit

Permalink
ALSA: firewire-lib: limit the MIDI data rate
Browse files Browse the repository at this point in the history
Do no send MIDI bytes at the full rate at which FireWire packets happen
to be sent, but restrict them to the actual rate of a real MIDI port.
This is required by the specification, and prevents data loss when the
device's buffer overruns.

Signed-off-by: Clemens Ladisch <[email protected]>
Reviewed-by: Takashi Sakamoto <[email protected]>
Tested-by: Takashi Sakamoto <[email protected]>
Signed-off-by: Takashi Iwai <[email protected]>
  • Loading branch information
cladisch authored and tiwai committed Jan 16, 2015
1 parent 5c697e5 commit 25ca917
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 6 deletions.
61 changes: 55 additions & 6 deletions sound/firewire/amdtp.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@
#define CYCLES_PER_SECOND 8000
#define TICKS_PER_SECOND (TICKS_PER_CYCLE * CYCLES_PER_SECOND)

/*
* Nominally 3125 bytes/second, but the MIDI port's clock might be
* 1% too slow, and the bus clock 100 ppm too fast.
*/
#define MIDI_BYTES_PER_SECOND 3093

/*
* Several devices look only at the first eight data blocks.
* In any case, this is more than enough for the MIDI data rate.
Expand Down Expand Up @@ -226,6 +232,14 @@ void amdtp_stream_set_parameters(struct amdtp_stream *s,
for (i = 0; i < pcm_channels; i++)
s->pcm_positions[i] = i;
s->midi_position = s->pcm_channels;

/*
* We do not know the actual MIDI FIFO size of most devices. Just
* assume two bytes, i.e., one byte can be received over the bus while
* the previous one is transmitted over MIDI.
* (The value here is adjusted for midi_ratelimit_per_packet().)
*/
s->midi_fifo_limit = rate - MIDI_BYTES_PER_SECOND * s->syt_interval + 1;
}
EXPORT_SYMBOL(amdtp_stream_set_parameters);

Expand Down Expand Up @@ -467,23 +481,58 @@ static void amdtp_fill_pcm_silence(struct amdtp_stream *s,
}
}

/*
* To avoid sending MIDI bytes at too high a rate, assume that the receiving
* device has a FIFO, and track how much it is filled. This values increases
* by one whenever we send one byte in a packet, but the FIFO empties at
* a constant rate independent of our packet rate. One packet has syt_interval
* samples, so the number of bytes that empty out of the FIFO, per packet(!),
* is MIDI_BYTES_PER_SECOND * syt_interval / sample_rate. To avoid storing
* fractional values, the values in midi_fifo_used[] are measured in bytes
* multiplied by the sample rate.
*/
static bool midi_ratelimit_per_packet(struct amdtp_stream *s, unsigned int port)
{
int used;

used = s->midi_fifo_used[port];
if (used == 0) /* common shortcut */
return true;

used -= MIDI_BYTES_PER_SECOND * s->syt_interval;
used = max(used, 0);
s->midi_fifo_used[port] = used;

return used < s->midi_fifo_limit;
}

static void midi_rate_use_one_byte(struct amdtp_stream *s, unsigned int port)
{
s->midi_fifo_used[port] += amdtp_rate_table[s->sfc];
}

static void amdtp_fill_midi(struct amdtp_stream *s,
__be32 *buffer, unsigned int frames)
{
unsigned int f, port;
u8 *b;

for (f = 0; f < frames; f++) {
buffer[s->midi_position] = 0;
b = (u8 *)&buffer[s->midi_position];

port = (s->data_block_counter + f) % 8;
if ((f >= MAX_MIDI_RX_BLOCKS) ||
(s->midi[port] == NULL) ||
(snd_rawmidi_transmit(s->midi[port], b + 1, 1) <= 0))
b[0] = 0x80;
else
if (f < MAX_MIDI_RX_BLOCKS &&
midi_ratelimit_per_packet(s, port) &&
s->midi[port] != NULL &&
snd_rawmidi_transmit(s->midi[port], &b[1], 1) == 1) {
midi_rate_use_one_byte(s, port);
b[0] = 0x81;
} else {
b[0] = 0x80;
b[1] = 0;
}
b[2] = 0;
b[3] = 0;

buffer += s->data_block_quadlets;
}
Expand Down
2 changes: 2 additions & 0 deletions sound/firewire/amdtp.h
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@ struct amdtp_stream {
bool double_pcm_frames;

struct snd_rawmidi_substream *midi[AMDTP_MAX_CHANNELS_FOR_MIDI * 8];
int midi_fifo_limit;
int midi_fifo_used[AMDTP_MAX_CHANNELS_FOR_MIDI * 8];

/* quirk: fixed interval of dbc between previos/current packets. */
unsigned int tx_dbc_interval;
Expand Down

0 comments on commit 25ca917

Please sign in to comment.