changeset 43:62e31e7980e6

Various fixes to audio, pipes, character encodings etc.
author samer
date Tue, 13 Jan 2015 13:52:18 +0000
parents ae596261e75f
children 3cedfd4549ef
files audio/filesink.m audio/flacfile.m audio/mp3enc.m audio/mp3file.m audio/oggenc.m audio/oggfile.m audio/pipesink.m audio/private/austream.m audio/private/jfile.m audio/private/pipein.m audio/private/pipeout.m audio/rawpipe.m audio/sndfile.m audio/sndpipe.m audio/sndread.m audio/wavedata.m dsp/specgrm.m signals/@signal/signal.m
diffstat 18 files changed, 89 insertions(+), 51 deletions(-) [+]
line wrap: on
line diff
--- a/audio/filesink.m	Tue Dec 02 14:51:13 2014 +0000
+++ b/audio/filesink.m	Tue Jan 13 13:52:18 2015 +0000
@@ -1,3 +1,5 @@
+% filesink - sink which writes a signal to a WAV file
+% filesink :: C:natural, R:real, path, options {...} -> sink(C,R).
 function s=filesink(ch,rate,file,varargin)
 	str=sprintf('filesink(''%s'')',file);
 	s=wavsink(ch,rate,@()filestream(file),@()str,varargin{:});
--- a/audio/flacfile.m	Tue Dec 02 14:51:13 2014 +0000
+++ b/audio/flacfile.m	Tue Jan 13 13:52:18 2015 +0000
@@ -1,4 +1,7 @@
-function s=mp3dec(file,varargin)
-	s=sndpipe(sprintf('flac --decode -o - "%s"',file),'stringfn',@()sprintf('flacfile(''%s'')',file),varargin{:});
+% flacfile - audio signal from FLAC file
+% flacfile :: path, options -> signal(C,R).
+% Options are passed to SNDPIPE
+function s=flacfile(file,varargin)
+	s=sndpipe(sprintf('flac --decode -o - %s',bash_arg(file)),'stringfn',@()sprintf('flacfile(''%s'')',file),varargin{:});
 end
 
--- a/audio/mp3enc.m	Tue Dec 02 14:51:13 2014 +0000
+++ b/audio/mp3enc.m	Tue Jan 13 13:52:18 2015 +0000
@@ -1,3 +1,4 @@
+% mp3enc - sink which writes audio signal to MP3 file using lame
 function s=mp3enc(ch,rate,file,varargin)
 	opts=options('quality',5,varargin{:});
 	s=pipesink(ch,rate,sprintf('lame -h -V %d - "%s"',opts.quality,file));
--- a/audio/mp3file.m	Tue Dec 02 14:51:13 2014 +0000
+++ b/audio/mp3file.m	Tue Jan 13 13:52:18 2015 +0000
@@ -1,9 +1,12 @@
-function s=mp3dec(file,varargin)
+% mp3file - audio signal from MP3 file
+% mp3file :: path, options(sndpipe) -> signal(C,R).
+function s=mp3file(file,varargin)
 	% NB: mpg123 was sometimes putting the wrong sampling rate in the header (expecting to seek
 	% back and fix it after writing) but mpg321 seems to get it right first time.
 
 	% mpg321 broken in Macports, 17th Nov 2014
 	% s=sndpipe(sprintf('mpg321 --au - "%s"',file),'stringfn',@()sprintf('mp3file(''%s'')',file),varargin{:});
-	s=sndpipe(sprintf('sox -t mp3 "%s" -t au -',file),'stringfn',@()sprintf('mp3file(''%s'')',file),varargin{:});
+	% NB: sox already knows how to read URLs, so we only need to properly escape the path/url
+	s=sndpipe(sprintf('sox -t mp3 %s -t au -',bash_esc(file)),'stringfn',@()sprintf('mp3file(''%s'')',file),varargin{:});
 end
 
--- a/audio/oggenc.m	Tue Dec 02 14:51:13 2014 +0000
+++ b/audio/oggenc.m	Tue Jan 13 13:52:18 2015 +0000
@@ -1,3 +1,4 @@
+% oggenc - sink which writes audio signal to OGG file using oggenc
 function s=oggenc(ch,rate,file,varargin)
 	opts=options('quality',5,varargin{:});
 	s=pipesink(ch,rate,sprintf('oggenc -q %d -o "%s" -',opts.quality,file));
