-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathtonegen.cpp
414 lines (328 loc) · 16.5 KB
/
tonegen.cpp
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
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
/*
Tone generator
BSD 2-Clause License
Copyright (c) 2019, Daniel Lorch
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <iostream>
#include <fstream>
#include <vector>
#include <cmath>
#include <climits>
#include "tonegen.h"
#include "portable_endian.h"
double PureToneGenerator::generate(int toneFrequencyHz, double timeIndexSeconds, double durationSeconds)
{
double tonePeriodSeconds = 1.0 / toneFrequencyHz;
double radians = timeIndexSeconds / tonePeriodSeconds * (2 * M_PI);
double result = sin(radians);
return result;
}
// Square Wave is generated by adding odd-numbered harmonics with decreasing amplitude https://youtu.be/YsZKvLnf7wU?t=363
double SquareWaveGenerator::generate(int fundamentalFrequencyHz, double timeIndexSeconds, double durationSeconds)
{
double firstHarmonicPeriodSeconds = 1.0 / fundamentalFrequencyHz;
double firstHarmonicRadians = timeIndexSeconds / firstHarmonicPeriodSeconds * (2 * M_PI);
double thirdHarmonicPeriodSeconds = 1.0 / (fundamentalFrequencyHz * 3);
double thirdHarmonicRadians = timeIndexSeconds / thirdHarmonicPeriodSeconds * (2 * M_PI);
double thirdHarmonicAmplitude = 1.0 / 3.0;
double fifthHarmonicPeriodSeconds = 1.0 / (fundamentalFrequencyHz * 5);
double fifthHarmonicRadians = timeIndexSeconds / fifthHarmonicPeriodSeconds * (2 * M_PI);
double fifthHarmonicAmplitude = 1.0 / 5.0;
double seventhHarmonicPeriodSeconds = 1.0 / (fundamentalFrequencyHz * 7);
double seventhHarmonicRadians = timeIndexSeconds / seventhHarmonicPeriodSeconds * (2 * M_PI);
double seventhHarmonicAmplitude = 1.0 / 7.0;
double ninthHarmonicPeriodSeconds = 1.0 / (fundamentalFrequencyHz * 9);
double ninthHarmonicRadians = timeIndexSeconds / ninthHarmonicPeriodSeconds * (2 * M_PI);
double ninthHarmonicAmplitude = 1.0 / 9.0;
// ... continue to infinite
double result = sin(firstHarmonicRadians) \
+ thirdHarmonicAmplitude * sin(thirdHarmonicRadians) \
+ fifthHarmonicAmplitude * sin(fifthHarmonicRadians) \
+ seventhHarmonicAmplitude * sin(seventhHarmonicRadians) \
+ ninthHarmonicAmplitude * sin(ninthHarmonicRadians);
return result;
}
// Violin sound https://meettechniek.info/additional/additive-synthesis.html
double ViolinGenerator::generate(int fundamentalFrequencyHz, double timeIndexSeconds, double durationSeconds)
{
double amplitude = 0.49;
double harmonic1PeriodSeconds = 1.0 / fundamentalFrequencyHz;
double harmonic1Radians = timeIndexSeconds / harmonic1PeriodSeconds * (2 * M_PI);
double harmonic1Amplitude = 0.995;
double harmonic2PeriodSeconds = 1.0 / (fundamentalFrequencyHz * 2);
double harmonic2Radians = timeIndexSeconds / harmonic2PeriodSeconds * (2 * M_PI);
double harmonic2Amplitude = 0.940;
double harmonic3PeriodSeconds = 1.0 / (fundamentalFrequencyHz * 3);
double harmonic3Radians = timeIndexSeconds / harmonic3PeriodSeconds * (2 * M_PI);
double harmonic3Amplitude = 0.425;
double harmonic4PeriodSeconds = 1.0 / (fundamentalFrequencyHz * 4);
double harmonic4Radians = timeIndexSeconds / harmonic4PeriodSeconds * (2 * M_PI);
double harmonic4Amplitude = 0.480;
double harmonic6PeriodSeconds = 1.0 / (fundamentalFrequencyHz * 6);
double harmonic6Radians = timeIndexSeconds / harmonic4PeriodSeconds * (2 * M_PI);
double harmonic6Amplitude = 0.365;
double harmonic7PeriodSeconds = 1.0 / (fundamentalFrequencyHz * 7);
double harmonic7Radians = timeIndexSeconds / harmonic7PeriodSeconds * (2 * M_PI);
double harmonic7Amplitude = 0.040;
double harmonic8PeriodSeconds = 1.0 / (fundamentalFrequencyHz * 8);
double harmonic8Radians = timeIndexSeconds / harmonic8PeriodSeconds * (2 * M_PI);
double harmonic8Amplitude = 0.085;
double harmonic10PeriodSeconds = 1.0 / (fundamentalFrequencyHz * 10);
double harmonic10Radians = timeIndexSeconds / harmonic10PeriodSeconds * (2 * M_PI);
double harmonic10Amplitude = 0.090;
double result = amplitude *
( harmonic1Amplitude * sin(harmonic1Radians)
+ harmonic2Amplitude * cos(harmonic2Radians)
+ harmonic3Amplitude * sin(harmonic3Radians)
+ harmonic4Amplitude * cos(harmonic4Radians)
+ harmonic6Amplitude * cos(harmonic6Radians)
+ harmonic7Amplitude * sin(harmonic6Radians)
+ harmonic8Amplitude * cos(harmonic8Radians)
+ harmonic10Amplitude * cos(harmonic10Radians) );
return result;
}
double ChirpGenerator::generate(int initialFrequencyHz, double timeIndexSeconds, double durationSeconds)
{
int finalFrequencyHz = initialFrequencyHz * 10;
// modulate the frequency with time, linearly decreasing from initialFrequencyHz to finalFrequencyHz
int momentaryFrequencyHz = initialFrequencyHz + (double)finalFrequencyHz / durationSeconds * timeIndexSeconds;
double tonePeriodSeconds = 1.0 / momentaryFrequencyHz;
double radians = timeIndexSeconds / tonePeriodSeconds * (2 * M_PI);
double result = sin(radians);
return result;
}
BellGenerator::BellGenerator(int fm_Hz, int I0, double tau): fm_Hz(fm_Hz), I0(I0), tau(tau), theta_m(-M_PI/2), theta_c(-M_PI/2)
{
}
double BellGenerator::generate(int fc_Hz, double timeIndexSeconds, double durationSeconds)
{
double At = exp(-timeIndexSeconds / this->tau);
double It = this->I0 * exp(-timeIndexSeconds / this->tau);
double result = At * cos(2 * M_PI * fc_Hz * timeIndexSeconds + It * cos(2 * M_PI * this->fm_Hz * timeIndexSeconds + this->theta_m) + this->theta_c);
return result;
}
double NoEnvelope::getAmplitude(double timeIndexSeconds)
{
return 1.0;
}
ADSREnvelope::ADSREnvelope(double durationSeconds)
{
if(durationSeconds <= 0.0)
throw std::logic_error("Invalid value for durationSeconds: must be positive non-zero value");
this->attackAmplitude = 1.0;
this->attackDurationSeconds = durationSeconds * 0.1;
// decay amplitude range is given by attack and sustain
this->decayDurationSeconds = durationSeconds * 0.1;
this->sustainAmplitude = 0.7;
this->sustainDurationSeconds = durationSeconds * 0.7;
this->releaseAmplitude = 0.0;
this->releaseDurationSeconds = durationSeconds * 0.1;
}
double ADSREnvelope::getAmplitude(double timeIndexSeconds)
{
double result;
if(timeIndexSeconds < this->attackDurationSeconds)
{
// linearly increasing
result = this->attackAmplitude / this->attackDurationSeconds * timeIndexSeconds;
}
else if(timeIndexSeconds < this->attackDurationSeconds + this->decayDurationSeconds)
{
double relativeTimeIndexSeconds = timeIndexSeconds - this->attackDurationSeconds;
double relativeAmplitude = this->attackAmplitude - this->sustainAmplitude;
// linearly decreasing
result = this->attackAmplitude - (relativeAmplitude / this->decayDurationSeconds * relativeTimeIndexSeconds);
}
else if(timeIndexSeconds < this->attackDurationSeconds + this->decayDurationSeconds + this->sustainDurationSeconds)
{
double relativeTimeIndexSeconds = timeIndexSeconds - this->attackDurationSeconds - this->decayDurationSeconds;
// keep at same level
result = this->sustainAmplitude;
}
else
{
double relativeTimeIndexSeconds = timeIndexSeconds - this->attackDurationSeconds - this->decayDurationSeconds - this->sustainDurationSeconds;
// linearly decreasing
result = this->sustainAmplitude - (this->sustainAmplitude / this->releaseDurationSeconds * relativeTimeIndexSeconds);
}
return result;
}
BellEnvelope::BellEnvelope(double tau): tau(tau)
{
}
double BellEnvelope::getAmplitude(double timeIndexSeconds)
{
double result = exp(-timeIndexSeconds / this->tau);
return result;
}
Sampler::Sampler(int sampleRateHz, int bitsPerSample, int numChannels): sampleRateHz(sampleRateHz), bitsPerSample(bitsPerSample), numChannels(numChannels)
{
if(numChannels != 1)
throw std::logic_error("Unsupported value for numChannels: only 1 channel (mono) supported");
if(bitsPerSample != 8)
throw std::logic_error("Unsupported value for bitsPerSample: only 8 bits supported");
}
void Sampler::sample(ToneGenerator* generator, int toneFrequencyHz, double durationSeconds, Envelope* envelope, double volume)
{
if(volume == 11) // loudest
volume = 1.0;
else if(volume < 0 || volume > 1)
throw std::logic_error("Invalid volume: must be within range 0.0 .. 1.0");
const double sampleValueRange = pow(2, this->bitsPerSample);
for(int i=0; i < this->sampleRateHz * durationSeconds; i++) {
double timeIndexSeconds = (double)i / this->sampleRateHz;
double sample = generator->generate(toneFrequencyHz, timeIndexSeconds, durationSeconds);
// apply envelope
sample = sample * envelope->getAmplitude(timeIndexSeconds);
// apply volume
sample = sample * volume;
// map continous result from tone generator [-1.0, 1.0] to discrete sample value range [0 .. 255]
char sampleValue = (sample + 1.0) / 2.0 * sampleValueRange;
this->sampleData.push_back(sampleValue);
}
}
int Sampler::getSampleRateHz()
{
return this->sampleRateHz;
}
int Sampler::getBitsPerSample()
{
return this->bitsPerSample;
}
int Sampler::getNumChannels()
{
return this->numChannels;
}
std::vector<char>& Sampler::getSampleData()
{
return this->sampleData;
}
// WAVE Format: http://soundfile.sapp.org/doc/WaveFormat/
void WAVWriter::writeSamplesToBinaryStream(Sampler *sampler, std::ofstream *wavStream)
{
DataSubChunk dataSubChunk;
dataSubChunk.Subchunk2ID = htobe32(0x64617461); // "data"
dataSubChunk.Subchunk2Size = htole32(sampler->getSampleData().size() * sampler->getNumChannels() * sampler->getBitsPerSample()/8);
// WTF MSFT: mixed big- and little endian in the *same* header structs? You've got to be kidding me...
FmtSubChunk fmtSubChunk;
fmtSubChunk.Subchunk1ID = htobe32(0x666d7420); // "fmt "
fmtSubChunk.Subchunk1Size = htole32(16); // size of the rest of this subchunk
fmtSubChunk.AudioFormat = htole32(1); // PCM (i.e. linear quantization)
fmtSubChunk.NumChannels = htole32(sampler->getNumChannels());
fmtSubChunk.SampleRate = htole32(sampler->getSampleRateHz());
fmtSubChunk.ByteRate = htole32(sampler->getSampleRateHz() * sampler->getNumChannels() * sampler->getBitsPerSample()/8);
fmtSubChunk.BlockAlign = htole32(sampler->getNumChannels() * sampler->getBitsPerSample()/8);
fmtSubChunk.BitsPerSample = htole32(sampler->getBitsPerSample());
RIFFHeader riffHeader;
riffHeader.ChunkID = htobe32(0x52494646); // "RIFF"
riffHeader.ChunkSize = htole32(4 + (8 + fmtSubChunk.Subchunk1Size) + (8 + dataSubChunk.Subchunk2Size));
riffHeader.Format = htobe32(0x57415645); // "WAVE"
wavStream->write((char *)&riffHeader, sizeof(riffHeader));
wavStream->write((char *)&fmtSubChunk, sizeof(fmtSubChunk));
wavStream->write((char *)&dataSubChunk, sizeof(dataSubChunk));
// C++ apparently guarantees, that the first element of a vector points to consecutive memory of the data
wavStream->write((char *)&sampler->getSampleData()[0], sizeof(char)*sampler->getSampleData().size());
}
int main() {
const int sampleRateHz = 22050; // number of samples per second
const int numChannels = 1; // Mono
const int bitsPerSample = CHAR_BIT; // 8 bits
const double volume = 0.75; // 0.0 .. 1.0
const double noteDuration = 0.25; // seconds
PureToneGenerator pureTone = PureToneGenerator();
SquareWaveGenerator squareWave = SquareWaveGenerator();
ViolinGenerator violin = ViolinGenerator();
ChirpGenerator chirp = ChirpGenerator();
NoEnvelope noEnvelope = NoEnvelope();
ADSREnvelope adsrEnvelope = ADSREnvelope(noteDuration);
Sampler sampler = Sampler(sampleRateHz, bitsPerSample, numChannels);
// Mary had a Little Lamb: http://www.choose-piano-lessons.com/piano-notes.html
const int marySong[] =
{
// Ma-----ry had a lit----le lamb
E4, D4, C4, D4, E4, E4, E4,
// lit----le lamb, lit----le lamb
D4, D4, D4, E4, E4, E4,
// Ma-----ry had a lit----le lamb
E4, D4, C4, D4, E4, E4, E4,
// Its fleece was white as snow.
E4, D4, D4, E4, D4, C4
};
const int maryLength = sizeof(marySong)/sizeof(marySong[0]);
// pure, sinusoidal tone; no envelope
for(int i=0; i<maryLength; i++)
{
sampler.sample(&pureTone, marySong[i], noteDuration, &noEnvelope, volume);
}
// square waves; no envelope
for(int i=0; i<maryLength; i++)
{
sampler.sample(&squareWave, marySong[i], noteDuration, &noEnvelope, volume);
}
// square waves; ADSR envelope
for(int i=0; i<maryLength; i++)
{
sampler.sample(&squareWave, marySong[i], noteDuration, &adsrEnvelope, volume);
}
// violin; ADSR envelope
for(int i=0; i<maryLength; i++)
{
sampler.sample(&violin, marySong[i], noteDuration, &adsrEnvelope, volume);
}
sampler.sample(&chirp, C4, noteDuration, &adsrEnvelope, volume);
sampler.sample(&chirp, C4, noteDuration, &adsrEnvelope, volume);
sampler.sample(&chirp, C4, noteDuration, &adsrEnvelope, volume);
std::ofstream maryFile("output/mary.wav", std::ios::out | std::ios::binary);
WAVWriter::writeSamplesToBinaryStream(&sampler, &maryFile);
maryFile.close();
std::cout << "Wrote output/mary.wav" << std::endl;
// Bells 1-6, https://web.eecs.utk.edu/~qi/ece505/project/proj1.pdf
BellGenerator bell1 = BellGenerator(220, 10, 2);
BellGenerator bell2 = BellGenerator(440, 5, 2);
BellGenerator bell3 = BellGenerator(220, 10, 12);
BellGenerator bell4 = BellGenerator(220, 10, 0.3);
BellGenerator bell5 = BellGenerator(350, 5, 2);
BellGenerator bell6 = BellGenerator(350, 3, 1);
BellEnvelope bell1Envelope = BellEnvelope(2);
BellEnvelope bell2Envelope = BellEnvelope(2);
BellEnvelope bell3Envelope = BellEnvelope(12);
BellEnvelope bell4Envelope = BellEnvelope(0.3);
BellEnvelope bell5Envelope = BellEnvelope(2);
BellEnvelope bell6Envelope = BellEnvelope(1);
const double bell1Duration = 6;
const double bell2Duration = 6;
const double bell3Duration = 3;
const double bell4Duration = 3;
const double bell5Duration = 5;
const double bell6Duration = 5;
Sampler bellSampler = Sampler(sampleRateHz, bitsPerSample, numChannels);
bellSampler.sample(&bell1, 110, bell1Duration, &bell1Envelope, volume);
bellSampler.sample(&bell2, 220, bell2Duration, &bell2Envelope, volume);
bellSampler.sample(&bell3, 110, bell3Duration, &bell3Envelope, volume);
bellSampler.sample(&bell4, 110, bell4Duration, &bell4Envelope, volume);
bellSampler.sample(&bell5, 250, bell5Duration, &bell5Envelope, volume);
bellSampler.sample(&bell6, 250, bell6Duration, &bell6Envelope, volume);
std::ofstream bellFile("output/bells.wav", std::ios::out | std::ios::binary);
WAVWriter::writeSamplesToBinaryStream(&bellSampler, &bellFile);
bellFile.close();
std::cout << "Wrote output/bells.wav" << std::endl;
return 0;
}