#!/usr/bin/python3.13
#
# 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

from ligo.lw import lsctables, ligolw
from ligo.lw import utils as ligolw_utils
from ligo.lw.utils import segments as ligolw_segments

from gstlal import datasource
from gstlal.psd import read_psd
from gstlal.stream import Stream

### This program will play data in a variety of ways
###
### This program will play data in a variety of ways.  Its input is anything
### supported by :any:`datasource`.  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
###
### .. graphviz::
###
###    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()";
###   }
###
### Usage cases
### -----------
###
### See :py:func:`datasource.append_options` for additional usage cases for datasource specific command line options
###
### 1. 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
###
### 2. 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
###
### 3. 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
###
### 4. Other things are certainly possible.  Please add some!
###


@lsctables.use_in
class LIGOLWContentHandler(ligolw.LIGOLWContentHandler):
	pass


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("--sample-format", metavar = "name", help = "Force a specific sample format for the output.  If not specified, the format is chosen by auto-negotiation with the encoder.  Allowed values are any GStreamer-recognized format that is compatible with the requested encoder.  Examples include \"F32LE\", \"F64LE\".")
	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("--veto-segments-file", metavar = "filename", help = "Set the name of the LIGO light-weight XML file from which to load vetoes (optional). Must coincide with --whiten")
	parser.add_option("--veto-segments-name", metavar = "name", help = "Set the name of the segments to extract from the segment tables and use as the veto list.  Must coincide with --whiten", default = "vetoes")
	parser.add_option("--reference-psd", metavar = "file", 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 = "num", 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.reference_psd or options.veto_segments_file) and not options.whiten:
		raise ValueError("--reference-psd and --veto-segments-file 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.DataSourceInfo.from_optparse(options)
instrument, = gw_data_source_info.channel_dict

if options.reference_psd is not None:
	psd = read_psd(options.reference_psd, verbose = options.verbose)[instrument]
else:
	psd = None

if options.veto_segments_file is not None:
	veto_segments = ligolw_segments.segmenttable_get_by_name(ligolw_utils.load_filename(options.veto_segments_file, verbose = options.verbose, contenthandler = LIGOLWContentHandler), options.veto_segments_name).coalesce()[instrument]
else:
	veto_segments = None

#
# the pipeline
#
# A basic src
stream = Stream.from_datasource(gw_data_source_info, instrument, name="gstlal_play", verbose=options.verbose)

# if not whitening, just resample
if options.whiten:
	stream = stream.condition(options.rate, instrument, psd=psd, track_psd=True, veto_segments=veto_segments)
else:
	stream = stream.resample(quality=9).capsfilter(f"audio/x-raw, rate={options.rate:d}")

# handle filtering
if options.high_pass_filter is not None and options.low_pass_filter is not None:
	stream = stream.audiochebband(lower_frequency=options.high_pass_filter, upper_frequency=options.low_pass_filter)
elif options.high_pass_filter is not None:
	stream = stream.audiocheblimit(cutoff=options.high_pass_filter, mode="high-pass")
elif options.low_pass_filter is not None:
	stream = stream.audiocheblimit(cutoff=options.low_pass_filter, mode="low-pass")

# necessary audio convert and amplify 
stream = stream.audioamplify(options.amplification).audioconvert()
if options.sample_format is not None:
	stream = stream.capsfilter(f"audio/x-raw, format={options.sample_format}")

if options.output is None:
	stream.autoaudiosink()
elif options.output.endswith(".wav"):
	stream.wavenc().filesink(options.output)
elif options.output.endswith(".flac"):
	stream.flacenc().filesink(options.output)
elif options.output.endswith(".ogg"):
	stream.vorbisenc().oggmux().filesink(options.output)
elif options.output.endswith(".txt") or options.output in ("/dev/stdout", "/dev/stderr"):
	stream.nxydumpsink(options.output)
else:
	raise ValueError("unrecognized format for --output")

# play
stream.start()
