//| Copyright: (C) 2020-2024 Kevin Larke <contact AT larke DOT org> 
//| License: GNU GPL version 3.0 or above. See the accompanying LICENSE file.
#include "cwCommon.h"
#include "cwLog.h"
#include "cwCommonImpl.h"
#include "cwTest.h"
#include "cwMem.h"
#include "cwFile.h"
#include "cwText.h"
#include "cwObject.h"
#include "cwFileSys.h"
#include "cwAudioFile.h"
#include "cwMath.h"
#include "cwVectOps.h"
#include "cwMidi.h"
#include "cwDspTypes.h"
#include "cwDsp.h"
#include "cwAudioTransforms.h"
#include "cwWaveTableBank.h"
#include "cwWaveTableNotes.h"

cw::rc_t cw::wt_note::gen_note( wt_bank::handle_t wtbH,
                                unsigned          instr_idx,
                                unsigned          midi_pitch,
                                unsigned          velocity,
                                srate_t           srate, 
                                sample_t**        audioChA,
                                unsigned          audioChN,
                                unsigned          audioFrmN )
{
  rc_t                              rc            = kOkRC;
  unsigned                          chN           = audioChN; // output audio channels
  const wt_bank::multi_ch_wt_seq_t* mcs           = nullptr;  // wave table ptr 
  sample_t*                         aM            = nullptr;  // temp. audio buffer
  const unsigned                    kDspFrmN      = 64;       // default frame request count
  unsigned                          reqFrmN       = 0;        // count of sample frames requestdd
  unsigned                          retFrmN       = 0;        // countof sample frames returned   
  unsigned                          audioChFrmIdx = 0;        // current audio frame index into audioChA[]
  audiofile::handle_t afH;
  
  // multi-channel wave table oscillator
  struct dsp::multi_ch_wt_seq_osc::obj_str<sample_t,srate_t> osc;  

  // get the requested wave table
  if((rc = wt_bank::get_wave_table( wtbH, instr_idx, midi_pitch, velocity, mcs )) != kOkRC )
  {
    goto errLabel;
  }

  // if the wave table has fewer channels than the output audio buffer
  if( mcs->chN < chN )
    chN = mcs->chN;

  // allocate,setup and validate the expected srate of the oscillator
  if((rc = create(&osc,chN,mcs,srate)) != kOkRC )
  {
    rc = cwLogError(rc,"multi-ch-wt-seq-osc create failed.");
    goto errLabel;
  }

  // allocate a tempory audio buffer 
  aM = mem::allocZ<sample_t>(chN*kDspFrmN);

  // for each sample in the output signal
  while( retFrmN==reqFrmN && audioChFrmIdx < audioFrmN )
  {
    // calc. the count of requested output audio frames on this iteration
    reqFrmN = std::min(kDspFrmN,audioFrmN-audioChFrmIdx);

    // generate reqFrmN output samples with the oscillator
    if((rc = process(&osc, aM, chN, reqFrmN, retFrmN)) != kOkRC )
      goto errLabel;

    // copy the generated signals into the output signal
    for(unsigned i=0; i<chN; ++i)
      vop::copy(audioChA[i]+audioChFrmIdx, (const sample_t*)(aM + i*reqFrmN), retFrmN);

    // advance the output signal 
    audioChFrmIdx += retFrmN;
  }

errLabel:
  if( rc != kOkRC )
    rc = cwLogError(rc,"note generation failed.");
  
  mem::release(aM);
  destroy(&osc);
  
  return rc;
}

 
cw::rc_t cw::wt_note::gen_notes( wt_bank::handle_t wtbH,
                                 const note_t*     noteA,
                                 unsigned          noteN,
                                 srate_t           srate,
                                 sample_t**        outChA,
                                 unsigned          outChN,
                                 unsigned          outFrmN )
{
  rc_t      rc   = kOkRC;
  sample_t* chA[ outChN ];
  double    secs = 0;
  
  for(unsigned i=0; i<noteN; ++i)
  {
    
    const note_t* n           = noteA + i;

    secs += n->delta_sec;
    
    unsigned      beg_frm_idx = (unsigned)floor(secs*srate);
    unsigned      dur_frm_cnt = (unsigned)floor(n->dur_sec*srate);

    if( beg_frm_idx > outFrmN )
    {
      // TODO:
      assert(0);
    }

    if( beg_frm_idx + dur_frm_cnt > outFrmN )
    {
      // TODO:
      assert(0);
    }
    
    for(unsigned j=0; j<outChN; ++j)
      chA[j] = outChA[j] + beg_frm_idx;
    
    
    if((rc = gen_note( wtbH, n->instr_idx, n->pitch, n->velocity, srate, chA, outChN, dur_frm_cnt)) != kOkRC )
    {
      rc = cwLogError(rc,"Generate note failed on 'instr:%i pitch:%i vel:%i.",n->instr_idx, n->pitch, n->velocity);
      goto errLabel;
    }

  }

errLabel:
  cwLogInfo("Generated %6.1f seconds of audio.",secs);
  return rc;
}

