[SOLVED] 24bits/96kHz conversion of s32le to f32le in a stdout to stdin pipe? - Page 1
Forum section: electronics / general

author: RoGeorge
author_rank: Super Contributor
timestamp: August 07, 2022, 07:27:37 am
content: How to pipe 24bits samples from the sound card, as float 32 into another program?No resampling please, only data format conversion.There are 3 different formats to deal with:- the source (ALSA driver of an audio card) produces 24bits at each sample- the 24 bits are packed as S32_LE by the recording program, arecord, and can pipe to stdout- the playing program, baudline (an audio spectrum analyzer), doesn't know s32le, but can read float 32 from stdinThe goal is to get all the 24bits into 'baudline', without lowering the resolution at 16bits and without resampling.Tried so far:1. - piping through sox, very cumbersome syntax and slows down the frame rate in 'baudline'2. - convert the format with python:- read S32_LE from stdin- convert S32_LE to f32le- write f32le to stdoutThe Python program is an example from stackoverflow, which works, except 'baudline' displays rubbish, something is not OK with the format conversion (inside the process_data(buffer) there are a few failed attempts to convert, all commented out now):Code:#!/usr/bin/env python3######### g.py ########## run python3 with stin stdout stderr unbuffered (-u)# arecord -D hw:1,4 -B 10000 -r 96000 -f S32_LE -c 2 | python3 -u ./le32_to_le32f.py | aplay## arecord -D hw:1,2 -B 10000 -r 96000 -f S32_LE -c 2 | \# python3 -u ./le32s_to_le32f.py | \# ./baudline -stdin -record -channels 2 -format le32fimport signalimport sysfrom struct import pack, unpackdef process_data(buffer):# convert s32le into f32le## buffer = pack('<f', float((int.from_bytes(buffer, "little")/256)))##pack('<f', float((int.from_bytes(buffer, "little")/256)))## pack('<f', float(int.from_bytes(buffer, "little", signed=True))/float(2**23) )## print(buffer)## print(int.from_bytes(buffer, "little", signed=True))## print(int.from_bytes(buffer, "little", signed=True)/256)## print(float(int.from_bytes(buffer, "little", signed=True)))## print()sys.stdout.buffer.write(buffer)sys.stdout.buffer.flush()# def read_stdin_stream(handler, chunk_size=1024):def read_stdin_stream(handler, chunk_size=8192):with sys.stdin as f:while True:buffer = f.buffer.read(chunk_size)if buffer == b'':breakhandler(buffer)def signal_handler(sig, frame):sys.stdout.buffer.flush()sys.exit(0)def main():signal.signal(signal.SIGINT, signal_handler)# notice the `chunk_size` of 1 for this particular example# read_stdin_stream(process_data, chunk_size=1)read_stdin_stream(process_data, chunk_size=4)if __name__ == "__main__":main()Tried to read the docs fromhttps://docs.python.org/3.8/library/struct.html#struct-examplesand the first example there doesn't work:Code:$ python3Python 3.8.10 (default, Jun 22 2022, 20:18:18)[GCC 9.4.0] on linuxType "help", "copyright", "credits" or "license" for more information.>>> from struct import *>>> pack('hhl', 1, 2, 3)b'\x01\x00\x02\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00'>>> unpack('hhl', b'\x00\x01\x00\x02\x00\x00\x00\x03')Traceback (most recent call last):File "<stdin>", line 1, in <module>struct.error: unpack requires a buffer of 16 bytes>>> calcsize('hhl')16>>>Since not even the example from the manual works, thought I might ask instead of keep poking blindly at it.To show the alignement of the 24bits inside the S32_LE, this is what a hex dump for noise only looks like (the small +/- noise signal around zero is useful here to see the layout of the 24bits into 32 and the endianness):Code:$ arecord -v -D hw:1,2 -B 10000 -r 128000 -f S32_LE -c 2 | hd...0019b0f0 00 60 00 00 00 a0 ff ff 00 c0 01 00 00 e0 fd ff |.`..............|0019b100 00 a0 fd ff 00 90 05 00 00 10 03 00 00 60 fa ff |.............`..|0019b110 00 40 ff ff 00 30 03 00 00 e0 fe ff 00 e0 00 00 |.@...0..........|0019b120 00 90 04 00 00 40 ff ff 00 d0 fd ff 00 60 02 00 |.....@.......`..|0019b130 00 a0 fe ff 00 d0 fb ff 00 70 04 00 00 10 02 00 |.........p......|0019b140 00 10 f9 ff 00 a0 02 00 00 20 06 00 00 a0 fb ff |......... ......|0019b150 00 90 fe ff 00 30 04 00 00 70 fd ff 00 c0 ff ff |.....0...p......|0019b160 00 10 03 00 00 f0 fa ff 00 60 fe ff 00 d0 04 00 |.........`......|If there is a 4 bytes variable called "buffer", how to convert "buffer" from S32_LE into f32le, preferably using struct from Python?

author: golden_labels
author_rank: Super Contributor
timestamp: August 07, 2022, 11:39:47 am
content: Note: float32 has a 23-bit mantissa, so you will inevitably lose 1 bit at the maximum level.What about usingffmpeg?Code:ffmpeg -f s32le -i pipe: -c:a pcm_f32le -f f32le pipe:The first-fsets the input format,-i pipe:instructs ffmpeg to read from stdin,-c:asets the output codec to PCM (float32 LE) and the second-fthe output file format to raw float32 LE.

author: DiTBho
author_rank: Super Contributor
timestamp: August 07, 2022, 11:43:29 am
content: sos32lemeans "PCM signed 32-bit little-endian"

author: magic
author_rank: Super Contributor
timestamp: August 07, 2022, 12:37:01 pm
content: Good enough for x86/ARM Linux.Code:#include <stdio.h>int main() {int i;while (fread(&i, 4, 1, stdin)) {float f = i;fwrite(&f, 4, 1, stdout);}}

