desc:MIDI Logger
//tags: MIDI analysis utility
slider1:g_note_analysis=0<0,2,1>-note-on/off analysis mode

in_pin:none
out_pin:none

@init
ext_midi_bus = 1; // causes midi_bus to be used for send/recv and midirecv() receives on all buses
ent_time = 0;
ent_blockoffs = 1;
ent_midiinfo = 2;
ent_seq = 3;
ent_bus = 4;
ent_size = 5;

hist = 1000;
hist_size = 0;
hist_max = 10000*ent_size;
hist_chunk = 1000*ent_size; // advance

notebuf = hist + hist_max; // 16*128 * notebuf_ent_size;
notebuf_ent_size = 5; // current count, last note type, min onstate count, maxonstate count, sequence ignore before

seq=0;

log_active=1;
g_cap=0;
g_capst=0;
g_at_end = 1;
g_force_redraw=1;

gfx_clear=-1;

function notebuf_from_ptr(ptr) local(m) (
  m = ptr[ent_midiinfo];
  (m&0xe0) == 0x80 && ptr[ent_bus] == 0 ? (
    notebuf + ((m&0xf)*128 + ((m>>8)&127)) * notebuf_ent_size;
  );
);

@block

// temp varibles used in audio thread all start with _
log_active ? while (
  midirecv(_ts,_msg1,_msg23) ? (
    hist_size >= hist_max ? (
      memcpy(hist,hist+hist_chunk,hist_max-hist_chunk);
      hist_size = hist_max - hist_chunk;
    );

    hist[hist_size+ent_time] = play_position;
    hist[hist_size+ent_blockoffs] = _ts;
    hist[hist_size+ent_midiinfo] = _msg1 + _msg23*256;
    hist[hist_size+ent_seq] = seq;
    hist[hist_size+ent_bus] = midi_bus;
    
    (_nbptr = notebuf_from_ptr(hist+hist_size)) ? (
      _note_on = ((_msg1&0x10) && (_msg23>>8)) ? 1 : -1;
      _nbptr[0] += _note_on;
      _nbptr[1] = _note_on;
      _nbptr[2] = min(_nbptr[2],_nbptr[0]);
      _nbptr[3] = max(_nbptr[3],_nbptr[0]);
      _nbptr[0] == 0 && _nbptr[2]>=0 && _nbptr[3]<=1 ? _nbptr[4] = seq+1;
    );

    seq+=1;
    hist_size+=ent_size;

    g_force_redraw=1;
    midisend(_ts,_msg1,_msg23);
    1;
  );
);

@gfx 600 400

function draw_button(str, cap) local(r,g,b) (
  this.capmode = cap;
  this.x = gfx_x;
  this.y = gfx_y;
  this.w = strlen(str)*8;
  this.h = gfx_texth;
  g_cap == cap && g_capst ? (
    r=gfx_r; g=gfx_g; b=gfx_b;
    gfx_set(1,0,0);
    gfx_rect(gfx_x,gfx_y,this.w,gfx_texth+4);
    gfx_set(r,g,b);
  );
  gfx_y+=2;
  gfx_drawstr(str);
  gfx_y-=2;
);

function hit_button(cap) (
  (!cap || cap == this.capmode) &&
    mouse_x >= this.x && mouse_x < this.x+this.w &&
      mouse_y >= this.y && mouse_y < this.y+this.h ? this.capmode;
);

function draw_message(ptr,xo) local(m)
(
  m=ptr[ent_midiinfo];
  (m & 0xe0) == 0x80 ? (
    (m&0x10) && (m>>16) ? gfx_set(.5,1,.5) : gfx_set(1,.5,.5);
  ) :  gfx_set(1);

  gfx_x=xo;
  gfx_drawnumber(ptr[ent_seq],0);
  gfx_x=60+xo;
  gfx_drawnumber(ptr[ent_time] + ptr[ent_blockoffs]/srate,4);
  gfx_x=180+xo;
  gfx_drawnumber(ptr[ent_blockoffs],0);
  gfx_x=230+xo;
  gfx_drawchar('B');
  gfx_drawnumber(ptr[ent_bus]+1,0);
  gfx_x=300+xo;
  gfx_printf("%02x %02x %02x",m&255,(m>>8)&255,(m>>16)&255);
  gfx_y += gfx_texth+2;
);

function notebuf_want_display(nbptr) (
  nbptr[0]||nbptr[3]>1||nbptr[2]
);

function noteanalyze_message(ptr) local(nbptr) (
  (nbptr = notebuf_from_ptr(ptr)) ? (
    ptr[ent_seq]>=nbptr[4] ? (
      notebuf_want_display(nbptr) ? (
        draw_message(ptr,0);
      );
    );
  );
);

function grouped_noteanalysis() local(nbptr ptr nbend srch chan note) (
  nbptr = notebuf;
  nbend = notebuf + 16*128*notebuf_ent_size;
  while (gfx_y < gfx_h && nbptr < nbend)
  (
    notebuf_want_display(nbptr) ? (
      gfx_x = 0;
      gfx_set(1,.25,.25);
      note = (nbptr-notebuf)/notebuf_ent_size;
      chan = note>>8;
      note &= 127;
      gfx_printf("NOTE (chan %d) -- %02x", chan, note);
      gfx_y += gfx_texth+2;

      ptr = hist;
      srch = 0x80 | chan | (note << 8);
      while (gfx_y < gfx_h && ptr < hist + hist_size)
      (
        ptr[ent_seq] >= nbptr[4] && (ptr[ent_midiinfo] & 0xffef) == srch ? (
          draw_message(ptr,16);
        );
        ptr += ent_size;
      );
    );
    nbptr += notebuf_ent_size;
  );
);