--- a/audio/oggfile.m	Tue Dec 02 14:51:13 2014 +0000
+++ b/audio/oggfile.m	Tue Jan 13 13:52:18 2015 +0000
@@ -1,2 +1,4 @@
+% oggfile - audio signal from OGG file using ogg123
+% oggfile :: bash_file, options(sndpipe) -> signal(C,R).
 function s=oggdec(file)
-	s=sndpipe(sprintf('ogg123 -d au -f - "%s"',file),'stringfn',@()sprintf('oggfile(''%s'')',file));
+	s=sndpipe(sprintf('ogg123 -d au -f - %s',bash_arg(file)),'stringfn',@()sprintf('oggfile(''%s'')',file));
--- a/audio/pipesink.m	Tue Dec 02 14:51:13 2014 +0000
+++ b/audio/pipesink.m	Tue Jan 13 13:52:18 2015 +0000
@@ -1,3 +1,5 @@
+% pipesink - sink which writes audio signal to shell pipeline.
+% pipesink :: C:natural, R:nonneg, string, options(wavsink) -> sink(C,R).
 function s=pipesink(ch,rate,cmd,varargin)
 	str=sprintf('pipesink(''%s'')',cmd);
 	s=wavsink(ch,rate,@()pipeout(cmd),@()str,varargin{:});
--- a/audio/private/austream.m	Tue Dec 02 14:51:13 2014 +0000
+++ b/audio/private/austream.m	Tue Jan 13 13:52:18 2015 +0000
@@ -1,3 +1,5 @@
+% austream - construct audio stream from ordinary stream
+% austream :: java.io.InputStream -> java.sound.sampled.AudioInputStream.
 function str=austream(str)
 	if ~str.markSupported, str=java.io.BufferedInputStream(str); end
 	str=javax.sound.sampled.AudioSystem.getAudioInputStream(str);
--- a/audio/private/jfile.m	Tue Dec 02 14:51:13 2014 +0000
+++ b/audio/private/jfile.m	Tue Jan 13 13:52:18 2015 +0000
@@ -1,5 +1,17 @@
+% jfile - construct Java File object from path string
+% jfile :: text -> java.io.File.
 function f=jfile(path)
 	% The problem was that file names with accents in where not being found 
 	% when converted to Java file names.
 	% I have no idea what is going on here but it seems to work
-	f=java.io.File(java.lang.String(unicode2native(path,'latin1'),'UTF8'));
+	% f=java.io.File(java.lang.String(unicode2native(path,'latin1'),'UTF8'));
+
+	% SA 2014: UPDATE
+	% Now I have a faint idea about what is going on here. Matlab ignores the
+	% LANG environment variable, and previously was using LATIN1 as it's
+	% feature('DefaultCharacterSet'). This meant that the entire Java subsystem
+	% was starting up with LATIN1 as its chacterset. Now, I have found a way
+	% to persuade Matlab to use Unicode, and indeed, for the JVM to use unicode
+	% also. This means that the old version of this function was no good.
+	% Instead, we should use unicode2native with no 2nd argument.
+	f=java.io.File(java.lang.String(unicode2native(path),'UTF8'));
--- a/audio/private/pipein.m	Tue Dec 02 14:51:13 2014 +0000
+++ b/audio/private/pipein.m	Tue Jan 13 13:52:18 2015 +0000
@@ -1,6 +1,12 @@
+% pipein - start shell command and return stream for reading standard output
+% pipein :: 
+%    text ~'bash shell command',
+%    bool ~'quiet operation flag'
+% -> java.io.InputStream ~ 'connected to stdout of command',
+%    (void -> action void) ~'call this to clean up afterwards'.
+%
+% If quiet flag is not supplied, it defaults to false.
 function [str,cleanup]=pipein(cmd,q)
-	import java.io.*;
-
 	if nargin<2, q=false; end
 	if ~q, fprintf('Starting sub-process: %s\n',cmd); end
 