author: adeuring
author_rank: Contributor
timestamp: August 07, 2022, 03:24:44 pm
content: Quote from: RoGeorge on August 07, 2022, 07:27:37 amWelcome to the messy world of C types! I guess that the documentation was written on a 32 bit platform:hrepresents a C short integer, which has quite reliably 16 bits;lrepresents a C long integer, which has 32 bits in the example from the documentation but 64 bits on your machine...If you run the example with a slight modification, it works:Code:>>> from struct import *>>> pack('hhl', 1, 2, 3)b'\x01\x00\x02\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00'>>> unpack('hhl', b'\x01\x00\x02\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00')(1, 2, 3)>>> unpack('hhl', pack('hhl', 1, 2, 3))(1, 2, 3)>>>(Just use your mouse to copy the result of the pack() call as an argument into the unpack() call.)The table under "Format Characters" in the docs (https://docs.python.org/3.8/library/struct.html#format-characters) mentionsias another 32bit integer type which seems to indeed have 32 bits on today's 64bit platforms:Code:>>> pack('i', 2)b'\x02\x00\x00\x00'So, trypack('hhi', 1, 2, 3)instead.QuoteI am not absolutely sure about the meaning of "S32_LE" and "f32le". Assuming that "S32_LE" means a 32 bit integer in "little endian" order and "f32le" means a 32 bit float number in "little endian" order, this might work(Seehttps://docs.python.org/3.8/library/struct.html#byte-order-size-and-alignment):Code:>>> # Create 32 bit "example input".>>> pack('<i', 123123)b'\xf3\xe0\x01\x00'>>> # unpack the 4 byte string, convert to "Python internal float" and pack as 32bit float>>> pack('<f', float(unpack('<i', b'\xf3\xe0\x01\x00')))b'\x80y\xf0G'Final remarks:1. @magic showed a nice example of a much more efficient C solution for your problem. Using Python introduces a huge amount of overhead. (Numpy has, IIRC, also some mechanisms to load arrays from/into byte sequences or buffers - that might be another option. Not as fast as a pure C implementation but for sure faster than my example if used for buffers with, let's say, 512 4 byte numbers or more.2. There is a reason why building a C program from source starts quite often with running a script called "configure" or "autoconf". One important purpose is to figure out which size a given C type has for the current platform and compiler. The source code of these propgrams does not use the standard C types like int, short or long, but int8, int16, int32, which are defined in special C header files that were generated by configure or autoconf.

author: DiTBho
author_rank: Super Contributor
timestamp: August 07, 2022, 03:46:37 pm
content: Quote from: adeuring on August 07, 2022, 03:24:44 pmI haven't checked the doc, but if S32_LE means signed 32 bit LE, well .. I am not sure about the range of the final float32 le, may be (if it goes between -1..+1) it requires normalization or adjustment

author: adeuring
author_rank: Contributor
timestamp: August 07, 2022, 03:50:35 pm
content: Quote from: DiTBho on August 07, 2022, 03:46:37 pmOops... You are of course right – some kind of "scaling" is most likely necessary.

author: Nominal Animal
author_rank: Super Contributor
timestamp: August 07, 2022, 04:23:13 pm
content: For conversion of native-endianint32_ttofloatin a pipe, I'd use something likeCode:// SPDX-License-Identifier: CC0-1.0//// Converts native byte order signed 32-bit integers to native byte order floats.//#include <stdlib.h>#include <unistd.h>#include <stdint.h>#ifndef BLOCK_SIZE#define BLOCK_SIZE 8192#endifint main(void){int32_t in_buf;float out_buf;while (1) {size_t have = 0;/* Read some (complete) int32_t's. */do {ssize_t n = read(STDIN_FILENO, (char *)(in_buf) + have, (sizeof in_buf) - have);if (n > 0)have += n;elseif (n == 0)return EXIT_SUCCESS;elsereturn EXIT_FAILURE;} while (!have || (have % sizeof in_buf));/* Count in number of samples in input. */have /= sizeof in_buf;/* Convert to float. */{float *const end = out_buf + have;int32_t *src = in_buf;float *dst = out_buf;while (dst < end)*(dst++) = *(src++);}/* Write floats. */{const char *const end = (const char *)(out_buf + have);const char *ptr = (const char *)(out_buf);while (ptr < end) {ssize_t n = write(STDOUT_FILENO, ptr, (size_t)(end - ptr));if (n > 0)ptr += n;elsereturn EXIT_FAILURE;}}}/* Never reached. */return EXIT_FAILURE;}Note that it uses low-level<unistd.h>I/O, to avoid any sort of buffering.Interestingly, this only uses three syscalls:read,write, andreturn/exit_group, and in POSIXy systems including Linux,STDIN_FILENO==0andSTDOUT_FILENO == 1, so one could write this in freestanding C in Linux, using e.g. GCC extended asm for the syscall wrappers on each target architecture.

author: Nominal Animal
author_rank: Super Contributor
timestamp: August 07, 2022, 04:56:18 pm
content: Here's the freestanding version for Linux on x86 or x86-64:Code:// SPDX-License-Identifier: CC0-1.0//// Converts native byte order signed 32-bit integers to native byte order floats.//// To compile, use// gcc -Wall -O2 -static -s -ffreestanding -nostdlib this.c -o executable//#include <stdint.h>#if defined(__x86_64__)/** Linux on x86-64*/#define SYS_exit 60#define SYS_read 0#define SYS_write 1static inline long syscall3(long num, long arg1, long arg2, long arg3){long ret;asm volatile ( "syscall": "=a" (ret): "a" (num), "D" (arg1), "S" (arg2), "d" (arg3): "rcx", "r11", "memory");return ret;}static inline long syscall1(long num, long arg1){long ret;asm volatile ( "syscall": "=a" (ret): "a" (num), "D" (arg1): "rcx", "r11", "memory");return ret;}#elif defined(__i386__)/** Linux on x86*/#define SYS_exit 1#define SYS_read 3#define SYS_write 4static inline long syscall3(long num, long arg1, long arg2, long arg3){long ret;asm volatile ( "int $0x80": "=a" (ret): "a" (num), "b" (arg1), "c" (arg2), "d" (arg3): "cc", "memory");return ret;}static inline long syscall1(long num, long arg1){long ret;asm volatile ( "int $0x80": "=a" (ret): "a" (num), "D" (arg1): "ecx", "edx", "cc", "memory");return ret;}#else#error Unsupported architecture.#endif/** Linux kernel interface (same across architectures)*/static inline long read_stdin(void *buffer, long length){return syscall3(SYS_read, 0, (long)buffer, length);}static inline long write_stdout(const void *buffer, long length){return syscall3(SYS_write, 1, (long)buffer, length);}static inline void exit_success(void){syscall1(SYS_exit, 0);while (1) /* nothing */;}static inline void exit_failure(void){syscall1(SYS_exit, 1);while (1) /* nothing */;}/** Application itself*/#ifndef BLOCK_SIZE#define BLOCK_SIZE 8192#endifvoid _start(void){int32_t in_buf;float out_buf;while (1) {long have = 0;/* Read some (complete) int32_t's. */do {long n = read_stdin((char *)(in_buf) + have, (sizeof in_buf) - have);if (n > 0)have += n;elseif (n == 0)exit_success();elseexit_failure();} while (!have || (have % sizeof in_buf));/* Count in number of samples in input. */have /= sizeof in_buf;/* Convert to float. */{float *const end = out_buf + have;int32_t *src = in_buf;float *dst = out_buf;while (dst < end)*(dst++) = *(src++);}/* Write floats. */{const char *const end = (const char *)(out_buf + have);const char *ptr = (const char *)(out_buf);while (ptr < end) {long n = write_stdout(ptr, (long)(intptr_t)(end - ptr));if (n > 0)ptr += n;elseexit_failure();}}}/* Never reached. */exit_success();}Compiles to a static binary less than 9k in size. There's a lot of bloat even in standard C nowadays...