top_margin=gfx_texth + 4.0;
g_histsize = ((hist_size/ent_size)|0);
g_viewsize = (((gfx_h-top_margin-1)/(gfx_texth+2))|0);

g_pos = g_histsize - g_viewsize;
g_at_end ? (
  g_pos += 1;
  g_scroll = g_pos;
) : (
  g_pos > g_scroll ? g_pos = g_scroll;
);

g_pos < 0 ? g_pos=0;

g_scrollbar_top = top_margin+1 + g_pos * (gfx_h-top_margin-1) / g_histsize;
g_scrollbar_bot = top_margin + 1+  (g_pos + g_viewsize)*(gfx_h-top_margin-1)/g_histsize;

g_cap ? (
  g_capst=0;
  g_cap == 1 ? (
    g_pos = mouse_y;
    g_scrollbar_offs >= 0 ? g_pos-= g_scrollbar_offs;
    g_pos -= top_margin+1;
    g_pos = g_histsize * g_pos / (gfx_h-top_margin-1);

    g_scroll = (g_pos |= 0);
    g_pos < 0 ? g_pos=0;

    g_pos >= g_histsize-g_viewsize ? ( g_at_end=1; g_pos = g_histsize-g_viewsize; ) : g_at_end=0;
    g_pos < 0 ? g_pos=0;

    g_scrollbar_top = top_margin+1 + g_pos * (gfx_h-top_margin-1) / g_histsize;
    g_scrollbar_bot = top_margin + 1+  (g_pos + g_viewsize)*(gfx_h-top_margin-1)/g_histsize;
  ) :
  log_button.hit_button(g_cap) ? (
   !(mouse_cap&1)? log_active=!log_active;
   g_capst=1;
  ) :
  note_button.hit_button(g_cap) ? (
   !(mouse_cap&1)? g_note_analysis = (g_note_analysis+1)%3;
   g_capst=1;
  ) :
  clear_button.hit_button(g_cap) ? (
    !(mouse_cap&1) ? (
       seq = hist_size = 0;
       g_histsize=0;
       g_pos=1; 
       g_at_end=1;
       memset(notebuf,0,notebuf_ent_size*16*128);
       g_scrollbar_top = top_margin+1 + g_pos * (gfx_h-top_margin-1) / g_histsize;
       g_scrollbar_bot = top_margin + 1+  (g_pos + g_viewsize)*(gfx_h-top_margin-1)/g_histsize;
    );
    g_capst=1;
  );
  !(mouse_cap&1) ? g_cap=0;
  g_force_redraw=1;
) : (
  (mouse_cap & 1) ? (
    g_capst=1;
    mouse_y > top_margin && mouse_x > gfx_w - top_margin ? (
      g_scrollbar_offs = -1;

      mouse_y >= g_scrollbar_top && mouse_y <= g_scrollbar_bot ? g_scrollbar_offs = mouse_y - g_scrollbar_top;

      g_cap=1; // scrollbar
    ) : (
      (g_cap = log_button.hit_button(0)) ||
        (g_cap = clear_button.hit_button(0)) ||
          (g_cap = note_button.hit_button(0)) ||
             g_cap = -1;
      g_cap > 0 ? g_force_redraw=1;
    );
  ) : g_cap=0;
);

gfx_w != g_lw || gfx_h != g_lh ||  g_force_redraw ? (
  g_force_redraw=0;
  g_lw=gfx_w; g_lh=gfx_h;
  gfx_set(0,0,0,1);
  gfx_rect(0,0,gfx_w,gfx_h);


  // draw top line
  gfx_set(0.3);
  gfx_rect(0,0,g_lw,top_margin);

  gfx_x=gfx_y=0;

  gfx_set(log_active?1:0);
  log_button.draw_button("LOG",4);

  gfx_x += 16;

  gfx_set(1);
  clear_button.draw_button("CLEAR",3);

  gfx_x+=44;
  gfx_set(g_note_analysis ? 1 : .1);
  note_button.draw_button(g_note_analysis == 2 ? "NOTE-ON/OFF GROUPED" : 
                          g_note_analysis == 1 ? "NOTE-ON/OFF SEQUENTIAL" : 
                          "NOTE-ON/OFF ANALYZER",2);


  g_note_analysis == 0 ? (
    // right scrollbar, not implemented for note analysis modes
    gfx_set(0,.3,.3);
    gfx_rect(gfx_w-top_margin,top_margin+1,top_margin,gfx_h-top_margin-1);

    gfx_set(0,.7,.7);
    gfx_rect(gfx_w-top_margin+1,g_scrollbar_top,top_margin+1,g_scrollbar_bot-g_scrollbar_top);
  );

  gfx_y=top_margin+1;
  g_pos = (g_pos|0) * ent_size + hist;
  
  g_note_analysis == 2 ? (
    grouped_noteanalysis();
  ) : (
    while (gfx_y < gfx_h && g_pos < hist + hist_size) (
      g_note_analysis ?  noteanalyze_message(g_pos) : draw_message(g_pos,0);
      g_pos += ent_size;
    );
    g_at_end ? (
      gfx_x=0;
      gfx_set(1);
      gfx_drawchar('_');
    );
  );
);
