<commit_before>/**
 * \file pcm/pcm_multi.c
 * \ingroup PCM_Plugins
 * \brief PCM Multi Streams to One Conversion Plugin Interface
 * \author <NAME> <<EMAIL>>
 * \date 2000-2001
 */
/*
 *  PCM - Multi
 *  Copyright (c) 2000 by <NAME> <<EMAIL>>
 *
 *
 *   This library is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU Lesser General Public License as
 *   published by the Free Software Foundation; either version 2.1 of
 *   the License, or (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU Lesser General Public License for more details.
 *
 *   You should have received a copy of the GNU Lesser General Public
 *   License along with this library; if not, write to the Free Software
 *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
 *
 */
  
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <math.h>
#include "pcm_local.h"
#include "pcm_generic.h"

#ifndef PIC
/* entry for static linking */
const char *_snd_module_pcm_multi = "";
#endif

#ifndef DOC_HIDDEN

typedef struct {
	snd_pcm_t *pcm;
	unsigned int channels_count;
	int close_slave;
	int linked;
} snd_pcm_multi_slave_t;

typedef struct {
	int slave_idx;
	unsigned int slave_channel;
} snd_pcm_multi_channel_t;

typedef struct {
	unsigned int slaves_count;
	unsigned int master_slave;
	snd_pcm_multi_slave_t *slaves;
	int slave_link_master;
	unsigned int channels_count;
	snd_pcm_multi_channel_t *channels;
} snd_pcm_multi_t;

#endif

static int snd_pcm_multi_close(snd_pcm_t *pcm)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	unsigned int i;
	int ret = 0;
	for (i = 0; i < multi->slaves_count; ++i) {
		snd_pcm_multi_slave_t *slave = &multi->slaves[i];
		if (slave->close_slave) {
			int err = snd_pcm_close(slave->pcm);
			if (err < 0)
				ret = err;
		}
	}
	free(multi->slaves);
	free(multi->channels);
	free(multi);
	return ret;
}

static int snd_pcm_multi_nonblock(snd_pcm_t *pcm ATTRIBUTE_UNUSED, int nonblock ATTRIBUTE_UNUSED)
{
	return 0;
}

static int snd_pcm_multi_async(snd_pcm_t *pcm, int sig, pid_t pid)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	snd_pcm_t *slave_0 = multi->slaves[multi->master_slave].pcm;
	return snd_pcm_async(slave_0, sig, pid);
}

static int snd_pcm_multi_poll_descriptors_count(snd_pcm_t *pcm)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	snd_pcm_t *slave_0 = multi->slaves[multi->master_slave].pcm;
	return snd_pcm_poll_descriptors_count(slave_0);
}

static int snd_pcm_multi_poll_descriptors(snd_pcm_t *pcm, struct pollfd *pfds, unsigned int space)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	snd_pcm_t *slave;
	snd_pcm_t *slave_0 = multi->slaves[multi->master_slave].pcm;
	int err;
	unsigned int i;

	for (i = 0; i < multi->slaves_count; ++i) {
		slave = multi->slaves[i].pcm;
		if (slave == slave_0)
			continue;
		err = snd_pcm_poll_descriptors(slave, pfds, space);
		if (err < 0)
			return err;
	}
	/* finally overwrite with master's pfds */
	return snd_pcm_poll_descriptors(slave_0, pfds, space);
}

static int snd_pcm_multi_poll_revents(snd_pcm_t *pcm, struct pollfd *pfds, unsigned int nfds, unsigned short *revents)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	snd_pcm_t *slave_0 = multi->slaves[multi->master_slave].pcm;
	return snd_pcm_poll_descriptors_revents(slave_0, pfds, nfds, revents);
}

static int snd_pcm_multi_info(snd_pcm_t *pcm, snd_pcm_info_t *info)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	int err, n;
	assert(info->subdevice < multi->slaves_count);
	n = info->subdevice;
	info->subdevice = 0;
	err = snd_pcm_info(multi->slaves[n].pcm, info);
	if (err < 0)
		return err;
	info->subdevices_count = multi->slaves_count;
	return 0;
}

static int snd_pcm_multi_hw_refine_cprepare(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	snd_pcm_access_mask_t access_mask;
	int err;
	snd_pcm_access_mask_any(&access_mask);
	snd_pcm_access_mask_reset(&access_mask, SND_PCM_ACCESS_MMAP_INTERLEAVED);
	err = _snd_pcm_hw_param_set_mask(params, SND_PCM_HW_PARAM_ACCESS,
					 &access_mask);
	if (err < 0)
		return err;
	err = _snd_pcm_hw_param_set(params, SND_PCM_HW_PARAM_CHANNELS,
				    multi->channels_count, 0);
	if (err < 0)
		return err;
	params->info = ~0U;
	return 0;
}

static int snd_pcm_multi_hw_refine_sprepare(snd_pcm_t *pcm, unsigned int slave_idx,
					    snd_pcm_hw_params_t *sparams)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	snd_pcm_multi_slave_t *slave = &multi->slaves[slave_idx];
	snd_pcm_access_mask_t saccess_mask = { SND_PCM_ACCBIT_MMAP };
	_snd_pcm_hw_params_any(sparams);
	_snd_pcm_hw_param_set_mask(sparams, SND_PCM_HW_PARAM_ACCESS,
				   &saccess_mask);
	_snd_pcm_hw_param_set(sparams, SND_PCM_HW_PARAM_CHANNELS,
			      slave->channels_count, 0);
	return 0;
}

static int snd_pcm_multi_hw_refine_schange(snd_pcm_t *pcm ATTRIBUTE_UNUSED,
					   unsigned int slave_idx ATTRIBUTE_UNUSED,
					   snd_pcm_hw_params_t *params,
					   snd_pcm_hw_params_t *sparams)
{
	int err;
	unsigned int links = (SND_PCM_HW_PARBIT_FORMAT |
			      SND_PCM_HW_PARBIT_SUBFORMAT |
			      SND_PCM_HW_PARBIT_RATE |
			      SND_PCM_HW_PARBIT_PERIOD_SIZE |
			      SND_PCM_HW_PARBIT_PERIOD_TIME |
			      SND_PCM_HW_PARBIT_PERIODS |
			      SND_PCM_HW_PARBIT_BUFFER_SIZE |
			      SND_PCM_HW_PARBIT_BUFFER_TIME |
			      SND_PCM_HW_PARBIT_TICK_TIME);
	const snd_pcm_access_mask_t *access_mask = snd_pcm_hw_param_get_mask(params, SND_PCM_HW_PARAM_ACCESS);
	if (!snd_pcm_access_mask_test(access_mask, SND_PCM_ACCESS_RW_INTERLEAVED) &&
	    !snd_pcm_access_mask_test(access_mask, SND_PCM_ACCESS_RW_NONINTERLEAVED) &&
	    !snd_pcm_access_mask_test(access_mask, SND_PCM_ACCESS_MMAP_NONINTERLEAVED)) {
		snd_pcm_access_mask_t saccess_mask;
		snd_pcm_access_mask_any(&saccess_mask);
		snd_pcm_access_mask_reset(&saccess_mask, SND_PCM_ACCESS_MMAP_NONINTERLEAVED);
		err = _snd_pcm_hw_param_set_mask(sparams, SND_PCM_HW_PARAM_ACCESS,
						 &saccess_mask);
		if (err < 0)
			return err;
	}
	err = _snd_pcm_hw_params_refine(sparams, links, params);
	if (err < 0)
		return err;
	return 0;
}
	
