Created
July 17, 2022 09:39
-
-
Save chbtoys/287deba92291d0dbcb4a71f46260de13 to your computer and use it in GitHub Desktop.
audio programming - beginner level
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// compile: clang++ -std=c++20 -lpng 03-formant.cpp -o 03-formant | |
#define _USE_MATH_DEFINES | |
#include <cmath> | |
#include <vector> | |
#include <random> | |
#include <filesystem> | |
#include "indicators.hpp" | |
#include <png++/png.hpp> | |
#include "AudioFile/AudioFile.h" | |
#include "Envelope.hpp" | |
namespace Render | |
{ | |
void fillbackground(png::image<png::rgb_pixel>& image); | |
void drawpx(png::image<png::rgb_pixel>& image, int x, int y); | |
void drawline(png::image<png::rgb_pixel>& image, int x1, int y1, int x2, int y2); | |
void drawwave(png::image<png::rgb_pixel>& image,std::vector<uint32_t>& signalY); | |
void normalizedtoimg(png::image<png::rgb_pixel>& image, std::vector<float>& v1,std::vector<uint32_t>& v2); | |
void renderimage(png::image<png::rgb_pixel>& image,std::vector<uint32_t>& v); | |
void saveimagefile(std::vector<uint32_t>& v, std::string filename); | |
void saveimagefile(std::vector<float>& v2, std::string filename); | |
void normalizedenvelopetoimg(png::image<png::rgb_pixel>& image, std::vector<float>& v1,std::vector<uint32_t>& v2); | |
void saveenvelopeimage(std::vector<uint32_t>& v, std::string filename); | |
void saveenvelopeimage(std::vector<float>& v2, std::string filename); | |
} | |
namespace SignalGenerators | |
{ | |
void gain(std::vector<float>& v, double gain); | |
void normalize(std::vector<float>& v); | |
void addwaves(std::vector<float>& v1,std::vector<float>& v2,std::vector<float>& v3); | |
void generatesinewave(std::vector<float>& v, int duration, float frequencyInHz); | |
void generatenoise(std::vector<float>& v, int duration); | |
void generateformant(std::vector<float>& v, int formantnumber, int duration); | |
void saveaudiofile(std::vector<float>& v, std::string filename); | |
} | |
int main() | |
{ | |
namespace fs = std::filesystem; | |
fs::create_directory("formant"); | |
const int duration=1; | |
const double sampleRate=44100.0; | |
std::vector<float> formant0; | |
std::vector<float> formant1; | |
std::vector<float> formant2; | |
std::vector<float> formant3; | |
std::vector<float> formant4; | |
std::vector<float> formant5; | |
std::vector<float> formant6; | |
std::vector<float> formant7; | |
std::vector<float> formant8; | |
std::vector<float> formant9; | |
std::vector<float> formant10; | |
std::vector<float> envelope; | |
using namespace indicators; | |
// Hide cursor | |
show_console_cursor(false); | |
// Setup ProgressBar | |
ProgressBar bar{ | |
option::BarWidth{50}, | |
option::Start{"["}, | |
option::Fill{"■"}, | |
option::Lead{"■"}, | |
option::Remainder{"-"}, | |
option::End{" ]"}, | |
option::PostfixText{"Generate Formant0 1/23"}, | |
option::ForegroundColor{Color::cyan}, | |
option::FontStyles{std::vector<FontStyle>{FontStyle::bold}} | |
}; | |
// Update progress | |
bar.set_progress(0); | |
// Vowel [i] | |
SignalGenerators::generateformant(formant0,0,duration); | |
SignalGenerators::saveaudiofile(formant0,"01-formant0-vowel-i.aiff"); | |
Render::saveimagefile(formant0,"02-formant0-vowel-i.png"); | |
// Update progress | |
bar.set_progress(4); | |
bar.set_option(option::PostfixText{"Generate Formant1 2/23"}); | |
// Vowel [ɪ] | |
SignalGenerators::generateformant(formant1,1,duration); | |
SignalGenerators::saveaudiofile(formant1,"03-formant1-vowel-ɪ.aiff"); | |
Render::saveimagefile(formant0,"04-formant1-vowel-ɪ.png"); | |
// Update progress | |
bar.set_progress(9); | |
bar.set_option(option::PostfixText{"Generate Formant2 3/23"}); | |
// Vowel [e] | |
SignalGenerators::generateformant(formant2,2,duration); | |
SignalGenerators::saveaudiofile(formant2,"05-formant2-vowel-e.aiff"); | |
Render::saveimagefile(formant0,"06-formant2-vowel-e.png"); | |
// Update progress | |
bar.set_progress(13); | |
bar.set_option(option::PostfixText{"Generate Formant3 4/23"}); | |
// Vowel [ɛ] | |
SignalGenerators::generateformant(formant3,3,duration); | |
SignalGenerators::saveaudiofile(formant3,"07-formant3-vowel-ɛ.aiff"); | |
Render::saveimagefile(formant0,"08-formant3-vowel-ɛ.png"); | |
// Update progress | |
bar.set_progress(17); | |
bar.set_option(option::PostfixText{"Generate Formant4 5/23"}); | |
// Vowel [æ] | |
SignalGenerators::generateformant(formant4,4,duration); | |
SignalGenerators::saveaudiofile(formant4,"09-formant4-vowel-æ.aiff"); | |
Render::saveimagefile(formant0,"10-formant4-vowel-æ.png"); | |
// Update progress | |
bar.set_progress(22); | |
bar.set_option(option::PostfixText{"Generate Formant5 6/23"}); | |
// Vowel [ɑ] | |
SignalGenerators::generateformant(formant5,5,duration); | |
SignalGenerators::saveaudiofile(formant5,"11-formant5-vowel-ɑ.aiff"); | |
Render::saveimagefile(formant0,"12-formant5-vowel-ɑ.png"); | |
// Update progress | |
bar.set_progress(26); | |
bar.set_option(option::PostfixText{"Generate Formant6 7/23"}); | |
// Vowel [ɔ] | |
SignalGenerators::generateformant(formant6,6,duration); | |
SignalGenerators::saveaudiofile(formant6,"13-formant6-vowel-ɔ.aiff"); | |
Render::saveimagefile(formant0,"14-formant6-vowel-ɔ.png"); | |
// Update progress | |
bar.set_progress(30); | |
bar.set_option(option::PostfixText{"Generate Formant7 8/23"}); | |
// Vowel [o] | |
SignalGenerators::generateformant(formant7,7,duration); | |
SignalGenerators::saveaudiofile(formant7,"15-formant7-vowel-o.aiff"); | |
Render::saveimagefile(formant0,"16-formant7-vowel-o.png"); | |
// Update progress | |
bar.set_progress(35); | |
bar.set_option(option::PostfixText{"Generate Formant8 9/23"}); | |
// Vowel [ʊ] | |
SignalGenerators::generateformant(formant8,8,duration); | |
SignalGenerators::saveaudiofile(formant8,"17-formant8-vowel-ʊ.aiff"); | |
Render::saveimagefile(formant0,"18-formant8-vowel-ʊ.png"); | |
// Update progress | |
bar.set_progress(39); | |
bar.set_option(option::PostfixText{"Generate Formant9 10/23"}); | |
// Vowel [u] | |
SignalGenerators::generateformant(formant9,9,duration); | |
SignalGenerators::saveaudiofile(formant9,"19-formant9-vowel-u.aiff"); | |
Render::saveimagefile(formant0,"20-formant9-vowel-u.png"); | |
// Update progress | |
bar.set_progress(43); | |
bar.set_option(option::PostfixText{"Generate Formant10 11/23"}); | |
// Vowel [ʌ] | |
SignalGenerators::generateformant(formant10,10,duration); | |
SignalGenerators::saveaudiofile(formant10,"21-formant10-vowel-ʌ.aiff"); | |
Render::saveimagefile(formant0,"22-formant10-vowel-ʌ.png"); | |
// Update progress | |
bar.set_progress(48); | |
bar.set_option(option::PostfixText{"Generate Envelope 12/23"}); | |
ADSR::Envelope env(sampleRate,duration); | |
env.generateenvelope(envelope); | |
Render::saveenvelopeimage(envelope,"23-envelope.png"); | |
// Update progress | |
bar.set_progress(52); | |
bar.set_option(option::PostfixText{"Apply Envelope to Formant0 13/23"}); | |
env.applyenvelope(formant0,envelope); | |
SignalGenerators::normalize(formant0); | |
SignalGenerators::saveaudiofile(formant0,"24-env-formant0-vowel-i.aiff"); | |
Render::saveimagefile(formant0,"25-env-formant0-vowel-i.png"); | |
// Update progress | |
bar.set_progress(57); | |
bar.set_option(option::PostfixText{"Apply Envelope to Formant1 14/23"}); | |
env.applyenvelope(formant1,envelope); | |
SignalGenerators::normalize(formant1); | |
SignalGenerators::saveaudiofile(formant1,"26-env-formant1-vowel-ɪ.aiff"); | |
Render::saveimagefile(formant1,"27-env-formant1-vowel-ɪ.png"); | |
// Update progress | |
bar.set_progress(61); | |
bar.set_option(option::PostfixText{"Apply Envelope to Formant2 15/23"}); | |
env.applyenvelope(formant2,envelope); | |
SignalGenerators::normalize(formant2); | |
SignalGenerators::saveaudiofile(formant2,"28-env-formant2-vowel-e.aiff"); | |
Render::saveimagefile(formant2,"29-env-formant2-vowel-e.png"); | |
// Update progress | |
bar.set_progress(65); | |
bar.set_option(option::PostfixText{"Apply Envelope to Formant3 16/23"}); | |
env.applyenvelope(formant3,envelope); | |
SignalGenerators::normalize(formant3); | |
SignalGenerators::saveaudiofile(formant3,"30-env-formant3-vowel-ɛ.aiff"); | |
Render::saveimagefile(formant3,"31-env-formant3-vowel-ɛ.png"); | |
// Update progress | |
bar.set_progress(70); | |
bar.set_option(option::PostfixText{"Apply Envelope to Formant4 17/23"}); | |
env.applyenvelope(formant4,envelope); | |
SignalGenerators::normalize(formant4); | |
SignalGenerators::saveaudiofile(formant4,"32-env-formant4-vowel-æ.aiff"); | |
Render::saveimagefile(formant4,"33-env-formant4-vowel-æ.png"); | |
// Update progress | |
bar.set_progress(74); | |
bar.set_option(option::PostfixText{"Apply Envelope to Formant5 18/23"}); | |
env.applyenvelope(formant5,envelope); | |
SignalGenerators::normalize(formant5); | |
SignalGenerators::saveaudiofile(formant5,"34-env-formant5-vowel-ɑ.aiff"); | |
Render::saveimagefile(formant5,"35-env-formant5-vowel-ɑ.png"); | |
// Update progress | |
bar.set_progress(78); | |
bar.set_option(option::PostfixText{"Apply Envelope to Formant6 19/23"}); | |
env.applyenvelope(formant6,envelope); | |
SignalGenerators::normalize(formant6); | |
SignalGenerators::saveaudiofile(formant6,"36-env-formant6-vowel-ɔ.aiff"); | |
Render::saveimagefile(formant6,"37-env-formant6-vowel-ɔ.png"); | |
// Update progress | |
bar.set_progress(83); | |
bar.set_option(option::PostfixText{"Apply Envelope to Formant7 20/23"}); | |
env.applyenvelope(formant7,envelope); | |
SignalGenerators::normalize(formant7); | |
SignalGenerators::saveaudiofile(formant7,"38-env-formant7-vowel-o.aiff"); | |
Render::saveimagefile(formant7,"39-env-formant7-vowel-o.png"); | |
// Update progress | |
bar.set_progress(87); | |
bar.set_option(option::PostfixText{"Apply Envelope to Formant8 21/23"}); | |
env.applyenvelope(formant8,envelope); | |
SignalGenerators::normalize(formant8); | |
SignalGenerators::saveaudiofile(formant8,"40-env-formant8-vowel-ʊ.aiff"); | |
Render::saveimagefile(formant8,"41-env-formant8-vowel-ʊ.png"); | |
// Update progress | |
bar.set_progress(91); | |
bar.set_option(option::PostfixText{"Apply Envelope to Formant9 22/23"}); | |
env.applyenvelope(formant9,envelope); | |
SignalGenerators::normalize(formant9); | |
SignalGenerators::saveaudiofile(formant9,"42-env-formant9-vowel-u.aiff"); | |
Render::saveimagefile(formant9,"43-env-formant9-vowel-u.png"); | |
// Update progress | |
bar.set_progress(96); | |
bar.set_option(option::PostfixText{"Apply Envelope to Formant10 23/23"}); | |
env.applyenvelope(formant10,envelope); | |
SignalGenerators::normalize(formant10); | |
SignalGenerators::saveaudiofile(formant10,"44-env-formant10-vowel-ʌ.aiff"); | |
Render::saveimagefile(formant10,"45-env-formant10-vowel-ʌ.png"); | |
// Update progress | |
bar.set_progress(100); | |
bar.set_option(option::PostfixText{"Done 23/23"}); | |
// Show cursor | |
show_console_cursor(true); | |
return 0; | |
} | |
namespace SignalGenerators | |
{ | |
void gain(std::vector<float>& v, double gain) | |
{ | |
for (uint32_t i=0;i<v.size();++i) { | |
v[i]=v[i]*gain; | |
} | |
} | |
void normalize(std::vector<float>& v) | |
{ | |
float max=0.0,value=0.0; | |
for (uint32_t i=0;i<v.size();++i) { | |
value=v[i]; | |
if (value > max) {max=value;} | |
} | |
// max=std::ceil(max); | |
for (uint32_t i=0;i<v.size();++i) { | |
// v[i]=v[i]/max; | |
v[i]=(v[i]/max)*0.707; | |
} | |
} | |
void addwaves(std::vector<float>& v1,std::vector<float>& v2,std::vector<float>& v3) | |
{ | |
for (uint32_t i=0;i<v1.size();++i) | |
{ | |
v3[i]=v1[i]+v2[i]; | |
} | |
} | |
void generatesinewave(std::vector<float>& v, int duration, float frequencyInHz) | |
{ | |
const double sampleRate=44100.0; | |
for (uint32_t i=0;i<sampleRate*duration;++i) { | |
v.push_back(sin((static_cast<double> (i) / sampleRate) * frequencyInHz * 2.0 * M_PI)); | |
} | |
} | |
void generatenoise(std::vector<float>& v, int duration) | |
{ | |
const double sampleRate=44100.0; | |
std::random_device rd; | |
std::mt19937 gen(rd()); | |
std::uniform_real_distribution<> dis(-1.0, 1.0); | |
for (uint32_t i=0;i<sampleRate*duration;++i) { | |
v.push_back(dis(gen)); | |
} | |
} | |
void generateformant(std::vector<float>& mixer, int formantnumber, int duration) | |
{ | |
// Vowel [i] [ɪ] [e] [ɛ] [æ] [ɑ] [ɔ] [o] [ʊ] [u] [ʌ] | |
// F1 280 370 405 600 860 830 560 430 400 330 680 | |
// F2 2230 2090 2080 1930 1550 1170 820 980 1100 1260 1310 | |
const double sampleRate=44100.0; | |
float f1[]={280.0,370.0,405.0,600.0,860.0,830.0,560.0,430.0,400.0,330.0,680.0}; | |
float f2[]={2230.0,2090.0,2080.0,1930.0,1550.0,1170.0,820.0,980.0,1100.0,1260.0,1310}; | |
mixer.resize(duration*sampleRate); | |
std::vector<float> osc1; | |
std::vector<float> osc2; | |
std::vector<float> osc3; | |
generatesinewave(osc1,duration,f1[formantnumber]); | |
generatesinewave(osc2,duration,f2[formantnumber]); | |
generatenoise(osc3,duration); | |
gain(osc3,0.2); | |
addwaves(osc1,osc2,mixer); | |
addwaves(mixer,osc3,mixer); | |
normalize(mixer); | |
} | |
void saveaudiofile(std::vector<float>& v, std::string filename) | |
{ | |
const std::string path="formant/"; | |
// Setup the audio file | |
AudioFile<float> a; | |
a.setNumChannels(1); | |
a.setBitDepth(24); | |
a.setNumSamplesPerChannel(44100); | |
for (int i=0;i<a.getNumSamplesPerChannel();++i) | |
{ | |
for (int channel=0;channel<a.getNumChannels();++channel) | |
{ | |
a.samples[channel][i]=v[i]; | |
} | |
} | |
a.save(path+filename,AudioFileFormat::Aiff); | |
} | |
} | |
namespace Render | |
{ | |
void fillbackground(png::image<png::rgb_pixel>& image) | |
{ | |
png::rgb_pixel px(0x04,0x13,0x31); | |
for (uint32_t y=0;y<image.get_height();y++) { | |
for (uint32_t x=0;x<image.get_width();++x) { | |
image.set_pixel(x,y,px); | |
} | |
} | |
} | |
void drawpx(png::image<png::rgb_pixel>& image, int x, int y) | |
{ | |
if (((x >= 0) && (x < image.get_width())) && ((y >= 0) && (y < image.get_height()))) | |
{ | |
png::rgb_pixel px(0x7a,0xb1,0xe3); | |
image.set_pixel(x,y,px); | |
} | |
} | |
void drawline(png::image<png::rgb_pixel>& image, int x1, int y1, int x2, int y2) | |
{ | |
int x, y, dx, dy, dx1, dy1, px, py, xe, ye, i; | |
dx = x2 - x1; dy = y2 - y1; | |
if (dx == 0) | |
{ | |
if (y2 < y1) std::swap(y1, y2); | |
for (y = y1; y <= y2; y++) | |
drawpx(image, x1, y); | |
return; | |
} | |
if (dy == 0) | |
{ | |
if (x2 < x1) std::swap(x1, x2); | |
for (x = x1; x <= x2; x++) | |
drawpx(image, x, y1); | |
return; | |
} | |
dx1 = abs(dx); dy1 = abs(dy); | |
px = 2 * dy1 - dx1; py = 2 * dx1 - dy1; | |
if (dy1 <= dx1) | |
{ | |
if (dx >= 0) | |
{ | |
x = x1; y = y1; xe = x2; | |
} | |
else | |
{ | |
x = x2; y = y2; xe = x1; | |
} | |
drawpx(image, x, y); | |
for (i = 0; x<xe; i++) | |
{ | |
x = x + 1; | |
if (px<0) | |
px = px + 2 * dy1; | |
else | |
{ | |
if ((dx<0 && dy<0) || (dx>0 && dy>0)) y = y + 1; else y = y - 1; | |
px = px + 2 * (dy1 - dx1); | |
} | |
drawpx(image, x, y); | |
} | |
} | |
else | |
{ | |
if (dy >= 0) | |
{ | |
x = x1; y = y1; ye = y2; | |
} | |
else | |
{ | |
x = x2; y = y2; ye = y1; | |
} | |
drawpx(image, x, y); | |
for (i = 0; y<ye; i++) | |
{ | |
y = y + 1; | |
if (py <= 0) | |
py = py + 2 * dx1; | |
else | |
{ | |
if ((dx<0 && dy<0) || (dx>0 && dy>0)) x = x + 1; else x = x - 1; | |
py = py + 2 * (dx1 - dy1); | |
} | |
drawpx(image, x, y); | |
} | |
} | |
} | |
void drawwave(png::image<png::rgb_pixel>& image,std::vector<uint32_t>& signalY) | |
{ | |
uint32_t y=0,ox=0,oy=0; | |
for (uint32_t x=0;x<image.get_width();++x) | |
{ | |
y=signalY[x]; | |
if (x == 0) {ox=x;oy=y;} | |
drawline(image,x,y,ox,oy); | |
ox=x;oy=y; | |
} | |
} | |
void normalizedtoimg(png::image<png::rgb_pixel>& image, std::vector<float>& v1,std::vector<uint32_t>& v2) | |
{ | |
uint32_t halfHeight=image.get_height()/2; | |
double value=0.0; | |
if (v2.size() == 0 || v2.size() > v1.size()) { | |
v2.resize(v1.size()); | |
} | |
for (uint32_t i=0;i<v1.size();++i) | |
{ | |
value=v1[i]; | |
if (value >= 0.0) { | |
v2[i]=halfHeight-(halfHeight*value); | |
} else if (value < 0.0) | |
{ | |
v2[i]=halfHeight+(halfHeight*fabs(value)); | |
} | |
} | |
} | |
void renderimage(png::image<png::rgb_pixel>& image,std::vector<uint32_t>& v) | |
{ | |
fillbackground(image); | |
drawwave(image,v); | |
} | |
void saveimagefile(std::vector<uint32_t>& v, std::string filename) | |
{ | |
const std::string path="formant/"; | |
png::image<png::rgb_pixel> image(44100,600); | |
renderimage(image,v); | |
image.write(path+filename); | |
} | |
void saveimagefile(std::vector<float>& v2, std::string filename) | |
{ | |
const std::string path="formant/"; | |
png::image<png::rgb_pixel> image(44100,600); | |
std::vector<uint32_t> v; | |
normalizedtoimg(image,v2,v); | |
renderimage(image,v); | |
image.write(path+filename); | |
} | |
void normalizedenvelopetoimg(png::image<png::rgb_pixel>& image, std::vector<float>& v1,std::vector<uint32_t>& v2) | |
{ | |
uint32_t height=image.get_height(); | |
if (v2.size() == 0 || v2.size() > v1.size()) { | |
v2.resize(v1.size()); | |
} | |
for (uint32_t i=0;i<v1.size();++i) | |
{ | |
v2[i]=height-(height*v1[i]); | |
} | |
} | |
void saveenvelopeimage(std::vector<uint32_t>& v, std::string filename) | |
{ | |
const std::string path="formant/"; | |
png::image<png::rgb_pixel> image(44100,600); | |
renderimage(image,v); | |
image.write(path+filename); | |
} | |
void saveenvelopeimage(std::vector<float>& v2, std::string filename) | |
{ | |
const std::string path="formant/"; | |
png::image<png::rgb_pixel> image(44100,600); | |
std::vector<uint32_t> v; | |
normalizedenvelopetoimg(image,v2,v); | |
renderimage(image,v); | |
image.write(path+filename); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment