#
#	post_cs2mtx.awk
#
#  -----------------------------------------------------------------------
#  Copyright 2008  Alfred Steffens Jr.
#  -----------------------------------------------------------------------
#  -----------------------------------------------------------------------
#    Copying Permission:
#
#    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.
#
#    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 (see file called "COPYING"); if not, write
#    to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
#    Boston, MA 02111-1307  USA
#  ------------------------------------------------------------------------
#
#   mscore release 1.2
#
#	Revision history:
#	1.4		11/16/2008	A. Steffens Jr.
#	1.3		11/15/2008	A. Steffens Jr.	Modification to add CONTROL types
#                                       PAN, EFFECT1, EFFECT3
#	1.2		05/10/2008	A. Steffens Jr.	Added code to fetch program change
#	1.1		03/10/2007	A. Steffens Jr.	Added code to fetch control change and
#										pitch bend
#	1.0		03/07/2007	A. Steffens Jr.	File creation.
#
#
#	usage:
#
#	cat hold.sco sep <mtx_file> | awk -f post_cs2mtx.awk  >  tmp.mtx
#
#	where
#		mtx_file = the result from cs2mtx.awk (run_cs2mtx.sh script)
#		hold.sco = the csound score that resulted from ascore.awk
#
#--------------------------------------------------------------------------
#	Notice that at least 3 files are piped to this script: the csound score
#	followed by the initial MTX file created by cs2mtx.awk.  This script is
#	carefully coded the tell the difference between the two input files, but
#	the break point between the two scores is made unique by inserting a
#	dummy line (as a single-line file) between them--the line must start
#	with #MTX.  Everything before the #MTX is found is assumed to be the
#	hold.sco file.
#--------------------------------------------------------------------------
#
#	This script adds tempo changes, taken from the csound score, and puts
#	them into the .mtx file created from cs2mtx.awk, thereby creating a
#	new .mtx file that should be used to create the midi file.
#
#	This is just a fix, because cs2mtx.awk lacks the capability to translate
#	the tempo changes on its own.
#
#
#	MIDI Control Change
#	-------------------
#	This script will gather the Control Change Midi commands that were passed
#	through from the Ascore script.  The Ascore script is to be human-readable,
#	so the syntax in Ascore is to be
#		CONTROL <type> <value> <channel>
#	example:
#		CONTROL MODWHEEL 68 3
#	where
#	the value of the modwheel is 68 (0-127) and the midi channel is 3 (0-15).
#	The type may be abbreviated with the following minimum letters:
#		MODWHEEL			MODW
#		PORTATIME			PORTAT
#		POARTASWITCH		PORTAS
#		VOLUME				VOL
#	Ascore must pass through the above parameter line and adds the absolute
#	time in the score when this event happens.  The above line will appear in
#	the csound score as
#		;CONTROL <time> MODWHEEL  68 3
#	When the control change parameter is passed into the MTX file (by this
#	script), the syntax will be
#		<deltatime> MIDI TK<number> CH<number> CT<number> VL<number>
#	example:
#		0		MIDI	TK1		CH3		CT1		VL68
#	where
#	the track number (TK1) is determined (by this script) from the file
#	run_cs2mtx.sh that was created previously using csndshell.
#
#
#	MIDI Pitch Bend
#	---------------
#	Pitch bend is handled similarly, but the syntax in the Ascore file
#	will have 1 less parameter:
#		PITCH <value> <channel>
#	where
#	the value has a range from -8191 to +8191.
#	When this parameter line is passed through by Ascore, it will appear
#	in the csound score as
#		;PITCH <time> <value> <channel>
#	This script interprets the signed value and calculates an LSB and MSB
#	which combine to an unsigned number between 0 and 0x3FFF.  The parameter
#	line will appear in the MTX script as
#		<deltatime> MIDI TK<number> CH<number> PB<number> VL<number>
#
#
#	MIDI Program Change
#	-------------------
#	Program Change is handled similarly, but the syntax in the Ascore file
#	will have 1 less parameter:
#		PROGRAM <value> <channel>
#	where
#	the value has a range from 0--127
#	When this parameter line is passed through by Ascore, it will appear
#	in the csound score as
#		;PROGRAM <time> <value> <channel>
#	When the program change parameter is passed into the MTX file (by this
#	script), the syntax will be
#		<deltatime> MIDI TK<number> CH<number> PG<number>
#	example:
#		0		MIDI	TK1		CH3		PG37
#	where
#	the track number (TK1) is determined (by this script) from the file
#	run_cs2mtx.sh that was created previously using csndshell.
#
#