@@ -24,7 +30,7 @@
 	% BETTER:
 	cs=feature('DefaultCharacterSet'),
 	process=java.lang.Runtime.getRuntime().exec('bash');
-	writer=OutputStreamWriter(process.getOutputStream(),cs);
+	writer=java.io.OutputStreamWriter(process.getOutputStream(),cs);
 	writer.write(cmd); writer.close();
 
 	str=process.getInputStream();
--- a/audio/private/pipeout.m	Tue Dec 02 14:51:13 2014 +0000
+++ b/audio/private/pipeout.m	Tue Jan 13 13:52:18 2015 +0000
@@ -1,11 +1,25 @@
-function [str,cleanup]=pipeout(cmd)
-	fprintf('Starting sub-process: %s\n',cmd);
-	process=java.lang.Runtime.getRuntime().exec({'bash','-c',cmd});
+% pipeout - start shell command and return stream for writing to its standard input
+% pipeout :: 
+%    text ~'bash shell command',
+%    bool ~'quiet operation flag'
+% -> java.io.OutputStream ~ 'connected to stdout of command',
+%    (void -> action void) ~'call this to clean up afterwards'.
+%
+% If quiet flag is not supplied, it defaults to false.
+function [str,cleanup]=pipeout(cmd,q)
+	if nargin<2, q=false; end
+	if ~q, fprintf('Starting sub-process: %s\n',cmd); end
+
+	cs=feature('DefaultCharacterSet'),
+	process=java.lang.Runtime.getRuntime().exec('bash');
+	writer=java.io.OutputStreamWriter(process.getOutputStream(),cs);
+	writer.write(cmd); writer.close();
+
 	str=process.getOutputStream();
 	cleanup=@dispose;
 
 	function dispose
-		fprintf('Killing subprocess...\n');
+		if ~q, fprintf('Killing subprocess...\n'); end
 		process.destroy();
 	end
 end
--- a/audio/rawpipe.m	Tue Dec 02 14:51:13 2014 +0000
+++ b/audio/rawpipe.m	Tue Jan 13 13:52:18 2015 +0000
@@ -1,13 +1,9 @@
-% rawpipe - pipe reader implementation of sndstream (raw audio stream)
+% rawpipe - audio signal from raw stream from shell pipe 
 %
 % rawpipe ::
 %    string     ~'shell pipe',
 %    AudioFormat~'stream format',
-%    options {
-%       channels :: natural/nan ~'desired number of channels';
-%       rate     :: nonneg/nan ~'desired sampling rate';
-%       bits     :: natural/16 ~'desired bits per sample';
-%    }
+%    options(sndstream)
 % -> signal(C,R).
 %
 % If channels or rate are not nan, audio format will be converted to match.
--- a/audio/sndfile.m	Tue Dec 02 14:51:13 2014 +0000
+++ b/audio/sndfile.m	Tue Jan 13 13:52:18 2015 +0000
@@ -1,40 +1,37 @@
-% sndfile - file reader implementation using samer.audio.alt.FileSource
+% sndfile - audio signal from audio file
 %
 % sndfile ::
-%    path       ~'file name'
+%    path | cell { nonneg, nonneg, path } ~'file name or { start, end, path }'
 %    options {
+%       enc      :: {'mp3','aac','flac','ogg'};
 %       channels :: natural/nan ~'desired number of channels';
 %       rate     :: nonneg/nan ~'desired sampling rate';
 %       bits     :: natural/16 ~'desired bits per sample';
 %    }
 % -> signal(C,R).
 %
+% This will use mp3file, aacfile or flacfile for those types of file, but jsndfile
+% for anything else, including OGG files.
+%
 % If channels or rate are not nan, audio format will be converted to match.
 % If either of them are nan, the corresponding value from the audio file will
 % be left unchanged.
-%
-% Javazoom MP3 decoder does not get the length of the signal right.
-% Would be better to use mpg123 on a pipe in this case.
+
 function s=sndfile(file,varargin)
+	opts=options('enc','unknown',varargin{:});
 	if iscell(file)
 		s=taket(file{2}-file{1},dropt(file{1},sndfile(file{3})))
 		return
 	end
 	string=sprintf('sndfile(''%s'')',file);
