Commit graph

39794 commits

Author SHA1 Message Date
Michael Giacomelli
b7170e03c4 atrac3: apply the whole mdct window
Only the first and last 128 points of the 512 point window were
applied, and the 256 between were taken to be one. They are not:
the window rises to 1.207 there. Every block was therefore up to
1.6 dB low over half its length, which left the output 0.7 dB low
on average and an error about 23 dB below the signal at all
frequencies.

Add the missing half of the table and apply it.

Checked with perfsim (Sansa e200v1 build) against ffmpeg's decode,
with the two previous fixes applied before and after:

                               level     SNR
  five plain stereo RM files   -0.7 dB   23 dB  ->  0.0 dB   54-57 dB
  joint stereo RM file         -0.7 dB   23 dB  ->  0.0 dB   37-45 dB
  joint stereo OMA file        -0.7 dB   23 dB  ->  0.0 dB   45 dB

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 21:56:47 -04:00
Michael Giacomelli
1bae7e0e0a atrac3: reverse the whole of an odd band's spectrum
The spectra of the odd QMF bands are stored reversed. 8722c6f2bb
moved that reversal out of IMLT() and into the decoding of the
coefficients, but did it within each scale factor band, one place
off, and not at all for tonal components. The odd bands, which is
5.5 to 11 kHz and above 16.5 kHz at 44.1 kHz, have been decoded
wrongly since: against ffmpeg's decode that range was uncorrelated.

Reverse the whole band in IMLT() again, as ffmpeg does. The old
commit measured the saving it is giving up as 0.11 MHz on PP5024.

Checked with perfsim (Sansa e200v1 build) against ffmpeg's decode
of an OMA file and six RM files: the coherence of the 5.7 to 10.7
kHz range goes from under 0.01 to 0.99, the same as other bands.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 21:56:47 -04:00
Michael Giacomelli
f1d7ae3bbf atrac3: give the decoder its frame size for RM files
Since 2e314093c8 the ATRAC3 decoder takes its frame size from
id3->bytesperframe, which the RM metadata parser does not set. With
a frame size of zero, a file in plain stereo mode had its second
channel decoded from the first channel's data: both outputs were
the left channel.

Set it from the RM sub packet size before the decoder is opened.

Checked with perfsim (Sansa e200v1 build) on six ATRAC3 .rm files:
the two channels were identical sample for sample before, and each
now follows its own channel of ffmpeg's decode.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 21:56:47 -04:00
Michael Giacomelli
dcee3ce62f jpeg: decode 16-bit quantization tables (SOF1)
Both decoders rejected extended sequential JPEGs (SOF1), which are
baseline files in all but name when they have 8-bit samples, and
16-bit quantization tables, which libjpeg writes for very low
quality settings unless told to force baseline.

Accept SOF1 with 8-bit samples, and read 16-bit table entries. The
core loader keeps its tables in 16 bits and scales them for the IDCT,
so it rejects entries over 8191; libjpeg's largest at quality 1 is
4950. Files using more than two Huffman tables are still rejected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: I705a61402b4a0ab252995f8559b7ff2f68b05d72
2026-10-06 00:44:06 -04:00
Michael Giacomelli
2890d4a40d vorbis: don't buffer the comment packet
The comment packet is not used by the codec, but skipping it grew
the stream's packet buffer to the packet's full size. Heap use rose
with the size of the tags: a file with 300 KB of embedded album art
needed 617 KB of heap, and one with 940 KB of art could not be
opened even with a 992 KB codec buffer.

Skip the packet without storing it: forget the part already
buffered and let ogg_stream_pagein() drop the rest, as it does for
a packet whose start was lost.

Seeking to the start of a file ran the headers through the stream
again, which buffered the whole comment packet by the normal path.
Start the seek search at the first audio page instead, as libvorbis
does, and handle a target inside that page.

With both, heap use no longer depends on the tags. Large tags cost
at most about 90 KB over an untagged file, from the two Ogg buffers
growing to hold one full page each.

Checked with perfsim on the Sansa Clip+ model, not on a device,
with the heap limited to what a 512 KB codec buffer leaves:
files with up to 1.5 MB of art or 240 KB of text decode to the
same PCM hashes as the untagged audio, and 15 seeks per file,
including to 0, 30 and 150 ms, give the same positions and PCM
as the unmodified codec on ten files. Chained streams were not
tested.

Change-Id: Id27ebf98b436d813cfa3179951eee5bd7b2c0cd2
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 22:41:43 -04:00
Michael Giacomelli
34666474b8 wma: fix dropouts in files with LSP exponents
Where the LSP curve peaks, the value its -1/4 power is taken of is
far below one step of 16.16 and was rounded to zero. pow_m1_4() of
zero is enormous, the block's levels are taken relative to that
maximum, and so the whole block came out about 46 dB down. In
StutteringFile.wma (22 kHz mono, 20 kbps) this muted a block here
and there, which is heard as a stutter.

