#!/usr/bin/env python
#
# Copyright (C) 2011--2013  Kipp Cannon, Chad Hanna, Drew Keppel
#
# 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.,
# 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.


from optparse import OptionParser
import sys

import pygtk
pygtk.require("2.0")
import gobject
gobject.threads_init()
import pygst
pygst.require('0.10')
import gst
import signal


from pylal import series as lalseries
from glue.ligolw import ligolw
from glue.ligolw import array
from glue.ligolw import param
from glue.ligolw import lsctables
array.use_in(ligolw.LIGOLWContentHandler)
param.use_in(ligolw.LIGOLWContentHandler)
lsctables.use_in(ligolw.LIGOLWContentHandler)
from glue.ligolw import utils
from gstlal import datasource
from gstlal import pipeparts
from gstlal import simplehandler
from glue.ligolw.utils import segments as ligolw_segments
from pylal import series as lalseries

## @file gstlal_play
# This program will play data in a variety of ways; See gstlal_play for help and usage.
#
# This program will play data in a variety of ways.  Its input is anything
# supported by datasource.py.  You can additionally whiten the data or apply a
# band pass filtering.  It can direct its output to either your sound card,
# various audio file formats, or stderr/stdout in tab delimited ASCII text.
#
# #### Graph of the gsreamer pipeline
#
# - gray boxes are optional and depend on the command line given
#
# @dot
# digraph G {
#	// graph properties
#
#	rankdir=LR;
#	compound=true;
#	node [shape=record fontsize=10 fontname="Verdana"];
#	edge [fontsize=8 fontname="Verdana"];
#
#	// nodes
#
#	"mkbasicsrc()" [URL="\ref datasource.mkbasicsrc()"];
#	"whitened_multirate_src()" [label="whitened_multirate_src()", URL="\ref multirate_datasource.mkwhitened_multirate_src()", style=filled, color=lightgrey];
#	"mkresample()" [URL="\ref pipeparts.mkresample()", style=filled, color=lightgrey];
#	"mkcapsfilter()" [URL="\ref pipeparts.mkcapsfilter()", style=filled, color=lightgrey];
#	"mkaudiochebband()" [URL="\ref pipeparts.mkaudiochebband()", style=filled, color=lightgrey];
#	"mkaudiocheblimit()" [URL="\ref pipeparts.mkaudiocheblimit()", style=filled, color=lightgrey];
#	"mkaudioconvert()" [URL="\ref pipeparts.mkaudioconvert()"];
#	"mkaudioamplify()" [URL="\ref pipeparts.mkaudioamplify()"];
#	"mkautoaudiosink()" [URL="\ref pipeparts.mkautoaudiosink()", style=filled, color=lightgrey];
#	"mkwavenc()" [URL="\ref pipeparts.mkwavenc()", style=filled, color=lightgrey];
#	"mkflacenc()" [URL="\ref pipeparts.mkflacenc()", style=filled, color=lightgrey];
#	"mkvorbisenc()" [URL="\ref pipeparts.mkvorbisenc()", style=filled, color=lightgrey];
#	"mkfilesink()" [URL="\ref pipeparts.mkfilesink()", style=filled, color=lightgrey];
#	"mknxydumpsink()" [URL="\ref pipeparts.mknxydumpsink()", style=filled, color=lightgrey];
#
#	// connections
#
#	"mkbasicsrc()" -> "mkresample()" [label=" --whiten not provided"];
#	"mkresample()" -> "mkcapsfilter()";
#	"mkcapsfilter()" -> "mkaudioconvert()" [label=" neither --low-pass-filter nor --high-pass-filter provided"];
#	"mkcapsfilter()" -> "mkaudiochebband()" [label=" --low-pass-filter and --high-pass-filter provided"];
#	"mkcapsfilter()" -> "mkaudiocheblimit()" [label=" --low-pass-filter or --high-pass-filter provided"];
#
#	"mkbasicsrc()" -> "whitened_multirate_src()" [label=" --whiten provided"];
#	"whitened_multirate_src()" -> "mkaudioconvert()" [label=" neither --low-pass-filter nor --high-pass-filter provided"];
#	"whitened_multirate_src()" -> "mkaudiochebband()" [label=" --low-pass-filter and --high-pass-filter provided"];
#	"whitened_multirate_src()" -> "mkaudiocheblimit()" [label=" --low-pass-filter or --high-pass-filter provided"];
#
#	"mkaudiochebband()" -> "mkaudioconvert()";
#	"mkaudiocheblimit()" -> "mkaudioconvert()";
#
#	"mkaudioconvert()" -> "mkaudioamplify()";
#
#	"mkaudioamplify()" -> "mkautoaudiosink()" [label=" --output not provided"];
#	"mkaudioamplify()" -> "mkwavenc()" [label=" --output ends with '.wav'"];
#	"mkaudioamplify()" -> "mkflacenc()" [label=" --output ends with '.flac'"];
#	"mkaudioamplify()" -> "mkvorbisenc()" [label=" --output ends with '.ogg'"];
#	"mkaudioamplify()" -> "mknxydumpsink()" [label=" --output ends with '.txt' or is /dev/stderr or /dev/stdout"];
#	"mkwavenc()" -> "mkfilesink()";
#	"mkvorbisenc()" -> "mkfilesink()";
#	"mkflacenc()" -> "mkfilesink()";
# }
# @enddot
#
# ### Usage cases
#
# See datasource.append_options() for additional usage cases for datasource specific command line options
#
# -# Viewing low latency data in stdout (e.g. on CIT cluster)
#	- Note ctrl+c kills this
#
#			gstlal_play --data-source framexmit --channel-name=L1=FAKE-STRAIN  --output /dev/stdout
#
# -# Pipe low latency data to an ogg file narrowing in on the sweet spot and
# add amplification to make it audible.  
#	- Note ctrl+c kills this
#
#			gstlal_play --data-source framexmit --channel-name=L1=FAKE-STRAIN  --high-pass-filter 40 --low-pass-filter 1000 --amplification 1e21 --output test.ogg
#
# -# Write injection time series from an xml file into an ASCII delimited text file
#
#			gstlal_play --data-source silence --gps-start-time 966383960 --gps-end-time 966384960 --channel-name=L1=FAKE-STRAIN  --injections=BNS-MDC1-FIXEDMASS.xml --output test.txt
#
# -# Other things are certainly possible.  Please add some!
#
# ### Command line options ###
#
# See datasource.append_options() for common command line options shared among different programs
#
#	+ `--output` [filename]: Set the filename in which to save the output.  If not given, output is sent to the default audio device.  The filename's extension determines the format, the following are recognized:  .wav, .flac, .ogg, .txt, /dev/stdout, /dev/stderr
#	+ `--rate` [int] (Hz): Downsample input to this sample rate. Default = 4096 Hz.  Must be @f$ \leq @f$ input sample rate or else you will get a caps negotiation error.
#	+ `--whiten`: Whiten the time series (default = do not whiten.
#	+ `--reference-psd` [filename]: When whitening, normalize the time series to the spectrum described in this XML file.  If this option is not given, the spectrum is measured from the data.
#	+ `--low-pass-filter` [float] (Hz): Low pass filter frequency (default = no low-pass filter).  Low-pass filter is applied after whitening.
#	+ `--high-pass-filter` [float] (Hz): High pass filter frequency (default = no high-pass filter).  High-pass filter is applied after whitening.
#	+ `--amplification` [float] (dimensionless): Amplify the timeseries this much (default = no amplification).  Amplification is applied after low- and high-pass filtering. For unwhitened h(t) that is bandpassed to the most sensitive region you might need to set this to 1e20 to make it audible
#	+ `--verbose`: Be verbose.
#