-	if endswith(file,'mp3') || endswith(file,'MP3')
-		s=mp3file(file,'stringfn',@()string,varargin{:}); % Java version doesn't remove padding correctly
-	elseif endswith(file,'m4a') 
-		s=aacfile(file,'stringfn',@()string,varargin{:});
+	if strcmp(opts.enc,'mp3') || endswith(file,'mp3') || endswith(file,'MP3')
+		s=mp3file(file,'stringfn',@()string,opts); % Java version doesn't remove padding correctly
+	elseif strcmp(opts.enc,'aac') || endswith(file,'m4a') 
+		s=aacfile(file,'stringfn',@()string,opts);
+	elseif strcmp(opts.enc,'flac') || endswith(file,'flac') 
+		s=flacfile(file,'stringfn',@()string,opts);
 	else
-		s=sndstream(@filestream,'stringfn',@()string,varargin{:});
-	end
-
-	function [str,cleanup]=filestream(q)
-		jf=jfile(file);
-		if ~jf.exists(), error(sprintf('File %s does not exist',file)); end
-		if ~q, fprintf('Opening sound file: %s\n',file); end
-		str=austream(java.io.FileInputStream(jf));
-		cleanup=@nop;
+		s=jsndfile(file,varargin{:});
 	end
 end
 
--- a/audio/sndpipe.m	Tue Dec 02 14:51:13 2014 +0000
+++ b/audio/sndpipe.m	Tue Jan 13 13:52:18 2015 +0000
@@ -1,17 +1,8 @@
-% sndpipe - pipe reader implementation of sndstream
+% sndpipe - audio signal from shell pipe line.
+% sndpipe :: string~'shell command', options(sndstream) -> signal(C,R).
 %
-% sndpipe ::
-%    string     ~'shell pipe'
-%    options {
-%       channels :: natural/nan ~'desired number of channels';
-%       rate     :: nonneg/nan ~'desired sampling rate';
-%       bits     :: natural/16 ~'desired bits per sample';
-%    }
-% -> signal(C,R).
-%
-% If channels or rate are not nan, audio format will be converted to match.
-% If either of them are nan, the corresponding value from the audio file will
-% be left unchanged.
+% The shell command must produce a self-describing stream that can be recognised
+% by the JavaSound subsystem. Then,
 function s=sndpipe(cmd,varargin)
 	s=sndstream(@pipestream,'stringfn',@()sprintf('sndpipe(''%s'')',cmd),varargin{:});
 
--- a/audio/sndread.m	Tue Dec 02 14:51:13 2014 +0000
+++ b/audio/sndread.m	Tue Jan 13 13:52:18 2015 +0000
@@ -1,3 +1,4 @@
+% sndread - read entire contents of any audo file readable by sndfile
 function X=sndread(file,FS,varargin)
 	opts=options('bs',1024,varargin{:});
 	sig=sndfile(file,opts);
--- a/audio/wavedata.m	Tue Dec 02 14:51:13 2014 +0000
+++ b/audio/wavedata.m	Tue Jan 13 13:52:18 2015 +0000
@@ -1,1 +1,2 @@
+% wavedata - alternative name for WAVREAD
 function y=wavedata(varargin), y=wavdata(varargin{:}); end
--- a/dsp/specgrm.m	Tue Dec 02 14:51:13 2014 +0000
+++ b/dsp/specgrm.m	Tue Jan 13 13:52:18 2015 +0000
@@ -29,7 +29,7 @@
 	if nargout==0
 		opts=options('fs',1,'range',13.5,varargin{:});
 
-		tfdimage(y,n,m,opts.fs,opts.range);
+		tfdimage(y,n,m,opts.fs,opts.range,opts);
 		clear y;
 	end
 
--- a/signals/@signal/signal.m	Tue Dec 02 14:51:13 2014 +0000
+++ b/signals/@signal/signal.m	Tue Jan 13 13:52:18 2015 +0000
@@ -68,5 +68,9 @@
 			if rate(s1)==f2, s2=s1;
 			else, s2=sigresample(f2,s1,varargin{:}); end
 		end
+
+		function varargout=specgrm(x,varargin)
+			 [varargout{1:nargout}]=specgrm(gather(x),varargin{:},'fs',rate(x));
+		 end
 	end
 end