The officially official Devuan Forum!

You are not logged in.

#1 Today 21:48:40

igorzwx
Member
Registered: 2024-05-06
Posts: 530  

ALSA with double precision

ALSA with double precision

1. alsa-lib

Prerequisites

sudo apt-get install build-essential dpkg-dev devscripts quilt fakeroot debhelper
sudo apt-get build-dep alsa-lib 
$ tree BUILD3
BUILD3
└── PATCHES_alsa-lib
    ├── 0001-rate-support-float-input-output-formats.patch
    ├── 0002-plug-allow-float-formats-in-rate-channel-change-plug.patch
    ├── 0003-route-fix-float-support-in-channel-mixing-paths.patch
    └── 0004-pcm_plugin.h-update-rate-plugin-comment-for-float-fo.patch 
cd BUILD3
apt-get source alsa-lib
cd alsa-lib-*/
export QUILT_PATCHES=debian/patches
quilt push -a
quilt import ../PATCHES_alsa-lib/000*.patch
quilt push -a
dch -v "1.2.50-1" "Local rebuild: enable float64 audio" 
dpkg-buildpackage -b -us -uc
mkdir -p ../dist && mv -v ../*.deb ../*.changes ../*.buildinfo ../dist 2>/dev/null
sudo dpkg -i ../dist/libasound2_*.deb ../dist/libasound2-data_*.deb ../dist/libasound2-dev_*.deb

PATCHES for alsa-lib

$ cat PATCHES_alsa-lib/0001*
From 98e1f84f04e190dc5d9086aaa61f359267438e8a Mon Sep 17 00:00:00 2001
From: igorzwx <igorzwx@devuan.cargo-cult.org>
Date: Thu, 15 Oct 2026 13:13:44 +0200
Subject: [PATCH 1/4] rate: support float input/output formats
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

The rate plugin restricted clients and slaves to linear formats only,
even though converters implementing the 0x010003 protocol may advertise
non-linear formats via get_supported_formats (e.g. fftrate handles
float and float64 internally).

- snd_pcm_rate_hw_refine_cprepare: OR the converter-advertised format
  mask (in_formats for playback, out_formats for capture) into the
  client format mask instead of restricting to SND_PCM_FMTBIT_LINEAR.
- get_best_format: score float candidates so a float client format
  prefers a float converter format over an integer one.
- snd_pcm_rate_hw_params: select lfloat get/put indices when a float
  format is involved; reject float↔float width mismatches.
- do_convert: dispatch float↔linear conversion to
  snd_pcm_lfloat_convert_float_integer / _convert_integer_float.
- snd_pcm_rate_open / _snd_pcm_rate_open: accept float sformat.
- export lfloat helper prototypes in pcm_plugin.h.
- doc: update "formats must be linear" wording.
---
 src/pcm/pcm_plugin.h |  12 ++++
 src/pcm/pcm_rate.c   | 163 +++++++++++++++++++++++++++++++++++++------
 2 files changed, 154 insertions(+), 21 deletions(-)

diff --git a/src/pcm/pcm_plugin.h b/src/pcm/pcm_plugin.h
index 9896ee8..d9e8714 100644
--- a/src/pcm/pcm_plugin.h
+++ b/src/pcm/pcm_plugin.h
@@ -104,6 +104,18 @@ snd_pcm_sframes_t snd_pcm_plugin_undo_write_generic
 int snd_pcm_linear_get_index(snd_pcm_format_t src_format, snd_pcm_format_t dst_format);
 int snd_pcm_linear_put_index(snd_pcm_format_t src_format, snd_pcm_format_t dst_format);
 int snd_pcm_linear_convert_index(snd_pcm_format_t src_format, snd_pcm_format_t dst_format);
+int snd_pcm_lfloat_get_s32_index(snd_pcm_format_t format);
+int snd_pcm_lfloat_put_s32_index(snd_pcm_format_t format);
+
+void snd_pcm_lfloat_convert_integer_float(const snd_pcm_channel_area_t *dst_areas, snd_pcm_uframes_t dst_offset,
+					  const snd_pcm_channel_area_t *src_areas, snd_pcm_uframes_t src_offset,
+					  unsigned int channels, snd_pcm_uframes_t frames,
+					  unsigned int get32idx, unsigned int put32floatidx);
+
+void snd_pcm_lfloat_convert_float_integer(const snd_pcm_channel_area_t *dst_areas, snd_pcm_uframes_t dst_offset,  
+					  const snd_pcm_channel_area_t *src_areas, snd_pcm_uframes_t src_offset,  
+					  unsigned int channels, snd_pcm_uframes_t frames,  
+					  unsigned int put32idx, unsigned int get32floatidx);
 
 void snd_pcm_linear_convert(const snd_pcm_channel_area_t *dst_areas, snd_pcm_uframes_t dst_offset,
 			    const snd_pcm_channel_area_t *src_areas, snd_pcm_uframes_t src_offset,
diff --git a/src/pcm/pcm_rate.c b/src/pcm/pcm_rate.c
index e5d8edd..14ac6a3 100644
--- a/src/pcm/pcm_rate.c
+++ b/src/pcm/pcm_rate.c
@@ -64,6 +64,11 @@ struct _snd_pcm_rate {
 	snd_pcm_rate_ops_t ops;
 	unsigned int src_conv_idx;
 	unsigned int dst_conv_idx;
+	/* float-aware conversion: lfloat ops instead of conv_labels */
+	unsigned int src_get_idx, src_put_idx;
+	unsigned int dst_get_idx, dst_put_idx;
+	int src_lfloat_mode;	/* 0=linear_convert, 1=lfloat_getput, 2=lfloat_putget */
+	int dst_lfloat_mode;
 	snd_pcm_channel_area_t *src_buf;
 	snd_pcm_channel_area_t *dst_buf;
 	int start_pending; /* start is triggered but not commited to slave */
@@ -77,6 +82,8 @@ struct _snd_pcm_rate {
 	unsigned int format_flags;
 };
 
+#define RATE_COPY	0xffffffffU	/* sentinel: pure copy, no conversion */
+
 #define SND_PCM_RATE_PLUGIN_VERSION_OLD	0x010001	/* old rate plugin */
 #endif /* DOC_HIDDEN */
 
