Chris@476
|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
Chris@476
|
2
|
Chris@476
|
3 /*
|
Chris@476
|
4 Sonic Visualiser
|
Chris@476
|
5 An audio file viewer and annotation editor.
|
Chris@476
|
6 Centre for Digital Music, Queen Mary, University of London.
|
Chris@476
|
7
|
Chris@476
|
8 This program is free software; you can redistribute it and/or
|
Chris@476
|
9 modify it under the terms of the GNU General Public License as
|
Chris@476
|
10 published by the Free Software Foundation; either version 2 of the
|
Chris@476
|
11 License, or (at your option) any later version. See the file
|
Chris@476
|
12 COPYING included with this distribution for more information.
|
Chris@476
|
13 */
|
Chris@476
|
14
|
Chris@574
|
15 #include "AudioCallbackRecordTarget.h"
|
Chris@476
|
16
|
Chris@476
|
17 #include "base/ViewManagerBase.h"
|
Chris@476
|
18 #include "base/TempDirectory.h"
|
Chris@476
|
19
|
Chris@477
|
20 #include "data/model/WritableWaveFileModel.h"
|
Chris@476
|
21
|
Chris@476
|
22 #include <QDir>
|
Chris@476
|
23
|
Chris@574
|
24 AudioCallbackRecordTarget::AudioCallbackRecordTarget(ViewManagerBase *manager,
|
Chris@574
|
25 QString clientName) :
|
Chris@476
|
26 m_viewManager(manager),
|
Chris@476
|
27 m_clientName(clientName.toUtf8().data()),
|
Chris@476
|
28 m_recording(false),
|
Chris@476
|
29 m_recordSampleRate(44100),
|
Chris@546
|
30 m_recordChannelCount(2),
|
Chris@485
|
31 m_frameCount(0),
|
Chris@574
|
32 m_model(0),
|
Chris@574
|
33 m_inputLeft(0.f),
|
Chris@574
|
34 m_inputRight(0.f)
|
Chris@476
|
35 {
|
Chris@574
|
36 m_viewManager->setAudioRecordTarget(this);
|
Chris@574
|
37
|
Chris@574
|
38 connect(this, SIGNAL(recordStatusChanged(bool)),
|
Chris@574
|
39 m_viewManager, SLOT(recordStatusChanged(bool)));
|
Chris@476
|
40 }
|
Chris@476
|
41
|
Chris@574
|
42 AudioCallbackRecordTarget::~AudioCallbackRecordTarget()
|
Chris@476
|
43 {
|
Chris@476
|
44 QMutexLocker locker(&m_mutex);
|
Chris@574
|
45 m_viewManager->setAudioRecordTarget(0);
|
Chris@476
|
46 }
|
Chris@476
|
47
|
Chris@559
|
48 int
|
Chris@574
|
49 AudioCallbackRecordTarget::getApplicationSampleRate() const
|
Chris@559
|
50 {
|
Chris@559
|
51 return 0; // don't care
|
Chris@559
|
52 }
|
Chris@559
|
53
|
Chris@559
|
54 int
|
Chris@574
|
55 AudioCallbackRecordTarget::getApplicationChannelCount() const
|
Chris@559
|
56 {
|
Chris@559
|
57 return m_recordChannelCount;
|
Chris@559
|
58 }
|
Chris@559
|
59
|
Chris@476
|
60 void
|
Chris@574
|
61 AudioCallbackRecordTarget::setSystemRecordBlockSize(int)
|
Chris@476
|
62 {
|
Chris@476
|
63 }
|
Chris@476
|
64
|
Chris@476
|
65 void
|
Chris@574
|
66 AudioCallbackRecordTarget::setSystemRecordSampleRate(int n)
|
Chris@476
|
67 {
|
Chris@476
|
68 m_recordSampleRate = n;
|
Chris@476
|
69 }
|
Chris@476
|
70
|
Chris@476
|
71 void
|
Chris@574
|
72 AudioCallbackRecordTarget::setSystemRecordLatency(int)
|
Chris@476
|
73 {
|
Chris@476
|
74 }
|
Chris@476
|
75
|
Chris@476
|
76 void
|
Chris@574
|
77 AudioCallbackRecordTarget::setSystemRecordChannelCount(int c)
|
Chris@546
|
78 {
|
Chris@546
|
79 m_recordChannelCount = c;
|
Chris@546
|
80 }
|
Chris@546
|
81
|
Chris@546
|
82 void
|
Chris@574
|
83 AudioCallbackRecordTarget::putSamples(const float *const *samples, int, int nframes)
|
Chris@476
|
84 {
|
Chris@485
|
85 bool secChanged = false;
|
Chris@485
|
86 sv_frame_t frameToEmit = 0;
|
Chris@485
|
87
|
Chris@485
|
88 {
|
Chris@485
|
89 QMutexLocker locker(&m_mutex); //!!! bad here
|
Chris@485
|
90 if (!m_recording) return;
|
Chris@485
|
91
|
Chris@485
|
92 m_model->addSamples(samples, nframes);
|
Chris@485
|
93
|
Chris@485
|
94 sv_frame_t priorFrameCount = m_frameCount;
|
Chris@485
|
95 m_frameCount += nframes;
|
Chris@485
|
96
|
Chris@485
|
97 RealTime priorRT = RealTime::frame2RealTime
|
Chris@485
|
98 (priorFrameCount, m_recordSampleRate);
|
Chris@485
|
99 RealTime postRT = RealTime::frame2RealTime
|
Chris@485
|
100 (m_frameCount, m_recordSampleRate);
|
Chris@485
|
101
|
Chris@485
|
102 secChanged = (postRT.sec > priorRT.sec);
|
Chris@573
|
103 if (secChanged) {
|
Chris@573
|
104 m_model->updateModel(); //!!! v bad here
|
Chris@573
|
105 frameToEmit = m_frameCount;
|
Chris@573
|
106 }
|
Chris@485
|
107 }
|
Chris@485
|
108
|
Chris@485
|
109 if (secChanged) {
|
Chris@485
|
110 emit recordDurationChanged(frameToEmit, m_recordSampleRate);
|
Chris@485
|
111 }
|
Chris@476
|
112 }
|
Chris@476
|
113
|
Chris@476
|
114 void
|
Chris@574
|
115 AudioCallbackRecordTarget::setInputLevels(float left, float right)
|
Chris@476
|
116 {
|
Chris@574
|
117 if (left > m_inputLeft) m_inputLeft = left;
|
Chris@574
|
118 if (right > m_inputRight) m_inputRight = right;
|
Chris@574
|
119 }
|
Chris@574
|
120
|
Chris@574
|
121 bool
|
Chris@574
|
122 AudioCallbackRecordTarget::getInputLevels(float &left, float &right)
|
Chris@574
|
123 {
|
Chris@574
|
124 left = m_inputLeft;
|
Chris@574
|
125 right = m_inputRight;
|
Chris@574
|
126 m_inputLeft = 0.f;
|
Chris@574
|
127 m_inputRight = 0.f;
|
Chris@574
|
128 return true;
|
Chris@476
|
129 }
|
Chris@476
|
130
|
Chris@477
|
131 void
|
Chris@574
|
132 AudioCallbackRecordTarget::modelAboutToBeDeleted()
|
Chris@477
|
133 {
|
Chris@477
|
134 QMutexLocker locker(&m_mutex);
|
Chris@477
|
135 if (sender() == m_model) {
|
Chris@477
|
136 m_model = 0;
|
Chris@477
|
137 m_recording = false;
|
Chris@477
|
138 }
|
Chris@477
|
139 }
|
Chris@477
|
140
|
Chris@483
|
141 QString
|
Chris@574
|
142 AudioCallbackRecordTarget::getRecordContainerFolder()
|
Chris@508
|
143 {
|
Chris@508
|
144 QDir parent(TempDirectory::getInstance()->getContainingPath());
|
Chris@508
|
145 QString subdirname("recorded");
|
Chris@508
|
146
|
Chris@508
|
147 if (!parent.mkpath(subdirname)) {
|
Chris@574
|
148 SVCERR << "ERROR: AudioCallbackRecordTarget::getRecordContainerFolder: Failed to create recorded dir in \"" << parent.canonicalPath() << "\"" << endl;
|
Chris@508
|
149 return "";
|
Chris@508
|
150 } else {
|
Chris@508
|
151 return parent.filePath(subdirname);
|
Chris@508
|
152 }
|
Chris@508
|
153 }
|
Chris@508
|
154
|
Chris@508
|
155 QString
|
Chris@574
|
156 AudioCallbackRecordTarget::getRecordFolder()
|
Chris@483
|
157 {
|
Chris@508
|
158 QDir parent(getRecordContainerFolder());
|
Chris@508
|
159 QDateTime now = QDateTime::currentDateTime();
|
Chris@508
|
160 QString subdirname = QString("%1").arg(now.toString("yyyyMMdd"));
|
Chris@508
|
161
|
Chris@483
|
162 if (!parent.mkpath(subdirname)) {
|
Chris@574
|
163 SVCERR << "ERROR: AudioCallbackRecordTarget::getRecordFolder: Failed to create recorded dir in \"" << parent.canonicalPath() << "\"" << endl;
|
Chris@483
|
164 return "";
|
Chris@483
|
165 } else {
|
Chris@483
|
166 return parent.filePath(subdirname);
|
Chris@483
|
167 }
|
Chris@483
|
168 }
|
Chris@483
|
169
|
Chris@477
|
170 WritableWaveFileModel *
|
Chris@574
|
171 AudioCallbackRecordTarget::startRecording()
|
Chris@476
|
172 {
|
Chris@477
|
173 {
|
Chris@570
|
174 QMutexLocker locker(&m_mutex);
|
Chris@498
|
175
|
Chris@570
|
176 if (m_recording) {
|
Chris@574
|
177 SVCERR << "WARNING: AudioCallbackRecordTarget::startRecording: We are already recording" << endl;
|
Chris@570
|
178 return 0;
|
Chris@570
|
179 }
|
Chris@476
|
180
|
Chris@570
|
181 m_model = 0;
|
Chris@570
|
182 m_frameCount = 0;
|
Chris@477
|
183
|
Chris@570
|
184 QString folder = getRecordFolder();
|
Chris@570
|
185 if (folder == "") return 0;
|
Chris@570
|
186 QDir recordedDir(folder);
|
Chris@476
|
187
|
Chris@570
|
188 QDateTime now = QDateTime::currentDateTime();
|
Chris@476
|
189
|
Chris@570
|
190 // Don't use QDateTime::toString(Qt::ISODate) as the ":" character
|
Chris@570
|
191 // isn't permitted in filenames on Windows
|
Chris@570
|
192 QString nowString = now.toString("yyyyMMdd-HHmmss-zzz");
|
Chris@570
|
193
|
Chris@570
|
194 QString filename = tr("recorded-%1.wav").arg(nowString);
|
Chris@570
|
195 QString label = tr("Recorded %1").arg(nowString);
|
Chris@476
|
196
|
Chris@570
|
197 m_audioFileName = recordedDir.filePath(filename);
|
Chris@476
|
198
|
Chris@570
|
199 m_model = new WritableWaveFileModel(m_recordSampleRate,
|
Chris@570
|
200 m_recordChannelCount,
|
Chris@570
|
201 m_audioFileName);
|
Chris@477
|
202
|
Chris@570
|
203 if (!m_model->isOK()) {
|
Chris@574
|
204 SVCERR << "ERROR: AudioCallbackRecordTarget::startRecording: Recording failed"
|
Chris@570
|
205 << endl;
|
Chris@570
|
206 //!!! and throw?
|
Chris@570
|
207 delete m_model;
|
Chris@570
|
208 m_model = 0;
|
Chris@570
|
209 return 0;
|
Chris@570
|
210 }
|
Chris@476
|
211
|
Chris@570
|
212 m_model->setObjectName(label);
|
Chris@570
|
213 m_recording = true;
|
Chris@477
|
214 }
|
Chris@477
|
215
|
Chris@477
|
216 emit recordStatusChanged(true);
|
Chris@477
|
217 return m_model;
|
Chris@476
|
218 }
|
Chris@476
|
219
|
Chris@476
|
220 void
|
Chris@574
|
221 AudioCallbackRecordTarget::stopRecording()
|
Chris@476
|
222 {
|
Chris@477
|
223 {
|
Chris@570
|
224 QMutexLocker locker(&m_mutex);
|
Chris@570
|
225 if (!m_recording) {
|
Chris@574
|
226 SVCERR << "WARNING: AudioCallbackRecordTarget::startRecording: Not recording" << endl;
|
Chris@570
|
227 return;
|
Chris@570
|
228 }
|
Chris@476
|
229
|
Chris@570
|
230 m_model->writeComplete();
|
Chris@570
|
231 m_model = 0;
|
Chris@570
|
232 m_recording = false;
|
Chris@477
|
233 }
|
Chris@477
|
234
|
Chris@477
|
235 emit recordStatusChanged(false);
|
Chris@497
|
236 emit recordCompleted();
|
Chris@476
|
237 }
|
Chris@476
|
238
|
Chris@476
|
239
|