-- computerman is multiway user tools.
-- Computerman is Copyright (C) 2024 Manuel De Girardi ; 
--
--   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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
--
-- Date := "2024-05-11 09:44:21"
-- Version := "0.0.1a"
with Computer.Midi.Portmidi;
with Computer.Midi.Devices;
use Computer.Midi.Devices;
--with Computer.Midi.Step_Sequencer;
--use Computer.Midi.Step_Sequencer;
with Text_Io;

package body Computer.Midi.Sequencer is
      function image(Signature : in Time_Signature_Type) return String is
      
   begin      
      
      return 
	Positive'Image(Signature.Number) & '/' &
	Positive'Image(signature.Unit);
   end Image;      
   
   
   function Image(Bar_Beat : in Bar_Beat_Type) return String is
      
   begin
      
      return Positive'Image(Bar_Beat.Bar) & '.' &
	Positive'Image(Bar_Beat.Time_Number) & '.' &
	Positive'Image(Bar_Beat.Time_unit);
   end Image;
   
   procedure Next(Bar_Beat    : in out Bar_Beat_Type;
		  Bar         : in Natural;
		  Number      : in Time_Number_Type;
		  Unit        : in Time_Unit_Type) is
      
   begin
      
      if Bar_Beat.Time_Unit + 1 > Unit then
	 Bar_Beat.Time_Unit := 1;
	 if Bar_Beat.Time_Number + 1 > Number then
	    Bar_Beat.Time_Number := 1;
	    if Bar_Beat.Bar < Bar then
	       Bar_Beat.Bar := Bar_Beat.Bar + 1;
	    else
	       Bar_Beat.Bar := 1;
	    end if;
	 else
	    Bar_Beat.Time_Number := Bar_Beat.Time_Number + 1;
	 end if;
      else
	 Bar_Beat.Time_Unit := Bar_Beat.Time_Unit + 1;
      end if;      
   end Next;
   
   function Bpm_To_Duration(Bpm : Tempo_Type;
                            figure : T_figure) return Duration is
   begin
      case figure is
         when Quadruple =>
            return duration(duration(60000.0/Bpm)/16.0)/1000.0;
         when Triple =>
            return duration(duration(60000.0/Bpm)/8.0)/1000.0;
         when Double =>
            return duration(duration(60000.0/Bpm)/4.0)/1000.0;
         when Simple =>
            return duration(duration(60000.0/Bpm)/2.0)/1000.0;
         when Noire =>
            return  duration(60000.0/Bpm)/1000.0;
         When Blanche =>
            return Duration(duration(60000.0/Bpm)*2.0)/1000.0;
         when Ronde =>
            return Duration(duration(60000.0/Bpm)*4.0)/1000.0;
      end case;
   end Bpm_To_Duration;

   function To_Figure(Value : in Value_Type) return T_Figure Is
   begin
      case Value is
         when 0..18 =>
            return Quadruple;
         when 19..36 =>
            return Triple;
         when 37..54 =>
            return Double;
         when 55..72 =>
            return Simple;
         when 73..91 =>
            return Noire;
         when 92..107 =>
            return Blanche;
         when 108..127 =>
            return Ronde;
      end case;
   end To_Figure;

   use Step_Vectors;
   
   task body Mod_Sequencer_Type is
      
      
      
      type Sequence_Type is array (Positive  range <>) of Step_Vector;
      
      subtype Input_Sequence_Type is Sequence_Type(1..1024);
      
      type Sheduler_Type is array (Boolean) of Input_Sequence_Type;
                  
      
      
      Message : Long := 0;
      Verax : Boolean := False;
      Sheduled_Sequence : Sheduler_Type;
      Step_index     : Positive := 1;
      Rec_Mod_Status  : Boolean := False;      
      The_Control   : Control_Type := Null_Item;
      
      task Synth_Input_Deamon is     	    
	 entry Start;
	 entry Stop;
	 entry Halt;
      end Synth_Input_Deamon;
      
      task body Synth_Input_Deamon is
	 Message : Interfaces.C.Long;
	 End_Of_Task : Boolean := False;
	 Suspended   : Boolean := True;
      begin
	 while not End_Of_Task loop
	    loop
	       
	       select
		  accept Stop;
	       or
		  accept Halt do
		     End_Of_Task := True;
		     Suspended := True;						
		  end Halt;		  
		  exit;
	       or
		  accept Start do
		     Suspended := False;		  			
		  end Start;
		  exit;
	       end select;
	    end loop;	    	 	 
	    --Text_Io.Put_Line("Deamon input starting...");	    
	    
	    while not Suspended loop
	       
	       select
		  accept Start;
		  
	       or
		  accept Stop do			
		     Suspended := True;		  
		  end Stop;
	       or
		  
		  accept Halt do
		     End_Of_Task := True;
		     Suspended := True;			
		  end Halt;
		  
		  exit;
	       or 
		  delay 0.001; 
		  if instrument.Input_Device_Driver /= null then
		     if Rec_Mod_Status then
			select
		     
			   Instrument.Input_Device_Driver.Input_Driver.Send(Message);
			   
			   
			   --			select
			   --Synth_Input_Deamon.Send(Message);
			   --Text_Io.Put_Line("Receive message from channel :" & Channel_Type'Image(Channel_Of(Message)));		     
			   
			   --The_Control := Control_of(Message);
			   
			   --Text_Io.Put_Line(Control_Type'Image(The_Control));		     
			   
			   Sheduled_Sequence(not Verax)(Step_Index) := Step_Vectors."&"(Sheduled_Sequence(not Verax)(Step_Index), Message);
			   
			   --			or
			   --			   delay 0.05;
			   --			end select;
			or delay 0.001;
			--Put("No input message");
			end select;
		     end if;	       
		     
		     
		     
		--  	--	select
		--  	   accept Send(Message : out Long) do
		--  	      Message := Synth_Input_Deamon.Message;			      
		--  	   end Send;
		--  --	or
		--  --	   delay 0.01;
		--  --	end select;
		--    --   or
		--  --	delay 0.05;
		--    --   end select;
		  else
		     delay 0.05;
		  end if;
	       end select;
	    end loop;
	    --Text_Io.Put_Line("Deamon input stopped...");	    
	 end loop;
	 Text_Io.Put_Line("Deamon input halted...");	    
      end Synth_Input_Deamon;
      

      
      
      Quantum : Duration := 0.0;
      
      End_Of_Task : Boolean := False;
      Suspended   : Boolean := False;
      Play_Mod_Status : Boolean := False;
      
      
      
      
      
      
      
      
      
      Tempo : Tempo_Type := 120.0;
      Date      : Time := Clock;
   begin
      
      while not End_Of_Task loop
	 loop	    
	    
	    
	    select
	       accept Stop;
	    or
	       accept Reset do		  
		  for I in Sheduled_Sequence(True)'Range loop
		     Step_Vectors.Clear (Sheduled_Sequence(True)(I));
		     Step_Vectors.Clear (Sheduled_Sequence(False)(I));
		  end loop;
	       end Reset;
	    or	       
	       accept Rec(In_Rec : in Boolean) do
		  Rec_Mod_Status := In_Rec;
		  --Text_Io.Put_Line("Rec mod " & Boolean'Image(Rec_Mod_Status));
	       end Rec;		     	       
	    or
	       accept Play(In_Play : in Boolean) do
		  Play_Mod_Status := In_Play;
		  --Text_Io.Put_Line("Play mod " & Boolean'Image(Play_Mod_Status));
	       end Play;
	       
	    or
	       accept Halt do
		  End_Of_Task := True;
		  Suspended := True;
		  Synth_Input_Deamon.Halt;
	       end Halt;		  
	       exit;
	    or
	       accept Start(Tempo : in Tempo_Type; Signature : in Time_Signature_type) do
		  Quantum :=  Duration(60000.0/Float(Tempo)/16.0/1000.0);	       
		  Suspended := False;
		  Synth_Input_Deamon.Start;
	       end Start;	       	       	       
	       exit;
	    end select;
	    
	 end loop;
	 
	 
	 --Text_Io.Put_Line("Mod Sequencer starting...");	    
	 Date := Clock + Quantum;
	 while not Suspended loop	    	       
	    
	    select
	       accept Stop;		  
	       Synth_Input_Deamon.Stop;
	       exit;
	    or
	       accept Start(Tempo : in Tempo_Type; Signature : in Time_Signature_Type) do
		  Quantum :=  Duration(60000.0/Float(Tempo)/16.0/1000.0);
	       end Start;
	    or		  
	       accept Halt do
		  End_Of_Task := True;
		  Synth_Input_Deamon.Halt;	       
	       end Halt;	       	       
	       exit;		  
	    or
	       accept Rec(In_Rec : in Boolean) do
		  Rec_Mod_Status := In_Rec;
		  --Text_Io.Put_Line("Rec mod " & Boolean'Image(Rec_Mod_Status));
	       end Rec;
	       
	       
	    or
	       accept Play(In_Play : in Boolean) do
		  Play_Mod_Status := In_Play;
		  --Text_Io.Put_Line("Play mod " & Boolean'Image(Play_Mod_Status));
	       end Play;
	       
	    or
	       accept Reset do		     
		  for I in Sheduled_Sequence(True)'Range loop
		     Clear (Sheduled_Sequence(True)(I));
		     Clear (Sheduled_Sequence(False)(I));
		  end loop;
	       end Reset;
	    or delay 0.001;
	    --Put_Line("TETE -1.0");
	    end select;
	    --Put_Line("TETE 0.0");
	    if Play_Mod_Status then	     		     
	       if not Step_Vectors.Is_Empty (Sheduled_Sequence(Verax)(Step_Index)) then
		  --Put_Line("TETE 1.0");
		  for Num_Message in 1..Step_Vectors.Last_Index(Sheduled_Sequence(Verax)(Step_Index)) loop
		     --Put_Line("TETE 1.1");
		     declare
			Message : constant Long := Step_Vectors.Element(Sheduled_Sequence(Verax)(Step_Index), Num_Message);
			
		     begin
			
			--Put_Line("TETE 1.3");
			--Text_Io.Put_Line("outputing message");
			instrument.Output_Device_Driver.Output_Driver.Receive(Message);			      
			--Text_Io.Put_Line("message outputed");
		     end;
		  end loop;
	       end if;		     
	    end if;	       
	    if Step_Index + 1 > Input_Sequence_Type'Last then
	       Step_Index := 1;
	       Verax := not Verax;
	    else
	       Step_Index := Step_Index + 1;
	    end if;		     
	    
	    Date := Date + Quantum;
	    delay until Date - 0.001;
	 end loop;	 
	 --Text_Io.Put_Line("Mod Sequencer stopping...");
	 
      end loop;      
      Text_Io.Put_Line("Mod Sequencer halted.");
   end Mod_Sequencer_Type;
   
   
   task body Tempo_Sync_Type is
      Start_Time  : Time := Clock;
      Quantum     : Duration := 0.0;
      
      Tempo : Tempo_Type := 120.0;	 
      Date      : Time := Clock;
      
      End_Of_Task : Boolean := False;
      
   begin	 	 
      
      while not End_Of_Task loop
	 loop
	    
	    select	       
	       accept Start(Start_Time : in Time; Tempo : in Tempo_Type; Signature : in Time_Signature_type) do
		  Quantum :=  Duration(60000.0/Float(Tempo)/24.0/1000.0);
	       end Start;
	       exit;
	    or
	       accept Halt;
	       End_Of_Task := True;
	       exit;
	    or
	       accept Stop;
	    end select;
	 end loop;
	 Start_Time := Clock;
	 while not End_Of_Task loop
	    
	    select
	       accept Stop;
	       exit;
	    or
	       accept Halt;
	       End_Of_Task := True;
	       exit;
	    or
	       accept Start(Start_Time : in Time; Tempo : in Tempo_Type; Signature : in Time_Signature_type) do
		  Quantum :=  Duration(60000.0/Float(Tempo)/24.0/1000.0);
	       end Start;
	    or 
	       delay until Start_Time;		  
	       
	       if Instrument.Sync then
		  Instrument.Output_Device_Driver.Output_Driver.Receive(Portmidi.Pm_Message(16#F8#, 0, 0));		  
	       end if;
	       
	       Start_Time := Start_Time + Quantum;
	       
	    end select;
	    
	 end loop;
      end loop;
   end Tempo_Sync_Type;                        
   
   function Nb_Of_Instrument return Natural is
      Outputs : constant Devices_List_Access := Outputs_List;
   begin
      return Outputs'Last;
   end Nb_Of_Instrument;
      
   
   
   procedure Initialize(Master : in out Bandmaster_Record; Ignore : in Boolean) is
      Outputs : constant Devices_List_Access := Outputs_List;
      Inputs  : constant Devices_List_Access := Inputs_List;
      
      Id : Instrument_Id := 1;
      
      Ignored : Natural := 1;
      
      
   begin      
      if Ignore then
	 Ignored := 2;
      end if;
      
      
      for Output in Ignored+1..Outputs'last loop
	 
	 if Master.Orchester(Id) = null then
	    Master.Orchester(Id) := new MIDI_Instrument_Record;
	 end if;	 	 
	 Master.Orchester(Id).Output_Device_Driver :=
	   new Output_Device_Driver_Type;
	 
	 Text_Io.Put_Line("initializing output driver for device : " & 
		    Outputs_List(Output).Name.all & 
		    "instrument N°" & Instrument_Id'Image(Id));
	 
	 Initialize_Output(Master.Orchester(Id).Output_Device_Driver.all,
			   Outputs_List(Output));
	 
	 Computer.MIDI.Devices.Open(Master.Orchester(Id).Output_Device_Driver.Output.all);	 	 
	 
	 
	 Id := Id + 1;
	 
      end loop;
      
      Id := 1;
      
      for Input in Ignored+1..Inputs'last loop
	 
	 Master.Orchester(Id).Input_Device_Driver :=
	   new Input_Device_Driver_Type;	 
	 
	 Text_Io.Put_Line("initializing Input driver for device : " & 
		    Inputs_List(Input).Name.all & 
		    "instrument N°" & Instrument_Id'Image(Id));
		    
	 
	 Initialize_Input(Master.Orchester(Id).Input_Device_Driver.all,
			   Inputs_List(Input));
	 Computer.MIDI.Devices.Open(Master.Orchester(Id).Input_Device_Driver.Input.all);
	 Master.Orchester(Id).Input_Device_Driver.Input_Driver.Start;
	 
	 Id := Id + 1;
	 
      end loop;
      Master.Inst_Last := Natural(Id-1);
   end Initialize;
   
   procedure Finalize(Master : in out Bandmaster_Record) is
   begin

      for Id in Master.Orchester'Range loop

	 if Master.Orchester(Id) /= null then

	    if (Master.Orchester(Id).Input_Device_Driver /= null) and then
	      (Master.Orchester(Id).Input_Device_Driver.Input /= null) then

	       if Master.Orchester(Id).Input_Device_Driver.Input /= null then

		  Computer.MIDI.Devices.Close(Master.Orchester(Id).Input_Device_Driver.Input.all);

	       end if;
	    end if;
	 end if;
      end loop;
   end Finalize;
	    
      

   

end Computer.Midi.Sequencer ;