author: RoGeorge
author_rank: Super Contributor
timestamp: August 08, 2022, 06:47:15 am
content: So far I've accidentally found that 'arecord' first puts a header before streaming, and now suspect 'baudline' might read it and take hints about the data format from that header, so maybe I should prepare a proper header for float instead of turning the header itself into float.Also 'baudline' might expect the float to be scaled down to +/-1.0 range.Posting an 'arecord' header grab and RIFF WAVfmt header format just for the docs:Code:// arecord RIFF WAVfmt header// format copied from http://www.topherlee.com/software/pcm-tut-wavformat.html// PositionsSample ValueDescription// 1 - 4"RIFF"Marks the file as a riff file. Characters are each 1 byte long.// 5 - 8File size (integer)Size of the overall file - 8 bytes, in bytes (32-bit integer). Typically, you'd fill this in after creation.// 9 -12"WAVE"File Type Header. For our purposes, it always equals "WAVE".// 13-16"fmt "Format chunk marker. Includes trailing null// 17-2016Length of format data as listed above// 21-221Type of format (1 is PCM) - 2 byte integer// 23-242Number of Channels - 2 byte integer// 25-2844100Sample Rate - 32 byte integer. Common values are 44100 (CD), 48000 (DAT). Sample Rate = Number of Samples per second, or Hertz.// 29-32176400(Sample Rate * BitsPerSample * Channels) / 8.// 33-344(BitsPerSample * Channels) / 8.1 - 8 bit mono2 - 8 bit stereo/16 bit mono4 - 16 bit stereo// 35-3616Bits per sample// 37-40"data""data" chunk header. Marks the beginning of the data section.// 41-44File size (data)Size of the data section.// Sample values are given above for a 16-bit stereo source.// random data to observe indianness sample of the 32 bits stream incoming from arecord with -f S32_LE// audio signal was a cable with both L/R channels grounded, so small +/- noise values around zero// $ arecord -D hw:1,2 -B 10000 -r 48000 -f S32_LE -c 2 -s 128 -i | hd// Recording WAVE 'stdin' : Signed 32 bit Little Endian, Rate 48000 Hz, Stereo// 00000000 52 49 46 46 24 04 00 00 57 41 56 45 66 6d 74 20 |RIFF$...WAVEfmt |// 00000010 10 00 00 00 01 00 02 00 80 bb 00 00 00 dc 05 00 |................|// 00000020 08 00 20 00 64 61 74 61 00 04 00 00 00 00 00 00 |.. .data........|// 00000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|// *// 00000210 00 00 00 00 00 00 00 00 00 00 00 00 f0 ff ff ff |................|// 00000220 f0 ff ff ff e0 ff ff ff e0 ff ff ff 20 ff ff ff |............ ...|// 00000230 a0 fd ff ff 60 df ff ff c0 b4 ff ff f0 81 fe ff |....`...........|// 00000240 30 83 fd ff e0 19 fa ff d0 c9 f8 ff b0 79 f7 ff |0............y..|// 00000250 40 16 f8 ff c0 89 fa ff 60 6e fa ff 80 71 f9 ff |@.......`n...q..|// 00000260 a0 9a f8 ff 10 02 f9 ff 20 5e f9 ff c0 3e fa ff |........ ^...>..|// 00000270 f0 20 f9 ff 10 71 f9 ff b0 d3 f9 ff d0 e1 fc ff |. ...q..........|// 00000280 10 f1 fb ff 10 84 fc ff d0 8d fb ff 50 b2 f8 ff |............P...|// 00000290 50 a9 f7 ff 10 3b f8 ff f0 67 f9 ff 70 32 fb ff |P....;...g..p2..|// 000002a0 f0 3e fb ff 50 70 f7 ff a0 31 f7 ff 70 85 fa ff |.>..Pp...1..p...|// 000002b0 c0 e6 fa ff 00 f2 f7 ff 80 6a f8 ff 00 85 fa ff |.........j......|// 000002c0 e0 42 fa ff 40 b7 fc ff 20 65 fc ff 60 7c 08 00 |.B..@... e..`|..|// 000002d0 b0 8e 08 00 c0 d5 0f 00 d0 ea 0f 00 20 0e 0b 00 |............ ...|// 000002e0 70 f8 0a 00 40 54 0c 00 a0 1f 0c 00 b0 a1 0c 00 |p...@T..........|// 000002f0 b0 f8 0c 00 40 39 0b 00 60 0f 0b 00 10 7c 0d 00 |....@9..`....|..|// 00000300 60 55 0d 00 20 55 0b 00 a0 f9 0a 00 f0 e5 0b 00 |`U.. U..........|// 00000310 f0 4e 0c 00 30 da 0a 00 70 0f 0b 00 a0 45 0b 00 |.N..0...p....E..|// 00000320 20 38 0b 00 70 ce 0a 00 c0 a7 0a 00 40 f7 0b 00 | 8..p.......@...|// 00000330 40 ad 0b 00 c0 66 0b 00 10 ef 0a 00 30 c4 0b 00 |@....f......0...|// 00000340 d0 27 0c 00 f0 38 0c 00 b0 b0 0b 00 20 fe 0b 00 |.'...8...... ...|// 00000350 80 c4 0b 00 d0 c3 0b 00 80 a7 0b 00 50 c1 0c 00 |............P...|// 00000360 90 7a 0c 00 e0 1b 0c 00 80 4f 0c 00 40 56 0c 00 |.z.......O..@V..|// 00000370 a0 9d 0c 00 80 8b 0c 00 60 3f 0c 00 a0 ac 0c 00 |........`?......|// 00000380 b0 48 0c 00 a0 df 0b 00 50 34 0c 00 60 74 0c 00 |.H......P4..`t..|// 00000390 c0 5e 0c 00 70 c0 0b 00 40 54 0c 00 c0 c7 0b 00 |.^..p...@T......|// 000003a0 80 02 0c 00 d0 a2 0b 00 60 18 0c 00 80 bc 0b 00 |........`.......|// 000003b0 10 ce 0b 00 80 a2 0b 00 20 ff 0b 00 a0 ea 0b 00 |........ .......|// 000003c0 00 f5 0b 00 50 ae 0b 00 70 41 0b 00 f0 5c 0b 00 |....P...pA...\..|// 000003d0 80 60 0b 00 40 29 0c 00 80 7c 0c 00 10 29 0b 00 |.`..@)...|...)..|// 000003e0 40 68 0b 00 d0 16 0b 00 90 33 0b 00 30 8b 0b 00 |@h.......3..0...|// 000003f0 40 a9 0b 00 30 e7 0a 00 70 4c 0b 00 e0 f4 0a 00 |@...0...pL......|// 00000400 80 5b 0a 00 60 1f 07 00 a0 ff 06 00 60 0c fb ff |.[..`.......`...|// 00000410 30 68 fb ff e0 13 f4 ff 80 31 f4 ff 50 c2 fa ff |0h.......1..P...|// 00000420 90 2d fa ff 40 e8 f8 ff 50 21 f9 ff |.-..@...P!..|// 0000042c// indianness at runtime from https://sourceforge.net/p/predef/wiki/Endianness/// #include <stdint.h>//// enum {// ENDIAN_UNKNOWN,// ENDIAN_BIG,// ENDIAN_LITTLE,// ENDIAN_BIG_WORD, /* Middle-endian, Honeywell 316 style */// ENDIAN_LITTLE_WORD /* Middle-endian, PDP-11 style */// };//// int endianness(void)// {// union// {// uint32_t value;// uint8_t data[sizeof(uint32_t)];// } number;//// number.data = 0x00;// number.data = 0x01;// number.data = 0x02;// number.data = 0x03;//// switch (number.value)// {// case UINT32_C(0x00010203): return ENDIAN_BIG;// case UINT32_C(0x03020100): return ENDIAN_LITTLE;// case UINT32_C(0x02030001): return ENDIAN_BIG_WORD;// case UINT32_C(0x01000302): return ENDIAN_LITTLE_WORD;// default: return ENDIAN_UNKNOWN;// }// }Side note, noticed this sound card is not 24/96, must be some marketing crafted lie. From the data output by 'arecord' the stream seems to be 20bits/96kHz, 24 bits is only when sampling at 48kHz. I remember Creative lied like this before, when they advertised a sound card as being 24bits, when in fact only the CODEC was 24 bits but all the rest was working in 16 bits.Thank you all for the help, will try each method.

author: magic
author_rank: Super Contributor
timestamp: August 08, 2022, 06:56:14 am
content: Output format of arecord can be changed, but I don't know what baudline expects.I'm pretty sure that sox and mplayer could accept either WAV or RAW on in and out.If you are having problems with those programs creating too much latency or something, perhaps there are options to reduce buffer size.I can't see myself sitting and reinventing this particular wheel