@@ -125,6 +132,9 @@ static int snd_pcm_rate_hw_refine_cprepare(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_
 	int err;
 	snd_pcm_access_mask_t access_mask = { SND_PCM_ACCBIT_SHM };
 	snd_pcm_format_mask_t format_mask = { SND_PCM_FMTBIT_LINEAR };
+	/* converter-advertised extras, e.g. float — client side only */  
+	format_mask.bits[0] |= pcm->stream == SND_PCM_STREAM_PLAYBACK ?  
+			       rate->in_formats : rate->out_formats;
 	err = _snd_pcm_hw_param_set_mask(params, SND_PCM_HW_PARAM_ACCESS,
 					 &access_mask);
 	if (err < 0)
@@ -284,6 +294,7 @@ static int get_best_format(uint64_t mask, snd_pcm_format_t orig)
 	int pwidth = snd_pcm_format_physical_width(orig);
 	int width = snd_pcm_format_width(orig);
 	int signd = snd_pcm_format_signed(orig);
+	int orig_float = snd_pcm_format_float(orig);
 	int best_score = -1;
 	int match = -1;
 	int f, score;
@@ -292,7 +303,18 @@ static int get_best_format(uint64_t mask, snd_pcm_format_t orig)
 		if (!(mask & (1ULL << f)))
 			continue;
 		score = 0;
-		if (snd_pcm_format_linear(f)) {
+		if (snd_pcm_format_float(f)) {
+			if (!orig_float)
+				continue;	/* never pick float for linear orig */
+			score += 8;		/* same family: always beats linear */
+			if (snd_pcm_format_physical_width(f) == pwidth)
+				score += 2;
+			else if (snd_pcm_format_physical_width(f) > pwidth)
+				score++;
+		} else if (snd_pcm_format_linear(f)) {
+			if (orig_float)
+				continue;	/* never pick linear for float orig */
+			score += 8;
 			if (snd_pcm_format_physical_width(f) == pwidth)
 				score++;
 			if (snd_pcm_format_physical_width(f) >= pwidth)
@@ -439,9 +461,38 @@ static int snd_pcm_rate_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t * params)
 	}
 
 	if (need_src_buf) {
-		rate->src_conv_idx =
-			snd_pcm_linear_convert_index(rate->orig_in_format,
-						     rate->info.in.format);
+		snd_pcm_format_t f1 = rate->orig_in_format;
+		snd_pcm_format_t f2 = rate->info.in.format;
+		if (f1 == f2) {
+			rate->src_lfloat_mode = 0;
+			rate->src_conv_idx = RATE_COPY;		/* deinterleave only */
+		} else if (snd_pcm_format_linear(f1) && snd_pcm_format_linear(f2)) {
+			rate->src_lfloat_mode = 0;
+			rate->src_conv_idx = snd_pcm_linear_convert_index(f1, f2);
+		} else if (snd_pcm_format_float(f1) && snd_pcm_format_linear(f2)) {
+			rate->src_lfloat_mode = 1;	/* float -> int: get32float + put32 */
+			rate->src_get_idx = snd_pcm_lfloat_get_s32_index(f1);
+			rate->src_put_idx = snd_pcm_linear_put_index(SND_PCM_FORMAT_S32, f2);
+		} else if (snd_pcm_format_linear(f1) && snd_pcm_format_float(f2)) {
+			rate->src_lfloat_mode = 2;	/* int -> float: get32 + put32float */
+			rate->src_get_idx = snd_pcm_linear_get_index(f1, SND_PCM_FORMAT_S32);
+			rate->src_put_idx = snd_pcm_lfloat_put_s32_index(f2);
+		} else {
+			/* float -> float of different width: no single-pass
+			 * converter (lfloat ops go via int32 on one side) */
+			SNDERR("rate plugin: unsupported src conversion %s -> %s",
+			       snd_pcm_format_name(f1), snd_pcm_format_name(f2));
+			err = -EINVAL;
+			goto error;
+		}
+		if ((rate->src_lfloat_mode && ((int)rate->src_get_idx < 0 || (int)rate->src_put_idx < 0)) ||
+		    (!rate->src_lfloat_mode && rate->src_conv_idx != RATE_COPY &&
+		     (int)rate->src_conv_idx < 0)) {
+			SNDERR("rate plugin: unsupported src conversion %s -> %s",
+			       snd_pcm_format_name(f1), snd_pcm_format_name(f2));
+			err = -EINVAL;
+			goto error;
+		}
 		rate->src_buf = rate_alloc_tmp_buf(rate->info.in.format,
 						   channels, rate->info.in.period_size);
 		if (!rate->src_buf) {
@@ -451,9 +502,36 @@ static int snd_pcm_rate_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t * params)
 	}
 
 	if (need_dst_buf) {
-		rate->dst_conv_idx =
-			snd_pcm_linear_convert_index(rate->info.out.format,
-						     rate->orig_out_format);
+		snd_pcm_format_t f1 = rate->info.out.format;
+		snd_pcm_format_t f2 = rate->orig_out_format;
+		if (f1 == f2) {
+			rate->dst_lfloat_mode = 0;
+			rate->dst_conv_idx = RATE_COPY;
+		} else if (snd_pcm_format_linear(f1) && snd_pcm_format_linear(f2)) {
+			rate->dst_lfloat_mode = 0;
+			rate->dst_conv_idx = snd_pcm_linear_convert_index(f1, f2);
+		} else if (snd_pcm_format_float(f1) && snd_pcm_format_linear(f2)) {
+			rate->dst_lfloat_mode = 1;	/* float -> int */
+			rate->dst_get_idx = snd_pcm_lfloat_get_s32_index(f1);
+			rate->dst_put_idx = snd_pcm_linear_put_index(SND_PCM_FORMAT_S32, f2);
+		} else if (snd_pcm_format_linear(f1) && snd_pcm_format_float(f2)) {
+			rate->dst_lfloat_mode = 2;	/* int -> float */
+			rate->dst_get_idx = snd_pcm_linear_get_index(f1, SND_PCM_FORMAT_S32);
+			rate->dst_put_idx = snd_pcm_lfloat_put_s32_index(f2);
+		} else {
+			SNDERR("rate plugin: unsupported dst conversion %s -> %s",
+			       snd_pcm_format_name(f1), snd_pcm_format_name(f2));
+			err = -EINVAL;
+			goto error;
+		}
+		if ((rate->dst_lfloat_mode && ((int)rate->dst_get_idx < 0 || (int)rate->dst_put_idx < 0)) ||
+		    (!rate->dst_lfloat_mode && rate->dst_conv_idx != RATE_COPY &&
+		     (int)rate->dst_conv_idx < 0)) {
+			SNDERR("rate plugin: unsupported dst conversion %s -> %s",
+			       snd_pcm_format_name(f1), snd_pcm_format_name(f2));
+			err = -EINVAL;
+			goto error;
+		}
 		rate->dst_buf = rate_alloc_tmp_buf(rate->info.out.format,
 						   channels, rate->info.out.period_size);
 		if (!rate->dst_buf) {
@@ -585,10 +663,28 @@ static void do_convert(const snd_pcm_channel_area_t *dst_areas,
 	}
 
 	if (rate->src_buf) {
-		snd_pcm_linear_convert(rate->src_buf, 0,
-				       src_areas, src_offset,
-				       channels, src_frames,
-				       rate->src_conv_idx);
+		if (rate->src_lfloat_mode == 1)		/* float -> int */
+			snd_pcm_lfloat_convert_float_integer(rate->src_buf, 0,
+					      src_areas, src_offset,
+					      channels, src_frames,
+					      rate->src_put_idx,
+					      rate->src_get_idx);
+		else if (rate->src_lfloat_mode == 2)	/* int -> float */
+			snd_pcm_lfloat_convert_integer_float(rate->src_buf, 0,
+					      src_areas, src_offset,
+					      channels, src_frames,
+					      rate->src_get_idx,
+					      rate->src_put_idx);
+		else if (rate->src_conv_idx == RATE_COPY)
+			snd_pcm_areas_copy(rate->src_buf, 0,
+					   src_areas, src_offset,
+					   channels, src_frames,
+					   rate->info.in.format);
+		else
+			snd_pcm_linear_convert(rate->src_buf, 0,
+					       src_areas, src_offset,
+					       channels, src_frames,
+					       rate->src_conv_idx);
 		src_areas = rate->src_buf;
 		src_offset = 0;
 	}
@@ -602,11 +698,30 @@ static void do_convert(const snd_pcm_channel_area_t *dst_areas,
 				      dst_frames,
 				      snd_pcm_channel_area_addr(src_areas, src_offset),
 				      src_frames);
-	if (rate->dst_buf)
-		snd_pcm_linear_convert(dst_areas, dst_offset,
-				       rate->dst_buf, 0,
-				       channels, dst_frames,
-				       rate->dst_conv_idx);
+	if (rate->dst_buf) {  
+		if (rate->dst_lfloat_mode == 1)		/* float -> int */
+			snd_pcm_lfloat_convert_float_integer(dst_areas, dst_offset,
+					      rate->dst_buf, 0,
+					      channels, dst_frames,
+					      rate->dst_put_idx,
+					      rate->dst_get_idx);
+		else if (rate->dst_lfloat_mode == 2)	/* int -> float */
+			snd_pcm_lfloat_convert_integer_float(dst_areas, dst_offset,
+					      rate->dst_buf, 0,
+					      channels, dst_frames,
+					      rate->dst_get_idx,
+					      rate->dst_put_idx);
+		else if (rate->dst_conv_idx == RATE_COPY)  
+			snd_pcm_areas_copy(dst_areas, dst_offset,  
+					   rate->dst_buf, 0,  
+					   channels, dst_frames,  
+					   rate->orig_out_format);  
+		else  
+			snd_pcm_linear_convert(dst_areas, dst_offset,  
+					       rate->dst_buf, 0,  
+					       channels, dst_frames,  
+					       rate->dst_conv_idx);  
+	}
 }
 
 static inline void
@@ -1483,7 +1598,7 @@ static int is_string_array(const snd_config_t *conf)
  * \brief Creates a new rate PCM
  * \param pcmp Returns created PCM handle
  * \param name Name of PCM
- * \param sformat Slave format
+ * \param sformat Slave format (linear or float).
  * \param srate Slave rate
  * \param converter SRC type string node
  * \param slave Slave PCM handle
@@ -1509,7 +1624,8 @@ int snd_pcm_rate_open(snd_pcm_t **pcmp, const char *name,
 
 	assert(pcmp && slave);
 	if (sformat != SND_PCM_FORMAT_UNKNOWN &&
-	    snd_pcm_format_linear(sformat) != 1)
+	    snd_pcm_format_linear(sformat) != 1 &&
+	    snd_pcm_format_float(sformat) != 1)
 		return -EINVAL;
 	rate = calloc(1, sizeof(snd_pcm_rate_t));
 	if (!rate) {
@@ -1566,6 +1682,7 @@ int snd_pcm_rate_open(snd_pcm_t **pcmp, const char *name,
 			break;
 		}
 		if (!type) {
+
 			SNDERR("No name given for rate converter");
 			snd_pcm_free(pcm);
 			free(rate);
@@ -1623,7 +1740,10 @@ int snd_pcm_rate_open(snd_pcm_t **pcmp, const char *name,
 
 \section pcm_plugins_rate Plugin: Rate
 
-This plugin converts a stream rate. The input and output formats must be linear.
+This plugin converts a stream rate. The input and output formats must be  
+linear or float. Since protocol version 0x010003, a converter may advertise  
+its supported formats via the get_supported_formats callback; the plugin  
+then negotiates a matching format and inserts the necessary conversions. 
 
 \code
 pcm.name {
@@ -1713,9 +1833,10 @@ int _snd_pcm_rate_open(snd_pcm_t **pcmp, const char *name,
 	if (err < 0)
 		return err;
 	if (sformat != SND_PCM_FORMAT_UNKNOWN &&
-	    snd_pcm_format_linear(sformat) != 1) {
+	    snd_pcm_format_linear(sformat) != 1 &&
+	    snd_pcm_format_float(sformat) != 1) {
 	    	snd_config_delete(sconf);
-		SNDERR("slave format is not linear");
+		SNDERR("slave format is not linear or float");
 		return -EINVAL;
 	}
 	err = snd_pcm_open_slave(&spcm, root, sconf, stream, mode, conf);
-- 
2.39.5
$ cat PATCHES_alsa-lib/0002*
From 8a9e12f00ec215eb9801b34da8a852cab5631ea6 Mon Sep 17 00:00:00 2001
From: igorzwx <igorzwx@devuan.cargo-cult.org>
Date: Thu, 15 Oct 2026 13:17:22 +0200
Subject: [PATCH 2/4] plug: allow float formats in rate/channel-change plugin
 insertion

snd_pcm_plug_change_rate and snd_pcm_plug_change_channels asserted
snd_pcm_format_linear(slv->format), forcing float clients through an
integer downconversion before the rate plugin could see them. Relax
the assertions and the clt->format propagation checks to accept float
formats, so a float-capable rate converter (get_supported_formats)
receives float input when channels also differ.
---
 src/pcm/pcm_plug.c | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/src/pcm/pcm_plug.c b/src/pcm/pcm_plug.c
index abf6f1a..34a7185 100644
--- a/src/pcm/pcm_plug.c
+++ b/src/pcm/pcm_plug.c
@@ -374,14 +374,16 @@ static int snd_pcm_plug_change_rate(snd_pcm_t *pcm, snd_pcm_t **new, snd_pcm_plu
 	int err;
 	if (clt->rate == slv->rate)
 		return 0;
-	assert(snd_pcm_format_linear(slv->format));
+	assert(snd_pcm_format_linear(slv->format) ||
+	       snd_pcm_format_float(slv->format));
 	err = snd_pcm_rate_open(new, NULL, slv->format, slv->rate, plug->rate_converter,
 				plug->gen.slave, plug->gen.slave != plug->req_slave);
 	if (err < 0)
 		return err;
 	slv->access = clt->access;
 	slv->rate = clt->rate;
-	if (snd_pcm_format_linear(clt->format))
+	if (snd_pcm_format_linear(clt->format) ||
+	    snd_pcm_format_float(clt->format))
 		slv->format = clt->format;
 	return 1;
 }
@@ -400,7 +402,8 @@ static int snd_pcm_plug_change_channels(snd_pcm_t *pcm, snd_pcm_t **new, snd_pcm
 	if (clt->rate != slv->rate &&
 	    clt->channels > slv->channels)
 		return 0;
-	assert(snd_pcm_format_linear(slv->format));
+	assert(snd_pcm_format_linear(slv->format) ||
+	       snd_pcm_format_float(slv->format));
 	tt_ssize = slv->channels;
 	tt_cused = clt->channels;
 	tt_sused = slv->channels;
-- 
2.39.5
$ cat PATCHES_alsa-lib/0003*
From 55a6cf40140048488d05475e5db8f53e68db53d8 Mon Sep 17 00:00:00 2001
From: igorzwx <igorzwx@devuan.cargo-cult.org>
Date: Thu, 15 Oct 2026 13:18:58 +0200
Subject: [PATCH 3/4] route: fix float support in channel mixing paths

Complete the SND_PCM_PLUGIN_ROUTE_FLOAT code paths: get/put index
selection for float formats and the many-source summation labels so
routing/attenuation works when the negotiated format is float.
---
 src/pcm/pcm_route.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/pcm/pcm_route.c b/src/pcm/pcm_route.c
index d3e5f3f..a612dc7 100644
--- a/src/pcm/pcm_route.c
+++ b/src/pcm/pcm_route.c
@@ -1025,7 +1025,8 @@ int snd_pcm_route_open(snd_pcm_t **pcmp, const char *name,
 	int err;
 	assert(pcmp && slave && ttable);
 	if (sformat != SND_PCM_FORMAT_UNKNOWN && 
-	    snd_pcm_format_linear(sformat) != 1)
+	    snd_pcm_format_linear(sformat) != 1 &&
+	    snd_pcm_format_float(sformat) != 1)
 		return -EINVAL;
 	route = calloc(1, sizeof(snd_pcm_route_t));
 	if (!route) {
-- 
2.39.5
$ cat PATCHES_alsa-lib/0004*
From 6d386441fbf81f3fb6b82222e74b22abdb3ff360 Mon Sep 17 00:00:00 2001
From: igorzwx <igorzwx@devuan.cargo-cult.org>
Date: Thu, 15 Oct 2026 13:20:21 +0200
Subject: [PATCH 4/4] pcm_plugin.h: update rate plugin comment for float
 formats
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

"Rate plugin for linear formats" → reflect that float formats are
now supported end-to-end.
---
 include/pcm_plugin.h | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/include/pcm_plugin.h b/include/pcm_plugin.h
index 2d01439..2b226f7 100644
--- a/include/pcm_plugin.h
+++ b/include/pcm_plugin.h
@@ -134,7 +134,7 @@ int _snd_pcm_adpcm_open(snd_pcm_t **pcmp, const char *name,
 			snd_pcm_stream_t stream, int mode);
 
 /*
- *  Route plugin for linear formats
+ *  Route plugin for linear formats and float formats
  */
 int snd_pcm_route_load_ttable(snd_config_t *tt, snd_pcm_route_ttable_entry_t *ttable,
 			      unsigned int tt_csize, unsigned int tt_ssize,
@@ -154,7 +154,7 @@ int _snd_pcm_route_open(snd_pcm_t **pcmp, const char *name,
 			snd_pcm_stream_t stream, int mode);
 
 /*
- *  Rate plugin for linear formats
+ *  Rate plugin for linear formats and float formats
  */
 int snd_pcm_rate_open(snd_pcm_t **pcmp, const char *name,
 		      snd_pcm_format_t sformat, unsigned int srate,
-- 
2.39.5

2. alsa-utils

Build-deps

sudo apt-get install libasound2-dev libatopology-dev libfftw3-dev libncurses-dev pkg-config python3-docutils xmlto
$ tree BUILD2
BUILD2
└── PATCHES_alsa-utils
    ├── 0001-debian-remove-leftover-systemd-artifacts-from-Devuan.patch
    └── 0002-aplay-add-64-bit-float-WAV-support-and-fix-vumeter-f.patch
cd BUILD2
apt-get source alsa-utils
cd alsa-utils-*/
export QUILT_PATCHES=debian/patches
quilt push -a
quilt import ../PATCHES_alsa-utils/000*.patch
quilt push -a
dch -v "1.2.50-1" "Local rebuild: enable float64 audio" 
dpkg-buildpackage -b -us -uc
mkdir -p ../dist && mv -v ../*.deb ../*.changes ../*.buildinfo ../dist 2>/dev/null
sudo dpkg -i ../dist/alsa-utils_*.deb 

WARNING: 0001-*.patch modifies files inside debian/ (control, rules,
install, init). Fine for local .deb builds; if you plan to build a
source package or upload, apply the debian/ changes directly instead.
Example: How to Create a Debian Package Release Bundle
_https://dev1galaxy.org/viewtopic.php?pid=60629#p60629

PATCHES for alsa-utils

$ cat PATCHES_alsa-utils/0001*
From 941fb6274bed74f1a51f66b82453fd2cc940cfae Mon Sep 17 00:00:00 2001
From: igorzwx <igorzwx@devuan.cargo-cult.org>
Date: Fri, 13 Nov 2026 03:00:20 +0100
Subject: [PATCH 1/2] debian: remove leftover systemd artifacts from Devuan
 packaging
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This tree comes from the Devuan official repository source package
(apt source alsa-utils). The packaging was originally built with
systemd support, then unpacked, had the systemd dependency removed,
and re-packed — leaving stale systemd references that break the
build on a sysvinit system:

- debian/control: drop "systemd" from Build-Depends. It only exists
  to let configure resolve systemdsystemunitdir via pkg-config
  (configure.ac); without it the unit dir is empty and
  HAVE_SYSTEMD is false. Also replace the transitional
  libncursesw5-dev with libncurses-dev (ncursesw is probed via
  pkg-config/ncursesw5-config/-lncursesw, all provided by
  libncurses-dev), and drop libsamplerate-dev (optional
  AC_CHECK_HEADERS([samplerate.h]) for alsaloop only).

- debian/rules: --with-systemdsystemunitdir=/lib/systemd/system ->
  --without-systemdsystemunitdir. The explicit path forced
  HAVE_SYSTEMD on even without systemd.pc, installing
  alsa-state.service/alsa-restore.service and creating
  sound.target.wants symlinks (alsactl/Makefile.am) — dead
  units on a sysvinit system.

- debian/install: drop "lib/systemd" pattern. With units no longer
  installed into debian/tmp, dh_install fails with "missing files"
  under dh_missing --fail-missing. Keep "lib/udev":
  90-alsa-restore.rules is installed unconditionally via
  udevrules_DATA (alsactl/Makefile.am) and works with eudev.

- debian/init: fix and extend the sysvinit script, which is now the
  only state save/restore mechanism (with the udev rule) since
  systemd units are disabled:
  * add LSB-required "status" action reporting loaded cards
    (/proc/asound/cards), state file presence/timestamp, and
    alsactl daemon pidfile (/run/alsactl.pid)
  * restart/force-reload: pass "$2" so per-card restart works
  * store_levels: drop "sleep 1" (alsactl store is synchronous)
  * executable(): replace deprecated which(1) with POSIX command -v
  * drop redundant -F flag (force_restore=1 is the compiled-in
    default, alsactl.c)
  * move runtime-dir mkdir into the actions that need it
  * fix warning-message quoting; remove dead mute_and_zero_*
    helpers; shellcheck clean (SC1091 source= directives, SC2086)
---
 debian/control |   4 +-
 debian/init    | 239 ++++++++++++++++++++-----------------------------
 debian/install |   1 -
 debian/rules   |   2 +-
 4 files changed, 101 insertions(+), 145 deletions(-)

diff --git a/debian/control b/debian/control
index 01229ec..b2564dc 100644
--- a/debian/control
+++ b/debian/control
@@ -9,11 +9,9 @@ Build-Depends: debhelper-compat (= 13),
                libasound2-dev (>= 1.2.1),
                libatopology-dev (>= 1.2.2),
                libfftw3-dev,
-               libncursesw5-dev,
-               libsamplerate-dev,
+               libncurses-dev,
                pkg-config,
                python3-docutils,
-               systemd,
                xmlto
 Standards-Version: 4.6.1
 Homepage: https://www.alsa-project.org/
diff --git a/debian/init b/debian/init
index 632573c..853d571 100755
--- a/debian/init
+++ b/debian/init
@@ -10,7 +10,7 @@
 # Default-Stop:      0 1 6
 # Short-Description: Restore and store ALSA driver settings
 # Description:       This script stores and restores mixer levels on
-#                    shutdown and bootup.On sysv-rc systems: to
+#                    shutdown and bootup. On sysv-rc systems: to
 #                    disable storing of mixer levels on shutdown,
 #                    remove /etc/rc[06].d/K50alsa-utils.  To disable
 #                    restoring of mixer levels on bootup, rename the
@@ -27,28 +27,25 @@ PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
 MYNAME=/etc/init.d/alsa-utils
 ALSACTLHOME=/run/alsa
 ALSACTLRUNTIME="${ALSACTLHOME}/runtime"
+ALSACTL_PIDFILE=/run/alsactl.pid
 
-[ -d "$ALSACTLRUNTIME" ] || mkdir -p "$ALSACTLRUNTIME"
-
+# shellcheck source=/lib/lsb/init-functions
 . /lib/lsb/init-functions
+# shellcheck source=debian/utils.sh
 . /usr/share/alsa/utils.sh
 
 # $1 EXITSTATUS
 # [$2 MESSAGE]
 log_action_end_msg_and_exit()
 {
-	log_action_end_msg "$1" ${2:+"$2"}
-	exit $1
+    log_action_end_msg "$1" ${2:+"$2"}
+    exit "$1"
 }
 
 # $1 PROGRAM
 executable()
 {
-	# If which is not available then we must be running before
-	# /usr is mounted on a system that has which in /usr/bin/.
-	# Conclude that $1 is not executable.
-	[ -x /bin/which ] || [ -x /usr/bin/which ] || return 1
-	which "$1" >/dev/null 2>&1
+    command -v "$1" >/dev/null 2>&1
 }
 
 executable amixer || { echo "${MYNAME}: Error: No amixer program available." >&2 ; exit 1 ; }
@@ -56,102 +53,45 @@ executable amixer || { echo "${MYNAME}: Error: No amixer program available." >&2
 # $1 <card ID> | "all"
 restore_levels()
 {
-	[ -f /var/lib/alsa/asound.state ] || return 1
-	CARD="$1"
-	[ "$1" = all ] && CARD=""
-	# Assume that if alsactl prints a message on stderr
-	# then it failed somehow.  This works around the fact
-	# that alsactl doesn't return nonzero status when it
-	# can't restore settings for the card
-	if MSG="$(alsactl -E HOME="$ALSACTLHOME" -E XDG_RUNTIME_DIR="${ALSACTLRUNTIME}" restore $CARD 2>&1 >/dev/null)" && [ ! "$MSG" ] ; then
-		return 0
-	else
-		# Retry with the "force" option.  This restores more levels
-		# but it results in much longer error messages.
-		alsactl -E HOME="$ALSACTLHOME" -E XDG_RUNTIME_DIR="${ALSACTLRUNTIME}" -F restore $CARD >/dev/null 2>&1
-		log_action_cont_msg "warning: 'alsactl -E HOME="$ALSACTLHOME" -E XDG_RUNTIME_DIR="${ALSACTLRUNTIME}" restore${CARD:+ $CARD}' failed with error message '$MSG'"
-		return 1
-	fi
+    [ -f /var/lib/alsa/asound.state ] || return 1
+    CARD="$1"
+    [ "$1" = all ] && CARD=""
+    # Assume that if alsactl prints a message on stderr
+    # then it failed somehow.  This works around the fact
+    # that alsactl doesn't return nonzero status when it
+    # can't restore settings for the card
+    if MSG="$(alsactl -E HOME="$ALSACTLHOME" -E XDG_RUNTIME_DIR="$ALSACTLRUNTIME" restore $CARD 2>&1 >/dev/null)" && [ ! "$MSG" ] ; then
+        return 0
+    else
+        log_action_cont_msg "warning: alsactl restore${CARD:+ $CARD} failed: $MSG"
+        return 1
+    fi
 }
 
 # $1 <card ID> | "all"
 store_levels()
 {
-	CARD="$1"
-	[ "$1" = all ] && CARD=""
-	if MSG="$(alsactl -E HOME="$ALSACTLHOME" -E XDG_RUNTIME_DIR="${ALSACTLRUNTIME}" store $CARD 2>&1)" ; then
-		sleep 1
-		return 0
-	else
-		log_action_cont_msg "warning: 'alsactl store${CARD:+ $CARD}' -E HOME="$ALSACTLHOME" -E XDG_RUNTIME_DIR="${ALSACTLRUNTIME}" failed with error message '$MSG'"
-		return 1
-	fi
-}
-
-
-# $1 <card ID>
-mute_and_zero_levels_on_card()
-{
-	CARDOPT="-c $1"
-	for CTL in \
-		Master \
-		PCM \
-		Synth \
-		CD \
-		Line \
-		Mic \
-		"PCM,1" \
-		Wave \
-		Music \
-		AC97 \
-		"Master Digital" \
-		DAC \
-		"DAC,0" \
-		"DAC,1" \
-		Headphone \
-		Speaker \
-		Playback
-	do
-		mute_and_zero_level "$CTL"
-	done
-#	for CTL in \
-#		"Audigy Analog/Digital Output Jack" \
-#		"SB Live Analog/Digital Output Jack"
-#	do
-#		switch_control "$CTL" off
-#	done
-	return 0
+    CARD="$1"
+    [ "$1" = all ] && CARD=""
+    if MSG="$(alsactl -E HOME="$ALSACTLHOME" -E XDG_RUNTIME_DIR="$ALSACTLRUNTIME" store $CARD 2>&1)" ; then
+        return 0
+    else
+        log_action_cont_msg "warning: alsactl store${CARD:+ $CARD} failed: $MSG"
+        return 1
+    fi
 }
 
-# $1 <card ID> | "all"
-mute_and_zero_levels()
-{
-	TTZML_RETURNSTATUS=0
-	case "$1" in
-	  all)
-		for CARD in $(echo_card_indices) ; do
-			mute_and_zero_levels_on_card "$CARD" || TTZML_RETURNSTATUS=1
-		done
-		;;
-	  *)
-		mute_and_zero_levels_on_card "$1" || TTZML_RETURNSTATUS=1
-		;;
-	esac
-	return $TTZML_RETURNSTATUS
-}
-
-
 # $1 <card ID> | "all"
 card_OK()
 {
-	[ "$1" ] || bugout
-	if [ "$1" = all ] ; then
-		[ -d /proc/asound ]
-		return $?
-	else
-		[ -d "/proc/asound/card$1" ] || [ -d "/proc/asound/$1" ]
-		return $?
-	fi
+    [ "$1" ] || bugout
+    if [ "$1" = all ] ; then
+        [ -d /proc/asound ]
+        return $?
+    else
+        [ -d "/proc/asound/card$1" ] || [ -d "/proc/asound/$1" ]
+        return $?
+    fi
 }
 
 # If a card identifier is provided in $2 then regard it as an error
@@ -159,52 +99,71 @@ card_OK()
 
 case "$1" in
   start)
-	EXITSTATUS=0
-	TARGET_CARD="$2"
-	case "$TARGET_CARD" in
-	  ""|all) TARGET_CARD=all ; log_action_begin_msg "Setting up ALSA" ;;
-	  *) log_action_begin_msg "Setting up ALSA card ${TARGET_CARD}" ;;
-	esac
-	card_OK "$TARGET_CARD" || log_action_end_msg_and_exit "$( [ ! "$2" ] ; echo $? ; )" "none loaded"
-	preinit_levels "$TARGET_CARD" || EXITSTATUS=1
-	if ! restore_levels "$TARGET_CARD" ; then
-		sanify_levels "$TARGET_CARD" || EXITSTATUS=1
-		restore_levels "$TARGET_CARD" >/dev/null 2>&1 || :
-	fi
-	log_action_end_msg_and_exit "$EXITSTATUS"
-	;;
+    EXITSTATUS=0
+    TARGET_CARD="$2"
+    case "$TARGET_CARD" in
+      ""|all) TARGET_CARD=all ; log_action_begin_msg "Setting up ALSA" ;;
+      *) log_action_begin_msg "Setting up ALSA card ${TARGET_CARD}" ;;
+    esac
+    card_OK "$TARGET_CARD" || log_action_end_msg_and_exit "$( [ ! "$2" ] ; echo $? ; )" "none loaded"
+    [ -d "$ALSACTLRUNTIME" ] || mkdir -p "$ALSACTLRUNTIME"
+    preinit_levels "$TARGET_CARD" || EXITSTATUS=1
+    if ! restore_levels "$TARGET_CARD" ; then
+        sanify_levels "$TARGET_CARD" || EXITSTATUS=1
+        restore_levels "$TARGET_CARD" >/dev/null 2>&1 || :
+    fi
+    log_action_end_msg_and_exit "$EXITSTATUS"
+    ;;
   stop)
-	EXITSTATUS=0
-	TARGET_CARD="$2"
-	case "$TARGET_CARD" in
-	  ""|all) TARGET_CARD=all ; log_action_begin_msg "Shutting down ALSA" ;;
-	  *) log_action_begin_msg "Shutting down ALSA card ${TARGET_CARD}" ;;
-	esac
-	card_OK "$TARGET_CARD" || log_action_end_msg_and_exit "$( [ ! "$2" ] ; echo $? ; )" "none loaded"
-	store_levels "$TARGET_CARD" || EXITSTATUS=1
-	#mute_and_zero_levels "$TARGET_CARD" || EXITSTATUS=1
-	log_action_end_msg_and_exit "$EXITSTATUS"
-	;;
+    EXITSTATUS=0
+    TARGET_CARD="$2"
+    case "$TARGET_CARD" in
+      ""|all) TARGET_CARD=all ; log_action_begin_msg "Shutting down ALSA" ;;
+      *) log_action_begin_msg "Shutting down ALSA card ${TARGET_CARD}" ;;
+    esac
+    card_OK "$TARGET_CARD" || log_action_end_msg_and_exit "$( [ ! "$2" ] ; echo $? ; )" "none loaded"
+    [ -d "$ALSACTLRUNTIME" ] || mkdir -p "$ALSACTLRUNTIME"
+    store_levels "$TARGET_CARD" || EXITSTATUS=1
+    log_action_end_msg_and_exit "$EXITSTATUS"
+    ;;
   restart|force-reload)
-	EXITSTATUS=0
-	$0 stop || EXITSTATUS=1
-	$0 start || EXITSTATUS=1
-	exit $EXITSTATUS
-	;;
+    EXITSTATUS=0
+    $0 stop "$2" || EXITSTATUS=1
+    $0 start "$2" || EXITSTATUS=1
+    exit "$EXITSTATUS"
+    ;;
   reset)
-	TARGET_CARD="$2"
-	case "$TARGET_CARD" in
-	  ""|all) TARGET_CARD=all ; log_action_begin_msg "Resetting ALSA" ;;
-	  *) log_action_begin_msg "Resetting ALSA card ${TARGET_CARD}" ;;
-	esac
-	card_OK "$TARGET_CARD" || log_action_end_msg_and_exit "$( [ ! "$2" ] ; echo $? ; )" "none loaded"
-	preinit_levels "$TARGET_CARD"
-	sanify_levels "$TARGET_CARD"
-	log_action_end_msg_and_exit "$?"
-	;;
+    TARGET_CARD="$2"
+    case "$TARGET_CARD" in
+      ""|all) TARGET_CARD=all ; log_action_begin_msg "Resetting ALSA" ;;
+      *) log_action_begin_msg "Resetting ALSA card ${TARGET_CARD}" ;;
+    esac
+    card_OK "$TARGET_CARD" || log_action_end_msg_and_exit "$( [ ! "$2" ] ; echo $? ; )" "none loaded"
+    [ -d "$ALSACTLRUNTIME" ] || mkdir -p "$ALSACTLRUNTIME"
+    preinit_levels "$TARGET_CARD"
+    sanify_levels "$TARGET_CARD"
+    log_action_end_msg_and_exit "$?"
+    ;;
+  status)
+    if [ -f "$ALSACTL_PIDFILE" ] && kill -0 "$(cat "$ALSACTL_PIDFILE")" 2>/dev/null ; then
+        echo "alsactl daemon running (PID $(cat "$ALSACTL_PIDFILE"))"
+    fi
+    if [ -d /proc/asound ] && [ -n "$(cat /proc/asound/cards 2>/dev/null)" ] ; then
+        echo "ALSA sound cards loaded:"
+        cat /proc/asound/cards
+        if [ -f /var/lib/alsa/asound.state ] ; then
+            echo "State file: /var/lib/alsa/asound.state ($(date -r /var/lib/alsa/asound.state '+%Y-%m-%d %H:%M:%S' 2>/dev/null))"
+        else
+            echo "No saved state file (/var/lib/alsa/asound.state)"
+        fi
+        exit 0
+    else
+        echo "No ALSA sound cards loaded."
+        exit 3
+    fi
+    ;;
   *)
-	echo "Usage: $MYNAME {start [CARD]|stop [CARD]|restart [CARD]|reset [CARD]}" >&2
-	exit 3
-	;;
+    echo "Usage: $MYNAME {start [CARD]|stop [CARD]|restart [CARD]|reset [CARD]|status}" >&2
+    exit 3
+    ;;
 esac
-
diff --git a/debian/install b/debian/install
index 0a64ff7..f76ccae 100644
--- a/debian/install
+++ b/debian/install
@@ -1,5 +1,4 @@
 debian/utils.sh /usr/share/alsa
-lib/systemd
 lib/udev
 usr/bin
 usr/lib/*/alsa-topology/*.so
diff --git a/debian/rules b/debian/rules
index f988405..0e509c3 100755
--- a/debian/rules
+++ b/debian/rules
@@ -10,7 +10,7 @@ override_dh_auto_configure:
 			--with-asound-state-dir=/var/lib/alsa \
 			--with-alsactl-home-dir=/run/alsa \
 			--with-alsactl-runtime-dir=/run/alsa/runtime \
-			--with-systemdsystemunitdir=/lib/systemd/system \
+			--without-systemdsystemunitdir \
 			--disable-alsaconf
 
 override_dh_auto_test:
-- 
2.39.5
$ cat PATCHES_alsa-utils/0002*
From d545385298e4b993cab8bcb6514b997ab3e2f544 Mon Sep 17 00:00:00 2001
From: igorzwx <igorzwx@devuan.cargo-cult.org>
Date: Fri, 13 Nov 2026 03:22:47 +0100
Subject: [PATCH 2/2] aplay: add 64-bit float WAV support and fix vumeter for
 float formats

test_wavefile() rejected WAVE files with bit_p_spl=64, aborting with
"can't play WAVE-files with sample 64 bits wide", even though
SND_PCM_FORMAT_FLOAT64_LE/BE exist in the ALSA UAPI and are already
handled by axfer (container-riff-wave.c maps WAVE_FORMAT_IEEE_FLOAT
to FLOAT64_LE/BE).

- test_wavefile(): add case 64 mapping WAV_FMT_IEEE_FLOAT to
  SND_PCM_FORMAT_FLOAT64_LE/BE, mirroring the existing case 32
  float handling.
- begin_wave(): add SND_PCM_FORMAT_FLOAT64_LE -> bits=64 and emit
  WAV_FMT_IEEE_FLOAT for FLOAT64_LE, so arecord can write float64
  WAV headers symmetrically.
- compute_max_peak(): add case 64 reading double samples via
  fabs() scaled to int32 peak range, so -V vumeter works instead
  of printing "Unsupported bit size 64". Also fix the pre-existing
  case 32 bug where FLOAT_LE samples were interpreted as signed
  int bit patterns, producing garbage meter readings; float
  formats are now detected via snd_pcm_format_float() and handled
  as float/double respectively.

Verified: aplay plays Float64/Float32 WAVs through the plug chain;
aplay -V s renders the stereo meter for both.
---
 aplay/aplay.c | 69 +++++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 67 insertions(+), 2 deletions(-)

diff --git a/aplay/aplay.c b/aplay/aplay.c
index 6ce7191..cc3471b 100644
--- a/aplay/aplay.c
+++ b/aplay/aplay.c
@@ -1136,6 +1136,15 @@ static ssize_t test_wavefile(int fd, u_char *_buffer, size_t size)
 			hwparams.format = native_format;
 		}
 		break;
+	case 64:
+		if (format == WAV_FMT_IEEE_FLOAT) {
+			if (big_endian)
+				native_format = SND_PCM_FORMAT_FLOAT64_BE;
+			else
+				native_format = SND_PCM_FORMAT_FLOAT64_LE;
+			hwparams.format = native_format;
+		}
+		break;
 	default:
 		error(_(" can't play WAVE-files with sample %d bits wide"),
 		      TO_CPU_SHORT(f->bit_p_spl, big_endian));
@@ -1869,6 +1878,31 @@ static void compute_max_peak(u_char *data, size_t samples)
 		signed int *valp = (signed int *)data;
 		signed int mask = snd_pcm_format_silence_32(hwparams.format);
 
+		if (snd_pcm_format_float(hwparams.format)) {
+			float *fvalp = (float *)data;
+			float fval;
+
+			c = 0;
+			while (samples-- > 0) {
+				if (format_little_endian)
+					fval = *fvalp;
+				else {
+					uint32_t tmp = be32toh(*(uint32_t *)fvalp);
+					memcpy(&fval, &tmp, sizeof(fval));
+				}
+				fval = fabsf(fval);
+				if (fval > 1.0f)
+					fval = 1.0f;
+				val = (signed int)(fval * 0x7fffffff);
+				if (max_peak[c] < val)
+					max_peak[c] = val;
+				fvalp++;
+				if (vumeter == VUMETER_STEREO)
+					c = !c;
+			}
+			break;
+		}
+
 		c = 0;
 		while (samples-- > 0) {
 			if (format_little_endian)
@@ -1888,6 +1922,30 @@ static void compute_max_peak(u_char *data, size_t samples)
 		}
 		break;
 	}
+	case 64: {
+		double *valp = (double *)data;
+		double dval;
+
+		c = 0;
+		while (samples-- > 0) {
+			if (format_little_endian)
+				dval = *valp;
+			else {
+				uint64_t tmp = be64toh(*(uint64_t *)valp);
+				memcpy(&dval, &tmp, sizeof(dval));
+			}
+			dval = fabs(dval);
+			if (dval > 1.0)
+				dval = 1.0;
+			val = (signed int)(dval * 0x7fffffff);
+			if (max_peak[c] < val)
+				max_peak[c] = val;
+			valp++;
+			if (vumeter == VUMETER_STEREO)
+				c = !c;
+		}
+		break;
+	}
 	default:
 		if (run == 0) {
 			fprintf(stderr, _("Unsupported bit size %d.\n"), (int)bits_per_sample);
@@ -1895,7 +1953,10 @@ static void compute_max_peak(u_char *data, size_t samples)
 		}
 		return;
 	}
-	max = 1 << (significant_bits_per_sample-1);
+	if (snd_pcm_format_float(hwparams.format))
+		max = 0x7fffffff;
+	else
+		max = 1 << (significant_bits_per_sample-1);
 	if (max <= 0)
 		max = 0x7fffffff;
 
@@ -2671,6 +2732,9 @@ static void begin_wave(int fd, size_t cnt)
         case SND_PCM_FORMAT_FLOAT_LE:
 		bits = 32;
 		break;
+	case SND_PCM_FORMAT_FLOAT64_LE:
+		bits = 64;
+		break;
 	case SND_PCM_FORMAT_S24_LE:
 	case SND_PCM_FORMAT_S24_3LE:
 		bits = 24;
@@ -2687,7 +2751,8 @@ static void begin_wave(int fd, size_t cnt)
 	cf.type = WAV_FMT;
 	cf.length = LE_INT(16);
 
-        if (hwparams.format == SND_PCM_FORMAT_FLOAT_LE)
+        if (hwparams.format == SND_PCM_FORMAT_FLOAT_LE ||
+	    hwparams.format == SND_PCM_FORMAT_FLOAT64_LE)
                 f.format = LE_SHORT(WAV_FMT_IEEE_FLOAT);
         else
                 f.format = LE_SHORT(WAV_FMT_PCM);
-- 
2.39.5

3. PATCHES for fftrate

$ ls -1 PATCHES_fftrate
0001-plugin-add-float32-float64-format-support-in-data_se.patch
0002-plugin-advertise-float-input-formats-and-harden-peri.patch
0003-pcm_conv-enlarge-static-I-O-buffers-for-high-ratio-c.patch
$ cat PATCHES_fftrate/0001*
From 8e94e8fc09b7925fc99a6d4b6d7ebde4646a3bb8 Mon Sep 17 00:00:00 2001
From: igorzwx <igorzwx@devuan.cargo-cult.org>
Date: Thu, 8 Oct 2026 11:41:33 +0200
Subject: [PATCH 1/3] plugin: add float32/float64 format support in
 data_setup()
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Map SND_PCM_FORMAT_FLOAT_LE to WAVE_FORMAT_IEEE_FLOAT
(sample_size=4) and SND_PCM_FORMAT_FLOAT64_LE to
WAVE_FORMAT_IEEE_FLOAT (sample_size=8) instead of rejecting
them as "unsupported".

The conversion engine already supports both unit types:
mmsys_format_unit_type() maps IEEE_FLOAT/32-bit to
UNIT_TYPE_FLOAT and IEEE_FLOAT/64-bit to UNIT_TYPE_DOUBLE,
and samples_load()/samples_save() in convertlib.c implement
both paths. The interleave/deinterleave helpers are
byte-oriented and need no changes.

Note: under current alsa-lib, the rate plugin API negotiates
only linear integer formats, so plug demotes float client
output to s32_le/s16_le before the plugin sees it — these
cases are forward-looking and currently unreachable via the
dmix path, but harmless and required if alsa-lib ever offers
float formats to rate plugins (or if the plugin is reused in
a context where they are offered).
---
 src/tools/alsa/plugin/plugin.c | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/src/tools/alsa/plugin/plugin.c b/src/tools/alsa/plugin/plugin.c
index 400e83b..8c9b92d 100644
--- a/src/tools/alsa/plugin/plugin.c
+++ b/src/tools/alsa/plugin/plugin.c
@@ -207,7 +207,21 @@ static int data_setup( conv_data *data, snd_pcm_rate_side_info_t *info, int n_ch
 		break;
 
 	case SND_PCM_FORMAT_FLOAT_LE:
+		data->format_tag	= WAVE_FORMAT_IEEE_FLOAT;
+		data->sample_size	= 4;
+		data->dummy			= 0;
+		data->fill			= 0x00;
+		fmt_name			= "float_le";
+		break;
+
 	case SND_PCM_FORMAT_FLOAT64_LE:
+		data->format_tag	= WAVE_FORMAT_IEEE_FLOAT;
+		data->sample_size	= 8;
+		data->dummy			= 0;
+		data->fill			= 0x00;
+		fmt_name			= "float64_le";
+		break;
+
 	default:
 		fmt_name			= "unsupported";
 		break;
-- 
2.39.5
$ cat PATCHES_fftrate/0002*
From 3f4f64d105ca0ed76270051cea5688e5a9369581 Mon Sep 17 00:00:00 2001
From: igorzwx <igorzwx@devuan.cargo-cult.org>
Date: Wed, 21 Oct 2026 17:28:38 +0200
Subject: [PATCH 2/3] plugin: advertise float input formats and harden
 period-alignment math

- Declare SND_PCM_FORMAT_FLOAT_LE/Float64_LE support via
  pcm_rate_formats/get_supported_formats in pcm_rate_ops, guarded
  by SND_PCM_RATE_PLUGIN_VERSION >= 0x010003 so builds against
  older pcm_rate.h still work.
- Handle version 0x010001/0x010002 ops structs in
  SND_PCM_RATE_PLUGIN_ENTRY via braced arrcpy_bytes branches.
- Compute period-alignment products and shifted rates in 64-bit
  arithmetic (uint64_t) to avoid int32 overflow at high input
  rates and large period sizes (e.g. 2822400 Hz, period 677376).
- Log the resulting period ratio in pcm_rate_init.
---
 src/tools/alsa/plugin/plugin.c | 67 ++++++++++++++++++++++++++++------
 1 file changed, 55 insertions(+), 12 deletions(-)

diff --git a/src/tools/alsa/plugin/plugin.c b/src/tools/alsa/plugin/plugin.c
index 8c9b92d..8a0a519 100644
--- a/src/tools/alsa/plugin/plugin.c
+++ b/src/tools/alsa/plugin/plugin.c
@@ -1,5 +1,6 @@
 #include <stdio.h>
 #include <stdarg.h>
+#include <stddef.h>   /* offsetof */
 
 #include <alsa/asoundlib.h>
 #include <alsa/pcm_rate.h>
@@ -417,15 +418,22 @@ static int pcm_rate_init( void *obj, snd_pcm_rate_info_t *info )
 
 		//--------------------------------------------------------------------------------------------
 
-		if( ( in_rate * info->out.period_size ) != ( out_rate * info->in.period_size ) )
 		{
-			double l_in  = log2( (double)in_rate  / (double)info->in.period_size );
-			double l_out = log2( (double)out_rate / (double)info->out.period_size );
-			int   fpp   = (int)( 0.5 * ( l_in + l_out ) );
+			uint64_t prod_in  = (uint64_t)in_rate  * info->out.period_size;
+			uint64_t prod_out = (uint64_t)out_rate * info->in.period_size;
 
-			fpp = MAX( fpp, 0 );
-			in_rate	 = info->in.period_size  << fpp;
-			out_rate = info->out.period_size << fpp;
+			fprintf( OUTF, "Period ratio: %.6f\n", (double)prod_in / (double)prod_out );
+
+			if( prod_in != prod_out )
+				{
+					double l_in  = log2( (double)in_rate  / (double)info->in.period_size );
+					double l_out = log2( (double)out_rate / (double)info->out.period_size );
+					int   fpp   = (int)( 0.5 * ( l_in + l_out ) );
+
+					fpp = MAX( fpp, 0 );
+						in_rate	 = (int)( (uint64_t)info->in.period_size  << fpp );
+						out_rate = (int)( (uint64_t)info->out.period_size << fpp );
+				}
 		}
 
 		//--------------------------------------------------------------------------------------------
@@ -783,6 +791,30 @@ static void pcm_rate_dump( void *obj, snd_output_t *out )
 }
 #endif
 
+#if SND_PCM_RATE_PLUGIN_VERSION >= 0x010003
+static int pcm_rate_formats
+(
+	void			*obj,
+	uint64_t		*in_formats,
+	uint64_t		*out_formats,
+	unsigned int	*flags
+)
+{
+	dbg_printf( "[pcm_rate_formats]\n" );
+
+	*in_formats = *out_formats =
+		  ( 1ULL << SND_PCM_FORMAT_U8 )
+		| ( 1ULL << SND_PCM_FORMAT_S16_LE )
+		| ( 1ULL << SND_PCM_FORMAT_S24_3LE )
+		| ( 1ULL << SND_PCM_FORMAT_S24_LE )
+		| ( 1ULL << SND_PCM_FORMAT_S32_LE )
+		| ( 1ULL << SND_PCM_FORMAT_FLOAT_LE )
+		| ( 1ULL << SND_PCM_FORMAT_FLOAT64_LE );
+
+	*flags = 0;
+	return 0;
+}
+#endif
 
 static const snd_pcm_rate_ops_t pcm_rate_ops =
 {
@@ -801,6 +833,10 @@ static const snd_pcm_rate_ops_t pcm_rate_ops =
 	.get_supported_rates	= pcm_rate_rates,
 	.dump					= pcm_rate_dump,
 #endif
+
+#if SND_PCM_RATE_PLUGIN_VERSION >= 0x010003
+	.get_supported_formats	= pcm_rate_formats,
+#endif
 };

@@ -830,11 +866,18 @@ int SND_PCM_RATE_PLUGIN_ENTRY( fftrate )
 	arrzero_unit( inst );
 	*objp = inst;
 
-#if SND_PCM_RATE_PLUGIN_VERSION >= 0x010002
-	if( version == 0x010001 )
-		arrcpy_bytes( ops, &pcm_rate_ops, sizeof(snd_pcm_rate_old_ops_t) )
-	else
-#endif
+#if SND_PCM_RATE_PLUGIN_VERSION >= 0x010002  
+	if( version == 0x010001 )  
+	{  
+		arrcpy_bytes( ops, &pcm_rate_ops, sizeof(snd_pcm_rate_old_ops_t) );  
+	}  
+	else  
+	if( version == 0x010002 )  
+	{  
+		arrcpy_bytes( ops, &pcm_rate_ops, sizeof(snd_pcm_rate_v2_ops_t) );  
+	}  
+	else  
+#endif  
 		*ops = pcm_rate_ops;
 
 	return 0;
-- 
2.39.5
$ cat PATCHES_fftrate/0003*
From 120e746adc3f5e5ea0ea43fa110cbba6427e21c3 Mon Sep 17 00:00:00 2001
From: igorzwx <igorzwx@devuan.cargo-cult.org>
Date: Thu, 22 Oct 2026 19:26:48 +0200
Subject: [PATCH 3/3] pcm_conv: enlarge static I/O buffers for high-ratio
 conversions

BUFFER_SIZE (65636 bytes) was too small for extreme upsampling
ratios (e.g. 352800 -> 2822400/5644800 Hz DSD-equivalent rates
with -T fft), whose Vorbis window size exceeds the buffer,
causing the conversion loop to exit on its first iteration and
silently write a 44-byte (header-only) WAV while still printing
'Ok.'
---
 src/apps/pcm_conv/Main.cpp | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/src/apps/pcm_conv/Main.cpp b/src/apps/pcm_conv/Main.cpp
index d7fd865..5eef1bb 100644
--- a/src/apps/pcm_conv/Main.cpp
+++ b/src/apps/pcm_conv/Main.cpp
@@ -36,7 +36,9 @@
 // Definition
 //-----------------------------------------------------------------------------
 #define DEFAULT_OUTPUT		"convert.wav"
-#define BUFFER_SIZE			65636L
+#define BUFFER_SIZE			4194304L	/* 4 MiB: large enough for high-ratio
+						 * FFT windows (e.g. DSD512-rate simulation,
+						 * 352800 Hz -> 5644800 Hz, 64-bit float) */

 //=============================================================================
-- 
2.39.5

TEST

$ aplay -V s test*
Playing WAVE 'test_f64.wav' : Float 64 bit Little Endian, Rate 192000 Hz, Stereo
Input:  192000 Hz, 2 ch, 'float64_le' (0x10): dummy = 0, period = 7680
Output: 192000 Hz, 2 ch, 's32_le' (0xa): dummy = 0, period = 7680
Period ratio: 1.000000
Rates:  192000 --> 192000 (J: 0.00%, T: None, W: Planar)
Ok.

                   +###############48%|49%################+                  
$ aplay -V s *Float.wav
Playing WAVE '32bitFloat.wav' : Float 32 bit Little Endian, Rate 352800 Hz, Stereo
Input:  352800 Hz, 2 ch, 'float_le' (0xe): dummy = 0, period = 14112
Output: 192000 Hz, 2 ch, 's32_le' (0xa): dummy = 0, period = 7680
Period ratio: 1.000000
Rates:  352800 --> 192000 (J: 0.00%, T: FFT, W: Vorbis)
Ok.

                       +      #####36%|49%##########      +                  

Test file can be created with pcm_conv (fftrate)

pcm_conv 01*.wav test_f64.wav -b 64f -f 192000 -T fft -v 

Offline

Board footer