Reply To: Usage of PlaySoundFile()

HomeForumsMonoBrick EV3 FirmwareUsage of PlaySoundFile()Reply To: Usage of PlaySoundFile()

#5614
Author Image
rasep retrep
Participant

Hi,

I stumbled upon the same issue and took the time to fix it (my daughter desperately wanted to add minion sounds to our robot).
For this just took the LeJOS implementation for this which looked a bit more robust and migrated it to c#
The (only?) reason it didnt work is that the sound device was sent data without checking if it is ready, so the whole sample would just be almost instantaneously fed to the sound device which results in this odd “crackling” sound.

Btw., Samples sent to the function MUST be 8 bit, mono 8000hz
for converting samples I just used http://www.online-convert.com/ to convert my mp3s to this rather unusual format.

I am not using git right now, so I am sorry for not adding this to the official repo and just posting it here. Maybe Lars can add it.
To fix it just replace the following 2 files in the MonoBrickFirmware project and recompile

————————–
Sound\Speaker.cs
————————–

using System;
using System.IO;
using MonoBrickFirmware.Native;

namespace MonoBrickFirmware.Sound
{

public enum AudioMode{ Break = 0, Tone = 1, Play = 2, Repeat = 3, Service = 4}

public class Speaker
{
private UnixDevice soundDevice = new UnixDevice(“/dev/lms_sound”);
//private MemoryArea soundMemory;
private int currentVolume;
private const UInt16 beepFrequency = 600;
private const UInt16 buzzFrequency = 100;
private const UInt16 clickFrequency = 100;

private const UInt16 buzzDurationMs = 300;
private const UInt16 beepDurationMs = 300;
private const UInt16 clickDurationMs = 100;

private const int RIFF_HDR_SIZE = 44;
private const int RIFF_RIFF_SIG = 0×52494646;
private const int RIFF_WAVE_SIG = 0×57415645;
private const int RIFF_FMT_SIG = 0x666d7420;
private const short RIFF_FMT_PCM = 0×0100;
private const short RIFF_FMT_1CHAN = 0×0100;
private const short RIFF_FMT_8BITS = 0×0800;
private const int RIFF_DATA_SIG = 0×64617461;
private const int PCM_BUFFER_SIZE = 250;

public Speaker (int volume)
{
currentVolume = volume;
}

public int Volume {
get{return currentVolume;}
set{currentVolume = value; }
}

/// <summary>
/// Play a tone.
/// </summary>
/// <param name=”volume”>Volume.</param>
/// <param name=”frequency”>Frequency of the tone</param>
/// <param name=”durationMs”>Duration in ms.</param>
public void PlayTone(UInt16 frequency, UInt16 durationMs){
PlayTone(frequency,durationMs, Volume);
}

/// <summary>
/// Play a tone.
/// </summary>
/// <param name=”volume”>Volume.</param>
/// <param name=”frequency”>Frequency of the tone</param>
/// <param name=”durationMs”>Duration in ms.</param>
/// <param name=”durationMs”>Volume .</param>
public void PlayTone(UInt16 frequency, UInt16 durationMs, int volume){
if (volume < 0)
volume = -volume;
var command = new MonoBrickFirmware.Tools.ByteArrayCreator();
command.Append(AudioMode.Tone);
command.Append((byte)volume);
command.Append(frequency);
command.Append(durationMs);
command.Print();
soundDevice.Write(command.Data);
System.Threading.Thread.Sleep(durationMs);
}

/// <summary>
/// Make the brick beep
/// </summary>
public void Beep(){
Beep(beepDurationMs, Volume);
}

/// <summary>
/// Make the brick beep
/// </summary>
/// <param name=”durationMs”>Duration in ms.</param>
public void Beep(UInt16 durationMs){
Beep(durationMs, Volume);
}

/// <summary>
/// Make the brick beep
/// </summary>
/// <param name=”durationMs”>Duration in ms.</param>
/// <param name=”volume”>Volume of the beep</param>
public void Beep(UInt16 durationMs, int volume){
PlayTone(beepFrequency, durationMs,volume);
}

/// <summary>
/// Make the brick buzz
/// </summary>
public void Buzz ()
{
Buzz(buzzDurationMs, Volume);
}

/// <summary>
/// Make the brick buzz
/// </summary>
/// <param name=”durationMs”>Duration in ms.</param>
public void Buzz (UInt16 durationMs)
{
Buzz(durationMs, Volume);
}

/// <summary>
/// Make the brick buzz
/// </summary>
/// <param name=”durationMs”>Duration in ms.</param>
/// <param name=”volume”>Volume of the beep</param>
public void Buzz (UInt16 durationMs, int volume)
{
PlayTone(buzzFrequency, durationMs, volume);
}

/// <summary>
/// Make the brick click
/// </summary>
public void Click ()
{
Click(Volume);
}

/// <summary>
/// Make the brick click
/// </summary>
public void Click (int volume)
{
//Click(clickDurationMs, Volume);
}

/// <summary>
/// Play a sound file.
/// </summary>
/// <param name=”name”>Name the name of the file to play</param>
/// <param name=”volume”>Volume.</param>
/// <param name=”repeat”>If set to <c>true</c> the file will play in a loop</param>
public int PlaySoundFile(string name){
return PlaySoundFile(name, Volume);
}

private int readLSBInt(Stream d)
{
int val = d.ReadByte() & 0xff;
val |= (d.ReadByte() & 0xff) << 8;
val |= (d.ReadByte() & 0xff) << 16;
val |= (d.ReadByte() & 0xff) << 24;
return val;
}

private int readInt (Stream d)
{
int val = (d.ReadByte() & 0xff) << 24;
val |= (d.ReadByte() & 0xff) << 16;
val |= (d.ReadByte() & 0xff) << 8;
val |= (d.ReadByte() & 0xff);
return val;
}

private Int16 readShort(Stream d)
{

Int16 val = (Int16) ((d.ReadByte() & 0xff) << 8);
val |= (Int16) (d.ReadByte() & 0xff);
return val;
}

/// <summary>
///
/// </summary>
/// <param name=”name”></param>
/// <param name=”volume”></param>
/// <returns>0 == ok else error </returns>
public int PlaySoundFile(string name, int volume)
{

if (new FileInfo(name).Length < RIFF_HDR_SIZE)
throw new IOException(“Not a valid sound file”);

using (var afs = File.Open(name, FileMode.Open))
{
int offset = 0;
int sampleRate = 0;
int dataLen = 0;
//check file

if (readInt(afs) != RIFF_RIFF_SIG)
return -1;

readInt(afs);

if (readInt(afs) != RIFF_WAVE_SIG)
return -2;

if (readInt(afs) != RIFF_FMT_SIG)
return -3;

offset += 16;
int sz = readLSBInt(afs);

if (readShort(afs) != RIFF_FMT_PCM)
return -4;
if (readShort(afs) != RIFF_FMT_1CHAN)
return -5;

sampleRate = readLSBInt(afs);
readInt(afs);
readShort(afs);

if (readShort(afs) != RIFF_FMT_8BITS)
return -6;

// Skip any data in this chunk after the 16 bytes above
sz -= 16;
offset += 20 + sz;
while (sz– > 0)
afs.ReadByte();

// Skip optional chunks until we find a data sig (or we hit eof!)
for (; ; )
{
int chunk = readInt(afs);
dataLen = readLSBInt(afs);
offset += 8;
if (chunk == RIFF_DATA_SIG) break;
// Skip to the start of the next chunk
offset += dataLen;
while (dataLen– > 0)
afs.ReadByte();
}

if (volume < 0)
volume = -volume;

byte[] buf = new byte[2];
// get ready to play, set the volume
buf[0] = (byte)AudioMode.Play;
buf[1] = (byte)volume;
soundDevice.Write(buf);

buf = new byte[PCM_BUFFER_SIZE * 4 + 1];

while ((dataLen = afs.Read(buf, 1, buf.Length – 1)) > 0)
{
// now make sure we write all of the data
offset = 0;
while (offset < dataLen)
{
buf[offset] = (byte) AudioMode.Service;
int len = dataLen – offset;
if (len > PCM_BUFFER_SIZE) len = PCM_BUFFER_SIZE;

byte [] buf2 = new byte [len+1];
Array.Copy(buf, offset, buf2, 0, len + 1);
int bytesWritten = soundDevice.Write(buf2);
if (bytesWritten <= 0)
{
System.Threading.Thread.Sleep(1);
}
else
{
offset += bytesWritten;
}
}
}
}
return 0;
}

/// <summary>
/// Stops all sound playback.
/// </summary>
public void StopSoundPlayback(){
/*var command = new Command(0,0,123,reply);
command.Append(ByteCodes.Sound);
command.Append(SoundSubCodes.Break);
connection.Send(command);
if(reply){
var brickReply = connection.Receive();
Error.CheckForError(brickReply,123);
}*/
}
}
}

