Entropy Piano Tuner  1.1.3 (documentation not yet complete)
An open-source experimental software for piano tuning by entropy minimization
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
pianofile.cpp
Go to the documentation of this file.
1 /*****************************************************************************
2  * Copyright 2015 Haye Hinrichsen, Christoph Wick
3  *
4  * This file is part of Entropy Piano Tuner.
5  *
6  * Entropy Piano Tuner is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by the
8  * Free Software Foundation, either version 3 of the License, or (at your
9  * option) any later version.
10  *
11  * Entropy Piano Tuner is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
14  * more details.
15  *
16  * You should have received a copy of the GNU General Public License along with
17  * Entropy Piano Tuner. If not, see http://www.gnu.org/licenses/.
18  *****************************************************************************/
19 
20 #include "pianofile.h"
21 
22 #include <iomanip>
23 #include <ctime>
24 #include <sstream>
25 #include <chrono>
26 #include <fstream>
27 #include <cmath>
28 
29 #include "../system/eptexception.h"
30 #include "../adapters/filemanager.h"
31 #include "../system/log.h"
32 #include "../thirdparty/timesupport/timesupport.h"
33 #include "piano.h"
34 
35 const std::string PianoFile::FILE_TYPE_NAME("EntropyPianoTunerFile");
39 
41  : mFileVersion(-1) {
42 }
43 
45 }
46 
47 bool PianoFile::read(const std::string &absolutePath, Piano &piano) {
48  // change locale to be sure to read doubles in classic format
49  std::locale oldLocale(std::locale::global(std::locale::classic()));
50  try {
51  readXmlFile(absolutePath, piano);
52  } catch (...) {
53  std::locale::global(oldLocale);
54  throw;
55  }
56 
57  std::locale::global(oldLocale);
58 
59  return true;
60 }
61 
62 bool PianoFile::write(const std::string &absolutePath, const Piano &piano, piano::FileType fileType) const {
63  // change locale to be sure to write doubles in classic format
64  std::locale oldLocale(std::locale::global(std::locale::classic()));
65  try {
66  if (fileType == piano::FT_EPT) {
67  writeXmlFile(absolutePath, piano);
68  } else if (fileType == piano::FT_CSV) {
69  writeCsvFile(absolutePath, piano);
70  } else {
71  EPT_EXCEPT(EptException::ERR_NOT_IMPLEMENTED, "Writing of file type not supported yet.");
72  }
73  } catch (...) {
74  std::locale::global(oldLocale);
75  throw;
76  }
77 
78  std::locale::global(oldLocale);
79 
80  return true;
81 }
82 
83 void PianoFile::readXmlFile(const std::string &absolutePath, Piano &piano) {
84  auto startTime = std::chrono::high_resolution_clock::now();
85 
86  tinyxml2::XMLDocument doc(true, tinyxml2::COLLAPSE_WHITESPACE);
87  doc.LoadFile(absolutePath.c_str());
88  if (doc.Error()) {
89  std::stringstream errormsg;
90  errormsg << "Error " << doc.ErrorID() << ": " << doc.ErrorName() << " - File: " << absolutePath;
92  }
93 
94  auto parsedTime = std::chrono::high_resolution_clock::now();
95 
96  int parsedDuration = static_cast<int>(std::chrono::duration_cast<std::chrono::milliseconds>(parsedTime - startTime).count());
97  LogI("Succesfully loaded Xml-File: %s. This took %i ms", absolutePath.c_str(), parsedDuration);
98 
99  tinyxml2::XMLElement *root = doc.FirstChildElement();
100  if (!root) {
101  LogW("Xml-document has no root node");
102  return;
103  }
104 
105  // read version
107  root->QueryIntAttribute("version", &mFileVersion);
108 
110  LogW("No file version specified. Trying to continue with minimum supported.");
112  }
113 
114  // check version
116  std::stringstream errormsg;
117  errormsg << "File version " << mFileVersion << " is not supported anymore. Minimum file version is " << MIN_SUPPORTED_FILE_VERSION;
119  }
120 
121  // parse all child elements
122  for (tinyxml2::XMLElement *child = root->FirstChildElement(); child;
123  child = child->NextSiblingElement()) {
124  if (strcmp(child->Value(), "piano") == 0) {
125  read(child, piano);
126  }
127  }
128 
129  auto contentParseTime = std::chrono::high_resolution_clock::now();
130 
131  int contentTime = static_cast<int>(std::chrono::duration_cast<std::chrono::milliseconds>(contentParseTime - parsedTime).count());
132  int totalTime = static_cast<int>(std::chrono::duration_cast<std::chrono::milliseconds>(contentParseTime - startTime).count());
133 
134  LogI("Content parsed in %i ms. Complete file opening took %i ms.", contentTime, totalTime);
135 
136 }
137 
138 void PianoFile::writeXmlFile(const std::string &absolutePath, const Piano &piano) const {
139  tinyxml2::XMLDocument doc;
140 
141  // create xml declaration header
142  doc.InsertEndChild(doc.NewDeclaration());
143 
144  tinyxml2::XMLElement *root = doc.NewElement("entropyPianoTunerFile");
145  doc.InsertEndChild(root);
146  root->SetAttribute("version", CURRENT_FILE_VERSION);
147 
148  // piano
149  tinyxml2::XMLElement *pianoElement = doc.NewElement("piano");
150  root->InsertEndChild(pianoElement);
151  write(pianoElement, piano);
152 
153  // save the file
154  doc.SaveFile(absolutePath.c_str());
155  if (doc.Error()) {
156  std::stringstream errormsg;
157  errormsg << "Error " << doc.ErrorID() << ": " << doc.ErrorName() << " - File: " << absolutePath;
159  }
160 
161  LogI("Succesfully saved Xml-File: %s", absolutePath.c_str());
162 }
163 
164 std::string PianoFile::getText(const tinyxml2::XMLElement *element) {
165  if (!element->GetText()) {
166  return std::string();
167  } else {
168  return std::string(element->GetText());
169  }
170 }
171 
172 void PianoFile::createTextXMLElement(tinyxml2::XMLElement *parent, const char *label, const char *text) const {
173  tinyxml2::XMLElement *e = parent->GetDocument()->NewElement(label);
174  parent->InsertEndChild(e);
175  e->SetText(text);
176 }
177 
178 void PianoFile::read(const tinyxml2::XMLElement *e, Piano &piano) {
179  EptAssert(e, "XMLElement has to exist.");
180  int type = piano.getPianoType();
181 
182  e->QueryDoubleAttribute("concertPitch", &piano.getConcertPitch());
183  e->QueryIntAttribute("type", &type);
184  piano.setType(static_cast<piano::PianoType>(type));
185 
186  for (const tinyxml2::XMLElement *data = e->FirstChildElement(); data;
187  data = data->NextSiblingElement()) {
188  if (strcmp(data->Value(), "name") == 0) {
189  piano.setName(getText(data));
190  } else if (strcmp(data->Value(), "serialNumber") == 0) {
191  piano.setSerialNumber(getText(data));
192  } else if (strcmp(data->Value(), "manufactionYear") == 0) {
193  piano.setManufactureYear(getText(data));
194  } else if (strcmp(data->Value(), "manufactionLocation") == 0) {
195  piano.setManufactureLocation(getText(data));
196  } else if (strcmp(data->Value(), "tuningLocation") == 0) {
197  piano.setTuningLocation(getText(data));
198  } else if (strcmp(data->Value(), "tuningTimestamp") == 0) {
199  if (data->GetText()) {
200  piano.setTuningTime(data->GetText());
201  } else {
203  }
204  } else if (strcmp(data->Value(), "keyboard") == 0) {
205  read(data, piano.getKeyboard());
206  }
207  }
208 }
209 
210 void PianoFile::write(tinyxml2::XMLElement *e, const Piano &piano) const {
211  e->SetAttribute("concertPitch", piano.getConcertPitch());
212  e->SetAttribute("type", piano.getPianoType());
213 
214  createTextXMLElement(e, "name", piano.getName().c_str());
215  createTextXMLElement(e, "serialNumber", piano.getSerialNumber().c_str());
216  createTextXMLElement(e, "manufactionYear", piano.getManufactionYear().c_str());
217  createTextXMLElement(e, "manufactionLocation", piano.getManufactionLocation().c_str());
218  createTextXMLElement(e, "tuningLocation", piano.getTuningLocation().c_str());
219  createTextXMLElement(e, "tuningTimestamp", piano.getTuningTime().c_str());
220 
221  // write key data
222  tinyxml2::XMLElement *keyboardElem = e->GetDocument()->NewElement("keyboard");
223  e->InsertEndChild(keyboardElem);
224  write(keyboardElem, piano.getKeyboard());
225 
226 }
227 
228 void PianoFile::read(const tinyxml2::XMLElement *e, Keyboard &keyboard) {
229  int numberOfKeys = keyboard.getNumberOfKeys();
230  int keyNumberOfA = keyboard.getKeyNumberOfA4();
231  e->QueryIntAttribute("numberOfKeys", &numberOfKeys);
232  e->QueryIntAttribute("keyNumberOfA", &keyNumberOfA);
233  keyboard.changeKeyboardConfiguration(numberOfKeys, keyNumberOfA);
234 
235  for (const tinyxml2::XMLElement *keyElement = e->FirstChildElement(); keyElement;
236  keyElement = keyElement->NextSiblingElement()) {
237  if (strcmp(keyElement->Value(), "key") == 0) {
238  int key = -1;
239  keyElement->QueryAttribute("key", &key);
240  if (key < 0) {
241  LogE("Key attribute not provided.");
242  return;
243  }
244 
245  // clear the peaks list
246  keyboard[key].getPeaks().clear();
247 
248  // reset the specturum
249  std::fill(keyboard[key].getSpectrum().begin(), keyboard[key].getSpectrum().end(), 0);
250 
251  // read data
252  keyElement->QueryAttribute("recordedFrequency", &keyboard[key].getRecordedFrequency());
253  keyElement->QueryAttribute("measuredInharmonicity", &keyboard[key].getMeasuredInharmonicity());
254  keyElement->QueryAttribute("computedFrequency", &keyboard[key].getComputedFrequency());
255  keyElement->QueryAttribute("tunedFrequency", &keyboard[key].getTunedFrequency());
256  keyElement->QueryAttribute("recorded", &keyboard[key].isRecorded());
257  keyElement->QueryAttribute("quality", &keyboard[key].getRecognitionQuality());
258 
259  for (const tinyxml2::XMLElement *child = keyElement->FirstChildElement(); child;
260  child = child->NextSiblingElement()) {
261  if (strcmp(child->Value(), "spectrum") == 0) {
262  // spectrum
263  auto &spectrum = keyboard[key].getSpectrum();
264  // pointer to the text
265  const char *s = child->GetText();
266  // pointer to the last char that was read by strtod
267  char *p = 0;
268  // pointer to the data structure to fill
269  double *spec = spectrum.data();
270  do {
271  // read value
272  double val = strtod(s, &p);
273  // nothing to read, finish
274  if (s == p) break;
275 
276  // store value and increase write position
277  *spec = (val);
278  ++spec;
279 
280  // check if we reached the end
281  if (spec == &spectrum[0] + Key::NumberOfBins) {break;}
282 
283  // adjust read position
284  s = p+1;
285  } while (*p);
286  } else if (strcmp(child->Value(), "peak") == 0) {
287  // peak
288  double frequency = -1;
289  double intensity = -1;
290  child->QueryDoubleAttribute("frequency", &frequency);
291  child->QueryDoubleAttribute("intensity", &intensity);
292  keyboard[key].getPeaks()[frequency] = intensity;
293  }
294  }
295  }
296  }
297 }
298 
299 void PianoFile::write(tinyxml2::XMLElement *e, const Keyboard &keyboard) const {
300  assert(e);
301  e->SetAttribute("numberOfKeys", keyboard.getNumberOfKeys());
302  e->SetAttribute("keyNumberOfA", keyboard.getKeyNumberOfA4());
303 
304  for (size_t key = 0; key < keyboard.size(); ++key) {
305  tinyxml2::XMLElement *keyElement = e->GetDocument()->NewElement("key");
306  e->InsertEndChild(keyElement);
307 
308  keyElement->SetAttribute("key", static_cast<int>(key));
309 
310  keyElement->SetAttribute("recordedFrequency", keyboard[key].getRecordedFrequency());
311  keyElement->SetAttribute("measuredInharmonicity", keyboard[key].getMeasuredInharmonicity());
312  keyElement->SetAttribute("computedFrequency", keyboard[key].getComputedFrequency());
313  keyElement->SetAttribute("tunedFrequency", keyboard[key].getTunedFrequency());
314  keyElement->SetAttribute("recorded", keyboard[key].isRecorded());
315  keyElement->SetAttribute("quality", keyboard[key].getRecognitionQuality());
316 
317  // spectrum
318  const Key::SpectrumType &spectrum(keyboard[key].getSpectrum());
319  std::stringstream oss;
320  for (size_t spec = 0; spec < spectrum.size() - 1; ++spec) {
321  // all but last with space
322  oss << spectrum[spec] << " ";
323  }
324  // last without space
325  oss << spectrum[spectrum.size() - 1];
326  tinyxml2::XMLElement *specElement = e->GetDocument()->NewElement("spectrum");
327  keyElement->InsertEndChild(specElement);
328  specElement->SetText(oss.str().c_str());
329 
330  // peaks
331  for (auto peak : keyboard[key].getPeaks()) {
332  tinyxml2::XMLElement *peakElement = e->GetDocument()->NewElement("peak");
333  keyElement->InsertEndChild(peakElement);
334  peakElement->SetAttribute("frequency", peak.first);
335  peakElement->SetAttribute("intensity", peak.second);
336  }
337  }
338 }
339 
340 
341 // ==================================================================================================
342 // CSV
343 
344 
345 void PianoFile::writeCsvFile(const std::string &absolutePath, const Piano &piano) const {
346  std::ofstream stream;
347  stream.open(absolutePath);
348  if (!stream.is_open()) {
349  EPT_EXCEPT(EptException::ERR_CANNOT_WRITE_TO_FILE, "Cannot write to file '" + absolutePath + "'");
350  }
351 
352  // only write
353  // key index + 1, inharmonicity, recorded frequency, computed frequency, tuned frequency, quality
354 
355  // header
356  stream << "\"Key index\",\"Inharmonicity\",\"Recorded frequency\",\"Recorded deviation\",\"Computed frequency\",\"Computed deviation\",\"Tuned frequency\",\"Tuned deviation\",\"Quality\"" << std::endl;
357 
358  // data
359  const int A4 = piano.getKeyboard().getKeyNumberOfA4();
360  const Key &Akey = piano.getKey(A4);
361  auto cents = [A4] (int i, double f, double fA4)
362  {
363  double ET = pow(2.0,(i-A4)/12.0);
364  if (f == 0 or fA4 == 0) return 0.0;
365  else return 1200.0 * std::log(f/fA4/ET)/std::log(2.0);
366  };
367  for (int i = 0; i < piano.getKeyboard().getNumberOfKeys(); ++i) {
368  const Key &key = piano.getKeyboard()[i];
369  stream << std::setw(3) << std::left << i + 1 << std::fixed << std::right
370  << ", " << std::setw(8) << std::setprecision(5) << key.getMeasuredInharmonicity()
371  << ", " << std::setw(8) << std::setprecision(2) << key.getRecordedFrequency()
372  << ", " << std::setw(6) << std::setprecision(1) << cents(i,key.getRecordedFrequency(),Akey.getRecordedFrequency())
373  << ", " << std::setw(8) << std::setprecision(2) << key.getComputedFrequency()
374  << ", " << std::setw(6) << std::setprecision(1) << cents(i,key.getComputedFrequency(),Akey.getComputedFrequency())
375  << ", " << std::setw(8) << std::setprecision(2) << key.getTunedFrequency()
376  << ", " << std::setw(6) << std::setprecision(1) << cents(i,key.getTunedFrequency(),piano.getConcertPitch())
377  << ", " << std::setw(6) << std::setprecision(2) << key.getRecognitionQuality()
378  << std::endl;
379  }
380 }
FileVersionType mFileVersion
file version as integer
Definition: pianofile.h:63
double getRecordedFrequency() const
Get recorded frequency.
Definition: key.cpp:119
static const FileVersionType UNSET_FILE_VERSION
Definition: pianofile.h:33
std::string getText(const tinyxml2::XMLElement *element)
Definition: pianofile.cpp:164
int FileVersionType
Definition: pianofile.h:31
Class describing the piano keyboard, holding a collection of keys.
Definition: keyboard.h:39
static const std::string FILE_TYPE_NAME
Definition: pianofile.h:32
piano::PianoType getPianoType() const
Definition: piano.h:60
void setName(const std::string &name)
Definition: piano.h:56
FileType
supported piano file types
Definition: pianodefines.h:59
const std::string & getManufactionYear() const
Definition: piano.h:67
void changeKeyboardConfiguration(int numberOfKeys, int keyNumberOfA)
Change keyboard configuration.
Definition: keyboard.cpp:73
#define LogW(...)
Definition: log.h:56
void readXmlFile(const std::string &absolutePath, Piano &piano)
Definition: pianofile.cpp:83
Class describing a single piano key.
Definition: key.h:45
static const int NumberOfBins
Total number of slots: 9 octaves.
Definition: key.h:50
Definition: piano.h:40
const std::string & getManufactionLocation() const
Definition: piano.h:70
bool write(const std::string &absolutePath, const Piano &piano, piano::FileType fileType) const
Definition: pianofile.cpp:62
void writeXmlFile(const std::string &absolutePath, const Piano &piano) const
Definition: pianofile.cpp:138
const std::string & getSerialNumber() const
Definition: piano.h:64
void setType(piano::PianoType type)
Definition: piano.h:59
bool read(const std::string &absolutePath, Piano &piano)
Definition: pianofile.cpp:47
void setSerialNumber(const std::string &number)
Definition: piano.h:63
#define EPT_EXCEPT(num, desc)
Definition: eptexception.h:119
const Keyboard & getKeyboard() const
Definition: piano.h:83
void setTuningTimeToActualTime()
This function resets the tuning time to the actual time. This is used as default value.
Definition: piano.cpp:70
void setManufactureYear(const std::string &year)
Definition: piano.h:66
double getComputedFrequency() const
Get computed frequency.
Definition: key.cpp:174
double getMeasuredInharmonicity() const
Get estimated inharmonicity.
Definition: key.cpp:141
size_t size() const
Definition: keyboard.h:48
#define LogI(...)
Definition: log.h:50
void createTextXMLElement(tinyxml2::XMLElement *parent, const char *label, const char *text) const
Definition: pianofile.cpp:172
static const FileVersionType CURRENT_FILE_VERSION
Definition: pianofile.h:34
static const FileVersionType MIN_SUPPORTED_FILE_VERSION
Definition: pianofile.h:35
const std::string & getTuningTime() const
Definition: piano.h:77
#define LogE(...)
Definition: log.h:62
double getRecognitionQuality() const
Get quality of recognition.
Definition: key.cpp:155
std::vector< double > SpectrumType
Type of a log-binned spectrum.
Definition: key.h:54
const std::string & getTuningLocation() const
Definition: piano.h:73
int getKeyNumberOfA4() const
Definition: keyboard.h:73
const double & getConcertPitch() const
Definition: piano.h:80
#define EptAssert(a, b)
Definition: eptexception.h:47
void setTuningLocation(const std::string &loc)
Definition: piano.h:72
void setManufactureLocation(const std::string &loc)
Definition: piano.h:69
void writeCsvFile(const std::string &absolutePath, const Piano &piano) const
Definition: pianofile.cpp:345
int getNumberOfKeys() const
Definition: keyboard.h:72
const std::string & getName() const
Definition: piano.h:57
double getTunedFrequency() const
Get tuned frequency.
Definition: key.cpp:193
void setTuningTime(const std::string &time)
Definition: piano.h:75
const Key & getKey(int i) const
Definition: piano.h:86