unit dopenal_ogg;

interface

uses
  dtypes,
  dstrnum,
  dstream,
  dopenal,
  doggparser;

procedure OpenALOggInit;
procedure OpenALOggDone;
procedure OpenALOggUpdate;
procedure OpenALOggPlayMusic(Data: TData);
procedure OpenALOggReplay;
// 0 - no, 255 - max
procedure OpenALOggSetVolume(Volume: LongInt);

implementation

type
TOggPlayer = object
private
  FPreviousData: TData;
  FData: TData;
  FSourceID: TALuint;
  FOggVorbisFile: TOggVorbisFile;
  FBlockSize: LongInt;
  FBlocks: array[0 .. OGG_BLOCKS_COUNT - 1] of TALuint;
  FLooped: Boolean;
  FFormat: LongInt;
  FRate: LongInt;

  FDevice: TALCdevice;
  FContext: TALCContext;

  function ReadOggBlock(BufID: TALuint; Format, Rate: Cardinal; Size: LongInt): Boolean;
public
  constructor Init;
  destructor Done; virtual;
  procedure Update;
  procedure Play(Data: TData);
  procedure Replay;
  procedure SetVolume(Volume: LongInt);
end;

function TOggPlayer.ReadOggBlock(BufID: TALuint; Format, Rate: Cardinal; Size: LongInt): Boolean;
var
  PCM: PArrayOfByte;
  Readed, Temp: LongInt;
  bitStream: LongInt;
begin
  Pointer(PCM) := GetMem(Size);
  Readed := 0;
  while Readed < Size do begin
    Temp := ov_read(@FOggVorbisFile, @(PCM^[Readed]), Size - Readed, 0, 2, 1, @bitStream);
    if Temp <= 0 then
      break;
    Inc(Readed, Temp);
  end;
  {repeat
    Temp := ov_read(@FOggVorbisFile, @(PCM^[Readed]), Size - Readed, 0, 2, 1, nil);
    Inc(Readed, Temp);
  until (Temp = 0) or (Readed = Size);}
  alBufferData(BufID, Format, PCM, Readed, Rate);
  FreeMem(PCM);  
  Result := Temp > 0;
  if Temp < 0 then
    System.Writeln('TOggPlayer: ov_read return error code ' + IntToStr(Temp));
end;

constructor TOggPlayer.Init;
begin
  FPreviousData.Init(nil, False);
  FData.Init(nil, False);
  FLooped := True;

  // TODO: TEMPORARY, вынести в класс TOpenAL
  //if not InitOpenAL then begin
  //  System.Writeln(stderr, 'OpenAL initialization failed');
  //  Exit;
  //end;
  
  //FDevice  := alcOpenDevice(Nil);
  //FContext := alcCreateContext(FDevice, Nil);
  //alcMakeContextCurrent(FContext);    
  /////////////////////////////////////////
  
  alGenSources(1, @FSourceID);
  alSourcef(FSourceID, AL_PITCH, 1.0);
  alSourcef(FSourceID, AL_GAIN, 1.0);
end;

destructor TOggPlayer.Done;
begin
  FData.Done;
  FPreviousData.Done;
end;

procedure TOggPlayer.Update;
var
  Param: LongInt;
  Processed: LongInt;
  BufId: TALuint;
  ALError: TALEnum;
begin
  alGetSourcei(FSourceID, AL_SOURCE_STATE, @Param);
  if Param <> AL_PLAYING then
    alSourcePlay(FSourceID);
  alGetSourcei(FSourceID, AL_BUFFERS_PROCESSED, @Processed);
  while Processed > 0 do begin
    alSourceUnqueueBuffers(FSourceID, 1, @BufId);
    if ReadOggBlock(BufId, FFormat, FRate, OGG_BLOCK_SIZE) then begin
      alSourceQueueBuffers(FSourceID, 1, @BufId);
    end else begin
      alSourceQueueBuffers(FSourceID, 1, @BufId);
      ov_pcm_seek(@FOggVorbisFile, 0);
      // if not FLooped then
      //   Stop;
    end;
    Dec(Processed);
  end;
  ALError := alGetError;
  while ALError <> AL_NO_ERROR do begin
    System.Writeln('TOggPlayer.Update: ' + IntToHex(ALError));
    ALError := alGetError;
  end;
end;

procedure TOggPlayer.Play(Data: TData);
var
  I: LongInt;
  Param: LongInt;
  Processed: LongInt;
  BufId: TALuint;
begin
  alGetSourcei(FSourceID, AL_SOURCE_STATE, @Param);
  if Param = AL_PLAYING then begin
    alSourceStop(FSourceID);
    alGetSourcei(FSourceID, AL_BUFFERS_PROCESSED, @Processed);
    while Processed > 0 do begin
      alSourceUnqueueBuffers(FSourceID, 1, @BufId);
      alDeleteBuffers(1, @BufId);
      Dec(Processed);
    end;
  end;

  if ov_open_callbacks(Pointer(Data.Stream), @FOggVorbisFile, nil, 0, OVCallbacks) < 0 then begin
    Data.Done;
    Exit;
  end;
  FBlockSize := OGG_BLOCK_SIZE;
  alSourcei(FSourceID, AL_LOOPING, AL_FALSE);
  if FOggVorbisFile.VI^.Channels = 1 then begin
    FFormat := AL_FORMAT_MONO16;
  end else
    FFormat := AL_FORMAT_STEREO16;
  FRate := FOggVorbisFile.VI^.Rate;
  for I := 0 to OGG_BLOCKS_COUNT - 1 do begin
    alGenBuffers(1, @FBlocks[I]);
    ReadOggBlock(FBlocks[I], FFormat, FRate, OGG_BLOCK_SIZE);
    alSourceQueueBuffers(FSourceId, 1, @FBlocks[I]);
  end;

  alSourcePlay(FSourceID);
  FPreviousData := Data;
  Data := Data;
end;

procedure TOggPlayer.Replay;
var
  Param: LongInt;
  Processed: LongInt;
  BufId: TALuint;
begin
  alGetSourcei(FSourceID, AL_SOURCE_STATE, @Param);
  if Param = AL_PLAYING then begin
    alSourceStop(FSourceID);
    {alGetSourcei(FSourceID, AL_BUFFERS_PROCESSED, @Processed);
    while Processed > 0 do begin
      alSourceUnqueueBuffers(FSourceID, 1, @BufId);
      alDeleteBuffers(1, @BufId);
      Dec(Processed);
    end;}
  end;

  ov_pcm_seek(@FOggVorbisFile, 0);
end;

procedure TOggPlayer.SetVolume(Volume: LongInt);
begin
  if Volume < 0 then
    Volume := 0;
  if Volume > 255 then
    Volume := 255;
  alSourcef(FSourceID, AL_GAIN, Volume / 255);
end;

var
  OggPlayer: TOggPlayer;

procedure OpenALOggInit;
begin
  OggPlayer.Init;
end;

procedure OpenALOggDone;
begin
  OggPlayer.Done;
end;

procedure OpenALOggUpdate;
begin
  OggPlayer.Update;
end;

procedure OpenALOggPlayMusic(Data: TData);
begin
  OggPlayer.Play(Data);
end;

procedure OpenALOggReplay;
begin
  OggPlayer.Replay;
end;

procedure OpenALOggSetVolume(Volume: LongInt);
begin
  OggPlayer.SetVolume(Volume);
end;

end.
