Векторный звук. Кодек.

Планы, идеология, архитектура и т.п.

Модератор: Модераторы

Векторный звук. Кодек.

Сообщение Alexander » 07.03.2025 10:46:48

https://chatgpt.com сделал большую "научную" работу, но результат не вышел, хотя и хотел что-то основательно оптимизировать.
А deepseek.com сделал и даже работоспособный, но файлы получаются огромными, без оптимизации по размеру получился. Так что только 16 бит и на маленьких файлах пробовать. В таком виде представляет только исследовательский интерес без практического, но его представляет.

Итак: кодек векторного звука:

Код: Выделить всё
program vacodec;

{$mode objfpc}{$H+}

uses
  SysUtils, vanodeunit, wavfileunit, vaformatunit,vatypesunit;

procedure CompressWAVToVA(const InputFile, OutputFile: string);
var
  Amplitudes: TMultiChannelAmplitudeArray;
  SampleRate, NumChannels: Integer;
  Nodes: TVANodeArray;
begin
  WriteLn('Сжатие WAV-файла: ', InputFile);
 
  // Загрузка WAV-файла
  WriteLn('Загрузка WAV-файла...');
  Amplitudes := LoadWAVToAmplitudeArray(InputFile, SampleRate, NumChannels);

  // Проверка на минимальную длину данных
  WriteLn('Проверка длины данных...');
  if Length(Amplitudes) = 0 then
    raise Exception.Create('Файл не содержит данных');
  if Length(Amplitudes[0]) < 4 then
    raise Exception.Create('Файл слишком короткий для векторизации: требуется минимум 4 сэмпла');

  // Векторизация звука
  WriteLn('Векторизация звука...');
  Nodes := VectorizeAmplitudeArray(Amplitudes, SampleRate);

  // Сохранение в .va
  WriteLn('Сохранение в .va...');
  SaveVAToFile(OutputFile, Nodes, SampleRate, NumChannels);
  WriteLn('Файл успешно сжат: ', OutputFile);
end;

procedure DecompressVAToWAV(const InputFile, OutputFile: string);
var
  SampleRate, NumChannels: Integer;
  Nodes: TVANodeArray;
  Amplitudes: TMultiChannelAmplitudeArray;
begin
  // Загрузка .va-файла
  Nodes := LoadVAFromFile(InputFile, SampleRate, NumChannels);

  // Восстановление звука
  Amplitudes := ReconstructAmplitudeArray(Nodes, SampleRate, NumChannels);

  // Сохранение в WAV
  SaveAmplitudeArrayToWAV(OutputFile, Amplitudes, SampleRate, NumChannels);
  WriteLn('Файл успешно распакован: ', OutputFile);
end;

begin
  try
    if (ParamCount < 2) or (ParamCount > 3) then
    begin
      WriteLn('Использование:');
      WriteLn('  vacodec input.wav output.va       — сжатие WAV в VA');
      WriteLn('  vacodec -d input.va output.wav    — распаковка VA в WAV');
      Exit;
    end;

    if ParamStr(1) = '-d' then
      DecompressVAToWAV(ParamStr(2), ParamStr(3))
    else
      CompressWAVToVA(ParamStr(1), ParamStr(2));
  except
    on E: Exception do
    begin
      WriteLn('Ошибка: ', E.Message);
      Halt(1);
    end;
  end;
end.


Код: Выделить всё
unit vaformatunit;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, vanodeunit,vatypesunit;

const
  VA_MAGIC = 'VAVF'; // Магическое число
  VA_VERSION = 1;    // Версия формата

// Запись векторного звука в файл
procedure SaveVAToFile(const FileName: string; const Nodes: TVANodeArray; SampleRate: Integer; NumChannels: Integer);

// Чтение векторного звука из файла
function LoadVAFromFile(const FileName: string; out SampleRate: Integer; out NumChannels: Integer): TVANodeArray;

implementation

procedure SaveVAToFile(const FileName: string; const Nodes: TVANodeArray; SampleRate: Integer; NumChannels: Integer);
var
  FileStream: TFileStream;
  i, j: Integer;