def parse_command_line():

	parser = OptionParser(description = __doc__)

	#
	# First append the datasource common options
	#

	datasource.append_options(parser)

	parser.add_option("--output", metavar = "filename", help = "Set the filename in which to save the output.  If not given, output is sent to the default audio device.  The filename's extension determines the format, the following are recognized:  .wav, .flac, .ogg, .txt, /dev/stdout, /dev/stderr")
	parser.add_option("--rate", metavar = "Hz", type = "int", default = 4096, help = "Downsample input to this sample rate. Default = 4096 Hz.  Must be <= input sample rate or else you will get a caps negotiation error.")
	parser.add_option("--whiten", action = "store_true", help = "Whiten the time series (default = do not whiten).")
	parser.add_option("--reference-psd", metavar = "filename", help = "When whitening, normalize the time series to the spectrum described in this XML file.  If this option is not given, the spectrum is measured from the data.") 
	parser.add_option("--low-pass-filter", metavar = "Hz", type = "float", help = "Low pass filter frequency (default = no low-pass filter).  Low-pass filter is applied after whitening.")
	parser.add_option("--high-pass-filter", metavar = "Hz", type = "float", help = "High pass filter frequency (default = no high-pass filter).  High-pass filter is applied after whitening.")
	parser.add_option("--amplification", metavar = "factor", type = "float", default = 1.0, help = "Amplify the timeseries this much (default = no amplification).  Amplification is applied after low- and high-pass filtering. For unwhitened h(t) that is bandpassed to the most sensitive region you might need to set this to 1e20 to make it audible")
	parser.add_option("-v", "--verbose", action = "store_true", help = "Be verbose.")

	#
	# parse the arguments and sanity check
	#

	options, filenames = parser.parse_args()

	if options.low_pass_filter is not None and options.high_pass_filter is not None:
		if options.low_pass_filter <= options.high_pass_filter:
			raise ValueError("--low-pass-filter must be > --high-pass-filter")

	if options.whiten and (options.low_pass_filter or options.high_pass_filter):
		raise ValueError("specify either whitening or filtering not both")

	if options.reference_psd and not options.whiten:
		raise ValueError("--reference-psd requires --whiten")

	if len(options.channel_name) > 1:
		raise ValueError("only one --channel-name allowed")

	return options, filenames


