procedure MusicKillPlayer(var Music: TMusic);
begin
  Music.Stop;
  if Music.FPlayer <> nil then begin
    Dispose(Music.FPlayer, Done);
    Music.FData.Done;
  end;
  Music.FPlayer := nil;
end;

constructor TMusic.Init;
begin
  FData := AsData(nil, False);
  FVolume := DENG_MAX_VOLUME div 2;
  FPlayer := nil;
end;

destructor TMusic.Done;
begin
end;

procedure TMusic.OnInit;
begin
  // TODO check if OpenAL actualy initialized in DAudio!
  alGenSources(1, @FSourceId);
end;

procedure TMusic.OnDone;
begin
  MusicKillPlayer(Self);
  alDeleteSources(1, @FSourceId);
end;

procedure TMusic.Update;
label
  LReread;
var
  Processed: ALint;
  BufId: ALuint;
  Error: ALenum;
  Format: ALenum;
  Count: LongInt;
begin
  if FPlayer = nil then
    Exit;
  if not IsPlaying then
    alSourcePlay(FSourceId);
  if FPlayer^.Channels = 1 then begin
    Format := AL_FORMAT_MONO16;
  end else
    Format := AL_FORMAT_STEREO16;
  alGetSourcei(FSourceID, AL_BUFFERS_PROCESSED, @Processed);
  while Processed > 0 do begin
    alSourceUnqueueBuffers(FSourceId, 1, @BufId);
  LReread:
    Count := FPlayer^.ReadPCM(@FPCM[0], BUFFER_SIZE);
    if Count > 0 then begin
      alBufferData(BufId, Format, @FPCM[0], Count, FPlayer^.Rate);
      alSourceQueueBuffers(FSourceId, 1, @BufId);
    end else begin
      // TODO not looped callback here!
      //alBufferData(BufId, Format, @FPCM[0], 0, FPlayer^.Rate);
      //alSourceQueueBuffers(FSourceId, 1, @BufId);
      FPlayer^.Rewind;
      Dec(Processed); // <- FIXME protection against infinite loop
      goto LReread;
    end;
    Dec(Processed);
  end;
  Error := alGetError;
  while Error <> AL_NO_ERROR do begin
    Log('Eng.Music.Update error: ' + IntToStr(Error));
    Error := alGetError;
  end;
end;

function TMusic.Play(Data: TData): Boolean;
var
  Loader: PMusicLoader;
  Format: ALenum;
  I: LongInt;
begin
  MusicKillPlayer(Self);
  FData := Data;
  for Loader in MusicLoaders do begin
    FPlayer := Loader^.Load(FData.Stream);
    if FPlayer <> nil then begin
      Result := True;
      FBufferSize := BUFFER_SIZE;
      alSourcei(FSourceId, AL_LOOPING, AL_FALSE);
      if FPlayer^.Channels = 1 then begin
        Format := AL_FORMAT_MONO16;
      end else
        Format := AL_FORMAT_STEREO16;
      for I := 0 to BUFFERS_COUNT - 1 do begin
        alGenBuffers(1, @FBuffers[I]);
        FPlayer^.ReadPCM(@FPCM[0], BUFFER_SIZE);
        alBufferData(FBuffers[I], Format, @FPCM[0], BUFFER_SIZE, FPlayer^.Rate);
        alSourceQueueBuffers(FSourceId, 1, @FBuffers[I]);
      end;
      alSourcePlay(FSourceId);
      Exit;
    end;
  end;
  LogTagged('D', 'Eng.Music.Play failed');
  Data.Done;
  Result := False;
end;

procedure TMusic.Stop;
var
  Processed: LongInt;
  BufId: TALuint;
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;

procedure TMusic.Pause;
begin
  alSourcePause(FSourceId);
end;

procedure TMusic.SetVolume(Volume: Integer);
begin
  FVolume := Volume;
  alSourcef(FSourceId, AL_GAIN, Volume / 255);
end;

function TMusic.IsPlaying: Boolean;
var
  Param: ALint;
begin
  alGetSourcei(FSourceId, AL_SOURCE_STATE, @Param);
  Result := Param = AL_PLAYING;
end;