Pass the value to pow_m1_4() with all of its 38 fractional bits.

Checked with perfsim (Sansa e200v1 build) against ffmpeg's decode
of that file, in blocks of 46 ms: 11 of 841 were off by more than
1 dB, the worst by 10.6 dB; none are now, the worst by 0.4 dB.
Four other files with LSP exponents are unchanged or closer, and
files without them are byte-identical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 21:21:27 -04:00
Michael Giacomelli
d084219b9a vorbis: refuse streams with more than two channels
Tremor is built here with CHANNELS 2: synthesis produces no PCM
for a stream with more channels, so a 5.1 file "played" as nothing
at all. It was still set up in full first, which took 416 to 880 KB
of the codec heap for the 5.1 and 7.1 files tried, and wrote one
entry per channel into arrays sized for two.

Refuse such a stream when its identification header is read. The
codec then returns an error at once, with under 8 KB of heap used.

Checked with perfsim on the Sansa Clip+ model, not on a device:
six 5.1 and 7.1 files are refused, and 13 stereo and mono files
give the same PCM hashes as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 20:47:12 -04:00
Michael Giacomelli
9477e371a3 wma: fix overflow in the LSP exponent curve
Very low bitrate WMA files send their spectral envelope as LSP
coefficients. The curve computed from them squares two products
whose value can reach about a million, in 16.16 fixed point. Where
the envelope should be lowest the squares overflowed, and those
points came out near the curve's maximum instead: bands at the top
of the spectrum up to 17 dB too loud, and everything else a few dB
low, as levels are taken relative to the maximum.

Sum the squares in 64 bits, and let pow_m1_4() take a value above
the 16.16 range.

Checked with perfsim (Sansa e200v1 build). The curve is within
0.2 dB of ffmpeg's formula at all 57600 points of 80 blocks traced
from two files. Against ffmpeg's decode, as the mean difference in
band levels over the whole file, with the noise coding fix applied
before and after:

  StutteringFile (22 kHz mono, 20 kbps)    1.6 dB -> 0.3 dB
  shoujicomcn2117164 (same)                0.2 dB -> 0.1 dB
  terra8k (11 kHz mono, 8 kbps)            0.3 dB -> 0.0 dB

Only the five files that use LSP exponents change; the other 23
are byte-identical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 20:47:12 -04:00
Michael Giacomelli
11b12894ad wma: fix the level of noise coded high bands
Low bitrate WMA files code their upper bands as noise at a given
power. Those bands came out 12 to 25 dB too quiet, which dulled
the treble. Two things were wrong.

The exponent pointer was moved too far in blocks whose exponents
have another resolution. This is ffmpeg's r20756 (f78501b264,
"Fix apparent 10l typos introduced in r8627"); r8627 was merged
here in 2f1da8d24a but the fix for it never was.

The fixed point gain of a band lost nearly all its precision: it
was zero for three bands in four in the file traced, and the sum
for a band's power overflowed in a quarter of them. Compute the
gain in 64 bits, once a band. The factor for the noise below the
first coded coefficient (WMA v1 only) was 16 bits too large and is
corrected by the same reasoning; no file here exercises it.

Checked with perfsim (Sansa e200v1 build) against ffmpeg's decode,
as the mean difference in band levels over the whole file:

  01 - Jane Austen (44.1 kHz, 32 kbps)   5.5 dB -> 0.0 dB
  07.Devil.In.My.Mind (same)             6.2 dB -> 0.0 dB
  beyonthepain907z (same)                6.8 dB -> 0.0 dB
  moshimoashitaga (same)                 2.8 dB -> 0.0 dB

Four more noise coded files, already within 0.3 dB, now match too.
The output of the 15 files without noise coding is byte-identical.
Files that use LSP exponents still differ in their top bands; that
is a separate fault in the LSP curve.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 20:47:12 -04:00
Michael Giacomelli
fb2ee6c08f wma: decode every codec packet in an ASF payload
A payload normally holds one codec packet of blockalign bytes, but
some files put several in each. Only the first was decoded, so such
a file played one packet in every 8 or 15, as a few seconds of
broken sound, and then ended.

Step through the payload in blockalign sized packets.

Checked with perfsim (Sansa e200v1 build) against ffmpeg's decode:

  doors-test.wma (WMA v2, 11 kHz mono), 15 packets a payload:
    80896 of 1205760 samples before, all of them after, 86 dB SNR
  test.wma (WMA v1, 44.1 kHz stereo), 8 packets a payload:
    1294336 of 10346496 samples before, all after, 114 dB SNR

The output of 26 other WMA files is byte-identical before and after.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 20:47:12 -04:00
Michael Giacomelli
6ffbee954b wmapro: fix the level and accuracy of 16 bit streams
A WMA Professional stream of 16 bits per sample played about 48 dB
too quiet and with a noise floor near -70 dBFS.