# parsing and setting up some core structures
options, filenames = parse_command_line()

gw_data_source_info = datasource.GWDataSourceInfo(options)
instrument, = gw_data_source_info.channel_dict

if options.reference_psd is not None:
	psd = lalseries.read_psd_xmldoc(utils.load_filename(options.reference_psd, verbose = options.verbose, contenthandler = ligolw.LIGOLWContentHandler))[instrument]
else:
	psd = None


# building the event loop and pipeline
gobject.threads_init()
mainloop = gobject.MainLoop()
pipeline = gst.Pipeline("gstlal_play_frames")
handler = simplehandler.Handler(mainloop, pipeline)

#
# the pipeline
#
# A basic src
head = datasource.mkbasicsrc(pipeline, gw_data_source_info, instrument, verbose = options.verbose)

# if whitening, leverage mkwhitened_multirate_src() otherwise just resample
if options.whiten:
	head = mkwhitened_multirate_src(pipeline, head, [options.rate], instrument, psd)[options.rate]
else:
	head = pipeparts.mkresample(pipeline, head, quality = 9)
	head = pipeparts.mkcapsfilter(pipeline, head, "audio/x-raw-float, rate=%d" % options.rate)

# handle filtering
if options.high_pass_filter is not None and options.low_pass_filter is not None:
	head = pipeparts.mkaudiochebband(pipeline, head, lower_frequency = options.high_pass_filter, upper_frequency = options.low_pass_filter)
else:
	if options.high_pass_filter is not None:
		head = pipeparts.mkaudiocheblimit(pipeline, head, cutoff = options.high_pass_filter, mode = "high-pass")
	elif options.low_pass_filter is not None:
		head = pipeparts.mkaudiocheblimit(pipeline, head, cutoff = options.low_pass_filter, mode = "low-pass")

# necessary audio convert and amplify 
head = pipeparts.mkaudioconvert(pipeline, pipeparts.mkaudioamplify(pipeline, head, options.amplification))

if options.output is None:
	pipeparts.mkautoaudiosink(pipeline, head)
elif options.output.endswith(".wav"):
	pipeparts.mkfilesink(pipeline, pipeparts.mkwavenc(pipeline, head), options.output)
elif options.output.endswith(".flac"):
	pipeparts.mkfilesink(pipeline, pipeparts.mkflacenc(pipeline, head), options.output)
elif options.output.endswith(".ogg"):
	head = pipeparts.mkoggmux(pipeline, pipeparts.mkvorbisenc(pipeline, head))
	pipeparts.mkfilesink(pipeline, head, options.output)
elif options.output.endswith(".txt") or options.output in ("/dev/stdout", "/dev/stderr"):
	pipeparts.mknxydumpsink(pipeline, head, options.output)
else:
	raise ValueError("unrecognized format for --output")

# Allow Ctrl+C or sig term to gracefully shut down the program for online
# sources, otherwise it will just kill it
if gw_data_source_info.data_source in ("lvshm", "framexmit"):# what about nds online?
	simplehandler.OneTimeSignalHandler(pipeline)

# run
if pipeline.set_state(gst.STATE_PLAYING) == gst.STATE_CHANGE_FAILURE:
	raise RuntimeError("pipeline failed to enter PLAYING state")
mainloop.run()
