# 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 TtakAnsiChar;

    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:

- Determines error code using tak_SSD_State.
- Calls tak_SSD_GetErrorString in order to convert the error code to an English error text.
- Shows the error text.
- Stops program execution.

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

Writes AByteNum bytes out of ABuf to the Wave file.

    procedure CreateWaveHeader (const AStreamInfo : Ttak_str_StreamInfo_V22);

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_V22;
      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_V22 (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 tak_ssd_opt_SequentialRead option prohibits seek operations and thus is not appropriate for media player applications!
- When a damaged file is decoded using the tak_ssd_opt_SkipDamagedFrames option, the created file contains less samples than the original file. As the original Wave header contains information about the file length, it cannot be used for the new, shorter file!

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_V22 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_V22);

    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:

- The frame size (in samples) is copied from FrameSizeInSamples to SamplesPerBuf.
- The memory requirement of a sample (across all channels) is copied from BlockSize to SampleSize.

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.