ffmpeg scales the transform's output by the stream's sample size.
That was dropped when the decoder was converted to fixed point
(d884af2b99, 16284ae8ae), and the output is passed to the DSP as if
every stream had 24 bits. A stream of fewer bits has a lower
quantization step to match, so it came out low by the difference,
and it used the bottom of the integer quantization table, where the
factors have only a few significant bits.

Decode a 16 or 20 bit stream at the level of a 24 bit stream: use
that stream's quantization step, and scale each band's factor by
the ratio that is left. 24 bit streams are not affected.

Checked with perfsim (Sansa e200v1 build) against ffmpeg's decode
of a 16 bit, 192 kbps stereo file, the only such file to hand:

            level      SNR vs ffmpeg   noise
  before    -48 dB     37 dB           -70 dBFS
  after     correct    85 dB           -117 dBFS

A 24 bit file's output is byte-identical before and after. The
20 bit case follows the same rule but is untested.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 20:13:48 -04:00
neofright
4b8ea97fa9 Support ROCKBOX_DIR in HibyOS hosted bootloader
Currently if you want to compile a bootloader like so:

$ ../tools/configure --rbdir=/.rockbox.aigoerosq --target=aigoerosq --type=b

This won't work because the bootloader is hard coded to look for the rockbox Linux binary in /.rockbox/rockbox.erosq regardless of what is specified by '--rbdir'.

This commit adds support for ROCKBOX_DIR in the hosted HibyOS bootloaders.

After compiling, they can be installed with adb via (for example):

adb push build/bootloader.erosq /usr/bin/bootloader.erosq

Change-Id: Ic4ceb75107f608beacfd497998a11a5874e87ac1
2026-10-05 19:42:58 -04:00
Michael Giacomelli
4502948cd8 wmapro: add a two-channel path to the channel transform
inverse_channel_transform() ran its general N-channel matrix loop
for every sample of a stereo stream, where the matrix is exactly
+-1.0. That loop was a quarter to a third of the whole decode, and
GCC 9.5.0 compiles it worse than 4.9.4 did, which made the codec
6-9% slower on ARM7TDMI after the toolchain update.

Handle a group of two channels separately: add and subtract when
the matrix is +-1.0, and a plain four multiply loop otherwise. More
than two channels still use the general loop.

This reverses the regression from the GCC 9.5.0 update and goes
well past it. The loop the newer compiler handled badly is no longer
used for stereo, so the two compilers now give the same speed to
within 1%, about 25% faster than the codec was with GCC 4.9.4
(estimated with perfsim, e200v1, wmapro_141k: 25.81 MHz with 4.9.4
before this change, 19.5 MHz with either compiler after it).

Output is bit-identical: whole-file PCM hashes match before and
after for five stereo files at 55-271 kbps, built with GCC 9.5.0
and with 4.9.4, and also with the multiply path forced on.

Measured with test_codec, wmapro_141k.wma, MHz for real time:

  Sansa e200v1  27.99 -> 19.70
  Sansa Clip+   21.78 -> 15.80

Estimated with perfsim for the other files (e200v1 / Clip+):

  wmapro_55k   25.21 -> 17.06 / 20.02 -> 13.71
  wmapro_80k   26.17 -> 18.01 / 20.75 -> 14.44
  wmapro_173k  28.52 -> 20.29 / 22.55 -> 16.25
  wmapro_271k  30.85 -> 22.52 / 24.34 -> 18.04

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 19:29:19 -04:00
Marcin Bukat
361913fdd2 YP-CP3: make the WM8750 the I2S master
The WM8750 frames its ADC on ADCLRC and its DAC on DACLRC. As the I2S
slave both are inputs, and the rk27xx has a single LRCK, which on the
YP-CP3 evidently does not reach ADCLRC: with the rk27xx as the master,
the recording DMA got exact zeros on most visits to the recording
screen and real samples only by chance, sometimes changing partway
through a visit. That stays so with the codec PLL no longer glitching
the I2S clocks (previous commit); with the codec as the master it does
not happen.

The codec now drives BCLK and both LRCKs itself, and the rk27xx I2S
transmitter and receiver are slaves. The codec's MCLK still comes from
the rk27xx codec PLL at 256 fs, so sample rates stay exact. The
original firmware runs the codec as master too, but off a fixed 12 MHz
in USB mode.

RK27XX_I2S_MCLK says the rk27xx makes the codec's MCLK, apart from
CODEC_SLAVE, which also makes it the I2S master. The YP-CP3 drops
CODEC_SLAVE for RK27XX_I2S_MCLK, and the WM8750 driver sets its master
bit as it does for any codec that is not a slave.

Tested on a YP-CP3: playback, FM radio, the recording screen's peak
meter on every visit, recording from the microphone and from FM.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: I0dc93d3b2c45fddf61649702a931d11806e78cbf
2026-10-05 22:35:12 +02:00
Marcin Bukat
ce968e7da1 rk27xx: don't reprogram the codec PLL for an unchanged rate
Every start of recording applies the sample rate again, mostly
unchanged, and set_codec_freq() reprogrammed the codec PLL each time.
That glitches MCLK and every I2S clock made of it, right before the
recording receiver is reset and starts on them. Whether it then kept
its framing came down to code timing: on the Samsung YP-CP3 the
recording screen's peak meter showed one channel empty and the other
saturated on some visits, and builds with debug code added never did.

