Commit graph

39809 commits

Author SHA1 Message Date
Marcin Bukat
1669e49cd7 rk27xx: debug menu switch to show NAND SYS over USB
The NAND's SYS volume holds the bootloader's firmware and is hidden
unless a build defines HAVE_RK27XX_NAND_SYS. Getting at it, e.g. to
replace BASE.RKW, needed a special build.

Add "NAND SYS on next USB" to the debug menu. While it is set, the
next USB connection shows SYS through the USER drive, as "NAND SYS",
and the switch clears when that connection ends. It is decided once
per connection, so setting it while connected waits for the next
one, and it is held in RAM only. Writes still need FTL_ALLOW_WRITE.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: Idda3f1c5e00bea511618021397a6cb3bb0be7361
2026-10-07 18:33:01 +02:00
Marcin Bukat
406a28cc6a rk27xx: boot the OF when it asked for a reboot
Before the original firmware resets itself, e.g. at the end of a
firmware update, it stores a boot mode in an undocumented GPIO1
register that keeps its value over the reset. The NAND bootloader
reads it to decide whether to boot at all, then starts our
bootloader, which chose Rockbox or the OF from the buttons alone, so
an OF that restarted itself came back up as Rockbox.

Rockbox never writes the register and it reads 0 after power-on, so
boot the OF whenever it is set.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: I22a98cafad48f66302a8401c9df847a073675a58
2026-10-07 18:09:15 +02:00
Marcin Bukat
c3a62cee96 rk27xx: pass the NAND bootloader's handoff words to the OF
Before it jumps to an image, the rk27xx NAND bootloader writes three
words at the address in RKW header field 0x14: a magic, its version
and which copy of the image it loaded. The original firmware never
initialises them: it reports the version over USB and counts its own
reboots in the third word, and once the count reaches 5 it reboots
into a mode the NAND bootloader will not boot. When our bootloader
started the OF, nothing wrote them, so the OF ran with whatever was
left in DRAM.

Field 0x14 of our own images held a constant taken from some other
image. Point it at the last 12 bytes of DRAM, which nothing uses
before our bootloader runs. load_rkw() in the bootloader now reads
the words there before loading an image, and writes them at the
address in the loaded image's header when that lies between the
image and the bootloader. Started over USB, it passes version 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: Ife1d63e361a780c28a9f0626d110552b98bb6b7f
2026-10-07 18:09:15 +02:00
Marcin Bukat
e61aa0cd37 usb_storage: allocate the buffer at the storage handover
The host configures the device before Rockbox has handed the storage
over, and mass storage allocated its buffer right then. On the
recording screen the recording buffer still holds all free memory at
that point and cannot shrink, so plugging in USB there panicked with
"usb_storage_init_connection(): OOM". Recording closes, and frees its
buffer, only on SYS_USB_CONNECTED, which the handover broadcasts.
Playback does not hit this: its buffer gives memory up on request.

The buffer is only needed to run commands, and commands already wait
for the handover. So it is allocated, and the endpoint primed for the
first command, in the notify event that both handover paths in usb.c
send, or at once when the storage is already handed over. Until then
the host's first command waits at the endpoint, NAKed. GET_MAX_LUN,
which comes before the handover, is answered from the core's control
buffer instead of the transfer buffer. Targets with static USB buffers
are unchanged.

Tested on a Samsung YP-CP3: USB plugged in on the recording screen,
at boot and from the main menu, mounts, and files copied both ways
keep their checksums.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: Icadc24fd52a07d03125234e492dab0ede9e339c9
2026-10-07 09:30:49 -04:00
Marcin Bukat
136e6b9953 recording: stop monitoring the input before USB mode
Plugging in USB on the recording screen closes recording, but the input
stays selected: the screen switches it back to playback only when it
ends, which is after USB mode, at the unplug. On a Samsung YP-CP3,
whose codec passes the microphone or FM radio through to the
headphones while recording, the input could be heard all through USB
mode. Switch to playback before entering it.

Tested on a YP-CP3.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: I194d5e120cc39685aa43cea4741e826a86ed2c65
2026-10-07 07:56:53 -04:00
Marcin Bukat
9ce3fca4cb rk27xx: no whole-cache invalidate at run time
commit_discard_idcache() invalidated both cache ways at run time, at
every codec and plugin load and every USB connect. With the cache on,
the CPU crashed in the poll loop when that loop started a cache line of
its own; with the cache off around it, since 0e1952a4d1, code fetched
again afterwards could come back wrong. On a Samsung YP-CP3 a build
with a few changes elsewhere took an undefined instruction exception
in the USB interrupt handler at every boot into USB mode, always at
the same instruction, wherever the linker put it. The same build with
only the invalidate at USB connect replaced by a nop booted and
worked.

The original firmware invalidates the ways only once, at power-on with
the cache off, as crt0.S does, and after that only single lines. None
is needed at run time: the cache is unified and write-through, so what
the CPU writes, code included, is in memory and in any cached copy, and
every DMA into memory - SD reads, USB receives, recording - discards
the lines of its own buffer first. NAND is read by the CPU. So
commit_discard_idcache(), and commit_discard_dcache() with it, now do
nothing.

Tested on a YP-CP3, with the build that crashed: it boots into USB
mode, copies files with matching checksums, plays several formats,
runs plugins (fft with playback, bubbles), and records.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Change-Id: I14301a0c05736e28e53a3365c838f4a4a73052b3
2026-10-07 08:20:44 +02:00
Solomon Peachy
9d3d2c6e67 checkwps: validate themes against builtin cabbiev2 assets
Themes for a given device often reference the assets of the default
cabbiev2 theme.  Record a list of what is bundled, and check anything
not included with the theme against that list.

