SSD program example 1 - Pascal

Copyright 2007 by Thomas Becker, D-49080 Osnabrück.
All rights reserved.

Content

This example shows how the content of a complete TAK file is sequentially decoded and written to a new Wave file.

Important Notice

The example is intended primarily to illustrate the use of the relevant SDK functions. Accordingly the code is stripped down to a minimum. It includes neither proper dealing with errors nor appropriate resource protection. Furthermore, some help functions (writing to a Wave file, for example) are only placeholders without any implementation.

Help functions

Partially placeholder functions without implementation.

procedure Proto (const ALine : String);

Writes the text line ALine to the screen or a file.

procedure Error (const AWhat : String);

begin
  Proto (AWhat);
  HALT;  // Stop program execution.
end;

Shows the error message AWhat and stops program execution.

procedure DecoError (ADecoder : TtakSeekableStreamDecoder);

var
  ErrorMsg   : Array[0..tak_ErrorStringSizeMax - 1] of Char;

begin
  tak_SSD_GetErrorString (tak_SSD_State (ADecoder), ErrorMsg, tak_ErrorStringSizeMax);
  Error (ErrorMsg);
end;

This procedure is called at any error, supplied the affected decoder object as a parameter. It does the following:

procedure WriteToWave (ABuf     : Pointer;
                       AByteNum : TtakInt32);

Writes AByteNum bytes out of ABuf to the Wave file.

procedure CreateWaveHeader (const AStreamInfo : Ttak_str_StreamInfo);

Creates a Wave header according to audio format and number of samples specified by AStreamInfo and writes it to the Wave file.

type
  PByte_Arr = ^TByte_Arr;				      
  TByte_Arr = Array[0..$7FFFFFFF] of Byte;

Required for access to the content of a dynamically created buffer.

Main program

Note: This procedure calls a few functions explained in detail in the subsequent paragraphs.

procedure DecodeFile;

var
  Options      : TtakSSDOptions;
  Decoder      : TtakSeekableStreamDecoder;
  StreamInfo   : Ttak_str_StreamInfo;
  WaveMetaDesc : Ttak_str_SimpleWaveDataHeader;
  WaveMetaBuf  : PByte_Arr;

begin
  Options.Cpu   := tak_Cpu_Any;
  Options.Flags := tak_ssd_opt_SequentialRead;

  Decoder := tak_SSD_Create_FromFile ('d:\YourFilePath.tak', Options, Nil, Nil);
  if Decoder <> Nil then begin
    if tak_SSD_Valid (Decoder) = tak_True then begin
      if tak_SSD_GetStreamInfo (ADecoder, StreamInfo) <> tak_res_Ok then
        DecoError (Decoder);
          { Can virtually never happen if the decoder was valid after
            its construction...
          }
      ReadWaveMetaData (Decoder, WaveMetaDesc, WaveMetaBuf);
      if WaveMetaBuf <> Nil then
        WriteWaveMetaDataHead (WaveMetaDesc, WaveMetaBuf)
          { If WaveMetaBuf <> Nil, the Wave metadata was
            read successfully. 
          }
      else
        CreateWaveHeader (StreamInfo);
          { No Wave metadata available, we have to create an appropriate
            Wave header. 
          }
      DecodeAudioData (StreamInfo);
      if WaveMetaBuf <> Nil then begin
        WriteWaveMetaDataTail (WaveMetaDesc, WaveMetaBuf);
        DestroyWaveMetaData (WaveMetaDesc, WaveMetaBuf);
      end;
    end  
    else
      DecoError (Decoder);
    tak_SSD_Destroy (Decoder);
  end
  else
    Proto ('System error: Unable to create Decoder!'); 
end;

First the decoder options are defined. The Cpu field should always be set to tak_Cpu_Any for the decoder to use all available CPU features for optimizations. Features not available (such as SSE, for example) are deactivated automatically.

The Flags field contains a combination of option constants. tak_ssd_opt_SequentialRead gives maximum performance if sequential file reading is used exclusively.

By default, audio data of damaged frames is replaced with silence. If you want this data to be skipped, the tak_ssd_opt_SkipDamagedFrames flag is additionally required:

  Options.Flags := tak_ssd_opt_SequentialRead or tak_ssd_opt_SkipDamagedFrames;

It is recommended to offer an appropriate option to the end user of the software.

Important notes:

The result of the decoder constructor tak_SSD_Create_FromFile is only Nil if a serious system error occurred (such as OutOfMemory). Because of this, after a successful construction it is additionally required to use tak_SSD_Valid to test for decoder errors such as an error while opening the source file.

tak_SSD_GetStreamInfo reads the StreamInfo containing some useful information that is required later on.

After decoding is finished, tak_SSD_Destroy destroys the decoder construct.

Reading Wave metadata

By default, the metadata block of a TAK file contains the original Wave file header and - where applicable - metadata attached to the audio data. Both will be read now.

procedure DestroyWaveMetaData (const ADesc : Ttak_str_SimpleWaveDataHeader;
                               var   ABuf  : PByte_Arr);
  { If ABuf <> Nil, the memory allocated by it is deallocated
    according to the size information in ADesc. ABuf
    is then set to Nil.
  }
  