begin
  FileStream := TFileStream.Create(FileName, fmCreate);
  try
    // Запись заголовка
    FileStream.WriteBuffer(VA_MAGIC[1], Length(VA_MAGIC));
    FileStream.WriteWord(VA_VERSION);
    FileStream.WriteWord(NumChannels);
    FileStream.WriteDWord(SampleRate);
    FileStream.WriteDWord(Length(Nodes));
    // Запись данных узлов
    for i := 0 to High(Nodes) do
    begin
      FileStream.WriteBuffer(Nodes[i].GetTimeStamp, SizeOf(Double));
      for j := 0 to NumChannels - 1 do
      begin
        FileStream.WriteBuffer(Nodes[i].GetControlPoint(j, 0), SizeOf(TBezierControlPoint));
        FileStream.WriteBuffer(Nodes[i].GetControlPoint(j, 1), SizeOf(TBezierControlPoint));
        FileStream.WriteBuffer(Nodes[i].GetControlPoint(j, 2), SizeOf(TBezierControlPoint));
        FileStream.WriteBuffer(Nodes[i].GetControlPoint(j, 3), SizeOf(TBezierControlPoint));
      end;
    end;
  finally
    FileStream.Free;
  end;
end;

function LoadVAFromFile(const FileName: string; out SampleRate: Integer; out NumChannels: Integer): TVANodeArray;
var
  FileStream: TFileStream;
  Magic: array[1..4] of Char;
  Version: Word;
  NodeCount: Integer;
  i, j: Integer;
  TimeStamp: Double;
  ControlPoints: array[0..3] of TBezierControlPoint;
begin
  FileStream := TFileStream.Create(FileName, fmOpenRead);
  try
    // Чтение заголовка
    FileStream.ReadBuffer(Magic, SizeOf(Magic));
    if Magic <> VA_MAGIC then
      raise Exception.Create('Неверный формат файла .va');

    Version := FileStream.ReadWord;
    if Version <> VA_VERSION then
      raise Exception.Create('Неподдерживаемая версия формата .va');

    NumChannels := FileStream.ReadWord;
    SampleRate := FileStream.ReadDWord;
    NodeCount := FileStream.ReadDWord;

    // Проверка на допустимое количество узлов
    if NodeCount < 0 then
      raise Exception.Create('Некорректное количество узлов в файле .va');

    // Чтение данных узлов
    SetLength(Result, NodeCount);
    for i := 0 to NodeCount - 1 do
    begin
      FileStream.ReadBuffer(TimeStamp, SizeOf(Double));
      Result[i].Init(TimeStamp, NumChannels);
      for j := 0 to NumChannels - 1 do
      begin
        FileStream.ReadBuffer(ControlPoints[0], SizeOf(TBezierControlPoint));
        FileStream.ReadBuffer(ControlPoints[1], SizeOf(TBezierControlPoint));
        FileStream.ReadBuffer(ControlPoints[2], SizeOf(TBezierControlPoint));
        FileStream.ReadBuffer(ControlPoints[3], SizeOf(TBezierControlPoint));
        Result[i].SetControlPoint(j, 0, ControlPoints[0].X, ControlPoints[0].Y);
        Result[i].SetControlPoint(j, 1, ControlPoints[1].X, ControlPoints[1].Y);
        Result[i].SetControlPoint(j, 2, ControlPoints[2].X, ControlPoints[2].Y);
        Result[i].SetControlPoint(j, 3, ControlPoints[3].X, ControlPoints[3].Y);
      end;
    end;
  finally
    FileStream.Free;
  end;
end;

end.


Код: Выделить всё
unit vanodeunit;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, wavfileunit,vatypesunit;