The PLL is now left alone when it already runs the rate asked for. On a
real change the lock bit, which may still show the old lock at first,
is polled only after the 0.3 ms the datasheet gives for locking, with a
timeout of at least a full tick, and the clocks get 1 ms more to settle
before anything starts on them.

Tested on a YP-CP3, together with the codec as I2S master: the peak
meter on every one of many visits to the recording screen, with the
microphone and FM, and at 22 kHz.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: I2267e17c5bc7d8044715e762dbb6b92f45af4dcc
2026-10-05 22:35:12 +02:00
Solomon Peachy
59f15ca630 cabbiev2: Don't generate certain entries for monochrome devices
Namely, 'backdrop' and 'filetype colours'

Change-Id: Ied0b87d9b43d010a37ebb1e33488463c6ad1ebcc
2026-10-05 14:11:34 -04:00
Solomon Peachy
279e8524a6 makezip: Don't install classic_statusbar.rsbs on targets without remotes
Also nuke attempting to install the nonexistent 'rockbox_none.sbs'

Change-Id: I22ed33c5d3d322b0509118005b1327aa88c61fcc
2026-10-05 14:10:57 -04:00
Michael Giacomelli
374f7c0334 flac: ARM assembly for the 64-bit LPC filter
Replaces flac_lpc_32_c, the wide predictor path every 24-bit stream
takes, and 16-bit streams encoded at high coefficient precision. The
coefficients are invariant for the whole call, but the C loop reloaded
all of them for every output sample and spilled its loop bound to the
stack on top of that. Orders 1-8 now keep every coefficient in a
register; orders 10 and 12, which is what -8 emits, get their own
unrolled loops instead of the generic chunked one. An ARMv5E kernel
using the packed 16-bit multiplies is included behind
FLAC_LPC32_NARROW_ASM, off by default: it needs bps <= 16, and only
about half the subframes of such a stream can use it.

Measured with test_codec on 24-bit/96kHz streams. At predictor order 12:
36.24 MHz on e200 (ARMv4) against 62.29 MHz before. The same file
improves from 37.65 MHz to 27.3 MHz on Clip+ (ARMv5).

Bit-exact against flac -d over four complete streams on both targets,
with and without the assembly, and the kernels are checked against an
int64_t reference across every order 1-32, qlevel 0-15 and coefficient
precision 1-15.

The rarely-executed orders stay in DRAM: the codec's IRAM window on
PP502x is nearly full and demoting them measured no cost, leaving 96
bytes free. FLAC_LPC32_NO_IRAM demotes the whole filter.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Change-Id: Icd8bd298973b9f5c26c103af8adf47b03489d69f
2026-10-05 12:19:09 -04:00
Michael Giacomelli
1896c2128b test_codec: keep the log if the plugin is left early
A file's length and first cluster are only written to its directory
entry when it is closed, and the log was closed at the end of
plugin_start() alone.  Leaving by USB or by power off goes through
exit() instead, so a whole run's log could be left as an empty file
with its data in clusters nothing pointed to.

Close the log from an atexit handler, close it when a run over a
folder ends, and close and reopen it after each track so that at
most one result is lost if the player dies.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: I613d0ad8c637d7f48a82eb09bc310c6fcc6d77f9
2026-10-05 11:20:17 -04:00
Michael Giacomelli
a6b7321d9f libdemac: reciprocal table for the pivot division on ARMv5+
Of the two divisions a sample left in the entropy decoder, the first
divides the range by the Rice pivot, which is small: below 1024 for
99.9% of the samples of a 16-bit test file and never as much as 4096.
Keep a table of reciprocals, filled in as divisors turn up, and
divide with a 32x32->64 multiply and one correction.  Larger divisors
go to the division routine as before.  The other division, by help,
has no such pattern.

This is for ARMv5 and later without a hardware divide.  ARMv4 has
its own divider with a reciprocal table already, and a long multiply
is slow there; its codec is unchanged.

The table is 16 KB of bss.  With r = (2^32 - 1) / n the estimate is
the quotient or one less for any 32-bit numerator, checked against
true division for every n below 4096.

MHz for real time in perfsim's model of the Clip+, -c1000, -c2000
and -c3000: 32.5, 48.4, 76.4 to 30.1, 46.0, 74.0.  A Clip+ measures
30.63 at -c1000, from 32.85, with the same checksum, 1008ffab.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: I555ff83d7e19919848c8fb7b8d12ecfa671a976b
2026-10-05 10:19:38 -04:00
Michael Giacomelli
aa7ebbe057 libdemac: entropy state in registers, one less divide
After the division routines the entropy decoder was the largest part
of Monkey's Audio at the fast levels, with two things to gain.

