Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- local speaker = peripheral.find("speaker")
- if not speaker then
- print("No speaker attached!")
- return
- end
- -- Prompt for filename
- write("Enter PCM filename: ")
- local filename = read()
- -- Ask if file is signed or unsigned
- write("Is your file unsigned 8-bit PCM? (y/n): ")
- local isUnsigned = read():lower() == "y"
- -- Prompt for input sample rate
- write("Enter input sample rate (Hz, e.g. 44100): ")
- local inputSampleRate = tonumber(read())
- if not inputSampleRate or inputSampleRate < 1000 or inputSampleRate > 192000 then
- print("Invalid sample rate. Using 44100 Hz.")
- inputSampleRate = 44100
- end
- -- Prompt for volume (0.0 to 1.0)
- write("Enter playback volume (0.0 - 1.0, default 0.2): ")
- local volInput = read()
- local volume = tonumber(volInput)
- if not volume or volume < 0 or volume > 1 then
- volume = 0.2 -- default safe volume
- end
- -- Optional: DC offset removal
- write("Remove DC offset? (y/n, default n): ")
- local dcInput = read()
- local removeDC = dcInput:lower() == "y"
- -- Open file in binary mode
- local file = fs.open(filename, "rb")
- if not file then
- print("Could not open file: " .. filename)
- return
- end
- -- Read all samples into memory (for resampling)
- local samples = {}
- local sum = 0
- local count = 0
- while true do
- local byte = file.read()
- if not byte then break end
- if isUnsigned then
- byte = byte - 128 -- Convert unsigned (0-255) to signed (-128 to 127)
- end
- samples[#samples + 1] = byte
- sum = sum + byte
- count = count + 1
- end
- file.close()
- if #samples == 0 then
- print("File is empty or unreadable.")
- return
- end
- -- Remove DC offset if requested
- if removeDC then
- local avg = sum / count
- for i = 1, #samples do
- samples[i] = samples[i] - avg
- end
- print("DC offset removed (average sample value: " .. math.floor(avg) .. ")")
- end
- -- Resample to 48000 Hz (CraftOS-PC speaker expects 48kHz 8-bit signed PCM)
- local outputSampleRate = 48000
- if inputSampleRate ~= outputSampleRate then
- local resampled = {}
- local ratio = inputSampleRate / outputSampleRate
- for i = 1, math.floor(#samples / ratio) do
- -- Linear interpolation for smoother sound
- local src = (i - 1) * ratio + 1
- local idx = math.floor(src)
- local frac = src - idx
- local s1 = samples[idx] or 0
- local s2 = samples[idx + 1] or s1
- resampled[i] = s1 + (s2 - s1) * frac
- end
- samples = resampled
- print("Resampled to 48kHz for optimal playback.")
- end
- -- Chunked playback with volume scaling and clamping
- local chunkSize = 128 * 1024 -- Max buffer size per playAudio call
- local totalSamples = #samples
- local playedSamples = 0
- while playedSamples < totalSamples do
- local chunk = {}
- for i = 1, chunkSize do
- local idx = playedSamples + i
- if idx > totalSamples then break end
- -- Volume scaling and clamping
- local sample = math.floor(samples[idx] * volume + 0.5)
- if sample > 127 then sample = 127 end
- if sample < -128 then sample = -128 end
- chunk[i] = sample
- end
- if #chunk == 0 then break end
- while not speaker.playAudio(chunk) do
- os.pullEvent("speaker_audio_empty")
- end
- playedSamples = playedSamples + #chunk
- end
- print("Done playing " .. filename)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement