annotate data/fileio/CSVFileReader.cpp @ 1804:343ef2a866a4

Implement missing TabularModel editing methods. Also made these pure in TabularModel, since almost all subclasses want them and (clearly) forgetting to implement them is a problem!
author Chris Cannam
date Mon, 14 Oct 2019 14:17:37 +0100
parents f0ffc88a36b3
children 2654bf447a84
rev   line source
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@631 20 #include "base/StringBits.h"
Chris@1491 21 #include "base/ProgressReporter.h"
Chris@1519 22 #include "base/RecordDirectory.h"
Chris@148 23 #include "model/SparseOneDimensionalModel.h"
Chris@148 24 #include "model/SparseTimeValueModel.h"
Chris@152 25 #include "model/EditableDenseThreeDimensionalModel.h"
Chris@628 26 #include "model/RegionModel.h"
Chris@897 27 #include "model/NoteModel.h"
Chris@1793 28 #include "model/BoxModel.h"
Chris@1488 29 #include "model/WritableWaveFileModel.h"
Chris@308 30 #include "DataFileReaderFactory.h"
Chris@148 31
Chris@148 32 #include <QFile>
Chris@1519 33 #include <QDir>
Chris@1030 34 #include <QFileInfo>
Chris@148 35 #include <QString>
Chris@148 36 #include <QRegExp>
Chris@148 37 #include <QStringList>
Chris@148 38 #include <QTextStream>
Chris@1519 39 #include <QDateTime>
Chris@148 40
Chris@148 41 #include <iostream>
Chris@628 42 #include <map>
Chris@1428 43 #include <string>
Chris@148 44
Chris@1113 45 using namespace std;
Chris@1113 46
Chris@392 47 CSVFileReader::CSVFileReader(QString path, CSVFormat format,
Chris@1491 48 sv_samplerate_t mainModelSampleRate,
Chris@1491 49 ProgressReporter *reporter) :
Chris@392 50 m_format(format),
Chris@1582 51 m_device(nullptr),
Chris@1009 52 m_ownDevice(true),
Chris@631 53 m_warnings(0),
Chris@1491 54 m_mainModelSampleRate(mainModelSampleRate),
Chris@1491 55 m_fileSize(0),
Chris@1491 56 m_readCount(0),
Chris@1492 57 m_progress(-1),
Chris@1491 58 m_reporter(reporter)
Chris@148 59 {
Chris@1009 60 QFile *file = new QFile(path);
Chris@148 61 bool good = false;
Chris@148 62
Chris@1009 63 if (!file->exists()) {
Chris@1429 64 m_error = QFile::tr("File \"%1\" does not exist").arg(path);
Chris@1009 65 } else if (!file->open(QIODevice::ReadOnly | QIODevice::Text)) {
Chris@1429 66 m_error = QFile::tr("Failed to open file \"%1\"").arg(path);
Chris@148 67 } else {
Chris@1429 68 good = true;
Chris@148 69 }
Chris@148 70
Chris@1009 71 if (good) {
Chris@1009 72 m_device = file;
Chris@1030 73 m_filename = QFileInfo(path).fileName();
Chris@1491 74 m_fileSize = file->size();
Chris@1491 75 if (m_reporter) m_reporter->setDefinite(true);
Chris@1009 76 } else {
Chris@1429 77 delete file;
Chris@148 78 }
Chris@148 79 }
Chris@148 80
Chris@1009 81 CSVFileReader::CSVFileReader(QIODevice *device, CSVFormat format,
Chris@1491 82 sv_samplerate_t mainModelSampleRate,
Chris@1491 83 ProgressReporter *reporter) :
Chris@1009 84 m_format(format),
Chris@1009 85 m_device(device),
Chris@1009 86 m_ownDevice(false),
Chris@1009 87 m_warnings(0),
Chris@1491 88 m_mainModelSampleRate(mainModelSampleRate),
Chris@1491 89 m_fileSize(0),
Chris@1491 90 m_readCount(0),
Chris@1492 91 m_progress(-1),
Chris@1491 92 m_reporter(reporter)
Chris@1009 93 {
Chris@1491 94 if (m_reporter) m_reporter->setDefinite(false);
Chris@1009 95 }
Chris@1009 96
Chris@148 97 CSVFileReader::~CSVFileReader()
Chris@148 98 {
Chris@1009 99 SVDEBUG << "CSVFileReader::~CSVFileReader: device is " << m_device << endl;
Chris@148 100
Chris@1009 101 if (m_device && m_ownDevice) {
Chris@1009 102 SVDEBUG << "CSVFileReader::CSVFileReader: Closing device" << endl;
Chris@1009 103 m_device->close();
Chris@1009 104 delete m_device;
Chris@148 105 }
Chris@148 106 }
Chris@148 107
Chris@148 108 bool
Chris@148 109 CSVFileReader::isOK() const
Chris@148 110 {
Chris@1582 111 return (m_device != nullptr);
Chris@148 112 }
Chris@148 113
Chris@148 114 QString
Chris@148 115 CSVFileReader::getError() const
Chris@148 116 {
Chris@148 117 return m_error;
Chris@148 118 }
Chris@148 119
Chris@1038 120 sv_frame_t
Chris@1047 121 CSVFileReader::convertTimeValue(QString s, int lineno,
Chris@1047 122 sv_samplerate_t sampleRate,
Chris@929 123 int windowSize) const
Chris@631 124 {
Chris@631 125 QRegExp nonNumericRx("[^0-9eE.,+-]");
Chris@897 126 int warnLimit = 10;
Chris@631 127
Chris@631 128 CSVFormat::TimeUnits timeUnits = m_format.getTimeUnits();
Chris@631 129
Chris@1038 130 sv_frame_t calculatedFrame = 0;
Chris@631 131
Chris@631 132 bool ok = false;
Chris@631 133 QString numeric = s;
Chris@631 134 numeric.remove(nonNumericRx);
Chris@631 135
Chris@631 136 if (timeUnits == CSVFormat::TimeSeconds) {
Chris@631 137
Chris@631 138 double time = numeric.toDouble(&ok);
Chris@631 139 if (!ok) time = StringBits::stringToDoubleLocaleFree(numeric, &ok);
Chris@1038 140 calculatedFrame = sv_frame_t(time * sampleRate + 0.5);
Chris@990 141
Chris@990 142 } else if (timeUnits == CSVFormat::TimeMilliseconds) {
Chris@990 143
Chris@990 144 double time = numeric.toDouble(&ok);
Chris@990 145 if (!ok) time = StringBits::stringToDoubleLocaleFree(numeric, &ok);
Chris@1038 146 calculatedFrame = sv_frame_t((time / 1000.0) * sampleRate + 0.5);
Chris@631 147
Chris@631 148 } else {
Chris@631 149
Chris@631 150 long n = numeric.toLong(&ok);
Chris@631 151 if (n >= 0) calculatedFrame = n;
Chris@631 152
Chris@631 153 if (timeUnits == CSVFormat::TimeWindows) {
Chris@631 154 calculatedFrame *= windowSize;
Chris@631 155 }
Chris@631 156 }
Chris@631 157
Chris@631 158 if (!ok) {
Chris@631 159 if (m_warnings < warnLimit) {
Chris@1428 160 SVCERR << "WARNING: CSVFileReader::load: "
Chris@844 161 << "Bad time format (\"" << s
Chris@631 162 << "\") in data line "
Chris@843 163 << lineno+1 << endl;
Chris@631 164 } else if (m_warnings == warnLimit) {
Chris@1428 165 SVCERR << "WARNING: Too many warnings" << endl;
Chris@631 166 }
Chris@631 167 ++m_warnings;
Chris@631 168 }
Chris@631 169
Chris@631 170 return calculatedFrame;
Chris@631 171 }
Chris@631 172
Chris@148 173 Model *
Chris@148 174 CSVFileReader::load() const
Chris@148 175 {
Chris@1582 176 if (!m_device) return nullptr;
Chris@148 177
Chris@628 178 CSVFormat::ModelType modelType = m_format.getModelType();
Chris@392 179 CSVFormat::TimingType timingType = m_format.getTimingType();
Chris@628 180 CSVFormat::TimeUnits timeUnits = m_format.getTimeUnits();
Chris@1047 181 sv_samplerate_t sampleRate = m_format.getSampleRate();
Chris@929 182 int windowSize = m_format.getWindowSize();
Chris@631 183 QChar separator = m_format.getSeparator();
Chris@631 184 bool allowQuoting = m_format.getAllowQuoting();
Chris@148 185
Chris@392 186 if (timingType == CSVFormat::ExplicitTiming) {
Chris@611 187 if (modelType == CSVFormat::ThreeDimensionalModel) {
Chris@611 188 // This will be overridden later if more than one line
Chris@611 189 // appears in our file, but we want to choose a default
Chris@611 190 // that's likely to be visible
Chris@611 191 windowSize = 1024;
Chris@611 192 } else {
Chris@611 193 windowSize = 1;
Chris@611 194 }
Chris@1429 195 if (timeUnits == CSVFormat::TimeSeconds ||
Chris@990 196 timeUnits == CSVFormat::TimeMilliseconds) {
Chris@1429 197 sampleRate = m_mainModelSampleRate;
Chris@1429 198 }
Chris@148 199 }
Chris@148 200
Chris@1582 201 SparseOneDimensionalModel *model1 = nullptr;
Chris@1582 202 SparseTimeValueModel *model2 = nullptr;
Chris@1582 203 RegionModel *model2a = nullptr;
Chris@1582 204 NoteModel *model2b = nullptr;
Chris@1793 205 BoxModel *model2c = nullptr;
Chris@1582 206 EditableDenseThreeDimensionalModel *model3 = nullptr;
Chris@1582 207 WritableWaveFileModel *modelW = nullptr;
Chris@1582 208 Model *model = nullptr;
Chris@148 209
Chris@1009 210 QTextStream in(m_device);
Chris@148 211
Chris@148 212 unsigned int warnings = 0, warnLimit = 10;
Chris@148 213 unsigned int lineno = 0;
Chris@148 214
Chris@148 215 float min = 0.0, max = 0.0;
Chris@148 216
Chris@1038 217 sv_frame_t frameNo = 0;
Chris@1038 218 sv_frame_t duration = 0;
Chris@1038 219 sv_frame_t endFrame = 0;
Chris@631 220
Chris@631 221 bool haveAnyValue = false;
Chris@631 222 bool haveEndTime = false;
Chris@897 223 bool pitchLooksLikeMIDI = true;
Chris@631 224
Chris@1038 225 sv_frame_t startFrame = 0; // for calculation of dense model resolution
Chris@631 226 bool firstEverValue = true;
Chris@631 227
Chris@676 228 int valueColumns = 0;
Chris@676 229 for (int i = 0; i < m_format.getColumnCount(); ++i) {
Chris@676 230 if (m_format.getColumnPurpose(i) == CSVFormat::ColumnValue) {
Chris@676 231 ++valueColumns;
Chris@676 232 }
Chris@676 233 }
Chris@676 234
Chris@1518 235 int audioChannels = 0;
Chris@1582 236 float **audioSamples = nullptr;
Chris@1518 237 float sampleShift = 0.f;
Chris@1518 238 float sampleScale = 1.f;
Chris@1518 239
Chris@1518 240 if (modelType == CSVFormat::WaveFileModel) {
Chris@1518 241
Chris@1518 242 audioChannels = valueColumns;
Chris@1518 243
Chris@1518 244 audioSamples =
Chris@1518 245 breakfastquay::allocate_and_zero_channels<float>
Chris@1518 246 (audioChannels, 1);
Chris@1518 247
Chris@1518 248 switch (m_format.getAudioSampleRange()) {
Chris@1518 249 case CSVFormat::SampleRangeSigned1:
Chris@1518 250 case CSVFormat::SampleRangeOther:
Chris@1518 251 sampleShift = 0.f;
Chris@1518 252 sampleScale = 1.f;
Chris@1518 253 break;
Chris@1518 254 case CSVFormat::SampleRangeUnsigned255:
Chris@1518 255 sampleShift = -128.f;
Chris@1518 256 sampleScale = 1.f / 128.f;
Chris@1518 257 break;
Chris@1518 258 case CSVFormat::SampleRangeSigned32767:
Chris@1518 259 sampleShift = 0.f;
Chris@1518 260 sampleScale = 1.f / 32768.f;
Chris@1518 261 break;
Chris@1518 262 }
Chris@1518 263 }
Chris@1518 264
Chris@1518 265 map<QString, int> labelCountMap;
Chris@1518 266
Chris@1509 267 bool abandoned = false;
Chris@1509 268
Chris@1509 269 while (!in.atEnd() && !abandoned) {
Chris@148 270
Chris@283 271 // QTextStream's readLine doesn't cope with old-style Mac
Chris@283 272 // CR-only line endings. Why did they bother making the class
Chris@283 273 // cope with more than one sort of line ending, if it still
Chris@283 274 // can't be configured to cope with all the common sorts?
Chris@148 275
Chris@283 276 // For the time being we'll deal with this case (which is
Chris@283 277 // relatively uncommon for us, but still necessary to handle)
Chris@283 278 // by reading the entire file using a single readLine, and
Chris@283 279 // splitting it. For CR and CR/LF line endings this will just
Chris@283 280 // read a line at a time, and that's obviously OK.
Chris@148 281
Chris@283 282 QString chunk = in.readLine();
Chris@283 283 QStringList lines = chunk.split('\r', QString::SkipEmptyParts);
Chris@1491 284
Chris@1491 285 m_readCount += chunk.size() + 1;
Chris@1491 286
Chris@1491 287 if (m_reporter) {
Chris@1491 288 if (m_reporter->wasCancelled()) {
Chris@1509 289 abandoned = true;
Chris@1491 290 break;
Chris@1491 291 }
Chris@1491 292 int progress;
Chris@1491 293 if (m_fileSize > 0) {
Chris@1491 294 progress = int((double(m_readCount) / double(m_fileSize))
Chris@1491 295 * 100.0);
Chris@1491 296 } else {
Chris@1491 297 progress = int(m_readCount / 10000);
Chris@1491 298 }
Chris@1491 299 if (progress != m_progress) {
Chris@1491 300 m_reporter->setProgress(progress);
Chris@1491 301 m_progress = progress;
Chris@1491 302 }
Chris@1491 303 }
Chris@283 304
Chris@897 305 for (int li = 0; li < lines.size(); ++li) {
Chris@148 306
Chris@283 307 QString line = lines[li];
Chris@1009 308
Chris@283 309 if (line.startsWith("#")) continue;
Chris@283 310
Chris@631 311 QStringList list = StringBits::split(line, separator, allowQuoting);
Chris@283 312 if (!model) {
Chris@283 313
Chris@1519 314 QString modelName = m_filename;
Chris@1519 315
Chris@283 316 switch (modelType) {
Chris@283 317
Chris@392 318 case CSVFormat::OneDimensionalModel:
Chris@283 319 model1 = new SparseOneDimensionalModel(sampleRate, windowSize);
Chris@283 320 model = model1;
Chris@283 321 break;
Chris@1429 322
Chris@392 323 case CSVFormat::TwoDimensionalModel:
Chris@283 324 model2 = new SparseTimeValueModel(sampleRate, windowSize, false);
Chris@283 325 model = model2;
Chris@283 326 break;
Chris@1429 327
Chris@628 328 case CSVFormat::TwoDimensionalModelWithDuration:
Chris@628 329 model2a = new RegionModel(sampleRate, windowSize, false);
Chris@628 330 model = model2a;
Chris@628 331 break;
Chris@1429 332
Chris@897 333 case CSVFormat::TwoDimensionalModelWithDurationAndPitch:
Chris@897 334 model2b = new NoteModel(sampleRate, windowSize, false);
Chris@897 335 model = model2b;
Chris@897 336 break;
Chris@1429 337
Chris@1793 338 case CSVFormat::TwoDimensionalModelWithDurationAndExtent:
Chris@1793 339 model2c = new BoxModel(sampleRate, windowSize, false);
Chris@1793 340 model = model2c;
Chris@1793 341 break;
Chris@1793 342
Chris@392 343 case CSVFormat::ThreeDimensionalModel:
Chris@535 344 model3 = new EditableDenseThreeDimensionalModel
Chris@1777 345 (sampleRate, windowSize, valueColumns);
Chris@283 346 model = model3;
Chris@283 347 break;
Chris@1488 348
Chris@1488 349 case CSVFormat::WaveFileModel:
Chris@1517 350 {
Chris@1517 351 bool normalise = (m_format.getAudioSampleRange()
Chris@1517 352 == CSVFormat::SampleRangeOther);
Chris@1519 353 QString path = getConvertedAudioFilePath();
Chris@1488 354 modelW = new WritableWaveFileModel
Chris@1520 355 (path, sampleRate, valueColumns,
Chris@1520 356 normalise ?
Chris@1520 357 WritableWaveFileModel::Normalisation::Peak :
Chris@1520 358 WritableWaveFileModel::Normalisation::None);
Chris@1519 359 modelName = QFileInfo(path).fileName();
Chris@1488 360 model = modelW;
Chris@1488 361 break;
Chris@283 362 }
Chris@1517 363 }
Chris@1030 364
Chris@1508 365 if (model && model->isOK()) {
Chris@1519 366 if (modelName != "") {
Chris@1519 367 model->setObjectName(modelName);
Chris@1030 368 }
Chris@1030 369 }
Chris@283 370 }
Chris@148 371
Chris@1508 372 if (!model || !model->isOK()) {
Chris@1508 373 SVCERR << "Failed to create model to load CSV file into"
Chris@1508 374 << endl;
Chris@1508 375 if (model) {
Chris@1508 376 delete model;
Chris@1582 377 model = nullptr;
Chris@1793 378 model1 = nullptr; model2 = nullptr;
Chris@1793 379 model2a = nullptr; model2b = nullptr; model2c = nullptr;
Chris@1582 380 model3 = nullptr; modelW = nullptr;
Chris@1508 381 }
Chris@1509 382 abandoned = true;
Chris@1508 383 break;
Chris@1508 384 }
Chris@1508 385
Chris@631 386 float value = 0.f;
Chris@1793 387 float otherValue = 0.f;
Chris@897 388 float pitch = 0.f;
Chris@631 389 QString label = "";
Chris@148 390
Chris@631 391 duration = 0.f;
Chris@631 392 haveEndTime = false;
Chris@1518 393
Chris@283 394 for (int i = 0; i < list.size(); ++i) {
Chris@148 395
Chris@631 396 QString s = list[i];
Chris@631 397
Chris@631 398 CSVFormat::ColumnPurpose purpose = m_format.getColumnPurpose(i);
Chris@631 399
Chris@631 400 switch (purpose) {
Chris@631 401
Chris@631 402 case CSVFormat::ColumnUnknown:
Chris@631 403 break;
Chris@631 404
Chris@631 405 case CSVFormat::ColumnStartTime:
Chris@631 406 frameNo = convertTimeValue(s, lineno, sampleRate, windowSize);
Chris@631 407 break;
Chris@631 408
Chris@631 409 case CSVFormat::ColumnEndTime:
Chris@631 410 endFrame = convertTimeValue(s, lineno, sampleRate, windowSize);
Chris@631 411 haveEndTime = true;
Chris@631 412 break;
Chris@631 413
Chris@631 414 case CSVFormat::ColumnDuration:
Chris@631 415 duration = convertTimeValue(s, lineno, sampleRate, windowSize);
Chris@631 416 break;
Chris@631 417
Chris@631 418 case CSVFormat::ColumnValue:
Chris@1793 419 if (haveAnyValue) {
Chris@1793 420 otherValue = value;
Chris@1793 421 }
Chris@631 422 value = s.toFloat();
Chris@631 423 haveAnyValue = true;
Chris@631 424 break;
Chris@631 425
Chris@897 426 case CSVFormat::ColumnPitch:
Chris@897 427 pitch = s.toFloat();
Chris@897 428 if (pitch < 0.f || pitch > 127.f) {
Chris@897 429 pitchLooksLikeMIDI = false;
Chris@897 430 }
Chris@897 431 break;
Chris@897 432
Chris@631 433 case CSVFormat::ColumnLabel:
Chris@631 434 label = s;
Chris@631 435 break;
Chris@283 436 }
Chris@631 437 }
Chris@148 438
Chris@1113 439 ++labelCountMap[label];
Chris@1113 440
Chris@631 441 if (haveEndTime) { // ... calculate duration now all cols read
Chris@631 442 if (endFrame > frameNo) {
Chris@631 443 duration = endFrame - frameNo;
Chris@628 444 }
Chris@283 445 }
Chris@148 446
Chris@392 447 if (modelType == CSVFormat::OneDimensionalModel) {
Chris@1429 448
Chris@1658 449 Event point(frameNo, label);
Chris@1658 450 model1->add(point);
Chris@148 451
Chris@392 452 } else if (modelType == CSVFormat::TwoDimensionalModel) {
Chris@148 453
Chris@1651 454 Event point(frameNo, value, label);
Chris@1651 455 model2->add(point);
Chris@148 456
Chris@628 457 } else if (modelType == CSVFormat::TwoDimensionalModelWithDuration) {
Chris@628 458
Chris@1649 459 Event region(frameNo, value, duration, label);
Chris@1649 460 model2a->add(region);
Chris@628 461
Chris@897 462 } else if (modelType == CSVFormat::TwoDimensionalModelWithDurationAndPitch) {
Chris@897 463
Chris@897 464 float level = ((value >= 0.f && value <= 1.f) ? value : 1.f);
Chris@1643 465 Event note(frameNo, pitch, duration, level, label);
Chris@1644 466 model2b->add(note);
Chris@897 467
Chris@1793 468 } else if (modelType == CSVFormat::TwoDimensionalModelWithDurationAndExtent) {
Chris@1793 469
Chris@1793 470 float level = 0.f;
Chris@1793 471 if (value > otherValue) {
Chris@1793 472 level = value - otherValue;
Chris@1793 473 value = otherValue;
Chris@1793 474 } else {
Chris@1793 475 level = otherValue - value;
Chris@1793 476 }
Chris@1793 477 Event box(frameNo, value, duration, level, label);
Chris@1793 478 model2c->add(box);
Chris@1793 479
Chris@392 480 } else if (modelType == CSVFormat::ThreeDimensionalModel) {
Chris@148 481
Chris@283 482 DenseThreeDimensionalModel::Column values;
Chris@148 483
Chris@631 484 for (int i = 0; i < list.size(); ++i) {
Chris@148 485
Chris@676 486 if (m_format.getColumnPurpose(i) != CSVFormat::ColumnValue) {
Chris@676 487 continue;
Chris@676 488 }
Chris@676 489
Chris@283 490 bool ok = false;
Chris@283 491 float value = list[i].toFloat(&ok);
Chris@611 492
Chris@676 493 values.push_back(value);
Chris@1429 494
Chris@631 495 if (firstEverValue || value < min) min = value;
Chris@631 496 if (firstEverValue || value > max) max = value;
Chris@676 497
Chris@631 498 if (firstEverValue) {
Chris@611 499 startFrame = frameNo;
Chris@611 500 model3->setStartFrame(startFrame);
Chris@611 501 } else if (lineno == 1 &&
Chris@611 502 timingType == CSVFormat::ExplicitTiming) {
Chris@1038 503 model3->setResolution(int(frameNo - startFrame));
Chris@611 504 }
Chris@631 505
Chris@631 506 firstEverValue = false;
Chris@148 507
Chris@283 508 if (!ok) {
Chris@283 509 if (warnings < warnLimit) {
Chris@1428 510 SVCERR << "WARNING: CSVFileReader::load: "
Chris@390 511 << "Non-numeric value \""
Chris@844 512 << list[i]
Chris@491 513 << "\" in data line " << lineno+1
Chris@843 514 << ":" << endl;
Chris@1428 515 SVCERR << line << endl;
Chris@283 516 ++warnings;
Chris@283 517 } else if (warnings == warnLimit) {
Chris@1428 518 // SVCERR << "WARNING: Too many warnings" << endl;
Chris@283 519 }
Chris@283 520 }
Chris@283 521 }
Chris@1429 522
Chris@690 523 // SVDEBUG << "Setting bin values for count " << lineno << ", frame "
Chris@687 524 // << frameNo << ", time " << RealTime::frame2RealTime(frameNo, sampleRate) << endl;
Chris@148 525
Chris@611 526 model3->setColumn(lineno, values);
Chris@1488 527
Chris@1488 528 } else if (modelType == CSVFormat::WaveFileModel) {
Chris@1488 529
Chris@1518 530 int channel = 0;
Chris@1490 531
Chris@1518 532 for (int i = 0;
Chris@1518 533 i < list.size() && channel < audioChannels;
Chris@1518 534 ++i) {
Chris@1488 535
Chris@1490 536 if (m_format.getColumnPurpose(i) !=
Chris@1490 537 CSVFormat::ColumnValue) {
Chris@1488 538 continue;
Chris@1488 539 }
Chris@1488 540
Chris@1488 541 bool ok = false;
Chris@1488 542 float value = list[i].toFloat(&ok);
Chris@1518 543 if (!ok) {
Chris@1518 544 value = 0.f;
Chris@1518 545 }
Chris@1517 546
Chris@1518 547 value += sampleShift;
Chris@1518 548 value *= sampleScale;
Chris@1488 549
Chris@1518 550 audioSamples[channel][0] = value;
Chris@1490 551
Chris@1490 552 ++channel;
Chris@1488 553 }
Chris@1488 554
Chris@1518 555 while (channel < audioChannels) {
Chris@1518 556 audioSamples[channel][0] = 0.f;
Chris@1518 557 ++channel;
Chris@1518 558 }
Chris@1488 559
Chris@1518 560 bool ok = modelW->addSamples(audioSamples, 1);
Chris@1488 561
Chris@1488 562 if (!ok) {
Chris@1488 563 if (warnings < warnLimit) {
Chris@1488 564 SVCERR << "WARNING: CSVFileReader::load: "
Chris@1488 565 << "Unable to add sample to wave-file model"
Chris@1488 566 << endl;
Chris@1488 567 SVCERR << line << endl;
Chris@1488 568 ++warnings;
Chris@1488 569 }
Chris@1488 570 }
Chris@283 571 }
Chris@1488 572
Chris@283 573 ++lineno;
Chris@392 574 if (timingType == CSVFormat::ImplicitTiming ||
Chris@283 575 list.size() == 0) {
Chris@283 576 frameNo += windowSize;
Chris@283 577 }
Chris@283 578 }
Chris@148 579 }
Chris@148 580
Chris@631 581 if (!haveAnyValue) {
Chris@631 582 if (model2a) {
Chris@631 583 // assign values for regions based on label frequency; we
Chris@631 584 // have this in our labelCountMap, sort of
Chris@631 585
Chris@1113 586 map<int, map<QString, float> > countLabelValueMap;
Chris@1113 587 for (map<QString, int>::iterator i = labelCountMap.begin();
Chris@631 588 i != labelCountMap.end(); ++i) {
Chris@1113 589 countLabelValueMap[i->second][i->first] = -1.f;
Chris@631 590 }
Chris@631 591
Chris@631 592 float v = 0.f;
Chris@1113 593 for (map<int, map<QString, float> >::iterator i =
Chris@631 594 countLabelValueMap.end(); i != countLabelValueMap.begin(); ) {
Chris@631 595 --i;
Chris@1428 596 SVCERR << "count -> " << i->first << endl;
Chris@1113 597 for (map<QString, float>::iterator j = i->second.begin();
Chris@631 598 j != i->second.end(); ++j) {
Chris@631 599 j->second = v;
Chris@1428 600 SVCERR << "label -> " << j->first << ", value " << v << endl;
Chris@631 601 v = v + 1.f;
Chris@631 602 }
Chris@631 603 }
Chris@631 604
Chris@1649 605 map<Event, Event> eventMap;
Chris@1649 606
Chris@1649 607 EventVector allEvents = model2a->getAllEvents();
Chris@1649 608 for (const Event &e: allEvents) {
Chris@1649 609 int count = labelCountMap[e.getLabel()];
Chris@1649 610 v = countLabelValueMap[count][e.getLabel()];
Chris@1649 611 // SVCERR << "mapping from label \"" << p.label
Chris@1649 612 // << "\" (count " << count
Chris@1649 613 // << ") to value " << v << endl;
Chris@1649 614 eventMap[e] = Event(e.getFrame(), v,
Chris@1649 615 e.getDuration(), e.getLabel());
Chris@631 616 }
Chris@631 617
Chris@1649 618 for (const auto &i: eventMap) {
Chris@1113 619 // There could be duplicate regions; if so replace
Chris@1113 620 // them all -- but we need to check we're not
Chris@1113 621 // replacing a region by itself (or else this will
Chris@1113 622 // never terminate)
Chris@1649 623 if (i.first.getValue() == i.second.getValue()) {
Chris@1113 624 continue;
Chris@1113 625 }
Chris@1649 626 while (model2a->containsEvent(i.first)) {
Chris@1649 627 model2a->remove(i.first);
Chris@1649 628 model2a->add(i.second);
Chris@1113 629 }
Chris@631 630 }
Chris@631 631 }
Chris@631 632 }
Chris@631 633
Chris@897 634 if (model2b) {
Chris@897 635 if (pitchLooksLikeMIDI) {
Chris@897 636 model2b->setScaleUnits("MIDI Pitch");
Chris@897 637 } else {
Chris@897 638 model2b->setScaleUnits("Hz");
Chris@897 639 }
Chris@897 640 }
Chris@897 641
Chris@961 642 if (model3) {
Chris@1429 643 model3->setMinimumLevel(min);
Chris@1429 644 model3->setMaximumLevel(max);
Chris@148 645 }
Chris@148 646
Chris@1489 647 if (modelW) {
Chris@1518 648 breakfastquay::deallocate_channels(audioSamples, audioChannels);
Chris@1493 649 modelW->updateModel();
Chris@1489 650 modelW->writeComplete();
Chris@1489 651 }
Chris@1489 652
Chris@148 653 return model;
Chris@148 654 }
Chris@148 655
Chris@1519 656 QString
Chris@1519 657 CSVFileReader::getConvertedAudioFilePath() const
Chris@1519 658 {
Chris@1519 659 QString base = m_filename;
Chris@1519 660 base.replace(QRegExp("[/\\,.:;~<>\"'|?%*]+"), "_");
Chris@1519 661
Chris@1519 662 QString convertedFileDir = RecordDirectory::getConvertedAudioDirectory();
Chris@1519 663 if (convertedFileDir == "") {
Chris@1519 664 SVCERR << "WARNING: CSVFileReader::getConvertedAudioFilePath: Failed to retrieve converted audio directory" << endl;
Chris@1519 665 return "";
Chris@1519 666 }
Chris@1519 667
Chris@1519 668 auto ms = QDateTime::currentDateTime().toMSecsSinceEpoch();
Chris@1519 669 auto s = ms / 1000; // there is a toSecsSinceEpoch in Qt 5.8 but
Chris@1519 670 // we currently want to support older versions
Chris@1519 671
Chris@1519 672 return QDir(convertedFileDir).filePath
Chris@1519 673 (QString("%1-%2.wav").arg(base).arg(s));
Chris@1519 674 }
Chris@1519 675