From 9939ed0517c7ee1502deed1b4a514fe8d97711e3 Mon Sep 17 00:00:00 2001 From: Thomas Buck Date: Tue, 13 Feb 2024 23:38:36 +0100 Subject: [PATCH] misguided attempt at image rendering --- .gitignore | 1 + render.py | 99 ++++++++++++++++++++++++++++++++++++++++++++++++++++ toolbox.png | Bin 0 -> 8518 bytes 3 files changed, 100 insertions(+) create mode 100644 .gitignore create mode 100755 render.py create mode 100644 toolbox.png 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 0000000000000000000000000000000000000000..ec15986ade23b8667da6a994a9e19fe3c86f6650 GIT binary patch literal 8518 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