begin
  if ABuf <> Nil then begin
    FreeMem (ABuf, ADesc.HeadSize + ADesc.TailSize);
    ABuf := Nil;
  end;
end;


procedure ReadWaveMetaData (    ADecoder : TtakSeekableStreamDecoder;
                            var ADesc    : Ttak_str_SimpleWaveDataHeader;
                            var ABuf     : PByte_Arr);
  { Reads the Wave metadata description in ADesc and the metadata itself
    in ABuf. Success results in ABuf <> Nil after return.
	
    Possibly ABuf has to be deallocated later, using DestroyWaveMetaData. 
  }

begin   
  ABuf := Nil;	  
  if tak_SSD_GetSimpleWaveDataDesc (ADecoder, ADesc) = tak_res_Ok then begin
    { The Wave metadata description was read successfully.
    } 
    GetMem (ABuf, ADesc.HeadSize + ADesc.TailSize);
      { Allocate memory for metadata.
      }
    if tak_SSD_ReadSimpleWaveData (ADecoder, ABuf,
                                   ADesc.HeadSize + ADesc.TailSize)
       <> tak_res_Ok
    then begin
      { Reading metadata failed.
      } 
      if tak_SSD_Valid (ADecoder) <> tak_True then 
         DecoError (ADecoder)
           { Fatal error!
           } 
      else
        DestroyWaveMetaData (ADesc, ABuf);
          { We can continue, but we have to create
            our own header.
          }
    end;	
  end	
  else
  begin
    { The Wave metadata description could not be read.
    } 
    if tak_SSD_Valid (ADecoder) <> tak_True then 
      DecoError (ADecoder);
        { The cause was a fatal error.
        }
  end;
end

tak_SSD_GetSimpleWaveDataDesc reads the Wave metadata description from the stream. As this is optional, a failure not necessarily implies an error. If required, the function result sheds light on the reason for the failure.

The metadata description ADesc contains the sizes for leading (header) and trailing metadata. The sum of these is the required size of the buffer ABuf which the metadata is written to, using tak_SSD_ReadSimpleWaveData.

Writing Wave metadata

The following two procedures write the Wave header and, if required, trailing metadata to the destination file:

procedure WriteWaveMetaDataHead (const ADesc : Ttak_str_SimpleWaveDataHeader;
                                       ABuf  : PByte_Arr);
  { Writes the Wave header (if there is any) from ABuf to the Wave file.
  }
  
begin
  if ABuf <> Nil then
    WriteToWave (ABuf, ADesc.HeadSize);
end;


procedure WriteWaveMetaDataTail (const ADesc : Ttak_str_SimpleWaveDataHeader;
                                       ABuf  : PByte_Arr);
  { Writes trailing metadata (if there is any) from ABuf to the Wave file.
  }

begin
  if (ABuf <> Nil) and (ADesc.TailSize <> 0) then begin
    WriteToWave (@ABuf^[ADesc.HeadSize], ADesc.TailSize);
end;

Decoding and outputting audio data

The audio data is sequentially decoded and written to the Wave file.

procedure DecodeAudioData (const AStreamInfo : Ttak_str_StreamInfo);

var
  SamplesPerBuf : TtakInt32;
  SampleSize    : TtakInt32;
  BufSize       : TtakInt32;
  Audio         : PByte_Arr;
  ReadNum       : TtakInt32;
  OpResult      : TtakResult;

begin
  SamplesPerBuf := AStreamInfo.Sizes.FrameSizeInSamples;
  SampleSize    := AStreamInfo.Audio.BlockSize;
    { Frame / Sample size.
    }
  BufSize := SamplesPerBuf * SampleSize;
    { Enough space to hold a decoded frame.
    }
  GetMem (Audio, BufSize);
    { For optimum performance, the memory should be aligned to an integer
      multiple of 4 or - even better - 8.
    }

AStreamInfo contains some useful information which is copied to local variables for easier handling:

If the stream is read sequentially, the highest speed can be achieved by reading the audio data frame by frame. The product of the frame size SamplesPerBuf and the sample size SampleSize is the appropriate size BufSize of the buffer memory Audio for read operations.

On every iteration of the following loop a frame is decoded and the resulting audio data is written to the Wave file:

  ReadNum := 1;
  while ReadNum > 0 do begin
    OpResult := tak_SSD_ReadAudio (Decoder, Audio, SamplesPerBuf, ReadNum);
    if  (OpResult <> tak_res_Ok)
    and (OpResult <> tak_res_ssd_FrameDamaged) then 
      DecoError (Decoder);
    if ReadNum > 0 then
      WriteToWave (Audio, ReadNum * SampleSize);
  end;
  
  FreeMem (Audio, BufSize);
end;

tak_SSD_ReadAudio reads a maximum of SamplesPerBuf samples to the buffer Audio. ReadNum contains the number of samples actually read and can be less than SamplesPerBuf only when reading the last frame of the file.

The result tak_res_ssd_FrameDamaged indicates that damaged audio data was skipped or replaced with silence. The decoder can proceed without problems nonetheless; it is up to the programmer whether decoding is cancelled in case of damaged frames. The example ignores them.

It is not necessary to record messages regarding damaged frames. A tak_SSD_GetStateInfo call provides detailed status information at any time, including damages.