diff --git a/Linux/Makefile b/Linux/Makefile new file mode 100644 index 0000000..d74456b --- /dev/null +++ b/Linux/Makefile @@ -0,0 +1,16 @@ +synth: synth1 synth2 + +synth1: main1.cpp SDLConsole.o SDLAudioManager.o + g++ main1.cpp SDLConsole.o SDLAudioManager.o -g -std=c++17 -o synth1 -lSDL2 -lSDL2_ttf + +synth2: main2.cpp SDLConsole.o SDLAudioManager.o + g++ main2.cpp SDLConsole.o SDLAudioManager.o -g -std=c++17 -o synth2 -lSDL2 -lSDL2_ttf + +SDLConsole.o: SDLConsole.cpp SDLConsole.h + g++ SDLConsole.cpp -c -g -std=c++17 -o SDLConsole.o -lSDL2 -lSDL2_ttf + +SDLAudioManager.o: SDLAudioManager.cpp SDLAudioManager.h + g++ SDLAudioManager.cpp -c -g -std=c++17 -o SDLAudioManager.o -lSDL2 + +clean: + rm *.o synth1 synth2 diff --git a/Linux/SDLAudioManager.cpp b/Linux/SDLAudioManager.cpp new file mode 100644 index 0000000..301431e --- /dev/null +++ b/Linux/SDLAudioManager.cpp @@ -0,0 +1,78 @@ +/* +SDLAudioManager.cpp +Copyright 2019 David V. Makray + +This file is dual licensed under both the GPL3 and MIT licenses. +*/ + +#include +#include +#include "SDLAudioManager.h" + +SDLAudioManager* SDLAudioManager::s_Instance = nullptr; + +SDLAudioManager::SDLAudioManager() +{ + s_Instance = this; + + if(SDL_Init(SDL_INIT_AUDIO) < 0) + { + std::cerr << "SDL2 didn't initialize." << std::endl; + exit(1); + } +} + +SDLAudioManager::~SDLAudioManager() +{ + SDL_CloseAudio(); +} + +static void AudioCallbackWrap(void* userdata, unsigned char* stream, int streamLength) +{ + SDLAudioManager::s_Instance->AudioCallback(userdata, stream, streamLength); +} + +void SDLAudioManager::AudioCallback(void* userdata, unsigned char* stream, int streamLength) +{ + TimeStruct* time_struct = (TimeStruct*)userdata; + if (streamLength == 0) + return; + + SDL_memset(stream, 0, streamLength); + float* stream_float = (float*)stream; + int audio_index; + for (audio_index = 0; audio_index < (streamLength/sizeof(float)); audio_index++) + { + stream_float[audio_index] = (float)(m_userFunction(time_struct->audio_time + (double)audio_index / (double)44100)); + } + time_struct->audio_time += (double)audio_index / 44100.0; +} + +double SDLAudioManager::GetTime() +{ + return time_struct.audio_time; +} + +void SDLAudioManager::SetUserFunction(double(*func)(double)) +{ + m_userFunction = func; + + //Setup the parameters of the audio stream + SDL_memset(&m_audio_req, 0, sizeof(m_audio_req)); + m_audio_req.freq = 48000; + m_audio_req.format = AUDIO_F32; + m_audio_req.channels = 1; + m_audio_req.samples = 1024; + m_audio_req.callback = AudioCallbackWrap; + m_audio_req.userdata = &time_struct; + + m_dev = SDL_OpenAudioDevice(NULL, 0, &m_audio_req, NULL, 0); + if(m_dev == 0) + { + std::cerr << "Error: " << SDL_GetError() << std::endl; + SDL_Quit(); + exit(1); + } + + SDL_PauseAudioDevice(m_dev, 0); //Play audio +} diff --git a/Linux/SDLAudioManager.h b/Linux/SDLAudioManager.h new file mode 100644 index 0000000..c18809f --- /dev/null +++ b/Linux/SDLAudioManager.h @@ -0,0 +1,25 @@ +const double PI = 2.0 * acos(0.0); + +static void AudioCallbackWrap(void*, unsigned char*, int); + +struct TimeStruct +{ + double audio_time = 0.0; +}; + +class SDLAudioManager +{ + public: + SDLAudioManager(); + ~SDLAudioManager(); + static SDLAudioManager* s_Instance; + void AudioCallback(void*, unsigned char*, int); + double GetTime(); + void SetUserFunction(double(*func)(double)); + + private: + TimeStruct time_struct; + double(*m_userFunction)(double); + SDL_AudioSpec m_audio_req; + SDL_AudioDeviceID m_dev; +}; diff --git a/Linux/SDLConsole.cpp b/Linux/SDLConsole.cpp new file mode 100644 index 0000000..1239623 --- /dev/null +++ b/Linux/SDLConsole.cpp @@ -0,0 +1,123 @@ +/* +SDLConsole.cpp +Copyright 2019 David V. Makray + +This file is dual licensed under both the GPL3 and MIT licenses. +*/ + +#include +#include +#include +#include + +#include "SDLConsole.h" + +SDLConsole::SDLConsole() +{ + //Initialize SDL video + if (SDL_Init(SDL_INIT_VIDEO) < 0) + { + std::cerr << "Unable to init SDL: " << SDL_GetError() << std::endl; + exit(1); + } + + if (TTF_Init() != 0) + { + std::cerr << "TTF_Init failed." << std::endl; + SDL_Quit(); + exit(1); + } + + m_font = TTF_OpenFont("/usr/share/fonts/truetype/freefont/FreeMono.ttf", 16); + if (m_font == nullptr) + { + std::cerr << "TTF_OpenFont failed." << std::endl; + SDL_Quit(); + exit(1); + } + m_font_height = TTF_FontHeight(m_font); + + //Make sure SDL cleans up prior to exit + atexit(SDL_Quit); + + //Create a new window + SDL_Window* window = SDL_CreateWindow(NULL, 0, 0, 800, 600, SDL_WINDOW_SHOWN); + if (window == nullptr) + { + std::cerr << "CreateWindow failed." << std::endl; + SDL_Quit(); + exit(1); + } + + m_renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); + if (m_renderer == nullptr) + { + std::cerr << "CreateRenderer failed." << std::endl; + SDL_DestroyWindow(window); + SDL_Quit(); + exit(1); + } +} + +SDLConsole::~SDLConsole() +{ + TTF_CloseFont(m_font); + SDL_Quit(); +} + +SDLConsole& SDLConsole::operator<<(std::string stream_text) +{ + if (m_ConsoleText.size() == 0) + m_ConsoleText.push_back(stream_text); + else + m_ConsoleText.back() += stream_text; + return *this; +} + +SDLConsole& SDLConsole::operator<<(Endl) +{ + m_ConsoleText.push_back(""); + return *this; +} + +void SDLConsole::Render() +{ + SDL_SetRenderDrawColor(m_renderer, 0, 0, 0, 255); + SDL_RenderClear(m_renderer); + + const SDL_Color white = { 255, 255, 255, 255 }; + int cumlative_font_offset = 0; + + for (auto current_line : m_ConsoleText) + { + if (current_line == "") + { + cumlative_font_offset += m_font_height + 1; + continue; + } + + //Render a line of text into a surface + SDL_Surface* surface = TTF_RenderText_Blended(m_font, current_line.c_str(), white); + if (surface == nullptr) + { + std::cerr << "TTF_RenderText failed." << std::endl; + SDL_Quit(); + exit(1); + } + + //Make surface into a texture + SDL_Texture* texture = SDL_CreateTextureFromSurface(m_renderer, surface); + SDL_FreeSurface(surface); + if (texture == nullptr) + { + std::cerr << "CreateTexture failed." << std::endl; + SDL_Quit(); + exit(1); + } + SDL_Rect dest = {0, cumlative_font_offset, 0, 0}; + SDL_QueryTexture(texture, NULL, NULL, &dest.w, &dest.h); + SDL_RenderCopy(m_renderer, texture, NULL, &dest); + cumlative_font_offset += m_font_height + 1; + } + SDL_RenderPresent(m_renderer); +} diff --git a/Linux/SDLConsole.h b/Linux/SDLConsole.h new file mode 100644 index 0000000..1ab4777 --- /dev/null +++ b/Linux/SDLConsole.h @@ -0,0 +1,18 @@ +class SDLConsole +{ + public: + SDLConsole(); + ~SDLConsole(); + void Render(); + + std::vector m_ConsoleText; + static struct Endl{} endl; + + SDLConsole& operator<<(std::string); + SDLConsole& operator<<(Endl); + + private: + SDL_Renderer* m_renderer; + TTF_Font* m_font; + int m_font_height; +}; diff --git a/Linux/main1.cpp b/Linux/main1.cpp new file mode 100644 index 0000000..3a1ee7a --- /dev/null +++ b/Linux/main1.cpp @@ -0,0 +1,145 @@ +/* + OneLoneCoder.com - Simple Audio Noisy Thing + "Allows you to simply listen to that waveform!" - @Javidx9 + + License + ~~~~~~~ + Copyright (C) 2018 Javidx9 + This program comes with ABSOLUTELY NO WARRANTY. + This is free software, and you are welcome to redistribute it + under certain conditions; See license for details. + Original works located at: + https://www.github.com/onelonecoder + https://www.onelonecoder.com + https://www.youtube.com/javidx9 + + GNU GPLv3 + https://github.com/OneLoneCoder/videos/blob/master/LICENSE + + From Javidx9 :) + ~~~~~~~~~~~~~~~ + Hello! Ultimately I don't care what you use this for. It's intended to be + educational, and perhaps to the oddly minded - a little bit of fun. + Please hack this, change it and use it in any way you see fit. You acknowledge + that I am not responsible for anything bad that happens as a result of + your actions. However this code is protected by GNU GPLv3, see the license in the + github repo. This means you must attribute me if you use it. You can view this + license here: https://github.com/OneLoneCoder/videos/blob/master/LICENSE + Cheers! + + Author + ~~~~~~ + + Twitter: @javidx9 + Blog: www.onelonecoder.com + + Versions + ~~~~~~~~ + + This is the first version of the software. It presents a simple keyboard and a sine + wave oscillator. + + See video: https://youtu.be/tgamhuQnOkM + +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "SDLConsole.h" +#include "SDLAudioManager.h" + +using namespace std; + +// Global synthesizer variables +atomic dFrequencyOutput = 0.0; // dominant output frequency of instrument, i.e. the note +double dOctaveBaseFrequency = 110.0; // A2 // frequency of octave represented by keyboard +double d12thRootOf2 = pow(2.0, 1.0 / 12.0); // assuming western 12 notes per ocatve + +// Function used by olcNoiseMaker to generate sound waves +// Returns amplitude (-1.0 to +1.0) as a function of time +double MakeNoise(double dTime) +{ + double dOutput = sin(dFrequencyOutput * 2.0 * 3.14159 * dTime); + return dOutput * 0.5; // Master Volume +} + +int main() +{ + SDLAudioManager audio_manager; + audio_manager.SetUserFunction(MakeNoise); + SDLConsole console; + + // Shameless self-promotion + console << "www.OneLoneCoder.com - Synthesizer Part 1" << SDLConsole::endl; + console << "Single Sine Wave Oscillator, No Polyphony" << SDLConsole::endl << SDLConsole::endl; + console << "Using the SDL2 Library" << SDLConsole::endl << SDLConsole::endl; + + // Display a keyboard + console << SDLConsole::endl << + "| | | | | | | | | | | | | | | | |" << SDLConsole::endl << + "| | S | | | F | | G | | | J | | K | | L | | |" << SDLConsole::endl << + "| |___| | |___| |___| | |___| |___| |___| | |__" << SDLConsole::endl << + "| | | | | | | | | | |" << SDLConsole::endl << + "| Z | X | C | V | B | N | M | , | . | / |" << SDLConsole::endl << + "|_____|_____|_____|_____|_____|_____|_____|_____|_____|_____|" << SDLConsole::endl << SDLConsole::endl; + console << "Key not pressed."; + + // Sit in loop, capturing keyboard state changes and modify + // synthesizer output accordingly + int nCurrentKey = -1; + int keys[] = {SDLK_z, SDLK_s, SDLK_x, SDLK_c, SDLK_f, SDLK_v, SDLK_g, SDLK_b, SDLK_n, SDLK_j, SDLK_m, SDLK_k, SDLK_COMMA, SDLK_l, SDLK_PERIOD, SDLK_SLASH}; + bool done = false; + while (!done) + { + SDL_Event event; + if (SDL_WaitEvent(&event)) + { + switch (event.type) + { + case SDL_QUIT: + done = true; + break; + + case SDL_KEYDOWN: + if (event.key.keysym.sym == SDLK_ESCAPE) + done = true; + + for (int k = 0; k < 16; k++) + { + if (event.key.keysym.sym == keys[k]) + { + if (nCurrentKey != k) + { + dFrequencyOutput = dOctaveBaseFrequency * pow(d12thRootOf2, k); + console.m_ConsoleText.back() = "Note On : " + to_string(audio_manager.GetTime()) + "s " + to_string(dFrequencyOutput) + "Hz"; + nCurrentKey = k; + } + } + } + break; + + case SDL_KEYUP: + if (nCurrentKey != -1) + { + console.m_ConsoleText.back() = "Note Off: " + to_string(audio_manager.GetTime()) + "s"; + dFrequencyOutput = 0.0; + nCurrentKey = -1; + } + break; + } + } + console.Render(); + } + + SDL_Quit(); + return 0; +} diff --git a/Linux/main2.cpp b/Linux/main2.cpp new file mode 100644 index 0000000..03dc541 --- /dev/null +++ b/Linux/main2.cpp @@ -0,0 +1,289 @@ +/* +OneLoneCoder.com - Simple Audio Noisy Thing +"Allows you to simply listen to that waveform!" - @Javidx9 + +License +~~~~~~~ +Copyright (C) 2018 Javidx9 +This program comes with ABSOLUTELY NO WARRANTY. +This is free software, and you are welcome to redistribute it +under certain conditions; See license for details. +Original works located at: +https://www.github.com/onelonecoder +https://www.onelonecoder.com +https://www.youtube.com/javidx9 + +GNU GPLv3 +https://github.com/OneLoneCoder/videos/blob/master/LICENSE + +From Javidx9 :) +~~~~~~~~~~~~~~~ +Hello! Ultimately I don't care what you use this for. It's intended to be +educational, and perhaps to the oddly minded - a little bit of fun. +Please hack this, change it and use it in any way you see fit. You acknowledge +that I am not responsible for anything bad that happens as a result of +your actions. However this code is protected by GNU GPLv3, see the license in the +github repo. This means you must attribute me if you use it. You can view this +license here: https://github.com/OneLoneCoder/videos/blob/master/LICENSE +Cheers! + + +Author +~~~~~~ + +Twitter: @javidx9 +Blog: www.onelonecoder.com + +Versions +~~~~~~~~ + +main2.cpp +This version expands on oscillators to include other waveforms +and introduces envelopes +See Video: https://youtu.be/OSCzKOqtgcA + +main1.cpp +This is the first version of the software. It presents a simple +keyboard and a sine wave oscillator. +See video: https://youtu.be/tgamhuQnOkM + +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "SDLConsole.h" +#include "SDLAudioManager.h" + +using namespace std; + +// Converts frequency (Hz) to angular velocity +double w(double dHertz) +{ + return dHertz * 2.0 * PI; +} + +// General purpose oscillator +#define OSC_SINE 0 +#define OSC_SQUARE 1 +#define OSC_TRIANGLE 2 +#define OSC_SAW_ANA 3 +#define OSC_SAW_DIG 4 +#define OSC_NOISE 5 + +double osc(double dHertz, double dTime, int nType = OSC_SINE) +{ + switch (nType) + { + case OSC_SINE: // Sine wave bewteen -1 and +1 + return sin(w(dHertz) * dTime); + + case OSC_SQUARE: // Square wave between -1 and +1 + return sin(w(dHertz) * dTime) > 0 ? 1.0 : -1.0; + + case OSC_TRIANGLE: // Triangle wave between -1 and +1 + return asin(sin(w(dHertz) * dTime)) * (2.0 / PI); + + case OSC_SAW_ANA: // Saw wave (analogue / warm / slow) + { + double dOutput = 0.0; + + for (double n = 1.0; n < 40.0; n++) + dOutput += (sin(n * w(dHertz) * dTime)) / n; + + return dOutput * (2.0 / PI); + } + + case OSC_SAW_DIG: // Saw Wave (optimised / harsh / fast) + return (2.0 / PI) * (dHertz * PI * fmod(dTime, 1.0 / dHertz) - (PI / 2.0)); + + + case OSC_NOISE: // Pseudorandom noise + return 2.0 * ((double)rand() / (double)RAND_MAX) - 1.0; + + default: + return 0.0; + } +} + +// Amplitude (Attack, Decay, Sustain, Release) Envelope +struct sEnvelopeADSR +{ + double dAttackTime; + double dDecayTime; + double dSustainAmplitude; + double dReleaseTime; + double dStartAmplitude; + double dTriggerOffTime; + double dTriggerOnTime; + bool bNoteOn; + + sEnvelopeADSR() + { + dAttackTime = 0.10; + dDecayTime = 0.01; + dStartAmplitude = 1.0; + dSustainAmplitude = 0.8; + dReleaseTime = 0.20; + bNoteOn = false; + dTriggerOffTime = 0.0; + dTriggerOnTime = 0.0; + } + + // Call when key is pressed + void NoteOn(double dTimeOn) + { + dTriggerOnTime = dTimeOn; + bNoteOn = true; + } + + // Call when key is released + void NoteOff(double dTimeOff) + { + dTriggerOffTime = dTimeOff; + bNoteOn = false; + } + + // Get the correct amplitude at the requested point in time + double GetAmplitude(double dTime) + { + double dAmplitude = 0.0; + double dLifeTime = dTime - dTriggerOnTime; + + if (bNoteOn) + { + if (dLifeTime <= dAttackTime) + { + // In attack Phase - approach max amplitude + dAmplitude = (dLifeTime / dAttackTime) * dStartAmplitude; + } + + if (dLifeTime > dAttackTime && dLifeTime <= (dAttackTime + dDecayTime)) + { + // In decay phase - reduce to sustained amplitude + dAmplitude = ((dLifeTime - dAttackTime) / dDecayTime) * (dSustainAmplitude - dStartAmplitude) + dStartAmplitude; + } + + if (dLifeTime > (dAttackTime + dDecayTime)) + { + // In sustain phase - dont change until note released + dAmplitude = dSustainAmplitude; + } + } + else + { + // Note has been released, so in release phase + dAmplitude = ((dTime - dTriggerOffTime) / dReleaseTime) * (0.0 - dSustainAmplitude) + dSustainAmplitude; + } + + // Amplitude should not be negative + if (dAmplitude <= 0.0001) + dAmplitude = 0.0; + + return dAmplitude; + } +}; + + + + +// Global synthesizer variables +atomic dFrequencyOutput = 0.0; // dominant output frequency of instrument, i.e. the note +sEnvelopeADSR envelope; // amplitude modulation of output to give texture, i.e. the timbre +double dOctaveBaseFrequency = 110.0; // A2 // frequency of octave represented by keyboard +double d12thRootOf2 = pow(2.0, 1.0 / 12.0); // assuming western 12 notes per ocatve + +// Function used by olcNoiseMaker to generate sound waves +// Returns amplitude (-1.0 to +1.0) as a function of time +double MakeNoise(double dTime) +{ + // Mix together a little sine and square waves + double dOutput = envelope.GetAmplitude(dTime) * + ( + + 1.0 * osc(dFrequencyOutput * 0.5, dTime, OSC_SINE) + + 1.0 * osc(dFrequencyOutput, dTime, OSC_SAW_ANA) + ); + + return dOutput * 0.4; // Master Volume +} + +int main() +{ + SDLAudioManager audio_manager; + audio_manager.SetUserFunction(MakeNoise); + SDLConsole console; + + // Shameless self-promotion + console << "www.OneLoneCoder.com - Synthesizer Part 2" << SDLConsole::endl << "Multiple Oscillators with Single Amplitude Envelope, No Polyphony" << SDLConsole::endl << SDLConsole::endl; + console << "Using the SDL2 Library" << SDLConsole::endl << SDLConsole::endl; + + // Display a keyboard + console << SDLConsole::endl << + "| | | | | | | | | | | | | | | | |" << SDLConsole::endl << + "| | S | | | F | | G | | | J | | K | | L | | |" << SDLConsole::endl << + "| |___| | |___| |___| | |___| |___| |___| | |__" << SDLConsole::endl << + "| | | | | | | | | | |" << SDLConsole::endl << + "| Z | X | C | V | B | N | M | , | . | / |" << SDLConsole::endl << + "|_____|_____|_____|_____|_____|_____|_____|_____|_____|_____|" << SDLConsole::endl << SDLConsole::endl; + console << "Key not pressed."; + + // Sit in loop, capturing keyboard state changes and modify + // synthesizer output accordingly + int nCurrentKey = -1; + int keys[] = {SDLK_z, SDLK_s, SDLK_x, SDLK_c, SDLK_f, SDLK_v, SDLK_g, SDLK_b, SDLK_n, SDLK_j, SDLK_m, SDLK_k, SDLK_COMMA, SDLK_l, SDLK_PERIOD, SDLK_SLASH}; + bool done = false; + while (!done) + { + SDL_Event event; + if (SDL_WaitEvent(&event)) + { + switch (event.type) + { + case SDL_QUIT: + done = true; + break; + + case SDL_KEYDOWN: + if (event.key.keysym.sym == SDLK_ESCAPE) + done = true; + + for (int k = 0; k < 16; k++) + { + if (event.key.keysym.sym == keys[k]) + { + if (nCurrentKey != k) + { + dFrequencyOutput = dOctaveBaseFrequency * pow(d12thRootOf2, k); + envelope.NoteOn(audio_manager.GetTime()); + console.m_ConsoleText.back() = "Note On : " + to_string(audio_manager.GetTime()) + "s " + to_string(dFrequencyOutput) + "Hz"; + nCurrentKey = k; + } + } + } + break; + + case SDL_KEYUP: + if (nCurrentKey != -1) + { + console.m_ConsoleText.back() = "Note Off: " + to_string(audio_manager.GetTime()) + "s"; + envelope.NoteOff(audio_manager.GetTime()); + nCurrentKey = -1; + } + break; + } + } + console.Render(); + } + + SDL_Quit(); + return 0; +}