BEGIN {

	total_time = 0
	curr_tempo = 0
	scoreline = 0
	got_mtx = 0
	tempo_index = 1
	tempo_count = 0
	control_index = 1
	control_count = 0
	pitch_index = 1
	pitch_count = 0
	program_index = 1
	program_count = 0
	tracks = 0
	midi_channel_count = 0
	sysex_index = 1
	sysex_count = 0
}


#
#	parsing a Csound score
#
$1 ~ /;CONTROL/ {
	#
	#	pass-through MIDI control statement in the csound score
	#

	control_count++
	if (index($3,"MODW")==1) {
		arycontrol[control_count, 1] = 1
	}
	else if (index($3,"PORTAT")==1) {
		arycontrol[control_count, 1] = 5
	}
	else if (index($3,"PORTAS")==1) {
		arycontrol[control_count, 1] = 65
	}
	else if (index($3,"VOL")==1) {
		arycontrol[control_count, 1] = 7
	}
	else if (index($3,"BANK_M")==1) {
		arycontrol[control_count, 1] = 0
	}
	else if (index($3,"BANK_L")==1) {
		arycontrol[control_count, 1] = 32
	}
	else if (index($3,"PAN")==1) {
		arycontrol[control_count, 1] = 10
	}
	else if (index($3,"YM_EF1")==1) {
		arycontrol[control_count, 1] = 91
	}
	else if (index($3,"YM_EF3")==1) {
		arycontrol[control_count, 1] = 93
	}
	else {
        # accept a numerical argument
		arycontrol[control_count, 1] = $3
	}
	arycontrol[control_count, 2] = $4  # control value
	arycontrol[control_count, 3] = $2  # at this absolute time
	arycontrol[control_count, 4] = $5  # channel
	next
}


#
#	parsing a Csound score
#
$1 ~ /;PITCH/ {
	#
	#	pass-through MIDI pitch bend statement in the csound score
	#
	pitch_count++
	uvalue = $3 + 8191
	abtime =$2 
	msbyte = int(uvalue / 128)
	lsbyte = uvalue - msbyte
	arypitch[pitch_count, 1] = lsbyte
	arypitch[pitch_count, 2] = msbyte
	arypitch[pitch_count, 3] = abtime
	arypitch[pitch_count, 4] = $4 # channel
	next
}



#
#	parsing a Csound score
#
$1 ~ /;PROGRAM/ {
	#
	#	pass-through MIDI control statement in the csound score
	#

	program_count++
	aryprogram[program_count, 2] = $3  # control value
	aryprogram[program_count, 3] = $2  # at this absolute time
	aryprogram[program_count, 4] = $4  # channel
	next
}


#
#	parsing a Csound score
#
$1 ~ /;SYSEX/ {
	#
	#	pass-through MIDI control statement in the csound score
	#

    #
    #   we expect a syntax of the form
    #
    #       ;SYSEX <scoretime> <ch> d0 d1 ... dn
    #

	sysex_count++
	arysysex[sysex_count, 1] = $2  # at this absolute time
	arysysex[sysex_count, 2] = $3  # midi track (arbitrary)
	arysysex[sysex_count, 3] = $3  # midi channel (arbitrary)
    for (sj = 4; sj <= NF; sj++)
    {
        # data bytes
	    arysysex[sysex_count, sj] = $sj
    }

	next
}


#
#	parsing a Csound score
#
$1 ~ /;TEMPO/ {
	#
	#	pass-through MIDI pitch bend statement in the csound score
	#
	tempo_count++
	arytempo[tempo_count, 1] = $3	# new tempo value
	arytempo[tempo_count, 2] = $2  # at this absolute time
	next
}


#
#	parsing a Csound score
#
$1 ~ /;CS2MTX/ {

    #
    #   This is a duplication of code from pre_cs2mtx.awk, but this is
	#	the easier way the get the track numbers into this script
    #
    #
    if (index($2, "midi_channel") == 1) {

		if (NF >= 3) {
			chn = $3
			if (exists_midi_channel(chn)) {
			}
			else {
				midi_channel_count++
            	midi_channel_number[midi_channel_count] = chn
			} # else
		}

	} # if
	next
}


