#################################
# The File Module
#
# Part of the SharpeSound Editor
# by Paul D. Sharpe
# A 3rd Year Project at the University of Leeds;
# Department of Electronic and Electrical Engineering.
# Date: 28/2/95
#
# Revision:3
# Date: 11/4/95

# The callback procedures to the file menu
 

###################################################################
# New
# This procedure is the callback to the File-New menu item.
# It sets and resets sound studio to its initial state, by
# setting globals to their default values and clearing the editing plot.
# The parameter option has two acceptable values;
# 1. init ; this is the setting function. It excludes those operations
#	    which cannot be done until Sound Studio is set-up properly.
# 2. any ; this is the default value, and is essentially the resetting 
#	   function.
# Return value : A boolean parameter indicating success. 
# (There is an option to abort if the previous sample was not saved.

proc New {{option any}} {
   global PLAY SAMPLE INFO FLAG PLOT FILE DialogCfg LOAD

   # The following is not required upon initialisation.
   if {$option!="init"} {

      # Make User aware that the previous sample was not saved.
      if {$FLAG(Edited)} {
	  # Generate a dialog box.
	  set opt [dialog .exit "" [format \
		"%s\n has changed.\n Do you want to discard changes?"\
		$FILE(CURR)]\
	  	$DialogCfg warning 1 "Discard Changes" "Cancel"]

	  # Check the User's response to the dialog box.
	  # (Return if cancelled)	
	  if {$opt} { return 0 }
      }
   }

   # This is reset in all cases.
   # Reset Variables
   # Set Initial SAMPLING parameters
   set SAMPLE(rate) 8000
   set SAMPLE(bit) 8
   set SAMPLE(chnl) 1
   set SAMPLE(Size) 0
   set SAMPLE(size) b
   set SAMPLE(format) raw
   set SAMPLE(style) "u"

   # Set initial LOAD parameters.
   set LOAD(maxvol) 2
   set LOAD(rate) 8000
   set LOAD(bit) 8
   set LOAD(chnl) 1
   set LOAD(style) u
   set LOAD(size) b
   set LOAD(format) raw


   #Set Other Globals to initial values
   set FILE(CURR)		Untitled

   # Update InfoBar
   InfoFill

   # Reset status flags.
   set FLAG(Loaded) 		0
   set FLAG(Edited)		0
   set FLAG(Playing)		0
   set FLAG(Recording) 		0
   set FLAG(Marked)		0
   set FLAG(Edited)		0
   set FLAG(Converted)		0
   set FLAG(Card_Exist)		1
   # Reset the estimated play time.
   set PLAY(Time)		0

   # Reset the editing plot parameters.
   set PLOT(Startx)		0
   set PLOT(Endx)		0
   set PLOT(Blocksize)		0

   # The following is not required upon initialisation.
   if {$option!="init"} {

	# Delete previous sample
	catch {exec rm $FILE(TMP1)}
	catch {exec rm $FILE(TMP3)}
	catch {exec rm $FILE(TMP4)}
	catch {exec rm $FILE(TMP5)}

	# Reset the Zoom
	Zoom init

	# Set the play panel button states.
	PP_ButtonAble

	# Empty Plot Canvas
	FillPlot delete
   
   # The following is only done upon initialisation.
   } else {

	# Set the clipboard status flag. 
	# (In case of a common clipboard)
	set FLAG(Clip) [expr [file exists $FILE(TMP2)]]

	# Set traces to adjust states of menu items.
	trace variable FLAG(Loaded) w FileMenuStates
	trace variable FLAG(Edited) w EditMenuStates
	trace variable FLAG(Marked) w EditMenuStates
	trace variable FLAG(Clip) w EditMenuStates
   }

   # Return notice that the new was successful.
   return 1
}




