From 34df687daf49b29e0f2b4d7e0be3bd69df1c40c9 Mon Sep 17 00:00:00 2001 From: Mitya Selivanov Date: Thu, 15 Feb 2024 22:30:01 +0100 Subject: Pythagorean tuning --- TODO | 1 + source/saw/main.c | 222 ++++++++++++++++++++++++++++++++++++++++++------------ 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; -- cgit v1.2.3