static int snd_pcm_multi_hw_refine_cchange(snd_pcm_t *pcm ATTRIBUTE_UNUSED,
					   unsigned int slave_idx ATTRIBUTE_UNUSED,
					   snd_pcm_hw_params_t *params,
					   snd_pcm_hw_params_t *sparams)
{
	int err;
	unsigned int links = (SND_PCM_HW_PARBIT_FORMAT |
			      SND_PCM_HW_PARBIT_SUBFORMAT |
			      SND_PCM_HW_PARBIT_RATE |
			      SND_PCM_HW_PARBIT_PERIOD_SIZE |
			      SND_PCM_HW_PARBIT_PERIOD_TIME |
			      SND_PCM_HW_PARBIT_PERIODS |
			      SND_PCM_HW_PARBIT_BUFFER_SIZE |
			      SND_PCM_HW_PARBIT_BUFFER_TIME |
			      SND_PCM_HW_PARBIT_TICK_TIME);
	snd_pcm_access_mask_t access_mask;
	const snd_pcm_access_mask_t *saccess_mask = snd_pcm_hw_param_get_mask(sparams, SND_PCM_HW_PARAM_ACCESS);
	snd_pcm_access_mask_any(&access_mask);
	snd_pcm_access_mask_reset(&access_mask, SND_PCM_ACCESS_MMAP_INTERLEAVED);
	if (!snd_pcm_access_mask_test(saccess_mask, SND_PCM_ACCESS_MMAP_NONINTERLEAVED))
		snd_pcm_access_mask_reset(&access_mask, SND_PCM_ACCESS_MMAP_NONINTERLEAVED);
	if (!snd_pcm_access_mask_test(saccess_mask, SND_PCM_ACCESS_MMAP_COMPLEX) &&
	    !snd_pcm_access_mask_test(saccess_mask, SND_PCM_ACCESS_MMAP_INTERLEAVED))
		snd_pcm_access_mask_reset(&access_mask, SND_PCM_ACCESS_MMAP_COMPLEX);
	err = _snd_pcm_hw_param_set_mask(params, SND_PCM_HW_PARAM_ACCESS,
					 &access_mask);
	if (err < 0)
		return err;
	err = _snd_pcm_hw_params_refine(params, links, sparams);
	if (err < 0)
		return err;
	params->info &= sparams->info;
	return 0;
}

static int snd_pcm_multi_hw_refine_slave(snd_pcm_t *pcm,
					 unsigned int slave_idx,
					 snd_pcm_hw_params_t *sparams)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	snd_pcm_t *slave = multi->slaves[slave_idx].pcm;
	return snd_pcm_hw_refine(slave, sparams);
}

static int snd_pcm_multi_hw_refine(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	unsigned int k;
	snd_pcm_hw_params_t sparams[multi->slaves_count];
	int err;
	unsigned int cmask, changed;
	err = snd_pcm_multi_hw_refine_cprepare(pcm, params);
	if (err < 0)
		return err;
	for (k = 0; k < multi->slaves_count; ++k) {
		err = snd_pcm_multi_hw_refine_sprepare(pcm, k, &sparams[k]);
		if (err < 0) {
			SNDERR("Slave PCM #%d not usable", k);
			return err;
		}
	}
	do {
		cmask = params->cmask;
		params->cmask = 0;
		for (k = 0; k < multi->slaves_count; ++k) {
			err = snd_pcm_multi_hw_refine_schange(pcm, k, params, &sparams[k]);
			if (err >= 0)
				err = snd_pcm_multi_hw_refine_slave(pcm, k, &sparams[k]);
			if (err < 0) {
				snd_pcm_multi_hw_refine_cchange(pcm, k, params, &sparams[k]);
				return err;
			}
			err = snd_pcm_multi_hw_refine_cchange(pcm, k, params, &sparams[k]);
			if (err < 0)
				return err;
		}
		err = snd_pcm_hw_refine_soft(pcm, params);
		changed = params->cmask;
		params->cmask |= cmask;
		if (err < 0)
			return err;
	} while (changed);
	return 0;
}

static int snd_pcm_multi_hw_params_slave(snd_pcm_t *pcm,
					 unsigned int slave_idx,
					 snd_pcm_hw_params_t *sparams)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	snd_pcm_t *slave = multi->slaves[slave_idx].pcm;
	int err = snd_pcm_hw_params(slave, sparams);
	if (err < 0)
		return err;
	err = snd_pcm_areas_silence(slave->running_areas, 0, slave->channels, slave->buffer_size, slave->format);
	if (err < 0)
		return err;
	if (slave->stopped_areas) {
		err = snd_pcm_areas_silence(slave->stopped_areas, 0, slave->channels, slave->buffer_size, slave->format);
		if (err < 0)
			return err;
	}
	return 0;
}

static int snd_pcm_multi_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	unsigned int i;
	snd_pcm_hw_params_t sparams[multi->slaves_count];
	int err;
	for (i = 0; i < multi->slaves_count; ++i) {
		err = snd_pcm_multi_hw_refine_sprepare(pcm, i, &sparams[i]);
		assert(err >= 0);
		err = snd_pcm_multi_hw_refine_schange(pcm, i, params, &sparams[i]);
		assert(err >= 0);
		err = snd_pcm_multi_hw_params_slave(pcm, i, &sparams[i]);
		if (err < 0) {
			snd_pcm_multi_hw_refine_cchange(pcm, i, params, &sparams[i]);
			return err;
		}
	}
	multi->slaves[0].linked = 0;
	multi->slave_link_master = 0;
	for (i = 1; i < multi->slaves_count; ++i) {
		err = snd_pcm_link(multi->slaves[0].pcm, multi->slaves[i].pcm);
		multi->slaves[i].linked = (err >= 0);
	}
	return 0;
}

static int snd_pcm_multi_hw_free(snd_pcm_t *pcm)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	unsigned int i;
	int err = 0;
	for (i = 0; i < multi->slaves_count; ++i) {
		snd_pcm_t *slave = multi->slaves[i].pcm;
		int e = snd_pcm_hw_free(slave);
		if (e < 0)
			err = e;
		if (!multi->slaves[i].linked)
			continue;
		multi->slaves[i].linked = 0;
		e = snd_pcm_unlink(slave);
		if (e < 0)
			err = e;
	}
	return err;
}