type
  // Основной объект, представляющий узел векторного звука
  TVANode = object
  private
    FTimeStamp: TTimeStamp; // Временная метка узла
    FControlPoints: array of array[0..3] of TBezierControlPoint; // Контрольные точки кривой Безье для каждого канала
  public
    // Конструктор для инициализации узла
    constructor Init(ATimeStamp: TTimeStamp; NumChannels: Integer);
   
    // Метод для установки контрольных точек кривой Безье
    procedure SetControlPoint(Channel, Index: Integer; X, Y: TAmplitude);
   
    // Метод для получения контрольной точки кривой Безье
    function GetControlPoint(Channel, Index: Integer): TBezierControlPoint;
   
    // Метод для вычисления амплитуды в заданный момент времени
    function GetAmplitudeAtTime(Channel: Integer; Time: TTimeStamp): TAmplitude;
   
    // Метод для получения временной метки узла
    function GetTimeStamp: TTimeStamp;
  end;

  // Тип для хранения массива узлов
  TVANodeArray = array of TVANode;

  // Функция для загрузки WAV-файла в массив амплитуд
  function LoadWAVToAmplitudeArray(const FileName: string; out SampleRate: Integer; out NumChannels: Integer): TMultiChannelAmplitudeArray;

  // Функция для сохранения массива амплитуд в WAV-файл
  procedure SaveAmplitudeArrayToWAV(const FileName: string; const Amplitudes: TMultiChannelAmplitudeArray; SampleRate: Integer; NumChannels: Integer);

  // Функция для векторизации звука (преобразование массива амплитуд в узлы TVANode)
  function VectorizeAmplitudeArray(const Amplitudes: TMultiChannelAmplitudeArray; SampleRate: Integer): TVANodeArray;

  // Функция для восстановления массива амплитуд из узлов TVANode
  function ReconstructAmplitudeArray(const Nodes: TVANodeArray; SampleRate: Integer; NumChannels: Integer): TMultiChannelAmplitudeArray;

implementation

constructor TVANode.Init(ATimeStamp: TTimeStamp; NumChannels: Integer);
begin
  FTimeStamp := ATimeStamp;
  SetLength(FControlPoints, NumChannels);
end;

procedure TVANode.SetControlPoint(Channel, Index: Integer; X, Y: TAmplitude);
begin
  if (Channel >= 0) and (Channel < Length(FControlPoints)) and (Index >= 0) and (Index <= 3) then
  begin
    FControlPoints[Channel][Index].X := X;
    FControlPoints[Channel][Index].Y := Y;
  end;
end;

function TVANode.GetControlPoint(Channel, Index: Integer): TBezierControlPoint;
begin
  if (Channel >= 0) and (Channel < Length(FControlPoints)) and (Index >= 0) and (Index <= 3) then
    Result := FControlPoints[Channel][Index]
  else
    Result := Default(TBezierControlPoint);
end;

function TVANode.GetAmplitudeAtTime(Channel: Integer; Time: TTimeStamp): TAmplitude;
var
  t: Double;
begin
  // Нормализация времени относительно временной метки узла
  t := (Time - FTimeStamp) / 1.0; // Здесь 1.0 — это длительность узла, можно изменить на нужное значение
 
  // Вычисление амплитуды с использованием кубической кривой Безье
  Result := (1 - t) * (1 - t) * (1 - t) * FControlPoints[Channel][0].Y +
            3 * (1 - t) * (1 - t) * t * FControlPoints[Channel][1].Y +
            3 * (1 - t) * t * t * FControlPoints[Channel][2].Y +
            t * t * t * FControlPoints[Channel][3].Y;
end;

function TVANode.GetTimeStamp: TTimeStamp;
begin
  Result := FTimeStamp;
end;

// Загрузка WAV-файла в массив амплитуд
function LoadWAVToAmplitudeArray(const FileName: string; out SampleRate: Integer; out NumChannels: Integer): TMultiChannelAmplitudeArray;
var
  WAVFile: TWAVFile;
  i: Integer;
  TotalSamples: Integer;
begin
  WriteLn('Загрузка WAV-файла: ', FileName);
  WAVFile := TWAVFile.Create(FileName);
  try
    WAVFile.LoadFromFile(FileName);
    SampleRate := WAVFile.GetSampleRate;
    NumChannels := WAVFile.GetNumChannels;

    // Проверка на поддерживаемый формат (PCM)
    if WAVFile.GetBitsPerSample <> 16 then
      raise Exception.Create('Поддерживаются только 16-битные WAV-файлы');

    // Проверка на наличие данных
    if WAVFile.GetSampleRate = 0 then
      raise Exception.Create('Файл не содержит данных');

    // Инициализация массива амплитуд
    WriteLn('Инициализация массива амплитуд...');
    WriteLn('Количество каналов: ', NumChannels);
    WriteLn('Частота дискретизации: ', SampleRate, ' Гц');
    WriteLn('Биты на выборку: ', WAVFile.GetBitsPerSample);

    // Вычисляем общее количество сэмплов
    TotalSamples := WAVFile.GetDataChunkSize div (NumChannels * (WAVFile.GetBitsPerSample div 8));
    WriteLn('Общее количество сэмплов: ', TotalSamples);

    SetLength(Result, NumChannels);
    for i := 0 to NumChannels - 1 do
    begin
      WriteLn('Выделение памяти для канала ', i, ': ', TotalSamples, ' сэмплов');
      SetLength(Result[i], TotalSamples); // Выделяем память для всех сэмплов
    end;

    // Чтение данных
    WriteLn('Чтение данных из WAV-файла...');
    WAVFile.ReadAmplitudes(Result);

    // Отладочное сообщение
    WriteLn('Загружено каналов: ', NumChannels);
    WriteLn('Длина данных в канале 0: ', Length(Result[0]));
  finally
    WAVFile.Free;
  end;
