Hi all, I've created a patch for supporting for the NXT RSO / EV3 RSF (Robot Sound File) format. It is a format used on the stock firmware of the LEGO NXT and EV3 programmable bricks. I thought that it would be nice to have native support in SoX.
The format is capable of holding U8 PCM or ADPCM samples. Currently decoding of both formats is implemented. However for encoding I have disabled the ADPCM support, because the created files sound very wrong on the real brick. U8 PCM mode on the other hand works well. RSO support is implemented also in FFmpeg, just the RSF alias isn't. I am posting the exported commit below. However, it is also available from GitHub: https://github.com/JakubVanek/sox-rsf/commit/f906852451a2efc5b1129c7b34bfc378d3f4da62.patch Regards, Jakub Vanek >From f906852451a2efc5b1129c7b34bfc378d3f4da62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Van=C4=9Bk?= <vanek.jak...@seznam.cz> Date: Wed, 15 May 2019 22:33:52 +0200 Subject: [PATCH] rso: add support for NXT RSO / EV3 RSF format --- FEATURES.in | 1 + msvc10/LibSoX.vcxproj | 1 + msvc10/LibSoX.vcxproj.filters | 3 + msvc9/LibSoX.vcproj | 4 + soxformat.7 | 7 + src/CMakeLists.txt | 1 + src/Makefile.am | 2 +- src/formats.c | 2 + src/formats.h | 1 + src/rso.c | 233 ++++++++++++++++++++++++++++++++++ 10 files changed, 254 insertions(+), 1 deletion(-) create mode 100644 src/rso.c diff --git a/FEATURES.in b/FEATURES.in index 43ab6c69bbe233f3cc45c90e9ae97f5d3d549428..7c1f33a9f26850fb81190e37ae461da7b2d9537a 100644 --- a/FEATURES.in +++ b/FEATURES.in @@ -42,6 +42,7 @@ The current release handles the following audio file formats: * Maxis XA Audio files ** EA ADPCM (read support only, for now) * Pseudo formats that allow direct playing/recording from most audio devices +* NXT RSO/EV3 RSF (Robot Sound File) files * The "null" pseudo-file that reads and writes from/to nowhere (:tableend:) diff --git a/msvc10/LibSoX.vcxproj b/msvc10/LibSoX.vcxproj index b2e9d5a3583f12d5cb9af2fdd5015d55e6eefa92..d00dbe6a551b9dc81968ac88db857ee11f299dfa 100644 --- a/msvc10/LibSoX.vcxproj +++ b/msvc10/LibSoX.vcxproj @@ -281,6 +281,7 @@ <ClCompile Include="..\src\prc.c" /> <ClCompile Include="..\src\pvf.c" /> <ClCompile Include="..\src\raw-fmt.c" /> + <ClCompile Include="..\src\rso.c" /> <ClCompile Include="..\src\s1-fmt.c" /> <ClCompile Include="..\src\s2-fmt.c" /> <ClCompile Include="..\src\s3-fmt.c" /> diff --git a/msvc10/LibSoX.vcxproj.filters b/msvc10/LibSoX.vcxproj.filters index f39449cadd1d22d207fc57c12f0ab2c08c6c6240..9ac27753686059c9511b7aee6e30171ec7d8e083 100644 --- a/msvc10/LibSoX.vcxproj.filters +++ b/msvc10/LibSoX.vcxproj.filters @@ -435,6 +435,9 @@ <ClCompile Include="..\src\raw-fmt.c"> <Filter>Format Sources</Filter> </ClCompile> + <ClCompile Include="..\src\rso.c"> + <Filter>Format Sources</Filter> + </ClCompile> <ClCompile Include="..\src\s1-fmt.c"> <Filter>Format Sources</Filter> </ClCompile> diff --git a/msvc9/LibSoX.vcproj b/msvc9/LibSoX.vcproj index 362f8d772daa9501e061600e9725009fc331fdfe..11c9423478a02072436225262502c2714ce4f485 100644 --- a/msvc9/LibSoX.vcproj +++ b/msvc9/LibSoX.vcproj @@ -869,6 +869,10 @@ RelativePath="..\src\au.c" > </File> + <File + RelativePath="..\src\rso.c" + > + </File> <File RelativePath="..\src\avr.c" > diff --git a/soxformat.7 b/soxformat.7 index 5e53b9f9600d94dec11f445b379938afb2227409..9f60eac0ed3317225647132da218a1044da5ad77 100644 --- a/soxformat.7 +++ b/soxformat.7 @@ -652,6 +652,13 @@ and .BR sox (1) .BR \-d . .TP +\fB.rso\fR, \fB.rsf\fR +NXT RSO / EV3 Robot Sound Files. A simple file format used in the stock +NXT and EV3 firmwares. +The format can store either U8 or IMA ADPCM samples. However only the U8 +mode is enabled, as the IMA ADPCM mode has some problems. +Also, only the 8000 Hz sample rate and a single channel are supported +.TP .B .txw Yamaha TX-16W sampler. A file format from a Yamaha sampling keyboard which wrote IBM-PC diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index bde10d6a96c8dd4c95260c2158ac9901f51a0604..41a2ee4a51391ebc9e572e4422078386e1299c7a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -111,6 +111,7 @@ set(formats_srcs prc raw raw-fmt + rso s1-fmt s2-fmt s3-fmt diff --git a/src/Makefile.am b/src/Makefile.am index d5f6d125fb2822600eb56eab2a10f3eade28ecc7..ed547cd2555563bff3c15ab402eee67c2da36488 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -121,7 +121,7 @@ libsox_la_SOURCES += raw-fmt.c s1-fmt.c s2-fmt.c s3-fmt.c \ lu-fmt.c 8svx.c aiff-fmt.c aifc-fmt.c au.c avr.c cdr.c cvsd-fmt.c \ dvms-fmt.c dat.c hcom.c htk.c maud.c prc.c sf.c smp.c \ sounder.c soundtool.c sphere.c tx16w.c voc.c vox-fmt.c ima-fmt.c adpcm.c adpcm.h \ - ima_rw.c ima_rw.h wav.c wve.c xa.c nulfile.c f4-fmt.c f8-fmt.c gsrt.c + ima_rw.c ima_rw.h wav.c wve.c xa.c nulfile.c f4-fmt.c f8-fmt.c gsrt.c rso.c libsox_la_LIBADD += @GSM_LIBS@ @LIBGSM_LIBADD@ libsox_la_LIBADD += @LPC10_LIBS@ @LIBLPC10_LIBADD@ diff --git a/src/formats.c b/src/formats.c index f3efe764cbb2adb8514bec4c4d98ffa4f12daffa..a2e2da51a939b4b6fdbf2b4e73875adf6638c170 100644 --- a/src/formats.c +++ b/src/formats.c @@ -88,6 +88,8 @@ static char const * auto_detect_format(sox_format_t * ft, char const * ext) CHECK(sf , 0, 0, "" , 0, 4, "\144\243\004\0") CHECK(sox , 0, 0, "" , 0, 4, ".SoX") CHECK(sox , 0, 0, "" , 0, 4, "XoS.") + CHECK(rso , 0, 0, "" , 0, 2, "\x01\x00") + CHECK(rso , 0, 0, "" , 0, 2, "\x01\x01") if (ext && !strcasecmp(ext, "snd")) CHECK(sndr , 7, 1, "" , 0, 2, "\0") diff --git a/src/formats.h b/src/formats.h index a42ce2703929ccf6f210089706f5924e612ccc33..e8341ac0863cadecc1f089163efc7cd286f3be01 100644 --- a/src/formats.h +++ b/src/formats.h @@ -62,6 +62,7 @@ FORMAT(wav) FORMAT(wve) FORMAT(xa) + FORMAT(rso) /*--------------------- Plugin or static format handlers ---------------------*/ diff --git a/src/rso.c b/src/rso.c new file mode 100644 index 0000000000000000000000000000000000000000..92256d130c63f79d1a8eb6ddf5db4197796b5f6b --- /dev/null +++ b/src/rso.c @@ -0,0 +1,233 @@ +/* (c) 2019 SoX contributors + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library 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 Lesser + * General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "sox_i.h" +#include "adpcms.h" +#include "sox.h" +#include "stdio.h" + +typedef struct { + adpcm_io_t adpcm; + sox_bool is_adpcm; +} priv_t; + +#define RSO_MAGIC_U8 0x0100 +#define RSO_MAGIC_ADPCM 0x0101 +#define RSO_LENGTH_EMPTY 0x0000 +#define RSO_MODE_DEFAULT 0x0000 + +enum { + LENGTH_ERROR = -1, + LENGTH_READY = 0, + LENGTH_MISSING = 1 +}; + +static int lsx_rso_calc_length(sox_format_t *ft, priv_t *priv, uint16_t *for_header) { + sox_uint64_t samples = ft->olength ? ft->olength : ft->signal.length; + sox_uint64_t length = 0; + + if (samples) { + if (priv->is_adpcm) { + length = (samples + 1) / 2; + } else { + length = samples; + } + if (length > 65535) { + return LENGTH_ERROR; + } else { + *for_header = (uint16_t)length; + return LENGTH_READY; + } + } else { + return LENGTH_MISSING; + } +} + +static int lsx_rso_startread(sox_format_t *ft) { + priv_t *priv = (priv_t*) ft->priv; + uint16_t hdr_magic = 0; + uint16_t hdr_bytes = 0; + uint16_t hdr_rate = 0; + uint16_t hdr_mode = 0; + + sox_encoding_t sample_type; + uint64_t sample_count; + unsigned bits_per_sample; + int rval; + + if (lsx_readw(ft, &hdr_magic) != SOX_SUCCESS || + lsx_readw(ft, &hdr_bytes) != SOX_SUCCESS || + lsx_readw(ft, &hdr_rate) != SOX_SUCCESS || + lsx_readw(ft, &hdr_mode) != SOX_SUCCESS) { + return SOX_EOF; + } + + if (hdr_magic == RSO_MAGIC_U8) { + priv->is_adpcm = sox_false; + + sample_count = hdr_bytes * 1; + sample_type = SOX_ENCODING_UNSIGNED; + bits_per_sample = 8; + + } else if (hdr_magic == RSO_MAGIC_ADPCM) { + priv->is_adpcm = sox_true; + + sample_count = hdr_bytes * 2; + sample_type = SOX_ENCODING_IMA_ADPCM; + bits_per_sample = 4; + + } else { + lsx_fail_errno(ft, SOX_EHDR, "`%s': unknown sample format", ft->filename); + return SOX_EOF; + } + + rval = lsx_check_read_params(ft, + 1 /* channels */, + 1.0 * hdr_rate, + sample_type, + bits_per_sample, + sample_count, + sox_true /* verify length */); + if (rval != SOX_SUCCESS) { + return rval; + } + + if (priv->is_adpcm) { + return lsx_adpcm_ima_start(ft, &priv->adpcm); + } else { + return lsx_rawstartread(ft); + } +} + +static size_t lsx_rso_read(sox_format_t *ft, sox_sample_t *buf, size_t len) { + priv_t *priv = (priv_t*) ft->priv; + + if (priv->is_adpcm) { + return lsx_adpcm_read(ft, &priv->adpcm, buf, len); + } else { + return lsx_rawread(ft, buf, len); + } +} + +static int lsx_rso_stopread(sox_format_t *ft) { + priv_t *priv = (priv_t*) ft->priv; + + if (priv->is_adpcm) { + return lsx_adpcm_stopread(ft, &priv->adpcm); + } + + return SOX_SUCCESS; +} + + +static int lsx_rso_startwrite(sox_format_t *ft) { + priv_t *priv = (priv_t*) ft->priv; + uint16_t magic; + + if (ft->encoding.encoding == SOX_ENCODING_UNSIGNED) { + priv->is_adpcm = sox_false; + magic = RSO_MAGIC_U8; + + } else if (ft->encoding.encoding == SOX_ENCODING_IMA_ADPCM) { + priv->is_adpcm = sox_true; + magic = RSO_MAGIC_ADPCM; + + } else { + lsx_fail_errno(ft, SOX_EFMT, "`%s': sample encoding is not supported", ft->filename); + return SOX_EOF; + } + + if (lsx_writew(ft, magic) != SOX_SUCCESS || + lsx_writew(ft, RSO_LENGTH_EMPTY) != SOX_SUCCESS || + lsx_writew(ft, (uint16_t)ft->signal.rate) != SOX_SUCCESS || + lsx_writew(ft, RSO_MODE_DEFAULT) != SOX_SUCCESS) { + return SOX_EOF; + } + + ft->data_start = lsx_tell(ft); + + if (priv->is_adpcm) { + return lsx_adpcm_ima_start(ft, &priv->adpcm); + } else { + return lsx_rawstartwrite(ft); + } +} + +static size_t lsx_rso_write(sox_format_t *ft, const sox_sample_t *buf, size_t len) { + priv_t *priv = (priv_t*) ft->priv; + + if (priv->is_adpcm) { + return lsx_adpcm_write(ft, &priv->adpcm, buf, len); + } else { + return lsx_rawwrite(ft, buf, len); + } +} + +static int lsx_rso_stopwrite(sox_format_t *ft) { + priv_t *priv = (priv_t*) ft->priv; + off_t target = (off_t)ft->data_start - 6; + uint16_t hdr_bytes = 0; + + if (priv->is_adpcm) { + if (lsx_adpcm_stopwrite(ft, &priv->adpcm) != SOX_SUCCESS) { + lsx_warn("IMA ADPCM stop failed"); + return SOX_EOF; + } + } + + if (ft->seekable) { + int rval = lsx_rso_calc_length(ft, priv, &hdr_bytes); + + if (rval == LENGTH_ERROR) { + lsx_warn("`%s': output file is too long, length header will be truncated", ft->filename); + hdr_bytes = 0xFFFF; + + } else if (rval == LENGTH_MISSING) { + lsx_warn("`%s': file length not available, file header will indicate zero length", ft->filename); + hdr_bytes = 0x0000; + } + + if (lsx_seeki(ft, target, SEEK_SET) != SOX_SUCCESS || + lsx_writew(ft, hdr_bytes) != SOX_SUCCESS) { + lsx_warn("`%s': seek or header write failed", ft->filename); + return SOX_EOF; + } + } else { + lsx_warn("`%s': seeking is not possible, the output file will have broken length header", ft->filename); + } + + return SOX_SUCCESS; +} + +LSX_FORMAT_HANDLER(rso) +{ + static char const * const names[] = {"rso", "rsf", NULL}; + static unsigned const write_encodings[] = { + SOX_ENCODING_UNSIGNED, 8, 0, +// SOX_ENCODING_IMA_ADPCM, 4, 0, // playback broken on EV3? + 0}; + static sox_rate_t const sample_rates[] = { + 8000.0, 0.0 + }; + static sox_format_handler_t handler = {SOX_LIB_VERSION_CODE, + "NXT RSO / EV3 Robot Sound File", names, SOX_FILE_MONO | SOX_FILE_BIG_END, + lsx_rso_startread, lsx_rso_read, lsx_rso_stopread, + lsx_rso_startwrite, lsx_rso_write, lsx_rso_stopwrite, + lsx_rawseek, write_encodings, sample_rates, sizeof(priv_t) + }; + return &handler; +} -- 2.17.1 _______________________________________________ SoX-devel mailing list SoX-devel@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/sox-devel