1 /*************************************************************************
2        ChannelMixer.cpp  -  matrix based mixer for multiple channels
3                              -------------------
4     begin                : Sun Oct 10 2010
5     copyright            : (C) 2010 by Thomas Eschenbacher
6     email                : Thomas.Eschenbacher@gmx.de
7  ***************************************************************************/
8 
9 /***************************************************************************
10  *                                                                         *
11  *   This program is free software; you can redistribute it and/or modify  *
12  *   it under the terms of the GNU General Public License as published by  *
13  *   the Free Software Foundation; either version 2 of the License, or     *
14  *   (at your option) any later version.                                   *
15  *                                                                         *
16  ***************************************************************************/
17 
18 #include "config.h"
19 
20 #include <limits>
21 #include <new>
22 
23 #include <QtGlobal>
24 #include <QByteArray>
25 #include <QMetaObject>
26 #include <QMutexLocker>
27 #include <QObject>
28 #include <QVarLengthArray>
29 
30 #include "libkwave/MixerMatrix.h"
31 #include "libkwave/Sample.h"
32 #include "libkwave/Utils.h"
33 #include "libkwave/modules/ChannelMixer.h"
34 #include "libkwave/modules/Indexer.h"
35 #include "libkwave/modules/StreamObject.h"
36 
37 //***************************************************************************
ChannelMixer(unsigned int inputs,unsigned int outputs)38 Kwave::ChannelMixer::ChannelMixer(unsigned int inputs, unsigned int outputs)
39     :Kwave::SampleSource(),
40      m_matrix(Q_NULLPTR),
41      m_inputs(inputs),
42      m_outputs(outputs),
43      m_indexer(),
44      m_input_queue(),
45      m_output_buffer(),
46      m_lock()
47 {
48 }
49 
50 //***************************************************************************
init()51 bool Kwave::ChannelMixer::init()
52 {
53     if (!m_inputs || !m_outputs) return false;
54 
55     // create queues for the input data
56     m_input_queue.resize(m_inputs);
57     Q_ASSERT(m_input_queue.count() == Kwave::toInt(m_inputs));
58     if (m_input_queue.count() != Kwave::toInt(m_inputs)) return false;
59 
60     // create the buffers for the output data
61     for (unsigned int index = 0; index < m_outputs; ++index) {
62 	// create a buffer for the input
63 	Kwave::SampleBuffer *out_buffer =
64 	    new(std::nothrow) Kwave::SampleBuffer();
65 	Q_ASSERT(out_buffer);
66 	if (!out_buffer) return false;
67 	m_output_buffer.append(out_buffer);
68     }
69 
70     // create indexing proxies and connect their output to this mixer
71     for (unsigned int index = 0; index < m_inputs; ++index) {
72 	Kwave::StreamObject *indexer =
73 	    new(std::nothrow) Kwave::Indexer(index);
74 	Q_ASSERT(indexer);
75 	if (!indexer) return false;
76 
77 	m_indexer.append(indexer);
78 	bool ok = Kwave::connect(
79 	    *indexer, SIGNAL(output(uint,Kwave::SampleArray)),
80 	    *this,    SLOT(idxInput(uint,Kwave::SampleArray)));
81 	Q_ASSERT(ok);
82 	if (!ok) return false;
83     }
84 
85     // create the mixer matrix
86     // create a translation matrix for mixing up/down to the desired
87     // number of output channels
88     m_matrix = new(std::nothrow) Kwave::MixerMatrix(m_inputs, m_outputs);
89     Q_ASSERT(m_matrix);
90     if (!m_matrix) return false;
91 
92     // everything succeeded
93     return true;
94 }
95 
96 //***************************************************************************
~ChannelMixer()97 Kwave::ChannelMixer::~ChannelMixer()
98 {
99     QMutexLocker _lock(&m_lock);
100 
101     while (!m_indexer.isEmpty()) {
102 	Kwave::StreamObject *indexer = m_indexer[0];
103 	if (indexer) delete indexer;
104 	m_indexer.remove(0);
105     }
106 
107     m_input_queue.clear();
108 
109     while (!m_output_buffer.isEmpty()) {
110 	Kwave::SampleBuffer *buffer = m_output_buffer[0];
111 	if (buffer) delete buffer;
112 	m_output_buffer.remove(0);
113     }
114 }
115 
116 //***************************************************************************
_sig(const char * sig)117 static inline QByteArray _sig(const char *sig)
118 {
119     return QMetaObject::normalizedSignature(sig);
120 }
121 
122 //***************************************************************************
tracksOfPort(const char * port) const123 unsigned int Kwave::ChannelMixer::tracksOfPort(const char *port) const
124 {
125     unsigned int retval = 0;
126     QMutexLocker _lock(const_cast<QMutex *>(&m_lock));
127 
128     if (_sig(port) == _sig(SLOT(input(Kwave::SampleArray)))) {
129 	// input ports
130 	retval = m_inputs; // init is done
131     } else if (_sig(port) == _sig(SIGNAL(output(Kwave::SampleArray)))) {
132 	// output ports
133 	retval = m_outputs;
134     } else if (_sig(port) ==
135                _sig(SLOT(idxInput(uint,Kwave::SampleArray)))) {
136 	retval = 1;
137     } else {
138 	qFatal("unknown port");
139     }
140 
141     return retval;
142 }
143 
144 //***************************************************************************
port(const char * port,unsigned int track)145 Kwave::StreamObject *Kwave::ChannelMixer::port(const char *port,
146                                                unsigned int track)
147 {
148     Kwave::StreamObject *retval = Q_NULLPTR;
149     QMutexLocker _lock(&m_lock);
150 
151     if (_sig(port) == _sig(SLOT(input(Kwave::SampleArray)))) {
152 	// input proxy
153 	Q_ASSERT(Kwave::toInt(track) < m_indexer.count());
154         if (Kwave::toInt(track) >= m_indexer.count()) return Q_NULLPTR;
155 	retval = m_indexer.at(track);
156     } else if (_sig(port) == _sig(SIGNAL(output(Kwave::SampleArray)))) {
157 	// output proxy
158 	Q_ASSERT(Kwave::toInt(track) < m_output_buffer.count());
159         if (Kwave::toInt(track) >= m_output_buffer.count()) return Q_NULLPTR;
160 	retval = m_output_buffer[track];
161     } else if (_sig(port) ==
162 	       _sig(SLOT(idxInput(uint,Kwave::SampleArray)))) {
163 	retval = this;
164     } else {
165 	qFatal("unknown port");
166     }
167 
168     return retval;
169 }
170 
171 //***************************************************************************
idxInput(unsigned int index,Kwave::SampleArray data)172 void Kwave::ChannelMixer::idxInput(unsigned int index, Kwave::SampleArray data)
173 {
174     QMutexLocker _lock(&m_lock);
175 
176     // put the data into the corresponding input queue
177     Q_ASSERT(index < m_inputs);
178     Q_ASSERT(Kwave::toInt(index) < m_input_queue.count());
179     if (Kwave::toInt(index) < m_input_queue.count())
180 	m_input_queue[index].enqueue(data);
181 
182     // check: if there is one empty queue we are not yet ready for mixing
183     bool ready = true;
184     foreach (const QQueue<Kwave::SampleArray> &queue, m_input_queue) {
185 	if (queue.isEmpty()) {
186 	    ready = false;
187 	    break;
188 	}
189     }
190 
191     // mix if we are ready
192     if (ready && m_matrix) mix();
193 }
194 
195 //***************************************************************************
mix()196 void Kwave::ChannelMixer::mix()
197 {
198     Q_ASSERT(m_matrix);
199 
200     // all inputs should contain a buffer, dequeue them into a vector
201     // and form an array of pointers to the raw data, for speeding up
202     QVector<Kwave::SampleArray> v_input(m_inputs);
203     QVarLengthArray<const sample_t *> input(m_inputs);
204     unsigned int min_len = std::numeric_limits<unsigned int>::max();
205     for (unsigned int track = 0; track < m_inputs; track++) {
206 	// dequeue the buffer with input data
207 	QQueue<Kwave::SampleArray> &queue = m_input_queue[track];
208 	Q_ASSERT(!queue.isEmpty());
209 	Kwave::SampleArray buffer = queue.dequeue();
210 	v_input[track] = buffer;
211 
212 	// get a pointer for quick access
213 	const sample_t *raw_data = v_input[track].constData();
214 	input[track] = raw_data;
215 
216 	// detect minimum input length
217 	min_len = qMin(min_len, buffer.size());
218     }
219     Q_ASSERT(min_len);
220     if (!min_len) return; // zero length buffer in the queue, data underrun?
221 
222     // make sure all output buffers are large enough
223     // and build an array of pointers to the raw data, for speeding up
224     QVarLengthArray<sample_t *> output(m_outputs);
225     for (unsigned int track = 0; track < m_outputs; track++) {
226 	Kwave::SampleBuffer *buffer = m_output_buffer[track];
227 	Q_ASSERT(buffer);
228 	if (!buffer) return;
229 	bool ok = true;
230 	if (buffer->constData().size() < min_len)
231 	    ok &= buffer->data().resize(min_len);
232 	if (!ok) {
233 	    qWarning("ChannelMixer: failed to increase buffer size to %u",
234 		     min_len);
235 	    return; // OOM ?
236 	}
237 	output[track] = buffer->data().data();
238     }
239 
240     // mix all channels together, using the mixer matrix
241     for (unsigned int y = 0; y < m_outputs; y++) {
242 	sample_t *out = output[y];
243 	for (unsigned int pos = 0; pos < min_len; pos++) {
244 	    double sum = 0.0;
245 	    for (unsigned int x = 0; x < m_inputs; x++) {
246 		const double f = (*m_matrix)[x][y];
247 		const double i = static_cast<double>(input[x][pos]);
248 		sum += (f * i);
249 	    }
250 	    out[pos] = static_cast<sample_t>(sum);
251 	}
252 
253 	// emit the output
254 	Kwave::SampleBuffer *out_buf = m_output_buffer[y];
255 	if (Q_UNLIKELY(out_buf->constData().size() > min_len)) {
256 	    bool ok = out_buf->data().resize(min_len);
257 	    Q_ASSERT(ok);
258 	    Q_UNUSED(ok)
259 	}
260 	out_buf->finished();
261     }
262 
263 }
264 
265 //***************************************************************************
266 //***************************************************************************
267