А 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.