Rev 2302 |
Blame |
Compare with Previous |
Last modification |
View Log
| RSS feed
/*
* keyboard_keyboard.cc
* DIN Is Noise is copyright (c) 2006-2025 Jagannathan Sampath
* DIN Is Noise is released under GNU Public License 2.0
* For more information, please visit https://dinisnoise.org/
*/
#include "main.h"
#include "keyboard_keyboard.h"
#include "input.h"
#include "ui_list.h"
#include "audio.h"
#include "din.h"
#include "audio.h"
#include "midi_in.h"
#include <math.h>
#include "console.h"
#include "color.h"
#include "log.h"
#include <fstream>
using namespace std;
extern int mousex, mousey, mouseyy;
extern din din0;
extern beat2value octave_shift;
extern float ATTACK_TIME, DECAY_TIME;
extern float NOTE_VOLUME;
extern int PITCH_BEND;
extern float PITCH_BEND_PER_PIXEL;
extern audio_out aout;
extern viewport view;
extern curve_library wav_lib, attack_lib, decay_lib;
extern int line_height;
typedef list<triggered_note>::iterator triggered_note_iterator;
extern int UI_OFF;
extern const char* INDIAN_SWAR [];
extern map<string, string> INT2IND;
extern int JUSTIFICATION;
void keyboard_keyboard::setup () {
vector<key_info> ki;
Uint8 ky [] = { // the keyboard keys
SDLK_t, SDLK_r, SDLK_e, SDLK_w, SDLK_q, SDLK_a, SDLK_s,
SDLK_d, SDLK_f, SDLK_g, SDLK_h, SDLK_j, SDLK_k, SDLK_l,
SDLK_SEMICOLON, SDLK_QUOTE, SDLK_RIGHTBRACKET, SDLK_LEFTBRACKET,
SDLK_p, SDLK_o, SDLK_i, SDLK_u
};
char kz [] = { // the keys as chars
't', 'r', 'e', 'w', 'q', 'a', 's',
'd', 'f', 'g', 'h', 'j', 'k', 'l',
';', '\'', ']', '[', 'p', 'o', 'i',
'u'
};
// assign keys to scale
for (int i = 1, j = 6, noct = 3; i < 13; ++i, --j) {
if (i > 7) {
j = 7;
noct = 1;
}
ki.clear ();
for (int k = 0, l = noct * i + 1; k < l; ++k) {
int jk = j + k;
ki.push_back ( key_info (ky [jk], kz [jk]) );
}
scale2keybd [i] = ki;
}
// setup waveform
waved.add (&wave, &wavlis);
waved.attach_library (&wav_lib);
// setup attack curve
attacked.add (&attackcrv, &attacklis);
attacklis.inst = this;
attacked.attach_library (&attack_lib);
// setup decay curve
decayed.add (&decaycrv, &decaylis);
decaylis.inst = this;
decayed.attach_library (&decay_lib);
// setup velocity curve (used on MIDI triggered notes)
velsol (&velcrv);
veled.add (&velcrv, &vellis);
veled.attach_library (&vellib);
// initial mouse position
prev_mousex = view.midx;
prev_mousey = view.midy;
}
keyboard_keyboard::~keyboard_keyboard () {
scaleinfo.save_scale ();
wave.save ("keyboard-keyboard-waveform.crv");
attackcrv.save ("attack.crv");
decaycrv.save ("decay.crv");
velcrv.save ("velocity.crv");
ofstream file ((user_data_dir + fname).c_str(), ios::out);
if (!file) {
dlog << "!!! couldnt save : " << fname << endl;
} else {
file << "show_nearby_notes " << show_nearby_notes << endl;
file << "attack_time " << ATTACK_TIME << endl;
file << "decay_time " << DECAY_TIME << endl;
file << "note_volume " << NOTE_VOLUME << endl;
file << "pitch_bend " << PITCH_BEND << endl;
file << "trig_what " << trig_what << endl;
}
dlog << "--- destroyed keyboard-keyboard ---" << endl;
}
void reduce (float& what, float& by, float limit) {
what -= by;
if (what <= limit) what = by;
}
void keyboard_keyboard::bg () {
remove_finished_notes (); // kill notes decayed to silence
}
int keyboard_keyboard::handle_input () {
din0.dinfo.delay = uis.cb_delay.state;
mouse_bend (); // pitch bend via mouse x movement
// trigger notes
//
for (int i = 0, j = keys.size (); i < j; ++i) {
key_info& ki = keys[i];
if (keypressed (ki.id)) { // pressed key of note
attack_note (ki.id); // trigger note
ki.attacked = 1;
} else if (ki.attacked && !keydown (ki.id)) { // released key of note
decay_note (ki.id); // decay
ki.attacked = 0;
}
}
if (keypressed (SDLK_RETURN)) {
trig_what = !trig_what;
extern const char* keys_trig_what [];
set_label (uis.ol_trig_what.option, keys_trig_what, trig_what);
}
if (keypressed (SDLK_SPACE))
turn_off_bend (); // turn of mouse based pitch bending
// octave shift
else if (keypressed (SDLK_z)) modulate_down ();
else if (keypressed (SDLK_x)) modulate_up ();
else if (keypressedd (SDLK_F11)) { // decrease octave shift bpm
octave_shift.set_bpm (octave_shift.bpm - (float)MENU.sp_octave_shift_bpm.f_delta);
cons << "octave shift bpm: " << octave_shift.bpm << eol;
} else if (keypressedd (SDLK_F12)) { // increase octave bpm
octave_shift.set_bpm (octave_shift.bpm + (float) MENU.sp_octave_shift_bpm.f_delta);
cons << "octave shift bpm: " << octave_shift.bpm << eol;
}
else if (keypressed (SDLK_n)) abort_octave_shift (this);
// browse waveforms
if (!mouse_slider0.active) {
if (keypressedd (SDLK_9)) uis.wdl.clicked (uis.ab_prev_wav); else
if (keypressedd (SDLK_0)) uis.wdl.clicked (uis.ab_next_wav);
}
if (keypressed (SDLK_F1)) helptext(); // show help
return 1;
}
void keyboard_keyboard::setup_notes (int overwrite) {
notes.clear ();
vector<std::string>& scalenotes = scaleinfo.notes;
float tonic;
int noct;
int num_notes = scalenotes.size () - 1; // bcos note & octave included in scale definition
if (num_notes > 7) { // 1 octave for > 7 notes
noct = 1;
tonic = scaleinfo.tonic;
} else { // 3 octaves for < 7 notes
noct = 3;
tonic = scaleinfo.tonic / 2.0f;
}
extern map<std::string, float> INTERVALS; // tuning
extern const char* WESTERN_FLAT [];
extern map<std::string, int> NOTE_POS; // inteval -> pos on chromatic scale
extern int NOTATION; // current notation
int western = scaleinfo.western; // index of key in the chromatic scale
string tonic_name;
if (NOTATION == WESTERN) {
tonic_name = WESTERN_FLAT [western]; // note on the chromatic scale
} else if (NOTATION == NUMERIC) tonic_name = "1";
else tonic_name = INDIAN_SWAR[0];
for (int i = 0; i < noct; ++i) {
for (int j = 0; j < num_notes; ++j) {
std::string& name = scalenotes [j];
notes.push_back (note());
note& last = notes [notes.size() - 1];
if (NOTATION == WESTERN) {
int k = (western + NOTE_POS [name]) % 12;
last.name = WESTERN_FLAT [k];
} else if (NOTATION == NUMERIC) last.name = name;
else last.name = INT2IND[name];
last.set_freq (tonic * INTERVALS [name]);
}
tonic *= 2; // next octave
}
// bring up the rear
notes.push_back (note());
note& last = notes [notes.size() - 1];
last.name = tonic_name;
last.set_freq (tonic);
memset (keybd2notes, 0, MAX_KEYS * sizeof (int));
if (overwrite) keys = scale2keybd [num_notes];
// green the tonic
for (int i = 0, j = keys.size (), k = 0; i < j; ++i) {
key_info& ki = keys[i];
keybd2notes [ki.id] = k;
if (notes[k++].name == tonic_name) ki.r = ki.b = 0;
}
}
void keyboard_keyboard::attack_note (int ky) { // also trigger note
int ni = keybd2notes [ky];
note& n = notes [ni];
key_info& ki = keys [ni];
recent_note_hz = n.hz;
marker_x = mousex;
int last_trig_x = ki.x;
int last_trig_y = ki.y;
if (show_nearby_notes) { // position note display at cursor
last_trig_x = mousex;
last_trig_y = mouseyy;
if (num_triggered_notes) turn_off_bend (); // stop bending all previous notes, will only bend the last note
}
int bin = is_din_binaural ();
float sep = 0.0f; if (bin) sep = get_binaural_separation_in_hz ();
triggered_notes.push_back ( triggered_note (n, NOTE_VOLUME, last_trig_x, last_trig_y, trig_what, bin, JUSTIFICATION, sep, ky, marker_x));
triggered_note& last_triggered_note = triggered_notes.back ();
last_triggered_note.setup (&wave, &attackcrv, &decaycrv);
++num_triggered_notes;
}
void keyboard_keyboard::decay_note (int ky) {
for (triggered_note_iterator i = triggered_notes.begin (), j = triggered_notes.end (); i != j; ++i) {
triggered_note& ti = *i;
if (ti.key == ky) ti.start_decay ();
}
}
void keyboard_keyboard::update_attack () {
for (triggered_note_iterator i = triggered_notes.begin (), j = triggered_notes.end (); i != j; ++i) (*i).attack.update ();
}
void keyboard_keyboard::update_decay () {
for (triggered_note_iterator i = triggered_notes.begin (), j = triggered_notes.end (); i != j; ++i) (*i).decay.update ();
}
void keyboard_keyboard::update_waveform (multi_curve& src) {
static const string nam ("keyboard-keyboard-waveform");
for (triggered_note_iterator i = triggered_notes.begin (), j = triggered_notes.end (); i != j; ++i)
(*i).update_solver (src, nam);
uis.cd_waveform_display.setbbox (wave.shapeform);
}
int keyboard_keyboard::render_audio (float* L, float* R) {
int ret = 0;
for (triggered_note_iterator i = triggered_notes.begin (), j = triggered_notes.end (); i != j; ++i) {
triggered_note& ti = *i;
if (ti.state != triggered_note::FINISHED) {
ti.eval (L, R, aout.result, aout.vol, aout.samples_per_channel, ATTACK_TIME, DECAY_TIME, _gotog);
ret += ti.player.mixer.active;
}
}
return ret;
}
void keyboard_keyboard::remove_finished_notes () {
for (triggered_note_iterator i = triggered_notes.begin (); i != triggered_notes.end();) {
triggered_note& ti = *i;
if (ti.state == triggered_note::FINISHED) {
i = triggered_notes.erase (i);
--num_triggered_notes;
} else ++i;
}
}
void keyboard_keyboard::calc_visual_params () {
int nkeys = keys.size ();
int key_width = 2 * fnt.charwidth.avg * fnt.cellsize.x + 3;
int press_width = get_char_width ("Press "); // in pixels
int print = press_width + key_width * nkeys;
pressx = (int) ((view.xmax - print) / 2 + 0.5f);
hearx = pressx;
int x = pressx + press_width;
int half_height = view.ymax / 2;
for (int i = 0; i < nkeys; ++i) {
key_info& ki = keys[i];
ki.x = x;
ki.y = half_height;
x += key_width;
}
extern float NOTE_VOLUME;
arm = (int) (0.4f * view.ymax / NOTE_VOLUME + 0.5f);
int lh = (int) (1.5f * line_height + 0.5f);
ymarker = (int) (1.25f * lh + 0.5f);
ynotes = (int)(ymarker + 0.75f * lh + 0.5f);
ychars = (int)(ynotes + lh);
if (nkeys) spacing = (view.width - 2 * keys[0].x) / 12;
}
void keyboard_keyboard::draw () {
glMatrixMode (GL_PROJECTION);
glLoadIdentity ();
glOrtho (0, view.xmax, 0, view.ymax, -1, 1);
glMatrixMode (GL_MODELVIEW);
glLoadIdentity ();
// draw triggerred notes
glEnable (GL_BLEND);
glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
const float alpha = 0.5f;
for (triggered_note_iterator i = triggered_notes.begin (), j = triggered_notes.end (); i != j; ++i) {
triggered_note& ti = *i;
// fill
glColor4f (ti.r, ti.g, ti.b, alpha);
int arm1 = (int) (ti.volume.now * arm);
glRecti (ti.x - arm1 - ti.bend_x, ti.y - arm1, ti.x + arm1 + ti.bend_x, ti.y + arm1);
// outline
glColor4f (1, 1, 1, alpha);
int xp = ti.x + arm1 + ti.bend_x, xm = ti.x - arm1 - ti.bend_x, yp = ti.y + arm1, ym = ti.y - arm1;
glLineWidth (2);
pts[0]=xm;pts[1]=ym;
pts[2]=xp;pts[3]=ym;
pts[4]=xp;pts[5]=yp;
pts[6]=xm;pts[7]=yp;
glVertexPointer(2, GL_INT, 0, pts);
glDrawArrays (GL_LINE_LOOP, 0, 4);
glLineWidth (1);
}
glDisable (GL_BLEND);
if (UI_OFF) return;
if (midiin.available) { // mark midi notes
glColor3f (0.25, 0.25, 0.25);
int startx = keys[0].x, starty = keys[0].y;
int marker = 15;
glVertexPointer (2, GL_INT, 0, pts);
for (int i = 0, num_notes = 12; i < num_notes; ++i) {
pts[0]=startx-marker;pts[1]=starty;
pts[2]=startx+marker;pts[3]=starty;
pts[4]=startx;pts[5]=starty-marker;
pts[6]=startx;pts[7]=starty+marker;
glDrawArrays (GL_LINES, 0, 4);
startx += spacing;
}
}
glEnable (GL_LINE_STIPPLE);
glLineStipple (1, 0xFFF0);
glColor3f (0.75, 0.75, 0.75);
if (num_triggered_notes) {
if (show_nearby_notes) { // mark notes near to cursor
for (int i = 0, j = notes.size (); i < j; ++i) {
key_info& ki = keys [i];
note& ni = notes[i];
float dhz = ni.hz - recent_note_hz;
int npix = (int) (dhz / PITCH_BEND_PER_PIXEL + 0.5);
int marker_nx = marker_x + npix;
/*pts[0]=marker_nx;pts[1]=0;
pts[2]=marker_nx;pts[3]=view.ymax;
glVertexPointer (2, GL_INT, 0, pts);
glDrawArrays (GL_LINES, 0, 2);*/
glBegin (GL_LINES);
glVertex2i (marker_nx, 0);
glVertex2i (marker_nx, view.ymax);
glEnd ();
glDisable (GL_LINE_STIPPLE);
draw_string (ni.name, marker_nx, ki.y);
fnt.draw_char (ki.ch, marker_nx, ki.y + line_height);
glEnable (GL_LINE_STIPPLE);
}
} else {
// mark 0 pitch bend
/*pts[0]=marker_x;pts[1]=0;
pts[2]=marker_x;pts[3]=view.ymax;
glVertexPointer (2, GL_INT, 0, pts);
glDrawArrays (GL_LINES, 0, 2);*/
glBegin (GL_LINES);
glVertex2i (marker_x, 0);
glVertex2i (marker_x, view.ymax);
glEnd ();
}
}
glLineWidth (1);
glDisable (GL_LINE_STIPPLE);
// show press keys & hear notes
static const string PRESS = "Press ", HEAR = " Hear ";
glColor3f (1, 1, 1);
draw_string (PRESS, pressx, ychars);
draw_string (HEAR, hearx, ynotes);
for (int i = 0, j = keys.size (); i < j; ++i) {
key_info& ki = keys[i];
note& ni = notes[i];
glColor3f (ki.r, ki.g, ki.b);
fnt.draw_char (ki.ch, ki.x, ychars);
draw_string (ni.name, ki.x, ynotes);
if (ki.attacked) { // hilite key that triggered last note
glBegin (GL_LINES);
glVertex2i (ki.x, ymarker);
glVertex2i (ki.x + fnt.charwidth.max, ymarker);
glEnd ();
/*pts[0]=ki.x;pts[1]=ymarker;
pts[2]=ki.x+fnt.charwidth.max;pts[3]=ymarker;
glVertexPointer (2, GL_INT, 0, pts);
glDrawArrays (GL_LINES, 0, 2);*/
}
}
}
void keyboard_keyboard::setup_midi_notes () {
const float middlec = 261.626f;
float start = middlec / 32;
extern int NUM_INTERVALS;
extern std::vector<float> INTERVAL_VALUES;
int id = 0;
while (1) {
int j = NUM_INTERVALS - 1;
for (int i = 0; i < j; ++i) {
if (id < MIDI_MAX) {
midi_notes[id].set_freq (start * INTERVAL_VALUES [i]);
id++;
} else return;
}
start = start * INTERVAL_VALUES [j];
}
}
void keyboard_keyboard::note_on (unsigned char id, unsigned char vel) {
if (vel == 0) {
note_off (id);
return;
}
note& n = midi_notes [id];
int i12 = id % 12;
marker_x = mousex;
int bin = is_din_binaural ();
float sep = 0.0f; if (bin) sep = get_binaural_separation_in_hz ();
triggered_notes.push_back (
triggered_note (n, velsol(vel) * NOTE_VOLUME, keys[0].x + i12 * spacing, keys[0].y, trig_what, bin, JUSTIFICATION, sep, id, marker_x)
);
triggered_note& last_triggered_note = *(--triggered_notes.end());
last_triggered_note.setup (&wave, &attackcrv, &decaycrv);
++num_triggered_notes;
}
void keyboard_keyboard::note_off (unsigned char id) {
decay_note (id);
}
void keyboard_keyboard::pitch_bend (float v) {
for (triggered_note_iterator i = triggered_notes.begin (), j = triggered_notes.end (); i != j; ++i) {
triggered_note& ti = *i;
float pv = PITCH_BEND * v;
ti.set_freq (ti.start_hz + pv);
ti.bend_x = fabs (pv);
}
}
void keyboard_keyboard::calc_mouse_bend (triggered_note& ti) {
int delta_x = mousex - ti.prev_mousex;
float pv = delta_x * PITCH_BEND_PER_PIXEL;
ti.set_freq (ti.start_hz + pv);
ti.bend_x = fabs (pv);
}
void keyboard_keyboard::mouse_bend () {
if (mousex != prev_mousex) {
for (triggered_note_iterator i = triggered_notes.begin (), j = triggered_notes.end (); i != j; ++i) {
triggered_note& ti = *i;
if (ti.bend) calc_mouse_bend (ti);
}
prev_mousex = mousex;
}
}
void keyboard_keyboard::enter () {
SDL_WarpMouse (prev_mousex, prev_mousey);
}
void keyboard_keyboard::tonic_changed () {
setup_notes (0);
calc_visual_params ();
}
void keyboard_keyboard::scale_changed () {
load_scale ();
}
void keyboard_keyboard::scale_loaded () {
load_scale ();
}
void keyboard_keyboard::load_scale (int dummy) {
setup_notes ();
setup_midi_notes ();
calc_visual_params ();
}
int keyboard_keyboard::turn_off_bend () {
for (triggered_note_iterator i = triggered_notes.begin (), j = triggered_notes.end (); i != j; ++i) {
triggered_note& ti = *i;
ti.bend = 0; // turn of mouse based pitch bending
}
return 0;
}