Advertisement
nonogamer9

PCM For ComputerCraft

Apr 18th, 2025
285
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 3.30 KB | Software | 0 0
  1. local speaker = peripheral.find("speaker")
  2. if not speaker then
  3.     print("No speaker attached!")
  4.     return
  5. end
  6.  
  7. -- Prompt for filename
  8. write("Enter PCM filename: ")
  9. local filename = read()
  10.  
  11. -- Ask if file is signed or unsigned
  12. write("Is your file unsigned 8-bit PCM? (y/n): ")
  13. local isUnsigned = read():lower() == "y"
  14.  
  15. -- Prompt for input sample rate
  16. write("Enter input sample rate (Hz, e.g. 44100): ")
  17. local inputSampleRate = tonumber(read())
  18. if not inputSampleRate or inputSampleRate < 1000 or inputSampleRate > 192000 then
  19.     print("Invalid sample rate. Using 44100 Hz.")
  20.     inputSampleRate = 44100
  21. end
  22.  
  23. -- Prompt for volume (0.0 to 1.0)
  24. write("Enter playback volume (0.0 - 1.0, default 0.2): ")
  25. local volInput = read()
  26. local volume = tonumber(volInput)
  27. if not volume or volume < 0 or volume > 1 then
  28.     volume = 0.2 -- default safe volume
  29. end
  30.  
  31. -- Optional: DC offset removal
  32. write("Remove DC offset? (y/n, default n): ")
  33. local dcInput = read()
  34. local removeDC = dcInput:lower() == "y"
  35.  
  36. -- Open file in binary mode
  37. local file = fs.open(filename, "rb")
  38. if not file then
  39.     print("Could not open file: " .. filename)
  40.     return
  41. end
  42.  
  43. -- Read all samples into memory (for resampling)
  44. local samples = {}
  45. local sum = 0
  46. local count = 0
  47.  
  48. while true do
  49.     local byte = file.read()
  50.     if not byte then break end
  51.     if isUnsigned then
  52.         byte = byte - 128 -- Convert unsigned (0-255) to signed (-128 to 127)
  53.     end
  54.     samples[#samples + 1] = byte
  55.     sum = sum + byte
  56.     count = count + 1
  57. end
  58. file.close()
  59.  
  60. if #samples == 0 then
  61.     print("File is empty or unreadable.")
  62.     return
  63. end
  64.  
  65. -- Remove DC offset if requested
  66. if removeDC then
  67.     local avg = sum / count
  68.     for i = 1, #samples do
  69.         samples[i] = samples[i] - avg
  70.     end
  71.     print("DC offset removed (average sample value: " .. math.floor(avg) .. ")")
  72. end
  73.  
  74. -- Resample to 48000 Hz (CraftOS-PC speaker expects 48kHz 8-bit signed PCM)
  75. local outputSampleRate = 48000
  76. if inputSampleRate ~= outputSampleRate then
  77.     local resampled = {}
  78.     local ratio = inputSampleRate / outputSampleRate
  79.     for i = 1, math.floor(#samples / ratio) do
  80.         -- Linear interpolation for smoother sound
  81.         local src = (i - 1) * ratio + 1
  82.         local idx = math.floor(src)
  83.         local frac = src - idx
  84.         local s1 = samples[idx] or 0
  85.         local s2 = samples[idx + 1] or s1
  86.         resampled[i] = s1 + (s2 - s1) * frac
  87.     end
  88.     samples = resampled
  89.     print("Resampled to 48kHz for optimal playback.")
  90. end
  91.  
  92. -- Chunked playback with volume scaling and clamping
  93. local chunkSize = 128 * 1024  -- Max buffer size per playAudio call
  94. local totalSamples = #samples
  95. local playedSamples = 0
  96.  
  97. while playedSamples < totalSamples do
  98.     local chunk = {}
  99.     for i = 1, chunkSize do
  100.         local idx = playedSamples + i
  101.         if idx > totalSamples then break end
  102.         -- Volume scaling and clamping
  103.         local sample = math.floor(samples[idx] * volume + 0.5)
  104.         if sample > 127 then sample = 127 end
  105.         if sample < -128 then sample = -128 end
  106.         chunk[i] = sample
  107.     end
  108.     if #chunk == 0 then break end
  109.     while not speaker.playAudio(chunk) do
  110.         os.pullEvent("speaker_audio_empty")
  111.     end
  112.     playedSamples = playedSamples + #chunk
  113. end
  114.  
  115. print("Done playing " .. filename)
  116.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement