summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--TODO9
-rw-r--r--source/saw/main.c180
2 files changed, 168 insertions, 21 deletions
diff --git a/TODO b/TODO
index 7641fae..d5fd380 100644
--- a/TODO
+++ b/TODO
@@ -1,10 +1,13 @@
To-Do list
-- Sheet editor UI
-- Playback control
+- UI: Piano roll
+- UI: Volume control
+- UI: Playback control
- Simple tonal synth
+- Build: WebAssembly
- Drum synth: kick, snare, cymbal
-- Track composing UI
+- UI: Track composing
+- Build: Faster recompilation
Done
diff --git a/source/saw/main.c b/source/saw/main.c
index d32188f..b362ed7 100644
--- a/source/saw/main.c
+++ b/source/saw/main.c
@@ -31,30 +31,91 @@
enum {
SAW_CHANNEL_COUNT = 2,
SAW_SAMPLE_RATE = 44100,
+
+ VOICE_COUNT = 4,
+ PIANOROLL_SIZE = 40
};
static struct NVGcontext *saw_nvg;
static ma_device saw_ma;
-static void saw_audio(ma_device *device, void *void_out_,
- void const *void_in_, ma_uint32 frame_count) {
- static i64 t = 0;
+static i32 saw_mouse_x = 0;
+static i32 saw_mouse_y = 0;
+static i8 saw_lbutton_click = 0;
+static i8 saw_lbutton_down = 0;
+static i8 saw_rbutton_click = 0;
+
+static i8 saw_voice_on[VOICE_COUNT] = { 0 };
+static i32 saw_voice_pitch[VOICE_COUNT] = { 0 };
+static i64 saw_voice_time[VOICE_COUNT] = { 0 };
+
+static i32 saw_pianoroll_last_index = -1;
+static i8 saw_pianoroll_turned_off[PIANOROLL_SIZE] = { 0 };
- f64 period = M_PI * 2.;
- f64 amplitude = .2;
- f64 frequency = 240.;
+#ifdef __GNUC__
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wunused-function"
+# pragma GCC diagnostic ignored "-Wunknown-pragmas"
+# pragma GCC push_options
+# pragma GCC optimize("O3")
+#endif
+
+static f64 saw_envelope(f64 t, f64 attack, f64 decay, f64 sustain,
+ f64 duration, f64 release) {
+ // FIXME
+ // Apply low-pass filter for the envelope curve to prevent
+ // clicking.
+ //
+ if (t < attack)
+ return t / attack;
+ else if (t < attack + decay)
+ return 1. - (1. - sustain) * (t - attack) / decay;
+ else if (t < duration)
+ return sustain;
+ else if (t < duration + release)
+ return sustain * (duration + release - t) / release;
+ else
+ return 0.;
+}
+
+static void saw_audio(ma_device *device, void *void_out_,
+ void const *void_in_, ma_uint32 frame_count) {
f32 *out = (f32 *) void_out_;
for (i64 i = 0; i < frame_count; i++) {
- f64 k = (period * frequency) / SAW_SAMPLE_RATE;
- out[i * 2] = (f32) (sin(k * t) * amplitude);
- out[i * 2 + 1] = (f32) (sin(k * t) * amplitude);
+ out[i * 2] = 0.f;
+ out[i * 2 + 1] = 0.f;
+ }
+
+ for (i32 n = 0; n < VOICE_COUNT; n++) {
+ if (!saw_voice_on[n])
+ continue;
+
+ f64 period = M_PI * 2.;
+ f64 frequency = pow(2., 6.5 + saw_voice_pitch[n] / 12.);
+
+ for (i64 i = 0; i < frame_count; i++) {
+ f64 t = (f64) saw_voice_time[n] / (f64) SAW_SAMPLE_RATE;
+ f64 amplitude = .4 * saw_envelope(t, .007, .2, .2, .6, .4);
+ f64 k = period * frequency;
- t++;
+ out[i * 2] += (f32) (sin(k * t) * amplitude);
+ out[i * 2 + 1] += (f32) (sin(k * t) * amplitude);
+
+ saw_voice_time[n]++;
+
+ if (t > 1.0)
+ saw_voice_on[n] = 0;
+ }
}
}
+#ifdef __GNUC__
+# pragma GCC pop_options
+# pragma GCC diagnostic pop
+#endif
+
static void saw_init(void) {
#ifdef SOKOL_GLCORE33
saw_nvg = nvgCreateGL3(NVG_ANTIALIAS | NVG_STENCIL_STROKES);
@@ -80,23 +141,78 @@ static void saw_init(void) {
}
static void saw_frame(void) {
- int width = sapp_width();
- int height = sapp_height();
+ i32 width = sapp_width();
+ i32 height = sapp_height();
glViewport(0, 0, width, height);
- glClearColor(.3f, .2f, .4f, 1.f);
+ glClearColor(.23f, .19f, .16f, 1.f);
glClearDepthf(1.f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT |
GL_STENCIL_BUFFER_BIT);
nvgBeginFrame(saw_nvg, width, height, sapp_dpi_scale());
- nvgBeginPath(saw_nvg);
- nvgRect(saw_nvg, 100, 100, 300, 200);
- nvgFillColor(saw_nvg, nvgRGBA(255, 192, 0, 255));
- nvgFill(saw_nvg);
+ // Draw piano roll
+ //
+
+ i32 x0 = 20;
+ i32 y0 = 20;
+
+ i32 pianokey_height = 40;
+ i32 pianokey_width = 100;
+ i32 pianokey_border = 2;
+
+ for (i32 i = 0; i < PIANOROLL_SIZE; i++) {
+ i32 x = x0 + pianokey_border;
+ i32 y = height - y0 - (i + 1) * pianokey_height;
+ i32 w = pianokey_width - pianokey_border * 2;
+ i32 h = pianokey_height - pianokey_border * 2;
+
+ if (y > height - pianokey_height)
+ continue;
+ if (y < 0)
+ break;
+
+ nvgBeginPath(saw_nvg);
+ nvgRect(saw_nvg, x, y, w, h);
+
+ i8 has_cursor = saw_mouse_x >= x && saw_mouse_x < x + w &&
+ saw_mouse_y >= y && saw_mouse_y < y + h;
+
+ nvgFillColor(saw_nvg, saw_pianoroll_turned_off[i]
+ ? nvgRGBA(220, 220, 220, 160)
+ : has_cursor ? nvgRGBA(200, 200, 255, 255)
+ : nvgRGBA(220, 220, 220, 255));
+ nvgFill(saw_nvg);
+
+ if (has_cursor) {
+ if (!saw_pianoroll_turned_off[i] &&
+ (saw_lbutton_click ||
+ (saw_lbutton_down && saw_pianoroll_last_index != i))) {
+ for (i32 n = VOICE_COUNT - 1; n > 0; --n) {
+ saw_voice_on[n] = saw_voice_on[n - 1];
+ saw_voice_pitch[n] = saw_voice_pitch[n - 1];
+ saw_voice_time[n] = saw_voice_time[n - 1];
+ }
+
+ saw_voice_on[0] = 1;
+ saw_voice_pitch[0] = i;
+ saw_voice_time[0] = 0;
+ saw_pianoroll_last_index = i;
+ }
+
+ if (saw_rbutton_click)
+ saw_pianoroll_turned_off[i] = !saw_pianoroll_turned_off[i];
+ }
+ }
nvgEndFrame(saw_nvg);
+
+ // Cleanup input state.
+ //
+
+ saw_lbutton_click = 0;
+ saw_rbutton_click = 0;
}
static void saw_cleanup(void) {
@@ -109,7 +225,35 @@ static void saw_cleanup(void) {
#endif
}
-static void saw_event(sapp_event const *event) { }
+static void saw_event(sapp_event const *event) {
+ switch (event->type) {
+ case SAPP_EVENTTYPE_MOUSE_MOVE:
+ saw_mouse_x = event->mouse_x;
+ saw_mouse_y = event->mouse_y;
+ break;
+
+ case SAPP_EVENTTYPE_MOUSE_DOWN:
+
+ switch (event->mouse_button) {
+ case SAPP_MOUSEBUTTON_LEFT:
+ saw_lbutton_down = 1;
+ saw_lbutton_click = 1;
+ break;
+ case SAPP_MOUSEBUTTON_RIGHT: saw_rbutton_click = 1; break;
+ default:;
+ }
+ break;
+
+ case SAPP_EVENTTYPE_MOUSE_UP:
+ switch (event->mouse_button) {
+ case SAPP_MOUSEBUTTON_LEFT: saw_lbutton_down = 0; break;
+ default:;
+ }
+ break;
+
+ default:;
+ }
+}
char const *__lsan_default_suppressions() {
// There is leaks in NVidia driver on Linux.