Skip to content

Instantly share code, notes, and snippets.

@chbtoys
Created July 17, 2022 09:39
Show Gist options
  • Save chbtoys/287deba92291d0dbcb4a71f46260de13 to your computer and use it in GitHub Desktop.
Save chbtoys/287deba92291d0dbcb4a71f46260de13 to your computer and use it in GitHub Desktop.
audio programming - beginner level
// 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