###################################################################
# Load
# This procedure is the callback to the File-Load menu item.
# It produces a dialog box to allow the user to enter a file name
# of a sample file, if a filename has not already been specified in the
# parameter "loadfile". Load then converts this file to a raw data file,
# extracting the sampling parameters from the file. The raw data file
# is stored in the file whose name given by the parameter "file".
# The default file is the working file (TMP1). 
proc Load {{file default} {loadfile ""}} {
   global ConfigSet FLAG PLOT  FILE INFO SAMPLE CARD LOAD
   # Some initial settings 
   set info {}
   set temp [split $ConfigSet(FNT_L) -]
   set fontname [lindex $temp 2 ]

   # Generate a file browsing dialog box.
   if ([string length $loadfile]==0) {
     catch {set loadfile \
	[FileBrowser "Load File" \
	  [list $ConfigSet(cBG1) $ConfigSet(cFG1)\
	   $ConfigSet(cABG) $ConfigSet(cAFG) $fontname] \
	  {{*.wav *.voc *.au *.aiff *.hcom *.raw *.sf *.cdr *.dat *.smp} {*} }\
	  { {All Sound Files} {All Files} } ]}
   }
   # Indicate that there will be a delay.			
   WaitPlease

   # The following action requires a file name to have been returned.
   if {$loadfile!=""} {
      # In the default case set the file to be the working file.
      if {$file=="default"} { 
	set file $FILE(TMP1)

	# Reset Values, only continue if New operation is successful.
	if {![New]} {
		# End delay indication.
		WaitPlease 0; 
		return
	}
      }

      # Get information about the sample file from conversion file.
      exec $FILE(Sox) -V -t .auto $loadfile -e stat >& $FILE(TMP3)

      # The follwing action is only taken if the file is the working file.	
      if {$file==$FILE(TMP1)} { 
	# Get information about the sample
	set info [parse_sox $FILE(TMP3)]
 
	# Store information into globals.
	# Sampling rate.
	set SAMPLE(rate) 	[lindex $info 0]
	# Bit resolution.
	set SAMPLE(bit) 	[lindex $info 1]
	set SAMPLE(size)	[lindex [Bit2SoxSize $SAMPLE(bit)] 0]
	# Channels.
	set SAMPLE(chnl) 	[lindex $info 2]
	# The maximum volume factor.
	set LOAD(maxvol) 	[lindex $info 3]
	# The style of the data.
	set SAMPLE(style) 	[lindex [lindex $info 4] 0]
	# The file format (eg. wav, voc)
	set SAMPLE(format) 	[lindex $info 5]
	# The file name of the current file.
	set FILE(CURR) 	$loadfile

	# Update InfoBar.
	InfoFill

	# Convert SAMPLE parameters to those playable by card
	if {$FLAG(Card_Exist)} {

	   # Set the Bit resolution to the nearest one available.
	   if {[lsearch $CARD(Bits) $SAMPLE(bit)]==-1} {
		# Change the Bit resolution.
		set LOAD(bit) [lindex $CARD(Bits) 0]
		set temp [Bit2SoxSize $LOAD(bit) ]
		set LOAD(size) [lindex $temp 0]
		set LOAD(style) [lindex $temp 1]
		
		# Set status flag to show that conversion was necessary.
		set FLAG(Converted) 1
	   } else { 
		# Keep the Bit resolution.
		set LOAD(bit) $SAMPLE(bit)
		set LOAD(size) [lindex [Bit2SoxSize $SAMPLE(bit) ] 0]
		set LOAD(style) [lindex [lindex $info 4] 1]
	   }

	   # Convert the number of channels to the nearest channels.	
	   if {[lsearch $CARD(Chnnls) $SAMPLE(chnl)]==-1} {
		# Average the channels to the nearest available.
		set LOAD(chnl) [lindex $CARD(Chnnls) 0]

		# Set status flag to show that conversion was necessary.
		set FLAG(Converted) 1
	   } else {
		# Keep the channels.
		set LOAD(chnl) $SAMPLE(chnl) 
	   }

	   # Convert the sampling rate to the nearest sampling rate.
	   set index [expr $SAMPLE(chnl) -1]
	   if {	$SAMPLE(rate)> [lindex $CARD(Maxrates) $index]} {
		# Convert rate to maximum available rate.
		set LOAD(rate) [lindex $CARD(Maxrates) $index]

		# Set status flag to show that conversion was necessary.
		set FLAG(Converted) 1
	   } elseif {$SAMPLE(rate)< [lindex $CARD(Minrates) $index]} {
		# Convert rate to the minimum avaliable.
		set LOAD(rate) [lindex $CARD(Minrates) $index]

		# Set status flag to show that conversion was necessary.
		set FLAG(Converted) 1
	   } else {
		# Keep the sampling rate.
		set LOAD(rate) $SAMPLE(rate)
	   }

	# If there is no sound card keep the parameters.
	} else {
	
	   foreach name [array names SAMPLE] {
		set LOAD($name) $SAMPLE($name)
		puts "$name :$LOAD($name)"
	   }
	}

	
	# Perform the convertion of the sample file to raw format.
	exec $FILE(Sox) -V -t .auto $loadfile \
		-t .raw -$LOAD(size) -$LOAD(style)\
		-r $LOAD(rate) -c $LOAD(chnl) $file rate >& $FILE(TMP3)

	# Plot into the Canvas
	FillPlot init

   # The file is not the working file do the following:
   } else {
	# Convert the file to the parameters of the working file 
	# and store in "file".	
	exec $FILE(Sox) -V -t .auto $loadfile \
		-t .raw -c $LOAD(chnl)\
		 -r $LOAD(rate) -$LOAD(size) -$LOAD(style)\
		 $file rate >& $FILE(TMP3)
	
   }
   # Set Loaded status flag.
	set FLAG(Loaded) 1
	# Enable playpanel buttons.
	PP_ButtonAble	
    }
  # End delay indication.
  WaitPlease 0
} 