static int snd_pcm_multi_sw_params(snd_pcm_t *pcm, snd_pcm_sw_params_t *params)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	unsigned int i;
	int err;
	for (i = 0; i < multi->slaves_count; ++i) {
		snd_pcm_t *slave = multi->slaves[i].pcm;
		err = snd_pcm_sw_params(slave, params);
		if (err < 0)
			return err;
	}
	return 0;
}

static int snd_pcm_multi_status(snd_pcm_t *pcm, snd_pcm_status_t *status)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	snd_pcm_t *slave = multi->slaves[multi->master_slave].pcm;
	return snd_pcm_status(slave, status);
}

static snd_pcm_state_t snd_pcm_multi_state(snd_pcm_t *pcm)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	snd_pcm_t *slave = multi->slaves[multi->master_slave].pcm;
	return snd_pcm_state(slave);
}

static int snd_pcm_multi_hwsync(snd_pcm_t *pcm)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	snd_pcm_t *slave = multi->slaves[multi->master_slave].pcm;
	return snd_pcm_hwsync(slave);
}

static int snd_pcm_multi_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	snd_pcm_t *slave = multi->slaves[multi->master_slave].pcm;
	return snd_pcm_delay(slave, delayp);
}

static snd_pcm_sframes_t snd_pcm_multi_avail_update(snd_pcm_t *pcm)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	snd_pcm_sframes_t ret = LONG_MAX;
	unsigned int i;
	for (i = 0; i < multi->slaves_count; ++i) {
		snd_pcm_sframes_t avail;
		avail = snd_pcm_avail_update(multi->slaves[i].pcm);
		if (avail < 0)
			return avail;
		if (ret > avail)
			ret = avail;
	}
	return ret;
}

static int snd_pcm_multi_prepare(snd_pcm_t *pcm)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	int err = 0;
	unsigned int i;
	for (i = 0; i < multi->slaves_count; ++i) {
		if (multi->slaves[i].linked)
			continue;
		err = snd_pcm_prepare(multi->slaves[i].pcm);
		if (err < 0)
			return err;
	}
	return err;
}

static int snd_pcm_multi_reset(snd_pcm_t *pcm)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	int err = 0;
	unsigned int i;
	for (i = 0; i < multi->slaves_count; ++i) {
		if (multi->slaves[i].linked)
			continue;
		err = snd_pcm_reset(multi->slaves[i].pcm);
		if (err < 0)
			return err;
	}
	return err;
}

static int snd_pcm_multi_start(snd_pcm_t *pcm)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	int err = 0;
	unsigned int i;
	for (i = 0; i < multi->slaves_count; ++i) {
		if (multi->slaves[i].linked)
			continue;
		err = snd_pcm_start(multi->slaves[i].pcm);
		if (err < 0)
			return err;
	}
	return err;
}

static int snd_pcm_multi_drop(snd_pcm_t *pcm)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	int err = 0;
	unsigned int i;
	for (i = 0; i < multi->slaves_count; ++i) {
		if (multi->slaves[i].linked)
			continue;
		err = snd_pcm_drop(multi->slaves[i].pcm);
		if (err < 0)
			return err;
	}
	return err;
}

static int snd_pcm_multi_drain(snd_pcm_t *pcm)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	int err = 0;
	unsigned int i;
	for (i = 0; i < multi->slaves_count; ++i) {
		if (multi->slaves[i].linked)
			continue;
		err = snd_pcm_drain(multi->slaves[i].pcm);
		if (err < 0)
			return err;
	}
	return err;
}

static int snd_pcm_multi_pause(snd_pcm_t *pcm, int enable)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	int err = 0;
	unsigned int i;
	for (i = 0; i < multi->slaves_count; ++i) {
		if (multi->slaves[i].linked)
			continue;
		err = snd_pcm_pause(multi->slaves[i].pcm, enable);
		if (err < 0)
			return err;
	}
	return err;
}

static int snd_pcm_multi_channel_info(snd_pcm_t *pcm, snd_pcm_channel_info_t *info)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	unsigned int channel = info->channel;
	snd_pcm_multi_channel_t *c = &multi->channels[channel];
	int err;
	if (c->slave_idx < 0)
		return -ENXIO;
	info->channel = c->slave_channel;
	err = snd_pcm_channel_info(multi->slaves[c->slave_idx].pcm, info);
	info->channel = channel;
	return err;
}

static snd_pcm_sframes_t snd_pcm_multi_rewind(snd_pcm_t *pcm, snd_pcm_uframes_t frames)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	unsigned int i;
	snd_pcm_uframes_t pos[multi->slaves_count];
	memset(pos, 0, sizeof(pos));
	for (i = 0; i < multi->slaves_count; ++i) {
		snd_pcm_t *slave_i = multi->slaves[i].pcm;
		snd_pcm_sframes_t f = snd_pcm_rewind(slave_i, frames);
		if (f < 0)
			return f;
		pos[i] = f;
		frames = f;
	}
	/* Realign the pointers */
	for (i = 0; i < multi->slaves_count; ++i) {
		snd_pcm_t *slave_i = multi->slaves[i].pcm;
		snd_pcm_uframes_t f = pos[i] - frames;
		snd_pcm_sframes_t result;
		if (f > 0) {
			result = INTERNAL(snd_pcm_forward)(slave_i, f);
			if (result < 0)
				return result;
			if ((snd_pcm_uframes_t)result != f)
				return -EIO;
		}
	}
	return frames;
}

static snd_pcm_sframes_t snd_pcm_multi_forward(snd_pcm_t *pcm, snd_pcm_uframes_t frames)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	unsigned int i;
	snd_pcm_uframes_t pos[multi->slaves_count];
	memset(pos, 0, sizeof(pos));
	for (i = 0; i < multi->slaves_count; ++i) {
		snd_pcm_t *slave_i = multi->slaves[i].pcm;
		snd_pcm_sframes_t f = INTERNAL(snd_pcm_forward)(slave_i, frames);
		if (f < 0)
			return f;
		pos[i] = f;
		frames = f;
	}
	/* Realign the pointers */
	for (i = 0; i < multi->slaves_count; ++i) {
		snd_pcm_t *slave_i = multi->slaves[i].pcm;
		snd_pcm_uframes_t f = pos[i] - frames;
		snd_pcm_sframes_t result;
		if (f > 0) {
			result = snd_pcm_rewind(slave_i, f);
			if (result < 0)
				return result;
			if ((snd_pcm_uframes_t)result != f)
				return -EIO;
		}
	}
	return frames;
}

static int snd_pcm_multi_resume(snd_pcm_t *pcm)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	int err = 0;
	unsigned int i;
	for (i = 0; i < multi->slaves_count; ++i) {
		if (multi->slaves[i].linked)
			continue;
		err = snd_pcm_resume(multi->slaves[i].pcm);
		if (err < 0)
			return err;
	}
	return err;
}

static int snd_pcm_multi_link_fd_failed(snd_pcm_t *pcm, int fd)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	unsigned int i;

	for (i = 0; i < multi->slaves_count; ++i) {
		if (_snd_pcm_link_descriptor(multi->slaves[i].pcm) != fd)
			continue;
		 multi->slaves[i].linked = 0;
	}
	return 0;
}