————————–
native\libc.cs
————————–

using System;
using System.Runtime.InteropServices;

namespace MonoBrickFirmware.Native
{
static public class Libc
{
public enum OpenFlags
{
O_RDONLY = 0×0000, /* open for reading only */
O_WRONLY = 0×0001, /* open for writing only */
O_RDWR = 0×0002, /* open for reading and writing */
O_ACCMODE = 0×0003 /* mask for above modes */
}
public enum ProtectionFlags
{
PROT_NONE = 0,
PROT_READ = 1,
PROT_WRITE = 2,
PROT_EXEC = 4
}
public enum MMapFlags
{
MAP_FILE = 0,
MAP_SHARED = 1,
MAP_PRIVATE = 2,
MAP_TYPE = 0xf,
MAP_FIXED = 0×10,
MAP_ANONYMOUS = 0×20,
MAP_ANON = 0×20
}

[DllImport("libc.so.6")]
public static extern int open(byte[] name, OpenFlags flags);

[DllImport("libc.so.6")]
public static extern IntPtr mmap(IntPtr addr, uint len, ProtectionFlags prot, MMapFlags flags, int fd, int offset);

[DllImport("libc.so.6")]
public static extern int write(int file, IntPtr buffer, uint count);

[DllImport("libc.so.6")]
public static extern int read(int file, IntPtr buffer,uint length);

[DllImport("libc.so.6")]
public static extern int ioctl(int fd, int cmd, IntPtr buffer);

[DllImport("libc.so.6")]
public static extern int close(int fd);
}

public class UnixDevice : IDisposable
{
static System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
int fd = -1;
public UnixDevice(string name)
{

fd = Libc.open(encoding.GetBytes(name + Char.MinValue), Libc.OpenFlags.O_RDWR);
if (fd < 0)
throw new InvalidOperationException(“Couldn’t open device: ” + name);
}

public MemoryArea MMap(uint size, int offset)
{
IntPtr ptr = Libc.mmap(IntPtr.Zero, size, Libc.ProtectionFlags.PROT_READ | Libc.ProtectionFlags.PROT_WRITE, Libc.MMapFlags.MAP_SHARED, fd, offset);
if ((int)ptr == -1)
throw new InvalidOperationException(“MMap operation failed”);
return new MemoryArea(ptr, size);
}

public int Write (byte[] data)
{
IntPtr pnt = IntPtr.Zero;
bool hasError = false;
Exception inner = null;
int bytesWritten = 0;

try {
int size = Marshal.SizeOf (data [0]) * data.Length;
pnt = Marshal.AllocHGlobal (size);
Marshal.Copy (data, 0, pnt, data.Length);
bytesWritten = Libc.write (fd, pnt,(uint) size);
if (bytesWritten == -1)
hasError = true;
}
catch (Exception e) {
hasError = true;
inner = e;
}
finally {
if (pnt != IntPtr.Zero)
Marshal.FreeHGlobal (pnt);
}
if (hasError) {
if (inner != null) {
throw inner;
}
else
{
throw new InvalidOperationException(“Failed to write to Unix device”);
}
}
return bytesWritten;
}

public byte[] Read (int length)
{

byte[] reply = new byte[length];
Exception inner = null;
IntPtr pnt = IntPtr.Zero;
int bytesRead = 0;
bool hasError = false;
try {
pnt = Marshal.AllocHGlobal (Marshal.SizeOf (reply [0]) * length);
bytesRead = Libc.read (fd, pnt, (uint)length);
if (bytesRead == -1) {
hasError = true;
Marshal.FreeHGlobal (pnt);
pnt = IntPtr.Zero;
} else {
if (bytesRead != length)
reply = new byte[bytesRead];
Marshal.Copy (pnt, reply, 0, bytesRead);
Marshal.FreeHGlobal (pnt);
pnt = IntPtr.Zero;
}

} catch (Exception e) {
hasError = true;
inner = e;
} finally {
if (pnt != IntPtr.Zero) {
Marshal.FreeHGlobal(pnt);
}
}
if(hasError){
if (inner != null) {
throw inner;
}
else
{
throw new InvalidOperationException(“Failed to read from Unix device”);
}
}
return reply;
}

/// <summary>
/// IO control command that copies to the IO output to a buffer
/// </summary>
/// <returns>Zero if successful</returns>
/// <param name=”cmd”>IoCtl request code</param>
/// <param name=”input”>Input arguments</param>
/// <param name=”output”>Output buffer</param>
/// <param name=”ioOutputIndex”>IO start index to copy to output buffer</param>
public int IoCtl (int cmd, byte[] input, byte[] output, int indexToOutput)
{
IntPtr pnt = IntPtr.Zero;
bool hasError = false;
Exception inner = null;
int result = -1;
try {
int size = Marshal.SizeOf (typeof(byte))* input.Length;
pnt = Marshal.AllocHGlobal (size);
Marshal.Copy (input, 0, pnt, input.Length);
result = Libc.ioctl (fd, cmd, pnt);
if (result == -1) {
hasError = true;
} else {
output = new byte[input.Length - indexToOutput];
Marshal.Copy (pnt, output, indexToOutput, input.Length – indexToOutput);
}
} catch (Exception e) {
hasError = true;
inner = e;
} finally {
if (pnt != IntPtr.Zero) {
Marshal.FreeHGlobal (pnt);
}
}
if (hasError) {
if (inner != null) {
throw inner;
}
else
{
throw new InvalidOperationException(“Failed to excute IO control command”);
}
}
return result;
}

/// <summary>
/// IO control command. Output is copied back to buffer
/// </summary>
/// <returns>Zero if successful</returns>
/// <param name=”requestCode”>IoCtl request code</param>
/// <param name=”arguments”>IO arguments</param>
public int IoCtl (int requestCode, byte[] arguments)
{
IntPtr pnt = IntPtr.Zero;
bool hasError = false;
Exception inner = null;
int result = -1;
try {
int size = Marshal.SizeOf (typeof(byte))* arguments.Length;
pnt = Marshal.AllocHGlobal (size);
Marshal.Copy (arguments, 0, pnt, arguments.Length);
result = Libc.ioctl (fd, requestCode, pnt);
if (result == -1) {
hasError = true;
}
else{
Marshal.Copy (pnt, arguments, 0, arguments.Length);
}
} catch (Exception e) {
hasError = true;
inner = e;
} finally {
if (pnt != IntPtr.Zero) {
Marshal.FreeHGlobal (pnt);
}
}
if (hasError) {
if (inner != null) {
throw inner;
}
else
{
throw new InvalidOperationException(“Failed to excute IO control command”);
}
}
return result;
}

public void Dispose()
{
Libc.close(fd);
fd = -1;
}

~UnixDevice()
{
if (fd >= 0)
{
Libc.close(fd);
}
}
}

public class MemoryArea
{
IntPtr ptr;
uint size;
public MemoryArea(IntPtr ptr, uint size)
{
this.ptr = ptr;
this.size = size;
}

/// <summary>
/// Write a byte array to the memory map
/// </summary>
/// <param name=”data”>Data.</param>
public void Write (byte[] data)
{
Write(0, data);
}

/// <summary>
/// Write a byte array to the memory map
/// </summary>
/// <param name=”offset”>Memory map offset</param>
/// <param name=”data”>Data to write</param>
public void Write (int offset, byte[] data)
{
if (offset + data.Length > size)
throw new IndexOutOfRangeException (string.Format (“Out of range accessing index {0}, max {1}”, offset + data.Length, size));
if (offset != 0) {
Marshal.Copy (data, 0, ptr.Add (offset), data.Length);
}
else
{
Marshal.Copy (data, 0, ptr , data.Length);
}
}

/// <summary>
/// Copy the whole memory map into an array and return it
/// </summary>
public byte[] Read ()
{
return Read(0,(int)size);
}

/// <summary>
/// Copy part of the memory map into an array and return it
/// </summary>
/// <param name=”offset”>Memory map offset</param>
/// <param name=”length”>Number of bytes to read</param>
public byte[] Read (int offset, int length)
{
if (offset + length > size)
throw new IndexOutOfRangeException (string.Format (“Out of range accessing index {0}, max {1}”, offset + length, size));
byte[] reply = new byte[length];
if (offset != 0) {
Marshal.Copy (ptr.Add(offset), reply, 0, length);
}
else
{
Marshal.Copy (ptr, reply, 0, length);
}
return reply;
}
}

public static class IntPtrExtensions
{
/// <summary>
/// Add a value to a int pointer
/// </summary>
/// <param name=”ptr”>Pointer to add value to</param>
/// <param name=”val”>Value to add</param>
public static IntPtr Add(this IntPtr ptr, int val)
{
return new IntPtr(ptr.ToInt64() + val);
}
}
}

Posted in

Make a donation