Forum > Audio and Video

ZxTune chiptunes player

(1/1)

Guva:
After several days of struggling, with the help of "Мата" and "Изоленты", I finally managed to build ZXTune using CMake.

--- Quote ---(Note: "Мат" is Russian slang for strong swear words, and "изолента" refers to duct tape—often used humorously to imply a rough, improvised solution.)

--- End quote ---
"The chip tune chapter is finally coming to a close."

I'm not releasing the demo yet - it plays perfectly, but I haven't fully figured out what parameters need to be passed to `ZXTune_GetPlayerParameterInt` and `ZXTune_SetPlayerParameterInt` functions. Also wrote a cross-compilation script for Linux.

This project is based on ZXTune version "zxtune-r4310". Regarding Z80 emulation, the project includes most of the original source code, including third-party dependencies. Other third-party emulators (etc.) have been removed (since I already have separate standalone players or don't need them), and I didn't want this player to be larger than necessary (e.g., FLAC, mp3, ogg, sidplayfp, vorbis, xmp support removed). Some unused "boosting" components were also removed, along with RAR support.

Supporting tracker formats such as:
Chip Tracker v1.xx, Digital Music Maker, Digital Studio AY/Covox, Extreme Tracker v1.xx,
ProDigiTracker v0.xx, SQ Digital Tracker, Sample Tracker and other. https://zxart.ee/rus/muzyka/


https://github.com/GuvaCode/zxTunePascal

compiled libraries

https://github.com/GuvaCode/zxTunePascal/releases/download/r4310_V1/binray_windows_linux.zip

Guva:
I wrote an example player, tested only under Linux.


--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---program ZxPlay; {$mode objfpc}{$H+} uses  {$IFDEF UNIX}  cthreads,  {$ENDIF}  Classes, SysUtils, CTypes, raylib, libZxTune, Contnrs, SyncObjs, Crt; const  DEFAULT_FREQ = 44100;  DEFAULT_BITS = 16;  DEFAULT_CHANNELS = 2;  BUFFER_SIZE = 8192;  DEFAULT_FRAME_DURATION = 20000; // 20ms in microseconds  POSITION_UPDATE_INTERVAL = 100; // ms  PROGRESS_BAR_WIDTH = 30; // Ширина полоски прогресса var  ZxTunePlayer: ZXTuneHandle = nil;  ZxTuneData: ZXTuneHandle = nil;  ZxTuneModule: ZXTuneHandle = nil;  ModuleInfo: ZXTuneModuleInfo;  ShouldExit: Boolean = False;  PositionLock: TCriticalSection;  InfoShown: Boolean = False; procedure LoadLibs();var  folder: PChar;begin  folder := '';  {$IFDEF CPUX86_64}     {$IFDEF LINUX}       folder := 'dlls/lin64/';     {$ENDIF}     {$IFDEF WINDOWS}        folder := 'dlls/win64/';     {$ENDIF}  {$ENDIF}  {$IFDEF CPU386}     {$IFDEF LINUX}        folder := 'dlls/lin32/';     {$ENDIF}     {$IFDEF WINDOWS}        folder := 'dlls/win32/';     {$ENDIF}  {$ENDIF}    LoadZXTuneLibrary(folder + libZxTune.library_name);end; procedure LoadModuleFile(const MusicFile: string);var  FileStream: TFileStream;  FFileData: Pointer;  FFileSize: NativeUInt;begin  try    FileStream := TFileStream.Create(MusicFile, fmOpenRead or fmShareDenyWrite);    try      FFileSize := FileStream.Size;      GetMem(FFileData, FFileSize);      FileStream.ReadBuffer(FFileData^, FFileSize);    finally      FileStream.Free;    end;     ZxTuneData := ZXTune_CreateData(FFileData, FFileSize);    if ZxTuneData = nil then      raise Exception.Create('Failed to create ZXTune data');     ZxTuneModule := ZXTune_OpenModule(ZxTuneData);    if ZxTuneModule = nil then      raise Exception.Create('Failed to open ZXTune module');     if not ZXTune_GetModuleInfo(ZxTuneModule, ModuleInfo) then      raise Exception.Create('Failed to get module info');     ZxTunePlayer := ZXTune_CreatePlayer(ZxTuneModule);    if ZxTunePlayer = nil then      raise Exception.Create('Failed to create ZXTune player');  except    on E: Exception do    begin      WriteLn('Error: ', E.Message);      raise;    end;  end;end; function GetPosition: Integer;var  Samples: NativeUInt;  Frequency: Integer;begin  Result := 0;  Frequency := 0;  PositionLock.Enter;  try    if ZxTunePlayer <> nil then    begin      Samples := ZXTune_GetCurrentPosition(ZxTunePlayer);      if not ZXTune_GetPlayerParameterInt(ZxTunePlayer, 'sound.frequency', Frequency) then        Frequency := DEFAULT_FREQ;      Result := Round((Samples / DEFAULT_CHANNELS) / Frequency * 1000)*2;    end;  finally    PositionLock.Leave;  end;end; function GetDuration: Integer;var  FrameDuration: Integer;begin  Result := 0;  PositionLock.Enter;  try    if ZxTuneModule <> nil then    begin      // Get frame duration in microseconds (default to 20ms if not available)      FrameDuration := ZXTune_GetDuration(ZxTunePlayer);      if FrameDuration <= 0 then        FrameDuration := DEFAULT_FRAME_DURATION; // Default 20ms frame duration      // Calculate duration: (frames * frame_duration) / 1000      Result := Round((ModuleInfo.Frames * FrameDuration) / 1000);    end;  finally    PositionLock.Leave;  end;end; procedure FormatTime(ms: Integer; out Minutes, Seconds: Integer);begin  Minutes := ms div 60000;  Seconds := (ms div 1000) mod 60;end;   procedure ShowPosition;var  PosMs, DurationMs: Integer;  PosMin, PosSec: Integer;  DurMin, DurSec: Integer;  ProgressPos: Integer;  ProgressBar: string;  i: Integer;begin  // Выводим информацию о модуле только один раз  if not InfoShown then  begin    ClrScr;    WriteLn('ZX Tune Player. ', ZXTune_GetVersion);    WriteLn('');    WriteLn('Module: ',ExtractFileName(ParamStr(1)));    WriteLn('Tempo: ', ModuleInfo.Frames);    WriteLn('Channels: ', ModuleInfo.Channels);    WriteLn;    InfoShown := True;  end;   PosMs := GetPosition;  DurationMs := GetDuration;   FormatTime(PosMs, PosMin, PosSec);  FormatTime(DurationMs, DurMin, DurSec);   // Рассчитываем позицию для полоски прогресса  if DurationMs > 0 then    ProgressPos := Round((PosMs / DurationMs) * PROGRESS_BAR_WIDTH)  else    ProgressPos := 0;   // Строим полоску прогресса  ProgressBar := '[';  for i := 1 to PROGRESS_BAR_WIDTH do  begin    if i <= ProgressPos then      ProgressBar := ProgressBar + '='    else      ProgressBar := ProgressBar + '-';  end;  ProgressBar := ProgressBar + ']';   // Выводим только изменяемую часть (позицию и прогресс-бар)  GotoXY(1, 7);  // Перемещаем курсор на строку после статической информации  Write(Format('Position: %d:%.2d / %d:%.2d %s',    [PosMin, PosSec, DurMin, DurSec, ProgressBar]), '      '); // Добавляем пробелы для очисткиend;   procedure FillAudio(bufferData: Pointer; frames: LongWord); cdecl;begin   ZXTune_RenderSound(ZxTunePlayer, bufferData, frames);end; procedure PlayFile(Filename: string);var  Stream: TAudioStream;  LastUpdate: QWord;begin  InitAudioDevice();  SetAudioStreamBufferSizeDefault(BUFFER_SIZE);   Stream := LoadAudioStream(DEFAULT_FREQ, DEFAULT_BITS, DEFAULT_CHANNELS);  if not IsAudioStreamValid(Stream) then    raise Exception.Create('Failed to initialize audio stream');   SetAudioStreamCallback(Stream, @FillAudio);  PlayAudioStream(Stream);   LoadModuleFile(Filename);   LastUpdate := GetTickCount64;  while not ShouldExit do  begin    if IsAudioStreamProcessed(Stream) then      ResumeAudioStream(Stream);     if GetTickCount64 - LastUpdate >= POSITION_UPDATE_INTERVAL then    begin       ShowPosition;      LastUpdate := GetTickCount64;    end;     if KeyPressed then    begin      ReadKey;      ShouldExit := True;    end;     Sleep(1);  end;   StopAudioStream(Stream);  CloseAudioDevice;   ZXTune_CloseData(ZxTuneData);  ZXTune_CloseModule(ZxTuneModule);  ZXTune_DestroyPlayer(ZxTunePlayer);  WriteLn;end; begin  PositionLock := TCriticalSection.Create;  ExitCode := 1;  LoadLibs;   if ParamCount = 1 then  begin    if SysUtils.FileExists(ParamStr(1)) then    begin      PlayFile(ParamStr(1));      ExitCode := 0;    end    else      WriteLn('File ', ParamStr(1), ' does not exist');  end  else    WriteLn('Usage: ', ExtractFileName(ParamStr(0)), ' <supported file (pt2, pt3, etc.)>');  PositionLock.Free;end.  

Gigatron:
Good work @guva , and many thanx , we can now listen more and more module file type format !

Also, the site you mention is excellent!! I've been listening to chip music for hours.

https://zxart.ee/eng/mainpage/

Guva:

--- Quote from: Gigatron on August 14, 2025, 09:15:26 pm ---Good work @guva , and many thanx , we can now listen more and more module file type format !
https://zxart.ee/eng/mainpage/

--- End quote ---
Tnx :)

I've been working in C code for a few days and I've added a few functions. The position is now displayed correctly.

--- Code: C  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---ZXTUNE_API long ZXTune_GetDuration(ZXTuneHandle player);ZXTUNE_API size_t ZXTune_GetCurrentPosition(ZXTuneHandle player);  ZXTUNE_API long ZXTune_GetDurationMs(ZXTuneHandle player, const ZXTuneModuleInfo* info);ZXTUNE_API long ZXTune_GetPositionMs(ZXTuneHandle player, const ZXTuneModuleInfo* moduleInfo); ZXTUNE_API long ZXTune_GetPlayerLoopTrack(ZXTuneHandle player);ZXTUNE_API bool ZXTune_SetPlayerLoopTrack(ZXTuneHandle player, int paramValue); ZXTUNE_API long ZXTune_GetSoundFrequency(ZXTuneHandle player); ZXTUNE_API bool ZXTune_SetDoneSamples(ZXTuneHandle player, const ZXTuneModuleInfo* moduleInfo); 
It remains to figure out why the library is not loaded in windows :(

Navigation

[0] Message Index

Go to full version