author: RoGeorge
author_rank: Super Contributor
timestamp: August 08, 2022, 07:25:31 am
content: At first I thought it would be just pipe-ing, hoping for a common float format between 'arecord' and 'baudline'. Then I thought some standard audio tool like 'sox' would do it, to convert the format between the two, maybe 10 minutes of man readings to figure out the syntax, and that's it!One day later, counting bits and bytes, and reverse engineer headers from a hex dump.From the man pages, 'arecord' knows these formats:Code:-f --format=FORMATSample formatRecognized sample formats are: S8 U8 S16_LE S16_BE U16_LE U16_BE S24_LES24_BE U24_LE U24_BE S32_LE S32_BE U32_LE U32_BE FLOAT_LE FLOAT_BEFLOAT64_LE FLOAT64_BE IEC958_SUBFRAME_LE IEC958_SUBFRAME_BE MU_LAWA_LAW IMA_ADPCM MPEG GSM SPECIAL S24_3LE S24_3BE U24_3LE U24_3BES20_3LE S20_3BE U20_3LE U20_3BE S18_3LE S18_3BE U18_3LESome of these may not be available on selected hardwareThe available format shortcuts are:-f cd (16 bit little endian, 44100, stereo) [-f S16_LE -c2 -r44100]-f cdr (16 bit big endian, 44100, stereo) [-f S16_BE -c2 -f44100]-f dat (16 bit little endian, 48000, stereo) [-f S16_LE -c2 -r48000]If no format is given U8 is used.However, when I try other formats arecord tells this:Code:$ arecord -D hw:1,2 -B 10000 -r 48000 -f FLOAT_LE -c 2 -s 128 | hdRecording WAVE 'stdin' : Float 32 bit Little Endian, Rate 48000 Hz, Stereoarecord: set_params:1368: Sample format non availableAvailable formats:- U8- S16_LE- S32_LE

author: Ed.Kloonk
author_rank: Super Contributor
timestamp: August 08, 2022, 07:25:45 am
content: Regarding 20-bit sound cards.In the late 90's, I had a Echo Darla 1st gen which had the 20-bit ADC. A great piece of hardware. Fantasic noise floor for a pci card. But the software to access it always sucked.I've got recordings taken from rare vinyl all stored in 24-bit or 32-bit in various integer or float combinations because there was something wrong with the least significant nibble being randomly flipped. Later on, when needed, I would use the MAD mp3 encoder, sometimes just sent 16-bits undithered. Yuk.I don't blame the devs. At the time, I understand they developed the card first on a Mac. Those cards had firmware uploaded by the driver on init so somewhere in the conversion to winintel (hence the byte order), firstly with a driver for libmm and later for AISO or was it directx? It became a bit of a dog's breakfast. Worked alright on Alsa briefly until you know what came along.

author: Ed.Kloonk
author_rank: Super Contributor
timestamp: August 08, 2022, 07:30:34 am
content: Quote from: RoGeorge on August 08, 2022, 06:47:15 amMy memory is foggy here but with .wav RIFF headers, I vaguely remember that when the data segment was bigger than 32 bits another header was injected at that point. Thanks MS.

author: Ed.Kloonk
author_rank: Super Contributor
timestamp: August 08, 2022, 07:36:16 am
content: Quote from: Ed.Kloonk on August 08, 2022, 07:30:34 amI'm starting to rememer now. Depending on the application, we had to fool the wavfile reader into thinking the bitspace was something else. I can remember going into a binary editor and tweaking the sample vals so early versions of cool edit would play nice with +16 bits.

author: magic
author_rank: Super Contributor
timestamp: August 08, 2022, 08:10:50 am
content: Regarding file formats, runman arecordand type/wavAs for sample formats, it surely supports many, but you are constrained by what the hardware can provide.And now I realized that this can be dealt with easilyReplacehw:Nwithplughw:Nand you will use an ALSA plugin which can convert stuff for you. Beware that it will also downmix to mono or interpolate the sample rate if you select output parameters not supported by hardware.

author: PKTKS
author_rank: Super Contributor
timestamp: August 08, 2022, 08:51:14 am
content: Quote from: magic on August 08, 2022, 08:10:50 amYou are in the proper directionAn ALSA plugin should do thisIf i find time i will try to drop scratch example. I have a pile of such pluginsPaul

author: gnif
author_rank: Unknown
timestamp: August 08, 2022, 10:06:57 am
content: Quote from: magic on August 08, 2022, 08:10:50 amExactly this, it's part of alsa already. `plughw` will do whatever is needed to match your needed format, if you match everything except for the sample format it will only convert the format.As for the arecord header, `arecord -t raw` will output just the plain samples, no header.Example, record from device 0, format little-endian float, at 96kHz, two channels and output to stdout without a header.Code:arecord -D plughw:0 -t raw -f FLOAT_LE -r 96000 -c 2If the hardware doesn't support FLOAT_LE, `plughw` will convert for you.

author: RoGeorge
author_rank: Super Contributor
timestamp: August 08, 2022, 10:36:58 am
content: Quote from: adeuring on August 07, 2022, 03:24:44 pmNow it all makes sense, thank you. Switched to C anyways, because of more control in handling data formats.However, in all the C tests from yesterday I was using '2^n' for scaling numbers, thinking '^' means "to the power of", while the '^' operator in C is bitwise xor. No wonder why all the scaling attempts from yesterday were behaving randomly.Now that I've noticed that, first C example frommagicworks just fine, though the float range expected by 'baudline seems to be +/-32768.0 just like it would be for a 16 bits ADC, but float, not +/-1.0, not +/-231, not +/-223.Code:#include <stdio.h>int main() {int i;while (fread(&i, 4, 1, stdin)) {float f = i;f = f/65536; //??? should be 8388608, to scale the 24bits of the ADC to +/-1.0 float//apparently 'baudline' expects the +/-32768.0 range for incoming le32f data ???fwrite(&f, 4, 1, stdout);}}// gcc -Wall -Wfatal-errors int_to_float_from_magic_EEVblog.c && arecord -D hw:1,2 -B 10000 -r 48000 -f S32_LE -c 2 -i | ./a.out | bl -format le32fThough the 'baudline' looks just the same for 24bits as if it were all S16_LE when eyeballing at it, the SNR is 2dB better, from 48dB relative to the fundamental for 16bits sampling, to 50dB for 24bits sampling.I should file a bug report, for why the C '^' operator doesn't behave like the spreadsheet '^' operator!Will try the other solutions, too, to learn more, thank you all, solved!

author: RoGeorge
author_rank: Super Contributor
timestamp: August 08, 2022, 11:14:32 am
content: Code:arecord -D plughw:1,2 -r 96000 -c 2 -f FLOAT_LE -t raw | bl -format le32fNice, this works, too, thank you, but the frame rate in baudline becomes very low, just like it was with sox or ffmpeg. However, the produced float range is +/-1.0, while baudline expects +/-32768.0, so the signal is barely visible. Couldn't find any scaling or volume in the arecord's man.

author: gnif
author_rank: Unknown
timestamp: August 08, 2022, 11:45:52 am
content: Quote from: RoGeorge on August 08, 2022, 11:14:32 amStandard range for float audio is +/-1.0, where S16 is -32767 to 32768. I have not used baudline but a quick look at it's documentation shows it supports this... why not just use S16 and allow plughw to convert to it?Edit: Actually it seems that baudline also has a `-scaleby` parameter, can't you use this?Code:baudline -stdin -format le32f -scaleby 32768Edit2:Got this working in realtime with the following command:arecord -D plughw:0 -t raw -f FLOAT_LE -r 44100 -c 1 | ./baudline -stdin -format le32f -scaleby 32768 -recordAlso with PipeWire if you are fortunate enough to be on a distro that has moved to it:pw-record --format f32 --channels 1 --rate 44100 - | ./baudline -stdin -format le32f -scaleby 32768 -record

author: RoGeorge
author_rank: Super Contributor
timestamp: August 08, 2022, 12:36:46 pm
content: To my ubuntu 20.04, arecord with plughw: (or sox, or ffmpeg) gives a much slower refresh in baudline making it all jumpy, than using hw: and converting to float with C. Found the -scaleby parameter in baudline, thought that would be a strange number to remember 1/65536=0.0000152587890625, so conversion with C is more convenient and doesn't slow down the framerate.Don't have PipeWire, Ubuntu switched to it starting this year, but my install is still using PulseAudio. However, I was trying to avoid any higher audio layers because of possible (and unwanted) resampling or conversions these layers might add.Trying to use 24 instead of the normal 16 bits because the soundcard can sample in 24 bits, and I am using it as a measuring instrument. For a measuring instrument, the extra bits are valuable.