cw::rc_t cw::wt_note::gen_notes( wt_bank::handle_t wtbH,
                                 const note_t*     noteA,
                                 unsigned          noteN,
                                 srate_t           srate,
                                 unsigned          audioChN,
                                 const char*       out_audio_fname,
                                 unsigned          audio_bits)
{
  rc_t rc = kOkRC;
  unsigned audioFrmN = 0;
  sample_t* audioM = nullptr;
  double max_sec = 0;
  sample_t* chA[ audioChN ];
  
  double secs = 0;
  for(unsigned i=0; i<noteN; ++i)
  {
    secs += noteA[i].delta_sec;
    
    if( secs+noteA[i].dur_sec > max_sec )
      max_sec = secs+noteA[i].dur_sec;
  }

  cwLogInfo("Allocated %i notes in  %6.1f seconds of audio.",noteN,max_sec);
  
  audioFrmN = (unsigned)ceil( max_sec * srate );
  audioM    = mem::allocZ<sample_t>( audioFrmN * audioChN );

  for(unsigned i=0; i<audioChN; ++i)
    chA[i] = audioM + (i*audioFrmN);

  if((rc = gen_notes( wtbH, noteA, noteN, srate, chA, audioChN, audioFrmN )) != kOkRC )
  {
    cwLogError(rc,"Note generation failed on '%s'.",cwStringNullGuard(out_audio_fname));
    goto errLabel;
  }

  if((rc = audiofile::writeFileFloat(  out_audio_fname, srate, audio_bits, audioFrmN, audioChN, chA)) != kOkRC )
  {
    cwLogError(rc,"Audio file write failed on '%s'.",cwStringNullGuard(out_audio_fname));
    goto errLabel;
  }
  
errLabel:
  mem::release(audioM);
  return rc;
}

cw::rc_t cw::wt_note::gen_notes( const char*       wtb_json_fname,
                                 unsigned          instr_idx,
                                 unsigned          min_pitch,
                                 unsigned          max_pitch,
                                 srate_t           srate,
                                 unsigned          audioChN,
                                 double            note_dur_sec,
                                 double            inter_note_sec,
                                 const char*       out_dir )
{
  rc_t              rc          = kOkRC;
  note_t*           noteA       = nullptr;
  char*             audio_fname = nullptr;
  wt_bank::handle_t wtbH;
  
  if( min_pitch > max_pitch )
  {
    rc = cwLogError(kInvalidArgRC,"The min pitch:%i is greater than the max pitch:%i.",min_pitch,max_pitch);
    goto errLabel;
  }

  if((rc = wt_bank::create(wtbH,2,wtb_json_fname)) != kOkRC )
  {
    goto errLabel;
  }

  if(!filesys::isDir(out_dir))
    filesys::makeDir(out_dir);
  
  for(unsigned midi_pitch=min_pitch; midi_pitch<=max_pitch; ++midi_pitch)
  {
    unsigned       velA[ midi::kMidiVelCnt ];
    unsigned       velCnt = 0;
    double         secs   = 0;
    const unsigned fnameN = 32;
    char           fname[fnameN+1];

    // get the sampled velocities for the instr_idx/midi_pitch
    if((rc = wt_bank::instr_pitch_velocities( wtbH, instr_idx, midi_pitch, velA, midi::kMidiVelCnt, velCnt )) != kOkRC )
    {
      goto errLabel;
    }

    // allocate the note records array
    noteA = mem::resizeZ<note_t>(noteA,velCnt);

    // fill in the note record array with one note for each sampled velocity
    for(unsigned i=0; i<velCnt; ++i)
    {
      noteA[i].instr_idx = instr_idx;
      noteA[i].pitch     = midi_pitch;
      noteA[i].velocity  = velA[i];
      noteA[i].delta_sec = note_dur_sec + inter_note_sec;
      noteA[i].dur_sec   = note_dur_sec;
      secs += note_dur_sec + inter_note_sec;
    }

    // form the audio file name
    snprintf(fname,fnameN,"%03i",midi_pitch);
    fname[fnameN] = 0;
    mem::release(audio_fname);
    
    audio_fname = filesys::makeFn(  out_dir, fname, "wav", nullptr );

    cwLogInfo("Generating notes for pitch:%i into '%s'.",midi_pitch,cwStringNullGuard(audio_fname));
        
    // generate the notes 
    if((rc = gen_notes( wtbH, noteA, velCnt, srate, audioChN, audio_fname )) != kOkRC )
    {
      rc = cwLogError(rc,"Note generation failed on instr:%i pitch:%i file:%s.",instr_idx,midi_pitch,cwStringNullGuard(audio_fname));
      goto errLabel;
    }
    
  }

errLabel:
  destroy(wtbH);
  mem::release(noteA);
  mem::release(audio_fname);
  return rc;
}

cw::rc_t cw::wt_note::test( const test::test_args_t& args )
{
  rc_t        rc             = kOkRC;
  const char* wtb_json_fname = "/home/kevin/temp/temp_5.json";
  unsigned    instr_idx      = 0;
  unsigned    min_pitch      = 21;
  unsigned    max_pitch      = 21;
  srate_t     srate          = 48000;
  unsigned    audioChN       = 2;
  double      note_dur_sec   = 9;
  double      inter_note_sec = 1;
  const char* out_dir        = "/home/kevin/temp/gen_note";
  
  rc = gen_notes( wtb_json_fname, instr_idx, min_pitch, max_pitch, srate, audioChN, note_dur_sec, inter_note_sec, out_dir);
  
  return rc;
}
  