end;

// Сохранение массива амплитуд в WAV-файл
procedure SaveAmplitudeArrayToWAV(const FileName: string; const Amplitudes: TMultiChannelAmplitudeArray; SampleRate: Integer; NumChannels: Integer);
var
  WAVFile: TWAVFile;
begin
  WAVFile := TWAVFile.Create(FileName);
  try
    WAVFile.SaveToFile(FileName, Amplitudes, SampleRate, NumChannels, 16); // 16 бит
  finally
    WAVFile.Free;
  end;
end;

// Векторизация звука (преобразование массива амплитуд в узлы TVANode)
function VectorizeAmplitudeArray(const Amplitudes: TMultiChannelAmplitudeArray; SampleRate: Integer): TVANodeArray;
var
  i, j: Integer;
  TimeStep: Double;
  ControlPoints: array[0..3] of TBezierControlPoint;
begin
  if Length(Amplitudes) = 0 then
    raise Exception.Create('Нет данных для векторизации');

  // Проверка, что в каждом канале достаточно данных для создания хотя бы одного узла
  for j := 0 to High(Amplitudes) do
  begin
    if Length(Amplitudes[j]) < 4 then
      raise Exception.CreateFmt('Недостаточно данных для векторизации в канале %d: требуется минимум 4 точки', [j]);
  end;

  TimeStep := 1.0 / SampleRate;
  SetLength(Result, Length(Amplitudes[0]) - 3);
  for i := 0 to High(Result) do
  begin
    Result[i].Init(i * TimeStep, Length(Amplitudes));
    for j := 0 to High(Amplitudes) do
    begin
      if (i + 3) > High(Amplitudes[j]) then
        raise Exception.Create('Недостаточно данных для векторизации');

      ControlPoints[0].X := i * TimeStep;
      ControlPoints[0].Y := Amplitudes[j][i];
      ControlPoints[1].X := (i + 1) * TimeStep;
      ControlPoints[1].Y := Amplitudes[j][i + 1];
      ControlPoints[2].X := (i + 2) * TimeStep;
      ControlPoints[2].Y := Amplitudes[j][i + 2];
      ControlPoints[3].X := (i + 3) * TimeStep;
      ControlPoints[3].Y := Amplitudes[j][i + 3];
      Result[i].SetControlPoint(j, 0, ControlPoints[0].X, ControlPoints[0].Y);
      Result[i].SetControlPoint(j, 1, ControlPoints[1].X, ControlPoints[1].Y);
      Result[i].SetControlPoint(j, 2, ControlPoints[2].X, ControlPoints[2].Y);
      Result[i].SetControlPoint(j, 3, ControlPoints[3].X, ControlPoints[3].Y);
    end;
  end;
end;

// Восстановление массива амплитуд из узлов TVANode
function ReconstructAmplitudeArray(const Nodes: TVANodeArray; SampleRate: Integer; NumChannels: Integer): TMultiChannelAmplitudeArray;
var
  i, j: Integer;
  TimeStep: Double;
begin
  TimeStep := 1.0 / SampleRate;
  SetLength(Result, NumChannels);
  for j := 0 to NumChannels - 1 do
    SetLength(Result[j], Length(Nodes) * Round(SampleRate * TimeStep));
  for i := 0 to High(Result[0]) do
  begin
    for j := 0 to NumChannels - 1 do
    begin
      Result[j][i] := Nodes[i div Round(SampleRate * TimeStep)].GetAmplitudeAtTime(j, i * TimeStep);
    end;
  end;
end;

end.