author: gnif
author_rank: Unknown
timestamp: August 08, 2022, 12:42:53 pm
content: Quote from: RoGeorge on August 08, 2022, 12:36:46 pmIt sounds like you have a larger issue at hand, I am getting real-time, smooth output even at 96kHz no matter how I feed baudline.As a test, do you get smooth framerates if you feed it /dev/urandom instead? If so you have an issue with your sound device, perhaps your period size/count is not dealing well and you need to tune this some.Further to this, what is your sound device?

author: PKTKS
author_rank: Super Contributor
timestamp: August 08, 2022, 01:19:25 pm
content: Standard DMIX pluginCode:pcm.FLOATCNVT {type dmixipc_key 2048ipc_key_add_uid 0ipc_perm 0660slave {pcm { type hw card 0 }format "FLOAT_LE"rate 96000channels 2}bindings {0 01 1}}ctl.FLOATCNVT {type hwcard 0}Paul

author: gnif
author_rank: Unknown
timestamp: August 08, 2022, 01:23:23 pm
content: Quote from: PKTKS on August 08, 2022, 01:19:25 pmThis is exactly what plughw is already doing

author: PKTKS
author_rank: Super Contributor
timestamp: August 08, 2022, 01:25:15 pm
content: Quote from: gnif on August 08, 2022, 01:23:23 pmALSALIB will parse the defined configures at any call.Unless you have them defined - it will not magically fall from the sky...It must be defined somewhere. Or... written in the command linePaul

author: RoGeorge
author_rank: Super Contributor
timestamp: August 08, 2022, 01:29:02 pm
content: Quote from: gnif on August 08, 2022, 12:42:53 pmNot sure how to feed data, do you have an example of the command, please?Couldn't make arecord to read from /dev/urandom or from stdin, and if you mean to test by feeding /dev/ramdom to baudline, directly, if I do this:Code:cat /dev/urandom | bl -format le32f -samplerate 96000# bl is an alias for "baudline_1.08_linux_x86_64/baudline -stdin -record -channels 2 "then cat will flood too much data into baudline, making baudline unresponsive.I guess some larger buffers come into play inside arecord (or sox or ffmpeg) when using plughw: instead of hw:Otherwise it all seems to be working fine, baudline shows about 60fps+ and fluid move (the monitor is 60Hz and the driver syncs to that to avoid video tearing).The soundcard is an onboard Sound Core3D Recon 3D internal (ca0132 chipset from Creative, the followup of emu10k1/2, Audigy, etc.), MB ASRock Fatal1ty Z97 Professional, nVidia GTX760/2GB, i7-4790K, 32GB RAM, SSD.

author: PKTKS
author_rank: Super Contributor
timestamp: August 08, 2022, 01:32:07 pm
content: Quote from: RoGeorge on August 08, 2022, 01:29:02 pmThere seems to be a necessary climbing of some steps....the use of ALSA LIB plugins is straight like-D plug:But... your systemREQUIREthe proper CARD DEFINITIONs and NODES (which mostly are messed and subverted by systemd and other problems...)Basically you just invoke aplay or arecord with the required config pluginI have some dozens plugins defined to handle al sort of complex things...including AUDIO ROUTING.. when not using JACKPaul

author: gnif
author_rank: Unknown
timestamp: August 08, 2022, 01:34:58 pm
content: Quote from: RoGeorge on August 08, 2022, 01:29:02 pmAh yes, sorry about that, I forgot that the audio device will be rate limiting the input. In any case, I still suspect an issue with your capture method/configuration. What is your sound device?

author: gnif
author_rank: Unknown
timestamp: August 08, 2022, 01:38:42 pm
content: Quote from: PKTKS on August 08, 2022, 01:32:07 pmPlease just stop, as mentioned in another thread, I am not just some general Linux user, I am both a kernel developer and one of my specialities is the Linux audio subsystems. I have written both audio drivers, user space audio applications, DSP chains and full audio pipelines/engines for applications. I am a contributor to some of the largest and most widely used FOSS projects that make use of audio under Linux and the author of the audio engine used by Kodi (formally XBMC) where we ripped out all the ALSA asoundrc junk in favour of directly using alsalib correctly. I am currently working on a DSP processing tool for general desktop users that does real time processing of audio without JACK, etc... your claim is nothing special.

author: RoGeorge
author_rank: Super Contributor
timestamp: August 08, 2022, 01:45:17 pm
content: The soundcard is an onboard Sound Core3D (Recon3D internal ?) (ca0132 chipset from Creative, the followup of emu10k1/2, Audigy, etc.), MB ASRock Fatal1ty Z97 Professional, nVidia GTX760/2GB, i7-4790K, 32GB RAM, SSD.It is known as buggy, long cumbersome history, with drivers windows only then Creative open sourced later the drivers from some other descendent chip (ca0100) and somebody took the time to sniff the messages exchange between the Windows driver and the Windows OS, then he crafted the ca0132 Linux driver.Only a week ago I was trying to modify the drivers and recompile, but if nobody bothered fixing all that in the last 10 years, then maybe it's not worth doing it, or maybe is not even possible.Here's a list of bugs I'm seeing:https://www.eevblog.com/forum/programming/audio-in-linux/msg4308922/#msg4308922And this is the github repo (inside another topic that might add details about this particular driver, but please keep in mind I was having no idea what I was doing/writing/testing there):https://www.eevblog.com/forum/programming/compile-a-linux-kernel-module/msg4327933/#msg4327933Connor McAdams is the one who reversed engineer all then wrote the ca0132 patch for Linux.This is his hda patch for ca0132 chip:https://github.com/torvalds/linux/blob/master/sound/pci/hda/patch_ca0132.c

author: gnif
author_rank: Unknown
timestamp: August 08, 2022, 01:55:53 pm
content: Quote from: RoGeorge on August 08, 2022, 01:45:17 pmThis is all I needed to see. Onboard sound devices are notorious for issues like that which you are seeing here, they commonly suffer from interrupt jitter which causes latency issues. If your device is getting into an under/overrun state it can cause stream stalls while it get's reinitialised by alsalib (DMA xfers stop and have to be re-started). Unfortunately when using `arecord` it's impossible to determine when/if these stalls are occurring.The reason why the C application in the pipeline is avoiding this issue somewhat is due to buffering. I suggest you have a play with different period sizes and buffer sizes with arecord. I see you were specifying the`-B` switch which sets the buffer time, but this doesn't control the number of buffers. The flags to play with are `--period-size` and `--buffer-size`.The buffer-size is a multiple of the period-size, so to have two periods you would want buffer-size to be 2x the period-size. The minimum periods you can run with is usually 2, but some cards (like onboard) need 3 or more. If this doesn't help also increase your buffer-size. Keep it to powers of 2, for example:period-size=512buffer-size=1024Play with these values and see if it helps your use case. Obviously the larger the buffers, the more latency that will be introduced, however for your use case I don't think that this matters much.

author: PKTKS
author_rank: Super Contributor
timestamp: August 08, 2022, 02:47:11 pm
content: I ve place to important tips for properly setting device nodes...They have been erased.... reason being unknown...Anyway the tip still holds: The OP has issues with the system nodesunless he can properly fix them.. it will be hard to achieve proper ALSA goals.Posted how nice a CRAPPY ON BOARD MOBO can perform at DAW level this days.. (including low latency MIDI resonse with real time scoring )also erased... so ... I rather mention for those who can see how the tips are relevantto contact me off topic..anyone welcome . as usualPaul

