
/*
**  jazz - a midi sequencer for Linux
**
**  Copyright (C) 1995-1996 Andreas Voss (andreas@avix.rhein-neckar.de)
**
**  Copyright (C) 1995-1996 Per Sigmond (Per.Sigmond@hia.no)
**
**  This program is free software; you can redistribute it and/or modify
**  it under the terms of the GNU General Public License as published by
**  the Free Software Foundation; either version 2 of the License, or
**  (at your option) any later version.
**
**  This program is distributed in the hope that it will be useful,
**  but WITHOUT ANY WARRANTY; without even the implied warranty of
**  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
**  GNU General Public License for more details.
**
**  You should have received a copy of the GNU General Public License
**  along with this program; if not, write to the Free Software
**  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#include "wx.h"
#pragma hdrstop

#include "rhythm.h"
#include "eventwin.h"
#include "song.h"
#include "jazz.h"
#include "command.h"
#include "harmony.h"
#include "trackwin.h"
#include "pianowin.h"

tRhythmWin *rhythm_win = 0;


// pseudo key nr's for harmony browser and sound effects
static const int CHORD_KEY	= -1;
static const int BASS_KEY	= -2;
static const int PASTE_KEY	= -3;
static const int CONTROL_KEY	= -4;


tRhythm::tRhythm(int k)
  : rhythm(64, 0, 99),
    length( 8, 0, 99),
    veloc (32, 0, 99)
{
  key             = k;
  parm            = 0;
  steps_per_count = 4;
  count_per_bar   = 4;
  n_bars          = 1;

  label = copystring("random rhythm");
}

tRhythm::tRhythm(const tRhythm &o)
  : rhythm(o.rhythm),
    length(o.length),
    veloc (o.veloc)
{
  key             = o.key;
  parm            = o.parm;
  n_bars          = o.n_bars;
  steps_per_count = o.steps_per_count;
  count_per_bar   = o.count_per_bar;
  label           = copystring(o.label);
}

tRhythm & tRhythm::operator=(const tRhythm &o)
{
  rhythm          = o.rhythm;
  length          = o.length;
  veloc           = o.veloc;
  key             = o.key;
  parm            = o.parm;
  n_bars          = o.n_bars;
  steps_per_count = o.steps_per_count;
  count_per_bar   = o.count_per_bar;

  delete label;
  label           = copystring(o.label);

  return *this;
}

tRhythm::~tRhythm()
{
  delete label;
}

ostream & operator << (ostream &os, tRhythm const &a)
{
  os << a.rhythm;
  os << a.length;
  os << a.veloc;

  os << a.steps_per_count << " ";
  os << a.count_per_bar << " ";
  os << a.n_bars << " ";
  os << a.key << " ";
  os << a.parm << endl;

  os << "\"" << a.label << "\"" << endl;

  return os;
}

istream & operator >> (istream &is, tRhythm &a)
{
  char buf[200];
  is >> a.rhythm;
  is >> a.length;
  is >> a.veloc;

  is >> a.steps_per_count;
  is >> a.count_per_bar;
  is >> a.n_bars;
  is >> a.key;
  is >> a.parm;

  // read a string (I hate C++)
  int c, i;
  do
    c = is.get();	// ignore \"
  while (c != '"');
  for (i = 0; i < sizeof(buf); i++) {
    c = is.get();
    if (c == '"')
      break;
    buf[i] = c;
  }
  buf[i] = 0;

  a.SetLabel(buf);

  return is;
}

void tRhythm::SetLabel(char const *s)
{
  delete label;
  label = copystring(s);
}


void tRhythm::Generate(tTrack *track, long fr_clock, long to_clock, long ticks_per_bar)
{
  int chan   = track->Channel - 1;
  long clock = fr_clock;

  long clocks_per_step = ticks_per_bar / (steps_per_count * count_per_bar);
  long total_steps = (to_clock - fr_clock) / clocks_per_step;

  while (clock < to_clock)
  {
    int i = ((clock - fr_clock) / clocks_per_step) % rhythm.Size();
    if (rhythm.Random(i))
    {
      // put event here
      short vel = veloc.Random() * 127 / veloc.Size() + 1;
      short len = (length.Random() + 1) * clocks_per_step;

      // generate keys from harmony browser
      if (key == CHORD_KEY || key == BASS_KEY)
      {
        if (the_harmony_browser)
        {
          long step = (clock - fr_clock) * total_steps / (to_clock - fr_clock);
	  int keys[12], n_keys;
          if (key == CHORD_KEY)
            n_keys = the_harmony_browser->GetChordKeys(keys, (int)step, (int)total_steps);
          else
            n_keys = the_harmony_browser->GetBassKeys(keys, (int)step, (int)total_steps);
          for (int j = 0; j < n_keys; j++)
          {
	    tKeyOn *k = new tKeyOn(clock, chan, keys[j], vel, len - clocks_per_step/2);
	    track->Put(k);
	  }
	}
      }

      // paste pianowin buffer
      else if (key == PASTE_KEY)
      {
        tEventArray &src = TrackWin->GetPianoWin()->PasteBuffer;
        for (int ii = 0; ii < src.nEvents; ii++)
        {
          tKeyOn *on = src.Events[ii]->IsKeyOn();
          if (on)
          {
	    tKeyOn *k = new tKeyOn(clock, chan, on->Key, vel, len - clocks_per_step/2);
	    track->Put(k);
          }
	}
      }

      // generate controller
      else if (key == CONTROL_KEY) {
	tControl *c = new tControl(clock, chan, parm - 1, vel);
	track->Put(c);
      }

      // generate note on events
      else
      {
	tKeyOn *k = new tKeyOn(clock, chan, key, vel, len - clocks_per_step/2);
	track->Put(k);
      }

      clock += len;
    }
    else
      clock += clocks_per_step;
  }
}


// ============================ tRhythmWin ==============================


#define MEN_CLOSE 1
#define MEN_LOAD  2
#define MEN_SAVE  3
#define MEN_HELP  4

tRhythmWin::tRhythmWin(tEventWin *e, tSong *s)
  : wxFrame(0, "Random Rhythm Generator", RhythmGeo[0], RhythmGeo[1], 500, 400),
    edit(0)
{
  event_win       = e;
  song            = s;
  in_create       = 1;
  n_instruments   = 0;
  act_instrument  = -1;

  steps_per_count = 0;
  count_per_bar   = 0;
  n_bars          = 0;
  instrument_list = 0;

  // panel items

  wxMenuBar *menu_bar = new wxMenuBar;
  wxMenu    *menu = new wxMenu;
  menu->Append(MEN_LOAD,	"&Load");
  menu->Append(MEN_SAVE,	"&Save");
  menu->Append(MEN_HELP,	"&Help");
  menu->Append(MEN_CLOSE,	"&Close");
  menu_bar->Append(menu,	"&Menu");
  SetMenuBar(menu_bar);

  int w, h;
  GetClientSize(&w, &h);
  panel = new wxPanel(this, 0, 0, w/2, 2*h/3, 0, "RhythmFrame");
  panel->SetLabelPosition(wxHORIZONTAL);

#ifdef wx_msw
  steps_per_count = new wxSlider(panel, (wxFunction)SliderChange, "", 4, 1, 8, w/3);
  (void) new wxMessage(panel, "steps/count");
  panel->NewLine();

  count_per_bar   = new wxSlider(panel, (wxFunction)SliderChange, "", 4, 1, 8, w/3);
  (void) new wxMessage(panel, "count/bar");
  panel->NewLine();

  n_bars          = new wxSlider(panel, (wxFunction)SliderChange, "", 4, 1, 8, w/3);
  (void) new wxMessage(panel, "# bars");
  panel->NewLine();
#else

  steps_per_count = new wxSlider(panel, (wxFunction)SliderChange, "", 4, 1, 8, w/6, 10,     1, wxFIXED_LENGTH);
  (void) new wxMessage(panel, "steps/count");
  panel->NewLine();

  count_per_bar   = new wxSlider(panel, (wxFunction)SliderChange, "", 4, 1, 8, w/6, 10, 1*h/12, wxFIXED_LENGTH);
  (void) new wxMessage(panel, "count/bar");
  panel->NewLine();

  n_bars          = new wxSlider(panel, (wxFunction)SliderChange, "", 4, 1, 8, w/6, 10, 2*h/12, wxFIXED_LENGTH);
  (void) new wxMessage(panel, "# bars");
  panel->NewLine();
#endif


  panel->SetLabelPosition(wxVERTICAL);
  instrument_list = new wxListBox(panel, (wxFunction)SelectInstr, "Instrument", wxSINGLE|wxALWAYS_SB, -1, -1, 220, 80);
  panel->NewLine();

  (void)new wxButton(panel, (wxFunction)Add, "add") ;
  (void)new wxButton(panel, (wxFunction)Del, "del") ;
  (void)new wxButton(panel, (wxFunction)Generate, "gen") ;

#if 0
  (void)new wxButton(panel, (wxFunction)Done, "bye") ;
#ifdef wx_msw
  panel->NewLine();
#endif
  (void)new wxButton(panel, (wxFunction)Help, "help") ;
#endif

  panel->NewLine();

  // RndArray Edits
                                                   //    x    y    w    h
  length_edit = new tArrayEdit   (this,  edit.length,  w/2,   0, w/2, h/3);
  length_edit->SetTicks(1, 1);
  length_edit->SetLabel("length");

  veloc_edit = new tArrayEdit    (this,  edit.veloc,   w/2, h/3, w/2, h/3);
  veloc_edit->SetTicks(6, 4);
  veloc_edit->SetLabel("velocity");

  rhythm_edit = new tRhyArrayEdit(this,  edit.rhythm,     0, 2*h/3, w, h/3);
  rhythm_edit->SetMeter(edit.steps_per_count, edit.count_per_bar, edit.n_bars);
  rhythm_edit->SetLabel("rhythm");

  in_create = 0;

  Show(TRUE);
}

void tRhythmWin::OnMenuCommand(int id) {
  switch (id) {
    case MEN_HELP: Help(); break;
    case MEN_CLOSE:
  	rhythm_win->GetPosition( &RhythmGeo[0], &RhythmGeo[1] );
  	Show(FALSE);
  	rhythm_win = 0;
  	delete this;
	break;

    case MEN_LOAD: 
      {
	char * fname = wxFileSelector("Load Rhythm", NULL, NULL, NULL, "*.rhy");
	if (fname) {
	  ifstream is(fname);
	  is >> *this;
	}
      }
      break;

    case MEN_SAVE: 
      {
        Win2Instrument();
	char * fname = wxFileSelector("Save Rhythm", NULL, NULL, NULL, "*.rhy");
	if (fname) {
	  ofstream os(fname);
	  os << *this;
	}
      }
      break;

  }
}

void tRhythmWin::SelectInstr(wxListBox& list, wxCommandEvent& event)
{
  tRhythmWin *win = (tRhythmWin *)list.GetParent()->GetParent();
  win->Win2Instrument();
  win->act_instrument = win->instrument_list->GetSelection();
  win->Instrument2Win();
  win->OnPaint();
}

void tRhythmWin::Add(wxButton &but, wxCommandEvent& event)
{
  tRhythmWin *win = (tRhythmWin *)but.GetParent()->GetParent();
  win->AddInstrumentDlg();
}

void tRhythmWin::AddInstrumentDlg()
{
  if (n_instruments >= MAX_INSTRUMENTS)
    return;

  int i, n = 0;
  char *names[150];
  int   keys[150];

  names[n] = "Controller";
  keys[n++] = CONTROL_KEY;

  if (the_harmony_browser && the_harmony_browser->SeqDefined())
  {
    names[n] = "harmony: chords";
    keys[n++] = CHORD_KEY;
    names[n] = "harmony: bass";
    keys[n++] = BASS_KEY;
  }

  names[n] = "pianowin buffer";
  keys[n++] = PASTE_KEY;

  for (i = 0; DrumNames[i].Name; i++)
  {
    if (DrumNames[i].Name[0])
    {
      keys[n]    = DrumNames[i].Value - 1;
      names[n++] = DrumNames[i].Name;
    }
  }
  i = wxGetSingleChoiceIndex("Instrument", "Select an instrument", n, names);
  if (i >= 0)
  {
    Win2Instrument(); // save actual values

    tRhythm *r = 0;
    if (act_instrument >= 0)
    {
      r = new tRhythm(*instruments[act_instrument]);
      r->key = keys[i];
    }
    else
      r = new tRhythm(keys[i]);

    // choose controller?
    if (r->key != CONTROL_KEY)
      r->SetLabel(names[i]);
    else
    {
      r->parm = SelectControllerDlg();
      if (r->parm < 0)
        return;
      r->SetLabel(ControlNames[r->parm].Name);
    }

    AddInstrument(r);
  }
}

void tRhythmWin::AddInstrument(tRhythm *r)
{
  act_instrument = n_instruments++;
  instruments[act_instrument] = r;
  instrument_list->Append((char *)r->GetLabel());

  instrument_list->SetSelection(act_instrument);
  Instrument2Win();
  OnPaint();
}


void tRhythmWin::Del(wxButton &but, wxCommandEvent& event)
{
  tRhythmWin *win = (tRhythmWin *)but.GetParent()->GetParent();
  win->DelInstrument();
}


void tRhythmWin::DelInstrument()
{
  int i = act_instrument;
  if (i >= 0)
  {
    int k;
    delete instruments[i];
    for (k = i; k < n_instruments-1; k++)
      instruments[k] = instruments[k+1];
    instruments[k] = 0;
    n_instruments--;
    instrument_list->Delete(i);
    act_instrument = instrument_list->GetSelection();
    Instrument2Win();
    OnPaint();
  }
}


void tRhythmWin::Generate(wxButton &but, wxCommandEvent& event)
{
  tRhythmWin *win = (tRhythmWin *)but.GetParent()->GetParent();
  win->Win2Instrument();
  win->GenRhythm();
}


void tRhythmWin::GenRhythm()
{
  if (!event_win->EventsSelected())
    return;

  tFilter *filter = event_win->Filter;

  if (filter->FromTrack != filter->ToTrack)
  {
    wxMessageBox("you must select exacty 1 track", "Error");
    return;
  }

  long fr_clock = filter->FromClock;
  long to_clock = filter->ToClock;
  tTrack *track = song->GetTrack(filter->FromTrack);
  song->NewUndoBuffer();

  // remove selection
  if (wxMessageBox("Erase destination before generating?", "Replace", wxYES_NO) == wxYES) {
    tCmdErase erase(filter, 1);
    erase.Execute(0);
  }

  tBarInfo bar_info(song);
  bar_info.SetClock(fr_clock);
  for (int i = 0; i < n_instruments; i++)
    instruments[i]->Generate(track, fr_clock, to_clock, bar_info.TicksPerBar);
  track->Cleanup();

  event_win->Redraw();
}


void tRhythmWin::Help()
{
#ifndef VMS
  HelpInstance->LoadFile( HelpPathList.FindValidPath("jazz") );
  HelpInstance->KeywordSearch("Random rhythm generator");
#endif
}


void tRhythmWin::SliderChange(wxItem& item, wxCommandEvent& event)
{
  tRhythmWin *win = (tRhythmWin *)item.GetParent()->GetParent();
  win->Win2Instrument();
  win->OnPaint();
}



void tRhythmWin::Win2Instrument(int i)
{
  if (in_create)
    return;

  if (i < 0)
    i = act_instrument;
  if (i < 0)
    return;

  edit.steps_per_count = steps_per_count->GetValue();
  edit.count_per_bar   = count_per_bar->GetValue();
  edit.n_bars          = n_bars->GetValue();

  *instruments[i] = edit;
}

void tRhythmWin::Instrument2Win(int i)
{
  if (in_create)
    return;

  if (i < 0)
    i = act_instrument;
  if (i < 0)
    return;

  edit = *instruments[i];
  steps_per_count->SetValue(edit.steps_per_count);
  count_per_bar->SetValue(edit.count_per_bar);
  n_bars->SetValue(edit.n_bars);
  rhythm_edit->SetMeter(edit.steps_per_count, edit.count_per_bar, edit.n_bars);

  switch(edit.key)
  {
    case CONTROL_KEY:
      veloc_edit->SetLabel("ctrl value");
      break;
    default:
      veloc_edit->SetLabel("velocity");
      break;
  }
}


tRhythmWin::~tRhythmWin()
{
  for (int i = 0; i < n_instruments; i++)
    delete instruments[i];
}

void tRhythmWin::OnPaint()
{
  if (in_create)
    return;

  rhythm_edit->SetMeter(edit.steps_per_count, edit.count_per_bar, edit.n_bars);

  length_edit->OnPaint();
  veloc_edit->OnPaint();
  rhythm_edit->OnPaint();
}

ostream & operator << (ostream &os, tRhythmWin const &a)
{
  int i;
  os << 1 << endl;
  os << a.n_instruments << endl;
  for (i = 0; i < a.n_instruments; i++)
    os << *a.instruments[i];
  return os;
}

istream & operator >> (istream &is, tRhythmWin &a)
{
  int version;
  is >> version;
  if (version != 1)
  {
    wxMessageBox("Wrong file format!", "Error");
    return is;
  }

  int i, n = a.n_instruments;
  for (i = 0; i < n; i++)
  {
    a.act_instrument = 0;
    a.DelInstrument();
  }

  is >> n;
  for (i = 0; i < n; i++)
  {
    tRhythm *r = new tRhythm(0);
    is >> *r;
    a.AddInstrument(r);
  }
  return is;
}

