gstlal  0.8.1
 All Classes Namespaces Files Functions Variables Pages
gstlal_play
1 #!/usr/bin/env python
2 #
3 # Copyright (C) 2011--2013 Kipp Cannon, Chad Hanna, Drew Keppel
4 #
5 # This program is free software; you can redistribute it and/or modify it
6 # under the terms of the GNU General Public License as published by the
7 # Free Software Foundation; either version 2 of the License, or (at your
8 # option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
13 # Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License along
16 # with this program; if not, write to the Free Software Foundation, Inc.,
17 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 
19 
20 from optparse import OptionParser
21 import sys
22 
23 import pygtk
24 pygtk.require("2.0")
25 import gobject
26 gobject.threads_init()
27 import pygst
28 pygst.require('0.10')
29 import gst
30 import signal
31 
32 
33 from pylal import series as lalseries
34 from glue.ligolw import ligolw
35 from glue.ligolw import array
36 from glue.ligolw import param
37 from glue.ligolw import lsctables
38 array.use_in(ligolw.LIGOLWContentHandler)
39 param.use_in(ligolw.LIGOLWContentHandler)
40 lsctables.use_in(ligolw.LIGOLWContentHandler)
41 from glue.ligolw import utils
42 from gstlal import datasource
43 from gstlal import pipeparts
44 from gstlal import simplehandler
45 from glue.ligolw.utils import segments as ligolw_segments
46 from pylal import series as lalseries
47 
48 ## @file gstlal_play
49 # This program will play data in a variety of ways; See gstlal_play for help and usage.
50 #
51 # This program will play data in a variety of ways. Its input is anything
52 # supported by datasource.py. You can additionally whiten the data or apply a
53 # band pass filtering. It can direct its output to either your sound card,
54 # various audio file formats, or stderr/stdout in tab delimited ASCII text.
55 #
56 # #### Graph of the gsreamer pipeline
57 #
58 # - gray boxes are optional and depend on the command line given
59 #
60 # @dot
61 # digraph G {
62 # // graph properties
63 #
64 # rankdir=LR;
65 # compound=true;
66 # node [shape=record fontsize=10 fontname="Verdana"];
67 # edge [fontsize=8 fontname="Verdana"];
68 #
69 # // nodes
70 #
71 # "mkbasicsrc()" [URL="\ref datasource.mkbasicsrc()"];
72 # "whitened_multirate_src()" [label="whitened_multirate_src()", URL="\ref multirate_datasource.mkwhitened_multirate_src()", style=filled, color=lightgrey];
73 # "mkresample()" [URL="\ref pipeparts.mkresample()", style=filled, color=lightgrey];
74 # "mkcapsfilter()" [URL="\ref pipeparts.mkcapsfilter()", style=filled, color=lightgrey];
75 # "mkaudiochebband()" [URL="\ref pipeparts.mkaudiochebband()", style=filled, color=lightgrey];
76 # "mkaudiocheblimit()" [URL="\ref pipeparts.mkaudiocheblimit()", style=filled, color=lightgrey];
77 # "mkaudioconvert()" [URL="\ref pipeparts.mkaudioconvert()"];
78 # "mkaudioamplify()" [URL="\ref pipeparts.mkaudioamplify()"];
79 # "mkautoaudiosink()" [URL="\ref pipeparts.mkautoaudiosink()", style=filled, color=lightgrey];
80 # "mkwavenc()" [URL="\ref pipeparts.mkwavenc()", style=filled, color=lightgrey];
81 # "mkflacenc()" [URL="\ref pipeparts.mkflacenc()", style=filled, color=lightgrey];
82 # "mkvorbisenc()" [URL="\ref pipeparts.mkvorbisenc()", style=filled, color=lightgrey];
83 # "mkfilesink()" [URL="\ref pipeparts.mkfilesink()", style=filled, color=lightgrey];
84 # "mknxydumpsink()" [URL="\ref pipeparts.mknxydumpsink()", style=filled, color=lightgrey];
85 #
86 # // connections
87 #
88 # "mkbasicsrc()" -> "mkresample()" [label=" --whiten not provided"];
89 # "mkresample()" -> "mkcapsfilter()";
90 # "mkcapsfilter()" -> "mkaudioconvert()" [label=" neither --low-pass-filter nor --high-pass-filter provided"];
91 # "mkcapsfilter()" -> "mkaudiochebband()" [label=" --low-pass-filter and --high-pass-filter provided"];
92 # "mkcapsfilter()" -> "mkaudiocheblimit()" [label=" --low-pass-filter or --high-pass-filter provided"];
93 #
94 # "mkbasicsrc()" -> "whitened_multirate_src()" [label=" --whiten provided"];
95 # "whitened_multirate_src()" -> "mkaudioconvert()" [label=" neither --low-pass-filter nor --high-pass-filter provided"];
96 # "whitened_multirate_src()" -> "mkaudiochebband()" [label=" --low-pass-filter and --high-pass-filter provided"];
97 # "whitened_multirate_src()" -> "mkaudiocheblimit()" [label=" --low-pass-filter or --high-pass-filter provided"];
98 #
99 # "mkaudiochebband()" -> "mkaudioconvert()";
100 # "mkaudiocheblimit()" -> "mkaudioconvert()";
101 #
102 # "mkaudioconvert()" -> "mkaudioamplify()";
103 #
104 # "mkaudioamplify()" -> "mkautoaudiosink()" [label=" --output not provided"];
105 # "mkaudioamplify()" -> "mkwavenc()" [label=" --output ends with '.wav'"];
106 # "mkaudioamplify()" -> "mkflacenc()" [label=" --output ends with '.flac'"];
107 # "mkaudioamplify()" -> "mkvorbisenc()" [label=" --output ends with '.ogg'"];
108 # "mkaudioamplify()" -> "mknxydumpsink()" [label=" --output ends with '.txt' or is /dev/stderr or /dev/stdout"];
109 # "mkwavenc()" -> "mkfilesink()";
110 # "mkvorbisenc()" -> "mkfilesink()";
111 # "mkflacenc()" -> "mkfilesink()";
112 # }
113 # @enddot
114 #
115 # ### Usage cases
116 #
117 # See datasource.append_options() for additional usage cases for datasource specific command line options
118 #
119 # -# Viewing low latency data in stdout (e.g. on CIT cluster)
120 # - Note ctrl+c kills this
121 #
122 # gstlal_play --data-source framexmit --channel-name=L1=FAKE-STRAIN --output /dev/stdout
123 #
124 # -# Pipe low latency data to an ogg file narrowing in on the sweet spot and
125 # add amplification to make it audible.
126 # - Note ctrl+c kills this
127 #
128 # gstlal_play --data-source framexmit --channel-name=L1=FAKE-STRAIN --high-pass-filter 40 --low-pass-filter 1000 --amplification 1e21 --output test.ogg
129 #
130 # -# Write injection time series from an xml file into an ASCII delimited text file
131 #
132 # 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
133 #
134 # -# Other things are certainly possible. Please add some!
135 #
136 # ### Command line options ###
137 #
138 # See datasource.append_options() for common command line options shared among different programs
139 #
140 # + `--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
141 # + `--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.
142 # + `--whiten`: Whiten the time series (default = do not whiten.
143 # + `--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.
144 # + `--low-pass-filter` [float] (Hz): Low pass filter frequency (default = no low-pass filter). Low-pass filter is applied after whitening.
145 # + `--high-pass-filter` [float] (Hz): High pass filter frequency (default = no high-pass filter). High-pass filter is applied after whitening.
146 # + `--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
147 # + `--verbose`: Be verbose.
148 #
149 
150 
151 def parse_command_line():
152 
153  parser = OptionParser(description = __doc__)
154 
155  #
156  # First append the datasource common options
157  #
158 
159  datasource.append_options(parser)
160 
161  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")
162  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.")
163  parser.add_option("--whiten", action = "store_true", help = "Whiten the time series (default = do not whiten).")
164  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.")
165  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.")
166  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.")
167  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")
168  parser.add_option("-v", "--verbose", action = "store_true", help = "Be verbose.")
169 
170  #
171  # parse the arguments and sanity check
172  #
173 
174  options, filenames = parser.parse_args()
175 
176  if options.low_pass_filter is not None and options.high_pass_filter is not None:
177  if options.low_pass_filter <= options.high_pass_filter:
178  raise ValueError("--low-pass-filter must be > --high-pass-filter")
179 
180  if options.whiten and (options.low_pass_filter or options.high_pass_filter):
181  raise ValueError("specify either whitening or filtering not both")
182 
183  if options.reference_psd and not options.whiten:
184  raise ValueError("--reference-psd requires --whiten")
185 
186  if len(options.channel_name) > 1:
187  raise ValueError("only one --channel-name allowed")
188 
189  return options, filenames
190 
191 
192 # parsing and setting up some core structures
193 options, filenames = parse_command_line()
194 
195 gw_data_source_info = datasource.GWDataSourceInfo(options)
196 instrument, = gw_data_source_info.channel_dict
197 
198 if options.reference_psd is not None:
199  psd = lalseries.read_psd_xmldoc(utils.load_filename(options.reference_psd, verbose = options.verbose, contenthandler = ligolw.LIGOLWContentHandler))[instrument]
200 else:
201  psd = None
202 
203 
204 # building the event loop and pipeline
205 gobject.threads_init()
206 mainloop = gobject.MainLoop()
207 pipeline = gst.Pipeline("gstlal_play_frames")
208 handler = simplehandler.Handler(mainloop, pipeline)
209 
210 #
211 # the pipeline
212 #
213 # A basic src
214 head = datasource.mkbasicsrc(pipeline, gw_data_source_info, instrument, verbose = options.verbose)
215 
216 # if whitening, leverage mkwhitened_multirate_src() otherwise just resample
217 if options.whiten:
218  head = mkwhitened_multirate_src(pipeline, head, [options.rate], instrument, psd)[options.rate]
219 else:
220  head = pipeparts.mkresample(pipeline, head, quality = 9)
221  head = pipeparts.mkcapsfilter(pipeline, head, "audio/x-raw-float, rate=%d" % options.rate)
222 
223 # handle filtering
224 if options.high_pass_filter is not None and options.low_pass_filter is not None:
225  head = pipeparts.mkaudiochebband(pipeline, head, lower_frequency = options.high_pass_filter, upper_frequency = options.low_pass_filter)
226 else:
227  if options.high_pass_filter is not None:
228  head = pipeparts.mkaudiocheblimit(pipeline, head, cutoff = options.high_pass_filter, mode = "high-pass")
229  elif options.low_pass_filter is not None:
230  head = pipeparts.mkaudiocheblimit(pipeline, head, cutoff = options.low_pass_filter, mode = "low-pass")
231 
232 # necessary audio convert and amplify
233 head = pipeparts.mkaudioconvert(pipeline, pipeparts.mkaudioamplify(pipeline, head, options.amplification))
234 
235 if options.output is None:
236  pipeparts.mkautoaudiosink(pipeline, head)
237 elif options.output.endswith(".wav"):
238  pipeparts.mkfilesink(pipeline, pipeparts.mkwavenc(pipeline, head), options.output)
239 elif options.output.endswith(".flac"):
240  pipeparts.mkfilesink(pipeline, pipeparts.mkflacenc(pipeline, head), options.output)
241 elif options.output.endswith(".ogg"):
242  head = pipeparts.mkoggmux(pipeline, pipeparts.mkvorbisenc(pipeline, head))
243  pipeparts.mkfilesink(pipeline, head, options.output)
244 elif options.output.endswith(".txt") or options.output in ("/dev/stdout", "/dev/stderr"):
245  pipeparts.mknxydumpsink(pipeline, head, options.output)
246 else:
247  raise ValueError("unrecognized format for --output")
248 
249 # Allow Ctrl+C or sig term to gracefully shut down the program for online
250 # sources, otherwise it will just kill it
251 if gw_data_source_info.data_source in ("lvshm", "framexmit"):# what about nds online?
252  simplehandler.OneTimeSignalHandler(pipeline)
253 
254 # run
255 if pipeline.set_state(gst.STATE_PLAYING) == gst.STATE_CHANGE_FAILURE:
256  raise RuntimeError("pipeline failed to enter PLAYING state")
257 mainloop.run()