summaryrefslogtreecommitdiff
path: root/source
diff options
context:
space:
mode:
authorMitya Selivanov <automainint@guattari.tech>2023-09-29 06:58:11 +0200
committerMitya Selivanov <automainint@guattari.tech>2023-09-29 06:58:11 +0200
commit59ac08837ca411410385050657942fe981e327db (patch)
tree7a31135a554dc6b2fa43f2c7c104df5aa67f2615 /source
parent98b7009f2c05afbd4cc68869a6f544ae67f696c1 (diff)
downloadsaw-59ac08837ca411410385050657942fe981e327db.zip
Stereo; Minor UI improvements
Diffstat (limited to 'source')
-rw-r--r--source/saw/_impl.c11
-rw-r--r--source/saw/main.c363
-rw-r--r--source/saw/profiler.c6
3 files changed, 296 insertions, 84 deletions
diff --git a/source/saw/_impl.c b/source/saw/_impl.c
index f405a6b..eb1d2ba 100644
--- a/source/saw/_impl.c
+++ b/source/saw/_impl.c
@@ -6,6 +6,16 @@
# define NANOVG_GL3 1
#endif
+// hotfix for weird GCC bug
+#ifdef __linux__
+# define _GNU_SOURCE
+# include <emmintrin.h>
+#endif
+
+// kit
+//
+#include "../kit/_lib.c"
+
// sokol
//
#define SOKOL_APP_IMPL
@@ -29,4 +39,3 @@
#include "../thirdparty/nanovg.c"
#include "../thirdparty/nanovg_gl.c"
#include "../thirdparty/android.c"
-
diff --git a/source/saw/main.c b/source/saw/main.c
index 77ec5df..c179564 100644
--- a/source/saw/main.c
+++ b/source/saw/main.c
@@ -3,6 +3,8 @@
#include "../kit/math.h"
#include "../kit/time.h"
#include "../kit/string_ref.h"
+#include "../kit/mersenne_twister_64.h"
+#include "../kit/secure_random.h"
#ifdef __EMSCRIPTEN__
# include <GLES3/gl3.h>
@@ -31,8 +33,11 @@ enum {
ROLL_SIZE = 80,
SHEET_SIZE = 200,
ROLL_COUNT = 16,
+ TRACK_COUNT = 8,
ROLL_DEFAULT_OFFSET_Y = 710,
+
+ INSTRUMENT_SINE = 0,
};
typedef struct {
@@ -41,6 +46,7 @@ typedef struct {
f64 duration;
f64 frequency;
f64 amplitude;
+ f64 stereo_factor;
} saw_voice_t;
typedef struct {
@@ -69,13 +75,29 @@ typedef struct {
} saw_roll_t;
typedef struct {
+ f64 sustain;
+ f64 attack;
+ f64 decay;
+ f64 release;
+} saw_envelope_t;
+
+typedef struct {
+ i32 instrument;
+ f64 warp;
+ f64 phase;
+ f64 stereo_width;
+ f64 volume;
+ saw_envelope_t envelope;
+} saw_track_t;
+
+typedef struct {
i64 rolls[ROLL_COUNT];
- i8 offset_input;
- i64 offset_x;
- i64 offset_y;
i8 grid_input;
i32 grid_roll;
i32 grid_cell;
+ i8 offset_input;
+ i64 offset_x;
+ i64 offset_y;
} saw_compose_t;
static struct NVGcontext *saw_nvg;
@@ -83,6 +105,8 @@ static ma_device saw_ma;
static i32 saw_font = -1;
+static mt64_state_t saw_mt64;
+
static i32 saw_mouse_x = 0;
static i32 saw_mouse_y = 0;
static i8 saw_lbutton_click = 0;
@@ -94,19 +118,21 @@ static i8 saw_mbutton_down = 0;
static i8 saw_playback_on = 0;
static i64 saw_playback_frame = 0;
+static i64 saw_current_track = 0;
static i64 saw_current_roll = 0;
static saw_voice_t saw_voices[VOICE_COUNT] = { 0 };
static saw_roll_t saw_rolls[ROLL_COUNT];
+static saw_track_t saw_tracks[TRACK_COUNT];
static saw_compose_t saw_compose = {
.rolls = { -1 },
- .offset_input = 0,
- .offset_x = 0,
- .offset_y = 0,
.grid_input = 0,
.grid_roll = 0,
.grid_cell = 0,
+ .offset_input = 0,
+ .offset_x = 0,
+ .offset_y = 0,
};
#ifdef __GNUC__
@@ -117,6 +143,14 @@ static saw_compose_t saw_compose = {
# pragma GCC optimize("O3")
#endif
+static f64 saw_random(f64 min, f64 max) {
+ if (max < min)
+ return min;
+ u64 x = mt64_generate(&saw_mt64);
+ u64 range = (u64) ((max - min) * 10000 + 0.5);
+ return min + (max - min) * (range * (x % (range + 1)));
+}
+
static f64 saw_envelope(f64 t, f64 attack, f64 decay, f64 sustain,
f64 duration, f64 release) {
f64 a = 1.;
@@ -169,11 +203,12 @@ static void saw_playback(i32 frame_count) {
saw_voices[n] = saw_voices[n - 1];
saw_voices[0] = (saw_voice_t) {
- .enabled = 1,
- .time = 0,
- .duration = ((f64) p->duration) / roll->rate,
- .frequency = saw_pitch_frequency(p->pitch),
- .amplitude = saw_pitch_amplitude(p->pitch),
+ .enabled = 1,
+ .time = 0,
+ .duration = ((f64) p->duration) / roll->rate,
+ .frequency = saw_pitch_frequency(p->pitch),
+ .amplitude = saw_pitch_amplitude(p->pitch),
+ .stereo_factor = saw_random(-0.01, 0.01),
};
}
}
@@ -196,9 +231,10 @@ static void saw_audio(ma_device *device, void *void_out_,
if (!saw_voices[n].enabled)
continue;
- f64 period = M_PI * 2.;
- f64 frequency = saw_voices[n].frequency;
- f64 amplitude = saw_voices[n].amplitude;
+ f64 period = M_PI * 2.;
+ f64 frequency = saw_voices[n].frequency;
+ f64 amplitude = saw_voices[n].amplitude;
+ f64 stereo_factor = saw_voices[n].stereo_factor;
// envelope
f64 attack = .007;
@@ -214,7 +250,7 @@ static void saw_audio(ma_device *device, void *void_out_,
f64 k = period * frequency;
out[i * 2] += (f32) (sin(k * t) * a);
- out[i * 2 + 1] += (f32) (sin(k * t) * a);
+ out[i * 2 + 1] += (f32) (sin(k * t + stereo_factor) * a);
saw_voices[n].time++;
@@ -242,7 +278,12 @@ static void saw_ui_compose(i64 x0, i64 y0, i64 width, i64 height) {
nvgBeginPath(saw_nvg);
nvgRect(saw_nvg, x0, frame_height - y0 - height + border, width,
- frame_height - y0 - height + track_height / 2 - border * 2);
+ frame_height - y0 - height + track_height / 5 - border * 2);
+ nvgRect(saw_nvg, x0,
+ frame_height - y0 - height + border +
+ (track_height * 4) / 5,
+ width,
+ frame_height - y0 - height + track_height / 5 - border * 2);
nvgFillColor(saw_nvg, nvgRGBA(180, 140, 120, 160));
nvgFill(saw_nvg);
@@ -256,17 +297,33 @@ static void saw_ui_compose(i64 x0, i64 y0, i64 width, i64 height) {
continue;
saw_roll_t *roll = saw_rolls + saw_compose.rolls[i];
- i64 x = x0 + saw_compose.offset_x +
- (roll->frame * grid_scale) / SAW_SAMPLE_RATE;
- i64 w = (roll->size * grid_scale) / roll->rate;
- i64 y = frame_height - y0 - height + track_height +
+ i64 top = frame_height - y0 - height + track_height;
+ i64 bottom = frame_height - y0;
+ i64 dx = x0 + saw_compose.offset_x;
+ i64 l = dx + (roll->frame * grid_scale) / SAW_SAMPLE_RATE;
+ i64 r = l + (roll->size * grid_scale) / roll->rate;
+ i64 u = frame_height - y0 - height + track_height +
saw_compose.offset_y + roll->track * track_height;
-
- if (x < x0 || x + w >= x0 + width ||
- y < frame_height - y0 - height ||
- y + track_height >= frame_height - y0)
+ i64 d = u + track_height;
+ i64 s = grid_scale / grid_rate;
+
+ if (l < x0)
+ l = dx + ((x0 - dx + (s - 1)) / s) * s;
+ if (r >= x0 + width)
+ r = dx + ((x0 + width - dx) / s) * s;
+ if (u < top)
+ u = top;
+ if (d > bottom)
+ d = bottom;
+
+ if (l >= r || u >= d)
continue;
+ i64 x = l;
+ i64 w = r - l;
+ i64 y = u;
+ i64 h = d - u;
+
i8 is_choosen = (saw_current_roll == i);
i8 is_playing = saw_playback_on &&
@@ -278,12 +335,11 @@ static void saw_ui_compose(i64 x0, i64 y0, i64 width, i64 height) {
i8 has_cursor = (saw_compose.grid_input &&
saw_compose.grid_roll == i) ||
(saw_mouse_x >= x && saw_mouse_x < x + w &&
- saw_mouse_y >= y &&
- saw_mouse_y < y + track_height);
+ saw_mouse_y >= y && saw_mouse_y < y + h);
nvgBeginPath(saw_nvg);
nvgRect(saw_nvg, x + border, y + border, w - border * 2,
- track_height - border * 2);
+ h - border * 2);
nvgFillColor(saw_nvg, is_choosen ? nvgRGBA(240, 230, 200, 255)
: is_playing ? nvgRGBA(255, 230, 200, 255)
: has_cursor ? nvgRGBA(210, 210, 255, 255)
@@ -313,46 +369,21 @@ static void saw_ui_compose(i64 x0, i64 y0, i64 width, i64 height) {
saw_compose.grid_cell = c1 - 1;
roll->size = ((c1 - cell + 1) * roll->rate) / grid_rate;
}
- } else
- saw_current_roll = saw_compose.rolls[i];
+ } else {
+ saw_current_roll = saw_compose.rolls[i];
+ saw_current_track = roll->track;
+ }
}
hover_any = 1;
}
}
}
- // Draw cursor
- //
-
- if (!hover_any && !saw_compose.grid_input) {
- i64 track = (saw_mouse_y - saw_compose.offset_y - frame_height +
- y0 + height) /
- track_height -
- 1;
- i64 cell = ((saw_mouse_x - saw_compose.offset_x) * grid_rate) /
- grid_scale;
- i64 x = x0 + saw_compose.offset_x +
- (cell * grid_scale) / grid_rate;
- i64 y = frame_height - y0 - height + track_height +
- saw_compose.offset_y + track * track_height;
- i64 w = grid_scale / grid_rate;
-
- if (track >= 0 && track < ROLL_COUNT && x >= x0 &&
- x + w < x0 + width &&
- y >= frame_height - y0 - height + track_height &&
- y + track_height < frame_height - y0) {
- nvgBeginPath(saw_nvg);
- nvgRect(saw_nvg, x + border, y + border, w - border * 2,
- track_height - border * 2);
- nvgFillColor(saw_nvg, nvgRGBA(180, 160, 140, 160));
- nvgFill(saw_nvg);
- }
- }
-
- // Placing new track
+ // Placing new sheet
//
- if (!hover_any && saw_lbutton_down && saw_mouse_x >= x0 &&
+ if (!hover_any && saw_lbutton_down &&
+ saw_mouse_x >= x0 + saw_compose.offset_x &&
saw_mouse_x < x0 + width) {
if (!saw_compose.grid_input &&
saw_mouse_y >= frame_height - y0 - height &&
@@ -385,7 +416,7 @@ static void saw_ui_compose(i64 x0, i64 y0, i64 width, i64 height) {
i64 y = frame_height - y0 - height + track_height +
saw_compose.offset_y + track * track_height;
- if (track < 0 || track >= ROLL_COUNT || x < x0 ||
+ if (track < 0 || track >= TRACK_COUNT || x < x0 ||
x >= x0 + width || y < frame_height - y0 - height ||
y + track_height >= frame_height - y0)
n = -1;
@@ -418,11 +449,25 @@ static void saw_ui_compose(i64 x0, i64 y0, i64 width, i64 height) {
.offset_y = ROLL_DEFAULT_OFFSET_Y,
};
- saw_current_roll = n;
+ saw_current_roll = n;
+ saw_current_track = track;
}
}
}
+ // Offset input
+ //
+
+ if (saw_mbutton_click) {
+ if (saw_mouse_x >= x0 &&
+ saw_mouse_y >= frame_height - y0 - height + track_height &&
+ saw_mouse_x < x0 + width && saw_mouse_y < frame_height - y0)
+ saw_compose.offset_input = 1;
+ }
+
+ if (!saw_mbutton_down)
+ saw_compose.offset_input = 0;
+
// Track stretching input
//
@@ -492,9 +537,40 @@ static void saw_ui_compose(i64 x0, i64 y0, i64 width, i64 height) {
nvgFill(saw_nvg);
}
+ // Draw cursor
+ //
+
+ if (!hover_any && !saw_compose.grid_input &&
+ saw_mouse_x >= x0 + saw_compose.offset_x) {
+ i64 track = (saw_mouse_y - saw_compose.offset_y - frame_height +
+ y0 + height) /
+ track_height -
+ 1;
+ i64 cell = ((saw_mouse_x - saw_compose.offset_x) * grid_rate) /
+ grid_scale;
+ i64 x = x0 + saw_compose.offset_x +
+ (cell * grid_scale) / grid_rate;
+ i64 y = frame_height - y0 - height + track_height +
+ saw_compose.offset_y + track * track_height;
+ i64 w = grid_scale / grid_rate;
+
+ if (track >= 0 && track < ROLL_COUNT && x >= x0 &&
+ x + w < x0 + width &&
+ y >= frame_height - y0 - height + track_height &&
+ y + track_height < frame_height - y0) {
+ nvgBeginPath(saw_nvg);
+ nvgRect(saw_nvg, x + border, y + border, w - border * 2,
+ track_height - border * 2);
+ nvgFillColor(saw_nvg, nvgRGBA(180, 160, 140, 160));
+ nvgFill(saw_nvg);
+ }
+ }
+
// Cursor indicator
//
- if (saw_mouse_x >= x0 && saw_mouse_x < x0 + width &&
+
+ if (saw_mouse_x >= x0 + saw_compose.offset_x &&
+ saw_mouse_x < x0 + width &&
saw_mouse_y >= frame_height - y0 - height &&
saw_mouse_y < frame_height - y0) {
i32 dx = x0 + saw_compose.offset_x;
@@ -510,6 +586,87 @@ static void saw_ui_compose(i64 x0, i64 y0, i64 width, i64 height) {
}
}
+static void saw_ui_track(saw_track_t *track, i64 x0, i64 y0,
+ i64 width, i64 height, str_t title) {
+ i64 frame_height = sapp_height();
+
+ i64 text_height = 35;
+ i64 border = 2;
+
+ // Title
+ //
+
+ nvgBeginPath(saw_nvg);
+ nvgFontSize(saw_nvg, text_height);
+ nvgFontFaceId(saw_nvg, saw_font);
+ nvgFillColor(saw_nvg, nvgRGBA(255, 255, 255, 255));
+ nvgText(saw_nvg, x0 + border * 2,
+ frame_height - y0 - height + text_height - border * 2,
+ title.values, title.values + title.size);
+ nvgFill(saw_nvg);
+
+ // Instrument
+ //
+
+ char buf_instr[][100] = { "Sine wave" };
+
+ nvgBeginPath(saw_nvg);
+ nvgFontSize(saw_nvg, text_height);
+ nvgFontFaceId(saw_nvg, saw_font);
+ nvgFillColor(saw_nvg, nvgRGBA(255, 255, 255, 255));
+ nvgText(saw_nvg, x0 + border * 2,
+ frame_height - y0 - height + text_height * 2 - border * 2,
+ buf_instr[track->instrument], 0);
+ nvgFill(saw_nvg);
+
+ // Phase
+ //
+
+ char buf_phase[100];
+ sprintf(buf_phase, "Phase: %d", (int) (track->phase * 100 + 0.5));
+
+ nvgBeginPath(saw_nvg);
+ nvgFontSize(saw_nvg, text_height);
+ nvgFontFaceId(saw_nvg, saw_font);
+ nvgFillColor(saw_nvg, nvgRGBA(255, 255, 255, 255));
+ nvgText(saw_nvg, x0 + border * 2,
+ frame_height - y0 - height + text_height * 3 - border * 2,
+ buf_phase, 0);
+ nvgFill(saw_nvg);
+
+ // Stereo width
+ //
+
+ char buf_stereo_width[100];
+ sprintf(buf_stereo_width, "Stereo width: %d",
+ (int) (track->stereo_width * 100 + 0.5));
+
+ nvgBeginPath(saw_nvg);
+ nvgFontSize(saw_nvg, text_height);
+ nvgFontFaceId(saw_nvg, saw_font);
+ nvgFillColor(saw_nvg, nvgRGBA(255, 255, 255, 255));
+ nvgText(saw_nvg, x0 + border * 2,
+ frame_height - y0 - height + text_height * 4 - border * 2,
+ buf_stereo_width, 0);
+ nvgFill(saw_nvg);
+
+ // Volume
+ //
+
+ char buf_volume[100];
+ sprintf(buf_volume, "Volume: %d",
+ (int) (track->volume * 100 + 0.5));
+
+ nvgBeginPath(saw_nvg);
+ nvgFontSize(saw_nvg, text_height);
+ nvgFontFaceId(saw_nvg, saw_font);
+ nvgFillColor(saw_nvg, nvgRGBA(255, 255, 255, 255));
+ nvgText(saw_nvg, x0 + border * 2,
+ frame_height - y0 - height + text_height * 5 - border * 2,
+ buf_volume, 0);
+ nvgFill(saw_nvg);
+}
+
static void saw_ui_roll(saw_roll_t *roll, i64 x0, i64 y0, i64 width,
i64 height, str_t title) {
i32 frame_height = sapp_height();
@@ -583,11 +740,12 @@ static void saw_ui_roll(saw_roll_t *roll, i64 x0, i64 y0, i64 width,
saw_voices[n] = saw_voices[n - 1];
saw_voices[0] = (saw_voice_t) {
- .enabled = 1,
- .time = 0,
- .duration = .6,
- .frequency = saw_pitch_frequency(pitch),
- .amplitude = saw_pitch_amplitude(pitch),
+ .enabled = 1,
+ .time = 0,
+ .duration = .6,
+ .frequency = saw_pitch_frequency(pitch),
+ .amplitude = saw_pitch_amplitude(pitch),
+ .stereo_factor = saw_random(-0.01, 0.01),
};
}
@@ -602,16 +760,19 @@ static void saw_ui_roll(saw_roll_t *roll, i64 x0, i64 y0, i64 width,
roll->last_index = -1;
// Offset input
+ //
+
if (saw_mbutton_click) {
if (saw_mouse_x >= x0 + pianokey_width + sheet_offset &&
- saw_mouse_y >= frame_height - y0 - height &&
+ saw_mouse_y >= frame_height - y0 - height + text_height &&
saw_mouse_x < x0 + width && saw_mouse_y < frame_height - y0)
roll->offset_x_input = 1;
if (saw_mouse_x >= x0 &&
- saw_mouse_y >= frame_height - y0 - height &&
+ saw_mouse_y >= frame_height - y0 - height + text_height &&
saw_mouse_x < x0 + width && saw_mouse_y < frame_height - y0)
roll->offset_y_input = 1;
}
+
if (!saw_mbutton_down) {
roll->offset_x_input = 0;
roll->offset_y_input = 0;
@@ -835,7 +996,10 @@ static void saw_ui_roll(saw_roll_t *roll, i64 x0, i64 y0, i64 width,
}
static void saw_init(void) {
- sapp_set_window_title("saw");
+ u64 rng_seed;
+ secure_random(sizeof rng_seed, &rng_seed);
+ mt64_init(&saw_mt64, rng_seed);
+ mt64_rotate(&saw_mt64);
#ifdef SOKOL_GLCORE33
saw_nvg = nvgCreateGL3(NVG_ANTIALIAS | NVG_STENCIL_STROKES);
@@ -886,6 +1050,21 @@ static void saw_init(void) {
.offset_y = ROLL_DEFAULT_OFFSET_Y,
};
}
+
+ for (i32 i = 0; i < TRACK_COUNT; i++)
+ saw_tracks[i] = (saw_track_t) {
+ .instrument = INSTRUMENT_SINE,
+ .warp = .5,
+ .phase = .0,
+ .stereo_width = .2,
+ .volume = .2,
+ .envelope = {
+ .sustain = .15,
+ .attack = .007,
+ .decay = .3,
+ .release = .4,
+ },
+ };
}
static void saw_frame(void) {
@@ -900,15 +1079,25 @@ static void saw_frame(void) {
nvgBeginFrame(saw_nvg, frame_width, frame_height, sapp_dpi_scale());
- saw_ui_compose(0, (frame_height * 3) / 5, frame_width,
+ saw_ui_compose(0, (frame_height * 3) / 5, (frame_width * 4) / 5,
(frame_height * 2) / 5);
+ if (saw_current_track != -1) {
+ char buf[64];
+ sprintf(buf, "Track %lld", saw_current_track + 1);
+
+ saw_ui_track(saw_tracks + saw_current_track,
+ (frame_width * 4) / 5, 0, frame_width / 5,
+ frame_height, kit_str(strlen(buf), buf));
+ }
+
if (saw_current_roll != -1) {
char buf[64];
- sprintf(buf, "Track %lld", saw_current_roll + 1);
+ sprintf(buf, "Sheet %lld", saw_current_roll + 1);
- saw_ui_roll(saw_rolls + saw_current_roll, 0, 0, frame_width,
- (frame_height * 3) / 5, kit_str(strlen(buf), buf));
+ saw_ui_roll(saw_rolls + saw_current_roll, 0, 0,
+ (frame_width * 4) / 5, (frame_height * 3) / 5,
+ kit_str(strlen(buf), buf));
}
nvgEndFrame(saw_nvg);
@@ -944,6 +1133,11 @@ static void saw_event(sapp_event const *event) {
saw_rolls[i].offset_y += event->mouse_dy;
}
+ if (saw_compose.offset_input) {
+ saw_compose.offset_x += event->mouse_dx;
+ saw_compose.offset_y += event->mouse_dy;
+ }
+
break;
case SAPP_EVENTTYPE_MOUSE_DOWN:
@@ -979,13 +1173,19 @@ static void saw_event(sapp_event const *event) {
case SAPP_KEYCODE_SPACE:
saw_playback_on = !saw_playback_on;
break;
+
case SAPP_KEYCODE_ENTER: saw_playback_frame = 0; break;
+
case SAPP_KEYCODE_ESCAPE:
if (saw_current_roll != -1) {
saw_rolls[saw_current_roll].offset_x = 0;
saw_rolls[saw_current_roll].offset_y =
ROLL_DEFAULT_OFFSET_Y;
}
+
+ saw_compose.offset_x = 0;
+ saw_compose.offset_y = 0;
+
break;
default:;
}
@@ -1002,12 +1202,15 @@ char const *__lsan_default_suppressions() {
}
sapp_desc sokol_main(int argc, char **argv) {
+ static char title[] = "saw";
+
return (sapp_desc) {
- .width = 1280,
- .height = 720,
- .init_cb = saw_init,
- .frame_cb = saw_frame,
- .cleanup_cb = saw_cleanup,
- .event_cb = saw_event,
+ .window_title = title,
+ .width = 1280,
+ .height = 720,
+ .init_cb = saw_init,
+ .frame_cb = saw_frame,
+ .cleanup_cb = saw_cleanup,
+ .event_cb = saw_event,
};
}
diff --git a/source/saw/profiler.c b/source/saw/profiler.c
index 14e6b41..2f8e23b 100644
--- a/source/saw/profiler.c
+++ b/source/saw/profiler.c
@@ -1,8 +1,9 @@
#include "../kit/time.h"
+#include "../kit/types.h"
#include <stdio.h>
-long long profiler_time_;
+i64 profiler_time_;
#ifdef __GNUC__
# pragma GCC diagnostic push
@@ -15,8 +16,7 @@ long long profiler_time_;
void profile_frame(char const *s) {
struct timespec ts;
timespec_get(&ts, TIME_UTC);
- long long t = ((long long) ts.tv_sec) * 1000 +
- ((long long) ts.tv_nsec) / 1000000;
+ i64 t = ((i64) ts.tv_sec) * 1000 + ((i64) ts.tv_nsec) / 1000000;
if (profiler_time_ == 0)
printf("%s\n", s);
else