diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/audio.cpp | 88 | ||||
-rw-r--r-- | src/audio.h | 24 | ||||
-rw-r--r-- | src/gui.cpp | 77 | ||||
-rw-r--r-- | src/gui.h | 37 | ||||
-rw-r--r-- | src/main.cpp | 57 | ||||
-rw-r--r-- | src/sequence.h | 12 |
6 files changed, 295 insertions, 0 deletions
diff --git a/src/audio.cpp b/src/audio.cpp new file mode 100644 index 0000000..db915e1 --- /dev/null +++ b/src/audio.cpp @@ -0,0 +1,88 @@ +#include "audio.h" + +#define _USE_MATH_DEFINES +#include <cmath> +#include <iostream> +#include <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 diff --git a/src/audio.h b/src/audio.h new file mode 100644 index 0000000..6205de7 --- /dev/null +++ b/src/audio.h @@ -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 diff --git a/src/gui.cpp b/src/gui.cpp new file mode 100644 index 0000000..fcd4511 --- /dev/null +++ b/src/gui.cpp @@ -0,0 +1,77 @@ +#include "gui.h" + +#include "sequence.h" + +void Slider::Draw(SDL_Renderer* renderer) +{ + SDL_RenderDrawRect(renderer, &m_bounds); + SDL_RenderDrawLine(renderer, m_bounds.x, m_bounds.y + (*value * valueScaling), m_bounds.x + m_bounds.w, m_bounds.y + (*value * valueScaling)); +} + +bool Slider::InBounds(int x, int y) +{ + SDL_Point point = { x, y }; + return SDL_PointInRect(&point, &m_bounds); +} + +GUI::GUI(Sequence& seq) + : m_sequence(seq) +{ + int i = 0; + for (auto& step : seq.steps) { + m_sliders.emplace_back(i * 40 + 14, 60, &step.carrierFreq, 0.1f); + m_sliders.emplace_back(i * 40 + 14, 145, &step.modFreq); + m_sliders.emplace_back(i * 40 + 14, 230, &step.modDepth, 3.0f); + i++; + } +} + +void GUI::OnMouseDown(int x, int y) +{ + for (Slider& slider : m_sliders) { + if (slider.InBounds(x, y)) { + m_activeSlider = &slider; + m_dragStart = y; + return; + } + } +} + +void GUI::OnMouseUp() +{ + m_activeSlider = nullptr; + m_dragStart = 0; +} + +void GUI::OnMouseMove(int x, int y) +{ + if (m_activeSlider) { + *m_activeSlider->value -= (m_dragStart - y) / m_activeSlider->valueScaling; + m_dragStart = y; + + if (*m_activeSlider->value < 0) + *m_activeSlider->value = 0; + } +} + +void GUI::Repaint(SDL_Renderer* renderer, int currentStep) +{ + SDL_Rect rect = { 10, 10, 32, 32 }; + + for (int i = 0; i < 8; i++) { + SDL_SetRenderDrawColor(renderer, m_sequence.steps[i].modDepth * 8, m_sequence.steps[i].modFreq * 2, m_sequence.steps[i].carrierFreq / 3, 255); + + if (i == currentStep) + SDL_RenderFillRect(renderer, &rect); + else + SDL_RenderDrawRect(renderer, &rect); + + rect.x += rect.w + 8; + } + + SDL_SetRenderDrawColor(renderer, 127, 127, 255, 255); + + for (Slider& slider : m_sliders) { + slider.Draw(renderer); + } +}
\ No newline at end of file diff --git a/src/gui.h b/src/gui.h new file mode 100644 index 0000000..76a1128 --- /dev/null +++ b/src/gui.h @@ -0,0 +1,37 @@ +#pragma once + +#include <SDL.h> + +#include <vector> +#include "sequence.h" + +class Slider { +public: + Slider(int x, int y, float* valuePtr, float scaling = 1.f) + : m_bounds({ x, y, 25, 75 }), value(valuePtr), valueScaling(scaling) {} + + void Draw(SDL_Renderer*); + bool InBounds(int x, int y); + + float* value; + float valueScaling; +private: + SDL_Rect m_bounds; +}; + +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<Slider> m_sliders; + + Sequence& m_sequence; + Slider* m_activeSlider{ nullptr }; + int m_dragStart{ 0 }; +};
\ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..6056442 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,57 @@ +#include <SDL.h> +#include <iostream> + +#include "audio.h" +#include "gui.h" +#include "sequence.h" + +int main(int argc, char** argv) +{ + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) { + std::cerr << "Failed to initalize SDL! " << SDL_GetError() << std::endl; + return 1; + } + + SDL_Window* window = SDL_CreateWindow("fmseq", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 640, 320, SDL_WINDOW_SHOWN); + SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED); + SDL_Event event; + + Sequence sequence; + AudioContext audio(sequence); + GUI gui(sequence); + + bool running = true; + while (running) { + while (SDL_PollEvent(&event)) { + switch (event.type) { + case SDL_QUIT: + running = false; + break; + case SDL_MOUSEBUTTONDOWN: + gui.OnMouseDown(event.button.x, event.button.y); + break; + case SDL_MOUSEMOTION: + gui.OnMouseMove(event.button.x, event.button.y); + break; + case SDL_MOUSEBUTTONUP: + gui.OnMouseUp(); + break; + } + } + + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); + SDL_RenderClear(renderer); + + gui.Repaint(renderer, audio.CurrentStep()); + + SDL_RenderPresent(renderer); + SDL_UpdateWindowSurface(window); + + SDL_Delay(10); + } + + SDL_DestroyWindow(window); + SDL_Quit(); + + return 0; +}
\ No newline at end of file diff --git a/src/sequence.h b/src/sequence.h new file mode 100644 index 0000000..decd9fd --- /dev/null +++ b/src/sequence.h @@ -0,0 +1,12 @@ +#pragma once + +struct StepData { + float carrierFreq = 110.f; + float modFreq = 0; + float modDepth = 1; +}; + +class Sequence { +public: + StepData steps[8]; +};
\ No newline at end of file |