{"id":17992,"date":"2025-11-12T23:01:23","date_gmt":"2025-11-13T04:01:23","guid":{"rendered":"https:\/\/scruss.com\/blog\/?p=17992"},"modified":"2025-11-13T07:11:32","modified_gmt":"2025-11-13T12:11:32","slug":"mz2synth-make-sounds-from-images","status":"publish","type":"post","link":"https:\/\/scruss.com\/blog\/2025\/11\/12\/mz2synth-make-sounds-from-images\/","title":{"rendered":"mz2synth: make sounds from images"},"content":{"rendered":"\n<p>E. Lamprecht&#8217;s <a href=\"https:\/\/github.com\/frankenbeans\/MZ2SYNTH\">MZ2SYNTH<\/a> is a delightfully weird piece of code. It is an advanced wavetable synthesizer programmed only by an input image. Here&#8217;s an example:<\/p>\n\n\n\n<figure class=\"wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-4-3 wp-has-aspect-ratio\"><div class=\"wp-block-embed__wrapper\">\n<iframe loading=\"lazy\" title=\"mz2synth example 01\" width=\"500\" height=\"375\" src=\"https:\/\/www.youtube.com\/embed\/vxz2uZin2dg?feature=oembed\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe>\n<\/div><\/figure>\n\n\n\n<p>Documentation is pretty sparse, so I&#8217;ve had to work it out as best I can:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>input data must be a 720 px high NetPBM PPM or PGM image with a black background<\/li>\n\n\n\n<li>waveforms are specified by pixel colour: sine, square, sawtooth and triangle are red, green, blue and luminance<\/li>\n\n\n\n<li>dynamics are manipulated by changing the pixel brightness<\/li>\n\n\n\n<li>the input plays at a constant rate along the horizontal pixels, defaulting to 10 pixels\/second<\/li>\n\n\n\n<li>The pitch is specified by the Y coordinate. To convert from MIDI note number <em>n<\/em> to an input coordinate for mz2synth, use this formula:<br>y=6\u00d7(140 &#8211; n)<br>So for Middle C (MIDI note 60), the Y coordinate would be 480.<\/li>\n<\/ol>\n\n\n\n<p>I&#8217;ve created a very simple example that plays a C major scale with simple sine waves with no dynamics.<\/p>\n\n\n\n<p>The input image:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"80\" height=\"720\" src=\"https:\/\/scruss.com\/wordpress\/wp-content\/uploads\/2025\/11\/mz2-cmaj.png\" alt=\"a black vertical strip with a red staircase pattern across the middle\" class=\"wp-image-17993\"\/><\/figure>\n\n\n\n<p>The resulting audio:<\/p>\n\n\n\n<figure class=\"wp-block-audio\"><audio controls src=\"https:\/\/scruss.com\/wordpress\/wp-content\/uploads\/2025\/11\/mz2-cmaj.mp3\"><\/audio><\/figure>\n\n\n\n<p>And the python code that produced the image:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; title: ; notranslate\" title=\"\">\n#!\/usr\/bin\/env python3\n# -*- coding: utf-8 -*-\n# mz2-draw - draw a Cmaj scale in the right input format for mz2synth\n# scruss, 2025-11\n# mz2synth - https:\/\/github.com\/frankenbeans\/MZ2SYNTH\n# command line:\n#   mz2 -v -o mz2-cmaj.au mz2-cmaj.ppm\n\nfrom PIL import Image, ImageDraw\n\n\n# convert midi note number (20..127) to \n# vertical offset for mz2 input\n# notes &lt; 20 (G#0) can&#039;t be played by mz2\ndef midi_to_y(n):\n    return 6 * (140 - n)\n\n\nmiddle_c = 60\nmaj_scale = (0, 2, 4, 5, 7, 9, 11, 12)\n# maj_chord = (0, 4, 7)\n\n# mz2 input must be 720 px high,\n# preferably black bg\nim = Image.new(&quot;RGB&quot;, (10 * len(maj_scale), 720), &quot;black&quot;)\ndraw = ImageDraw.Draw(im)\n\nfor i, d in enumerate(maj_scale):\n    # bright red lines mean full\n    # volume sine waves\n    draw.line(\n        &#x5B;\n            10 * i,\n            midi_to_y(middle_c + d),\n            10 * i + 8,\n            midi_to_y(middle_c + d),\n        ],\n        &quot;red&quot;,\n        1,\n    )\n\n# mz2 can only read NetPBM PPM format\nim.save(&quot;mz2-cmaj.ppm&quot;)\n<\/pre><\/div>\n\n\n<h2 class=\"wp-block-heading\">Building<\/h2>\n\n\n\n<p>mz2synth comes with Windows and Mac OS binaries. To run the Mac code, you need <a href=\"https:\/\/brew.sh\/\">Homebrew<\/a> with the <em>gcc@13<\/em> recipe. See this <a href=\"https:\/\/github.com\/frankenbeans\/MZ2SYNTH\/issues\/16\">issue<\/a> for details.<\/p>\n\n\n\n<p>To build on Linux, you&#8217;ll need <em>gfortran<\/em>. A build script could be something like this:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">git clone https:\/\/github.com\/frankenbeans\/MZ2SYNTH.git<br>cd MZ2SYNTH\/SOURCE<br>make -f Makefile.gfortran<\/pre>\n\n\n\n<p>Put the resulting <em>mz2<\/em> binary somewhere in your path, and that&#8217;s all the installation it needs. These same instructions should work for Mac OS.<\/p>\n\n\n\n<p>If you really want to live on the edge (note: not really) and get a faster binary at the expense of array bounds checking, use this to recompile instead of the above <em>make<\/em> line:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">rm *.mod *.o mz2<br>make -f Makefile.gortran.nochk<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>E. Lamprecht&#8217;s MZ2SYNTH is a delightfully weird piece of code. It is an advanced wavetable synthesizer programmed only by an input image. Here&#8217;s an example: Documentation is pretty sparse, so I&#8217;ve had to work it out as best I can: I&#8217;ve created a very simple example that plays a C major scale with simple sine [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"a very brief intro to programming mz2synth, a wavetable synthesizer programmed by an input image\n#synth #python #fortran","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":false,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[12,7],"tags":[1749,2540,3106],"class_list":["post-17992","post","type-post","status-publish","format-standard","hentry","category-audblog","category-computers-suck","tag-fortran","tag-python","tag-synth"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/pQNZZ-4Gc","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/scruss.com\/blog\/wp-json\/wp\/v2\/posts\/17992","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/scruss.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/scruss.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/scruss.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/scruss.com\/blog\/wp-json\/wp\/v2\/comments?post=17992"}],"version-history":[{"count":4,"href":"https:\/\/scruss.com\/blog\/wp-json\/wp\/v2\/posts\/17992\/revisions"}],"predecessor-version":[{"id":17998,"href":"https:\/\/scruss.com\/blog\/wp-json\/wp\/v2\/posts\/17992\/revisions\/17998"}],"wp:attachment":[{"href":"https:\/\/scruss.com\/blog\/wp-json\/wp\/v2\/media?parent=17992"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/scruss.com\/blog\/wp-json\/wp\/v2\/categories?post=17992"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/scruss.com\/blog\/wp-json\/wp\/v2\/tags?post=17992"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}