penguin/loopdeck

A simple player that support A-B looping for FiiO M11S

scripts/GaplessTest.java

raw ยท 3227 bytes

package net.astropenguin.loopdeck;

import java.util.*;
import java.util.concurrent.*;

public final class GaplessTest {
    private static void expect(boolean value) { if (!value) throw new AssertionError(); }
    private static final class Source implements PcmPrebuffer.Reader {
        int position, calls; final int frames;
        Source(int frames) { this.frames=frames; }
        public int read(float[] output,int requested) {
            calls++; int n=Math.min(Math.min(requested,37),frames-position);
            for (int i=0;i<n;i++) { output[i*2]=position+i; output[i*2+1]=-(position+i); }
            position+=n; return n;
        }
    }
    private static void stream(int total,int prefix,long limit) throws Exception {
        Source source=new Source(total); PcmPrebuffer buffer=new PcmPrebuffer(source,prefix,limit);
        buffer.prepare(() -> false);
        int calls=source.calls, expected=0; float[] block=new float[202];
        Random random=new Random(1);
        while (true) {
            int n=buffer.read(block,1+random.nextInt(101));
            if (n==0) break;
            for (int i=0;i<n;i++) {
                expect(block[i*2]==expected && block[i*2+1]==-expected); expected++;
            }
            if (expected<Math.min(total,Math.min(prefix,limit))) expect(source.calls==calls);
        }
        expect(expected==Math.min(total,limit));
        expect(buffer.read(block,101)==0);
    }
    private static final class Resource implements AutoCloseable {
        int closed;
        public void close() { closed++; expect(closed==1); }
    }
    public static void main(String[] args) throws Exception {
        stream(12345,4410,Long.MAX_VALUE); // Prefix to live decoding, irregular reads.
        stream(12345,4410,5217); // An explicit CUE boundary must not leak samples.
        stream(113,4410,Long.MAX_VALUE); // Entire short track fits in the prefix.
        stream(12345,4410,173); // CUE track shorter than the prefix.
        stream(0,4410,Long.MAX_VALUE);
        Source source=new Source(12345);
        new PcmPrebuffer(source,4410,Long.MAX_VALUE).prepare(() -> true);
        expect(source.calls==0);
        PrefetchSlot<Resource> slot=new PrefetchSlot<>(); Resource result=new Resource();
        slot.offer(result); expect(slot.take()==result); slot.cancel(); expect(result.closed==0); result.close();
        slot=new PrefetchSlot<>(); result=new Resource(); slot.offer(result); slot.cancel(); slot.cancel(); expect(result.closed==1);
        slot=new PrefetchSlot<>(); slot.cancel(); result=new Resource(); slot.offer(result); expect(result.closed==1 && slot.take()==null);
        for (int i=0;i<200;i++) {
            final PrefetchSlot<Resource> race=new PrefetchSlot<>(); final Resource resource=new Resource();
            CountDownLatch start=new CountDownLatch(1);
            Thread producer=new Thread(() -> { try { start.await(); race.offer(resource); } catch (InterruptedException e) { throw new AssertionError(e); } });
            producer.start(); start.countDown(); race.cancel(); producer.join(); expect(resource.closed==1);
        }
        System.out.println("Gapless PCM continuity, boundaries, cancellation and ownership passed");
    }
}