diff options
-rw-r--r-- | Makefile | 8 | ||||
-rw-r--r-- | audio.cpp | 88 | ||||
-rw-r--r-- | audio.h | 24 | ||||
-rw-r--r-- | gui.cpp | 86 | ||||
-rw-r--r-- | gui.h | 35 | ||||
-rw-r--r-- | main.cpp | 184 | ||||
-rw-r--r-- | sequence.h | 12 |
7 files changed, 261 insertions, 176 deletions
@@ -1,2 +1,6 @@ -fmseq: main.cpp - g++ main.cpp -lSDL2 -o fmseq
\ No newline at end of file +SRC=main.cpp\ + audio.cpp\ + gui.cpp + +fmseq: ${SRC} + g++ ${SRC} -lSDL2 -o fmseq
\ No newline at end of file diff --git a/audio.cpp b/audio.cpp new file mode 100644 index 0000000..712dddc --- /dev/null +++ b/audio.cpp @@ -0,0 +1,88 @@ +#include "audio.h" + +#define _USE_MATH_DEFINES +#include <cmath> +#include <iostream> +#include <SDL2/SDL.h> + +#include "sequence.h" + +static const float AMPLITUDE = 0.2f; + +// SDL periodically calls this function to fill a chunk of the audio output buffer +static void AudioCallback(void* userData, Uint8* rawBuffer, int bytes) +{ + float* buffer = (float*)rawBuffer; + int length = bytes / sizeof(float); + CallbackInfo* info = (CallbackInfo*)userData; + StepData& step = info->seq->steps[info->currentStep]; + + for (int i = 0; i < length; i++, info->samplesDone++) { + float time = (float)info->samplesDone / (float)info->sampleRate; + float modWave = sinf(2.0f * M_PI * step.modFreq * time); + float freq = step.carrierFreq + modWave * step.modDepth; + + // Check if enough samples have been generated for this step + if (info->samplesDone >= info->samplesPerStep) { + // If so, move on to the next step + info->samplesDone = 0; + step = info->seq->steps[info->currentStep]; + info->currentStep = (info->currentStep + 1) % 8; + } + + buffer[i] = (sinf(2.0f * M_PI * freq * time)) * AMPLITUDE; + } +} + +AudioContext::AudioContext(Sequence& seq) +{ + SDL_AudioSpec desiredSpec; + desiredSpec.freq = 44100; + desiredSpec.format = AUDIO_F32SYS; + desiredSpec.channels = 1; + desiredSpec.samples = 2048; + desiredSpec.callback = AudioCallback; + desiredSpec.userdata = &m_info; + + SDL_AudioSpec obtainedSpec; + if (SDL_OpenAudio(&desiredSpec, &obtainedSpec) < 0) { + std::cerr << "Failed to initalize audio! " << SDL_GetError() << std::endl; + std::exit(1); + } + + // The audio callback expects float values, so if a different data type + // is obtained we should throw an error. + if (desiredSpec.format != obtainedSpec.format) { + std::cerr << "Unexpected audio format: " << obtainedSpec.format << std::endl; + std::exit(1); + } + + m_info.samplesDone = 0; + m_info.samplesPerStep = obtainedSpec.freq * 0.4f; + m_info.currentStep = 0; + m_info.sampleRate = obtainedSpec.freq; + m_info.seq = &seq; + + this->Start(); +} + +AudioContext::~AudioContext() +{ + this->Stop(); + SDL_CloseAudio(); +} + +void AudioContext::Start() +{ + SDL_PauseAudio(0); +} + +void AudioContext::Stop() +{ + SDL_PauseAudio(1); +} + +int AudioContext::CurrentStep() +{ + return m_info.currentStep; +}
\ No newline at end of file @@ -0,0 +1,24 @@ +#pragma once + +class Sequence; + +struct CallbackInfo { + int samplesDone; + int samplesPerStep; + int currentStep; + int sampleRate; + Sequence* seq; +}; + +class AudioContext { +public: + AudioContext(Sequence&); + ~AudioContext(); + + void Start(); + void Stop(); + + int CurrentStep(); +private: + CallbackInfo m_info; +};
\ No newline at end of file @@ -0,0 +1,86 @@ +#include "gui.h" + +#include <SDL2/SDL.h> +#include "sequence.h" + +void Knob::Draw(SDL_Renderer* renderer) +{ + constexpr int Radius = 25; + SDL_Rect rect = { m_x, m_y, Radius, Radius }; + SDL_RenderDrawRect(renderer, &rect); + + SDL_RenderDrawLine(renderer, m_x, m_y + (*value * valueScaling), m_x + Radius, m_y + (*value * valueScaling)); +} + +bool Knob::InBounds(int x, int y) +{ + constexpr int Radius = 25; + SDL_Rect rect = { m_x, m_y, Radius, Radius }; + SDL_Point point = { x, y }; + + return SDL_PointInRect(&point, &rect); +} + +GUI::GUI(Sequence& seq) +{ + int i = 0; + for (auto& step : seq.steps) { + ++i; + m_knobs.emplace_back(i * 30 + 10, 120, &step.carrierFreq, 0.1f); + m_knobs.emplace_back(i * 30 + 10, 180, &step.modFreq); + m_knobs.emplace_back(i * 30 + 10, 220, &step.modDepth, 3.0f); + } +} + +void GUI::OnMouseDown(int x, int y) +{ + for (Knob& knob : m_knobs) { + if (knob.InBounds(x, y)) { + m_activeKnob = &knob; + m_dragStart = y; + return; + } + } +} + +void GUI::OnMouseUp() +{ + m_activeKnob = nullptr; + m_dragStart = 0; +} + +void GUI::OnMouseMove(int x, int y) +{ + if (m_activeKnob) { + *m_activeKnob->value -= (m_dragStart - y) / m_activeKnob->valueScaling; + m_dragStart = y; + + if (*m_activeKnob->value < 0) + *m_activeKnob->value = 0; + } +} + +void GUI::Repaint(SDL_Renderer* renderer, int currentStep) +{ + SDL_Rect rect = { + (640 - 8 * (32 + 8)) / 2, + (320 - 32) / 2, + 32, + 32 + }; + + for (int i = 0; i < 8; i++) { + SDL_SetRenderDrawColor(renderer, 128, i * (255 / 8), 255, 255); + + if (i == currentStep) + SDL_RenderFillRect(renderer, &rect); + else + SDL_RenderDrawRect(renderer, &rect); + + rect.x += rect.w + 8; + } + + for (Knob& knob : m_knobs) { + knob.Draw(renderer); + } +}
\ No newline at end of file @@ -0,0 +1,35 @@ +#pragma once + +#include <vector> +#include "sequence.h" + +struct SDL_Renderer; + +class Knob { +public: + Knob(int x, int y, float* valuePtr, float scaling = 1.f) + : m_x(x), m_y(y), value(valuePtr), valueScaling(scaling) {} + + void Draw(SDL_Renderer*); + bool InBounds(int x, int y); + + float* value; + float valueScaling; +private: + int m_x, m_y; +}; + +class GUI { +public: + GUI(Sequence&); + + void OnMouseDown(int x, int y); + void OnMouseUp(); + void OnMouseMove(int x, int y); + + void Repaint(SDL_Renderer*, int currentStep); +private: + std::vector<Knob> m_knobs; + Knob* m_activeKnob{ nullptr }; + int m_dragStart{ 0 }; +};
\ No newline at end of file @@ -1,107 +1,9 @@ #include <SDL2/SDL.h> -#define _USE_MATH_DEFINES -#include <cmath> #include <iostream> -#include <vector> -const float AMPLITUDE = 0.2f; -float sequence[8] = { 130.81f, 220.f, 130.81f, 440.f, 330.f, 440.f, 130.81f, 261.63f }; -float modSequence[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; -float mulSequence[8] = { 1, 1, 1, 1, 1, 1, 1, 1 }; - -class Knob { -public: - Knob(int x, int y, float *valuePtr, float scaling = 1.f) - : m_x(x), m_y(y), value(valuePtr), valueScaling(scaling) {} - - void Draw(SDL_Renderer*); - bool InBounds(int x, int y); - - float *value; - float valueScaling; -private: - int m_x, m_y; -}; - -std::vector<Knob> knobs; - -class Window { -public: - Window(); -private: - bool m_isRunning{ false }; -}; - -void Knob::Draw(SDL_Renderer *renderer) -{ - constexpr int Radius = 25; - SDL_Rect rect = { m_x, m_y, Radius, Radius }; - SDL_RenderDrawRect(renderer, &rect); - - SDL_RenderDrawLine(renderer, m_x, m_y + (*value * valueScaling), m_x + Radius, m_y + (*value * valueScaling)); -} - -bool Knob::InBounds(int x, int y) -{ - constexpr int Radius = 25; - SDL_Rect rect = { m_x, m_y, Radius, Radius }; - SDL_Point point = { x, y }; - - return SDL_PointInRect(&point, &rect); -} - -struct CallbackInfo { - int samplesDone; - int samplesPerStep; - int currentStep; - int sampleRate; -}; - -void AudioCallback(void *userData, Uint8 *rawBuffer, int bytes) -{ - float *buffer = (float*)rawBuffer; - int length = bytes / sizeof(float); - CallbackInfo *info = (CallbackInfo*)userData; - - for (int i = 0; i < length; i++, info->samplesDone++) { - float time = (float)info->samplesDone / (float)info->sampleRate; - float modWave = sinf(2.0f * M_PI * modSequence[info->currentStep] * time); - float freq = sequence[info->currentStep] + modWave * mulSequence[info->currentStep] ; - - if (info->samplesDone >= info->samplesPerStep) { - info->samplesDone = 0; - info->currentStep = (info->currentStep + 1) % 8; - } - - buffer[i] = (sinf(2.0f * M_PI * freq * time)) * AMPLITUDE; - } -} - -void DrawInterface(SDL_Renderer* renderer, int currentStep) -{ - SDL_Rect rect = { - (640 - 8 * (32 + 8)) / 2, - (320 - 32) / 2, - 32, - 32 - }; - - for (int i = 0; i < 8; i++) { - SDL_SetRenderDrawColor(renderer, 128, i * (255 / 8), 255, 255); - - if (i == currentStep) { - SDL_RenderFillRect(renderer, &rect); - } else { - SDL_RenderDrawRect(renderer, &rect); - } - - rect.x += rect.w + 8; - } - - for (Knob& knob : knobs) { - knob.Draw(renderer); - } -} +#include "audio.h" +#include "gui.h" +#include "sequence.h" int main(int argc, char** argv) { @@ -114,62 +16,11 @@ int main(int argc, char** argv) SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED); SDL_Event event; - CallbackInfo info; - - SDL_AudioSpec desiredSpec; - desiredSpec.freq = 44100; - desiredSpec.format = AUDIO_F32SYS; - desiredSpec.channels = 1; - desiredSpec.samples = 2048; - desiredSpec.callback = AudioCallback; - desiredSpec.userdata = &info; - - SDL_AudioSpec obtainedSpec; - if (SDL_OpenAudio(&desiredSpec, &obtainedSpec) < 0) { - std::cerr << "Failed to initalize audio! " << SDL_GetError() << std::endl; - return 1; - } - - if (desiredSpec.format != obtainedSpec.format) - std::cerr << "Different format: " << obtainedSpec.format << std::endl; - - info.samplesDone = 0; - info.samplesPerStep = obtainedSpec.freq * 0.4f; - info.currentStep = 0; - info.sampleRate = obtainedSpec.freq; - - SDL_PauseAudio(0); - - knobs.emplace_back(10, 120, sequence, 0.1f); - knobs.emplace_back(40, 120, sequence + 1, 0.1f); - knobs.emplace_back(70, 120, sequence + 2, 0.1f); - knobs.emplace_back(110, 120, sequence + 3, 0.1f); - knobs.emplace_back(140, 120, sequence + 4, 0.1f); - knobs.emplace_back(170, 120, sequence + 5, 0.1f); - knobs.emplace_back(210, 120, sequence + 6, 0.1f); - knobs.emplace_back(240, 120, sequence + 7, 0.1f); - - knobs.emplace_back(10, 180, modSequence); - knobs.emplace_back(40, 180, modSequence + 1); - knobs.emplace_back(70, 180, modSequence + 2); - knobs.emplace_back(110, 180, modSequence + 3); - knobs.emplace_back(140, 180, modSequence + 4); - knobs.emplace_back(170, 180, modSequence + 5); - knobs.emplace_back(210, 180, modSequence + 6); - knobs.emplace_back(240, 180, modSequence + 7); - - knobs.emplace_back(10, 220, mulSequence, 3.0f); - knobs.emplace_back(40, 220, mulSequence + 1, 3.0f); - knobs.emplace_back(70, 220, mulSequence + 2, 3.0f); - knobs.emplace_back(110, 220, mulSequence + 3, 3.0f); - knobs.emplace_back(140, 220, mulSequence + 4, 3.0f); - knobs.emplace_back(170, 220, mulSequence + 5, 3.0f); - knobs.emplace_back(210, 220, mulSequence + 6, 3.0f); - knobs.emplace_back(240, 220, mulSequence + 7, 3.0f); + Sequence sequence; + AudioContext audio(sequence); + GUI gui(sequence); bool running = true; - Knob* mouseKnob = nullptr; - int yStart = 0; while (running) { while (SDL_PollEvent(&event)) { switch (event.type) { @@ -177,25 +28,13 @@ int main(int argc, char** argv) running = false; break; case SDL_MOUSEBUTTONDOWN: - for (Knob& knob : knobs) { - if (knob.InBounds(event.button.x, event.button.y)) { - mouseKnob = &knob; - yStart = event.button.y; - break; - } - } + gui.OnMouseDown(event.button.x, event.button.y); break; case SDL_MOUSEMOTION: - if (mouseKnob) { - *mouseKnob->value -= (yStart - event.button.y) / mouseKnob->valueScaling; - yStart = event.button.y; - if (*mouseKnob->value < 0) *mouseKnob->value = 0; - } - + gui.OnMouseMove(event.button.x, event.button.y); break; case SDL_MOUSEBUTTONUP: - mouseKnob = nullptr; - yStart = 0; + gui.OnMouseUp(); break; } } @@ -203,7 +42,7 @@ int main(int argc, char** argv) SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); SDL_RenderClear(renderer); - DrawInterface(renderer, info.currentStep); + gui.Repaint(renderer, audio.CurrentStep()); SDL_RenderPresent(renderer); SDL_UpdateWindowSurface(window); @@ -211,9 +50,6 @@ int main(int argc, char** argv) SDL_Delay(10); } - SDL_PauseAudio(1); - SDL_CloseAudio(); - SDL_DestroyWindow(window); SDL_Quit(); diff --git a/sequence.h b/sequence.h new file mode 100644 index 0000000..2698fe6 --- /dev/null +++ b/sequence.h @@ -0,0 +1,12 @@ +#pragma once + +struct StepData { + float carrierFreq = 440.f; + float modFreq = 0; + float modDepth = 1; +}; + +class Sequence { +public: + StepData steps[8]; +};
\ No newline at end of file |