author: gnif
author_rank: Unknown
timestamp: August 08, 2022, 02:51:24 pm
content: Quote from: PKTKS on August 08, 2022, 02:47:11 pmOff topic and outside the scope of the problem, this is not a full DAW and does not have the issues you are describing. Audio nodes as you understand them are not part of this issue when talking to the hardware directly via `hw` or `plughw`. The issue is the application `baudline` is having a framerate issue when the audio source is coming from arecord, but for some reason the issue is somewhat resolved when coming via another process, this indicates that additional buffering (via stdio) is resolving this problem to a minor extent and points at period counts and buffer sizes being the issue along with the low quality integrated audio solution of the system.Quote from: PKTKS on August 08, 2022, 02:47:11 pmMIDI has no relationship at all with PCM, completely off topic.Last warning, stop posting off topic things and over-complicating an issue that is already being diagnosed.

author: PKTKS
author_rank: Super Contributor
timestamp: August 08, 2022, 02:53:14 pm
content: Quote from: gnif on August 08, 2022, 02:51:24 pmThey just do.Unless properly set the applications will not find them...and i am guessing that in the OP case... he has a void node being read from the PIPE.Paul

author: gnif
author_rank: Unknown
timestamp: August 08, 2022, 02:55:16 pm
content: Quote from: PKTKS on August 08, 2022, 02:53:14 pmThe MIDI device will not even be open! far out, learn how ALSA works, the PCM device is what is in use here, stop polluting this thread.https://github.com/alsa-project/alsa-utils/blob/master/aplay/aplay.c#L829QuoteIs not `snd_rawmidi_open`

author: PKTKS
author_rank: Super Contributor
timestamp: August 08, 2022, 02:58:50 pm
content: Quote from: gnif on August 08, 2022, 02:55:16 pmMy dear... of course MIDI has not been the OP issue.But it is a very important issue TO SETUP THE WHOLE SYTEM NODES.I ve posted how a cheap crap on board mobo CAN PERFORM REAL TIME..AUDIO AND MIDI AND SCORING in real time..YOU probable erased.. and threats to myself will not change the issue.The system must be set... the nodes must be valid..AND ... real time performance will be fine..If you feel fine to insult me and ban me go ahead...It seems to be your goal..Paul

author: magic
author_rank: Super Contributor
timestamp: August 08, 2022, 03:00:23 pm
content: Quote from: RoGeorge on August 08, 2022, 12:36:46 pmDon't think so.QuoteI don't know what "slow down the framerate" means; it's probably some buffering problem - not enough or too much?.I would start with recording something to file and tweaking those buffer/period options until the recording comes out right.If WAV files record right but piping the same data into baudrate doesn't, then maybe you need more buffering. Perhaps insertcatbetween them?

author: gnif
author_rank: Unknown
timestamp: August 08, 2022, 03:06:19 pm
content: Quote from: PKTKS on August 08, 2022, 02:58:50 pmBy using `hw` or `plughw` you bypass the entire asound system and take direct control of the hardware device.... it BYPASSES the system you're talking about. Again learn how the ALSA subsystem works,https://github.com/torvalds/linux/blob/20cf903a0c407cef19300e5c85a03c82593bde36/sound/core/pcm_native.c#L2820`snd_pcm_open` calls directly into the kernel and when we use `hw` there is a direct line to the hardware, NOTHING IN THE MIDDLE.

author: PKTKS
author_rank: Super Contributor
timestamp: August 08, 2022, 03:10:36 pm
content: ok my dear.. i take none of your insults...Facts still remain... you remind me my 7y old bonehead nephew...Impossible just so..GO ahead delete my stuff and ban me .... nothing prevents you..erasing my posts will not change facts.My stuff is working flawless based on what you think wrongPaul

author: gnif
author_rank: Unknown
timestamp: August 08, 2022, 03:15:34 pm
content: Quote from: PKTKS on August 08, 2022, 02:58:50 pmIf it were my goal you would already be banned mate. I have stated several times now to stop posting your suggestions here because they are WRONG. I have 15 years of experience with ALSA and how the internals of it work, including the emu10k core that the OPs sound card is loosely based on, along with the issues that it had when creative stopped support of it.Quote from: PKTKS on August 08, 2022, 03:10:36 pmI have not insulted you once.Quote from: PKTKS on August 08, 2022, 03:10:36 pmYet here you are insulting meQuote from: PKTKS on August 08, 2022, 03:10:36 pmExcept the fact that I have been trying to give you a fair chance...Quote from: PKTKS on August 08, 2022, 03:10:36 pmThey will remove useless information that has no bearing on this issue.Quote from: PKTKS on August 08, 2022, 03:10:36 pmEXACTLY, YOURSYOUR SETUPYOUR HARDWAREYOUR CUSTOM KERNEL,YOUR CUSTOM REQUIREMENTSYOUR DAW SETUP.This is NOT what the OP has, nor what the OP is dealing with, nor does the OP need real time processing as is needed in a DAW setup.Final warning, I have been more than fair here.

author: PKTKS
author_rank: Super Contributor
timestamp: August 08, 2022, 03:19:26 pm
content: You already deleted so much relevant stuff...to twist support your point and make me look like a rant idiot...Included some hard to find clues...Anyone reading this crippled (by you) posts will have a hard time to understand.Nevertheless. Anyone interested is very welcome to contact me with such issues.I have not much to add...as half you deleted...It adds up even being worst than ban.. being crippled by twisted deformed opinionsPaul

author: gnif
author_rank: Unknown
timestamp: August 08, 2022, 03:23:25 pm
content: Quote from: PKTKS on August 08, 2022, 03:19:26 pmKeep stating this crap, we do not delete posts but move them to a deleted topic where all moderators and server admins can see the full history. I have posted exactly why you are wrong and provided proof positive of your misinformation in both the ALSA sources and the Linux kernel itself and removed your posts you posted AFTER I told you to stop because it's miss-information and is not helpful here.As for your deleted posts, you offered a few screenshots and a script... none of the information provided was useful in any meaningful sense. The issue here is a buffering/interrupt issue, it may even be an issue with the baudline application itself, we have not yet isolated the exact cause, but your continual insistence that its the configuration of a subsystem we have bypassed entirely is just idiotic.

author: PKTKS
author_rank: Super Contributor
timestamp: August 08, 2022, 03:27:55 pm
content: You stated very well:- i HAVE MY STUFF ( kernel, DAW etc_etall ) working fine.I just have no other way to put examples to the OP or anyone else...Can you image a better example than a FULLL WORKING SYSTEM..You deleted them and still threat me to your biased already conceived point.My examples hold just true.. working fine.. and *ANYONE* can duplicate them..point is .. you deleted them...What is it?? it bothers you? you feel envy? threats make you feel good?Paul

author: gnif
author_rank: Unknown
timestamp: August 08, 2022, 03:31:19 pm
content: Quote from: PKTKS on August 08, 2022, 03:27:55 pmSo you think the OP has the same sound device as you with the same buffering configuration, the same IRQ patterns, the same codecs, and all your configurations apply to his device also?Quote from: PKTKS on August 08, 2022, 03:27:55 pmYes, one that matches the OPs hardware and replicates his issue so it can be properly diagnosed.I have had enough, you have had enough warnings.Edit: PKTKS temp banned for 3 days

