1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280
|
// -*-c++-*-
/**
* Tunings.h
* Copyright Paul Walker, 2019-2020
* Released under the MIT License. See LICENSE.md
*
* Tunings.h contains the public API required to determine full keyboard frequency maps
* for a scala SCL and KBM file in standalone, tested, open licensed C++ header only library.
*
* An example of using the API is
*
* ```
* auto s = Tunings::readSCLFile( "./my-scale.scl" );
* auto k = Tunings::readKBMFile( "./my-mapping.kbm" );
*
* Tunings::Tuning t( s, k );
*
* std::cout << "The frequency of C4 and A4 are "
* << t.frequencyForMidiNote( 60 ) << " and "
* << t.frequencyForMidiNote( 69 ) << std::endl;
* ```
*
* The API provides several other points, such as access to the structure of the SCL and KBM,
* the ability to create several prototype SCL and KBM files wthout SCL or KBM content,
* a frequency measure which is normalized by the frequency of standard tuning midi note 0
* and the logarithmic frequency scale, with a doubling per frequency doubling.
*
* Documentation is in the class header below; tests are in `tests/all_tests.cpp` and
* a variety of command line tools accompany the header.
*/
#ifndef __INCLUDE_TUNINGS_H
#define __INCLUDE_TUNINGS_H
#include <string>
#include <vector>
#include <iostream>
#include <memory>
#include <array>
namespace Tunings
{
const double MIDI_0_FREQ = 8.17579891564371; // or 440.0 * pow( 2.0, - (69.0/12.0 ) )
/**
* A Tone is a single entry in an SCL file. It is expressed either in cents or in
* a ratio, as described in the SCL documentation.
*
* In most normal use, you will not use this class, and it will be internal to a Scale
*/
struct Tone
{
typedef enum Type
{
kToneCents, // An SCL representation like "133.0"
kToneRatio // An SCL representation like "3/7"
} Type;
Type type;
double cents;
int ratio_d, ratio_n;
std::string stringRep;
double floatValue; // cents / 1200 + 1.
Tone() : type(kToneRatio), cents(0), ratio_d(1), ratio_n(1), stringRep("1/1"), floatValue(1.0)
{
}
};
/**
* Given an SCL string like "100.231" or "3/7" set up a Tone
*/
inline Tone toneFromString(const std::string &t, int lineno = -1);
/**
* The Scale is the representation of the SCL file. It contains several key
* features. Most importantly it has a count and a vector of Tones.
*
* In most normal use, you will simply pass around instances of this class
* to a Tunings::Tuning instance, but in some cases you may want to create
* or inspect this class yourself. Especially if you are displaying this
* class to your end users, you may want to use the rawText or count methods.
*/
struct Scale
{
std::string name; // The name in the SCL file. Informational only
std::string description; // The description in the SCL file. Informational only
std::string rawText; // The raw text of the SCL file used to create this Scale
int count; // The number of tones.
std::vector<Tone> tones; // The tones
Scale() : name("empty scale"), description(""), rawText(""), count(0) {}
};
/**
* The KeyboardMapping class represents a KBM file. In most cases, the salient
* features are the tuningConstantNote and tuningFrequency, which allow you to
* pick a fixed note in the midi keyboard when retuning. The KBM file can also
* remap individual keys to individual points in a scale, which kere is done with the
* keys vector.
*
* Just as with Scale, the rawText member contains the text of the KBM file used.
*/
struct KeyboardMapping
{
int count;
int firstMidi, lastMidi;
int middleNote;
int tuningConstantNote;
double tuningFrequency, tuningPitch; // pitch = frequency / MIDI_0_FREQ
int octaveDegrees;
std::vector<int> keys; // rather than an 'x' we use a '-1' for skipped keys
std::string rawText;
std::string name;
KeyboardMapping();
};
/**
* In some failure states, the tuning library will throw an exception of
* type TuningError with a descriptive message.
*/
class TuningError : public std::exception
{
public:
TuningError(std::string m) : whatv(m) {}
virtual const char *what() const noexcept override { return whatv.c_str(); }
private:
std::string whatv;
};
/**
* readSCLStream returns a Scale from the SCL input stream
*/
Scale readSCLStream(std::istream &inf);
/**
* readSCLFile returns a Scale from the SCL File in fname
*/
Scale readSCLFile(std::string fname);
/**
* parseSCLData returns a scale from the SCL file contents in memory
*/
Scale parseSCLData(const std::string &sclContents);
/**
* evenTemperament12NoteScale provides a utility scale which is
* the "standard tuning" scale
*/
Scale evenTemperament12NoteScale();
/**
* evenDivisionOfSpanByM provides a scale referd to as "ED2-17" or
* "ED3-24" by dividing the Span into M points. eventDivisionOfSpanByM(2,12)
* should be the evenTemperament12NoteScale
*/
Scale evenDivisionOfSpanByM(int Span, int M);
/**
* readKBMStream returns a KeyboardMapping from a KBM input stream
*/
KeyboardMapping readKBMStream(std::istream &inf);
/**
* readKBMFile returns a KeyboardMapping from a KBM file name
*/
KeyboardMapping readKBMFile(std::string fname);
/**
* parseKBMData returns a KeyboardMapping from a KBM data in memory
*/
KeyboardMapping parseKBMData(const std::string &kbmContents);
/**
* tuneA69To creates a KeyboardMapping which keeps the midi note 69 (A4) set
* to a constant frequency, given
*/
KeyboardMapping tuneA69To(double freq);
/**
* tuneNoteTo creates a KeyboardMapping which keeps the midi note given is set
* to a constant frequency, given
*/
KeyboardMapping tuneNoteTo(int midiNote, double freq);
/**
* startScaleOnAndTuneNoteTo generates a KBM where scaleStart is the note 0
* of the scale, where midiNote is the tuned note, and where feq is the frequency
*/
KeyboardMapping startScaleOnAndTuneNoteTo(int scaleStart, int midiNote, double freq);
/**
* The Tuning class is the primary place where you will interact with this library.
* It is constructed for a scale and mapping and then gives you the ability to
* determine frequencies across and beyond the midi keyboard. Since modulation
* can force key number well outside the [0,127] range in some of our synths we
* support a midi note range from -256 to + 256 spanning more than the entire frequency
* space reasonable.
*
* To use this class, you construct a fresh instance every time you want to use a
* different Scale and Keyboard. If you want to tune to a different scale or mapping,
* just construct a new instance.
*/
class Tuning
{
public:
// The number of notes we pre-compute
constexpr static int N = 512;
// Construct a tuning with even temperament and standard mapping
Tuning();
/**
* Construct a tuning for a particular scale, mapping, or for both.
*/
Tuning(const Scale &s);
Tuning(const KeyboardMapping &k);
Tuning(const Scale &s, const KeyboardMapping &k);
/*
* Skipped notes can either have nonsense values or interpolated values.
* The old API made the bad choice to have nonsense values which we retain
* for compatability, but this method will return a new tuning with correctly
* interpolated skipped notes.
*/
Tuning withSkippedNotesInterpolated() const;
/**
* These three related functions provide you the information you
* need to use this tuning.
*
* frequencyForMidiNote returns the Frequency in HZ for a given midi
* note. In standard tuning, FrequencyForMidiNote(69) will be 440
* and frequencyForMidiNote(60) will be 261.62 - the standard frequencies
* for A and middle C.
*
* frequencyForMidiNoteScaledByMidi0 returns the frequency but with the
* standard frequency of midi note 0 divided out. So in standard tuning
* frequencyForMidiNoteScaledByMidi0(0) = 1 and frequencyForMidiNoteScaledByMidi0(60) = 32
*
* Finally logScaledFrequencyForMidiNote returns the log base 2 of the scaled frequency.
* So logScaledFrequencyForMidiNote(0) = 0 and logScaledFrequencyForMidiNote(60) = 5.
*
* Both the frequency measures have the feature of doubling when frequency doubles
* (or when a standard octave is spanned), whereas the log one increase by 1 per frequency
* double.
*
* Depending on your internal pitch model, one of these three methods should allow you
* to calibrate your oscillators to the appropriate frequency based on the midi note
* at hand.
*
* The scalePositionForMidiNote returns the space in the logical scale. Note 0 is the root.
* It has a maxiumum value of count-1. Note that SCL files omit the root internally and so
* this logical scale position is off by 1 from the index in the tones array of the Scale data.
*/
double frequencyForMidiNote(int mn) const;
double frequencyForMidiNoteScaledByMidi0(int mn) const;
double logScaledFrequencyForMidiNote(int mn) const;
int scalePositionForMidiNote(int mn) const;
bool isMidiNoteMapped(int mn) const;
// For convenience, the scale and mapping used to construct this are kept as public copies
Scale scale;
KeyboardMapping keyboardMapping;
private:
std::array<double, N> ptable, lptable;
std::array<int, N> scalepositiontable;
};
} // namespace Tunings
#include "TuningsImpl.h"
#endif
|