Код: Выделить всё
unit vatypesunit;
{$MODE OBJFPC}
interface
type
  // Тип для хранения амплитуды звука
  TAmplitude = Double;
  // Тип для хранения контрольных точек кривой Безье
  // Тип для хранения массива амплитуд (растровое представление звука)
  TAmplitudeArray = array of TAmplitude;
  // Тип для хранения временной метки узла
  TTimeStamp = Double;
  // Тип для хранения многоканального массива амплитуд
  TMultiChannelAmplitudeArray = array of TAmplitudeArray;
  // Тип для хранения контрольных точек кривой Безье
  TBezierControlPoint = record
    X, Y: TAmplitude;
  end;

implementation

end.


Код: Выделить всё
unit wavfileunit;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, BaseUnix, Unix;

type
  // Тип для хранения массива амплитуд
  TAmplitudeArray = array of Double;

  // Заголовок WAV-файла
  TWAVHeader = packed record
    RIFF: array[0..3] of Char;
    FileSize: Cardinal;
    WAVE: array[0..3] of Char;
  end;

  // Структура для хранения информации о формате WAV-файла
  TFmtChunk = packed record
    ChunkSize: Cardinal;
    AudioFormat: Word;
    NumChannels: Word;
    SampleRate: Cardinal;
    ByteRate: Cardinal;
    BlockAlign: Word;
    BitsPerSample: Word;
    ExtensionSize: Word; // Размер расширения (для WAVE_FORMAT_EXTENSIBLE)
  end;

  // Основной класс для работы с WAV-файлами
  TWAVFile = class
  private
    FFileName: string;
    FHeader: TWAVHeader;
    FFmtChunk: TFmtChunk;
    FDataChunkSize: Cardinal;
    FDataOffset: Integer;
    FFileHandle: cint;
  public
    constructor Create(const FileName: string);
    destructor Destroy; override;

    // Загрузка WAV-файла
    procedure LoadFromFile(const FileName: string);
    // Сохранение WAV-файла
    procedure SaveToFile(const FileName: string; const Amplitudes: array of TAmplitudeArray; SampleRate: Cardinal; NumChannels: Word; BitsPerSample: Word);

    // Чтение данных из WAV-файла в массив амплитуд
    function ReadAmplitudes(out Amplitudes: array of TAmplitudeArray): Integer;
    // Запись массива амплитуд в WAV-файл
    procedure WriteAmplitudes(const Amplitudes: array of TAmplitudeArray);

    // Получение информации о WAV-файле
    function GetSampleRate: Cardinal;
    function GetNumChannels: Word;
    function GetBitsPerSample: Word;
    function GetDataChunkSize: Cardinal;
  end;

implementation

constructor TWAVFile.Create(const FileName: string);
begin
  FFileName := FileName;
  FFileHandle := -1;
end;

destructor TWAVFile.Destroy;
begin
  if FFileHandle <> -1 then
    FpClose(FFileHandle);
  inherited Destroy;
end;

procedure TWAVFile.LoadFromFile(const FileName: string);
var
  chunkID: array[0..3] of Char;
  chunkSize: Cardinal;
begin
  FFileHandle := FpOpen(FileName, O_RDONLY);
  if FFileHandle = -1 then
    raise Exception.Create('Ошибка открытия файла');

  // Чтение заголовка WAV-файла
  if FpRead(FFileHandle, FHeader, SizeOf(TWAVHeader)) <> SizeOf(TWAVHeader) then
    raise Exception.Create('Ошибка чтения заголовка WAV');

  if (String(FHeader.RIFF) <> 'RIFF') or (String(FHeader.WAVE) <> 'WAVE') then
    raise Exception.Create('Неверный формат WAV файла');

  // Поиск и чтение fmt-секции
  while FpRead(FFileHandle, chunkID, 4) = 4 do
  begin
    if FpRead(FFileHandle, chunkSize, 4) <> 4 then
      Break;

    if String(chunkID) = 'fmt ' then
    begin
      if chunkSize < 16 then
        raise Exception.Create('Ошибка: размер fmt-секции слишком мал');

      FFmtChunk.ChunkSize := chunkSize;
      if FpRead(FFileHandle, FFmtChunk.AudioFormat, 16) <> 16 then
        raise Exception.Create('Ошибка чтения fmt-секции');

      // Пропускаем оставшиеся байты, если это расширенный формат
      if FFmtChunk.ChunkSize > 16 then
        FpLseek(FFileHandle, FFmtChunk.ChunkSize - 16, SEEK_CUR);
    end
    else if String(chunkID) = 'data' then
    begin
      FDataChunkSize := chunkSize;
      FDataOffset := FpLseek(FFileHandle, 0, SEEK_CUR);
      Break;
    end
    else
    begin
      FpLseek(FFileHandle, chunkSize, SEEK_CUR);
    end;
  end;