author: Nominal Animal
author_rank: Super Contributor
timestamp: August 08, 2022, 03:37:30 pm
content: Quote from: gnif on August 08, 2022, 01:55:53 pmThat's exactly why I wrote my suggested converter like I did: it does the minimum number of read syscalls to obtain the data already in the pipe that consists of an integer number of convertable samples, up to a compile-time maximum number of samples, and immediately forwards the converted data. Should yield minimum latency, but will be affected by those arecord options.If one uses<stdio.h>fread()/fgetc()/getc()/getchar()/fwrite()/fputc()/putchar(), then the standard C library will add its own software buffers in between.While I do approve of the Unix philosophy, and chaining tools to get complex stuff done, when they start interfering with the task at hand (like dropping data), it is time to write a better tool, methinks.I already have written a clunky VU meter program for Ed Kloonk on top of ALSA in a different thread around here somewhere, that could be easily modified to do e.g. DFT on the raw data using just ALSA and FFTW3 libraries (and Gtk+3 for the UI, if desired). Then, the optimum buffer/period size would be either one or one half DFT/FFT window (depending on the desired overlap and windowing function). This stuff isn't difficult, except when you arealsofighting against hardware or its drivers... (or are a burned out husk of a man like I am, I guess; which is why I never finished that program for Ed. Sorry, Ed.)

author: gnif
author_rank: Unknown
timestamp: August 08, 2022, 03:45:12 pm
content: Quote from: Nominal Animal on August 08, 2022, 03:37:30 pmIndeed, which is why I believe arecord piped to the tool should also work fine if the buffering parameters are adjusted, but in reality we are at the mercy of the drivers and whatever the closed source `baudline` application is doing with the data as it takes it... I mean, `baudline` itself may be stalling due to some other factor for just long enough to cause a problem due to other workloads on the system, etc.While I can make it work fine for me, on my "unconfigured nodes" system (), my hardware is very very different from the OPs hardware, and as such all factors must still be considered.

author: gnif
author_rank: Unknown
timestamp: August 08, 2022, 04:01:08 pm
content: Playing more with baudline, seems there are some stats you can obtain that might be useful (attached).Right click and select `stats`, see if the video FPS and transforms/sec remains stable, it might help indicate if you have a general system performance issue with this workload.

author: RoGeorge
author_rank: Super Contributor
timestamp: August 08, 2022, 05:04:26 pm
content: Yes, noticed it, the 60+ fps I was writing to have when pipe-ing with C is from the baudline's stats 'menu'.Just to be sure I'm not misleading, the whole sound works OK in audio/video play, and record, and all the daily use of the desktop.The choppy thing of 5fps-like only happens while measuring the spectrum with baudline, and it does not affect anything else except the spectrum image rolling in baudline. Movies or audio plays fine at the same time with a choppy spectrogram in baudline.I guess the same, some buffering problem specific to baudline only, except it doesn't worth investigating since I can just avoid it by pipe-ing through the C converter.This is all hobby level, curious to see baudline plots with 24bits and how they differ from the ones in 16 bits, which thanks to everybody's help here, now I've learn how to do that. I'm sorry to see bans because of my topic.

author: gnif
author_rank: Unknown
timestamp: August 08, 2022, 05:08:42 pm
content: Quote from: RoGeorge on August 08, 2022, 05:04:26 pmNot your fault mate, don't feel bad. Unfortunately Linux audio is one of those topics that has a million "experts" but very few actual engineers. Thankfully you have had help from those that understand the issue here and have found you a workable solution.If you feel this is solved I will lock this thread to prevent it being necro'd when PKTKS is able to participate again.Btw, I have spent a bit of time trying to replicate your fault and I have found no combination of configurations that trigger it.

author: RoGeorge
author_rank: Super Contributor
timestamp: August 08, 2022, 05:28:49 pm
content: TBH, I don't mind if incorrect info will be posted, no matter by whom, though maybe do not lock the topic, please.I was planning to test the buferless C convertor from Nominal Animal, and post how it went. Just that I don't have the time to study his code write now (at a brief look seems something that might be interesting to look closer), but will do it later or tomorrow, and post how it goes. If you know it will be further rants, then close it and I'll PM to Nominal Animal, or open another one about that code only. Your call.From my side, it was solved when I've posted the successfull printscreens and the change in noise floor with 24/16 bits:Quote from: RoGeorge on August 08, 2022, 10:36:58 am

author: gnif
author_rank: Unknown
timestamp: August 08, 2022, 05:37:23 pm
content: No worries, I will keep this topic on my watch list

author: Nominal Animal
author_rank: Super Contributor
timestamp: August 08, 2022, 05:58:50 pm
content: Quote from: RoGeorge on August 08, 2022, 05:04:26 pmWell, 5 fps corresponds to a DFT with a 0.2 second non-overlapping windows, doesn't it? Does the minimum frequency happen to be 5 Hz?I do not use baudline, so I don't know how tunable its spectrum (discrete Fourier transform) options are.When using FFTW3 for DFTs, it is important to gather "wisdom" first for the selected window size. This "wisdom" is, simply put, testing the different methods of performing the same DFT (window size); and once found, will yield an extremely fast transform on that machine on that window size.The window size affects the spectrum, because the windowing function itself acts like a filter. (When the window is advanced by half, and the spectra summed, you often get a better time-domain representation of the actual signal spectrum. You could, in theory, advance the window sample by sample, but that just interpolates between the different spectrum representations, and does not really provide any new information.)A windowed sinc has a very steep (but not vertical) stop band, and very flat low pass response, so it works well for this kind of application. It would be more efficient if it was incorporated into the DFT (it would save one multiplication per sample), but any desktop machine should be able to do this without noticeable slowdown at any window size one wants. (Just remember that with a minimum frequencyf, the maximum update rate is 2/f when advancing each DFT by half a window, and 1/f when advancing each DFT by a full window. Having audio buffers of exactly 2/f or 4/f in duration will yield minimum latency and best overall throughput. 4/f buffer size can help on multicore machines.)In practice, the program would reads those fractional buckets of samples, then apply the windowed sinc to 2 or 4 consecutive buckets, and DFT to the windowed samples. (The older half of the buckets can then be recycled for new data.) The DFT consists of complex numbers, with the absolute magnitude (square root of the sum of squared real and imaginary parts) the interesting part, and the phase (atan2(imaginary part, real part)) often not visualized at all. This is then updated to the display, usually as a graph, but sometimes also as an intensity diagram in a continuously scrolling display. Each of these sub-tasks are well handled by a separate thread, utilizing multiple cores simultaneously. Whenever the DFT-to-absolute-magnitudes thread has constructed a new spectrogram, it just pushes it to a queue and uses a thread-safe notification to tell the UI toolkit that a new one is available for drawing. The total latency does depend on how much "extra" CPU power you have available, but should not be more than one or two full windows in duration, which should be acceptable for realtime spectrum analysis.Quote from: RoGeorge on August 08, 2022, 05:28:49 pmThat would be nice. Note that if you want to add scaling, say divide each sample by 32768, you can just change the conversion loop toCode:/* Convert to float. */{float *const end = out_buf + have;int32_t *src = in_buf;float *dst = out_buf;while (dst < end)*(dst++) = (float)(*(src++)) / 32768.0f;}or, if you prefer to do something more complicated, then maybeCode:/* Convert to float. */{float *const end = out_buf + have;int32_t *src = in_buf;float *dst = out_buf;while (dst < end) {const double sample = *(src++);*(dst++) = sample / 32768.0; /* Or some other operations */}}It is not technically bufferless either; it does read up to BLOCK_SIZE samples, whatever is immediately available in the pipe, and converts and outputs them as soon as possible. It never waits formoredata to arrive, unless it has already processed all previous data (or the data it has does not end at a sample boundary). You can increase BLOCK_SIZE if you want (by usinggcc -Wall -O2 -DBLOCK_SIZE=65536 ...when compiling); anything above 262144 is probably useless (because of the default pipe size limitation in Linux). A more proper term would be that it tries to use the same buffer size as its input, but not more than BLOCK_SIZE samples at a time.

author: RoGeorge
author_rank: Super Contributor
timestamp: August 09, 2022, 12:27:03 am
content: Wow, 125+ fps, thank you!So far that's the fastest one. BLOCK_SIZE almost doesn't matter, but I had to specify 10ms of buffer in arecord (-B 10000). Though, the code is way over my head, with all the low access and the inline.