static int snd_pcm_multi_link_fd(snd_pcm_t *pcm, int *fds, int count, int (**failed)(snd_pcm_t *pcm, int fd))
{ 
	snd_pcm_multi_t *multi = pcm->private_data;
	unsigned int i;

	if (count < (int)multi->slaves_count)
		return -ENOMEM;
	for (i = 0; i < multi->slaves_count; ++i) {
		if (multi->slaves[i].linked)
			snd_pcm_unlink(multi->slaves[i].pcm);
		fds[i] = _snd_pcm_link_descriptor(multi->slaves[i].pcm);
		multi->slaves[i].linked = 1;
	}
	*failed = snd_pcm_multi_link_fd_failed;
	return multi->slaves_count;
}

static int snd_pcm_multi_unlink(snd_pcm_t *pcm)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	unsigned int i;

	for (i = 0; i < multi->slaves_count; ++i) {
		if (multi->slaves[i].linked)
			snd_pcm_unlink(multi->slaves[i].pcm);
		multi->slaves[i].linked = 0;
	}
	return 0;
}

static snd_pcm_sframes_t snd_pcm_multi_mmap_commit(snd_pcm_t *pcm,
						   snd_pcm_uframes_t offset,
						   snd_pcm_uframes_t size)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	snd_pcm_t *slave;
	unsigned int i;
	snd_pcm_sframes_t result;

	for (i = 0; i < multi->slaves_count; ++i) {
		slave = multi->slaves[i].pcm;
		result = snd_pcm_mmap_commit(slave, offset, size);
		if (result < 0)
			return result;
		if ((snd_pcm_uframes_t)result != size)
			return -EIO;
	}
	return size;
}

static int snd_pcm_multi_mmap(snd_pcm_t *pcm ATTRIBUTE_UNUSED)
{
	return 0;
}

static int snd_pcm_multi_munmap(snd_pcm_t *pcm ATTRIBUTE_UNUSED)
{
	return 0;
}

static void snd_pcm_multi_dump(snd_pcm_t *pcm, snd_output_t *out)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	unsigned int k;
	snd_output_printf(out, "Multi PCM\n");
	snd_output_printf(out, "\nChannel bindings:\n");
	for (k = 0; k < multi->channels_count; ++k) {
		snd_pcm_multi_channel_t *c = &multi->channels[k];
		if (c->slave_idx < 0)
			continue;
		snd_output_printf(out, "%d: slave %d, channel %d\n", 
			k, c->slave_idx, c->slave_channel);
	}
	if (pcm->setup) {
		snd_output_printf(out, "\nIts setup is:\n");
		snd_pcm_dump_setup(pcm, out);
	}
	for (k = 0; k < multi->slaves_count; ++k) {
		snd_output_printf(out, "\nSlave #%d: ", k);
		snd_pcm_dump(multi->slaves[k].pcm, out);
	}
}

static snd_pcm_ops_t snd_pcm_multi_ops = {
	.close = snd_pcm_multi_close,
	.info = snd_pcm_multi_info,
	.hw_refine = snd_pcm_multi_hw_refine,
	.hw_params = snd_pcm_multi_hw_params,
	.hw_free = snd_pcm_multi_hw_free,
	.sw_params = snd_pcm_multi_sw_params,
	.channel_info = snd_pcm_multi_channel_info,
	.dump = snd_pcm_multi_dump,
	.nonblock = snd_pcm_multi_nonblock,
	.async = snd_pcm_multi_async,
	.mmap = snd_pcm_multi_mmap,
	.munmap = snd_pcm_multi_munmap,
};

static snd_pcm_fast_ops_t snd_pcm_multi_fast_ops = {
	.status = snd_pcm_multi_status,
	.state = snd_pcm_multi_state,
	.hwsync = snd_pcm_multi_hwsync,
	.delay = snd_pcm_multi_delay,
	.prepare = snd_pcm_multi_prepare,
	.reset = snd_pcm_multi_reset,
	.start = snd_pcm_multi_start,
	.drop = snd_pcm_multi_drop,
	.drain = snd_pcm_multi_drain,
	.pause = snd_pcm_multi_pause,
	.writei = snd_pcm_mmap_writei,
	.writen = snd_pcm_mmap_writen,
	.readi = snd_pcm_mmap_readi,
	.readn = snd_pcm_mmap_readn,
	.rewind = snd_pcm_multi_rewind,
	.forward = snd_pcm_multi_forward,
	.resume = snd_pcm_multi_resume,
	.link_fd = snd_pcm_multi_link_fd,
	.link = snd_pcm_generic_link2,
	.unlink = snd_pcm_multi_unlink,
	.avail_update = snd_pcm_multi_avail_update,
	.mmap_commit = snd_pcm_multi_mmap_commit,
	.poll_descriptors_count = snd_pcm_multi_poll_descriptors_count,
	.poll_descriptors = snd_pcm_multi_poll_descriptors,
	.poll_revents = snd_pcm_multi_poll_revents,
};

/**
 * \brief Creates a new Multi PCM
 * \param pcmp Returns created PCM handle
 * \param name Name of PCM
 * \param slaves_count Count of slaves
 * \param master_slave Master slave number
 * \param slaves_pcm Array with slave PCMs
 * \param schannels_count Array with slave channel counts
 * \param channels_count Count of channels
 * \param sidxs Array with channels indexes to slaves
 * \param schannels Array with slave channels
 * \param close_slaves When set, the slave PCM handle is closed
 * \retval zero on success otherwise a negative error code
 * \warning Using of this function might be dangerous in the sense
 *          of compatibility reasons. The prototype might be freely
 *          changed in future.
 */