###################################################################
# Bit2SoxSize
# Used in the Load procedure.
# This function converts the bit resolution into a list of letters.
# These letters represent the size and style of the data for the 
# conversion utility (Sox).
# The parameter option is a boolean indicating the direction of the
# convertion. By default a bit resloution is converted to a list.
# The alternate conversion is a letter indicating the size (eg. b for 
# bytes) and returning the bit resolution (eg. 8).
#
# BUGS: This is set up based on the way data would be expected from
# the sound card, for example an 8 bit recording would be in unsigned 
# bytes. There may be cases where this is not true especially in 
# converting different file formats.
proc Bit2SoxSize { bit {option 1} } {
   if {$option } {
	switch $bit {
		8  { set size "b u"}
		16 { set size "w s"}
		32 { set size "l s"}
		default {set size ""}
	}
   } else {
	switch $bit {
		b  { set size 8}
		w { set size 16}
		l { set size 32}
		default {set  size ""}
	}

   }
   return $size
}

###################################################################
# parse_sox
# This function is used in the Load procedure.
# It extracts the sample parameters from a file which contains
# the verbose (-V stat) output of a sox conversion.
# It uses the system command grep and awk, to locate the relevant information.
# RETURNS: a list containing 0.bit resolution, 1.channels, 2.sampling rate,
# 3.the volume adjustment factor for sox volume conversions, 4.size and
# 5.format.
#
# BUGS: Since this is attempting to parse a file that has no real structure,
# there may be occasion when this may fail. It is robust however for
# 8,16 bit, mono, stereo, wav,voc,au,raw conversions. (Tested).
proc parse_sox { filename } {
 
   # Extract sampling rate.
   set rate [exec grep rate $filename | awk {$NF!=prev {print $NF; prev=$NF}}]

   # Extract the line containing channel, style and size information.
   set line  [exec grep channel $filename ]
   # Extract the channels.     
   set chnnl [lindex $line [expr [lsearch $line channel]-1]]
   # Extract the style.
   set style [string trim [lindex $line [expr [lsearch $line style]+1]] ,]
   # Extract the sample size.
   set size [string trim [lindex $line [expr [lsearch $line size]+1]] ,]	

   # Extract the maximum volume factor.	
   set maxvol [exec grep adjustment $filename | awk {{print $3}}]

   # Extract the format.
   set format [exec grep AUTO $filename | awk {prev!=1 {print $NF; prev=1}}]

   # Convert sample size to a bit resolution
   switch -glob $size {

	b* { set bit 8 }
	w* { set bit 16 }
	s* { set bit 16 }
	default { set bit {} }

   }

   # Convert syles into the parameters for sox.
   # The first in list is the actual value, 
   # the second is the value to be used by the working file.
   switch -glob $style {
	unsigned { set style {u u}}
	signed	 { set style {s s}}
	u-law	 { set style {U u}}
	a-law	 { set style {a u}}
   }

   # Return the list of information.
   set list "$rate $bit $chnnl $maxvol {$style} $format"
   return $list
}


