diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d8dd753 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.wav diff --git a/render.py b/render.py new file mode 100755 index 0000000..3e3c46e --- /dev/null +++ b/render.py @@ -0,0 +1,99 @@ +#!/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() diff --git a/toolbox.png b/toolbox.png new file mode 100644 index 0000000..ec15986 Binary files /dev/null and b/toolbox.png differ