1 /*************************************************************************
2        RecordPlugin.cpp  -  plugin for recording audio data
3                              -------------------
4     begin                : Wed Jul 09 2003
5     copyright            : (C) 2003 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 <errno.h>
21 #include <math.h>
22 #include <stdlib.h>
23 
24 #include <new>
25 
26 #include <QApplication>
27 #include <QCursor>
28 #include <QDateTime>
29 #include <QList>
30 #include <QStringList>
31 #include <QVariant>
32 #include <QtGlobal>
33 
34 #include <KAboutData>
35 #include <KConfig>
36 #include <KConfigGroup>
37 #include <KSharedConfig>
38 #include <kxmlgui_version.h>
39 
40 #include "libkwave/Compression.h"
41 #include "libkwave/FileInfo.h"
42 #include "libkwave/InsertMode.h"
43 #include "libkwave/MessageBox.h"
44 #include "libkwave/PluginManager.h"
45 #include "libkwave/Sample.h"
46 #include "libkwave/SampleFIFO.h"
47 #include "libkwave/SampleFormat.h"
48 #include "libkwave/SignalManager.h"
49 #include "libkwave/String.h"
50 #include "libkwave/Utils.h"
51 #include "libkwave/Writer.h"
52 
53 #include "Record-ALSA.h"
54 #include "Record-OSS.h"
55 #include "Record-PulseAudio.h"
56 #include "Record-Qt.h"
57 #include "RecordDevice.h"
58 #include "RecordDialog.h"
59 #include "RecordPlugin.h"
60 #include "RecordThread.h"
61 #include "SampleDecoderLinear.h"
62 
KWAVE_PLUGIN(record,RecordPlugin)63 KWAVE_PLUGIN(record, RecordPlugin)
64 
65 #define OPEN_RETRY_TIME 1000 /**< time interval for trying to open [ms] */
66 
67 //***************************************************************************
68 Kwave::RecordPlugin::RecordPlugin(QObject *parent, const QVariantList &args)
69     :Kwave::Plugin(parent, args),
70      m_method(Kwave::RECORD_NONE),
71      m_device_name(),
72      m_controller(),
73      m_state(Kwave::REC_EMPTY),
74      m_device(Q_NULLPTR),
75      m_dialog(Q_NULLPTR),
76      m_thread(Q_NULLPTR),
77      m_decoder(Q_NULLPTR),
78      m_prerecording_queue(),
79      m_writers(Q_NULLPTR),
80      m_buffers_recorded(0),
81      m_inhibit_count(0),
82      m_trigger_value(),
83      m_retry_timer()
84 {
85     m_retry_timer.setSingleShot(true);
86     connect(&m_retry_timer, SIGNAL(timeout()),
87 	    this, SLOT(retryOpen()),
88 	    Qt::QueuedConnection
89    	);
90 }
91 
92 //***************************************************************************
~RecordPlugin()93 Kwave::RecordPlugin::~RecordPlugin()
94 {
95     Q_ASSERT(!m_dialog);
96     if (m_dialog) delete m_dialog;
97     m_dialog = Q_NULLPTR;
98 
99     Q_ASSERT(!m_thread);
100     if (m_thread) delete m_thread;
101     m_thread = Q_NULLPTR;
102 
103     Q_ASSERT(!m_decoder);
104     if (m_decoder) delete m_decoder;
105     m_decoder = Q_NULLPTR;
106 
107     if (m_device) delete m_device;
108     m_device = Q_NULLPTR;
109 }
110 
111 //***************************************************************************
setup(QStringList & previous_params)112 QStringList *Kwave::RecordPlugin::setup(QStringList &previous_params)
113 {
114     Kwave::RecordDialog::Mode mode = Kwave::RecordDialog::SETTINGS_DEFAULT;
115 
116     qDebug("RecordPlugin::setup(%s)", DBG(previous_params.join(_(","))));
117 
118     // if we have only one parameter, then we got called with a specific
119     // mode, e.g. "show format settings only"
120     if (previous_params.count() == 1) {
121 	const QString m = previous_params[0].toLower();
122 
123 	if (m == _("format"))
124 	    mode = Kwave::RecordDialog::SETTINGS_FORMAT;
125 	else if (m == _("source"))
126 	    mode = Kwave::RecordDialog::SETTINGS_SOURCE;
127 	else if (m == _("start_now"))
128 	    mode = Kwave::RecordDialog::START_RECORDING;
129 
130 	// get previous parameters for the setup dialog
131 	previous_params = manager().defaultParams(name());
132 	qDebug("RecordPlugin::setup(%s) - MODE=%d",
133 	       DBG(previous_params.join(_(","))), static_cast<int>(mode));
134     }
135 
136     // create the setup dialog
137     m_dialog = new(std::nothrow) Kwave::RecordDialog(
138 	parentWidget(), previous_params, &m_controller, mode
139     );
140     Q_ASSERT(m_dialog);
141     if (!m_dialog) return Q_NULLPTR;
142 
143     // create the lowlevel recording thread
144     m_thread = new(std::nothrow) Kwave::RecordThread();
145     Q_ASSERT(m_thread);
146     if (!m_thread) {
147 	delete m_dialog;
148         m_dialog = Q_NULLPTR;
149         return Q_NULLPTR;
150     }
151 
152     // connect some signals of the setup dialog
153     connect(m_dialog, SIGNAL(sigMethodChanged(Kwave::record_method_t)),
154             this,     SLOT(setMethod(Kwave::record_method_t)));
155     connect(m_dialog, SIGNAL(sigDeviceChanged(QString)),
156             this,     SLOT(setDevice(QString)));
157 
158     connect(m_dialog, SIGNAL(sigTracksChanged(uint)),
159             this,     SLOT(changeTracks(uint)));
160     connect(m_dialog, SIGNAL(sampleRateChanged(double)),
161             this,     SLOT(changeSampleRate(double)));
162     connect(m_dialog, SIGNAL(sigCompressionChanged(Kwave::Compression::Type)),
163 	    this,     SLOT(changeCompression(Kwave::Compression::Type)));
164     connect(m_dialog, SIGNAL(sigBitsPerSampleChanged(uint)),
165             this,     SLOT(changeBitsPerSample(uint)));
166     connect(m_dialog,
167 	    SIGNAL(sigSampleFormatChanged(Kwave::SampleFormat::Format)),
168             this,
169 	    SLOT(changeSampleFormat(Kwave::SampleFormat::Format)));
170     connect(m_dialog, SIGNAL(sigBuffersChanged()),
171             this,     SLOT(buffersChanged()));
172     connect(this,     SIGNAL(sigRecordedSamples(sample_index_t)),
173             m_dialog, SLOT(setRecordedSamples(sample_index_t)));
174 
175     connect(m_dialog,      SIGNAL(sigTriggerChanged(bool)),
176             &m_controller, SLOT(enableTrigger(bool)));
177     m_controller.enableTrigger(
178 	m_dialog->params().record_trigger_enabled ||
179 	m_dialog->params().start_time_enabled
180     );
181 
182     connect(m_dialog, SIGNAL(sigPreRecordingChanged(bool)),
183             &m_controller, SLOT(enablePrerecording(bool)));
184     connect(m_dialog, SIGNAL(sigPreRecordingChanged(bool)),
185             this, SLOT(prerecordingChanged(bool)));
186     m_controller.enablePrerecording(m_dialog->params().pre_record_enabled);
187 
188     // connect the record controller and this
189     connect(&m_controller, SIGNAL(sigReset(bool&)),
190             this,          SLOT(resetRecording(bool&)));
191     connect(&m_controller, SIGNAL(sigStartRecord()),
192             this,          SLOT(startRecording()));
193     connect(&m_controller, SIGNAL(sigStopRecord(int)),
194             &m_controller, SLOT(deviceRecordStopped(int)));
195     connect(&m_controller, SIGNAL(stateChanged(Kwave::RecordState)),
196             this,          SLOT(stateChanged(Kwave::RecordState)));
197 
198     // connect record controller and record thread
199     connect(m_thread,      SIGNAL(stopped(int)),
200             &m_controller, SLOT(deviceRecordStopped(int)));
201 
202     // connect us to the record thread
203     connect(m_thread, SIGNAL(stopped(int)),
204             this,     SLOT(recordStopped(int)));
205     connect(m_thread, SIGNAL(bufferFull()),
206             this,     SLOT(processBuffer()),
207             Qt::QueuedConnection);
208 
209     // dummy init -> disable format settings
210     m_dialog->setSupportedTracks(0, 0);
211 
212     // activate the recording method
213     setMethod(m_dialog->params().method);
214 
215     // directly start recording if requested
216     if (mode == Kwave::RecordDialog::START_RECORDING)
217 	m_controller.actionStart();
218 
219     QStringList *list = new(std::nothrow) QStringList();
220     Q_ASSERT(list);
221     if (list && (m_dialog->exec() == QDialog::Accepted) && m_dialog) {
222 	// user has pressed "OK"
223 	*list = m_dialog->params().toList();
224     } else {
225 	// user pressed "Cancel"
226 	if (list) delete list;
227         list = Q_NULLPTR;
228     }
229 
230     /* de-queue all buffers that are pending and remove the record thread */
231     if (m_thread) {
232 	m_thread->stop();
233 	while (m_thread->queuedBuffers())
234 	    processBuffer();
235 	delete m_thread;
236         m_thread = Q_NULLPTR;
237     }
238 
239     if (m_decoder) delete m_decoder;
240     m_decoder = Q_NULLPTR;
241 
242     delete m_dialog;
243     m_dialog = Q_NULLPTR;
244 
245     // flush away all prerecording buffers
246     m_prerecording_queue.clear();
247 
248     // enable undo again if we recorded something
249     if (!signalManager().isEmpty())
250 	signalManager().enableUndo();
251 
252     return list;
253 }
254 
255 //***************************************************************************
notice(QString message)256 void Kwave::RecordPlugin::notice(QString message)
257 {
258     Q_ASSERT(m_dialog);
259     if (m_dialog) m_dialog->message(message);
260 }
261 
262 //***************************************************************************
closeDevice()263 void Kwave::RecordPlugin::closeDevice()
264 {
265     if (m_retry_timer.isActive()) m_retry_timer.stop();
266 
267     if (m_device) {
268 	m_device->close();
269 	delete m_device;
270         m_device = Q_NULLPTR;
271     }
272 }
273 
274 //***************************************************************************
setMethod(Kwave::record_method_t method)275 void Kwave::RecordPlugin::setMethod(Kwave::record_method_t method)
276 {
277     Q_ASSERT(m_dialog);
278     if (!m_dialog) return;
279 
280     InhibitRecordGuard _lock(*this); // don't record while settings change
281     qDebug("RecordPlugin::setMethod(%d)", static_cast<int>(method));
282 
283     // change the recording method (class RecordDevice)
284     if ((method != m_method) || !m_device) {
285 	if (m_device) delete m_device;
286         m_device = Q_NULLPTR;
287 	bool searching = false;
288 
289 	// use the previous device
290 	QString section = _("plugin ") + name();
291 	KConfigGroup cfg = KSharedConfig::openConfig()->group(section);
292 
293 	// restore the previous device
294 	QString device = cfg.readEntry(
295 	    _("last_device_%1").arg(static_cast<int>(method)));
296 // 	    qDebug("<<< %d -> '%s'", static_cast<int>(method), device.data());
297 	m_device_name = device;
298 
299 	do {
300 	    switch (method) {
301 #ifdef HAVE_OSS_SUPPORT
302 		case Kwave::RECORD_OSS:
303 		    m_device = new(std::nothrow) Kwave::RecordOSS();
304 		    Q_ASSERT(m_device);
305 		    break;
306 #endif /* HAVE_OSS_SUPPORT */
307 
308 #ifdef HAVE_ALSA_SUPPORT
309 		case Kwave::RECORD_ALSA:
310 		    m_device = new(std::nothrow) Kwave::RecordALSA();
311 		    Q_ASSERT(m_device);
312 		    break;
313 #endif /* HAVE_ALSA_SUPPORT */
314 
315 #ifdef HAVE_PULSEAUDIO_SUPPORT
316 		case Kwave::RECORD_PULSEAUDIO:
317 		    m_device = new(std::nothrow) Kwave::RecordPulseAudio();
318 		    Q_ASSERT(m_device);
319 		    break;
320 #endif /* HAVE_PULSEAUDIO_SUPPORT */
321 
322 #ifdef HAVE_QT_AUDIO_SUPPORT
323 		case Kwave::RECORD_QT:
324 		    m_device = new(std::nothrow) Kwave::RecordQt();
325 		    Q_ASSERT(m_device);
326 		    break;
327 #endif /* HAVE_QT_AUDIO_SUPPORT */
328 		default:
329 		    qDebug("unsupported recording method (%d)",
330 			static_cast<int>(method));
331 		    if (!searching) {
332 			// start trying all other methods
333 			searching = true;
334 			method = Kwave::RECORD_NONE;
335 			++method;
336 			continue;
337 		    } else {
338 			// try next method
339 			++method;
340 		    }
341 		    qDebug("unsupported recording method - trying next (%d)",
342 		           static_cast<int>(method));
343 		    if (method != Kwave::RECORD_INVALID) continue;
344 	    }
345 	    break;
346 	} while (true);
347     }
348     Q_ASSERT(m_device);
349 
350     // if we found no recording method
351     if (method == Kwave::RECORD_INVALID) {
352 	qWarning("found no valid recording method");
353     }
354 
355     // take the change in the method
356     m_method = method;
357 
358     // activate the cange in the dialog
359     m_dialog->setMethod(method);
360 
361     // set list of supported devices
362     QStringList supported_devices;
363     Q_ASSERT(m_device);
364     if (m_device) supported_devices = m_device->supportedDevices();
365     m_dialog->setSupportedDevices(supported_devices);
366 
367     // set current device (again), no matter if supported or not,
368     // the dialog will take care of this.
369     setDevice(m_device_name);
370 
371     // check the filter for the "select..." dialog. If it is
372     // empty, the "select" dialog will be disabled
373     QString file_filter;
374     if (m_device) file_filter = m_device->fileFilter();
375     m_dialog->setFileFilter(file_filter);
376 }
377 
378 //***************************************************************************
retryOpen()379 void Kwave::RecordPlugin::retryOpen()
380 {
381     qDebug("RecordPlugin::retryOpen()");
382     setDevice(m_device_name);
383 }
384 
385 //***************************************************************************
setDevice(const QString & device)386 void Kwave::RecordPlugin::setDevice(const QString &device)
387 {
388     Q_ASSERT(m_dialog);
389     Q_ASSERT(m_device);
390     if (!m_dialog || !m_device) return;
391 
392     InhibitRecordGuard _lock(*this); // don't record while settings change
393     qDebug("RecordPlugin::setDevice('%s')", DBG(device));
394 
395     if (m_retry_timer.isActive()) m_retry_timer.stop();
396 
397     // select the default device if this one is not supported
398     QString dev = device;
399     QStringList supported = m_device->supportedDevices();
400     if (!supported.isEmpty() && !supported.contains(device)) {
401 	// use the first entry as default
402 	dev = supported.first();
403 	qDebug("RecordPlugin::setDevice(%s) -> fallback to '%s'",
404 	       DBG(device), DBG(dev));
405     }
406 
407     // if there was no valid device name, fall back to default device
408     if (dev.startsWith(_("#"))) {
409 	dev = _("/dev/dsp");
410 	qDebug("RecordPlugin::setDevice(%s) -> no valid device, using '%s'",
411 	       DBG(device), DBG(dev));
412     }
413 
414     // open and initialize the device
415     QString result = m_device->open(dev);
416 
417     // set the device in the dialog
418     m_device_name = dev;
419     m_dialog->setDevice(dev);
420 
421     // remember the device selection, just for the GUI
422     // for the next change in the method
423     QString section = _("plugin ") + name();
424     KConfigGroup cfg = KSharedConfig::openConfig()->group(section);
425     cfg.writeEntry(_("last_device_%1").arg(
426 	static_cast<int>(m_method)), m_device_name);
427 //     qDebug(">>> %d -> '%s'", static_cast<int>(m_method), DBG(m_device_name));
428     cfg.sync();
429 
430     if (!result.isNull()) {
431 	bool shouldRetry = false;
432 
433 	qWarning("RecordPlugin::setDevice('%s'): "
434 	         "opening the device failed. error message='%s'",
435 	         DBG(device), DBG(result));
436 
437 	m_controller.setInitialized(false);
438 
439 	if (m_device_name.length()) {
440 	    // build a short device name for showing to the user
441 	    QString short_device_name = m_device_name;
442 	    if (m_device_name.contains(_("|"))) {
443 		// tree syntax: extract card + device
444 		short_device_name = m_device_name.section(_("|"), 0, 0);
445 		if (m_device_name.section(_("|"), 3, 3).length())
446 		    short_device_name += _(", ") +
447 			m_device_name.section(_("|"), 3, 3);
448 	    }
449 
450 	    bool errIsNumeric = false;
451 	    int errNumber = result.toInt(&errIsNumeric);
452 	    if (errIsNumeric) {
453 		if (errNumber == ENODEV) {
454 		    result = i18n(
455 			"Maybe your system lacks support for the "\
456 			"corresponding hardware or the hardware is not "\
457 			"connected."
458 		    );
459 		} else if (errNumber == EBUSY) {
460 		    result = i18n(
461 			"The audio device seems to be occupied by another "\
462 			"application. Retrying..."
463 		    );
464 		    shouldRetry = true;
465 		} else {
466 		    result = i18n(
467 			"Some unexpected error happened (%1). "\
468 			"You may try an other recording method or "\
469 			"recording device.",
470 			QString::fromLocal8Bit(strerror(errNumber))
471 		    );
472 		}
473 	    }
474 
475 	    if (result.length()) {
476 		if (shouldRetry) {
477 		    notice(result);
478 		} else {
479 		    m_dialog->showDevicePage();
480 		    Kwave::MessageBox::sorry(parentWidget(),
481 		        result, i18nc("%1 = a device name",
482 		        "Unable to open the recording device (%1)",
483 		        short_device_name));
484 		}
485 	    }
486 	}
487 
488 	if (shouldRetry) {
489 	    // retry later...
490 	    m_retry_timer.start(OPEN_RETRY_TIME);
491 	} else {
492 	    m_device_name = QString();
493 	    changeTracks(0);
494 	}
495     } else {
496 	changeTracks(m_dialog->params().tracks);
497     }
498 
499     if (paramsValid()) {
500 	m_controller.setInitialized(true);
501     } else {
502 	qDebug("RecordPlugin::setDevice('%s') failed, "
503 	        "returning to 'UNINITIALIZED'", DBG(device));
504 	m_controller.setInitialized(false);
505     }
506 
507 }
508 
509 //***************************************************************************
changeTracks(unsigned int new_tracks)510 void Kwave::RecordPlugin::changeTracks(unsigned int new_tracks)
511 {
512     Q_ASSERT(m_dialog);
513     if (!m_dialog) return;
514 
515     InhibitRecordGuard _lock(*this); // don't record while settings change
516 //     qDebug("RecordPlugin::changeTracks(%u)", new_tracks);
517 
518     if (!m_device || m_device_name.isNull()) {
519 	// no device -> dummy/shortcut
520 	m_dialog->setSupportedTracks(0, 0);
521 	m_dialog->setTracks(0);
522 	changeSampleRate(0);
523 	return;
524     }
525 
526     // check the supported tracks
527     unsigned int min = 0;
528     unsigned int max = 0;
529     if ((m_device->detectTracks(min, max) < 0) || (max < 1))
530 	min = max = 0;
531     if (min > max) min = max;
532 
533     unsigned int channels = new_tracks;
534     if ((channels < min) || (channels > max)) {
535 	// clip to the supported number of tracks
536 	if (channels < min) channels = min;
537 	if (channels > max) channels = max;
538 	qDebug("RecordPlugin::changeTracks(%u) -> clipped to %u",
539 	       new_tracks, channels);
540 
541 	if ((new_tracks && channels) && (new_tracks != channels)) {
542 	    QString s1;
543 	    switch (new_tracks) {
544 		case 1: s1 = i18n("Mono");   break;
545 		case 2: s1 = i18n("Stereo"); break;
546 		case 4: s1 = i18n("Quadro"); break;
547 		default:
548 		    s1 = i18n("%1 channels", new_tracks);
549 	    }
550 	    QString s2;
551 	    switch (channels) {
552 		case 1: s2 = i18n("Mono");   break;
553 		case 2: s2 = i18n("Stereo"); break;
554 		case 4: s2 = i18n("Quadro"); break;
555 		default:
556 		    s2 = i18n("%1 channels", channels);
557 	    }
558 
559 	    notice(i18n("%1 is not supported, using %2", s1, s2));
560 	}
561     }
562     Q_ASSERT(channels >= min);
563     Q_ASSERT(channels <= max);
564     m_dialog->setSupportedTracks(min, max);
565 
566     // try to activate the new number of tracks
567     int err = m_device->setTracks(channels);
568     if (err < 0) {
569 	// revert to the current device setting if failed
570 	int t = m_device->tracks();
571 	if (t > 0) {
572 	    // current device state seems to be valid
573 	    channels = t;
574 	    if (channels < min) channels = min;
575 	    if (channels > max) channels = max;
576 	} else {
577 	    // current device state is invalid
578 	    channels = 0;
579 	}
580 
581 	if (new_tracks && (channels > 0)) notice(
582 	    i18n("Recording with %1 channel(s) failed, "\
583 		 "using %2 channel(s)", new_tracks, channels));
584     }
585     m_dialog->setTracks(channels);
586 
587     // activate the new sample rate
588     changeSampleRate(m_dialog->params().sample_rate);
589 }
590 
591 //***************************************************************************
changeSampleRate(double new_rate)592 void Kwave::RecordPlugin::changeSampleRate(double new_rate)
593 {
594     Q_ASSERT(m_dialog);
595     if (!m_dialog) return;
596 
597     InhibitRecordGuard _lock(*this); // don't record while settings change
598 //     qDebug("RecordPlugin::changeSampleRate(%u)", Kwave::toInt(new_rate));
599 
600     if (!m_device || m_device_name.isNull()) {
601 	// no device -> dummy/shortcut
602 	m_dialog->setSampleRate(0);
603 	changeCompression(Kwave::Compression::INVALID);
604 	return;
605     }
606 
607     // check the supported sample rates
608     QList<double> supported_rates = m_device->detectSampleRates();
609     bool is_supported = false;
610     foreach (const double &r, supported_rates)
611 	if (qFuzzyCompare(new_rate, r)) { is_supported = true; break; }
612     double rate = new_rate;
613     if (!is_supported && !supported_rates.isEmpty()) {
614 	// find the nearest sample rate
615 	double nearest = supported_rates.last();
616 	foreach (double r, supported_rates) {
617 	    if (fabs(r - rate) <= fabs(nearest - rate))
618 	        nearest = r;
619 	}
620 	rate = nearest;
621 
622 	const QString sr1(m_dialog->rate2string(new_rate));
623 	const QString sr2(m_dialog->rate2string(rate));
624 	if ((Kwave::toInt(new_rate) > 0) &&
625 	    (Kwave::toInt(rate) > 0) &&
626 	    (Kwave::toInt(new_rate) != Kwave::toInt(rate)))
627 	    notice(i18n("%1 Hz is not supported, "\
628 		        "using %2 Hz", sr1, sr2));
629     }
630     m_dialog->setSupportedSampleRates(supported_rates);
631 
632     // try to activate the new sample rate
633     int err = m_device->setSampleRate(rate);
634     if (err < 0) {
635 	// revert to the current device setting if failed
636 	rate = m_device->sampleRate();
637 	if (rate < 0) rate = 0;
638 
639 	const QString sr1(m_dialog->rate2string(new_rate));
640 	const QString sr2(m_dialog->rate2string(rate));
641 	if ((Kwave::toInt(new_rate) > 0) &&
642 	    (Kwave::toInt(rate) > 0) &&
643 	    (Kwave::toInt(new_rate) != Kwave::toInt(rate)))
644 	    notice(i18n("%1 Hz failed, using %2 Hz", sr1, sr2));
645     }
646     m_dialog->setSampleRate(rate);
647 
648     // set the compression again
649     changeCompression(m_dialog->params().compression);
650 }
651 
652 //***************************************************************************
changeCompression(Kwave::Compression::Type new_compression)653 void Kwave::RecordPlugin::changeCompression(
654     Kwave::Compression::Type new_compression
655 )
656 {
657     Q_ASSERT(m_dialog);
658     if (!m_dialog) return;
659 
660     InhibitRecordGuard _lock(*this); // don't record while settings change
661 //     qDebug("RecordPlugin::changeCompression(%d)", new_compression);
662 
663     if (!m_device || m_device_name.isNull()) {
664 	// no device -> dummy/shortcut
665 	m_dialog->setCompression(-1);
666 	changeBitsPerSample(0);
667 	return;
668     }
669 
670     // check the supported compressions
671     QList<Kwave::Compression::Type> supported_comps =
672 	m_device->detectCompressions();
673     Kwave::Compression::Type compression = new_compression;
674     if (!supported_comps.contains(compression) &&
675 	(compression != Kwave::Compression::NONE))
676     {
677 	// try to disable the compression (type 0)
678 	compression = Kwave::Compression::NONE;
679 	if (!supported_comps.isEmpty() &&
680 	    !supported_comps.contains(compression))
681 	{
682 	    // what now, "None" is not supported
683 	    // -> take the first supported one
684 	    compression = supported_comps[0];
685 	}
686 
687 	if (compression != new_compression) {
688 	    const QString c1(Kwave::Compression(new_compression).name());
689 	    const QString c2(Kwave::Compression(compression).name());
690 	    notice(i18n("Compression '%1' not supported, using '%2'", c1, c2));
691 	}
692     }
693     m_dialog->setSupportedCompressions(supported_comps);
694 
695     // try to activate the new compression
696     int err = m_device->setCompression(compression);
697     if (err < 0) {
698 	// revert to the current device setting if failed
699 	if (compression != m_device->compression()) {
700 	    const QString c1(Kwave::Compression(compression).name());
701 	    const QString c2(Kwave::Compression(m_device->compression()).name());
702 	    notice(i18n("Compression '%1' failed, using '%2'.", c1 ,c2));
703 	}
704 	compression = m_device->compression();
705     }
706     m_dialog->setCompression(compression);
707 
708     // set the resolution in bits per sample again
709     changeBitsPerSample(m_dialog->params().bits_per_sample);
710 }
711 
712 //***************************************************************************
changeBitsPerSample(unsigned int new_bits)713 void Kwave::RecordPlugin::changeBitsPerSample(unsigned int new_bits)
714 {
715     Q_ASSERT(m_dialog);
716     if (!m_dialog) return;
717 
718     InhibitRecordGuard _lock(*this); // don't record while settings change
719 //     qDebug("RecordPlugin::changeBitsPerSample(%d)", new_bits);
720 
721     if (!m_device || m_device_name.isNull()) {
722 	// no device -> dummy/shortcut
723 	m_dialog->setBitsPerSample(0);
724 	changeSampleFormat(Kwave::SampleFormat::Unknown);
725 	return;
726     }
727 
728     // check the supported resolution in bits per sample
729     QList<unsigned int> supported_bits = m_device->supportedBits();
730     int bits = new_bits;
731     if (!supported_bits.contains(bits) && !supported_bits.isEmpty()) {
732 	// find the nearest resolution
733 	int nearest = supported_bits.last();
734 	foreach (unsigned int b, supported_bits) {
735 	    if (qAbs(Kwave::toInt(b) - nearest) <= qAbs(bits - nearest))
736 	        nearest = Kwave::toInt(b);
737 	}
738 	bits = nearest;
739 
740 	if ((Kwave::toInt(new_bits) > 0) && (bits > 0)) notice(
741 	    i18n("%1 bits per sample is not supported, "\
742 	         "using %2 bits per sample",
743 		 Kwave::toInt(new_bits), bits));
744     }
745     m_dialog->setSupportedBits(supported_bits);
746 
747     // try to activate the resolution
748     int err = m_device->setBitsPerSample(bits);
749     if (err < 0) {
750 	// revert to the current device setting if failed
751 	bits = m_device->bitsPerSample();
752 	if (bits < 0) bits = 0;
753 	if ((new_bits > 0) && (bits > 0)) notice(
754 	    i18n("%1 bits per sample failed, "
755 		 "using %2 bits per sample",
756 		 Kwave::toInt(new_bits), bits));
757     }
758     m_dialog->setBitsPerSample(bits);
759 
760     // set the sample format again
761     changeSampleFormat(m_dialog->params().sample_format);
762 }
763 
764 //***************************************************************************
changeSampleFormat(Kwave::SampleFormat::Format new_format)765 void Kwave::RecordPlugin::changeSampleFormat(
766     Kwave::SampleFormat::Format new_format)
767 {
768     Q_ASSERT(m_dialog);
769     if (!m_dialog) return;
770 
771     InhibitRecordGuard _lock(*this); // don't record while settings change
772 
773     if (!m_device || m_device_name.isNull()) {
774 	// no device -> dummy/shortcut
775 	m_dialog->setSampleFormat(Kwave::SampleFormat::Unknown);
776 	return;
777     }
778 
779     // check the supported sample formats
780     QList<Kwave::SampleFormat::Format> supported_formats =
781 	m_device->detectSampleFormats();
782     Kwave::SampleFormat::Format format = new_format;
783     if (!supported_formats.contains(format) && !supported_formats.isEmpty()) {
784 	// use the device default instead
785 	format = m_device->sampleFormat();
786 
787 	// if this was also not supported -> stupid device !?
788 	if (!supported_formats.contains(format)) {
789 	    format = supported_formats.first(); // just take the first one :-o
790 	}
791 
792 	Kwave::SampleFormat::Map sf;
793 	const QString s1 = sf.description(sf.findFromData(new_format), true);
794 	const QString s2 = sf.description(sf.findFromData(format), true);
795 	if (!(new_format == -1) && !(new_format == format)) {
796 	    notice(i18n("Sample format '%1' is not supported, "\
797 		        "using '%2'", s1, s2));
798 	}
799     }
800     m_dialog->setSupportedSampleFormats(supported_formats);
801 
802     // try to activate the new format
803     int err = m_device->setSampleFormat(format);
804     if (err < 0) {
805 	// use the device default instead
806 	format = m_device->sampleFormat();
807 
808 	Kwave::SampleFormat::Map sf;
809 	const QString s1 = sf.description(sf.findFromData(new_format), true);
810 	const QString s2 = sf.description(sf.findFromData(format), true);
811 	if (format > 0) notice(
812 	    i18n("Sample format '%1' failed, using '%2'", s1, s2));
813     }
814     m_dialog->setSampleFormat(format);
815 }
816 
817 //***************************************************************************
buffersChanged()818 void Kwave::RecordPlugin::buffersChanged()
819 {
820     InhibitRecordGuard _lock(*this); // don't record while settings change
821     // this implicitly activates the new settings
822 }
823 
824 //***************************************************************************
enterInhibit()825 void Kwave::RecordPlugin::enterInhibit()
826 {
827     m_inhibit_count++;
828     if ((m_inhibit_count == 1) && m_thread) {
829 	// set hourglass cursor
830 	QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
831 
832 // 	qDebug("RecordPlugin::enterInhibit() - STOPPING");
833 	m_thread->stop();
834 	Q_ASSERT(!m_thread->isRunning());
835 
836 	// de-queue all buffers that are still in the queue
837 	while (m_thread->queuedBuffers())
838 	    processBuffer();
839     }
840 }
841 
842 //***************************************************************************
leaveInhibit()843 void Kwave::RecordPlugin::leaveInhibit()
844 {
845     Q_ASSERT(m_inhibit_count);
846     Q_ASSERT(m_dialog);
847 
848     if (m_inhibit_count) m_inhibit_count--;
849 
850     while (!m_inhibit_count && paramsValid()) {
851 // 	qDebug("RecordPlugin::leaveInhibit() - STARTING ("
852 // 	       "%d channels, %d bits)",
853 // 	       m_dialog->params().tracks,
854 // 	       m_dialog->params().bits_per_sample);
855 
856 	Q_ASSERT(!m_thread->isRunning());
857 	if (m_thread->isRunning()) break;
858 
859 	// set new parameters for the recorder
860 	setupRecordThread();
861 
862 	// and let the thread run (again)
863 	m_thread->start();
864 	break;
865     }
866 
867     // take back the hourglass cursor
868     if (!m_inhibit_count) QApplication::restoreOverrideCursor();
869 }
870 
871 //***************************************************************************
paramsValid()872 bool Kwave::RecordPlugin::paramsValid()
873 {
874     if (!m_thread || !m_device || !m_dialog) return false;
875 
876     // check for a valid/usable record device
877     if (m_device_name.isNull()) return false;
878     if ( (m_device->sampleFormat() != Kwave::SampleFormat::Unsigned) &&
879          (m_device->sampleFormat() != Kwave::SampleFormat::Signed) )
880 	return false;
881     if (m_device->bitsPerSample() < 1) return false;
882     if (m_device->endianness() == Kwave::UnknownEndian) return false;
883 
884     // check for valid parameters in the dialog
885     const Kwave::RecordParams &params = m_dialog->params();
886     if (params.tracks < 1) return false;
887     if ( (params.sample_format != Kwave::SampleFormat::Unsigned) &&
888          (params.sample_format != Kwave::SampleFormat::Signed) ) return false;
889 
890     return true;
891 }
892 
893 //***************************************************************************
resetRecording(bool & accepted)894 void Kwave::RecordPlugin::resetRecording(bool &accepted)
895 {
896     InhibitRecordGuard _lock(*this);
897 
898     if (m_writers) m_writers->clear();
899 
900     emitCommand(_("nomacro:close()"));
901     QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
902     accepted = manager().signalManager().isEmpty();
903     if (!accepted) return;
904 
905     // the parent context might have changed, maybe we have to
906     // re-parent this plugin instance!
907     migrateToActiveContext();
908 
909     m_buffers_recorded = 0;
910 
911     m_controller.setEmpty(true);
912     emit sigRecordedSamples(0);
913 }
914 
915 //***************************************************************************
setupRecordThread()916 void Kwave::RecordPlugin::setupRecordThread()
917 {
918     Q_ASSERT(m_thread);
919     Q_ASSERT(m_dialog);
920     Q_ASSERT(m_device);
921     if (!paramsValid()) return;
922 
923     // stop the thread if necessary (should never happen)
924     Q_ASSERT(!m_thread->isRunning());
925     if (m_thread->isRunning()) m_thread->stop();
926     Q_ASSERT(!m_thread->isRunning());
927 
928     // delete the previous decoder
929     if (m_decoder) delete m_decoder;
930     m_decoder = Q_NULLPTR;
931 
932     // our own reference to the record parameters
933     const Kwave::RecordParams &params = m_dialog->params();
934     if (!paramsValid()) return;
935 
936     // create a decoder for the current sample format
937     switch (params.compression) {
938 	case Kwave::Compression::NONE:
939 	    switch (params.sample_format) {
940 		case Kwave::SampleFormat::Unsigned: /* FALLTHROUGH */
941 		case Kwave::SampleFormat::Signed:
942 		    // decoder for all linear formats
943 		    m_decoder = new(std::nothrow) Kwave::SampleDecoderLinear(
944 			m_device->sampleFormat(),
945 			m_device->bitsPerSample(),
946 			m_device->endianness()
947 		    );
948 		    break;
949 		default:
950 		    notice(
951 			i18n("The current sample format is not supported!")
952 		    );
953 	    }
954 	    break;
955 	default:
956 	    notice(
957 		i18n("The current compression type is not supported!")
958 	    );
959 	    return;
960     }
961 
962     Q_ASSERT(m_decoder);
963     if (!m_decoder) {
964 	Kwave::MessageBox::sorry(m_dialog, i18n("Out of memory"));
965 	return;
966     }
967 
968     // set up the prerecording queues
969     m_prerecording_queue.clear();
970     if (params.pre_record_enabled) {
971 	// prepare a queue for each track
972 	const unsigned int prerecording_samples = Kwave::toUint(
973 	    rint(params.pre_record_time * params.sample_rate));
974 	m_prerecording_queue.resize(params.tracks);
975 	for (int i=0; i < m_prerecording_queue.size(); i++)
976 	    m_prerecording_queue[i].setSize(prerecording_samples);
977 
978 	if (m_prerecording_queue.size() != Kwave::toInt(params.tracks)) {
979 	    m_prerecording_queue.clear();
980 	    Kwave::MessageBox::sorry(m_dialog, i18n("Out of memory"));
981 	    return;
982 	}
983     }
984 
985     // set up the recording trigger values
986     m_trigger_value.resize(params.tracks);
987     m_trigger_value.fill(0.0);
988 
989     // set up the record thread
990     m_thread->setRecordDevice(m_device);
991     unsigned int buf_count = params.buffer_count;
992     unsigned int buf_size  = params.tracks *
993                              m_decoder->rawBytesPerSample() *
994                             (1 << params.buffer_size);
995     m_thread->setBuffers(buf_count, buf_size);
996 }
997 
998 //***************************************************************************
startRecording()999 void Kwave::RecordPlugin::startRecording()
1000 {
1001     Q_ASSERT(m_dialog);
1002     Q_ASSERT(m_thread);
1003     Q_ASSERT(m_device);
1004     if (!m_dialog || !m_thread || !m_device) return;
1005 
1006     InhibitRecordGuard _lock(*this); // don't record while settings change
1007 
1008     if ((m_state != Kwave::REC_PAUSED) || !m_decoder) {
1009 	double rate = m_dialog->params().sample_rate;
1010 	unsigned int tracks = m_dialog->params().tracks;
1011 	unsigned int bits = m_dialog->params().bits_per_sample;
1012 
1013 	if (!tracks) return;
1014 
1015 	/*
1016 	 * if tracks or sample rate has changed
1017 	 * -> start over with a new signal and new settings
1018 	 */
1019 	if ((!m_writers) ||
1020 	    (m_writers->tracks() != tracks) || !qFuzzyCompare(
1021 	     Kwave::FileInfo(signalManager().metaData()).rate(), rate))
1022 	{
1023 	    // create a new and empty signal
1024 	    emitCommand(QString(_("newsignal(0,%1,%2,%3)")).arg(
1025 		rate).arg(bits).arg(tracks));
1026 	    QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
1027 
1028 	    // the parent context might have changed, maybe we have to
1029 	    // re-parent this plugin instance!
1030 	    migrateToActiveContext();
1031 
1032 	    Kwave::SignalManager &mgr = signalManager();
1033 	    if (!qFuzzyCompare(mgr.rate(), rate) || (mgr.bits() != bits) ||
1034 	        (mgr.tracks() != tracks))
1035 	    {
1036 		emitCommand(_("close"));
1037 		return;
1038 	    }
1039 
1040 	    // we do not need undo while recording, this would only waste undo
1041 	    // buffers with modified/inserted data
1042 	    signalManager().disableUndo();
1043 
1044 	    // create a sink for our audio data
1045 	    if (m_writers) delete m_writers;
1046 	    m_writers = new(std::nothrow) Kwave::MultiTrackWriter(
1047 	        signalManager(), Kwave::Append);
1048 	    if ((!m_writers) || (m_writers->tracks() != tracks)) {
1049 		Kwave::MessageBox::sorry(m_dialog, i18n("Out of memory"));
1050 		return;
1051 	    }
1052 	} else {
1053 	    // re-use the current signal and append to it
1054 	}
1055 
1056 	// initialize the file information
1057 	Kwave::FileInfo fileInfo(signalManager().metaData());
1058 	fileInfo.setRate(rate);
1059 	fileInfo.setBits(bits);
1060 	fileInfo.setTracks(tracks);
1061 	fileInfo.set(Kwave::INF_MIMETYPE, _("audio/vnd.wave"));
1062 	fileInfo.set(Kwave::INF_SAMPLE_FORMAT,
1063 	    Kwave::SampleFormat(m_dialog->params().sample_format).toInt());
1064 	fileInfo.set(Kwave::INF_COMPRESSION, m_dialog->params().compression);
1065 
1066 	// add our Kwave Software tag
1067 	const KAboutData about_data = KAboutData::applicationData();
1068 	QString software = about_data.componentName() + _("-") +
1069 	                   about_data.version() + _(" ") +
1070 	                   i18n("(built with KDE Frameworks %1)",
1071 	                   _(KXMLGUI_VERSION_STRING));
1072 	fileInfo.set(Kwave::INF_SOFTWARE, software);
1073 
1074 	// add a date tag, ISO format
1075 	QString date(QDate::currentDate().toString(_("yyyy-MM-dd")));
1076 	fileInfo.set(Kwave::INF_CREATION_DATE, date);
1077 	signalManager().setFileInfo(fileInfo, false);
1078     }
1079 
1080     // now the recording can be considered to be started
1081     m_controller.deviceRecordStarted();
1082 }
1083 
1084 //***************************************************************************
recordStopped(int reason)1085 void Kwave::RecordPlugin::recordStopped(int reason)
1086 {
1087     qDebug("RecordPlugin::recordStopped(%d)", reason);
1088     if (reason >= 0) return; // nothing to do
1089 
1090     // recording was aborted
1091     QString err_msg;
1092     switch (reason) {
1093 	case -ENOBUFS:
1094 	    err_msg = i18n("Buffer overrun. Please increase the "\
1095 	                    "number and/or size of the record buffers.");
1096 	    break;
1097 	case -EBUSY:
1098 	    err_msg = i18n("The recording device seems to be busy.");
1099 	    break;
1100 	default:
1101 	    err_msg = i18n("Reading from the recording device failed. "\
1102 	                   "Error number = %1 (%2)", -reason,
1103 	                    QString::fromLocal8Bit(strerror(-reason)));
1104     }
1105     Kwave::MessageBox::error(m_dialog, err_msg);
1106 
1107     if (m_writers) m_writers->flush();
1108     qDebug("RecordPlugin::recordStopped(): last=%lu",
1109 	   static_cast<unsigned long int>(
1110            (m_writers) ? m_writers->last() : 0));
1111 
1112     // flush away all prerecording buffers
1113     m_prerecording_queue.clear();
1114 
1115     // update the file info if we recorded something
1116     // NOTE: this implicitly sets the "modified" flag of the signal
1117     if (m_writers && m_writers->last()) {
1118 	Kwave::FileInfo info(signalManager().metaData());
1119 	info.setLength(signalLength());
1120 	info.setTracks(m_dialog->params().tracks);
1121 	signalManager().setFileInfo(info, false);
1122     }
1123 
1124 }
1125 
1126 //***************************************************************************
stateChanged(Kwave::RecordState state)1127 void Kwave::RecordPlugin::stateChanged(Kwave::RecordState state)
1128 {
1129     m_state = state;
1130     switch (m_state) {
1131 	case Kwave::REC_PAUSED:
1132 	    if (m_writers) m_writers->flush();
1133 	    break;
1134 	case Kwave::REC_UNINITIALIZED:
1135 	case Kwave::REC_EMPTY:
1136 	case Kwave::REC_DONE:
1137 	    // reset buffer status
1138 	    if (m_writers) {
1139 		m_writers->flush();
1140 		delete m_writers;
1141                 m_writers = Q_NULLPTR;
1142 	    }
1143 	    m_buffers_recorded = 0;
1144 	    m_dialog->updateBufferState(0, 0);
1145 	    break;
1146 	default:
1147 	    ;
1148     }
1149 }
1150 
1151 //***************************************************************************
updateBufferProgressBar()1152 void Kwave::RecordPlugin::updateBufferProgressBar()
1153 {
1154     Q_ASSERT(m_dialog);
1155     Q_ASSERT(m_thread);
1156     if (!m_dialog || !m_thread) return;
1157 
1158     unsigned int buffers_total = m_dialog->params().buffer_count;
1159 
1160     // if we are still recording: update the progress bar
1161     if ((m_state != Kwave::REC_EMPTY) && (m_state != Kwave::REC_PAUSED) &&
1162         (m_state != Kwave::REC_DONE))
1163     {
1164 	// count up the number of recorded buffers
1165 	m_buffers_recorded++;
1166 
1167 	if (m_buffers_recorded <= buffers_total) {
1168 	    // buffers are just in progress of getting filled
1169 	    m_dialog->updateBufferState(m_buffers_recorded, buffers_total);
1170 	} else {
1171 	    // we have remaining+1 buffers (one is currently filled)
1172 	    unsigned int remaining = m_thread->remainingBuffers() + 1;
1173 	    if (remaining > buffers_total) remaining = buffers_total;
1174 	    m_dialog->updateBufferState(remaining, buffers_total);
1175 	}
1176     } else {
1177 	// no longer recording: count the buffer downwards
1178 	unsigned int queued = m_thread->queuedBuffers();
1179 	if (!queued) buffers_total = 0;
1180 	m_dialog->updateBufferState(queued, buffers_total);
1181     }
1182 }
1183 
1184 //***************************************************************************
split(QByteArray & raw_data,QByteArray & dest,unsigned int bytes_per_sample,unsigned int track,unsigned int tracks)1185 void Kwave::RecordPlugin::split(QByteArray &raw_data, QByteArray &dest,
1186                                 unsigned int bytes_per_sample,
1187                                 unsigned int track,
1188                                 unsigned int tracks)
1189 {
1190     unsigned int samples = raw_data.size() / bytes_per_sample / tracks;
1191 
1192 #if 0
1193     // simple sawtooth generator, based on raw data
1194     // works for up to 16 channels
1195     static int saw[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
1196     raw_data.fill(0x05);
1197     for (unsigned int s = 0; s < samples; s++) {
1198 	int v = saw[track];
1199 	for (unsigned int byte = 0; byte < bytes_per_sample; byte++) {
1200 	    quint8 x = (quint8)v;
1201 	    raw_data[(((s * tracks) + track) * bytes_per_sample) + byte] = x;
1202 	    v >>= 8;
1203 	}
1204 
1205 	const int max = (1 << ((bytes_per_sample * 8) - 1)) - 1;
1206 	saw[track] += max / 64;
1207 	if (saw[track] >= max) saw[track] = 0;
1208     }
1209 #endif
1210 
1211     if (tracks == 1) {
1212 	// this would give a 1:1 memcpy
1213 	dest = raw_data;
1214     } else {
1215 	switch (bytes_per_sample) {
1216 	    case 1: {
1217 		// 1...8 bits per sample, use 8 bit pointers
1218 		const quint8 *src =
1219 		    reinterpret_cast<const quint8 *>(raw_data.constData());
1220 		quint8 *dst =
1221 		    reinterpret_cast<quint8 *>(dest.data());
1222 		src += track;
1223 		while (samples) {
1224 		    *dst = *src;
1225 		    dst++;
1226 		    src += tracks;
1227 		    samples--;
1228 		}
1229 		break;
1230 	    }
1231 	    case 2: {
1232 		// 9...16 bits per sample, use 16 bit pointers
1233 		const quint16 *src =
1234 		    reinterpret_cast<const quint16 *>(raw_data.constData());
1235 		quint16 *dst =
1236 		    reinterpret_cast<quint16 *>(dest.data());
1237 		src += track;
1238 		while (samples) {
1239 		    *dst = *src;
1240 		    dst++;
1241 		    src += tracks;
1242 		    samples--;
1243 		}
1244 		break;
1245 	    }
1246 	    case 3: {
1247 		// 17...24 bits per sample, use 8 bit pointers, three times
1248 		const quint8 *src =
1249 		    reinterpret_cast<const quint8 *>(raw_data.constData());
1250 		quint8 *dst =
1251 		    reinterpret_cast<quint8 *>(dest.data());
1252 		src += track * 3;
1253 		while (samples) {
1254 		    *(dst++) = *(src++);
1255 		    *(dst++) = *(src++);
1256 		    *(dst++) = *(src++);
1257 		    src += (tracks - 1) * 3;
1258 		    samples--;
1259 		}
1260 		break;
1261 	    }
1262 	    case 4: {
1263 		// 24...32 bits per sample, use 32 bit pointers
1264 		const quint32 *src =
1265 		    reinterpret_cast<const quint32 *>(raw_data.constData());
1266 		quint32 *dst =
1267 		    reinterpret_cast<quint32 *>(dest.data());
1268 		src += track;
1269 		while (samples) {
1270 		    *dst = *src;
1271 		    dst++;
1272 		    src += tracks;
1273 		    samples--;
1274 		}
1275 		break;
1276 	    }
1277 	    case 8: {
1278 		// 64 bits per sample, use 64 bit pointers
1279 		const quint64 *src =
1280 		    reinterpret_cast<const quint64 *>(raw_data.constData());
1281 		quint64 *dst =
1282 		    reinterpret_cast<quint64 *>(dest.data());
1283 		src += track;
1284 		while (samples) {
1285 		    *dst = *src;
1286 		    dst++;
1287 		    src += tracks;
1288 		    samples--;
1289 		}
1290 		break;
1291 	    }
1292 	    default: {
1293 		// default: byte wise operation
1294 		const quint8 *src =
1295 		    reinterpret_cast<const quint8 *>(raw_data.constData());
1296 		quint8 *dst =
1297 		    reinterpret_cast<quint8 *>(dest.data());
1298 		src += (track * bytes_per_sample);
1299 		unsigned int increment = (tracks - 1) * bytes_per_sample;
1300 		while (samples) {
1301 		    for (unsigned int b = 0; b < bytes_per_sample; b++) {
1302 			*dst = *src;
1303 			dst++;
1304 			src++;
1305 			samples--;
1306 		    }
1307 		    src += increment;
1308 		}
1309 	    }
1310 	}
1311     }
1312 }
1313 
1314 //***************************************************************************
checkTrigger(unsigned int track,const Kwave::SampleArray & buffer)1315 bool Kwave::RecordPlugin::checkTrigger(unsigned int track,
1316                                        const Kwave::SampleArray &buffer)
1317 {
1318     Q_ASSERT(m_dialog);
1319     if (!m_dialog) return false;
1320 
1321     // check if the recording start time has been reached
1322     if (m_dialog->params().start_time_enabled) {
1323 	if (QDateTime::currentDateTime() < m_dialog->params().start_time)
1324 	    return false;
1325     }
1326 
1327     // shortcut if no trigger has been set
1328     if (!m_dialog->params().record_trigger_enabled) return true;
1329 
1330     // check the input parameters
1331     if (!buffer.size()) return false;
1332     if (!m_writers) return false;
1333     if (m_trigger_value.size() != Kwave::toInt(m_writers->tracks()))
1334 	return false;
1335 
1336     // pass the buffer through a rectifier and a lowpass with
1337     // center frequency about 2Hz to get the amplitude
1338     float trigger = static_cast<float>(
1339 	m_dialog->params().record_trigger / 100.0);
1340     float rate = static_cast<float>(
1341 	m_dialog->params().sample_rate);
1342 
1343     /*
1344      * simple lowpass calculation:
1345      *
1346      *               1 + z
1347      * H(z) = a0 * -----------   | z = e ^ (j*2*pi*f)
1348      *               z + b1
1349      *
1350      *        1            1 - n
1351      * a0 = -----    b1 = --------
1352      *      1 + n          1 + n
1353      *
1354      * Fg = fg / fa
1355      *
1356      * n = cot(Pi * Fg)
1357      *
1358      * y[t] = a0 * x[t] + a1 * x[t-1] - b1 * y[t-1]
1359      *
1360      */
1361 
1362     // rise coefficient: ~20Hz
1363     const float f_rise = 20.0f;
1364     float Fg = f_rise / rate;
1365     float n = 1.0f / tanf(float(M_PI) * Fg);
1366     const float a0_r = 1.0f / (1.0f + n);
1367     const float b1_r = (1.0f - n) / (1.0f + n);
1368 
1369     // fall coefficient: ~1.0Hz
1370     const float f_fall = 1.0f;
1371     Fg = f_fall / rate;
1372     n = 1.0f / tanf(float(M_PI) * Fg);
1373     const float a0_f = 1.0f / (1.0f + n);
1374     const float b1_f = (1.0f - n) / (1.0f + n);
1375 
1376     float y = m_trigger_value[track];
1377     float last_x = y;
1378     for (unsigned int t = 0; t < buffer.size(); ++t) {
1379 	float x = fabsf(sample2float(buffer[t])); /* rectifier */
1380 
1381 	if (x > y) { /* diode */
1382 	    // rise if amplitude is above average (serial R)
1383 	    y = (a0_r * x) + (a0_r * last_x) - (b1_r * y);
1384 	}
1385 
1386 	// fall (parallel R)
1387 	y = (a0_f * x) + (a0_f * last_x) - (b1_f * y);
1388 
1389 	// remember x[t-1]
1390 	last_x = x;
1391 
1392 // nice for debugging:
1393 //	buffer[t] = (int)((double)(1 << (SAMPLE_BITS-1)) * y);
1394 	if (y > trigger) return true;
1395     }
1396     m_trigger_value[track] = y;
1397 
1398     qDebug(">> level=%5.3g, trigger=%5.3g", y, trigger);
1399 
1400     return false;
1401 }
1402 
1403 //***************************************************************************
enqueuePrerecording(unsigned int track,const Kwave::SampleArray & decoded)1404 void Kwave::RecordPlugin::enqueuePrerecording(unsigned int track,
1405                                               const Kwave::SampleArray &decoded)
1406 {
1407     Q_ASSERT(m_dialog);
1408     Q_ASSERT(Kwave::toInt(track) < m_prerecording_queue.size());
1409     if (!m_dialog) return;
1410     if (Kwave::toInt(track) >= m_prerecording_queue.size()) return;
1411 
1412     // append the array with decoded sample to the prerecording buffer
1413     m_prerecording_queue[track].put(decoded);
1414 }
1415 
1416 //***************************************************************************
flushPrerecordingQueue()1417 void Kwave::RecordPlugin::flushPrerecordingQueue()
1418 {
1419     if (!m_prerecording_queue.size()) return;
1420     Q_ASSERT(m_dialog);
1421     Q_ASSERT(m_thread);
1422     Q_ASSERT(m_decoder);
1423     if (!m_dialog || !m_thread || !m_decoder) return;
1424 
1425     const Kwave::RecordParams &params = m_dialog->params();
1426     const unsigned int tracks = params.tracks;
1427     Q_ASSERT(tracks);
1428     if (!tracks) return;
1429     Q_ASSERT(m_writers);
1430     if (!m_writers) return;
1431     Q_ASSERT(tracks == m_writers->tracks());
1432     if (tracks != m_writers->tracks()) return;
1433 
1434     for (unsigned int track=0; track < tracks; ++track) {
1435 	Kwave::SampleFIFO &fifo = m_prerecording_queue[track];
1436 	Q_ASSERT(fifo.length());
1437 	if (!fifo.length()) continue;
1438 	fifo.crop(); // enforce the correct size
1439 
1440 	// push all buffers to the writer, starting at the tail
1441 	Kwave::Writer *writer = (*m_writers)[track];
1442 	Q_ASSERT(writer);
1443 	if (writer) {
1444 	    Kwave::SampleArray buffer(writer->blockSize());
1445 	    unsigned int rest = fifo.length();
1446 	    while (rest) {
1447 		unsigned int read = fifo.get(buffer);
1448 		if (read < 1) break;
1449 		writer->write(buffer, read);
1450 		rest -= read;
1451 	    }
1452 	} else {
1453 	    // fallback: discard the FIFO content
1454 	    fifo.flush();
1455 	}
1456 	Q_ASSERT(fifo.length() == 0);
1457     }
1458 
1459     // the queues are no longer needed
1460     m_prerecording_queue.clear();
1461 
1462     // we have transferred data to the writers, we are no longer empty
1463     m_controller.setEmpty(false);
1464 }
1465 
1466 //***************************************************************************
processBuffer()1467 void Kwave::RecordPlugin::processBuffer()
1468 {
1469     bool recording_done = false;
1470 
1471     // de-queue the buffer from the thread
1472     if (!m_thread) return;
1473     if (!m_thread->queuedBuffers()) return;
1474     QByteArray buffer = m_thread->dequeue();
1475 
1476     // abort here if we have no dialog or no decoder
1477     if (!m_dialog || !m_decoder) return;
1478 
1479     // we received a buffer -> update the progress bar
1480     updateBufferProgressBar();
1481 
1482     const Kwave::RecordParams &params = m_dialog->params();
1483     const unsigned int tracks = params.tracks;
1484     Q_ASSERT(tracks);
1485     if (!tracks) return;
1486 
1487     const unsigned int bytes_per_sample = m_decoder->rawBytesPerSample();
1488     Q_ASSERT(bytes_per_sample);
1489     if (!bytes_per_sample) return;
1490 
1491     unsigned int samples = (buffer.size() / bytes_per_sample) / tracks;
1492     Q_ASSERT(samples);
1493     if (!samples) return;
1494 
1495     // check for reached recording time limit if enabled
1496     if (params.record_time_limited && m_writers) {
1497 	sample_index_t last = m_writers->last();
1498 	sample_index_t already_recorded = (last) ? (last + 1) : 0;
1499 	sample_index_t limit = static_cast<sample_index_t>(rint(
1500 	    params.record_time * params.sample_rate));
1501 	if (already_recorded + samples >= limit) {
1502 	    // reached end of recording time, we are full
1503 	    if (m_state == Kwave::REC_RECORDING) {
1504 		samples = Kwave::toUint(
1505 		    (limit > already_recorded) ?
1506 		    (limit - already_recorded) : 0);
1507 		buffer.resize(samples * tracks * bytes_per_sample);
1508 	    }
1509 	    recording_done = true;
1510 	}
1511     }
1512 
1513     QByteArray buf;
1514     buf.resize(bytes_per_sample * samples);
1515     Q_ASSERT(buf.size() == Kwave::toInt(bytes_per_sample * samples));
1516     if (buf.size() != Kwave::toInt(bytes_per_sample * samples)) return;
1517 
1518     Kwave::SampleArray decoded(samples);
1519     Q_ASSERT(decoded.size() == samples);
1520     if (decoded.size() != samples) return;
1521 
1522     // check for trigger
1523     // note: this might change the state, which affects the
1524     //       processing of all tracks !
1525     if ((m_state == Kwave::REC_WAITING_FOR_TRIGGER) ||
1526         ((m_state == Kwave::REC_PRERECORDING) && params.record_trigger_enabled) ||
1527         ((m_state == Kwave::REC_PRERECORDING) && params.start_time_enabled))
1528     {
1529 	for (unsigned int track=0; track < tracks; ++track) {
1530 	    // split off and decode buffer with current track
1531 	    split(buffer, buf, bytes_per_sample, track, tracks);
1532 	    m_decoder->decode(buf, decoded);
1533 	    if (checkTrigger(track, decoded)) {
1534 		m_controller.deviceTriggerReached();
1535 		break;
1536 	    }
1537 	}
1538     }
1539 
1540     if ((m_state == Kwave::REC_RECORDING) && !m_prerecording_queue.isEmpty()) {
1541 	// flush all prerecorded buffers to the output
1542 	flushPrerecordingQueue();
1543     }
1544 
1545     // use a copy of the state, in case it changes below ;-)
1546     Kwave::RecordState state = m_state;
1547     for (unsigned int track = 0; track < tracks; ++track) {
1548 	// decode and care for all special effects, meters and so on
1549 	// split off and decode buffer with current track
1550 	split(buffer, buf, bytes_per_sample, track, tracks);
1551 	m_decoder->decode(buf, decoded);
1552 
1553 	// update the level meter and other effects
1554 	m_dialog->updateEffects(track, decoded);
1555 
1556 	// if the first buffer is full -> leave REC_BUFFERING
1557 	// limit state transitions to a point before the first track is
1558 	// processed (avoid asymmetry)
1559 	if ((track == 0) && (m_state == Kwave::REC_BUFFERING) &&
1560 	    (m_buffers_recorded > 1))
1561 	{
1562 	    m_controller.deviceBufferFull();
1563 	    state = m_state; // might have changed!
1564 	}
1565 
1566 	switch (state) {
1567 	    case Kwave::REC_UNINITIALIZED:
1568 	    case Kwave::REC_EMPTY:
1569 	    case Kwave::REC_PAUSED:
1570 	    case Kwave::REC_DONE:
1571 	    case Kwave::REC_BUFFERING:
1572 	    case Kwave::REC_WAITING_FOR_TRIGGER:
1573 		// already handled before or nothing to do...
1574 		break;
1575 	    case Kwave::REC_PRERECORDING:
1576 		// enqueue the buffers into a FIFO
1577 		enqueuePrerecording(track, decoded);
1578 		break;
1579 	    case Kwave::REC_RECORDING: {
1580 		// put the decoded track data into the buffer
1581 		if (!m_writers) break; // (could happen due to queued signal)
1582 		Q_ASSERT(tracks == m_writers->tracks());
1583 		if (!tracks || (tracks != m_writers->tracks())) break;
1584 
1585 		Kwave::Writer *writer = (*m_writers)[track];
1586 		Q_ASSERT(writer);
1587 		if (writer) (*writer) << decoded;
1588 		m_controller.setEmpty(false);
1589 
1590 		break;
1591 	    }
1592 	}
1593     }
1594 
1595     // update the number of recorded samples
1596     if (m_writers) emit sigRecordedSamples(m_writers->last() + 1);
1597 
1598     // if this was the last received buffer, change state
1599     if (recording_done &&
1600 	(m_state != Kwave::REC_DONE) &&
1601 	(m_state != Kwave::REC_EMPTY))
1602     {
1603 	m_controller.actionStop();
1604     }
1605 
1606 }
1607 
1608 //***************************************************************************
prerecordingChanged(bool enable)1609 void Kwave::RecordPlugin::prerecordingChanged(bool enable)
1610 {
1611     Q_UNUSED(enable)
1612     InhibitRecordGuard _lock(*this); // activate the change
1613 }
1614 
1615 //***************************************************************************
1616 #include "RecordPlugin.moc"
1617 //***************************************************************************
1618 //***************************************************************************
1619