###################################################################
# Save
# This procedure is the callback to the File-Save menu item.
# It converts the working file back into the loaded format.
# In the case of a new file the 'Convert' procedure is called.
proc Save {} {
   global FILE FLAG SAMPLE

   # Proceed only if the file has been edited.
   if {$FLAG(Edited)} {
      if {$FILE(CURR)!="Untitled"} {
      	# Save the file
      	Saver $FILE(CURR) $SAMPLE(format)
      } else {
	# The File needs a name: call the convert subroutine
	Convert
      }	
   }
}


###################################################################
# Saver
# This procedure simply does the work that is common to the 
# Save and Convert procedures.
# The parameters are the things that vary between the two procedures:
# filename :the name of the output file.
# format : the output format.
proc Saver {filename format } {
   global FLAG SAMPLE FILE LOAD DialogCfg

   # Check that the number of channels are not to be increased.
   if {($SAMPLE(chnl)>=$LOAD(chnl))} {
	set chnl $LOAD(chnl)
   } else {
	set chnl $SAMPLE(chnl)
   }

   # Perform conversion and save in the file given by filename.
   catch {exec $FILE(Sox) -t raw \
		-r $LOAD(rate) \
		-$LOAD(style) -$LOAD(size) -c $LOAD(chnl) $FILE(TMP1)\
		 -t .$SAMPLE(style)$SAMPLE(size) -c $chnl\
		- copy |\
		$FILE(Sox) -t .$SAMPLE(style)$SAMPLE(size) - -t .$format\
		-$SAMPLE(style) -r $SAMPLE(rate) -c $chnl $filename rate} err
puts $err
   # Update Edited status flag.
   set FLAG(Edited) 0
}


###################################################################
# Convert
# This procedure is the callback to the File-Save As menu item.
# It produces a dialog box which allows the user to enter a filename
# and specify a file format for the sample to be converted into.
proc Convert {} {
   global FILE SAMPLE FLAG ConfigSet

   # Proceed only if there are sample to be saved.
   if {$FLAG(Loaded)} {
      	# Get a filename and the desired format from User
	# Create a list of the available sample file formats.
	# (For the call to FileBrowser2)
	set ConversionList { "Convert to:" 5 5\
			aiff au cdr dat hcom raw sf smp voc wav }
	# Get the font family for the configuration list of FileBrowser2.
	set temp [split $ConfigSet(FNT_L) -]
   	set fontname [lindex $temp 2 ]
	# Call FileBrowser2.
	set result [FileBrowser2 {Save File} \
			[list $ConfigSet(cBG1) $ConfigSet(cFG1)\
	   		  $ConfigSet(cABG) $ConfigSet(cAFG) $fontname] {}\
			 "[file tail $FILE(CURR)]" $ConversionList]

	# Store the results of the FileBrowser.
	set filename [lindex $result 0]
	set format [lindex $result 1]

	# The following requires a file name.
	if {$filename!=""} {
	   	# If no format was selected use the samples own.
		if {$format==""} { set format $SAMPLE(format) }

		# Convert and save the file.
		Saver $filename $format

		# Set the current sample format to the new format.
		set SAMPLE(format) $format

		# Set the current sample name to the new filename.
		set FILE(CURR) $filename
	}
	InfoFill
   }
}


