Chris@148
|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
Chris@148
|
2
|
Chris@148
|
3 /*
|
Chris@148
|
4 Sonic Visualiser
|
Chris@148
|
5 An audio file viewer and annotation editor.
|
Chris@148
|
6 Centre for Digital Music, Queen Mary, University of London.
|
Chris@148
|
7 This file copyright 2006 Chris Cannam.
|
Chris@148
|
8
|
Chris@148
|
9 This program is free software; you can redistribute it and/or
|
Chris@148
|
10 modify it under the terms of the GNU General Public License as
|
Chris@148
|
11 published by the Free Software Foundation; either version 2 of the
|
Chris@148
|
12 License, or (at your option) any later version. See the file
|
Chris@148
|
13 COPYING included with this distribution for more information.
|
Chris@148
|
14 */
|
Chris@148
|
15
|
Chris@148
|
16 #include "CSVFileReader.h"
|
Chris@148
|
17
|
Chris@150
|
18 #include "model/Model.h"
|
Chris@148
|
19 #include "base/RealTime.h"
|
Chris@148
|
20 #include "model/SparseOneDimensionalModel.h"
|
Chris@148
|
21 #include "model/SparseTimeValueModel.h"
|
Chris@152
|
22 #include "model/EditableDenseThreeDimensionalModel.h"
|
Chris@308
|
23 #include "DataFileReaderFactory.h"
|
Chris@148
|
24
|
Chris@148
|
25 #include <QFile>
|
Chris@148
|
26 #include <QString>
|
Chris@148
|
27 #include <QRegExp>
|
Chris@148
|
28 #include <QStringList>
|
Chris@148
|
29 #include <QTextStream>
|
Chris@148
|
30 #include <QFrame>
|
Chris@148
|
31 #include <QGridLayout>
|
Chris@148
|
32 #include <QPushButton>
|
Chris@148
|
33 #include <QHBoxLayout>
|
Chris@148
|
34 #include <QVBoxLayout>
|
Chris@148
|
35 #include <QTableWidget>
|
Chris@148
|
36 #include <QComboBox>
|
Chris@148
|
37 #include <QLabel>
|
Chris@148
|
38
|
Chris@148
|
39 #include <iostream>
|
Chris@148
|
40
|
Chris@148
|
41 CSVFileReader::CSVFileReader(QString path, size_t mainModelSampleRate) :
|
Chris@148
|
42 m_file(0),
|
Chris@148
|
43 m_mainModelSampleRate(mainModelSampleRate)
|
Chris@148
|
44 {
|
Chris@148
|
45 m_file = new QFile(path);
|
Chris@148
|
46 bool good = false;
|
Chris@148
|
47
|
Chris@148
|
48 if (!m_file->exists()) {
|
Chris@148
|
49 m_error = QFile::tr("File \"%1\" does not exist").arg(path);
|
Chris@148
|
50 } else if (!m_file->open(QIODevice::ReadOnly | QIODevice::Text)) {
|
Chris@148
|
51 m_error = QFile::tr("Failed to open file \"%1\"").arg(path);
|
Chris@148
|
52 } else {
|
Chris@148
|
53 good = true;
|
Chris@148
|
54 }
|
Chris@148
|
55
|
Chris@148
|
56 if (!good) {
|
Chris@148
|
57 delete m_file;
|
Chris@148
|
58 m_file = 0;
|
Chris@148
|
59 }
|
Chris@148
|
60 }
|
Chris@148
|
61
|
Chris@148
|
62 CSVFileReader::~CSVFileReader()
|
Chris@148
|
63 {
|
Chris@148
|
64 std::cerr << "CSVFileReader::~CSVFileReader: file is " << m_file << std::endl;
|
Chris@148
|
65
|
Chris@148
|
66 if (m_file) {
|
Chris@148
|
67 std::cerr << "CSVFileReader::CSVFileReader: Closing file" << std::endl;
|
Chris@148
|
68 m_file->close();
|
Chris@148
|
69 }
|
Chris@148
|
70 delete m_file;
|
Chris@148
|
71 }
|
Chris@148
|
72
|
Chris@148
|
73 bool
|
Chris@148
|
74 CSVFileReader::isOK() const
|
Chris@148
|
75 {
|
Chris@148
|
76 return (m_file != 0);
|
Chris@148
|
77 }
|
Chris@148
|
78
|
Chris@148
|
79 QString
|
Chris@148
|
80 CSVFileReader::getError() const
|
Chris@148
|
81 {
|
Chris@148
|
82 return m_error;
|
Chris@148
|
83 }
|
Chris@148
|
84
|
Chris@148
|
85 Model *
|
Chris@148
|
86 CSVFileReader::load() const
|
Chris@148
|
87 {
|
Chris@148
|
88 if (!m_file) return 0;
|
Chris@148
|
89
|
Chris@148
|
90 CSVFormatDialog *dialog = new CSVFormatDialog
|
Chris@148
|
91 (0, m_file, m_mainModelSampleRate);
|
Chris@148
|
92
|
Chris@148
|
93 if (dialog->exec() == QDialog::Rejected) {
|
Chris@148
|
94 delete dialog;
|
Chris@308
|
95 throw DataFileReaderFactory::ImportCancelled;
|
Chris@148
|
96 }
|
Chris@148
|
97
|
Chris@148
|
98 CSVFormatDialog::ModelType modelType = dialog->getModelType();
|
Chris@148
|
99 CSVFormatDialog::TimingType timingType = dialog->getTimingType();
|
Chris@148
|
100 CSVFormatDialog::TimeUnits timeUnits = dialog->getTimeUnits();
|
Chris@148
|
101 QString separator = dialog->getSeparator();
|
Chris@390
|
102 QString::SplitBehavior behaviour = dialog->getSplitBehaviour();
|
Chris@148
|
103 size_t sampleRate = dialog->getSampleRate();
|
Chris@148
|
104 size_t windowSize = dialog->getWindowSize();
|
Chris@148
|
105
|
Chris@148
|
106 delete dialog;
|
Chris@148
|
107
|
Chris@148
|
108 if (timingType == CSVFormatDialog::ExplicitTiming) {
|
Chris@148
|
109 windowSize = 1;
|
Chris@148
|
110 if (timeUnits == CSVFormatDialog::TimeSeconds) {
|
Chris@148
|
111 sampleRate = m_mainModelSampleRate;
|
Chris@148
|
112 }
|
Chris@148
|
113 }
|
Chris@148
|
114
|
Chris@148
|
115 SparseOneDimensionalModel *model1 = 0;
|
Chris@148
|
116 SparseTimeValueModel *model2 = 0;
|
Chris@152
|
117 EditableDenseThreeDimensionalModel *model3 = 0;
|
Chris@148
|
118 Model *model = 0;
|
Chris@148
|
119
|
Chris@148
|
120 QTextStream in(m_file);
|
Chris@148
|
121 in.seek(0);
|
Chris@148
|
122
|
Chris@148
|
123 unsigned int warnings = 0, warnLimit = 10;
|
Chris@148
|
124 unsigned int lineno = 0;
|
Chris@148
|
125
|
Chris@148
|
126 float min = 0.0, max = 0.0;
|
Chris@148
|
127
|
Chris@148
|
128 size_t frameNo = 0;
|
Chris@148
|
129
|
Chris@148
|
130 while (!in.atEnd()) {
|
Chris@148
|
131
|
Chris@283
|
132 // QTextStream's readLine doesn't cope with old-style Mac
|
Chris@283
|
133 // CR-only line endings. Why did they bother making the class
|
Chris@283
|
134 // cope with more than one sort of line ending, if it still
|
Chris@283
|
135 // can't be configured to cope with all the common sorts?
|
Chris@148
|
136
|
Chris@283
|
137 // For the time being we'll deal with this case (which is
|
Chris@283
|
138 // relatively uncommon for us, but still necessary to handle)
|
Chris@283
|
139 // by reading the entire file using a single readLine, and
|
Chris@283
|
140 // splitting it. For CR and CR/LF line endings this will just
|
Chris@283
|
141 // read a line at a time, and that's obviously OK.
|
Chris@148
|
142
|
Chris@283
|
143 QString chunk = in.readLine();
|
Chris@283
|
144 QStringList lines = chunk.split('\r', QString::SkipEmptyParts);
|
Chris@283
|
145
|
Chris@283
|
146 for (size_t li = 0; li < lines.size(); ++li) {
|
Chris@148
|
147
|
Chris@283
|
148 QString line = lines[li];
|
Chris@148
|
149
|
Chris@283
|
150 if (line.startsWith("#")) continue;
|
Chris@283
|
151
|
Chris@390
|
152 QStringList list = line.split(separator, behaviour);
|
Chris@283
|
153
|
Chris@283
|
154 if (!model) {
|
Chris@283
|
155
|
Chris@283
|
156 switch (modelType) {
|
Chris@283
|
157
|
Chris@283
|
158 case CSVFormatDialog::OneDimensionalModel:
|
Chris@283
|
159 model1 = new SparseOneDimensionalModel(sampleRate, windowSize);
|
Chris@283
|
160 model = model1;
|
Chris@283
|
161 break;
|
Chris@148
|
162
|
Chris@283
|
163 case CSVFormatDialog::TwoDimensionalModel:
|
Chris@283
|
164 model2 = new SparseTimeValueModel(sampleRate, windowSize, false);
|
Chris@283
|
165 model = model2;
|
Chris@283
|
166 break;
|
Chris@148
|
167
|
Chris@283
|
168 case CSVFormatDialog::ThreeDimensionalModel:
|
Chris@283
|
169 model3 = new EditableDenseThreeDimensionalModel(sampleRate,
|
Chris@283
|
170 windowSize,
|
Chris@283
|
171 list.size());
|
Chris@283
|
172 model = model3;
|
Chris@283
|
173 break;
|
Chris@283
|
174 }
|
Chris@283
|
175 }
|
Chris@148
|
176
|
Chris@283
|
177 QStringList tidyList;
|
Chris@390
|
178 QRegExp nonNumericRx("[^0-9eE.,+-]");
|
Chris@148
|
179
|
Chris@283
|
180 for (int i = 0; i < list.size(); ++i) {
|
Chris@148
|
181
|
Chris@283
|
182 QString s(list[i].trimmed());
|
Chris@148
|
183
|
Chris@283
|
184 if (s.length() >= 2 && s.startsWith("\"") && s.endsWith("\"")) {
|
Chris@283
|
185 s = s.mid(1, s.length() - 2);
|
Chris@283
|
186 } else if (s.length() >= 2 && s.startsWith("'") && s.endsWith("'")) {
|
Chris@283
|
187 s = s.mid(1, s.length() - 2);
|
Chris@283
|
188 }
|
Chris@148
|
189
|
Chris@283
|
190 if (i == 0 && timingType == CSVFormatDialog::ExplicitTiming) {
|
Chris@148
|
191
|
Chris@283
|
192 bool ok = false;
|
Chris@283
|
193 QString numeric = s;
|
Chris@283
|
194 numeric.remove(nonNumericRx);
|
Chris@148
|
195
|
Chris@283
|
196 if (timeUnits == CSVFormatDialog::TimeSeconds) {
|
Chris@148
|
197
|
Chris@283
|
198 double time = numeric.toDouble(&ok);
|
Chris@283
|
199 frameNo = int(time * sampleRate + 0.00001);
|
Chris@148
|
200
|
Chris@283
|
201 } else {
|
Chris@148
|
202
|
Chris@283
|
203 frameNo = numeric.toInt(&ok);
|
Chris@148
|
204
|
Chris@283
|
205 if (timeUnits == CSVFormatDialog::TimeWindows) {
|
Chris@283
|
206 frameNo *= windowSize;
|
Chris@283
|
207 }
|
Chris@283
|
208 }
|
Chris@148
|
209
|
Chris@283
|
210 if (!ok) {
|
Chris@283
|
211 if (warnings < warnLimit) {
|
Chris@283
|
212 std::cerr << "WARNING: CSVFileReader::load: "
|
Chris@283
|
213 << "Bad time format (\"" << s.toStdString()
|
Chris@283
|
214 << "\") in data line "
|
Chris@283
|
215 << lineno << ":" << std::endl;
|
Chris@283
|
216 std::cerr << line.toStdString() << std::endl;
|
Chris@283
|
217 } else if (warnings == warnLimit) {
|
Chris@283
|
218 std::cerr << "WARNING: Too many warnings" << std::endl;
|
Chris@283
|
219 }
|
Chris@283
|
220 ++warnings;
|
Chris@283
|
221 }
|
Chris@283
|
222 } else {
|
Chris@283
|
223 tidyList.push_back(s);
|
Chris@283
|
224 }
|
Chris@283
|
225 }
|
Chris@148
|
226
|
Chris@283
|
227 if (modelType == CSVFormatDialog::OneDimensionalModel) {
|
Chris@148
|
228
|
Chris@283
|
229 SparseOneDimensionalModel::Point point
|
Chris@283
|
230 (frameNo,
|
Chris@283
|
231 tidyList.size() > 0 ? tidyList[tidyList.size()-1] :
|
Chris@283
|
232 QString("%1").arg(lineno));
|
Chris@148
|
233
|
Chris@283
|
234 model1->addPoint(point);
|
Chris@148
|
235
|
Chris@283
|
236 } else if (modelType == CSVFormatDialog::TwoDimensionalModel) {
|
Chris@148
|
237
|
Chris@283
|
238 SparseTimeValueModel::Point point
|
Chris@283
|
239 (frameNo,
|
Chris@283
|
240 tidyList.size() > 0 ? tidyList[0].toFloat() : 0.0,
|
Chris@283
|
241 tidyList.size() > 1 ? tidyList[1] : QString("%1").arg(lineno));
|
Chris@148
|
242
|
Chris@283
|
243 model2->addPoint(point);
|
Chris@148
|
244
|
Chris@283
|
245 } else if (modelType == CSVFormatDialog::ThreeDimensionalModel) {
|
Chris@148
|
246
|
Chris@283
|
247 DenseThreeDimensionalModel::Column values;
|
Chris@148
|
248
|
Chris@283
|
249 for (int i = 0; i < tidyList.size(); ++i) {
|
Chris@148
|
250
|
Chris@283
|
251 bool ok = false;
|
Chris@283
|
252 float value = list[i].toFloat(&ok);
|
Chris@283
|
253 values.push_back(value);
|
Chris@148
|
254
|
Chris@283
|
255 if ((lineno == 0 && i == 0) || value < min) min = value;
|
Chris@283
|
256 if ((lineno == 0 && i == 0) || value > max) max = value;
|
Chris@148
|
257
|
Chris@283
|
258 if (!ok) {
|
Chris@283
|
259 if (warnings < warnLimit) {
|
Chris@283
|
260 std::cerr << "WARNING: CSVFileReader::load: "
|
Chris@390
|
261 << "Non-numeric value \""
|
Chris@390
|
262 << list[i].toStdString()
|
Chris@390
|
263 << "\" in data line " << lineno
|
Chris@283
|
264 << ":" << std::endl;
|
Chris@283
|
265 std::cerr << line.toStdString() << std::endl;
|
Chris@283
|
266 ++warnings;
|
Chris@283
|
267 } else if (warnings == warnLimit) {
|
Chris@390
|
268 // std::cerr << "WARNING: Too many warnings" << std::endl;
|
Chris@283
|
269 }
|
Chris@283
|
270 }
|
Chris@283
|
271 }
|
Chris@148
|
272
|
Chris@390
|
273 // std::cerr << "Setting bin values for count " << lineno << ", frame "
|
Chris@390
|
274 // << frameNo << ", time " << RealTime::frame2RealTime(frameNo, sampleRate) << std::endl;
|
Chris@148
|
275
|
Chris@283
|
276 model3->setColumn(frameNo / model3->getResolution(), values);
|
Chris@283
|
277 }
|
Chris@148
|
278
|
Chris@283
|
279 ++lineno;
|
Chris@283
|
280 if (timingType == CSVFormatDialog::ImplicitTiming ||
|
Chris@283
|
281 list.size() == 0) {
|
Chris@283
|
282 frameNo += windowSize;
|
Chris@283
|
283 }
|
Chris@283
|
284 }
|
Chris@148
|
285 }
|
Chris@148
|
286
|
Chris@148
|
287 if (modelType == CSVFormatDialog::ThreeDimensionalModel) {
|
Chris@148
|
288 model3->setMinimumLevel(min);
|
Chris@148
|
289 model3->setMaximumLevel(max);
|
Chris@148
|
290 }
|
Chris@148
|
291
|
Chris@148
|
292 return model;
|
Chris@148
|
293 }
|
Chris@148
|
294
|
Chris@148
|
295
|
Chris@148
|
296 CSVFormatDialog::CSVFormatDialog(QWidget *parent, QFile *file,
|
Chris@148
|
297 size_t defaultSampleRate) :
|
Chris@148
|
298 QDialog(parent),
|
Chris@148
|
299 m_modelType(OneDimensionalModel),
|
Chris@148
|
300 m_timingType(ExplicitTiming),
|
Chris@148
|
301 m_timeUnits(TimeAudioFrames),
|
Chris@390
|
302 m_separator(""),
|
Chris@390
|
303 m_behaviour(QString::KeepEmptyParts)
|
Chris@148
|
304 {
|
Chris@148
|
305 setModal(true);
|
Chris@148
|
306 setWindowTitle(tr("Select Data Format"));
|
Chris@148
|
307
|
Chris@148
|
308 (void)guessFormat(file);
|
Chris@148
|
309
|
Chris@148
|
310 QGridLayout *layout = new QGridLayout;
|
Chris@148
|
311
|
Chris@308
|
312 layout->addWidget(new QLabel(tr("<b>Select Data Format</b><p>Please select the correct data format for this file.")),
|
Chris@148
|
313 0, 0, 1, 4);
|
Chris@148
|
314
|
Chris@148
|
315 layout->addWidget(new QLabel(tr("Each row specifies:")), 1, 0);
|
Chris@148
|
316
|
Chris@148
|
317 m_modelTypeCombo = new QComboBox;
|
Chris@148
|
318 m_modelTypeCombo->addItem(tr("A point in time"));
|
Chris@148
|
319 m_modelTypeCombo->addItem(tr("A value at a time"));
|
Chris@148
|
320 m_modelTypeCombo->addItem(tr("A set of values"));
|
Chris@148
|
321 layout->addWidget(m_modelTypeCombo, 1, 1, 1, 2);
|
Chris@148
|
322 connect(m_modelTypeCombo, SIGNAL(activated(int)),
|
Chris@148
|
323 this, SLOT(modelTypeChanged(int)));
|
Chris@148
|
324 m_modelTypeCombo->setCurrentIndex(int(m_modelType));
|
Chris@148
|
325
|
Chris@148
|
326 layout->addWidget(new QLabel(tr("The first column contains:")), 2, 0);
|
Chris@148
|
327
|
Chris@148
|
328 m_timingTypeCombo = new QComboBox;
|
Chris@148
|
329 m_timingTypeCombo->addItem(tr("Time, in seconds"));
|
Chris@148
|
330 m_timingTypeCombo->addItem(tr("Time, in audio sample frames"));
|
Chris@148
|
331 m_timingTypeCombo->addItem(tr("Data (rows are consecutive in time)"));
|
Chris@148
|
332 layout->addWidget(m_timingTypeCombo, 2, 1, 1, 2);
|
Chris@148
|
333 connect(m_timingTypeCombo, SIGNAL(activated(int)),
|
Chris@148
|
334 this, SLOT(timingTypeChanged(int)));
|
Chris@148
|
335 m_timingTypeCombo->setCurrentIndex(m_timingType == ExplicitTiming ?
|
Chris@148
|
336 m_timeUnits == TimeSeconds ? 0 : 1 : 2);
|
Chris@148
|
337
|
Chris@148
|
338 m_sampleRateLabel = new QLabel(tr("Audio sample rate (Hz):"));
|
Chris@148
|
339 layout->addWidget(m_sampleRateLabel, 3, 0);
|
Chris@148
|
340
|
Chris@148
|
341 size_t sampleRates[] = {
|
Chris@148
|
342 8000, 11025, 12000, 22050, 24000, 32000,
|
Chris@148
|
343 44100, 48000, 88200, 96000, 176400, 192000
|
Chris@148
|
344 };
|
Chris@148
|
345
|
Chris@148
|
346 m_sampleRateCombo = new QComboBox;
|
Chris@148
|
347 m_sampleRate = defaultSampleRate;
|
Chris@148
|
348 for (size_t i = 0; i < sizeof(sampleRates) / sizeof(sampleRates[0]); ++i) {
|
Chris@148
|
349 m_sampleRateCombo->addItem(QString("%1").arg(sampleRates[i]));
|
Chris@148
|
350 if (sampleRates[i] == m_sampleRate) m_sampleRateCombo->setCurrentIndex(i);
|
Chris@148
|
351 }
|
Chris@148
|
352 m_sampleRateCombo->setEditable(true);
|
Chris@148
|
353
|
Chris@148
|
354 layout->addWidget(m_sampleRateCombo, 3, 1);
|
Chris@148
|
355 connect(m_sampleRateCombo, SIGNAL(activated(QString)),
|
Chris@148
|
356 this, SLOT(sampleRateChanged(QString)));
|
Chris@148
|
357 connect(m_sampleRateCombo, SIGNAL(editTextChanged(QString)),
|
Chris@148
|
358 this, SLOT(sampleRateChanged(QString)));
|
Chris@148
|
359
|
Chris@148
|
360 m_windowSizeLabel = new QLabel(tr("Frame increment between rows:"));
|
Chris@148
|
361 layout->addWidget(m_windowSizeLabel, 4, 0);
|
Chris@148
|
362
|
Chris@148
|
363 m_windowSizeCombo = new QComboBox;
|
Chris@148
|
364 m_windowSize = 1024;
|
Chris@148
|
365 for (int i = 0; i <= 16; ++i) {
|
Chris@148
|
366 int value = 1 << i;
|
Chris@148
|
367 m_windowSizeCombo->addItem(QString("%1").arg(value));
|
Chris@259
|
368 if (value == int(m_windowSize)) m_windowSizeCombo->setCurrentIndex(i);
|
Chris@148
|
369 }
|
Chris@148
|
370 m_windowSizeCombo->setEditable(true);
|
Chris@148
|
371
|
Chris@148
|
372 layout->addWidget(m_windowSizeCombo, 4, 1);
|
Chris@148
|
373 connect(m_windowSizeCombo, SIGNAL(activated(QString)),
|
Chris@148
|
374 this, SLOT(windowSizeChanged(QString)));
|
Chris@148
|
375 connect(m_windowSizeCombo, SIGNAL(editTextChanged(QString)),
|
Chris@148
|
376 this, SLOT(windowSizeChanged(QString)));
|
Chris@148
|
377
|
Chris@148
|
378 layout->addWidget(new QLabel(tr("\nExample data from file:")), 5, 0, 1, 4);
|
Chris@148
|
379
|
Chris@148
|
380 m_exampleWidget = new QTableWidget
|
Chris@148
|
381 (std::min(10, m_example.size()), m_maxExampleCols);
|
Chris@148
|
382
|
Chris@148
|
383 layout->addWidget(m_exampleWidget, 6, 0, 1, 4);
|
Chris@148
|
384 layout->setColumnStretch(3, 10);
|
Chris@148
|
385 layout->setRowStretch(4, 10);
|
Chris@148
|
386
|
Chris@148
|
387 QPushButton *ok = new QPushButton(tr("OK"));
|
Chris@148
|
388 connect(ok, SIGNAL(clicked()), this, SLOT(accept()));
|
Chris@148
|
389 ok->setDefault(true);
|
Chris@148
|
390
|
Chris@148
|
391 QPushButton *cancel = new QPushButton(tr("Cancel"));
|
Chris@148
|
392 connect(cancel, SIGNAL(clicked()), this, SLOT(reject()));
|
Chris@148
|
393
|
Chris@148
|
394 QHBoxLayout *buttonLayout = new QHBoxLayout;
|
Chris@148
|
395 buttonLayout->addStretch(1);
|
Chris@148
|
396 buttonLayout->addWidget(ok);
|
Chris@148
|
397 buttonLayout->addWidget(cancel);
|
Chris@148
|
398
|
Chris@148
|
399 QVBoxLayout *mainLayout = new QVBoxLayout;
|
Chris@148
|
400 mainLayout->addLayout(layout);
|
Chris@148
|
401 mainLayout->addLayout(buttonLayout);
|
Chris@148
|
402
|
Chris@148
|
403 setLayout(mainLayout);
|
Chris@148
|
404
|
Chris@148
|
405 timingTypeChanged(m_timingTypeCombo->currentIndex());
|
Chris@148
|
406 }
|
Chris@148
|
407
|
Chris@148
|
408 CSVFormatDialog::~CSVFormatDialog()
|
Chris@148
|
409 {
|
Chris@148
|
410 }
|
Chris@148
|
411
|
Chris@148
|
412 void
|
Chris@148
|
413 CSVFormatDialog::populateExample()
|
Chris@148
|
414 {
|
Chris@148
|
415 m_exampleWidget->setColumnCount
|
Chris@148
|
416 (m_timingType == ExplicitTiming ?
|
Chris@148
|
417 m_maxExampleCols - 1 : m_maxExampleCols);
|
Chris@148
|
418
|
Chris@148
|
419 m_exampleWidget->setHorizontalHeaderLabels(QStringList());
|
Chris@148
|
420
|
Chris@148
|
421 for (int i = 0; i < m_example.size(); ++i) {
|
Chris@148
|
422 for (int j = 0; j < m_example[i].size(); ++j) {
|
Chris@148
|
423
|
Chris@148
|
424 QTableWidgetItem *item = new QTableWidgetItem(m_example[i][j]);
|
Chris@148
|
425
|
Chris@148
|
426 if (j == 0) {
|
Chris@148
|
427 if (m_timingType == ExplicitTiming) {
|
Chris@148
|
428 m_exampleWidget->setVerticalHeaderItem(i, item);
|
Chris@148
|
429 continue;
|
Chris@148
|
430 } else {
|
Chris@148
|
431 QTableWidgetItem *header =
|
Chris@148
|
432 new QTableWidgetItem(QString("%1").arg(i));
|
Chris@148
|
433 header->setFlags(Qt::ItemIsEnabled);
|
Chris@148
|
434 m_exampleWidget->setVerticalHeaderItem(i, header);
|
Chris@148
|
435 }
|
Chris@148
|
436 }
|
Chris@148
|
437 int index = j;
|
Chris@148
|
438 if (m_timingType == ExplicitTiming) --index;
|
Chris@148
|
439 item->setFlags(Qt::ItemIsEnabled);
|
Chris@148
|
440 m_exampleWidget->setItem(i, index, item);
|
Chris@148
|
441 }
|
Chris@148
|
442 }
|
Chris@148
|
443 }
|
Chris@148
|
444
|
Chris@148
|
445 void
|
Chris@148
|
446 CSVFormatDialog::modelTypeChanged(int type)
|
Chris@148
|
447 {
|
Chris@148
|
448 m_modelType = (ModelType)type;
|
Chris@148
|
449
|
Chris@148
|
450 if (m_modelType == ThreeDimensionalModel) {
|
Chris@148
|
451 // We can't load 3d models with explicit timing, because the 3d
|
Chris@148
|
452 // model is dense so we need a fixed sample increment
|
Chris@148
|
453 m_timingTypeCombo->setCurrentIndex(2);
|
Chris@148
|
454 timingTypeChanged(2);
|
Chris@148
|
455 }
|
Chris@148
|
456 }
|
Chris@148
|
457
|
Chris@148
|
458 void
|
Chris@148
|
459 CSVFormatDialog::timingTypeChanged(int type)
|
Chris@148
|
460 {
|
Chris@148
|
461 switch (type) {
|
Chris@148
|
462
|
Chris@148
|
463 case 0:
|
Chris@148
|
464 m_timingType = ExplicitTiming;
|
Chris@148
|
465 m_timeUnits = TimeSeconds;
|
Chris@148
|
466 m_sampleRateCombo->setEnabled(false);
|
Chris@148
|
467 m_sampleRateLabel->setEnabled(false);
|
Chris@148
|
468 m_windowSizeCombo->setEnabled(false);
|
Chris@148
|
469 m_windowSizeLabel->setEnabled(false);
|
Chris@148
|
470 if (m_modelType == ThreeDimensionalModel) {
|
Chris@148
|
471 m_modelTypeCombo->setCurrentIndex(1);
|
Chris@148
|
472 modelTypeChanged(1);
|
Chris@148
|
473 }
|
Chris@148
|
474 break;
|
Chris@148
|
475
|
Chris@148
|
476 case 1:
|
Chris@148
|
477 m_timingType = ExplicitTiming;
|
Chris@148
|
478 m_timeUnits = TimeAudioFrames;
|
Chris@148
|
479 m_sampleRateCombo->setEnabled(true);
|
Chris@148
|
480 m_sampleRateLabel->setEnabled(true);
|
Chris@148
|
481 m_windowSizeCombo->setEnabled(false);
|
Chris@148
|
482 m_windowSizeLabel->setEnabled(false);
|
Chris@148
|
483 if (m_modelType == ThreeDimensionalModel) {
|
Chris@148
|
484 m_modelTypeCombo->setCurrentIndex(1);
|
Chris@148
|
485 modelTypeChanged(1);
|
Chris@148
|
486 }
|
Chris@148
|
487 break;
|
Chris@148
|
488
|
Chris@148
|
489 case 2:
|
Chris@148
|
490 m_timingType = ImplicitTiming;
|
Chris@148
|
491 m_timeUnits = TimeWindows;
|
Chris@148
|
492 m_sampleRateCombo->setEnabled(true);
|
Chris@148
|
493 m_sampleRateLabel->setEnabled(true);
|
Chris@148
|
494 m_windowSizeCombo->setEnabled(true);
|
Chris@148
|
495 m_windowSizeLabel->setEnabled(true);
|
Chris@148
|
496 break;
|
Chris@148
|
497 }
|
Chris@148
|
498
|
Chris@148
|
499 populateExample();
|
Chris@148
|
500 }
|
Chris@148
|
501
|
Chris@148
|
502 void
|
Chris@148
|
503 CSVFormatDialog::sampleRateChanged(QString rateString)
|
Chris@148
|
504 {
|
Chris@148
|
505 bool ok = false;
|
Chris@148
|
506 int sampleRate = rateString.toInt(&ok);
|
Chris@148
|
507 if (ok) m_sampleRate = sampleRate;
|
Chris@148
|
508 }
|
Chris@148
|
509
|
Chris@148
|
510 void
|
Chris@148
|
511 CSVFormatDialog::windowSizeChanged(QString sizeString)
|
Chris@148
|
512 {
|
Chris@148
|
513 bool ok = false;
|
Chris@148
|
514 int size = sizeString.toInt(&ok);
|
Chris@148
|
515 if (ok) m_windowSize = size;
|
Chris@148
|
516 }
|
Chris@148
|
517
|
Chris@148
|
518 bool
|
Chris@148
|
519 CSVFormatDialog::guessFormat(QFile *file)
|
Chris@148
|
520 {
|
Chris@148
|
521 QTextStream in(file);
|
Chris@148
|
522 in.seek(0);
|
Chris@148
|
523
|
Chris@148
|
524 unsigned int lineno = 0;
|
Chris@148
|
525
|
Chris@148
|
526 bool nonIncreasingPrimaries = false;
|
Chris@148
|
527 bool nonNumericPrimaries = false;
|
Chris@148
|
528 bool floatPrimaries = false;
|
Chris@148
|
529 bool variableItemCount = false;
|
Chris@148
|
530 int itemCount = 1;
|
Chris@148
|
531 int earliestNonNumericItem = -1;
|
Chris@148
|
532
|
Chris@148
|
533 float prevPrimary = 0.0;
|
Chris@148
|
534
|
Chris@148
|
535 m_maxExampleCols = 0;
|
Chris@148
|
536
|
Chris@148
|
537 while (!in.atEnd()) {
|
Chris@148
|
538
|
Chris@283
|
539 // See comment about line endings in load() above
|
Chris@148
|
540
|
Chris@283
|
541 QString chunk = in.readLine();
|
Chris@283
|
542 QStringList lines = chunk.split('\r', QString::SkipEmptyParts);
|
Chris@148
|
543
|
Chris@283
|
544 for (size_t li = 0; li < lines.size(); ++li) {
|
Chris@283
|
545
|
Chris@283
|
546 QString line = lines[li];
|
Chris@283
|
547
|
Chris@283
|
548 if (line.startsWith("#")) continue;
|
Chris@283
|
549
|
Chris@390
|
550 m_behaviour = QString::KeepEmptyParts;
|
Chris@390
|
551
|
Chris@283
|
552 if (m_separator == "") {
|
Chris@283
|
553 //!!! to do: ask the user
|
Chris@283
|
554 if (line.split(",").size() >= 2) m_separator = ",";
|
Chris@283
|
555 else if (line.split("\t").size() >= 2) m_separator = "\t";
|
Chris@283
|
556 else if (line.split("|").size() >= 2) m_separator = "|";
|
Chris@283
|
557 else if (line.split("/").size() >= 2) m_separator = "/";
|
Chris@283
|
558 else if (line.split(":").size() >= 2) m_separator = ":";
|
Chris@390
|
559 else {
|
Chris@390
|
560 m_separator = " ";
|
Chris@390
|
561 m_behaviour = QString::SkipEmptyParts;
|
Chris@390
|
562 }
|
Chris@283
|
563 }
|
Chris@283
|
564
|
Chris@390
|
565 QStringList list = line.split(m_separator, m_behaviour);
|
Chris@283
|
566 QStringList tidyList;
|
Chris@283
|
567
|
Chris@283
|
568 for (int i = 0; i < list.size(); ++i) {
|
Chris@148
|
569
|
Chris@283
|
570 QString s(list[i]);
|
Chris@283
|
571 bool numeric = false;
|
Chris@148
|
572
|
Chris@283
|
573 if (s.length() >= 2 && s.startsWith("\"") && s.endsWith("\"")) {
|
Chris@283
|
574 s = s.mid(1, s.length() - 2);
|
Chris@283
|
575 } else if (s.length() >= 2 && s.startsWith("'") && s.endsWith("'")) {
|
Chris@283
|
576 s = s.mid(1, s.length() - 2);
|
Chris@283
|
577 } else {
|
Chris@283
|
578 (void)s.toFloat(&numeric);
|
Chris@283
|
579 }
|
Chris@148
|
580
|
Chris@283
|
581 tidyList.push_back(s);
|
Chris@148
|
582
|
Chris@283
|
583 if (lineno == 0 || (list.size() < itemCount)) {
|
Chris@283
|
584 itemCount = list.size();
|
Chris@283
|
585 } else {
|
Chris@283
|
586 if (itemCount != list.size()) {
|
Chris@283
|
587 variableItemCount = true;
|
Chris@283
|
588 }
|
Chris@283
|
589 }
|
Chris@148
|
590
|
Chris@283
|
591 if (i == 0) { // primary
|
Chris@148
|
592
|
Chris@283
|
593 if (numeric) {
|
Chris@148
|
594
|
Chris@283
|
595 float primary = s.toFloat();
|
Chris@148
|
596
|
Chris@283
|
597 if (lineno > 0 && primary <= prevPrimary) {
|
Chris@283
|
598 nonIncreasingPrimaries = true;
|
Chris@283
|
599 }
|
Chris@148
|
600
|
Chris@283
|
601 if (s.contains(".") || s.contains(",")) {
|
Chris@283
|
602 floatPrimaries = true;
|
Chris@283
|
603 }
|
Chris@148
|
604
|
Chris@283
|
605 prevPrimary = primary;
|
Chris@148
|
606
|
Chris@283
|
607 } else {
|
Chris@283
|
608 nonNumericPrimaries = true;
|
Chris@283
|
609 }
|
Chris@283
|
610 } else { // secondary
|
Chris@148
|
611
|
Chris@283
|
612 if (!numeric) {
|
Chris@283
|
613 if (earliestNonNumericItem < 0 ||
|
Chris@283
|
614 i < earliestNonNumericItem) {
|
Chris@283
|
615 earliestNonNumericItem = i;
|
Chris@283
|
616 }
|
Chris@283
|
617 }
|
Chris@283
|
618 }
|
Chris@283
|
619 }
|
Chris@148
|
620
|
Chris@283
|
621 if (lineno < 10) {
|
Chris@283
|
622 m_example.push_back(tidyList);
|
Chris@283
|
623 if (lineno == 0 || tidyList.size() > m_maxExampleCols) {
|
Chris@283
|
624 m_maxExampleCols = tidyList.size();
|
Chris@283
|
625 }
|
Chris@283
|
626 }
|
Chris@148
|
627
|
Chris@283
|
628 ++lineno;
|
Chris@148
|
629
|
Chris@283
|
630 if (lineno == 50) break;
|
Chris@283
|
631 }
|
Chris@148
|
632 }
|
Chris@148
|
633
|
Chris@148
|
634 if (nonNumericPrimaries || nonIncreasingPrimaries) {
|
Chris@148
|
635
|
Chris@148
|
636 // Primaries are probably not a series of times
|
Chris@148
|
637
|
Chris@148
|
638 m_timingType = ImplicitTiming;
|
Chris@148
|
639 m_timeUnits = TimeWindows;
|
Chris@148
|
640
|
Chris@148
|
641 if (nonNumericPrimaries) {
|
Chris@148
|
642 m_modelType = OneDimensionalModel;
|
Chris@148
|
643 } else if (itemCount == 1 || variableItemCount ||
|
Chris@148
|
644 (earliestNonNumericItem != -1)) {
|
Chris@148
|
645 m_modelType = TwoDimensionalModel;
|
Chris@148
|
646 } else {
|
Chris@148
|
647 m_modelType = ThreeDimensionalModel;
|
Chris@148
|
648 }
|
Chris@148
|
649
|
Chris@148
|
650 } else {
|
Chris@148
|
651
|
Chris@148
|
652 // Increasing numeric primaries -- likely to be time
|
Chris@148
|
653
|
Chris@148
|
654 m_timingType = ExplicitTiming;
|
Chris@148
|
655
|
Chris@148
|
656 if (floatPrimaries) {
|
Chris@148
|
657 m_timeUnits = TimeSeconds;
|
Chris@148
|
658 } else {
|
Chris@148
|
659 m_timeUnits = TimeAudioFrames;
|
Chris@148
|
660 }
|
Chris@148
|
661
|
Chris@148
|
662 if (itemCount == 1) {
|
Chris@148
|
663 m_modelType = OneDimensionalModel;
|
Chris@148
|
664 } else if (variableItemCount || (earliestNonNumericItem != -1)) {
|
Chris@148
|
665 if (earliestNonNumericItem != -1 && earliestNonNumericItem < 2) {
|
Chris@148
|
666 m_modelType = OneDimensionalModel;
|
Chris@148
|
667 } else {
|
Chris@148
|
668 m_modelType = TwoDimensionalModel;
|
Chris@148
|
669 }
|
Chris@148
|
670 } else {
|
Chris@148
|
671 m_modelType = ThreeDimensionalModel;
|
Chris@148
|
672 }
|
Chris@148
|
673 }
|
Chris@148
|
674
|
Chris@148
|
675 std::cerr << "Estimated model type: " << m_modelType << std::endl;
|
Chris@148
|
676 std::cerr << "Estimated timing type: " << m_timingType << std::endl;
|
Chris@148
|
677 std::cerr << "Estimated units: " << m_timeUnits << std::endl;
|
Chris@148
|
678
|
Chris@148
|
679 in.seek(0);
|
Chris@148
|
680 return true;
|
Chris@148
|
681 }
|