4.6 Using an A/D converter unsupported by HARK

Problem

Read this section if you wish to capture acoustic signals using devices other than the sound cards supported by ALSA (Advanced Linux Sound Architecture), the RASP series (System In Frontier, Inc.), and TD-BD-16ADUSB (Tokyo Electron Device), all of which are supported by HARK as standards.

Solution

An A/D converter can be used by a corresponding node created by the user. We describe here a procedure for creating an AudioStreamFromMic node that supports, for example, NewDevice. The overall procedure consists of:

  1. Creation of a class NewDeviceRecorder corresponding to the device and placing its source and header files into the librecorder directory in the HARK directory.

  2. Rewriting AudioStreamFromMic so that the created class can be used.

  3. Rewriting Makefile.am, configure.in to compile.

In (a), a class is created that takes data from the device and sends them to a buffer. This class is regarded as the successor to the Recorder class. Initialization prepares the device for use, followed by the operator() method, which removes data from the device. In (b), processing is performed when “NewDevice” is designated as the option is described. The NewDevice is designated and initialized in the constructor, and the signals are set. In (c), Makefile.am and configure.in are changed to correspond to the newly added file. Described below is a sample of AudioStreamFromMic 2, which supports a new device (NewDevice)

#include "BufferedNode.h"
#include "Buffer.h"
#include "Vector.h"
#include <climits>
#include <csignal>
#
include <NewDeviceRecorder.hpp> // Point1:
Read a required header file
using namespace std;
using namespace FD;
class AudioStreamFromMic2;
DECLARE_NODE( AudioStreamFromMic2);
/*Node
*
* @name AudioStreamFromMic2
* @category MyHARK
* @description This node captures an audio stream using microphones and outputs frames.
*
* @output_name AUDIO
* @output_type Matrix<float>
* @output_description Windowed wave form.
A row index is a channel, and a column index is time.
*
* @output_name NOT_EOF
* @output_type bool
* @output_description True if we haven't reach the end of file yet.
*
* @parameter_name LENGTH
* @parameter_type int
* @parameter_value 512
* @parameter_description The length of a frame in one channel (in samples).
*
* @parameter_name ADVANCE
* @parameter_type int
* @parameter_value 160
* @parameter_description The shift length between adjacent frames (in samples).
*
* @parameter_name CHANNEL_COUNT
* @parameter_type int
* @parameter_value 16
* @parameter_description The number of channels.
*
* @parameter_name SAMPLING_RATE
* @parameter_type int
* @parameter_value 16000
* @parameter_description Sampling rate (Hz).
*
*
@parameter_name DEVICETYPE // Point2-1:
Add the type of a device to be used
* @parameter_type string
* @parameter_value NewDevice
* @parameter_description Device type.
*
*
@parameter_name DEVICE // Point2-2:
Add the name of a device to be used
* @parameter_type string
* @parameter_value /dev/newdevice
* @parameter_description The name of device.
END*/
// Point3:
Describe processing to stop sound recording in the middle
void sigint_handler_newdevice(int s)
{Recorder* recorder = NewDeviceRecorder::
GetInstance();
recorder->Stop();
exit(0);
}
class AudioStreamFromMic2:
public BufferedNode {
int audioID;
int eofID;
int length;
int advance;
int channel_count;
int sampling_rate;
string device_type;
string device;
Recorder* recorder;
vector<short> buffer;
public:
AudioStreamFromMic2(string nodeName, ParameterSet params)
:
BufferedNode(nodeName, params), recorder(0)
{
audioID = addOutput("AUDIO");
eofID = addOutput("NOT_EOF");
length = dereference_cast<int> (parameters.get("LENGTH"));
advance = dereference_cast<int> (parameters.get("ADVANCE"));
channel_count = dereference_cast<int> (parameters.get("CHANNEL_COUNT"));
sampling_rate = dereference_cast<int> (parameters.get("SAMPLING_RATE"));
device_type = object_cast<String> (parameters.get("DEVICETYPE"));
device = object_cast<String> (parameters.get("DEVICE"));
// Point4:
Create a recorder class corresponding to the device type
if (device_type == "NewDevice")
{recorder = NewDeviceRecorder::
GetInstance();
recorder->Initialize(device.c_str(), channel_count, sampling_rate, length * 1024);} else {
throw new NodeException(NULL, string("Device type " + device_type + " is not supported."), __FILE__, __LINE__);}
inOrder = true;}
virtual void initialize()
{outputs[audioID].
lookAhead = outputs[eofID].
lookAhead = 1 + max(outputs[audioID].
lookAhead, outputs[eofID].
lookAhead);
this->BufferedNode::
initialize();}
virtual void stop()
{recorder->Stop();}
void calculate(int output_id, int count, Buffer &out)
{Buffer &audioBuffer = *(outputs[audioID].
buffer);
Buffer &eofBuffer = *(outputs[eofID].
buffer);
eofBuffer[count]
= TrueObject;
RCPtr<Matrix<float> > outputp(new Matrix<float> (channel_count, length));
audioBuffer[count]
= outputp;
Matrix<float>& output = *outputp;
if (count == 0)
{ //Done only the first time
recorder->Start();
buffer.resize(length * channel_count);
Recorder::
BUFFER_STATE state;
do {usleep(5000);
state = recorder->ReadBuffer(0, length, buffer.begin());} while (state != Recorder::
OK);
// original
convertVectorToMatrix(buffer, output, 0);} else { // Normal case (not at start of file)
if (advance < length)
{Matrix<float>& previous = object_cast<Matrix<float> > (
for (int c = 0;
c < length - advance;
c++)
{for (int r = 0;r < output.nrows();r++)
{output(r, c)= previous(r, c + advance);}}} else {for (int c = 0;c < length - advance;c++)
{for (int r = 0;r < output.nrows();
r++)
{output(r, c)= 0;}}}
buffer.resize(advance * channel_count);
Recorder::
BUFFER_STATE state;
for (;;)
{
state = recorder->ReadBuffer((count - 1)
* advance + length,
advance, buffer.begin());
if (state == Recorder::
OK)
{break;} else {usleep(5000);}
}
int first_output = length - advance;
convertVectorToMatrix(buffer, output, first_output);
}
bool is_clipping = false;
for (int i = 0;
i < buffer.size();
i++)
{
if (!is_clipping && checkClipping(buffer[i]))
{
is_clipping = true;
}
}
if (is_clipping)
{
cerr << "[" << count << "][" << getName()
<< "] clipping" << endl;
}
}
protected:
void convertVectorToMatrix(const vector<short>& in, Matrix<float>& out,
int first_col)
{
for (int i = 0;
i < out.nrows();
i++)
{
for (int j = first_col;
j < out.ncols();
j++)
{
out(i, j)
= (float) in[i + (j - first_col)
* out.nrows()];
}
}
}
bool checkClipping(short x)
{
if (x >= SHRT_MAX ||
x <= SHRT_MIN)
{
return true;
} else {
return false;
}
}
};

The following is the outline of a source code of the class that was spun off from the Recorder class, enabling the NewDevice to be used. We describe the processing required to connect with the device with an initialize function and to read data from the device to the () operator. This source code (NewDeviceRecorder.cpp) is created in the librecorder Folder.

#include "NewDeviceRecorder.hpp"
using namespace boost;
NewDeviceRecorder* NewDeviceRecorder::
instance = 0;
// This function is executed in another thread, and
// records acoustic signals into circular buffer.
void NewDeviceRecorder::
operator()()
{
for(;;)
{
// wait during less than (read_buf_size/sampling_rate)
[ms]
usleep(sleep_time);
mutex::
scoped_lock lk(mutex_buffer);
if (state == PAUSE)
{
continue;
}
else if (state == STOP)
{
break;
}
else if (state == RECORDING)
{
lk.unlock();
// Point5:
Processing to read data from the device is described here.
read_buf = receive_data;
// Point6:
Forward cur_time for the time orresponding to the data read so far.
cur_time += timelength_of_read_data;
mutex::
scoped_lock lk(mutex_buffer);
buffer.insert(buffer.end(), read_buf.begin(), read_buf.begin()
+ buff_len);
lk.unlock();
}
}
}
int NewDeviceRecorder::
Initialize(const string& device_name, int chan_count, int samp_rate, size_t buf_size)
{
// Point7:
Processing to initialize variables and devices to be used is described.
new_device_open_function
}

Discussion

The AudioStreamFromMic module performs only the processing required to read the contents of the buffer belonging to the Recorder class. Data exchange with devices are performed by each class (ALSARecorder, ASIORecorder, and WSRecorder) derived from the Recorder class. Therefore, a new device can be supported by implementing a class corresponding to these derived classes (NewDeviceRecorder).

See Also

The details of constructing a node are described in Section 12.1, “How to create nodes?”.