#
#	parsing a Csound score
#
$1 ~ /[fi]\.*/ {

	#
	#	instrument line or ftable line in csound score
	#
	next
}

#
#	parsing a Csound score
#
$1 ~ /;\.*/ {

	#
	#	default comment line in the csound score
	#
	next
}



#
#---------------------------------------------------------------------------
#


$1 ~ /#MTX\.*/ {

	#
	#	in order for this to work, the first line of the MTX file MUST
	#	begin with a "#" character
	#
	got_mtx = 1
	next
}


#
#	parsing a MTX file
#
$1 ~ /TRACKS/ {

	if (got_mtx == 0) {
		#
		#	in order for this to work, the first line of the MTX file MUST
		#	begin with a "#" character
		#
		next
	}

	#
	#	from the MTX file
	#	this is the number of tracks specified in the .mtx file header
	#
	tracks = $2

	if (tracks != midi_channel_count) {
		print "ERROR: post_cs2mtx.awk: the csound score and the MTX file do not agree on track count" | "1>&2"
		exit 1
	}
}




#
#	parsing a MTX file
#
$4 ~ /TEMPO/ {

	if (got_mtx == 0) {
		#
		#	in order for this to work, the first line of the MTX file MUST
		#	begin with a "#" character
		#
		next
	}

	#
	#	from the MTX file
	#	this is read from the .mtx file header in order to provide an initial
	#	tempo for counting the total score time
	#


	#
	#	only read the first one
	#
	if (curr_tempo == 0) {
		#
		#	these should all be found at the beginning of the MTX file
		#
		curr_tempo = $5
	}
	print $0
	scoreline++
	next
}



