function [fs,classid] = block(source,varargin)
%BLOCK Initialize block stream
% Usage: block(source);
%
% Input parameters:
% source : Block stream input.
% Output parameters:
% fs : Sampling rate.
% classid : Data type.
%
% BLOCK(source) initializes block data stream from source which
% can be one of the following (the letter-case is ignored for strings):
%
% 'file.wav'
% name of a wav file
%
% 'dialog'
% shows the file dialog to choose a wav file.
%
% data
% input data as columns of a matrix for each input channel
%
% 'rec'
% input is taken from a microphone/auxilary input;
%
% {'rec','file.wav'} or {'rec','dialog'} or {'rec',data}
% does the same as 'rec' but plays a chosen audio data simultaneously.
%
% 'playrec'
% loopbacks the input to the output. In this case, the block size
% (in BLOCKREAD) cannot change during the playback.
%
% BLOCK accepts the following optional key-value pairs
%
% 'fs',fs
% Required sampling rate - Some devices might support only a
% limited range of samp. frequencies. Use BLOCKDEVICES to list
% supported sampling rates of individual devices.
% When the target device does not support the chosen sampling rate,
% on-the-fly resampling will be performed in the background.
% This option overrides sampling rate read from a wav file.
%
% The default value is 44100 Hz, min. 4000 Hz, max. 96000 Hz
%
% 'L',L
% Block length - Specifying L fixes the buffer length, which cannot be
% changed in the loop.
%
% The default is 1024. In the online mode the minimum is 32.
%
% 'devid',dev
% Whenever more input/output devices are present in your system,
% 'devid' can be used to specify one. For the 'playrec' option the
% devId should be a two element vector [playDevid, recDevid]. List
% of the installed devices and their IDs can be obtained by
% BLOCKDEVICES.
%
% 'playch',playch
% If device supports more output channels, 'playch' can be used to
% specify which ones should be used. E.g. for two channel device, [1,2]
% can be used to specify channels.
%
% 'recch',recch
% If device supports more input channels, 'recch' can be used to
% specify which ones should be used.
%
% 'outfile','file.wav'
% Creates a wav file header for on-the-fly storing of block data using
% BLOCKWRITE. Existing file will be overwritten. Only 16bit fixed
% point precision is supported in the files.
%
% 'nbuf',nbuf
% Max number of buffers to be preloaded. Helps avoiding glitches but
% increases delay.
%
% 'loadind',loadind
% How to show the load indicator. loadind can be the following:
%
% 'nobar'
% Suppresses any load display.
%
% 'bar'
% Displays ascii load bar in command line (Does not work in Octave).
%
% obj
% Java object which has a public method updateBar(double).
%
% Optional flag groups (first is default)
%
% 'noloop', 'loop'
% Plays the input in a loop.
%
% 'single', 'double'
% Data type to be used. In the offline mode (see below) the flag is
% ignored and everything is cast do double.
%
% 'online', 'offline'
% Use offline flag for offline blockwise processing of data input or a
% wav file without initializing and using the playrec MEX.
%
% See also: blockread, blockplay, blockana, blocksyn
%
% Demos: demo_blockproc_basicloop, demo_blockproc_slidingsgram
%
% Url: http://ltfat.github.io/doc/blockproc/block.html
% Copyright (C) 2005-2023 Peter L. Soendergaard <peter@sonderport.dk> and others.
% This file is part of LTFAT version 2.6.0
%
% 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 3 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, see <http://www.gnu.org/licenses/>.
% AUTHOR : Zdenek Prusa
% The function uses the Playrec tool by Robert Humphrey
% http://www.playrec.co.uk/ which in turn relies on
% Portaudio lib http://www.portaudio.com/
complainif_notenoughargs(nargin,1,'BLOCK');
definput.keyvals.devid=[];
definput.keyvals.nbuf=[];
definput.keyvals.fs=[];
definput.keyvals.playch=[];
definput.keyvals.recch=[];
definput.keyvals.outfile=[];
definput.keyvals.L=[];
definput.keyvals.loadind= 'nobar';
definput.flags.fmt={'single','double'};
definput.flags.loop={'noloop','loop'};
definput.flags.onoff={'online','offline'};
[flags,kv]=ltfatarghelper({},definput,varargin);
failIfNotPositiveInteger(kv.L,'L');
failIfNotPositiveInteger(kv.fs,'fs');
failIfNotPositiveInteger(kv.nbuf,'nbuf');
% Reset all persistent data
block_interface('reset');
if ~flags.do_offline
% Octave version check
skipLoadin = 0;
if isoctave
octs=strsplit(version,'.');
octN=str2num(octs{1})*1000+str2num(octs{2});
if octN<3007
warning('%s: Using Octave < 3.7. Disabling load indicator.',mfilename);
skipLoadin = 1;
end
end
if ~skipLoadin
if ischar(kv.loadind)
if ~strcmpi(kv.loadind,'bar') && ~strcmpi(kv.loadind,'nobar')
error('%s: Incorrect value parameter for the key ''loadin''.',...
upper(mfilename));
end
elseif isjava(kv.loadind)
try
javaMethod('updateBar',kv.loadind,0);
catch
error('%s: Java object does not contain updateBar method.',...
upper(mfilename))
end
end
end
% Store option for displaying the loop playback
block_interface('setIsLoop',flags.do_loop);
else
% Check whether any of incompatible params are set
failIfInvalidOfflinePar(kv,'devid');
failIfInvalidOfflinePar(kv,'playch');
failIfInvalidOfflinePar(kv,'recch');
failIfInvalidOfflinePar(kv,'nbuf');
failIfInvalidOfflinePar(kv,'loadind',definput.keyvals.loadind);
failIfInvalidOfflinePar(flags,'loop',definput.flags.loop{1});
kv.loadind = 'nobar';
% Different behavior for the fmt flag
flags.fmt = 'double';
% fs is maybe needed for setting fs for the output wav
block_interface('setOffline',1);
end
playChannels = 0;
recChannels = 0;
play = 0;
record = 0;
% Here we can define priority list of the host APIs.
% If none of the preferred API devices is present, the first one is taken.
hostAPIpriorityList = {};
% Force portaudio to use buffer of the following size
pa_bufLen = -1;
do_recaudio = 0;
oldsource = source;
% Handle {'rec',...} format
if iscell(source) && strcmpi(source{1},'rec')
recChannels = 1;
record = 1;
source = source{2};
if isempty(kv.nbuf)
kv.nbuf = 3;
end
do_recaudio = 1;
end
if ischar(source)
if(strcmpi(source,'rec'))
recChannels = 1;
record = 1;
if isempty(kv.nbuf)
kv.nbuf = 3;
end
elseif strcmpi(source,'playrec')
playChannels = 2;
recChannels = 1;
record = 1;
play = 1;
if isempty(kv.nbuf)
kv.nbuf = 1;
end
elseif strcmpi(source,'dialog')
[fileName,pathName] = uigetfile('*.wav','Select the *.wav file');
if fileName == 0
error('%s: No file chosen.',upper(mfilename));
end
source = fullfile(pathName,fileName);
if isempty(kv.fs)
[~, kv.fs] = wavload(source, 'i');
end
play = 1;
elseif(numel(source)>4)
if(strcmpi(source(end-3:end),'.wav'))
if exist(source,'file')~=2
error('%s: File "%s" does not exist.',upper(mfilename),source);
end
if isoctave
warning('off','Octave:fopen-file-in-path');
end
if isempty(kv.fs)
[~, kv.fs] = wavload(source, 'i');
end
play = 1;
else
error('%s: "%s" is not valid wav filename.',upper(mfilename),source);
end
else
error('%s: Unrecognized source "%s".',upper(mfilename),source);
end
elseif(isnumeric(source))
if isempty(kv.fs) && flags.do_online
kv.fs = 44100;
warning('%s: Sampling rate not specified. Using default value %i Hz.',...
upper(mfilename),kv.fs);
end
play = 1;
else
error('%s: Unrecognized source.',upper(mfilename));
end
if play && ~record
playChannels = 2;
if isempty(kv.nbuf)
kv.nbuf = 3;
end
end
if isempty(kv.fs)
kv.fs = 44100;
end
is_wav = ischar(source) && numel(source)>4 && strcmpi(source(end-3:end),'.wav');
is_numeric = isnumeric(source);
if flags.do_offline
if ~is_wav && ~is_numeric
error(['%s: In the offline mode, only wav file or a data vector can ',...
' be used as a source.'],upper(mfilename));
end
if isempty(kv.fs) && ~isempty(kv.outfile)
error('%s: Missing fs for the output file.',upper(mfilename));
end
end
% Store option for displaying the load bar
block_interface('setDispLoad',kv.loadind);
% Store data type.
block_interface('setClassId',flags.fmt);
% Store length of the buffer circular queue
block_interface('setBufCount',kv.nbuf);
%
if ~isempty(kv.outfile) && ~strcmpi(kv.outfile(end-3:end),'.wav')
error('%s: %s does not contain *.wav suffix.',upper(mfilename),kv.outfile);
end
% Return parameters
classid = flags.fmt;
fs = kv.fs;
% Set block length
if isempty(kv.L)
block_interface('setBufLen',-1);
else
if kv.L<32 && flags.do_online
error('%s: Minimum buffer length is 32.',upper(mfilename))
end
block_interface('setBufLen',kv.L);
end
% Store data
block_interface('setSource',source);
block_interface('setFs',fs);
% Handle sources with known input length
if is_wav
[~,~,~,fid] = wavload(source,'i');
Ls = fid(4);
chanNo = fid(5);
block_interface('setLs',[Ls,chanNo]);
block_interface('setSource',@(pos,endSample)...
cast(wavload(source,'',endSample-pos+1,pos-1),...
block_interface('getClassId')) );
% block_interface('setSource',@(pos,endSample)...
% cast(wavread(source,[pos,endSample]),...
% block_interface('getClassId')) );
elseif is_numeric
source = comp_sigreshape_pre(source,'BLOCK');
if size(source,2)>8
error('%s: More than 8 channels not allowed.',upper(mfilename));
end
block_interface('setLs',size(source));
block_interface('setSource',@(pos,endSample)...
cast(source(pos:endSample,:),...
block_interface('getClassId')));
end
% Modify the source just added to block_interface
if do_recaudio
block_interface('setSource',{'rec',block_interface('getSource')});
% By default, we only want a single speaker to be active
playChannels = 1;
end
% Not a single playrec call has been done until now
if ~flags.do_offline
if flags.do_online && kv.fs<4000 || kv.fs>96000
error('%s: Sampling rate must be in range 4-96 kHz ',upper(mfilename));
end
% From now on, playrec is called
isPlayrecInit = 0;
try
isPlayrecInit = playrec('isInitialised');
catch
err = lasterror;
if ~isempty(strfind(err.message,'The specified module could not be found'))
error('%s: playrec found but portaudio cannot be found.', upper(mfilename));
end
if ~isempty(strfind(err.message,'Undefined function'))
error('%s: playrec could not be found.', upper(mfilename));
end
error('%s: Error loading playrec.',upper(mfilename));
end
if isPlayrecInit
playrec('reset');
end
clear playrec;
if isempty(kv.playch)
kv.playch = 1:playChannels;
end
if isempty(kv.recch)
kv.recch = 1:recChannels;
end
devs = playrec('getDevices');
if isempty(devs)
error(['%s: No sound devices available. portaudio lib is probably ',...
'incorrectly built.'],upper(mfilename));
end
prioriryPlayID = -1;
priorityRecID = -1;
% Get all installed play devices
playDevStructs = devs(arrayfun(@(dEl) dEl.outputChans,devs)>0);
% Search for priority play device
for ii=1:numel(hostAPIpriorityList)
hostAPI = hostAPIpriorityList{ii};
priorityHostNo = find(arrayfun(@(dEl) ...
~isempty(strfind(lower(dEl.hostAPI),...
lower(hostAPI))),playDevStructs)>0);
if ~isempty(priorityHostNo)
prioriryPlayID = playDevStructs(priorityHostNo(1)).deviceID;
break;
end
end
% Get IDs of all play devices
playDevIds = arrayfun(@(dEl) dEl.deviceID, playDevStructs);
% Get all installed recording devices
recDevStructs = devs(arrayfun(@(dEl) dEl.inputChans,devs)>0);
% Search for priority rec device
for ii=1:numel(hostAPIpriorityList)
hostAPI = hostAPIpriorityList{ii};
priorityHostNo = find(arrayfun(@(dEl) ...
~isempty(strfind(lower(dEl.hostAPI),...
lower(hostAPI))),recDevStructs)>0);
if ~isempty(priorityHostNo)
priorityRecID = recDevStructs(priorityHostNo(1)).deviceID;
break;
end
end
% Get IDs of all rec devices
recDevIds = arrayfun(@(dEl) dEl.deviceID,recDevStructs);
if play && record
if ~isempty(kv.devid)
if(numel(kv.devid)~=2)
error('%s: devid should be 2 element vector.',upper(mfilename));
end
if ~any(playDevIds==kv.devid(1))
error('%s: There is no play device with id = %i.',...
upper(mfilename),kv.devid(1));
end
if ~any(recDevIds==kv.devid(2))
error('%s: There is no rec device with id = %i.',...
upper(mfilename),kv.devid(2));
end
else
% Use the priority device if present
if prioriryPlayID~=-1 && priorityRecID~=-1
kv.devid = [prioriryPlayID, priorityRecID];
else
kv.devid = [playDevIds(1), recDevIds(1)];
end
end
try
if pa_bufLen~=-1
playrec('init', kv.fs, kv.devid(1), kv.devid(2),...
max(kv.playch),max(kv.recch),pa_bufLen);
else
playrec('init', kv.fs, kv.devid(1), kv.devid(2));
end
catch
failedInit(devs,kv);
end
% if ~do_recaudio
% if numel(kv.recch) >1 && numel(kv.recch) ~= numel(kv.playch)
% error('%s: Using more than one input channel.',upper(mfilename));
% end
% end
block_interface('setPlayChanList',kv.playch);
block_interface('setRecChanList',kv.recch);
elseif play && ~record
if ~isempty(kv.devid)
if numel(kv.devid) >1
error('%s: devid should be scalar.',upper(mfilename));
end
if ~any(playDevIds==kv.devid)
error('%s: There is no play device with id = %i.',upper(mfilename),kv.devid);
end
else
% Use prefered device if present
if prioriryPlayID~=-1
kv.devid = prioriryPlayID;
else
% Use the first (hopefully default) device
kv.devid = playDevIds(1);
end
end
try
if pa_bufLen~=-1
playrec('init', kv.fs, kv.devid, -1,max(kv.playch),-1,pa_bufLen);
else
playrec('init', kv.fs, kv.devid, -1);
end
catch
failedInit(devs,kv);
end
block_interface('setPlayChanList',kv.playch);
if(playrec('getPlayMaxChannel')<numel(kv.playch))
error (['%s: Selected device does not support required output',...
' channels.\n'],upper(mfilename));
end
elseif ~play && record
if(numel(kv.devid)>1)
error('%s: devid should be scalar.',upper(mfilename));
end
if ~isempty(kv.devid)
if ~any(recDevIds==kv.devid)
error('%s: There is no rec device with id = %i.',upper(mfilename),kv.devid);
end
else
% Use asio device if present
if priorityRecID~=-1
kv.devid = priorityRecID;
else
% Use the first (hopefully default) device
kv.devid = recDevIds(1);
end
end
try
if pa_bufLen~=-1
playrec('init', kv.fs, -1, kv.devid,-1,max(kv.recch),pa_bufLen);
else
playrec('init', kv.fs, -1, kv.devid);
end
catch
failedInit(devs,kv);
end
block_interface('setRecChanList',kv.recch);
else
error('%s: Play or record should have been set.',upper(mfilename));
end
% From the playrec author:
% This slight delay is included because if a dialog box pops up during
% initialisation (eg MOTU telling you there are no MOTU devices
% attached) then without the delay Ctrl+C to stop playback sometimes
% doesn't work.
pause(0.1);
if(~playrec('isInitialised'))
error ('%s: Unable to initialise playrec correctly.',upper(mfilename));
end
if(playrec('pause'))
%fprintf('Playrec was paused - clearing all previous pages and unpausing.\n');
playrec('delPage');
playrec('pause', 0);
end
% Reset skipped samples
playrec('resetSkippedSampleCount');
if play
chanString = sprintf('%d,',kv.playch);
dev = devs(find(arrayfun(@(dEl) dEl.deviceID==kv.devid(1),devs)));
fprintf(['Play device: ID=%d, name=%s, API=%s, channels=%s, default ',...
'latency: %d--%d ms\n'],...
dev.deviceID,dev.name,dev.hostAPI,chanString(1:end-1),...
floor(1000*dev.defaultLowOutputLatency),...
floor(1000*dev.defaultHighOutputLatency));
end
if record
chanString = sprintf('%d,',kv.recch);
dev = devs(find(arrayfun(@(dEl) dEl.deviceID==kv.devid(end),devs)));
fprintf(['Rec. device: ID=%d, name=%s, API=%s, channels=%s, default ',...
'latency: %d--%d ms\n'],...
dev.deviceID,dev.name,dev.hostAPI,chanString(1:end-1),...
floor(1000*dev.defaultLowInputLatency),...
floor(1000*dev.defaultHighInputLatency));
end
% Another slight delay to allow printing all messages prior to the playback
% starts.
pause(0.1);
end
% Handle output file
if ~isempty(kv.outfile)
% Use number of recording channes only if mic is an input.
if (record && ~play) || (record && play)
blockreadChannels = numel(block_interface('getRecChanList'));
else
Ls = block_interface('getLs');
blockreadChannels = Ls(2);
end
headerStruct = writewavheader(blockreadChannels,kv.fs,kv.outfile);
block_interface('setOutFile',headerStruct);
end
%%%%%%%%%%%%%
% END BLOCK %
%%%%%%%%%%%%%
function failedInit(devs,kv)
% Common function for playrec initialization error messages
errmsg = '';
% playFs = devs([devs.deviceID]==kv.devid(1)).supportedSampleRates;
% if ~isempty(playFs) && ~any(playFs==kv.fs)
% fsstr = sprintf('%d, ',playFs);
% fsstr = ['[',fsstr(1:end-2),']' ];
% errmsg = [errmsg, sprintf(['Device %i does not ',...
% 'support the required fs. Supported are: %s \n'],...
% kv.devid(1),fsstr)];
% end
%
% if numel(kv.devid)>1
% recFs = devs([devs.deviceID]==kv.devid(2)).supportedSampleRates;
% if ~isempty(recFs) && ~any(recFs==kv.fs)
% fsstr = sprintf('%d, ',recFs);
% fsstr = ['[',fsstr(1:end-2),']' ];
% errmsg = [errmsg, sprintf(['Recording device %i does not ',...
% 'support the required fs. Supported are: %s \n'],...
% kv.devid(2),fsstr)];
% end
% end
if isempty(errmsg)
err = lasterror;
error('%s',err.message);
else
error(errmsg);
end
function failIfInvalidOfflinePar(kv,field,defval)
% Helper function for checking if field of kv is empty or not
% equal to defval.
failed = 0;
if nargin<3
if ~isempty(kv.(field))
failed = 1;
end
else
if ~isequal(kv.(field),defval)
failed = 1;
end
end
if failed
error('%s: ''%s'' is not a valid parameter in the offline mode',...
upper(mfilename),field);
end
function failIfNotPositiveInteger(par,name)
if ~isempty(par)
if ~isscalar(par) || ~isnumeric(par) || rem(par,1)~=0 || par<=0
error('%s: %s should be positive integer.',upper(mfilename),name);
end
end
function headerStruct = writewavheader(Nchan,fs,filename)
%WRITEWAVHEADER(NCHAN, FS, FILENAME)
%
%Creates a new WAV File and writes only the header into it.
%No audio data is written here.
%
%Note that this implementation is hardcoded to 16 Bits/sample.
%
%input parameters:
% NCHAN - 1: Mono, 2: Stereo
% FS - Sampling rate in Hz
% FILENAME - Name of the WAVE File including the suffix '.wav'
% Original copyright:
%---------------------------------------------------------------
% Oticon A/S, Bjoern Ohl, March 9, 2012
%---------------------------------------------------------------
% Modified: Zdenek Prusa
% predefined elements:
bitspersample = 16; % hardcoded in this implementation, as other
% quantizations do not seem relevant
mainchunk = 'RIFF';
chunktype = 'WAVE';
subchunk = 'fmt ';
subchunklen = 16; % 16 for PCM
format = 1; % 1 = PCM (linear quantization)
datachunk = 'data';
% calculated elements:
alignment = Nchan * bitspersample / 8;
%dlength = Total_Nsamp*alignment; % total amount of audio data in bytes
dlength = 0;
flength = dlength + 36; % dlength + 44 bytes (header) - 8 bytes (definition)
bytespersecond = fs*alignment; % data rate in bytes/s
% write header into file:
fid = fopen(filename,'w'); %writing access
fwrite(fid, mainchunk);
fwrite(fid, flength, 'uint32');
fwrite(fid, chunktype);
fwrite(fid, subchunk);
fwrite(fid, subchunklen, 'uint32');
fwrite(fid, format, 'uint16');
fwrite(fid, Nchan, 'uint16');
fwrite(fid, fs, 'uint32');
fwrite(fid, bytespersecond, 'uint32');
fwrite(fid, alignment, 'uint16');
fwrite(fid, bitspersample, 'uint16');
fwrite(fid, datachunk);
fwrite(fid, dlength, 'uint32');
fclose(fid); % close file
headerStruct = struct('filename',filename,'Nchan',Nchan,...
'alignment',alignment);