├── .gitignore ├── data ├── barking.wav └── wavTones.com.unregistred.sweep_100Hz_6000Hz_-3dBFS_5s.wav ├── img └── SoundtrackOptical.jpg ├── library └── SoundtrackOptical.jar ├── .project ├── library.properties ├── .classpath ├── examples ├── FrameExample │ └── FrameExample.pde ├── OpticalWithPlayback │ └── OpticalWithPlayback.pde ├── BufferExample │ └── BufferExample.pde ├── AllSoundtracks │ └── AllSoundtracks.pde └── BasicOptical │ └── BasicOptical.pde ├── .settings └── org.eclipse.jdt.core.prefs ├── LICENSE ├── README.md └── src └── soundtrack └── optical └── SoundtrackOptical.java /.gitignore: -------------------------------------------------------------------------------- 1 | /bin/ 2 | *.DS_Store 3 | */*.DS_Store 4 | -------------------------------------------------------------------------------- /data/barking.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sixteenmillimeter/SoundtrackOptical/HEAD/data/barking.wav -------------------------------------------------------------------------------- /img/SoundtrackOptical.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sixteenmillimeter/SoundtrackOptical/HEAD/img/SoundtrackOptical.jpg -------------------------------------------------------------------------------- /library/SoundtrackOptical.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sixteenmillimeter/SoundtrackOptical/HEAD/library/SoundtrackOptical.jar -------------------------------------------------------------------------------- /data/wavTones.com.unregistred.sweep_100Hz_6000Hz_-3dBFS_5s.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sixteenmillimeter/SoundtrackOptical/HEAD/data/wavTones.com.unregistred.sweep_100Hz_6000Hz_-3dBFS_5s.wav -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | SoundtrackOptical 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.jdt.core.javanature 16 | 17 | 18 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | # UTF-8 supported. 2 | 3 | name = SoundtrackOptical 4 | authors = [Matthew McWilliams](https://sixteenmillimeter.com) 5 | url = https://github.com/sixteenmillimeter/SoundtrackOptical 6 | categories = "Animation,Sound,Video & Vision" 7 | sentence = Framework for generating 16mm optical soundtracks from a digital audio file. 8 | paragraph = Create optical soundtracks in different styles to be used in super16 film-out 9 | version = 4 10 | prettyVersion = 0.04a 11 | minRevision = 2 12 | #maxRevision = 2 13 | -------------------------------------------------------------------------------- /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/FrameExample/FrameExample.pde: -------------------------------------------------------------------------------- 1 | import processing.sound.*; 2 | import soundtrack.optical.*; 3 | 4 | SoundtrackOptical soundtrack; 5 | String type = "dual variable area"; 6 | 7 | String soundtrackFile = "../../data/barking.wav"; 8 | int dpi = 2400; 9 | float volume = 1.0; 10 | String pitch = "long"; 11 | boolean positive = true; 12 | 13 | void setup() { 14 | size(1065, 620, P2D); 15 | soundtrack = new SoundtrackOptical(this, soundtrackFile, dpi, volume, type, pitch, positive); 16 | } 17 | 18 | void draw () { 19 | for (int i = 0; i < 5; i++) { 20 | soundtrack.frame(i * 213, 0, frameCount + i); 21 | } 22 | 23 | stroke(255, 0, 0); 24 | for (int i = 1; i < 5; i++) { 25 | line(213 * i, 0, 213 * i, height); 26 | } 27 | } -------------------------------------------------------------------------------- /examples/OpticalWithPlayback/OpticalWithPlayback.pde: -------------------------------------------------------------------------------- 1 | import processing.sound.*; 2 | import soundtrack.optical.*; 3 | 4 | SoundtrackOptical soundtrack; 5 | SoundFile playback; //to be used to play audio 6 | 7 | String soundtrackFile = "../../data/barking.wav"; 8 | int dpi = 2400; 9 | float volume = 1.0; 10 | String type = "dual variable area"; 11 | String pitch = "long"; 12 | boolean positive = true; 13 | 14 | void setup() { 15 | size(213, 620, P2D); 16 | frameRate(24); 17 | soundtrack = new SoundtrackOptical(this, soundtrackFile, dpi, volume, type, pitch, positive); 18 | playback = new SoundFile(this, soundtrackFile); 19 | 20 | playback.play(); //playback alongside image 21 | } 22 | 23 | void draw () { 24 | soundtrack.draw(0, 0); 25 | } -------------------------------------------------------------------------------- /examples/BufferExample/BufferExample.pde: -------------------------------------------------------------------------------- 1 | import processing.sound.*; 2 | import soundtrack.optical.*; 3 | 4 | SoundtrackOptical soundtrack; 5 | String type = "variable density"; 6 | PGraphics soundtrackBuffer; 7 | PImage transform; 8 | 9 | String soundtrackFile = "../../data/barking.wav"; 10 | int dpi = 2400; 11 | float volume = 1.0; 12 | String pitch = "long"; 13 | boolean positive = true; 14 | 15 | void setup() { 16 | size(620, 213, P2D); 17 | soundtrack = new SoundtrackOptical(this, soundtrackFile, dpi, volume, type, pitch, positive); 18 | imageMode(CENTER); 19 | } 20 | 21 | void draw () { 22 | soundtrackBuffer = soundtrack.buffer(frameCount); 23 | transform = soundtrackBuffer.get(); 24 | translate(width>>1, height>>1); 25 | rotate(HALF_PI); 26 | image(transform, 0, 0); 27 | } -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled 3 | org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate 4 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 5 | org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve 6 | org.eclipse.jdt.core.compiler.compliance=1.8 7 | org.eclipse.jdt.core.compiler.debug.lineNumber=generate 8 | org.eclipse.jdt.core.compiler.debug.localVariable=generate 9 | org.eclipse.jdt.core.compiler.debug.sourceFile=generate 10 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error 11 | org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled 12 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error 13 | org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning 14 | org.eclipse.jdt.core.compiler.release=enabled 15 | org.eclipse.jdt.core.compiler.source=1.8 16 | -------------------------------------------------------------------------------- /examples/AllSoundtracks/AllSoundtracks.pde: -------------------------------------------------------------------------------- 1 | import processing.sound.*; 2 | import soundtrack.optical.*; 3 | 4 | SoundtrackOptical[] soundtracks = { null, null, null, null, null }; 5 | String[] types = { "unilateral", "variable area", "dual variable area", "maurer", "variable density" }; 6 | 7 | String soundtrackFile = "../../data/barking.wav"; 8 | int dpi = 2400; 9 | float volume = 1.0; 10 | String pitch = "long"; 11 | boolean positive = true; 12 | 13 | void setup() { 14 | size(1065, 620, P2D); 15 | for (int i = 0; i < types.length; i++) { 16 | soundtracks[i] = new SoundtrackOptical(this, soundtrackFile, dpi, volume, types[i], pitch, positive); 17 | } 18 | } 19 | 20 | void draw () { 21 | for (int i = 0; i < types.length; i++) { 22 | soundtracks[i].draw(i * 213, 0); 23 | } 24 | 25 | stroke(255, 0, 0); 26 | for (int i = 1; i < types.length; i++) { 27 | line(213 * i, 0, 213 * i, height); 28 | } 29 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Matt McWilliams 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/BasicOptical/BasicOptical.pde: -------------------------------------------------------------------------------- 1 | import processing.sound.*; //requires Sound library to work 2 | import soundtrack.optical.*; 3 | 4 | SoundtrackOptical soundtrack; 5 | 6 | String soundtrackFile = "../../data/barking.wav"; //path to your mono audio file 7 | int dpi = 2400; //the "resolution" of your soundtrack (library is designed for printing) 8 | float volume = 1.0; //volume of the output, scaled from 0 to 1 9 | String type = "dual variable area"; //one of the following [unilateral, single variable area, dual variable area, multiple variable area] 10 | String pitch = "long"; //whether the film is "long" or "short" pitch 11 | boolean positive = true; //whether the film is positive or negative 12 | 13 | void setup() { 14 | size(213, 620); //this will perfectly fill the frame with the soundtrack @ 2400DPI 15 | //must run in P2D or P2D (acheives realtime playback easier) 16 | 17 | frameRate(24); //this will playback at realtime speed 18 | soundtrack = new SoundtrackOptical(this, soundtrackFile, dpi, volume, type, pitch, positive); 19 | } 20 | 21 | void draw () { 22 | soundtrack.draw(0, 0); 23 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # soundtrack.optical 2 | 3 | ![Illustration of all soundtrack formats displaying the same samples](./img/SoundtrackOptical.jpg) 4 | 5 | [Download library](https://github.com/sixteenmillimeter/SoundtrackOptical/archive/master.zip) 6 | 7 | Library for generating 16mm optical soundtracks with Processing. 8 | 9 | Install library by downloading library as .zip, uncompressing and [placing SoundtrackOptical in your Processing library directory](https://github.com/processing/processing/wiki/How-to-Install-a-Contributed-Library). Note: When extracting the .zip, the folder may be named "SoundtrackOptical-master" and should be renamed "SoundtrackOptical" before installing. Start up (or restart) Processing to use this library in a sketch. 10 | 11 | Supports mono audio only (at the moment, feel free to contribute). 12 | 13 | Draws various kinds of 16mm soundtracks. [Read about them here.](http://www.paulivester.com/films/filmstock/guide.htm). 14 | 15 | * unilateral 16 | * *dual unilateral (in progress!)* 17 | * single variable area 18 | * dual variable area 19 | * multiple variable area (Maurer) 20 | * variable density 21 | 22 | ### Example Usage 23 | 24 | ```java 25 | import processing.sound.*; 26 | import soundtrack.optical.*; 27 | 28 | SoundtrackOptical soundtrack; 29 | 30 | String soundtrackFile = "../../data/barking.wav"; 31 | int dpi = 2400; 32 | float volume = 1.0; 33 | String type = "dual variable area"; 34 | String pitch = "long"; 35 | boolean positive = true; 36 | 37 | void setup() { 38 | size(213, 620, P2D); 39 | frameRate(24); 40 | soundtrack = new SoundtrackOptical(this, soundtrackFile, dpi, volume, type, pitch, positive); 41 | } 42 | 43 | void draw () { 44 | soundtrack.draw(0, 0); 45 | } 46 | ``` 47 | 48 | ### Alternate usages 49 | 50 | Use the `frame(int x, int y, int frameNumber)` method to draw specific frames--used for laying out multiple frames of soundtrack on a single screen. 51 | 52 | ```java 53 | void draw () { 54 | soundtrack.frame(0, 0, frameCount); 55 | } 56 | 57 | ``` 58 | 59 | Use the `buffer(int frameNumber)` method to return the internal `PGraphics` object that contains the specific frame of soundtrack data specified by the frameNumber. You can then draw onto the canvas, address the pixels directly, or as in the provided BufferExample.pde assign the image data to a PImage to be manipulated using the built-in transformation methods. 60 | 61 | ```java 62 | void draw () { 63 | PGraphics soundtrackBuffer = soundtrack.buffer(frameCount); 64 | image(soundtrackBuffer, 0, 0); 65 | } 66 | 67 | ``` 68 | -------------------------------------------------------------------------------- /src/soundtrack/optical/SoundtrackOptical.java: -------------------------------------------------------------------------------- 1 | package soundtrack.optical; 2 | 3 | import processing.core.*; 4 | import processing.sound.*; 5 | 6 | public class SoundtrackOptical { 7 | String TYPE = "dual variable area"; 8 | int DPI = 2880; 9 | boolean POSITIVE = true; 10 | float VOLUME = (float) 1.0; 11 | String pitch = "long"; 12 | String FILEPATH; 13 | 14 | float IN = (float) 25.4; 15 | float FRAME_H = (float) 7.62; 16 | float FRAME_W = (float) (12.52 - 10.26); 17 | 18 | float DPMM = (float) (DPI / IN); 19 | int FRAME_H_PIXELS = (int) Math.round(DPMM * FRAME_H); 20 | int SAMPLE_RATE = FRAME_H_PIXELS * 24; 21 | int DEPTH = (int) Math.round(DPMM * FRAME_W); 22 | 23 | int RAW_RATE = 0; 24 | int RAW_FRAME_H = 0; 25 | int RAW_FRAME_W = 0; 26 | 27 | int LINE_W = 0; 28 | float DENSITY = 0; 29 | int LEFT = 0; 30 | int FRAMES = 0; 31 | int i = 0; 32 | 33 | float max = 0; 34 | float min = 0; 35 | float compare; 36 | 37 | float[] frameSample; 38 | SoundFile soundfile; 39 | PGraphics raw; 40 | PApplet parent; 41 | 42 | /** 43 | * @constructor 44 | * 45 | * 46 | * @param parent {PApplet} Parent process (usually this) 47 | * @param soundtrackFile {String} Path to soundtrackFile 48 | * @param dpi {Integer} Target DPI of printer 49 | * @param volume {Float} Volume of output soundtrack, 0 to 1.0 50 | * @param type {String} Type of soundtrack either "unilateral", "variable area", "dual variable area", "multiple variable area", "variable density" 51 | * @param pitch {String} Pitch of the film, either "long" for projection or "short" for camera stock 52 | * @param positive {Boolean} Whether or not soundtrack is positive or negative 53 | */ 54 | 55 | @SuppressWarnings("static-access") 56 | public SoundtrackOptical (PApplet parent, String soundtrackFile, int dpi, float volume, String type, String pitch, boolean positive ) { 57 | this.parent = parent; 58 | 59 | FILEPATH = soundtrackFile; 60 | TYPE = type; 61 | DPI = dpi; 62 | VOLUME = volume; 63 | POSITIVE = positive; 64 | FRAME_H = (float) ((pitch == "long") ? 7.62 : 7.605); 65 | DPMM = DPI / IN; 66 | FRAME_H_PIXELS = (int) Math.round(DPMM * FRAME_H); 67 | SAMPLE_RATE = FRAME_H_PIXELS * 24; 68 | DEPTH = (int) Math.floor(DPMM * FRAME_W); 69 | 70 | soundfile = new SoundFile(parent, FILEPATH); 71 | 72 | RAW_RATE = soundfile.sampleRate(); 73 | RAW_FRAME_H = Math.round(RAW_RATE / 24); 74 | RAW_FRAME_W = Math.round(((RAW_RATE / 24) / FRAME_H ) * FRAME_W); 75 | FRAMES = (int) Math.ceil(soundfile.frames() / RAW_FRAME_H); 76 | 77 | frameSample = new float[RAW_FRAME_H]; 78 | raw = parent.createGraphics(RAW_FRAME_W, RAW_FRAME_H, parent.sketchRenderer()); 79 | 80 | for (int x = 0; x < soundfile.frames(); x++) { 81 | compare = soundfile.read(x); 82 | if (compare > max) { 83 | max = compare; 84 | } 85 | if (compare < min) { 86 | min = compare; 87 | } 88 | } 89 | } 90 | 91 | /** 92 | * Calls frame() every frame of parent PApplet draw() 93 | * 94 | * @param X {Integer} Left position of soundtrack to draw on parent renderer 95 | * @param Y {Integer} Top position 96 | */ 97 | public void draw (int X, int Y) { 98 | frame(X, Y, parent.frameCount); 99 | } 100 | 101 | /** 102 | * Draws a frame on parent PApplet window at position 103 | * 104 | * @param X {Integer} Left position of soundtrack to draw on parent renderer 105 | * @param Y {Integer} Top position 106 | * @param frameNumber {Integer} Frame of soundtrack to draw 107 | */ 108 | 109 | @SuppressWarnings("static-access") 110 | public void frame(int X, int Y, int frameNumber) { 111 | if (frameNumber != -1) { 112 | i = frameNumber; 113 | } 114 | if (i >= FRAMES) { 115 | return; 116 | } 117 | raw.beginDraw(); 118 | //draw bg 119 | raw.noStroke(); 120 | if (POSITIVE) { 121 | raw.fill(0); 122 | } else { 123 | raw.fill(255); 124 | } 125 | 126 | if (TYPE != "variable density") { 127 | raw.rect(0, 0, RAW_FRAME_W, RAW_FRAME_H); 128 | } 129 | 130 | //draw top 131 | if (POSITIVE) { 132 | raw.stroke(255); 133 | } else { 134 | raw.stroke(0); 135 | } 136 | 137 | soundfile.read(i * RAW_FRAME_H, frameSample, 0, RAW_FRAME_H); 138 | 139 | for (int y = 0; y < RAW_FRAME_H; y++) { 140 | if (TYPE != "variable density") { 141 | LINE_W = Math.round(parent.map(frameSample[y], min, max, (float) 0, RAW_FRAME_W * VOLUME)); 142 | } 143 | if (TYPE == "unilateral") { 144 | unilateral(y, LINE_W); 145 | } else if (TYPE == "dual unilateral") { 146 | /* TODO!!!! */ 147 | } else if (TYPE == "single variable area" || TYPE == "variable area") { 148 | variableArea(y, LINE_W); 149 | } else if (TYPE == "dual variable area") { 150 | dualVariableArea(y, LINE_W); 151 | } else if (TYPE == "multiple variable area" || TYPE == "maurer") { 152 | multipleVariableArea(y, LINE_W); 153 | } else if (TYPE == "variable density") { 154 | variableDensity(y); 155 | } 156 | } 157 | raw.endDraw(); 158 | parent.image(raw, X, Y, DEPTH, FRAME_H_PIXELS); 159 | if (frameNumber == -1) { 160 | i++; 161 | } 162 | } 163 | 164 | @SuppressWarnings("static-access") 165 | public PGraphics buffer (int frameNumber) { 166 | if (frameNumber != -1) { 167 | i = frameNumber; 168 | } 169 | if (i >= FRAMES) { 170 | return null; 171 | } 172 | raw.beginDraw(); 173 | //draw bg 174 | raw.noStroke(); 175 | if (POSITIVE) { 176 | raw.fill(0); 177 | } else { 178 | raw.fill(255); 179 | } 180 | 181 | if (TYPE != "variable density") { 182 | raw.rect(0, 0, RAW_FRAME_W, RAW_FRAME_H); 183 | } 184 | 185 | //draw top 186 | if (POSITIVE) { 187 | raw.stroke(255); 188 | } else { 189 | raw.stroke(0); 190 | } 191 | 192 | soundfile.read(i * RAW_FRAME_H, frameSample, 0, RAW_FRAME_H); 193 | 194 | for (int y = 0; y < RAW_FRAME_H; y++) { 195 | if (TYPE != "variable density") { 196 | LINE_W = Math.round(parent.map(frameSample[y], min, max, (float) 0, RAW_FRAME_W * VOLUME)); 197 | } 198 | if (TYPE == "unilateral") { 199 | unilateral(y, LINE_W); 200 | } else if (TYPE == "dual unilateral") { 201 | /* TODO!!!! */ 202 | } else if (TYPE == "single variable area" || TYPE == "variable area") { 203 | variableArea(y, LINE_W); 204 | } else if (TYPE == "dual variable area") { 205 | dualVariableArea(y, LINE_W); 206 | } else if (TYPE == "multiple variable area" || TYPE == "maurer") { 207 | multipleVariableArea(y, LINE_W); 208 | } else if (TYPE == "variable density") { 209 | variableDensity(y); 210 | } 211 | } 212 | raw.endDraw(); 213 | return raw; 214 | } 215 | 216 | private void unilateral (int y, int LINE_W) { 217 | raw.line(0, y, LINE_W, y); 218 | } 219 | 220 | private void variableArea (int y, int LINE_W) { 221 | LEFT = Math.round((RAW_FRAME_W - LINE_W) / 2); 222 | raw.line(LEFT, y, LEFT + LINE_W, y); 223 | } 224 | 225 | private void dualVariableArea (int y, int LINE_W) { 226 | LEFT = Math.round((RAW_FRAME_W / 4) - (LINE_W / 4)); 227 | raw.line(LEFT, y, LEFT + (LINE_W / 2), y); 228 | raw.line(LEFT + (RAW_FRAME_W / 2), y, LEFT + (RAW_FRAME_W / 2) + (LINE_W / 2), y); 229 | } 230 | 231 | private void multipleVariableArea (int y, int LINE_W) { 232 | LEFT = Math.round((RAW_FRAME_W / 16) - (LINE_W / 16)); 233 | for (int x = 1; x < 7; x++) { 234 | raw.line(LEFT + ((x * RAW_FRAME_W) / 8), y, LEFT + ((x * RAW_FRAME_W) / 8) + (LINE_W / 8), y); 235 | } 236 | } 237 | @SuppressWarnings("static-access") 238 | private void variableDensity(int y) { 239 | DENSITY = parent.map(frameSample[y], min, max, (float) 0, 255 * VOLUME); 240 | if (POSITIVE) { 241 | raw.stroke(DENSITY); 242 | } else { 243 | raw.stroke(255 - DENSITY); 244 | } 245 | raw.line(0, y, RAW_FRAME_W, y); 246 | } 247 | } 248 | --------------------------------------------------------------------------------