int snd_pcm_multi_open(snd_pcm_t **pcmp, const char *name,
		       unsigned int slaves_count, unsigned int master_slave,
		       snd_pcm_t **slaves_pcm, unsigned int *schannels_count,
		       unsigned int channels_count,
		       int *sidxs, unsigned int *schannels,
		       int close_slaves)
{
	snd_pcm_t *pcm;
	snd_pcm_multi_t *multi;
	unsigned int i;
	snd_pcm_stream_t stream;
	char slave_map[32][32] = { { 0 } };
	int err;

	assert(pcmp);
	assert(slaves_count > 0 && slaves_pcm && schannels_count);
	assert(channels_count > 0 && sidxs && schannels);
	assert(master_slave < slaves_count);

	multi = calloc(1, sizeof(snd_pcm_multi_t));
	if (!multi) {
		return -ENOMEM;
	}

	stream = slaves_pcm[0]->stream;<commit_msg>pcm_multi: Increased maximum slave channels to 64 From: <NAME> <<EMAIL>><commit_after>/**
 * \file pcm/pcm_multi.c
 * \ingroup PCM_Plugins
 * \brief PCM Multi Streams to One Conversion Plugin Interface
 * \author <NAME> <<EMAIL>>
 * \date 2000-2001
 */
/*
 *  PCM - Multi
 *  Copyright (c) 2000 by <NAME> <<EMAIL>>
 *
 *
 *   This library is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU Lesser General Public License as
 *   published by the Free Software Foundation; either version 2.1 of
 *   the License, or (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU Lesser General Public License for more details.
 *
 *   You should have received a copy of the GNU Lesser General Public
 *   License along with this library; if not, write to the Free Software
 *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
 *
 */
  
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <math.h>
#include "pcm_local.h"
#include "pcm_generic.h"

#ifndef PIC
/* entry for static linking */
const char *_snd_module_pcm_multi = "";
#endif

#ifndef DOC_HIDDEN

typedef struct {
	snd_pcm_t *pcm;
	unsigned int channels_count;
	int close_slave;
	int linked;
} snd_pcm_multi_slave_t;

typedef struct {
	int slave_idx;
	unsigned int slave_channel;
} snd_pcm_multi_channel_t;

typedef struct {
	unsigned int slaves_count;
	unsigned int master_slave;
	snd_pcm_multi_slave_t *slaves;
	int slave_link_master;
	unsigned int channels_count;
	snd_pcm_multi_channel_t *channels;
} snd_pcm_multi_t;

#endif

static int snd_pcm_multi_close(snd_pcm_t *pcm)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	unsigned int i;
	int ret = 0;
	for (i = 0; i < multi->slaves_count; ++i) {
		snd_pcm_multi_slave_t *slave = &multi->slaves[i];
		if (slave->close_slave) {
			int err = snd_pcm_close(slave->pcm);
			if (err < 0)
				ret = err;
		}
	}
	free(multi->slaves);
	free(multi->channels);
	free(multi);
	return ret;
}

static int snd_pcm_multi_nonblock(snd_pcm_t *pcm ATTRIBUTE_UNUSED, int nonblock ATTRIBUTE_UNUSED)
{
	return 0;
}

static int snd_pcm_multi_async(snd_pcm_t *pcm, int sig, pid_t pid)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	snd_pcm_t *slave_0 = multi->slaves[multi->master_slave].pcm;
	return snd_pcm_async(slave_0, sig, pid);
}

static int snd_pcm_multi_poll_descriptors_count(snd_pcm_t *pcm)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	snd_pcm_t *slave_0 = multi->slaves[multi->master_slave].pcm;
	return snd_pcm_poll_descriptors_count(slave_0);
}

static int snd_pcm_multi_poll_descriptors(snd_pcm_t *pcm, struct pollfd *pfds, unsigned int space)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	snd_pcm_t *slave;
	snd_pcm_t *slave_0 = multi->slaves[multi->master_slave].pcm;
	int err;
	unsigned int i;

	for (i = 0; i < multi->slaves_count; ++i) {
		slave = multi->slaves[i].pcm;
		if (slave == slave_0)
			continue;
		err = snd_pcm_poll_descriptors(slave, pfds, space);
		if (err < 0)
			return err;
	}
	/* finally overwrite with master's pfds */
	return snd_pcm_poll_descriptors(slave_0, pfds, space);
}

static int snd_pcm_multi_poll_revents(snd_pcm_t *pcm, struct pollfd *pfds, unsigned int nfds, unsigned short *revents)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	snd_pcm_t *slave_0 = multi->slaves[multi->master_slave].pcm;
	return snd_pcm_poll_descriptors_revents(slave_0, pfds, nfds, revents);
}

static int snd_pcm_multi_info(snd_pcm_t *pcm, snd_pcm_info_t *info)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	int err, n;
	assert(info->subdevice < multi->slaves_count);
	n = info->subdevice;
	info->subdevice = 0;
	err = snd_pcm_info(multi->slaves[n].pcm, info);
	if (err < 0)
		return err;
	info->subdevices_count = multi->slaves_count;
	return 0;
}

static int snd_pcm_multi_hw_refine_cprepare(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	snd_pcm_access_mask_t access_mask;
	int err;
	snd_pcm_access_mask_any(&access_mask);
	snd_pcm_access_mask_reset(&access_mask, SND_PCM_ACCESS_MMAP_INTERLEAVED);
	err = _snd_pcm_hw_param_set_mask(params, SND_PCM_HW_PARAM_ACCESS,
					 &access_mask);
	if (err < 0)
		return err;
	err = _snd_pcm_hw_param_set(params, SND_PCM_HW_PARAM_CHANNELS,
				    multi->channels_count, 0);
	if (err < 0)
		return err;
	params->info = ~0U;
	return 0;
}

static int snd_pcm_multi_hw_refine_sprepare(snd_pcm_t *pcm, unsigned int slave_idx,
					    snd_pcm_hw_params_t *sparams)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	snd_pcm_multi_slave_t *slave = &multi->slaves[slave_idx];
	snd_pcm_access_mask_t saccess_mask = { SND_PCM_ACCBIT_MMAP };
	_snd_pcm_hw_params_any(sparams);
	_snd_pcm_hw_param_set_mask(sparams, SND_PCM_HW_PARAM_ACCESS,
				   &saccess_mask);
	_snd_pcm_hw_param_set(sparams, SND_PCM_HW_PARAM_CHANNELS,
			      slave->channels_count, 0);
	return 0;
}

static int snd_pcm_multi_hw_refine_schange(snd_pcm_t *pcm ATTRIBUTE_UNUSED,
					   unsigned int slave_idx ATTRIBUTE_UNUSED,
					   snd_pcm_hw_params_t *params,
					   snd_pcm_hw_params_t *sparams)
{
	int err;
	unsigned int links = (SND_PCM_HW_PARBIT_FORMAT |
			      SND_PCM_HW_PARBIT_SUBFORMAT |
			      SND_PCM_HW_PARBIT_RATE |
			      SND_PCM_HW_PARBIT_PERIOD_SIZE |
			      SND_PCM_HW_PARBIT_PERIOD_TIME |
			      SND_PCM_HW_PARBIT_PERIODS |
			      SND_PCM_HW_PARBIT_BUFFER_SIZE |
			      SND_PCM_HW_PARBIT_BUFFER_TIME |
			      SND_PCM_HW_PARBIT_TICK_TIME);
	const snd_pcm_access_mask_t *access_mask = snd_pcm_hw_param_get_mask(params, SND_PCM_HW_PARAM_ACCESS);
	if (!snd_pcm_access_mask_test(access_mask, SND_PCM_ACCESS_RW_INTERLEAVED) &&
	    !snd_pcm_access_mask_test(access_mask, SND_PCM_ACCESS_RW_NONINTERLEAVED) &&
	    !snd_pcm_access_mask_test(access_mask, SND_PCM_ACCESS_MMAP_NONINTERLEAVED)) {
		snd_pcm_access_mask_t saccess_mask;
		snd_pcm_access_mask_any(&saccess_mask);
		snd_pcm_access_mask_reset(&saccess_mask, SND_PCM_ACCESS_MMAP_NONINTERLEAVED);
		err = _snd_pcm_hw_param_set_mask(sparams, SND_PCM_HW_PARAM_ACCESS,
						 &saccess_mask);
		if (err < 0)
			return err;
	}
	err = _snd_pcm_hw_params_refine(sparams, links, params);
	if (err < 0)
		return err;
	return 0;
}
	