All of its state, the range coder's and the Rice parameters, was in
statics, so each use was a load and each update a store: about half
of the function's time on ARM7TDMI, where a load is 3 cycles.  Copy
it to locals for the length of a block and write it back after.  The
functions that take a pointer to it have to be inlined for that to
work, and on ARMv4 gcc left the per-sample one out of line, so force
them.

The symbol was found by dividing low by help and searching the count
table for the quotient.  counts[n] <= low / help is the same as
counts[n] * help <= low, so search with the multiply instead: the
first symbols are by far the likeliest.  That leaves two divisions a
sample from three.

MHz for real time in perfsim's models, -c1000, -c2000 and -c3000:
  Clip+ (ARMv5)  37.9, 53.8, 81.8  to  32.5, 48.4, 76.4
  e200 (ARMv4)   45.3, 68.6, 111.4 to  35.2, 58.5, 101.3
A Clip+ measures 32.85 at -c1000 (61.4 before this and the division
change), with test_codec's checksum, 1008ffab, the same as the old
code's.  The standalone decoder gives identical output for all four
levels tested.  The path for files older than 3.98 has the same
change and was not tested, for want of a file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: Idf9002a5b5f3a423f44f70443936ea5ac427cd12
2026-10-05 10:19:38 -04:00
Michael Giacomelli
5d8ce62b45 codecs: link libarm_support for its division routines
lib/arm_support/support-arm.S was written to replace libgcc's
division for ARM, and bff5a35c3c (FS#10943, 2010) added it to the
core, the plugin library and the codec library alike.  When
1501df045f (2013) replaced EXTRA_LIBS with explicit lists, plugins
kept it and codecs did not, and they have taken their division from
libgcc since.

With the gcc 4.4 toolchain that cost little: its libgcc had a
routine that used clz.  With gcc 9.5 libgcc has no soft-float ARMv5
variant, so ARMv5 targets get the ARMv4 routine, a shift and
subtract loop of about 130 cycles a division.

Monkey's Audio divides two or three times a sample in its range
decoder and, without codec IRAM, does it in C.  On a Clip+ it is
11% to 21% slower than 3.14 was, with over half of -c1000's decode
in __udivsi3.  MP2 is 4% to 7% slower.

Put libarm_support back, ahead of libgcc.  In perfsim's model of
the Clip+ a division falls to 44 cycles, Monkey's Audio by 38%, 30%
and 22% at -c1000, -c2000 and -c3000 (60.9 to 37.9 MHz at -c1000),
and MP2 by 3% to 7%; nothing else moves by more than 0.6%.  A Clip+
decodes -c1000 with the same checksum as before.

A division by zero in a codec goes to __div0 again, and so to the
firmware's handler, as it did with support-arm.S and with the old
libgcc (3.14's ape.codec calls it).  The gcc 9.5 libgcc returns
from its own stub instead.

Change-Id: I6a9a79ce8e85bca69870349a5c0823f392a578b6
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 07:46:37 -04:00
Marcin Bukat
4c78e98461 cabbiev2: 400x240 FM screen, sharing the WPS backdrop
CabbieV2 had an FM screen only for the 160x128 and 128x128 greyscale
targets. Add one for 400x240, laid out like its WPS: station art and
names where the album art and track info are, the frequency in the
progress bar, Scan/Preset, MHz and Stereo/Mono below it, and hold,
battery, volume and signal strength along the bottom.

Both screens share one backdrop, its header bar empty: each draws its
label, NOW PLAYING or FM RADIO, in Helvetica Bold rather than having it
painted into a backdrop of its own. The shuffle and repeat
placeholders, painted into the old WPS backdrop, are images drawn
while those are off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: I0331184479589cd4953bd78f4ead336f5d9b6fea
2026-10-05 10:03:20 +02:00
Marcin Bukat
b0ad97add8 wpsbuild: ship the fonts a skin loads with %Fl
A theme's own font is converted into the build; fonts its skins load
themselves with %Fl were not, and needed the font package - or the
skin fell back to another font. Convert those as well, the way the
images a skin uses are copied with it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: Ifb698f7417287954130be9b466fe3b2248c5c773
2026-10-05 10:03:20 +02:00
Marcin Bukat
cc362a8ecd buildzip: install wps/ subfolders with a dot in their name
make install skipped every wps/ subfolder whose name has a dot in it:
the test meant for "." and ".." matched a dot anywhere. A theme named
with one lost its images.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: Ic225960349e5d73ae4defa7524fa2cee5066e920
2026-10-05 10:03:20 +02:00
Marcin Bukat
13553bda06 radio: redraw the FM screen after the autoscan question
Entering the FM screen with no presets asks whether to scan for them.
The question and the scan clear the screen, after fms_fix_displays()
had shown the skin's backdrop, and nothing showed it again: the skin
redraws only its viewports, so the backdrop stayed missing everywhere
else - the header bar of CabbieV2's FM screen among it.

Leave the FM screen for the question and the scan and enter it again
after, as for the other screens shown from it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: I7caadf3ac2a03f2825176084d8969ae6e59af471
2026-10-05 10:03:20 +02:00
Michael Giacomelli
f86be7dc42 test_codec: fix stale results screen and scroll the log
The results were drawn right after backlight_on(), which only queues
a request to the backlight thread. lcd_update() does nothing while the
LCD is off, and the plugin then blocked waiting for a key without
updating again, so the display could keep showing the last progress
line. Refresh the display periodically while waiting for a key.

Also scroll the log up when the screen is full instead of wrapping
around to the top and overwriting old lines, which made the output
hard to read when testing a whole folder.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 22:46:38 -04:00
Solomon Peachy
ecdeb02dda FS#14005 - Use a 44.1KHz floor when guessing playback frequency
Regression introduced in f87ff3a9b, which made it possible for audio
playback to request a freq under 44.1KHz, instead of treating 44.1 as
a floor (and upsampling)

However, there is a report of 22KHz files playing back distorted on an
imx233 target.

IMO a 44KHz floor is reasonable, but this bug is a symptom of something
deeper.  Perhaps the mp3 codec isn't doing the right thing, or there's
an issue in the pcm mixer somewhere, or the imx233 codec doesn't properly
handle 22KHz?  Further investigation is warranted.

Change-Id: I751ce05f8605de7f90d3eb7b3c98873487df438b
2026-10-04 13:04:56 -04:00
Solomon Peachy
50d8c4a58e FS#14029 - Updated Vietnamese Translation (Chu Khánh Hạnh)
Change-Id: I6f5807d76989375b6318d02fd0e42f8beaa488fa
2026-10-04 13:01:55 -04:00
Michael Giacomelli
0ed3734e5e flac: cope with frames larger than one buffer request
The frame decoder reads from one flat buffer and cannot refill it, but
request_buffer() only guarantees 32KiB of contiguous data (the buffering
guard area). Frames that can be larger than that, such as high
resolution or poorly compressible streams (FLAC decoder testbench file
31), could be handed to the decoder truncated, which read past the end
of the data and lost sync.

When a request returns less than the largest frame the stream can
contain (STREAMINFO max framesize, or a bound from block size, channels
and bit depth) and it is not the end of the file, copy the frame into a
private static buffer and decode from that. Streams whose frames always
fit never touch the buffer and pay one comparison per frame. The buffer
is 64KiB, or sized for the 4608 sample blocks of memory limited targets,
and is left out entirely when MEMORYSIZE is 2MB or less.

Also fail with a codec error, instead of advancing past the data, if a
decoded frame consumed more bytes than were provided.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Change-Id: Ib4cf85ad513f8e096f73d13b4374d072e1745cb2
2026-10-03 18:17:56 -04:00
Michael Giacomelli
e430ecb607 imageviewer/jpegp: don't crash or hang on damaged files
On colour targets the image viewer hands every file its own decoder
rejects to jpegp, including damaged ones, but jpegp barely checks its
input. Corrupt and truncated files crashed or hung it:

- At the end of the file GETC() kept returning stale bytes, so marker
  searches and table reads never ended. Feed EOI markers (FF D9)
  instead, which ends every loop, and stop calling read() there. This
  state is reset in OPEN(): the overlay loader does not clear .bss.
- A file ending before any scan decoded as a blank image. Report it as
  corrupt instead.
- Out of range header values were used as array indexes: Huffman and
  conditioning table IDs, Huffman table sizes, sampling factors, scan
  component counts and spectral selection. Reject them, and frames of
  zero width or with no components.
- Invalid Huffman codes walked past the code length table, run lengths
  wrote past coefficient 63, and huge coefficients indexed past the
  IDCT clamp table. Bound all three.
- An odd DAC segment length never ended its loop.
- The coefficient buffer size could overflow an int.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: I49465f38d283e159274d2f381029280cea42a8f1
2026-10-03 08:01:44 -04:00
Solomon Peachy
bef221afd7 checkwps: Also validate 'filetype colours'
Change-Id: I6a3dd1b08afb1037e7315fbf820dada3d108d909
2026-10-02 17:51:31 -04:00
Solomon Peachy
90506b8808 checkwps: Numerous improvements to cfg validation
* Check iconset, remote iconset, and viewer iconset
 * don't crash (or fail) on empty strings
 * when component path is not absolute, search appropriate path
   (eg FONT_DIR, ICON_DIR, WPS_DIR, SBS_DIR)

Change-Id: I256d00d26d79b1d1f09038877cfaaa804123df72
2026-10-02 17:43:43 -04:00
Marcin Bukat
f61d379824 YP-CP3: simulator
- uisimulator/bitmaps/UI-samsungypcp3.bmp: the YP-CP3 from the front,
  614x324, the 400x240 screen at 40,37. Dithered to RGB565: the
  simulator converts its background to the 16-bit LCD format, which
  turned the case's gradients into bands.
- sim-ui-defines.h: its window and screen position
- buttonmap/samsung-ypcp3.c: the YP-R0's keyboard layout - the
  YP-CP3 shares its keypad - and click areas for the joystick, Back and
  Menu below it, and User (Rec) and Power on the top edge above them

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: I9146fd54400d85636a97509993b393bff8b1ab78
2026-10-02 16:42:34 +02:00
Solomon Peachy
9910b2aebf Revert "FS#14019: Detect and reject outdated binary language files (WIP)"
This reverts commit c0ae6c7cd1.
2026-10-02 10:18:29 -04:00
Solomon Peachy
025284d95a checkwps: '-' is also valid for backdrops
Change-Id: I67c2d871f053afca277c8fc4ad671f9689f5c647
2026-10-02 09:59:46 -04:00
Solomon Peachy
c0ae6c7cd1 FS#14019: Detect and reject outdated binary language files (WIP)
Accomplish this by checksumming the english language input
and (1) including that in binary files and (2) checking the
value matches what was compiled into the firmware image

Not sure if this is the best approach but it works.

Change-Id: I8f79ad1b9d1cdf69e6a085d7b3dd1b5e078af04b
2026-10-02 09:14:52 -04:00
Solomon Peachy
265cf7ac92 FS#14026 - Spanish Translation Update (Jordan Fajardo)
Change-Id: Idccd1d1a48c1c6f28d77782cc73e36b6868faff3
2026-10-02 08:28:55 -04:00
Michael Giacomelli
9936e65e6d imageviewer/jpegp: decode RGB images
jpegp converted every image from YCbCr, so RGB JPEGs showed scrambled
colours. That affects progressive RGB files, and now also baseline RGB
files the jpeg decoder rejects and hands on to jpegp, such as RGB with
the R component sampled 2x2.

Record the JFIF and Adobe APP14 markers, decide the colour space with
the same rule as the other decoders, and skip the YUV conversion for
RGB.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: If023eeb612b7f8a891d21fbe0070ab43c5a08f17
2026-10-02 08:24:03 -04:00
Michael Giacomelli
142a6fbb39 jpeg: decode RGB images
Both decoders treated every 3-component image as YCbCr, so RGB JPEGs
(as written by cjpeg -rgb, and by some Adobe software) came out with
wrong colours.

Decide the colour space as libjpeg does: a JFIF marker means YCbCr;
otherwise the transform flag of an Adobe APP14 marker decides (0 is
RGB); otherwise component IDs 'R', 'G', 'B' mean RGB.

Core loader: on colour targets R, G and B are stored in place in the
row buffer and the YUV conversion is skipped. Greyscale builds now
also decode G and B for RGB and combine them into luma per block,
which needs every component to be one block per MCU; other RGB
layouts are rejected there.

Plugin: RGB needs one block per MCU for every component, otherwise it
is rejected (colour targets fall back to jpegp). Colour builds convert
the R, G and B planes to YCbCr in place after decoding, so display and
greyscale view modes are unchanged; greyscale builds combine R, G and
B into luma per block as the core does.

Code size on the e200: core loader +351 bytes, plugin decoder +603
bytes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: Ib24a0b7690ca4c00b3ac01b2f511309efeac5975
2026-10-02 08:23:46 -04:00
Solomon Peachy
5dc5c4a06c checkwps: Fix red on monchrome devices
Change-Id: Ib9064fea51eaf9ce4a2095c1f1d599fc193d2a6d
2026-10-02 08:04:24 -04:00
Solomon Peachy
152c348537 FS#14025 Checkwps now validates theme cfg files
This allows fonts, backdrops, and wps/fms/sbs to be checked.

Note that while settings _names_ are validated, the _values_
can not always be checked.  Detectable settings errors
are flagged, but are not considered fatal.

Change-Id: I2a2b7ad94462e983345a1e692eccd6bd57e90eb9
2026-10-02 07:50:19 -04:00
Marcin Bukat
6350d3f7a0 YP-CP3: build plugins
The YP-CP3 shares the YP-R0's keypad and so its plugin keymaps; with
the 400x240 screen handled every plugin builds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: I516ab057c081e710cbc1cf6eca2b24ee87231244
2026-10-02 10:30:58 +02:00
Marcin Bukat
02f54fc3a8 plugins: 400x240 landscape screens
Seven plugins have no layout or bitmaps for a 400x240 landscape
screen - the Samsung YP-CP3's, which no target building plugins has had
in that orientation. Give each the 320x240 one: the same height, and
centred in the 80 pixels more width wherever a 320x240 background has
to line up with it.

- bubbles, invadrox, rockblox: the 320x240 layout and background,
  centred; the margins are cleared
- sudoku, jewels: the 320x240 bitmaps; their layouts already centre
  themselves or use the width
- superdom: the 320x240 box size and board items - boxes as wide as
  the screen allows make the board taller than it
- wormlet: the sizes of 320x240

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: I223154da5da2250ba0f6068106ba9bec242a29ee
2026-10-02 10:30:58 +02:00
Marcin Bukat
d94e41bade invadrox: bracket SCORENUM_Y
Five layouts define SCORENUM_Y as SCORE_Y + (...) unbracketed, so the
playfield update after each frame, PLAYFIELD_Y + 1 - SCORENUM_Y -
FONT_HEIGHT high, added that part rather than subtracting it: on a
240-line screen it ran 25 lines past the bottom. Most LCD drivers clip
it; the rk27xx one did not, and nothing in the playfield moved.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: Icecf7780d81595db9d6e42125fc421cc1cb40339
2026-10-02 10:30:58 +02:00
Marcin Bukat
69af17695d rk27xx: keep the LCD bus timing at the boosted clock
The LCD controller's CSn/WEn/RDn strobes count bus clocks, set once for
1-4-1 clocks: a 120 ns write cycle at the 50 MHz bus clock, but 60 ns
when the CPU is boosted and the bus runs at 100 MHz. That is too fast
for the Samsung YP-CP3's panel: with the CPU boosted it showed stray
pixels, and partial updates left tearing behind moving things - the
boot logo too, as the firmware boosts before lcd_init().

Double the clocks while boosted, 2-8-2, which keeps the write cycle at
120 ns: set_cpu_frequency() switches them before raising the clock and
after lowering it, and lcd init picks them for the clock it runs at -
the bootloader stays at crt0's CPUFREQ_MAX.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: I482073c16af39c6b1856a0e18d19a747c78975f0
2026-10-02 10:30:58 +02:00
Marcin Bukat
3fa27091f3 rk27xx: wait for the LCD DMA transfer to end
lcd_update_rect() waited on the channel's CTL_L LLP_DST_EN bit, which
the last descriptor has clear: it clears when the last block is
loaded, not when it is done. The update returned with that line still
being read, and the next one reprogrammed the window and the channel
under it.

Wait for the channel to disable itself, which it does after its last
block.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: Icf9314fbe40b8ea53a7f5f24ea11513e87a54cf6
2026-10-02 10:30:58 +02:00
Marcin Bukat
5c5dac2dbc rk27xx: clip LCD updates to the screen
lcd_update_rect() took the rect as given. One reaching past the screen
set a GRAM window off the panel, so the update showed nothing, and
built one DMA descriptor per line into scr_llp[LCD_HEIGHT] - past its
end for a rect taller than what is left of the screen. What follows
scr_llp in memory is the PCM driver's locks and then all_queues, the
kernel's queue list: a later broadcast posted to garbage.

invadrox asks for such a rect every frame. On a Samsung YP-CP3 nothing
of its playfield moved - aliens, bombs, the ship - and powering off
afterwards took a data abort in queue_post() from interrupt context.

Clip the rect to the screen, as other targets do, and do nothing if
nothing is left. Every rk27xx screen is a multiple of 4 pixels each
way, so aligning the clipped rect to 4 cannot take it past the edge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: I1f8bf865251859cd223e208a807c9a9da76a0ba0
2026-10-02 10:30:58 +02:00
Michael Giacomelli
1488b30c62 imageviewer/jpeg: reject damaged files safely
Damaged and truncated JPEGs could make the image viewer read or write
outside its buffers:

- process_markers() trusted segment lengths, and Huffman table symbol
  counts, so a segment running past the end of the file was parsed
  from whatever memory followed it. Check that each marker segment,
  and each Huffman table in it, lies within the file.
- A file without a complete SOS header was decoded from a NULL entropy
  data pointer, as load_image() checked only for DQT and SOF. Require
  SOS as well.
- img_mem() computed the image size in an int, which overflows for a
  large image (a 65535x65535 file came out as 0), so the decode wrote
  far past the buffer. Compute it in 64 bits and saturate.

Found with the jpeg-conformance files of the imazen codec-corpus,
which include truncated files and files from fuzzing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: I1e2a9a4346fd1c0e34b0813267c6317dbf704bc7
2026-10-01 19:32:57 -04:00
Marcin Bukat
5249466cc4 rk27xx: leave the NAND program running until the next access
Every program waited out its tPROG, about 0.8 ms on the Samsung
YP-CP3, before returning. Over USB mass storage that wait comes before
the status of each write command goes back to the host, and so before
the host sends the next one: nothing else runs during it.

Return once the program is started, WP# still lifted, and finish it -
wait, check the status, restore WP# - at the next chip access, or at
flash_sync(), which ftl_sync() calls. A program's result then arrives
with the next flash call; no caller checks flash_program()'s. A copy
still reports its own programs, the last one included, as the FTL
moves the data elsewhere when one fails.

On the YP-CP3 the NAND wrote at 3.64 MB/s; now 4.37 MB/s, against
4.35 MB/s in the original firmware, every read verified, also after a
power cycle.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: I8de01f8be052e5d3ff06f550c614865fb1946f05
2026-10-01 23:47:51 +02:00