end;

procedure TWAVFile.SaveToFile(const FileName: string; const Amplitudes: array of TAmplitudeArray; SampleRate: Cardinal; NumChannels: Word; BitsPerSample: Word);
var
  FileHandle: cint;
  i, j: Integer;
  Sample: Int16;
  ChunkSize: Cardinal;
  AudioFormat: Word;
begin
  FileHandle := FpOpen(FileName, O_WRONLY or O_CREAT or O_TRUNC, &666);
  if FileHandle = -1 then
    raise Exception.Create('Ошибка создания файла');

  try
    // Записываем заголовок RIFF
    FpWrite(FileHandle, 'RIFF', 4);
    ChunkSize := 36 + (Length(Amplitudes[0]) * NumChannels * (BitsPerSample div 8));
    FpWrite(FileHandle, @ChunkSize, SizeOf(ChunkSize));
    FpWrite(FileHandle, 'WAVE', 4);

    // Записываем fmt-секцию
    FpWrite(FileHandle, 'fmt ', 4);
    ChunkSize := 16; // Размер fmt-секции
    FpWrite(FileHandle, @ChunkSize, SizeOf(ChunkSize));

    AudioFormat := 1; // PCM
    FpWrite(FileHandle, @AudioFormat, SizeOf(AudioFormat));
    FpWrite(FileHandle, @NumChannels, SizeOf(NumChannels));
    FpWrite(FileHandle, @SampleRate, SizeOf(SampleRate));

    ChunkSize := SampleRate * NumChannels * (BitsPerSample div 8);
    FpWrite(FileHandle, @ChunkSize, SizeOf(Cardinal));

    ChunkSize := NumChannels * (BitsPerSample div 8);
    FpWrite(FileHandle, @ChunkSize, SizeOf(Word));

    FpWrite(FileHandle, @BitsPerSample, SizeOf(Word));

    // Записываем data-секцию
    FpWrite(FileHandle, 'data', 4);
    ChunkSize := Length(Amplitudes[0]) * NumChannels * (BitsPerSample div 8);
    FpWrite(FileHandle, @ChunkSize, SizeOf(ChunkSize));

    // Записываем амплитуды
    for i := 0 to High(Amplitudes[0]) do
    begin
      for j := 0 to NumChannels - 1 do
      begin
        Sample := Round(Amplitudes[j][i] * 32767);
        if FpWrite(FileHandle, @Sample, SizeOf(Sample)) <> SizeOf(Sample) then
          raise Exception.Create('Ошибка записи данных');
      end;
    end;
  finally
    FpClose(FileHandle);
  end;
end;

function TWAVFile.ReadAmplitudes(out Amplitudes: array of TAmplitudeArray): Integer;
var
  i, j: Integer;
  Sample: Int16;
begin
  FpLseek(FFileHandle, FDataOffset, SEEK_SET);
  Result := 0;

  // Проверка на наличие данных
  if FDataChunkSize = 0 then
    raise Exception.Create('Файл не содержит данных');

  // Вычисляем количество сэмплов
  Result := FDataChunkSize div (FFmtChunk.NumChannels * (FFmtChunk.BitsPerSample div 8));

  // Инициализация массива Amplitudes
  for j := 0 to High(Amplitudes) do
    SetLength(Amplitudes[j], Result);

  // Чтение данных
  for i := 0 to Result - 1 do
  begin
    for j := 0 to FFmtChunk.NumChannels - 1 do
    begin
      if FpRead(FFileHandle, @Sample, SizeOf(Sample)) <> SizeOf(Sample) then
        Break;
      Amplitudes[j][i] := Sample / 32768.0; // Нормализация амплитуды
    end;
  end;
end;

procedure TWAVFile.WriteAmplitudes(const Amplitudes: array of TAmplitudeArray);
begin
  // Реализация метода (если требуется)
end;

function TWAVFile.GetSampleRate: Cardinal;
begin
  Result := FFmtChunk.SampleRate;
end;

function TWAVFile.GetNumChannels: Word;
begin
  Result := FFmtChunk.NumChannels;