static int snd_pcm_multi_hw_refine_cchange(snd_pcm_t *pcm ATTRIBUTE_UNUSED,
					   unsigned int slave_idx ATTRIBUTE_UNUSED,
					   snd_pcm_hw_params_t *params,
					   snd_pcm_hw_params_t *sparams)
{
	int err;
	unsigned int links = (SND_PCM_HW_PARBIT_FORMAT |
			      SND_PCM_HW_PARBIT_SUBFORMAT |
			      SND_PCM_HW_PARBIT_RATE |
			      SND_PCM_HW_PARBIT_PERIOD_SIZE |
			      SND_PCM_HW_PARBIT_PERIOD_TIME |
			      SND_PCM_HW_PARBIT_PERIODS |
			      SND_PCM_HW_PARBIT_BUFFER_SIZE |
			      SND_PCM_HW_PARBIT_BUFFER_TIME |
			      SND_PCM_HW_PARBIT_TICK_TIME);
	snd_pcm_access_mask_t access_mask;
	const snd_pcm_access_mask_t *saccess_mask = snd_pcm_hw_param_get_mask(sparams, SND_PCM_HW_PARAM_ACCESS);
	snd_pcm_access_mask_any(&access_mask);
	snd_pcm_access_mask_reset(&access_mask, SND_PCM_ACCESS_MMAP_INTERLEAVED);
	if (!snd_pcm_access_mask_test(saccess_mask, SND_PCM_ACCESS_MMAP_NONINTERLEAVED))
		snd_pcm_access_mask_reset(&access_mask, SND_PCM_ACCESS_MMAP_NONINTERLEAVED);
	if (!snd_pcm_access_mask_test(saccess_mask, SND_PCM_ACCESS_MMAP_COMPLEX) &&
	    !snd_pcm_access_mask_test(saccess_mask, SND_PCM_ACCESS_MMAP_INTERLEAVED))
		snd_pcm_access_mask_reset(&access_mask, SND_PCM_ACCESS_MMAP_COMPLEX);
	err = _snd_pcm_hw_param_set_mask(params, SND_PCM_HW_PARAM_ACCESS,
					 &access_mask);
	if (err < 0)
		return err;
	err = _snd_pcm_hw_params_refine(params, links, sparams);
	if (err < 0)
		return err;
	params->info &= sparams->info;
	return 0;
}

static int snd_pcm_multi_hw_refine_slave(snd_pcm_t *pcm,
					 unsigned int slave_idx,
					 snd_pcm_hw_params_t *sparams)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	snd_pcm_t *slave = multi->slaves[slave_idx].pcm;
	return snd_pcm_hw_refine(slave, sparams);
}

static int snd_pcm_multi_hw_refine(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	unsigned int k;
	snd_pcm_hw_params_t sparams[multi->slaves_count];
	int err;
	unsigned int cmask, changed;
	err = snd_pcm_multi_hw_refine_cprepare(pcm, params);
	if (err < 0)
		return err;
	for (k = 0; k < multi->slaves_count; ++k) {
		err = snd_pcm_multi_hw_refine_sprepare(pcm, k, &sparams[k]);
		if (err < 0) {
			SNDERR("Slave PCM #%d not usable", k);
			return err;
		}
	}
	do {
		cmask = params->cmask;
		params->cmask = 0;
		for (k = 0; k < multi->slaves_count; ++k) {
			err = snd_pcm_multi_hw_refine_schange(pcm, k, params, &sparams[k]);
			if (err >= 0)
				err = snd_pcm_multi_hw_refine_slave(pcm, k, &sparams[k]);
			if (err < 0) {
				snd_pcm_multi_hw_refine_cchange(pcm, k, params, &sparams[k]);
				return err;
			}
			err = snd_pcm_multi_hw_refine_cchange(pcm, k, params, &sparams[k]);
			if (err < 0)
				return err;
		}
		err = snd_pcm_hw_refine_soft(pcm, params);
		changed = params->cmask;
		params->cmask |= cmask;
		if (err < 0)
			return err;
	} while (changed);
	return 0;
}

static int snd_pcm_multi_hw_params_slave(snd_pcm_t *pcm,
					 unsigned int slave_idx,
					 snd_pcm_hw_params_t *sparams)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	snd_pcm_t *slave = multi->slaves[slave_idx].pcm;
	int err = snd_pcm_hw_params(slave, sparams);
	if (err < 0)
		return err;
	err = snd_pcm_areas_silence(slave->running_areas, 0, slave->channels, slave->buffer_size, slave->format);
	if (err < 0)
		return err;
	if (slave->stopped_areas) {
		err = snd_pcm_areas_silence(slave->stopped_areas, 0, slave->channels, slave->buffer_size, slave->format);
		if (err < 0)
			return err;
	}
	return 0;
}

static int snd_pcm_multi_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	unsigned int i;
	snd_pcm_hw_params_t sparams[multi->slaves_count];
	int err;
	for (i = 0; i < multi->slaves_count; ++i) {
		err = snd_pcm_multi_hw_refine_sprepare(pcm, i, &sparams[i]);
		assert(err >= 0);
		err = snd_pcm_multi_hw_refine_schange(pcm, i, params, &sparams[i]);
		assert(err >= 0);
		err = snd_pcm_multi_hw_params_slave(pcm, i, &sparams[i]);
		if (err < 0) {
			snd_pcm_multi_hw_refine_cchange(pcm, i, params, &sparams[i]);
			return err;
		}
	}
	multi->slaves[0].linked = 0;
	multi->slave_link_master = 0;
	for (i = 1; i < multi->slaves_count; ++i) {
		err = snd_pcm_link(multi->slaves[0].pcm, multi->slaves[i].pcm);
		multi->slaves[i].linked = (err >= 0);
	}
	return 0;
}

static int snd_pcm_multi_hw_free(snd_pcm_t *pcm)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	unsigned int i;
	int err = 0;
	for (i = 0; i < multi->slaves_count; ++i) {
		snd_pcm_t *slave = multi->slaves[i].pcm;
		int e = snd_pcm_hw_free(slave);
		if (e < 0)
			err = e;
		if (!multi->slaves[i].linked)
			continue;
		multi->slaves[i].linked = 0;
		e = snd_pcm_unlink(slave);
		if (e < 0)
			err = e;
	}
	return err;
}

static int snd_pcm_multi_sw_params(snd_pcm_t *pcm, snd_pcm_sw_params_t *params)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	unsigned int i;
	int err;
	for (i = 0; i < multi->slaves_count; ++i) {
		snd_pcm_t *slave = multi->slaves[i].pcm;
		err = snd_pcm_sw_params(slave, params);
		if (err < 0)
			return err;
	}
	return 0;
}

