Recent

Author Topic: ZxTune chiptunes player  (Read 1290 times)

Guva

  • Full Member
  • ***
  • Posts: 198
  • 🌈 ZX-Spectrum !!!
ZxTune chiptunes player
« on: August 13, 2025, 06:50:37 pm »
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.)
"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
« Last Edit: August 13, 2025, 06:53:03 pm by Guva »

Guva

  • Full Member
  • ***
  • Posts: 198
  • 🌈 ZX-Spectrum !!!
Re: ZxTune chiptunes player
« Reply #1 on: August 14, 2025, 07:03:20 am »
I wrote an example player, tested only under Linux.

Code: Pascal  [Select][+][-]
  1. program ZxPlay;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. uses
  6.   {$IFDEF UNIX}
  7.   cthreads,
  8.   {$ENDIF}
  9.   Classes, SysUtils, CTypes, raylib, libZxTune, Contnrs, SyncObjs, Crt;
  10.  
  11. const
  12.   DEFAULT_FREQ = 44100;
  13.   DEFAULT_BITS = 16;
  14.   DEFAULT_CHANNELS = 2;
  15.   BUFFER_SIZE = 8192;
  16.   DEFAULT_FRAME_DURATION = 20000; // 20ms in microseconds
  17.   POSITION_UPDATE_INTERVAL = 100; // ms
  18.   PROGRESS_BAR_WIDTH = 30; // Ширина полоски прогресса
  19.  
  20. var
  21.   ZxTunePlayer: ZXTuneHandle = nil;
  22.   ZxTuneData: ZXTuneHandle = nil;
  23.   ZxTuneModule: ZXTuneHandle = nil;
  24.   ModuleInfo: ZXTuneModuleInfo;
  25.   ShouldExit: Boolean = False;
  26.   PositionLock: TCriticalSection;
  27.   InfoShown: Boolean = False;
  28.  
  29. procedure LoadLibs();
  30. var
  31.   folder: PChar;
  32. begin
  33.   folder := '';
  34.   {$IFDEF CPUX86_64}
  35.      {$IFDEF LINUX}
  36.        folder := 'dlls/lin64/';
  37.      {$ENDIF}
  38.      {$IFDEF WINDOWS}
  39.  
  40.        folder := 'dlls/win64/';
  41.      {$ENDIF}
  42.   {$ENDIF}
  43.   {$IFDEF CPU386}
  44.      {$IFDEF LINUX}
  45.         folder := 'dlls/lin32/';
  46.      {$ENDIF}
  47.      {$IFDEF WINDOWS}
  48.         folder := 'dlls/win32/';
  49.      {$ENDIF}
  50.   {$ENDIF}
  51.  
  52.  
  53.   LoadZXTuneLibrary(folder + libZxTune.library_name);
  54. end;
  55.  
  56. procedure LoadModuleFile(const MusicFile: string);
  57. var
  58.   FileStream: TFileStream;
  59.   FFileData: Pointer;
  60.   FFileSize: NativeUInt;
  61. begin
  62.   try
  63.     FileStream := TFileStream.Create(MusicFile, fmOpenRead or fmShareDenyWrite);
  64.     try
  65.       FFileSize := FileStream.Size;
  66.       GetMem(FFileData, FFileSize);
  67.       FileStream.ReadBuffer(FFileData^, FFileSize);
  68.     finally
  69.       FileStream.Free;
  70.     end;
  71.  
  72.     ZxTuneData := ZXTune_CreateData(FFileData, FFileSize);
  73.     if ZxTuneData = nil then
  74.       raise Exception.Create('Failed to create ZXTune data');
  75.  
  76.     ZxTuneModule := ZXTune_OpenModule(ZxTuneData);
  77.     if ZxTuneModule = nil then
  78.       raise Exception.Create('Failed to open ZXTune module');
  79.  
  80.     if not ZXTune_GetModuleInfo(ZxTuneModule, ModuleInfo) then
  81.       raise Exception.Create('Failed to get module info');
  82.  
  83.     ZxTunePlayer := ZXTune_CreatePlayer(ZxTuneModule);
  84.     if ZxTunePlayer = nil then
  85.       raise Exception.Create('Failed to create ZXTune player');
  86.   except
  87.     on E: Exception do
  88.     begin
  89.       WriteLn('Error: ', E.Message);
  90.       raise;
  91.     end;
  92.   end;
  93. end;
  94.  
  95. function GetPosition: Integer;
  96. var
  97.   Samples: NativeUInt;
  98.   Frequency: Integer;
  99. begin
  100.   Result := 0;
  101.   Frequency := 0;
  102.   PositionLock.Enter;
  103.   try
  104.     if ZxTunePlayer <> nil then
  105.     begin
  106.       Samples := ZXTune_GetCurrentPosition(ZxTunePlayer);
  107.       if not ZXTune_GetPlayerParameterInt(ZxTunePlayer, 'sound.frequency', Frequency) then
  108.         Frequency := DEFAULT_FREQ;
  109.       Result := Round((Samples / DEFAULT_CHANNELS) / Frequency * 1000)*2;
  110.     end;
  111.   finally
  112.     PositionLock.Leave;
  113.   end;
  114. end;
  115.  
  116. function GetDuration: Integer;
  117. var
  118.   FrameDuration: Integer;
  119. begin
  120.   Result := 0;
  121.   PositionLock.Enter;
  122.   try
  123.     if ZxTuneModule <> nil then
  124.     begin
  125.       // Get frame duration in microseconds (default to 20ms if not available)
  126.       FrameDuration := ZXTune_GetDuration(ZxTunePlayer);
  127.       if FrameDuration <= 0 then
  128.         FrameDuration := DEFAULT_FRAME_DURATION; // Default 20ms frame duration
  129.       // Calculate duration: (frames * frame_duration) / 1000
  130.       Result := Round((ModuleInfo.Frames * FrameDuration) / 1000);
  131.     end;
  132.   finally
  133.     PositionLock.Leave;
  134.   end;
  135. end;
  136.  
  137. procedure FormatTime(ms: Integer; out Minutes, Seconds: Integer);
  138. begin
  139.   Minutes := ms div 60000;
  140.   Seconds := (ms div 1000) mod 60;
  141. end;
  142.  
  143.  
  144.  
  145. procedure ShowPosition;
  146. var
  147.   PosMs, DurationMs: Integer;
  148.   PosMin, PosSec: Integer;
  149.   DurMin, DurSec: Integer;
  150.   ProgressPos: Integer;
  151.   ProgressBar: string;
  152.   i: Integer;
  153. begin
  154.   // Выводим информацию о модуле только один раз
  155.   if not InfoShown then
  156.   begin
  157.     ClrScr;
  158.     WriteLn('ZX Tune Player. ', ZXTune_GetVersion);
  159.     WriteLn('');
  160.     WriteLn('Module: ',ExtractFileName(ParamStr(1)));
  161.     WriteLn('Tempo: ', ModuleInfo.Frames);
  162.     WriteLn('Channels: ', ModuleInfo.Channels);
  163.     WriteLn;
  164.     InfoShown := True;
  165.   end;
  166.  
  167.   PosMs := GetPosition;
  168.   DurationMs := GetDuration;
  169.  
  170.   FormatTime(PosMs, PosMin, PosSec);
  171.   FormatTime(DurationMs, DurMin, DurSec);
  172.  
  173.   // Рассчитываем позицию для полоски прогресса
  174.   if DurationMs > 0 then
  175.     ProgressPos := Round((PosMs / DurationMs) * PROGRESS_BAR_WIDTH)
  176.   else
  177.     ProgressPos := 0;
  178.  
  179.   // Строим полоску прогресса
  180.   ProgressBar := '[';
  181.   for i := 1 to PROGRESS_BAR_WIDTH do
  182.   begin
  183.     if i <= ProgressPos then
  184.       ProgressBar := ProgressBar + '='
  185.     else
  186.       ProgressBar := ProgressBar + '-';
  187.   end;
  188.   ProgressBar := ProgressBar + ']';
  189.  
  190.   // Выводим только изменяемую часть (позицию и прогресс-бар)
  191.   GotoXY(1, 7);  // Перемещаем курсор на строку после статической информации
  192.   Write(Format('Position: %d:%.2d / %d:%.2d %s',
  193.     [PosMin, PosSec, DurMin, DurSec, ProgressBar]), '      '); // Добавляем пробелы для очистки
  194. end;
  195.  
  196.  
  197.  
  198. procedure FillAudio(bufferData: Pointer; frames: LongWord); cdecl;
  199. begin
  200.    ZXTune_RenderSound(ZxTunePlayer, bufferData, frames);
  201. end;
  202.  
  203. procedure PlayFile(Filename: string);
  204. var
  205.   Stream: TAudioStream;
  206.   LastUpdate: QWord;
  207. begin
  208.   InitAudioDevice();
  209.   SetAudioStreamBufferSizeDefault(BUFFER_SIZE);
  210.  
  211.   Stream := LoadAudioStream(DEFAULT_FREQ, DEFAULT_BITS, DEFAULT_CHANNELS);
  212.   if not IsAudioStreamValid(Stream) then
  213.     raise Exception.Create('Failed to initialize audio stream');
  214.  
  215.   SetAudioStreamCallback(Stream, @FillAudio);
  216.   PlayAudioStream(Stream);
  217.  
  218.   LoadModuleFile(Filename);
  219.  
  220.   LastUpdate := GetTickCount64;
  221.   while not ShouldExit do
  222.   begin
  223.     if IsAudioStreamProcessed(Stream) then
  224.       ResumeAudioStream(Stream);
  225.  
  226.     if GetTickCount64 - LastUpdate >= POSITION_UPDATE_INTERVAL then
  227.     begin
  228.  
  229.       ShowPosition;
  230.       LastUpdate := GetTickCount64;
  231.     end;
  232.  
  233.     if KeyPressed then
  234.     begin
  235.       ReadKey;
  236.       ShouldExit := True;
  237.     end;
  238.  
  239.     Sleep(1);
  240.   end;
  241.  
  242.   StopAudioStream(Stream);
  243.   CloseAudioDevice;
  244.  
  245.   ZXTune_CloseData(ZxTuneData);
  246.   ZXTune_CloseModule(ZxTuneModule);
  247.   ZXTune_DestroyPlayer(ZxTunePlayer);
  248.   WriteLn;
  249. end;
  250.  
  251. begin
  252.   PositionLock := TCriticalSection.Create;
  253.   ExitCode := 1;
  254.   LoadLibs;
  255.  
  256.   if ParamCount = 1 then
  257.   begin
  258.     if SysUtils.FileExists(ParamStr(1)) then
  259.     begin
  260.       PlayFile(ParamStr(1));
  261.       ExitCode := 0;
  262.     end
  263.     else
  264.       WriteLn('File ', ParamStr(1), ' does not exist');
  265.   end
  266.   else
  267.     WriteLn('Usage: ', ExtractFileName(ParamStr(0)), ' <supported file (pt2, pt3, etc.)>');
  268.   PositionLock.Free;
  269. end.
  270.  
  271.  

Gigatron

  • Sr. Member
  • ****
  • Posts: 334
  • Amiga Rulez !!
Re: ZxTune chiptunes player
« Reply #2 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 !

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

https://zxart.ee/eng/mainpage/
« Last Edit: August 14, 2025, 10:14:37 pm by Gigatron »
Trip to Europe...  20 days

Guva

  • Full Member
  • ***
  • Posts: 198
  • 🌈 ZX-Spectrum !!!
Re: ZxTune chiptunes player
« Reply #3 on: August 17, 2025, 08:40:38 pm »
Good work @guva , and many thanx , we can now listen more and more module file type format !
https://zxart.ee/eng/mainpage/
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  [Select][+][-]
  1. ZXTUNE_API long ZXTune_GetDuration(ZXTuneHandle player);
  2. ZXTUNE_API size_t ZXTune_GetCurrentPosition(ZXTuneHandle player);
  3.  
  4. ZXTUNE_API long ZXTune_GetDurationMs(ZXTuneHandle player, const ZXTuneModuleInfo* info);
  5. ZXTUNE_API long ZXTune_GetPositionMs(ZXTuneHandle player, const ZXTuneModuleInfo* moduleInfo);
  6.  
  7. ZXTUNE_API long ZXTune_GetPlayerLoopTrack(ZXTuneHandle player);
  8. ZXTUNE_API bool ZXTune_SetPlayerLoopTrack(ZXTuneHandle player, int paramValue);
  9.  
  10. ZXTUNE_API long ZXTune_GetSoundFrequency(ZXTuneHandle player);
  11.  
  12. ZXTUNE_API bool ZXTune_SetDoneSamples(ZXTuneHandle player, const ZXTuneModuleInfo* moduleInfo);
  13.  

It remains to figure out why the library is not loaded in windows :(
« Last Edit: August 17, 2025, 08:43:04 pm by Guva »

 

TinyPortal © 2005-2018