end;

function TWAVFile.GetBitsPerSample: Word;
begin
  Result := FFmtChunk.BitsPerSample;
end;

function TWAVFile.GetDataChunkSize: Cardinal;
begin
  Result := FDataChunkSize;
end;

end.


А также vaplayer, который воспроизводит получившиеся .va файлы. Декларации alsa из wavplayer.

Код: Выделить всё
program vaplayer;

{$mode objfpc}{$H+}

uses
  Classes, SysUtils, BaseUnix, Unix, ALSA, vanodeunit, wavfileunit, vaformatunit, vatypesunit;

// Восстановление массива амплитуд из узлов TVANode
function ReconstructAmplitudeArray(const Nodes: TVANodeArray; SampleRate: Integer; NumChannels: Integer; NumSamples: Integer): TMultiChannelAmplitudeArray;
var
  i, j: Integer;
  TimeStep: Double;
  NodeIndex: Integer;
  SamplesPerNode: Double;
begin
if Length(Nodes) = 0 then
  raise Exception.Create('Нет узлов для восстановления');
if NumSamples = 0 then
  raise Exception.Create('Количество сэмплов не может быть равно нулю');

  // Инициализация массива амплитуд
  SetLength(Result, NumChannels);
  for j := 0 to NumChannels - 1 do
    SetLength(Result[j], NumSamples);

  // Восстановление амплитуд
  TimeStep := 1.0 / SampleRate;

  // Рассчитываем количество сэмплов на один узел
  SamplesPerNode := NumSamples / Length(Nodes);

  for i := 0 to High(Result[0]) do
  begin
    // Рассчитываем индекс узла, соответствующего текущему сэмплу
    NodeIndex := Trunc(i / SamplesPerNode);
    if NodeIndex >= Length(Nodes) then
      NodeIndex := High(Nodes); // Ограничиваем индекс последним узлом

    for j := 0 to NumChannels - 1 do
    begin
      Result[j][i] := Nodes[NodeIndex].GetAmplitudeAtTime(j, i * TimeStep);
    end;
  end;
end;

procedure PlayVAFile(const FileName: string);
var
  Nodes: TVANodeArray;
  SampleRate, NumChannels: Integer;
  Amplitudes: TMultiChannelAmplitudeArray;
  alsaHandle: Psnd_pcm_t;
  alsaParams: Psnd_pcm_hw_params_t;
  format: snd_pcm_format_t;
  frames: snd_pcm_uframes_t;
  err: Integer;
  dir: Integer;
  rate: Cardinal;
  bufferSize: Integer;
  i, j: Integer;
  Sample: Int16;
  Buffer: array of Int16; // Буфер для передачи данных в ALSA
  BufferSizeInSamples: Integer;
  BufferPos: Integer;