If something is still missing, then that's an error.

This applies to fonts, wps files, backdrops, icons, and so forth.

Change-Id: I5ff7feaae892c8b9a81c2cb3035879672f3e6c29
2026-10-06 23:24:34 -04:00
Solomon Peachy
31fac4c400 configure: drop the 'bootloader only' from the ipodnano3g
This allows the themesite to validate this target

Change-Id: I77407098ec0c92c845e5e14f4d6bfcd3fb230ebb
2026-10-06 22:50:10 -04:00
Michael Giacomelli
4c08906670 test_codec: set the DSP's output samplerate
The DSP gets its output samplerate from the codec thread when a
track is played. test_codec did not set it, so its runs with the
DSP used the rate of the last track played, or the default if
there was none: after "Playback frequency" was changed, they
resampled to the old rate until something had been played.

Set it from the mixer for each file, as the codec thread does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 22:25:09 -04:00
Michael Giacomelli
e27fb9a9f0 test_codec: add checksum runs with the DSP
"Checksum with DSP" and "Checksum folder with DSP" give the CRC32
of the DSP's 16-bit output for a file or a folder, as "Checksum"
does for the codec's output. That lets the DSP of a device, with
its settings, be checked against a reference for several files in
one run; "Write WAV with DSP" writes one file, /test.wav.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 22:25:09 -04:00
Michael Giacomelli
fc1d445d69 atrac3: keep the second window table out of small IRAM
b7170e03c4 added window_lookup_mid, 512 bytes, to IRAM on every
target. On the targets with 48KB of IRAM for a codec that left
the ATRAC3 codecs 48 bytes too large to link (iPod Color and the
other PP502x players with 96KB of IRAM).

Put the table in IRAM only on the targets with more, like the
decoder's other large tables. Nothing changes there.

Built for iPod Color, where the two codecs now have 476 bytes of
IRAM to spare, and for the iriver H120.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 22:18:33 -04:00
Michael Giacomelli
02d5520c5f atrac3: output samples with 28 fractional bits
The decoder gave the DSP samples with two fractional bits (sample
depth 17), far fewer than any other codec. The DSP's filters are
only as precise as the samples they are given: with an equalizer
band at a low frequency the output was noise, or held at full
scale.

Scale the last stage of the synthesis filter up by 11 bits and
set the sample depth to 28. On ARMv4 the bits are taken from the
64-bit sums of the dewindowing, which had them. Elsewhere the
input of that stage is scaled, in its matrixing step; that is not
done on ARMv4 because its multiplier takes longer for the larger
operands (51.97 against 48.85 MHz below). The Coldfire path uses
the C matrixing and has not been run.

Seven files decoded under perfsim for the Sansa e200v1 and Clip+:
shifted down again, the e200v1 output is within one step of the
old output, and the Clip+ output no further from the e200v1's
than before. Through a ten band equalizer with bands at 32 and
64 Hz (the filter fix of the DSP included), the noise of
atrac3_lp2_132.oma falls from -64 dB to -101 dB relative to the
signal.

Estimated with perfsim (not measured on a device),
atrac3_lp2_132.oma: e200v1 48.81 -> 48.85 MHz, Clip+ 26.91 ->
26.96 MHz.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 21:56:47 -04:00
Michael Giacomelli
3966b420a3 atrac3: use the QMF window's symmetry in the ARMv4 filter
The dewindowing loop is 72% of ATRAC3 decoding on ARM7TDMI. The
window is symmetric, so read only its first half: each pair of
coefficients serves two inputs from the front of the 48 and, with
the two swapped, two from the back. That halves the coefficient
loads and lets them be done four at a time.

The sums are kept in 64 bits, so the new order of the additions
does not change the result: the PCM output of seven files is
byte-identical.

Estimated with perfsim for the Sansa e200v1 (not measured on the
device), atrac3_lp2_132.oma: 53.74 -> 48.81 MHz.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 21:56:47 -04:00
Michael Giacomelli
b8861e3f09 atrac3: remove the rounding bias of the ARMv5E QMF filter
The ARMv5E dewindowing routine takes 16 of each product's bits,
rounded down, so each sum of 24 products came out about 12 units
low. Through the filter bank that put a dc offset of about 12 LSB
(of 16 bits) on the output and, from the high band, a tone at half
the sample rate; together they were four fifths of the decoder's
error on these targets.

Start the sums 12 up. Also correct a comment in both ARM files.

Checked with perfsim (Sansa Clip+ build) against ffmpeg's decode of
seven files: the dc offset goes from 11 to 12 LSB down to under
1 LSB, and the error from about -66 dBFS to -69 to -72 dBFS, the
same as the ARMv4 routine gives. Decoding is 0.7% slower (26.73 to
26.91 MHz for atrac3_lp2_132.oma). The ARMv4 output is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 21:56:47 -04:00
Michael Giacomelli
8667af5cd7 atrac3: fix gain compensation at adjacent gain points
applyVariableGain() applied the constant gain before a gain point
in a do-while loop, so at least eight samples of it, even when
there are none: when the point is at the start of the block or
follows straight after the previous one. Every later gain point in
the block was then eight samples late. This came in with the loop
unrolling of 51a8be1a0f.

Use a while loop, as ffmpeg does.

Checked with perfsim (Sansa e200v1 build) against ffmpeg's decode.
Over the whole of atrac3_lp2_132.oma the SNR goes from 45 to 51 dB
and the worst frame from 14 to 21 dB; the right channel of a joint
stereo RM file goes from 37 to 54 dB. What is left is a noise floor
near -72 dBFS in every frame, from the 2 fractional bits the
decoder works with.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 21:56:47 -04:00
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