author: Nominal Animal
author_rank: Super Contributor
timestamp: August 09, 2022, 03:21:38 pm
content: Well, in case it is useful, let me describe the code!If not, no worries; I'm perfectly happy to just leave this here in case someone else may stumble on this later on, and find this useful.The core idea is that we use a singleread()call, to obtain whatever is already available in the pipe. Seeman 7 pipe, especially the "I/O on pipes" and "Pipe capacity" sections.To avoid dealing with partial samples, we do additional reads until we have a multiple of 4 characters (or a full BLOCK_SIZE) buffer.If read() returns 0, it indicates end-of-input, and the program can exit. For simplicity, if this occurs after a supplementary read because the previous ones did not return an integral number of samples, we just throw the entire tail buffer away. (This really is intended to be used as a real-time continuous data filter. To fix this, we could just set a flag ending the outer loop iterations, instead of immediately returning.)Read should return -1 on error, but because of certain old bugs related to 32-vs-64, I like to consider all other negative values as EIO errors as well.(Technically, read will return -1 witherrno==EINTRwhen a signal is delivered to a handler installed without SA_RESTART flag; and if nonblocking,errno==EAGAINorerrno==EWOULDBLOCKif no data is available, but since this program does not have userspace handlers and we can assume standard input and output are blocking (uh, not nonblocking), we can safely consider all of them as actual errors the program cannot deal with.)On the write side, we assume that most reads we do, are of the original buffer size, and therefore will fit in the output buffer in a single write() call.However, the code does not assume that indeedwillhappen every single time. Instead, it uses a loop to write the entire converted output buffer.See theman 2 writeman page for description and rules and further information.Since there is no cleanup or such to do when complete, and we exit the program directly from within the loop, the outermost loop is an forever one. I preferwhile (1) { ... }, some others preferfor (;;) { ... }; either one would work fine here.The logic thus described, let's open up the important parts. For clarity, I'll separate each chunk with a horizontal line.Code:/* Read some (complete) int32_t's. */do {ssize_t n = read(STDIN_FILENO, (char *)(in_buf) + have, (sizeof in_buf) - have);if (n > 0)have += n;elseif (n == 0)return EXIT_SUCCESS;elsereturn EXIT_FAILURE;} while (!have || (have % sizeof in_buf));Sinceread()returns the number ofchars, we need to consider our input buffer as a buffer ofchars. Within this loop,haveis the number ofchars we already have in thein_buf. Thus,(char *)(in_buf) + haveis a pointer to the first unusedcharinin_buf, and(sizeof in_have) - haveis the number of unusedchars in it. We do aread(), trying to fill the rest of the buffer –– and sincehavestarts at 0, we actually try to fill in the entire buffer. Of course,read()will block until there is some data in the pipe, and then return that data (up to the limit we specified).nis the actual number ofchars we read.Theifclauses check if we did get any data; and if not, exit the program. (Again, while we may throw some already read data away, we only do so if that already read data did not end at a valid sample boundary. To me, this is sufficient to indicate the tail part of the data is suspect anyway, and not worth processing.)The loop condition can be read in English as"as long ashaveis zero, orhaveis not a multiple of the size ofin_bufarray elements".After the above loop is done, we dividehaveby the size of thein_bufarray elements, so that it becomes the number of samples we have inin_buf. Because our reads started at the beginning of the buffer, we know the data is properly aligned. Essentially, it is at this point that we change from treating the input as a stream ofchars, and interpret it as the representation of thein_bufarray elements.(I like having such clear logical transition points.)The next part, the conversion fromin_bufto out_buf,Code:/* Convert to float. */{float *const end = out_buf + have;int32_t *src = in_buf;float *dst = out_buf;while (dst < end)*(dst++) = *(src++);}is just the pointer version of the simple loopCode:for (size_t i = 0; i < have; i++) {out_buf = in_buf;}Why did I write it in the pointer form, when the simple loop is so much more readable?I'm a creature of habit, and GCC used to generate better code when using pointers, compared to array indexing, on some architectures. x86 and x86-64 has powerful indexing built in to its instruction set, so the simple loop tended to be preferable on x86 and x86-64 even using old versions of GCC.Or, you could equally say that since I was in the pointer-logic-thought-mode when writing the code, I just didn't stop and think before I wrote the loop, and just let my fingers type the solution when I was already thinking something else.Even examining the possible code generated for the two loops atCompiler Explorershows I really should have written the array indexing form instead. What can I say in my defense? I never claimed the code was the best I could think of, I only indicated this would be something I would write in a couple of minutes to perform the task I needed it to perform, with the logic I described above.There isalwaysroom to learn and improve.The final part, writing out the (full) convertedout_buf, uses the same logic as the read loop, except that this time we loop until the buffer is empty.endpoints to just past the final char to be written. Note how(char *)(out_buf + have)is the char pointer to thehave'th element; it is deliberately NOT(char *)(out_buf) + have, which would be a char pointer to thehave'thchar.Bothptr(pointing to the first char that needs to be written) andend(pointing to the char following the last char to be written, or first char after the data to be written) are pointers tochar, because that is the units in whichwrite()operates with. The expression(size_t)(end - ptr)is the number of chars between the two pointers, if and only ifptr <= end(or equivalentlyend >= ptr).The loop condition can be read in English as"while we have chars betweenptrandendto be written":Code:while (ptr < end) {ssize_t n = write(STDOUT_FILENO, ptr, (size_t)(end - ptr));if (n > 0)ptr += n;elsereturn EXIT_FAILURE;}All error cases are aggregated into theelseclause. In theory,write()should return a positive value, or -1 witherrnoset (seeman 2 writefor a full description). I personally consider both other negative values and 0 as equivalent toEIOerror. (For descriptions oferrnocodes, seeman 3 errno.)Again, the key thing is that the low-levelwrite()call does not necessarily write the entire buffer: it can return a short count, for example because the output is to a pipe and the pipe is nearly full (because the reader is too slow in reading). It will block until at least one char is written, or an error occurs.(If the descriptor was nonblocking, it would return -1 witherrno==EINTRif interrupted by a signal delivery to an userspace handler installed without SA_RESTART flag,errno==EAGAINorerrno==EWOULDBLOCKif nothing can be written immediately, and so on.)If standard output is a pipe, and the read end closes its end, this process will be killed by the SIGPIPE signal the first time we attempt to write to the pipe after the read end is closed. We can catch or ignore that signal, in which case the write would fail witherrno==EINTRorerrno==EPIPE. However, since being killed by SIGPIPE is fine with us (humans using this tool for its stated purpose), we don't need to worry about this either.Even if you personally –– whoever might be reading this post –– do not find this useful, I think there is a chance it might be useful to someone finding this thread later using a web search due to the keywords (stdin stdout pipe conversion).In particular, those comparing the freestanding implementation to the standard library implementation might find it useful, because it illustrates how the logic stays the same even when the standard library is not used, and how GCC/clang/ICC extended assembly can be used to interface to something completely different (in this case, to a Linux kernel providing us withread,write, andexit/exit_groupsyscalls).In a very real way, the standard C library is an abstraction wecanreplace if we wanted to, as long as we can devise a sane/effective/acceptable function interfaces for the things we need. Because the three syscalls this program needs are so simple, I just used the Linux equivalents for the API.In particular, in all Linux architectures,sizeof (long) == sizeof (void *), so thatlonghas the same properties asintptr_t, andunsigned longthe same properties asuintptr_t. This is why the function wrappers around the syscalls uselongand not some other type. Other, non-Linux (compatible) systems, use other conventions, so this is the kind of thing one has to think about when creating interfaces and APIs in freestanding C.