annotate data/fileio/CSVFileReader.cpp @ 1777:d484490cdf69

Split EditableDenseThreeDimensionalModel into explicitly compressed and uncompressed variants. Simplifies the uncompressed version, and we may want to consider whether we need the compressed one at all.
author Chris Cannam
date Tue, 10 Sep 2019 16:34:47 +0100
parents 5b7b01da430a
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