(* :Title: Useful Mathematica functions for manipulating soundfiles *)

(* :Author: Craig Stuart Sapp   <craig@ccrma.stanford.edu> *)

(* :Summary:

   This package contains Mathematica functions of use for manipulating
   NeXT/Sun soundfile (and sooner or later .WAV files).

*)

(* :Name: SoundFile.m *)

(* :PackageVersion: 0.00 *)

(* :Version: Mathematica 3.0 *)

(* :Keywords: Sound Soundfile *)

(* :Warnings: none *)

(* :Date: 18 February 1997 *)

(*
 * Changing $Context to SoundFile` and $ContextPath to {SoundFile`, System`}:
 *)

Needs["Utilities`BinaryFiles`"]

BeginPackage["SoundFile`"]

(***** Usage Statements **************************************************)

NextSoundQ::usage = 
      "NextSoundQ[filename] returns True if file is a Next soundfile.";

SndChannels::usage = 
      "SndChannels[filename] returns the number of channels in the \
       given soundfile.";

SndComment::usage = 
      "SndComment[filename] returns the comment string from the given \
       soundfile.";

SndDataOffset::usage = 
      "SndDataOffset[filename] returns the byte offset in the file to the \
       raw sound data.";

SndDataSize::usage = 
      "SndDataOffset[filename] returns the size in bytes of the raw sound \
       data in the given file.";

SndDur::usage = 
      "SndDur[filename] returns the duration in seconds of the given \
       soundfile.";

SndFrame::usage = 
      "SndFrame[filename, index, length] gets length samples from soundfile \
       starting at index.";

SndHeader::usage = 
      "SndHeader[filename] returns the header information for the soundfile.";

SndSampleRate::usage = 
      "SndSampleRate[filename] returns the sampling rate of the given \
       soundfile.";

SndTime::usage = 
      "SndTime[filename] returns the duration of the soundfile in seconds.";


(***** Options ***********************************************************)

Options[SndHeader] := {Type->"", DataLocation->0, DataSize->0, 
   DataFormat->0, SampleRate->0, ChannelCount->0, 
   InfoString->0, SampleDepth->0 }

(***** Start of Function Definitions *************************************)

Begin["`Private`"]



(***** NextSoundQ *********************************************************)

NextSoundQ[file_] := Module[
   {
      stream = OpenRead[file],
      magicNumber
   },
   magicNumber = ReadList[stream, Character, 4];
   Close[stream];
   magicNumber === {".", "s", "n", "d"}
]



(***** SndChannels ********************************************************)

SndChannels[file_] := Module[
   {
      stream = OpenRead[file],
      magicNumber
   },
   magicNumber = StringJoin[ReadList[stream, Character, 4]];
   Close[stream];
   Which[
      magicNumber === ".snd", NextSndChannel[file],
      True,                   Print["Error: Unrecognized soundfile type."]
   ]
]
   

NextSndChannel[file_] := Module[
   {
      stream = OpenRead[file],
      magicNumber,
      channels
   },
   magicNumber = ReadList[stream, Character, 4];
   If[magicNumber != {".", "s", "n", "d"},
      Print["Error: Invalid soundfile type"];
      channels = 0;
   , 
      Skip[stream, Byte, 16];
      channels = ReadBinary[stream, Int32];
   ];
   Close[stream];
   channels
]
		


(***** SndComment *********************************************************)

SndComment[file_] := Module[
   {
      magicNumber,
      stream = OpenRead[file],
   },
   magicNumber = StringJoin[ReadList[stream, Character, 4]];
   Close[stream];
   Which[
      magicNumber === ".snd", NextSndComment[file],
      True,                   Print["Error: Unrecognized soundfile type."]
   ]
]

AppendTo[Attributes[SndComment], Listable];


NextSndComment[file_] := Module[
   {
      magicNumber,
      stream = OpenRead[file],
      headerByteSize,
      comment
   },
   magicNumber = ReadList[stream, Character, 4];
   If[magicNumber != {".", "s", "n", "d"},
      Print["Error: Invalid soundfile type"];
      comment = {};
   , 
      headerByteSize = ReadBinary[stream, Int32];
      Skip[stream, Byte, 16];
      comment = ReadList[stream, Character, headerByteSize-24];
      nullLocations = Select[MapIndexed[{#1, First[#2]}&, comment],
            (First[#] == FromCharacterCode[0])&];
      If[nullLocations != {}, 
         comment = Take[comment, First[nullLocations][[2]]-1];
      ];
   ];
   Close[stream];
   comment = StringJoin[comment];
   comment
]
		

SunSndComment[parameters___] := NextSndComment[parameters]



(***** SndDataOffset ******************************************************)

SndDataOffset[file_] := Module[
   {
      stream = OpenRead[file],
      magicNumber
   },
   magicNumber = StringJoin[ReadList[stream, Character, 4]];
   Close[stream];
   Which[
      magicNumber === ".snd", NextSndDataOffset[file],
      True,                   Print["Error: Unrecognized soundfile type."]
   ]
]

AppendTo[Attributes[SndDataOffset], Listable];
   

NextSndDataOffset[file_] := Module[
   {
      stream = OpenRead[file],
      magicNumber,
      channels
   },
   magicNumber = ReadList[stream, Character, 4];
   If[magicNumber != {".", "s", "n", "d"},
      Print["Error: Invalid soundfile type"];
      offset = -1;
   , 
      offset = ReadBinary[stream, Int32];
   ];
   Close[stream];
   offset
]
		


(***** SndDataSize ********************************************************)

SndDataSize[file_] := Module[
   {
      stream = OpenRead[file],
      magicNumber
   },
   magicNumber = StringJoin[ReadList[stream, Character, 4]];
   Close[stream];
   Which[
      magicNumber === ".snd", NextSndDataSize[file],
      True,                   Print["Error: Unrecognized soundfile type."]
   ]
]

AppendTo[Attributes[SndDataSize], Listable];
  
NextSndDataSize[file_] := Module[
   {
      stream = OpenRead[file],
      magicNumber,
      dataByteSize
   },
   magicNumber = ReadList[stream, Character, 4];
   If[magicNumber != {".", "s", "n", "d"},
      Print["Error: Invalid soundfile type"];
      dataByteSize = 0;
   ,
      Skip[stream, Byte, 4];
      dataByteSize = ReadBinary[stream, Int32];
   ];
   Close[stream];
   dataByteSize
]



(***** SndDur *************************************************************)


SndDur[filename_] := Module[{},
   N[SndDataSize[filename]/SndSampleRate[filename]/
   SndChannels[filename]/2] (* 2: assuming 16-bit soundfiles for now *)
]

AppendTo[Attributes[SndDur], Listable];



(***** SndFrame ***********************************************************)

SndFrame[file_, parameters___] := Module[
   {
      stream = OpenRead[file],
      magicNumber
   },
   magicNumber = StringJoin[ReadList[stream, Character, 4]];
   Close[stream];
   Which[
      magicNumber === ".snd", NextSndFrame[file, parameters],
      True,                   Print["Error: Unrecognized soundfile type."]
   ]
]


NextSndFrame[file_, index_, length_] := Module[
   {
      stream = OpenRead[file],
      channels, offset, output, i
   },
   
   If[ NextSoundQ[file], 
      header = SndHeader[file];
      channels = ChannelCount /. header;
      output = Table[0, {i,1,length*channels}];
      offset = DataLocation /. header;
      Skip[stream, Byte, offset + index];
      For[i=1, i<=length*channels, i++, 
         output[[i]] = ReadBinary[stream, SignedInt16];
      ]
   ];
   If[channels == 1, output,
      Transpose[Partition[output, channels]]
   ]
]



(***** SndHeader **********************************************************)

SndHeader[file_] := Module[
   {
      stream = OpenRead[file],
      magicNumber
   },
   magicNumber = StringJoin[ReadList[stream, Character, 4]];
   Close[stream];
   Which[
      magicNumber === ".snd", NextSndHeader[file],
      True,                   Print["Error: Unrecognized soundfile type."]
   ]
]
   

NextSndHeader[file_] := Module[
   {
      stream = OpenRead[file],
      magicNumber,
      header
   },
   magicNumber = ReadList[stream, Character, 4];
   If[magicNumber != {".", "s", "n", "d"},
      Print["Error: Invalid soundfile type"];
      {};
   , 
      header = {Type->"NeXT/Sun"};
      header = Join[header, {DataLocation->ReadBinary[stream, Int32]}];
      header = Join[header, {DataSize->ReadBinary[stream, Int32]}];
      header = Join[header, {DataFormat->ReadBinary[stream, Int32]}];
      header = Join[header, {SampleDepth->16}];
      header = Join[header, {SampleRate->ReadBinary[stream, Int32]}];
      header = Join[header, {ChannelCount->ReadBinary[stream, Int32]}];
      header = Join[header, {InfoString->SndComment[file]}];
   ];
   Close[stream];
   header
]
		

(***** SndSampleRate ******************************************************)


SndSampleRate[file_] := Module[
   {
      stream = OpenRead[file],
      magicNumber
   },
   magicNumber = StringJoin[ReadList[stream, Character, 4]];
   Close[stream];
   Which[
      magicNumber === ".snd", NextSndSampleRate[file],
      True,                   Print["Error: Unrecognized soundfile type."]
   ]
]

AppendTo[Attributes[SndSampleRate], Listable];


NextSndSampleRate[file_] := Module[
   {
      stream = OpenRead[file],
      magicNumber,
      sampleRate
   },
   magicNumber = ReadList[stream, Character, 4];
   If[magicNumber != {".", "s", "n", "d"},
      Print["Error: Invalid soundfile type"];
      sampleRate = 0;
   ,
      Skip[stream, Byte, 12];
      sampleRate = ReadBinary[stream, Int32]; 
   ];
   Close[stream];
   sampleRate
]





(***** ********************************************************************)





(***** Start of Private Definitions **************************************)

ReadBinary[parameters___] := Utilities`BinaryFiles`ReadBinary[parameters]
Int32 = Utilities`BinaryFiles`Int32
Int16 = Utilities`BinaryFiles`Int16
SignedInt16 = Utilities`BinaryFiles`SignedInt16



(***** End of Function Definitions ***************************************)

End[] (* SCMTheory`Private` *)


(* Proctect all of the defined functions (when package is finalized): *)

Protect[

]


EndPackage[]

(* :Examples:
   SndComment["/NextLibrary/Sounds/Cricket.snd"]
 *)