static int snd_pcm_multi_status(snd_pcm_t *pcm, snd_pcm_status_t *status)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	snd_pcm_t *slave = multi->slaves[multi->master_slave].pcm;
	return snd_pcm_status(slave, status);
}

static snd_pcm_state_t snd_pcm_multi_state(snd_pcm_t *pcm)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	snd_pcm_t *slave = multi->slaves[multi->master_slave].pcm;
	return snd_pcm_state(slave);
}

static int snd_pcm_multi_hwsync(snd_pcm_t *pcm)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	snd_pcm_t *slave = multi->slaves[multi->master_slave].pcm;
	return snd_pcm_hwsync(slave);
}

static int snd_pcm_multi_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	snd_pcm_t *slave = multi->slaves[multi->master_slave].pcm;
	return snd_pcm_delay(slave, delayp);
}

static snd_pcm_sframes_t snd_pcm_multi_avail_update(snd_pcm_t *pcm)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	snd_pcm_sframes_t ret = LONG_MAX;
	unsigned int i;
	for (i = 0; i < multi->slaves_count; ++i) {
		snd_pcm_sframes_t avail;
		avail = snd_pcm_avail_update(multi->slaves[i].pcm);
		if (avail < 0)
			return avail;
		if (ret > avail)
			ret = avail;
	}
	return ret;
}

static int snd_pcm_multi_prepare(snd_pcm_t *pcm)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	int err = 0;
	unsigned int i;
	for (i = 0; i < multi->slaves_count; ++i) {
		if (multi->slaves[i].linked)
			continue;
		err = snd_pcm_prepare(multi->slaves[i].pcm);
		if (err < 0)
			return err;
	}
	return err;
}

static int snd_pcm_multi_reset(snd_pcm_t *pcm)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	int err = 0;
	unsigned int i;
	for (i = 0; i < multi->slaves_count; ++i) {
		if (multi->slaves[i].linked)
			continue;
		err = snd_pcm_reset(multi->slaves[i].pcm);
		if (err < 0)
			return err;
	}
	return err;
}

static int snd_pcm_multi_start(snd_pcm_t *pcm)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	int err = 0;
	unsigned int i;
	for (i = 0; i < multi->slaves_count; ++i) {
		if (multi->slaves[i].linked)
			continue;
		err = snd_pcm_start(multi->slaves[i].pcm);
		if (err < 0)
			return err;
	}
	return err;
}

static int snd_pcm_multi_drop(snd_pcm_t *pcm)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	int err = 0;
	unsigned int i;
	for (i = 0; i < multi->slaves_count; ++i) {
		if (multi->slaves[i].linked)
			continue;
		err = snd_pcm_drop(multi->slaves[i].pcm);
		if (err < 0)
			return err;
	}
	return err;
}

static int snd_pcm_multi_drain(snd_pcm_t *pcm)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	int err = 0;
	unsigned int i;
	for (i = 0; i < multi->slaves_count; ++i) {
		if (multi->slaves[i].linked)
			continue;
		err = snd_pcm_drain(multi->slaves[i].pcm);
		if (err < 0)
			return err;
	}
	return err;
}

static int snd_pcm_multi_pause(snd_pcm_t *pcm, int enable)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	int err = 0;
	unsigned int i;
	for (i = 0; i < multi->slaves_count; ++i) {
		if (multi->slaves[i].linked)
			continue;
		err = snd_pcm_pause(multi->slaves[i].pcm, enable);
		if (err < 0)
			return err;
	}
	return err;
}

static int snd_pcm_multi_channel_info(snd_pcm_t *pcm, snd_pcm_channel_info_t *info)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	unsigned int channel = info->channel;
	snd_pcm_multi_channel_t *c = &multi->channels[channel];
	int err;
	if (c->slave_idx < 0)
		return -ENXIO;
	info->channel = c->slave_channel;
	err = snd_pcm_channel_info(multi->slaves[c->slave_idx].pcm, info);
	info->channel = channel;
	return err;
}

static snd_pcm_sframes_t snd_pcm_multi_rewind(snd_pcm_t *pcm, snd_pcm_uframes_t frames)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	unsigned int i;
	snd_pcm_uframes_t pos[multi->slaves_count];
	memset(pos, 0, sizeof(pos));
	for (i = 0; i < multi->slaves_count; ++i) {
		snd_pcm_t *slave_i = multi->slaves[i].pcm;
		snd_pcm_sframes_t f = snd_pcm_rewind(slave_i, frames);
		if (f < 0)
			return f;
		pos[i] = f;
		frames = f;
	}
	/* Realign the pointers */
	for (i = 0; i < multi->slaves_count; ++i) {
		snd_pcm_t *slave_i = multi->slaves[i].pcm;
		snd_pcm_uframes_t f = pos[i] - frames;
		snd_pcm_sframes_t result;
		if (f > 0) {
			result = INTERNAL(snd_pcm_forward)(slave_i, f);
			if (result < 0)
				return result;
			if ((snd_pcm_uframes_t)result != f)
				return -EIO;
		}
	}
	return frames;
}

static snd_pcm_sframes_t snd_pcm_multi_forward(snd_pcm_t *pcm, snd_pcm_uframes_t frames)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	unsigned int i;
	snd_pcm_uframes_t pos[multi->slaves_count];
	memset(pos, 0, sizeof(pos));
	for (i = 0; i < multi->slaves_count; ++i) {
		snd_pcm_t *slave_i = multi->slaves[i].pcm;
		snd_pcm_sframes_t f = INTERNAL(snd_pcm_forward)(slave_i, frames);
		if (f < 0)
			return f;
		pos[i] = f;
		frames = f;
	}
	/* Realign the pointers */
	for (i = 0; i < multi->slaves_count; ++i) {
		snd_pcm_t *slave_i = multi->slaves[i].pcm;
		snd_pcm_uframes_t f = pos[i] - frames;
		snd_pcm_sframes_t result;
		if (f > 0) {
			result = snd_pcm_rewind(slave_i, f);
			if (result < 0)
				return result;
			if ((snd_pcm_uframes_t)result != f)
				return -EIO;
		}
	}
	return frames;
}

static int snd_pcm_multi_resume(snd_pcm_t *pcm)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	int err = 0;
	unsigned int i;
	for (i = 0; i < multi->slaves_count; ++i) {
		if (multi->slaves[i].linked)
			continue;
		err = snd_pcm_resume(multi->slaves[i].pcm);
		if (err < 0)
			return err;
	}
	return err;
}

static int snd_pcm_multi_link_fd_failed(snd_pcm_t *pcm, int fd)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	unsigned int i;

	for (i = 0; i < multi->slaves_count; ++i) {
		if (_snd_pcm_link_descriptor(multi->slaves[i].pcm) != fd)
			continue;
		 multi->slaves[i].linked = 0;
	}
	return 0;
}

