summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMitya Selivanov <automainint@guattari.tech>2024-02-15 22:30:01 +0100
committerMitya Selivanov <automainint@guattari.tech>2024-02-15 22:30:01 +0100
commit34df687daf49b29e0f2b4d7e0be3bd69df1c40c9 (patch)
tree30e50e941660374b0f70f390dc03b1e6a8691b7c
parent28c6366769b7b7c9d978e3fa1d83362e63caf831 (diff)
downloadsaw-34df687daf49b29e0f2b4d7e0be3bd69df1c40c9.zip
Pythagorean tuning
-rw-r--r--TODO1
-rw-r--r--source/saw/main.c222
2 files changed, 174 insertions, 49 deletions
diff --git a/TODO b/TODO
index c1cca77..29ddeb3 100644
--- a/TODO
+++ b/TODO
@@ -56,6 +56,7 @@ Done
- Buffering
- Drag & drop audio files
- Sampler
+ - Pythagorean tuning
- UI
- Piano roll
diff --git a/source/saw/main.c b/source/saw/main.c
index f9773c0..38c1b58 100644
--- a/source/saw/main.c
+++ b/source/saw/main.c
@@ -83,14 +83,17 @@ enum {
#else
TRACK_COUNT = 16,
ROLL_COUNT = 32,
- PITCH_COUNT = 80,
+ PITCH_COUNT = 100,
VOICE_COUNT = 32,
UNISON_COUNT = 100,
SHEET_SIZE = 200,
#endif
ROLL_DEFAULT_RATE = 8,
- ROLL_DEFAULT_UI_OFFSET_Y = 710,
+ ROLL_DEFAULT_UI_OFFSET_Y = 2000,
+
+ TUNING_EQUAL_TEMPERAMENT = 0,
+ TUNING_PYTHAGOREAN,
WAVE_SINE = 0,
WAVE_SAW_UP,
@@ -124,6 +127,7 @@ enum {
TINT_WHITE = 0,
TINT_ORANGE,
+ TINT_PINK,
};
// Data types
@@ -151,6 +155,8 @@ typedef struct {
i64 track;
b8 pitch_turned_off[PITCH_COUNT];
f64 tuning[PITCH_COUNT];
+ i64 tuning_tag;
+ i64 mark_pitch;
i64 rate;
saw_roll_note_t notes[SHEET_SIZE];
i64 time;
@@ -292,15 +298,23 @@ static saw_compose_t saw_compose = {
static saw_ui_color_t saw_ui_colors[] = {
{
+ // TINT_WHITE
.normal = { .7f, .7f, .7f, .7f },
.active = { .7f, .7f, .7f, 1.f },
.hover = { 1.f, 1.f, 1.f, 1.f },
},
{
+ // TINT_ORANGE
.normal = { .9f, .65f, .4f, .6f },
.active = { .9f, .65f, .4f, 1.f },
.hover = { 1.f, 1.f, 1.f, 1.f },
},
+ {
+ // TINT_PINK
+ .normal = { .7f, .3f, .6f, .7f },
+ .active = { .8f, .25f, .6f, 1.f },
+ .hover = { 1.f, 1.f, 1.f, 1.f },
+ }
};
static i64 saw_ui_input_index = 0;
@@ -326,10 +340,51 @@ static void saw_tuning_equal_temperament(f64 *tuning) {
if (tuning == NULL)
return;
- for (i64 i = 0; i < PITCH_COUNT; ++i) {
+ for (i64 i = 0; i < PITCH_COUNT; ++i)
tuning[i] = REFERENCE_PITCH *
pow(EQUAL_TEMPERAMENT_FACTOR,
(f64) (i - REFERENCE_PITCH_INDEX));
+}
+
+static void saw_tuning_pythagorean(f64 *tuning, i64 base_pitch) {
+ assert(tuning != NULL && base_pitch >= 0 &&
+ base_pitch < PITCH_COUNT);
+ if (tuning == NULL || base_pitch < 0 || base_pitch >= PITCH_COUNT)
+ return;
+
+ f64 ref_freq = tuning[base_pitch];
+
+ f64 v[12] = {
+ ref_freq, // unison
+ ref_freq * 256. / 243., // minor second
+ ref_freq * 9. / 8., // major second
+ ref_freq * 32. / 27., // minor third
+ ref_freq * 81. / 64., // major third
+ ref_freq * 4. / 3., // perfect fourth
+ ref_freq * 1024. / 729., // diminished fifth
+ ref_freq * 3. / 2., // perfect fifth
+ ref_freq * 128. / 81., // minor sixth
+ ref_freq * 27. / 16., // major sixth
+ ref_freq * 16. / 9., // minor seventh
+ ref_freq * 243. / 128., // major seventh
+ };
+
+ for (i64 i = 0; i < PITCH_COUNT && i < base_pitch; i++) {
+ i64 octave = (base_pitch - i + 11) / 12;
+ i64 k = i - (base_pitch - 12 * octave);
+ assert(k >= 0 && k < 12);
+ tuning[i] = v[k] * pow(.5, octave);
+ }
+
+ for (i64 i = base_pitch; i < PITCH_COUNT && i < base_pitch + 12;
+ i++)
+ tuning[i] = v[i - base_pitch];
+
+ for (i64 i = base_pitch + 12; i < PITCH_COUNT; i++) {
+ i64 octave = (i - base_pitch) / 12;
+ i64 k = (i - base_pitch) % 12;
+ assert(k >= 0 && k < 12);
+ tuning[i] = v[k] * pow(2., octave);
}
}
@@ -367,12 +422,15 @@ static f64 saw_pitch_amplitude(i64 pitch) {
static void saw_play_voice(saw_track_t *track, saw_roll_t *roll,
i64 pitch, i64 duration) {
- if (saw_voices[VOICE_COUNT - 1].enabled)
+ if (saw_voices[VOICE_COUNT - 1].enabled || pitch < 0 ||
+ pitch >= PITCH_COUNT)
return;
for (i32 n = VOICE_COUNT - 1; n > 0; --n)
saw_voices[n] = saw_voices[n - 1];
+ f64 frequency = roll->tuning[pitch];
+
switch (track->instrument) {
// FIXME
// Apply volume during sound generation.
@@ -386,7 +444,7 @@ static void saw_play_voice(saw_track_t *track, saw_roll_t *roll,
.enabled = 1,
.time = 0,
.duration = (f64) duration / (f64) SAMPLE_RATE,
- .frequency = saw_pitch_frequency(pitch),
+ .frequency = frequency,
.amplitude = saw_pitch_amplitude(pitch) * osc->volume,
.phase = {
saw_random(-s, s),
@@ -403,7 +461,7 @@ static void saw_play_voice(saw_track_t *track, saw_roll_t *roll,
.enabled = 1,
.time = 0,
.duration = (f64) duration / (f64) SAMPLE_RATE,
- .frequency = saw_pitch_frequency(pitch),
+ .frequency = frequency,
.amplitude = saw_pitch_amplitude(pitch) * sam->volume,
.phase = { 0., 0., },
.track = roll->track,
@@ -1341,6 +1399,8 @@ static void saw_ui_compose(i64 x0, i64 y0, i64 width, i64 height) {
.track = track,
.pitch_turned_off = { 0 },
.tuning = { 0 },
+ .tuning_tag = TUNING_EQUAL_TEMPERAMENT,
+ .mark_pitch = REFERENCE_PITCH_INDEX,
.rate = ROLL_DEFAULT_RATE,
.notes = { 0 },
.time = frame,
@@ -1908,6 +1968,28 @@ static void saw_ui_roll(saw_roll_t *roll, i64 x0, i64 y0, i64 width,
title.values, title.values + title.size);
nvgFill(saw_nvg);
+ // Tuning control
+ //
+ {
+ i64 x = x0 + border;
+ i64 y = y0 + height - text_height - header_height;
+ i64 w = pianokey_width;
+ i64 h = header_height;
+
+ if (saw_ui_button(x, y, w / 2, h, TINT_PINK, SZ("\uf52d"), SZ(""),
+ roll->tuning_tag == TUNING_PYTHAGOREAN)) {
+ saw_tuning_pythagorean(roll->tuning, roll->mark_pitch);
+ roll->tuning_tag = TUNING_PYTHAGOREAN;
+ }
+
+ if (saw_ui_button(x + w / 2, y, w / 2, h, TINT_PINK, SZ("\uf5ac"),
+ SZ(""),
+ roll->tuning_tag == TUNING_EQUAL_TEMPERAMENT)) {
+ saw_tuning_equal_temperament(roll->tuning);
+ roll->tuning_tag = TUNING_EQUAL_TEMPERAMENT;
+ }
+ }
+
// Loop control
//
{
@@ -1925,8 +2007,9 @@ static void saw_ui_roll(saw_roll_t *roll, i64 x0, i64 y0, i64 width,
nvgFillColor(saw_nvg, nvgRGBA(80, 80, 80, 160));
else
nvgFillColor(saw_nvg, nvgRGBA(180, 80, 40, 200));
- nvgText(saw_nvg, x - header_height * 2 + border,
- y + header_height - border, repeat,
+ nvgTextAlign(saw_nvg, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE);
+ nvgText(saw_nvg, x - header_height / 2 - border,
+ y + header_height / 2, repeat,
repeat + (sizeof repeat - 1));
i64 rw = (roll->loop_duration * roll->rate * sheet_scale +
@@ -1983,57 +2066,93 @@ static void saw_ui_roll(saw_roll_t *roll, i64 x0, i64 y0, i64 width,
// Piano roll
//
-
- b8 hover_any = 0;
-
- for (i64 pitch = 0; pitch < PITCH_COUNT; pitch++) {
- i64 x = x0 + border;
- i64 y = frame_height - y0 - (pitch + 1) * pianokey_height +
- border + roll->ui_offset_y;
+ {
i64 w = pianokey_width - border * 2;
i64 h = pianokey_height - border * 2;
- if (y > frame_height - y0 - pianokey_height)
- continue;
- if (y < frame_height - y0 - height + text_height + header_height)
- break;
+ b8 hover_any = 0;
- nvgBeginPath(saw_nvg);
- nvgRect(saw_nvg, x, y, w, h);
+ for (i64 pitch = 0; pitch < PITCH_COUNT; pitch++) {
+ i64 x = x0 + border;
+ i64 y = frame_height - y0 - (pitch + 1) * pianokey_height +
+ border + roll->ui_offset_y;
+ if (y > frame_height - y0 - pianokey_height)
+ continue;
+ if (y <
+ frame_height - y0 - height + text_height + header_height)
+ break;
- b8 has_cursor = saw_mouse_x >= x && saw_mouse_x < x + w &&
- saw_mouse_y >= y && saw_mouse_y < y + h;
+ b8 has_cursor = saw_mouse_x >= x && saw_mouse_x < x + w &&
+ saw_mouse_y >= y && saw_mouse_y < y + h;
- nvgFillColor(saw_nvg, roll->pitch_turned_off[pitch]
- ? nvgRGBA(220, 220, 220, 160)
- : has_cursor ? nvgRGBA(200, 200, 255, 255)
- : nvgRGBA(220, 220, 220, 255));
- nvgFill(saw_nvg);
+ nvgBeginPath(saw_nvg);
+ nvgRect(saw_nvg, x, y, w, h);
+ nvgFillColor(saw_nvg, roll->pitch_turned_off[pitch]
+ ? nvgRGBA(220, 220, 220, 160)
+ : has_cursor
+ ? nvgRGBA(200, 200, 255, 255)
+ : nvgRGBA(220, 220, 220, 255));
+ nvgFill(saw_nvg);
- // Piano roll input
- //
+ nvgFontSize(saw_nvg, h / 2);
- if (has_cursor) {
- hover_any = 1;
+ if (pitch == roll->mark_pitch) {
+ nvgFontFaceId(saw_nvg, saw_font_icons);
+ nvgFillColor(saw_nvg, nvgRGBAf(.1f, .1f, .1f, 1.f));
+ nvgTextAlign(saw_nvg, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE);
+ nvgText(saw_nvg, x + w / 3, y + h / 2, "\uf52d", NULL);
+ }
- if (!roll->pitch_turned_off[pitch] &&
- ((saw_edit_mode == EDIT_MODE_HAND && saw_lbutton_click) ||
- (saw_edit_mode == EDIT_MODE_HAND && saw_lbutton_down &&
- roll->last_index != pitch)))
- saw_play_voice(saw_tracks + roll->track, roll, pitch,
- SAMPLE_RATE / roll->rate);
+ char buf[64];
+ char const *note_names[12] = {
+ "A", "Bb", "H", "C", "Db", "D",
+ "Eb", "E", "F", "Gb", "G", "Ab",
+ };
- if (saw_rbutton_click ||
- (saw_edit_mode == EDIT_MODE_ERASE && saw_lbutton_click))
- roll->pitch_turned_off[pitch] =
- !roll->pitch_turned_off[pitch];
+ sprintf(buf, "%s",
+ note_names[pitch %
+ (sizeof note_names / sizeof *note_names)]);
- roll->last_index = pitch;
+ nvgFontFaceId(saw_nvg, saw_font_text);
+ nvgFillColor(saw_nvg, nvgRGBAf(.1f, .1f, .1f, 1.f));
+ nvgTextAlign(saw_nvg, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE);
+ nvgText(saw_nvg, x + border, y + h / 2, buf, NULL);
+
+ memset(buf, 0, sizeof buf);
+ sprintf(buf, "%.1f", (f32) roll->tuning[pitch]);
+
+ nvgFontSize(saw_nvg, (h * 2) / 3);
+ nvgFillColor(saw_nvg, nvgRGBAf(.1f, .1f, .1f, 1.f));
+ nvgTextAlign(saw_nvg, NVG_ALIGN_RIGHT | NVG_ALIGN_MIDDLE);
+ nvgText(saw_nvg, x + w, y + h / 2, buf, NULL);
+
+ // Piano roll input
+ //
+
+ if (has_cursor) {
+ hover_any = 1;
+
+ if (!roll->pitch_turned_off[pitch] &&
+ ((saw_edit_mode == EDIT_MODE_HAND && saw_lbutton_click) ||
+ (saw_edit_mode == EDIT_MODE_HAND && saw_lbutton_down &&
+ roll->last_index != pitch))) {
+ saw_play_voice(saw_tracks + roll->track, roll, pitch,
+ SAMPLE_RATE / roll->rate);
+ roll->mark_pitch = pitch;
+ }
+
+ if (saw_rbutton_click ||
+ (saw_edit_mode == EDIT_MODE_ERASE && saw_lbutton_click))
+ roll->pitch_turned_off[pitch] =
+ !roll->pitch_turned_off[pitch];
+
+ roll->last_index = pitch;
+ }
}
- }
- if (!hover_any)
- roll->last_index = -1;
+ if (!hover_any)
+ roll->last_index = -1;
+ }
// Panning input
//
@@ -2405,7 +2524,7 @@ static void saw_init(void) {
// Init Saw state
//
- f64 tuning[PITCH_COUNT];
+ static f64 tuning[PITCH_COUNT];
saw_tuning_equal_temperament(tuning);
@@ -2418,6 +2537,8 @@ static void saw_init(void) {
.last_index = -1,
.pitch_turned_off = { 0 },
.tuning = { 0 },
+ .tuning_tag = TUNING_EQUAL_TEMPERAMENT,
+ .mark_pitch = REFERENCE_PITCH_INDEX,
.rate = ROLL_DEFAULT_RATE,
.notes = { 0 },
.time = 0,
@@ -2548,7 +2669,6 @@ static void saw_init(void) {
roll->pitch_turned_off[pitch] = flag ? 1 : 0;
}
-#if 0
SCAN_(" tuning %d", 1, &pitch_count);
if (pitch_count < 0 || pitch_count > PITCH_COUNT) {
@@ -2563,7 +2683,9 @@ static void saw_init(void) {
SCAN_(" %lld", 1, &frequency);
roll->tuning[pitch] = .0001 * frequency;
}
-#endif
+
+ SCAN_(" tuning_tag %lld", 1, &roll->tuning_tag);
+ SCAN_(" mark_pitch %lld", 1, &roll->mark_pitch);
SCAN_(" rate %lld", 1, &roll->rate);
@@ -2908,6 +3030,8 @@ static void saw_cleanup(void) {
fprintf(f, " %lld",
(i64) floor(roll->tuning[pitch] * 10000 + .5));
fprintf(f, "\n");
+ fprintf(f, "tuning_tag %lld\n", roll->tuning_tag);
+ fprintf(f, "mark_pitch %lld\n", roll->mark_pitch);
fprintf(f, "rate %lld\n", roll->rate);
i32 total_notes = 0;