├── .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 | 
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 |
--------------------------------------------------------------------------------