view yetilab/stream/audiofile.yeti @ 219:ff97765b1d1b matrix_opaque_immutable

More block -> vector
author Chris Cannam
date Sat, 11 May 2013 12:07:21 +0100
parents a7f4eb1cdd72
children 77c6a81c577f
line wrap: on
line source

module yetilab.stream.audiofile;

load yetilab.stream.streamtype;

import javax.sound.sampled:
     AudioSystem, AudioInputStream, AudioFormat, AudioFormat$Encoding,
     UnsupportedAudioFileException;

import java.io: File, IOException;

import java.nio: ByteBuffer, ByteOrder;

ch = load yetilab.stream.channels;
vec = load yetilab.block.vector;

decode8u bytes doubles n is ~byte[] -> ~double[] -> number -> () =
   (for [0..n-1] do i:
       doubles[i] := (bytes[i] / 128.0) - 1.0;
    done
   );

decode16s bytes doubles n is ~byte[] -> ~double[] -> number -> () =
   (bb = ByteBuffer#wrap(bytes, 0, n * 2);
    bb#order(ByteOrder#LITTLE_ENDIAN);
    for [0..n-1] do i:
       doubles[i] := bb#getShort(i*2) / 32768.0;
    done
   );

decode32f bytes doubles n is ~byte[] -> ~double[] -> number -> () =
   (bb = ByteBuffer#wrap(bytes, 0, n * 4);
    bb#order(ByteOrder#LITTLE_ENDIAN);
    for [0..n-1] do i:
       doubles[i] := bb#getFloat(i*4);
    done
   );

decodeFail () = 
    throw new UnsupportedAudioFileException("File format not supported. Supported formats are 8-bit unsigned PCM, 16-bit signed little-endian PCM, or IEEE float");

decode { format is ~AudioFormat } bytes doubles n = 
   (if format#isBigEndian() then
        decodeFail()
    else
        enc = format#getEncoding();
        bits = format#getSampleSizeInBits();
        if bits == 32 then
            decode32f bytes doubles n;
        elif bits == 16 and enc == AudioFormat$Encoding#PCM_SIGNED then
            decode16s bytes doubles n;
        elif bits == 8 and enc == AudioFormat$Encoding#PCM_UNSIGNED then
            decode8u bytes doubles n;
        else
            decodeFail();
        fi
    fi);

readInterleaved' { format is ~AudioFormat, aistream is ~AudioInputStream } nframes =
   (channels = format#getChannels();
    bytesPerSample = format#getSampleSizeInBits() / 8;
    bytes = new byte[nframes * channels * bytesPerSample];
    bytesRead = aistream#read(bytes);
    if bytesRead <= 0 then vec.zeros 0;
    else
        n = int(bytesRead / bytesPerSample);
        doubles = new double[n];
        decode { format } bytes doubles n;
        vec.vector doubles;
    fi;
   );

read' { format is ~AudioFormat, aistream is ~AudioInputStream } n =
   (b = readInterleaved' { format, aistream } n;
    channels = format#getChannels();
    ch.deinterleaved channels b;
   );

readMono' { format is ~AudioFormat, aistream is ~AudioInputStream } n =
   (b = readInterleaved' { format, aistream } n;
    channels = format#getChannels();
    ch.deinterleaved 1 (ch.mixedDownFromInterleaved channels b);
   );

// Note, all this assumes aistream is non-blocking (i.e. available()
// is to the end of file). Our stream interface does support
// indefinite and infinite streams, but audiofile doesn't yet.

available' { format is ~AudioFormat, aistream is ~AudioInputStream } =
    aistream#available() / ((format#getSampleSizeInBits() / 8) * format#getChannels());

close' { aistream is ~AudioInputStream } =
    aistream#close();

openWithReader reader ch name is 'a -> number -> string -> stream =
   (f = new File(name);
    aistream = AudioSystem#getAudioInputStream(f);
    format = aistream#getFormat();
    len = available' { format, aistream }; // at start of stream
    {
        get position () = len - available' { aistream, format },
        get channels () = if ch == 0 then format#getChannels() else ch fi,
        get sampleRate () = format#getSampleRate(),
        get available () = Known (available' { aistream, format }),
        get finished? () = not (aistream#available() > 0),
        read = reader { aistream, format },
        close () = close' { aistream },
    });

open = openWithReader read' 0;
openMono = openWithReader readMono' 1;

{
    open,
    openMono
} as {
    open is string -> stream,
    openMono is string -> stream
}