static int snd_pcm_multi_link_fd(snd_pcm_t *pcm, int *fds, int count, int (**failed)(snd_pcm_t *pcm, int fd))
{ 
	snd_pcm_multi_t *multi = pcm->private_data;
	unsigned int i;

	if (count < (int)multi->slaves_count)
		return -ENOMEM;
	for (i = 0; i < multi->slaves_count; ++i) {
		if (multi->slaves[i].linked)
			snd_pcm_unlink(multi->slaves[i].pcm);
		fds[i] = _snd_pcm_link_descriptor(multi->slaves[i].pcm);
		multi->slaves[i].linked = 1;
	}
	*failed = snd_pcm_multi_link_fd_failed;
	return multi->slaves_count;
}

static int snd_pcm_multi_unlink(snd_pcm_t *pcm)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	unsigned int i;

	for (i = 0; i < multi->slaves_count; ++i) {
		if (multi->slaves[i].linked)
			snd_pcm_unlink(multi->slaves[i].pcm);
		multi->slaves[i].linked = 0;
	}
	return 0;
}

static snd_pcm_sframes_t snd_pcm_multi_mmap_commit(snd_pcm_t *pcm,
						   snd_pcm_uframes_t offset,
						   snd_pcm_uframes_t size)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	snd_pcm_t *slave;
	unsigned int i;
	snd_pcm_sframes_t result;

	for (i = 0; i < multi->slaves_count; ++i) {
		slave = multi->slaves[i].pcm;
		result = snd_pcm_mmap_commit(slave, offset, size);
		if (result < 0)
			return result;
		if ((snd_pcm_uframes_t)result != size)
			return -EIO;
	}
	return size;
}

static int snd_pcm_multi_mmap(snd_pcm_t *pcm ATTRIBUTE_UNUSED)
{
	return 0;
}

static int snd_pcm_multi_munmap(snd_pcm_t *pcm ATTRIBUTE_UNUSED)
{
	return 0;
}

static void snd_pcm_multi_dump(snd_pcm_t *pcm, snd_output_t *out)
{
	snd_pcm_multi_t *multi = pcm->private_data;
	unsigned int k;
	snd_output_printf(out, "Multi PCM\n");
	snd_output_printf(out, "\nChannel bindings:\n");
	for (k = 0; k < multi->channels_count; ++k) {
		snd_pcm_multi_channel_t *c = &multi->channels[k];
		if (c->slave_idx < 0)
			continue;
		snd_output_printf(out, "%d: slave %d, channel %d\n", 
			k, c->slave_idx, c->slave_channel);
	}
	if (pcm->setup) {
		snd_output_printf(out, "\nIts setup is:\n");
		snd_pcm_dump_setup(pcm, out);
	}
	for (k = 0; k < multi->slaves_count; ++k) {
		snd_output_printf(out, "\nSlave #%d: ", k);
		snd_pcm_dump(multi->slaves[k].pcm, out);
	}
}

static snd_pcm_ops_t snd_pcm_multi_ops = {
	.close = snd_pcm_multi_close,
	.info = snd_pcm_multi_info,
	.hw_refine = snd_pcm_multi_hw_refine,
	.hw_params = snd_pcm_multi_hw_params,
	.hw_free = snd_pcm_multi_hw_free,
	.sw_params = snd_pcm_multi_sw_params,
	.channel_info = snd_pcm_multi_channel_info,
	.dump = snd_pcm_multi_dump,
	.nonblock = snd_pcm_multi_nonblock,
	.async = snd_pcm_multi_async,
	.mmap = snd_pcm_multi_mmap,
	.munmap = snd_pcm_multi_munmap,
};

static snd_pcm_fast_ops_t snd_pcm_multi_fast_ops = {
	.status = snd_pcm_multi_status,
	.state = snd_pcm_multi_state,
	.hwsync = snd_pcm_multi_hwsync,
	.delay = snd_pcm_multi_delay,
	.prepare = snd_pcm_multi_prepare,
	.reset = snd_pcm_multi_reset,
	.start = snd_pcm_multi_start,
	.drop = snd_pcm_multi_drop,
	.drain = snd_pcm_multi_drain,
	.pause = snd_pcm_multi_pause,
	.writei = snd_pcm_mmap_writei,
	.writen = snd_pcm_mmap_writen,
	.readi = snd_pcm_mmap_readi,
	.readn = snd_pcm_mmap_readn,
	.rewind = snd_pcm_multi_rewind,
	.forward = snd_pcm_multi_forward,
	.resume = snd_pcm_multi_resume,
	.link_fd = snd_pcm_multi_link_fd,
	.link = snd_pcm_generic_link2,
	.unlink = snd_pcm_multi_unlink,
	.avail_update = snd_pcm_multi_avail_update,
	.mmap_commit = snd_pcm_multi_mmap_commit,
	.poll_descriptors_count = snd_pcm_multi_poll_descriptors_count,
	.poll_descriptors = snd_pcm_multi_poll_descriptors,
	.poll_revents = snd_pcm_multi_poll_revents,
};

/**
 * \brief Creates a new Multi PCM
 * \param pcmp Returns created PCM handle
 * \param name Name of PCM
 * \param slaves_count Count of slaves
 * \param master_slave Master slave number
 * \param slaves_pcm Array with slave PCMs
 * \param schannels_count Array with slave channel counts
 * \param channels_count Count of channels
 * \param sidxs Array with channels indexes to slaves
 * \param schannels Array with slave channels
 * \param close_slaves When set, the slave PCM handle is closed
 * \retval zero on success otherwise a negative error code
 * \warning Using of this function might be dangerous in the sense
 *          of compatibility reasons. The prototype might be freely
 *          changed in future.
 */
int snd_pcm_multi_open(snd_pcm_t **pcmp, const char *name,
		       unsigned int slaves_count, unsigned int master_slave,
		       snd_pcm_t **slaves_pcm, unsigned int *schannels_count,
		       unsigned int channels_count,
		       int *sidxs, unsigned int *schannels,
		       int close_slaves)
{
	snd_pcm_t *pcm;
	snd_pcm_multi_t *multi;
	unsigned int i;
	snd_pcm_stream_t stream;
	char slave_map[64][64] = { { 0 } };
	int err;

	assert(pcmp);
	assert(slaves_count > 0 && slaves_pcm && schannels_count);
	assert(channels_count > 0 && sidxs && schannels);
	assert(master_slave < slaves_count);

	multi = calloc(1, sizeof(snd_pcm_multi_t));
	if (!multi) {
		return -ENOMEM;
	}

	stream = slaves_pcm[0]->stream;
{'fraction_non_alphanumeric': 0.11589216596120329, 'fraction_numerical': 0.007062178863260825, 'mean_word_length': 4.585335018963337, 'pattern_counts': {'":': 0, '<': 158, '<?xml version=': 0, '>': 384, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1262379', 'n_tokens_mistral': 19114, 'n_tokens_neox': 18077, 'n_words': 3708}