###################################################################
# Info
# This procedure is the callback to File-Information menu item.
# It generates a pop-up window containing sample and card information.
proc Info {} {
   global DialogCfg INFO FILE SAMPLE CARD FLAG
  
   # Create the lines of information.
   # Hardware Types
   set count 0
   set HW "\t"
   while {[info exists CARD(HW$count)]} {
	set HW "$HW$CARD(HW$count)\n\t"
	incr count
   }
   # Card Maximum Sampling rates.
   set list {Mono Stereo {} Quad}
   set count 0
   set MaxRate ""
   set insert "\t"
   foreach rate $CARD(Maxrates) {
	set MaxRate "$MaxRate$insert\([lindex $list $count]) $rate"
	set insert "\n\t\t\t"
	incr count
   }
   # Card Minimum Sampling rates.
   set count 0
   set MinRate ""
   set insert "\t"
   foreach rate $CARD(Minrates) {
	set MinRate "$MinRate$insert\([lindex $list $count]) $rate"
	set insert "\n\t\t\t"
	incr count
   }

   # Create a line indicating that the file has been converted.
   if {$FLAG(Converted)} {
	set convert "This Sample has been coverted to play on this card"
   } else {set convert ""}

   # Generate the Pop-up.
   set opt [dialog .info "File Information" \
	[format "\
		SAMPLE INFORMATION\n\n\
		Filename:\t\t$INFO(Sample)\n\
		Length:\t\t\t$INFO(Length)\n\
	  	Sample Rate:\t\t$SAMPLE(rate)\n\
		Bits per Sample:\t\t$SAMPLE(bit)\n\
		Channels:\t\t$SAMPLE(chnl) ($INFO(Stereo))\n\
		Format:\t\t\t$SAMPLE(format)\n\n\
		$convert\n\n\
		CARD INFORMATION\n\n\
		Bit Resolutions:\t\t[join $CARD(Bits) ,]\n\
		Possible Channels:\t[join $CARD(Chnnls) ,]\n\
		Min. Sampling Rate:$MinRate\n\
		Max. Sampling Rate:$MaxRate\n\n\
		DRIVER INFORMATION\t\n\n\
		Driver Version:\t\t$CARD(Driver)\n\
		Hardware:\n\
		$HW\n\
		 "]\
	$DialogCfg info 0 "OK" ]
}



###################################################################
# Exit
# This procedure is the callback to the File-Exit menu item and
# and the SAVE_YOURSELF and DELETE_WINDOW Window manager protocols. 
# It terminates the current session of Sound Studio, allowing the user
# to save edited data and removing the temporary files.
proc Exit {} {
   global INFO DialogCfg FLAG PID

   # Query user about edited data.	
   if {$FLAG(Edited)} {
	# Generate a dialog box.
	set opt [dialog .exit "Exit" \
		[format "\
			$INFO(Sample)\n\
			has changed.\n\
			Do you want to save it?"\
		]\
		$DialogCfg warning 1 "Exit" "Save and Exit" "Cancel" ]

	#Check the User's response to the dialog box.
	switch $opt {
		0 {	
			set FLAG(Edited) 0
		  }
		1 {
			Save
		  }
		2 {
			return
		  }
	}
   }
	# Remove the temporary files.
	catch {eval exec rm [glob /tmp/SoundStudio.tmp$PID*]}
	# Destroy the window.
	catch {destroy .}
}

###################################################################
# FileMenuStates
# This procedure is the callback to variable traces on file status flags.
# It enables and disables the relevant parts of the File and Edit menu according
# to the state of the Loaded Flag. The traces are 
# defined in the "New" procedure of the file module. 
# This procedure is called each time the flags are written to.
# The parameters are not used, but are required as a trace callback.
proc FileMenuStates {var w op} {
   global PLOT FLAG

   if {$FLAG(Loaded)} {
	MenuItemDisable .menubar.file.menu Save 0
	MenuItemDisable .menubar.file.menu "Save As" 0
	MenuItemDisable .menubar.edit.menu "Mark Range" 0

   } else {
	MenuItemDisable .menubar.file.menu Save 1
	MenuItemDisable .menubar.file.menu "Save As" 1
	MenuItemDisable .menubar.edit.menu "Mark Range" 1
   }
}
