Created
July 17, 2022 09:47
-
-
Save chbtoys/ed489aa83edbb0c71d2a6d7efd245c41 to your computer and use it in GitHub Desktop.
audio programming - beginner level
This file contains hidden or 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 09-vectorsynth.cpp -o 09-vectorsynth | |
| #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 generatevectorsynth(std::vector<float>& v, int duration, double frequencyInHz); | |
| void saveaudiofile(std::vector<float>& v, std::string filename); | |
| } | |
| int main() | |
| { | |
| namespace fs = std::filesystem; | |
| fs::create_directory("vectorsynth"); | |
| const int duration=1; | |
| const double sampleRate=44100.0; | |
| std::vector<float> vectorsynth; | |
| 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{"Setup Vector Synth 1/2"}, | |
| option::ForegroundColor{Color::cyan}, | |
| option::FontStyles{std::vector<FontStyle>{FontStyle::bold}} | |
| }; | |
| // Update progress | |
| bar.set_progress(0); | |
| SignalGenerators::generatevectorsynth(vectorsynth,duration,440); | |
| SignalGenerators::saveaudiofile(vectorsynth,"01-vectorsynth.aiff"); | |
| Render::saveimagefile(vectorsynth,"02-vectorsynth.png"); | |
| // Update progress | |
| bar.set_progress(50); | |
| bar.set_option(option::PostfixText{"Generating: Vector Synth 2/2"}); | |
| ADSR::Envelope env(sampleRate,duration); | |
| env.generateenvelope(envelope); | |
| env.applyenvelope(vectorsynth,envelope); | |
| SignalGenerators::saveaudiofile(vectorsynth,"03-final_vectorsynth.aiff"); | |
| Render::saveimagefile(vectorsynth,"04-final_vectorsynth.png"); | |
| // Update progress | |
| bar.set_progress(100); | |
| bar.set_option(option::PostfixText{"Done 2/2"}); | |
| // 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 generatetrianglewave(std::vector<float>& v, int duration, double frequencyInHz) | |
| { | |
| const double sampleRate=44100.0; | |
| for (uint32_t i=0;i<sampleRate*duration;++i) { | |
| v.push_back(M_2_PI*asin(sin(frequencyInHz*2*M_PI*i/sampleRate))); | |
| } | |
| } | |
| void generateinversesawtoothwave(std::vector<float>& v, int duration, double frequencyInHz) | |
| { | |
| const double sampleRate=44100.0; | |
| double period=sampleRate/frequencyInHz; | |
| std::vector<float> temp; | |
| for (uint32_t i=0;i<period;++i) { | |
| temp.push_back(-2/M_PI*atan(1/tan(frequencyInHz*M_PI*i/sampleRate))); | |
| } | |
| for (uint32_t i=0;i<sampleRate*duration;++i) { | |
| v.push_back(temp[temp.size()-(i%temp.size())]); | |
| } | |
| } | |
| void generatesawtoothwave(std::vector<float>& v, int duration, double frequencyInHz) | |
| { | |
| const double sampleRate=44100.0; | |
| for (uint32_t i=0;i<sampleRate*duration;++i) { | |
| v.push_back(-2/M_PI*atan(1/tan(frequencyInHz*M_PI*i/sampleRate))); | |
| } | |
| } | |
| void generatesquarewave(std::vector<float>& v, int duration, double frequencyInHz) | |
| { | |
| const double sampleRate=44100.0; | |
| double period=sampleRate/frequencyInHz; | |
| double dutyCycle=period*0.5; | |
| double ss=0.0; | |
| for (uint32_t i=0;i<sampleRate*duration;++i) { | |
| if ((i%int(period)) >= 0 && (i%int(period)) < int(dutyCycle)) | |
| { | |
| ss=0.7; | |
| } else { | |
| ss=-0.7; | |
| } | |
| v.push_back(ss); | |
| } | |
| v[0]=0.0; | |
| v[v.size()-1]=0.0; | |
| } | |
| void generatevectorsynth(std::vector<float>& v, int duration, double frequencyInHz) | |
| { | |
| float vectortransition[4][4]={{1.0,0.5,0.25,0.0},{0.5,1.0,0.0,0.25},{0.25,0.0,1.0,0.5},{0.0,0.25,0.5,1.0}}; | |
| const double sampleRate=44100.0; | |
| std::vector<float> sample1; | |
| std::vector<float> sample2; | |
| std::vector<float> sample3; | |
| std::vector<float> sample4; | |
| generatetrianglewave(sample1,duration,frequencyInHz); | |
| generateinversesawtoothwave(sample2,duration,frequencyInHz); | |
| generatesawtoothwave(sample3,duration,frequencyInHz); | |
| generatesquarewave(sample4,duration,frequencyInHz); | |
| int ptr=0; | |
| for (int i=0;i<4;++i) { | |
| for (int j=0;j<11025;++j) { | |
| sample1[ptr]=sample1[ptr]*vectortransition[i][0]; | |
| sample2[ptr]=sample2[ptr]*vectortransition[i][1]; | |
| sample3[ptr]=sample3[ptr]*vectortransition[i][2]; | |
| sample4[ptr]=sample4[ptr]*vectortransition[i][3]; | |
| ptr++; | |
| } | |
| } | |
| v.resize(sample1.size()); | |
| addwaves(sample1,sample2,v); | |
| addwaves(v,sample3,v); | |
| addwaves(v,sample4,v); | |
| normalize(v); | |
| } | |
| void saveaudiofile(std::vector<float>& v, std::string filename) | |
| { | |
| const std::string path="vectorsynth/"; | |
| // 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="vectorsynth/"; | |
| 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="vectorsynth/"; | |
| 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="vectorsynth/"; | |
| 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="vectorsynth/"; | |
| 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