begin
  // Загрузка .va файла
  Nodes := LoadVAFromFile(FileName, SampleRate, NumChannels);

  // Восстановление амплитуд
  Amplitudes := ReconstructAmplitudeArray(Nodes, SampleRate, NumChannels, Length(Nodes) * Round(SampleRate * (1.0 / SampleRate)));

  // Отладочные сообщения
  WriteLn('Частота дискретизации файла: ', SampleRate, ' Гц');
  WriteLn('Количество каналов: ', NumChannels);
  WriteLn('Восстановлено амплитуд: ', Length(Amplitudes[0]));

  // Открываем ALSA устройство
  if snd_pcm_open(@alsaHandle, 'default', SND_PCM_STREAM_PLAYBACK, 0) < 0 then
  begin
    WriteLn('Ошибка открытия ALSA устройства');
    Exit;
  end;

  snd_pcm_hw_params_malloc(@alsaParams);
  snd_pcm_hw_params_any(alsaHandle, alsaParams);

  // Устанавливаем параметры ALSA
  snd_pcm_hw_params_set_access(alsaHandle, alsaParams, SND_PCM_ACCESS_RW_INTERLEAVED);
  snd_pcm_hw_params_set_format(alsaHandle, alsaParams, SND_PCM_FORMAT_S16_LE);
  snd_pcm_hw_params_set_channels(alsaHandle, alsaParams, NumChannels);

  rate := SampleRate; // Используем частоту дискретизации файла
  dir := 0;
  err := snd_pcm_hw_params_set_rate_near(alsaHandle, alsaParams, @rate, @dir);
  if err < 0 then
    WriteLn('Ошибка установки частоты дискретизации: ', snd_strerror(err));
  WriteLn('Используемая частота дискретизации: ', rate, ' Гц');

  // Устанавливаем размер буфера
  frames := 4096;
  err := snd_pcm_hw_params_set_buffer_size_near(alsaHandle, alsaParams, @frames);
  if err < 0 then
    WriteLn('Ошибка установки размера буфера: ', snd_strerror(err));

  err := snd_pcm_hw_params(alsaHandle, alsaParams);
  if err < 0 then
    WriteLn('Ошибка применения параметров: ', snd_strerror(err));

  // Инициализация буфера
  BufferSizeInSamples := 4096 * NumChannels; // Размер буфера в сэмплах
  SetLength(Buffer, BufferSizeInSamples);
  BufferPos := 0;

  // Воспроизведение амплитуд
  for i := 0 to High(Amplitudes[0]) do
  begin
    for j := 0 to NumChannels - 1 do
    begin
      // Нормализация амплитуд
      if Amplitudes[j][i] < -1.0 then Amplitudes[j][i] := -1.0;
      if Amplitudes[j][i] > 1.0 then Amplitudes[j][i] := 1.0;
      Sample := Round(Amplitudes[j][i] * 32767);

      // Запись сэмпла в буфер
      Buffer[BufferPos] := Sample;
      Inc(BufferPos);

      // Если буфер заполнен, отправляем его в ALSA
      if BufferPos >= BufferSizeInSamples then
      begin
        if snd_pcm_writei(alsaHandle, @Buffer[0], frames) < 0 then
        begin
          WriteLn('Ошибка записи в ALSA: ', snd_strerror(err));
          Break;
        end;
        BufferPos := 0; // Сбрасываем позицию буфера
      end;
    end;
  end;

  // Отправляем оставшиеся данные в ALSA
  if BufferPos > 0 then
  begin
    if snd_pcm_writei(alsaHandle, @Buffer[0], BufferPos div NumChannels) < 0 then
    begin
      WriteLn('Ошибка записи в ALSA: ', snd_strerror(err));
    end;
  end;

  // Завершение воспроизведения
  snd_pcm_drain(alsaHandle);
  snd_pcm_close(alsaHandle);
end;

begin
  if ParamCount < 1 then
  begin
    WriteLn('Использование: vaplayer <filename.va>');
    Exit;
  end;
  PlayVAFile(ParamStr(1));
end.
Последний раз редактировалось Alexander 08.03.2025 19:58:01, всего редактировалось 1 раз.
Аватара пользователя
Alexander
энтузиаст
 
Сообщения: 819
Зарегистрирован: 18.12.2005 19:10:00
Откуда: оттуда

Re: Векторный звук. Кодек.

Сообщение Alex2013 » 07.03.2025 19:46:33

Совершенно не в курсе этой темы но звучит интересно . ( а чем обычная "трекерная музыка" не устраивает ? Кстати по идее векторный кодек может записывать звук в один из трекерных форматов (XM,S3M,MOD,STM,IT...) )
Alex2013
долгожитель
 
Сообщения: 3117
Зарегистрирован: 03.04.2013 11:59:44

Re: Векторный звук. Кодек.

Сообщение Alexander » 07.03.2025 20:23:53

Кодек как в векторной графике преобразует звуковой растр в кривые Безье.
И так хранит. Естественно это можно интегрировать и в трекерные форматы, если оптимизировать.
Аватара пользователя
Alexander
энтузиаст
 
Сообщения: 819
Зарегистрирован: 18.12.2005 19:10:00
Откуда: оттуда

Re: Векторный звук. Кодек.

Сообщение RRYTY » 09.03.2025 11:47:48

Декодирование дает исходный файл?
RRYTY
постоялец
 
Сообщения: 242
Зарегистрирован: 25.12.2021 10:00:32

Re: Векторный звук. Кодек.

Сообщение Alexander » 09.03.2025 12:02:18

В задачу это входило, но по факту нет. Некоторые отличия пока есть.
Аватара пользователя
Alexander
энтузиаст
 
Сообщения: 819
Зарегистрирован: 18.12.2005 19:10:00
Откуда: оттуда


Вернуться в Разработки на нашем сайте

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 4

Рейтинг@Mail.ru