#!/usr/bin/env python3 # Render image to Oscilloscope XY vector audio # # ---------------------------------------------------------------------------- # Copyright (c) 2024 Thomas Buck (thomas@xythobuz.de) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # See . # ---------------------------------------------------------------------------- import sys import wave import pyaudio from PIL import Image samplerate = 44100 #192000 default_duration = 5.0 def read_image(image): print("image: width={} height={} total={}".format(image.width, image.height, image.width * image.height)) # resize coordinates for conversion to amplitude values max_len = max(image.width, image.height) fact = 32767 / max_len sw, sh = int(image.width * fact), int(image.height * fact) print("amplitude: width={} height={}".format(sw, sh)) data = bytearray() for x in range(0, image.width): for y in range(0, image.height): if image.getpixel((x, y))[3] > 127: xc, yc = int((x - (image.width / 2)) * fact), int((y - (image.height / 2)) * fact) data.extend(yc.to_bytes(2, byteorder="little", signed=True)) data.extend(xc.to_bytes(2, byteorder="little", signed=True)) return data def play_waveform(data): pa = pyaudio.PyAudio() # int16 stream = pa.open(format=pa.get_format_from_width(2), channels=2, rate=samplerate, output=True) stream.write(data, int(len(data) / 4), True) stream.stop_stream() stream.close() pa.terminate() def write_waveform(data): with wave.open("out.wav", "w") as f: f.setnchannels(2) f.setsampwidth(2) f.setframerate(samplerate) f.writeframes(data) def main(): if len(sys.argv) <= 1: print("Usage:") print("\t" + sys.argv[0] + " image.png [seconds]") sys.exit(1) if len(sys.argv) >= 3: duration = float(sys.argv[2]) else: duration = default_duration with Image.open(sys.argv[1]) as image: wave = read_image(image) samplecount = int(len(wave) / 2 / 2) # stereo, int16 drawrate = samplerate / samplecount drawcount = drawrate * duration print("len={} samples={} drawrate={:.2f} count={:.2f}".format(len(wave), samplecount, drawrate, drawcount)) data = bytearray() for n in range(0, int(drawcount)): data.extend(wave) print("len={}".format(len(data))) #play_waveform(bytes(data)) write_waveform(bytes(data)) if __name__ == "__main__": main()