Recent

Author Topic: ZxTune chiptunes player  (Read 1931 times)

Guva

  • Full Member
  • ***
  • Posts: 220
  • 🌈 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: 220
  • 🌈 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: 426
  • Amiga Rulez !!
    • Gigatron Shader Network Demo
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 »
Coding faster than Light !  https://www.youtube.com/@gtrgtr9505

Guva

  • Full Member
  • ***
  • Posts: 220
  • 🌈 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 »

Guva

  • Full Member
  • ***
  • Posts: 220
  • 🌈 ZX-Spectrum !!!
Re: ZxTune chiptunes player
« Reply #4 on: June 16, 2026, 12:41:14 pm »
libzxtune
Since the author removed the creation of the library in the new versions of zxtune, I decided to fix this and added the ability to build separately. libzxtune.so/dll.
Shared library (.so / .dll) for playing chiptune and game music modules, built on top of the zxtune-develop engine.



Features

  • 60+ music formats (ZX Spectrum, NES, SNES, Amiga, C64, GBA, Atari, and more)
  • Simple C API with opaque handles
    • 16-bit stereo PCM output at configurable sample rate
    • Thread-safe playback from audio callbacks
    • Pascal/Delphi bindings included



Building

Linux

[/list]
Code: Bash  [Select][+][-]
  1. cd apps/libzxtune
  2. ./lin64_build.sh
  3.  

Or manually:

Code: Bash  [Select][+][-]
  1. cd apps/libzxtune
  2. make platform=linux compiler=gcc -j$(nproc)
  3.  

Windows (MinGW cross-compile)

Code: Bash  [Select][+][-]
  1. cd apps/libzxtune
  2. ./win64_build.sh
  3.  

Output


bin/linux/release/libzxtune.so    (Linux)
bin/linux/release/libzxtune.so.pdb (debug symbols)




Quick Start (C)

Code: C  [Select][+][-]
  1. #include "zxtune_api.h"
  2.  
  3. // Load file
  4. FILE *f = fopen("music.pt3", "rb");
  5. fseek(f, 0, SEEK_END);
  6. size_t size = ftell(f);
  7. fseek(f, 0, SEEK_SET);
  8. void *data = malloc(size);
  9. fread(data, 1, size, f);
  10. fclose(f);
  11.  
  12. // Open
  13. ZXTuneHandle hData    = ZXTune_CreateData(data, size);
  14. ZXTuneHandle hModule  = ZXTune_OpenModule(hData);
  15. ZXTuneHandle hPlayer  = ZXTune_CreatePlayer(hModule, 44100);
  16.  
  17. // Render audio
  18. int16_t buffer[4096 * 2];  // stereo
  19. int samples = ZXTune_RenderSound(hPlayer, buffer, 4096);
  20. // buffer now contains 16-bit stereo PCM
  21.  
  22. // Cleanup
  23. ZXTune_DestroyPlayer(hPlayer);
  24. ZXTune_CloseModule(hModule);
  25. ZXTune_CloseData(hData);
  26. free(data);
  27.  



Quick Start (Pascal / Delphi)

Code: Pascal  [Select][+][-]
  1. uses ZXTune;
  2.  
  3. var
  4.   data, mod, player: TZXTuneHandle;
  5.   info: TZXTuneModuleInfo;
  6.   buf: array[0..4095] of SmallInt;
  7. begin
  8.   // Load and open (see examples for full code)
  9.   data := ZXTune_CreateData(@fileBytes[0], fileSize);
  10.   mod := ZXTune_OpenModule(data);
  11.   player := ZXTune_CreatePlayer(mod, 44100);
  12.  
  13.   // Get info
  14.   ZXTune_GetModuleInfo(mod, @info);
  15.   WriteLn('Duration: ', info.DurationMs, ' ms');
  16.  
  17.   // Render
  18.   ZXTune_RenderSound(player, @buf[0], 4096);
  19.  
  20.   // Cleanup
  21.   ZXTune_DestroyPlayer(player);
  22.   ZXTune_CloseModule(mod);
  23.   ZXTune_CloseData(data);
  24. end.
  25.  



Raylib Integration

See examples/ray4laz/zxplay.pas for a complete example with raylib audio streaming.

Key pattern:

Code: Pascal  [Select][+][-]
  1. procedure FillAudio(bufferData: Pointer; frames: LongWord); cdecl;
  2. begin
  3.   ZXTune_RenderSound(player, bufferData, frames);
  4. end;
  5.  
  6. // Create player BEFORE audio init
  7. player := ZXTune_CreatePlayer(module, 44100);
  8.  
  9. // Then init audio and set callback
  10. InitAudioDevice;
  11. stream := LoadAudioStream(44100, 16, 2);
  12. SetAudioStreamCallback(stream, @FillAudio);
  13. PlayAudioStream(stream);
  14.  


    API Reference

    See command.md for complete API documentation.

    Core Functions

    FunctionDescription
    ZXTune_GetVersion()Library version string
    ZXTune_CreateData(data, size)Load raw file bytes
    ZXTune_OpenModule(data)Detect and open music module
    ZXTune_CreatePlayer(module, sampleRate)Create player (typically 44100 Hz)
    ZXTune_RenderSound(player, buffer, samples)Render PCM to buffer
    ZXTune_SeekSound(player, ms)Seek to position
    ZXTune_ResetSound(player)Reset to beginning
    ZXTune_GetPositionMs(player)Current position in ms
    ZXTune_GetDurationMs(player)Total duration in ms
    ZXTune_GetModuleAttribute(module, attr, buf, size)Read module metadata

    Audio Format

    • Sample rate: 44100 Hz (configurable)
    • Bit depth: 16-bit signed
    • Channels: 2 (stereo interleaved: L,R,L,R,...)
    • Buffer: samples = number of stereo frames (NOT individual int16 values)



    Supported Formats

    See format.md for the full list.

    ZX Spectrum

    PT3, PT2, STC, ST1, ST3, ASC, VTX, AY, PSG, TS, TXT, CHI, SQT, PSC, FTC, TFD, TFC, TFE, ...

    Nintendo

    NSF (NES), SPC (SNES), GSF (GBA), 2SF/NCSF (NDS), 64 (N64), GBS (Game Boy), SGB

    Commodore

    SID (C64), AHX

    Sega

    VGM/VGZ, SGC (SMS/GG), SDSF (Saturn)

    Atari

    SAP, CMC, CMR, DMC, DMX, MPT, RMT, TMC, TM8, TM2

    Multi-platform

    MOD, XM, IT, S3M (via libxmp/OpenMPT), MP3, OGG, FLAC, WAV, V2M

    Archives

    ZIP, RAR, LHA



    Module Attributes

    [/list][/list][/list]
    Code: C  [Select][+][-]
    1. char buf[256];
    2. ZXTune_GetModuleAttribute(mod, "Title",   buf, sizeof(buf));  // "Olson of bOc"
    3. ZXTune_GetModuleAttribute(mod, "Author",  buf, sizeof(buf));  // "dvsn"
    4. ZXTune_GetModuleAttribute(mod, "Type",    buf, sizeof(buf));  // "PT3"
    5. ZXTune_GetModuleAttribute(mod, "Program", buf, sizeof(buf));  // tracker name
    6.  



    Player Parameters

    Code: C  [Select][+][-]
    1. // Loop playback
    2. ZXTune_SetPlayerParameterInt(player, "zxtune.sound.looped", 1);
    3.  
    4. // Volume (100 = normal)
    5. ZXTune_SetPlayerParameterInt(player, "zxtune.sound.gain", 80);
    6.  
    7. // AYM clock rate
    8. ZXTune_SetPlayerParameterInt(player, "zxtune.core.aym.clockrate", 1750000);
    9.  

    See command.md for all parameters.



    Project Structure


    apps/libzxtune/
      zxtune_api.h            # C API header
      src/zxtune.cpp          # Implementation
      libzxtune.version       # Linker version script
      Makefile                # Build system
      lin64_build.sh          # Linux build script
      win64_build.sh          # Windows (MinGW) build script
      pascal/ZXTune.pas       # Pascal/Delphi bindings
      examples/ray4laz/       # Raylib examples
      command.md              # API reference
      format.md               # Supported formats




    License

    Same as zxtune-develop.
    « Last Edit: June 16, 2026, 12:45:57 pm by Guva »

     

    TinyPortal © 2005-2018