#
#	parsing a MTX file
#
#
#	read a score time
#
$1 ~ /([0-9]*)(\.?)([0-9]*)([\!\*qho]*)/ {

	if (got_mtx == 0) {
		#
		#	in order for this to work, the first line of the MTX file MUST
		#	begin with a "#" character
		#
		next
	}
	#
	#
	#	from the MTX file
	#
	#
	#	find a timing symbol, if any, in the first column
	#	we are adding up each score line duration to get the total score time
	#	at this line
	#
	#	Each entry in the first column is of the form
	#
	#		<decimal number><duration symbol>
	#
	#	For example,
	#
	#		1.0003q  means 1.0003 multiplied by the duration of a quarter
	#		note (the "q").
	#		0.0123!  means 0.0123 multiplied by the duration of a 16th
	#		note (the "!").
	#		0 means zero seconds
	#
	#
	npos = match($1, /[\!\*qho]/)
	if (npos) {

		if (npos > 1) {
			tratio = substr($1, 1, npos-1)  # number part
			tsym   = substr($1, npos, 1)
		}
		else {
			tratio = 1.0
			tsym   = substr($1, 1, 1)
		}

		if (curr_tempo > 0) {

			beats_per_second = curr_tempo / 60.0
			if (index(tsym, "!")) {
				tdur = 1.0 / (4.0 * beats_per_second)
			}
			else if (index(tsym, "*")) {
				tdur = 1.0 / (2.0 * beats_per_second)
			}
			else if (index(tsym, "q")) {
				tdur = 1.0 / beats_per_second
			}
			else if (index(tsym, "h")) {
				tdur = 2.0 / beats_per_second
			}
			else if (index(tsym, "o")) {
				tdur = 4.0 / beats_per_second
			}
			else {
				print "ERROR: post_cs2mtx.awk: unknown duration symbol" | "1>&2"
				exit 1
			}

			total_time += (tdur * tratio)

		} # if -- curr_tempo > 0

	}
	else {

		total_time += $1

	}

	#
	#	does a TEMPO go here?
	#	we look through the array of TEMPO statements that were found in the
	#	csound score
	#
	for (i=tempo_index; i <= tempo_count; i++) {
		if ((i,2) in arytempo) {
			if (arytempo[i,2] <= total_time) {
				#
				#	print a tempo line in the MTX file
				#
				if (tracks > 0 && total_time > 0.0) {
					for (j=1; j<=tracks; j++) {
						#
						#	set the tempo for every MIDI track
						#
						print "0	META	TK" j "		TEMPO	" arytempo[i,1]
					}
					curr_tempo = arytempo[i,1]
				}
			}
			else {
				break;
			}
		}
	}
	tempo_index = i;


	#
	#	does a CONTROL go here?
	#	we look through the array of CONTROL statements that were found in the
	#	csound score
	#
	for (i=control_index; i <= control_count; i++) {
		if ((i,3) in arycontrol) {
			if (arycontrol[i,3] <= total_time) {
				#
				#	print a control line in the MTX file
				#
				if (tracks > 0 && total_time > 0.0) {
					for (j=1; j<=tracks; j++) {
						# match the channel
						if (j in midi_channel_number) {
							if (arycontrol[i,4] == midi_channel_number[j]) {
								print "0    MIDI    TK" j "    CH" arycontrol[i,4]  "    CT" arycontrol[i,1] "    VL" arycontrol[i,2]
								break
							}
						}
					}
				}
			}
			else {
				break;
			}
		}
	}
	control_index = i;


	#
	#	does a PITCH BEND go here?
	#	we look through the array of PITCH statements that were found in the
	#	csound score
	#
	for (i=pitch_index; i <= pitch_count; i++) {
		if ((i,3) in arypitch) {
			if (arypitch[i,3] <= total_time) {
				#
				#	print a pitch line in the MTX file
				#
				if (tracks > 0 && total_time > 0.0) {
					for (j=1; j<=tracks; j++) {
						# match the channel
						if (j in midi_channel_number) {
							if (arypitch[i,4] == midi_channel_number[j]) {
								print "0    MIDI    TK" j "    CH" arypitch[i,4]  "    PB" arypitch[i,1] "    VL" arypitch[i,2]
								break
							}
						}
					}
				}
			}
			else {
				break;
			}
		}
	}
	pitch_index = i;


	#
	#	does a PROGRAM go here?
	#	we look through the array of PROGRAM statements that were found in the
	#	csound score
	#
	for (i=program_index; i <= program_count; i++) {
		if ((i,3) in aryprogram) {
			if (aryprogram[i,3] <= total_time) {
				#
				#	print a program line in the MTX file
				#
				if (tracks > 0 && total_time > 0.0) {
					for (j=1; j<=tracks; j++) {
						# match the channel
						if (j in midi_channel_number) {
							if (aryprogram[i,4] == midi_channel_number[j]) {
								print "0    MIDI    TK" j "    CH" aryprogram[i,4]  "    PG" aryprogram[i,2]
								break
							}
						}
					}
				}
			}
			else {
				break;
			}
		}
	}
	program_index = i;


	#
	#	does a SYSEX go here?
	#	we look through the array of SYSEX statements that were found in the
	#	csound score
    #
    #   we print to the MTX file the following format:
    #
    #       0   SYSEX  TR1  CH4   D0 D1 ... Dn
	#
	for (i=sysex_index; i <= sysex_count; i++) {
		if ((i,1) in arysysex) {
			if (arysysex[i,1] <= total_time) {
				#
				#	print a sysex line in the MTX file
				#
                sysexline = "0   SYSEX"
                # track num
                sysexline = sprintf("%s TR%d", sysexline, arysysex[i,2])
                # ch num
                sysexline = sprintf("%s CH%d", sysexline, arysysex[i,3])
                sj = 4
                while ((i,sj) in arysysex)
                {
                    sysexline = sprintf("%s %s", sysexline, arysysex[i,sj])
                    sj++
                }
                print sysexline
			}
			else {
				break;
			}
		}
	}
	sysex_index = i;

	print $0
	scoreline++
	next
}



#
#	parsing a MTX file
#
#
#	any other line (of the MTX file)
#
{
	if (got_mtx == 0) {
		#
		#	in order for this to work, the first line of the MTX file MUST
		#	begin with a "#" character
		#
		next
	}

	print $0
	scoreline++
	next
}




END {


}
#------------------------------------- END ---------------------------------



function exists_midi_channel(chn,     i)
{
    for (i=1; i<=midi_channel_count; i++) {

        if (i in midi_channel_number) {

            if (midi_channel_number[i] == chn) {
                return 1
            } # if -- matches this channel number

        } # if -- this array element exists

    } # for -- midi_channel_count

    return 0
}
#----------------------------- exists_midi_channel -----------------------


