annotate data/fileio/CSVFileReader.cpp @ 1778:59d9dcfd67c2

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