I have developed a set of demo sketches that implement simple cross-language random numbers.
The goal is to make it easy to adapt a sketch between Processing (Java), p5.js (JavaScript), or Processing.py (Python) such that passing the same random seed to each version will generate the same “random” playback from a generative artwork.
This is currently not possible using the core modes / libraries, as they each use different underlying systems for generating random numbers. One solution is to implement a cross-platform pseudo-random number generator (PNRG) that always gives the same sequence of pseudo-random values based on the same seed – robustly, including differences in numeric data types between the languages.
This first test is not designed to be “high quality” random numbers (good distribution), nor fast / efficient – that hasn’t been checked. Its goal is simply to make them the same, no matter what language is being used.
Below is code for three demo sketches – Java, Javascript, and Python.
Demo sketches
RandomAnywhere.randomSeed(0)
I would love feedback and advice – I have been away from Python for a few months and I am particularly weak in JavaScript, so there may be a lot of things that aren’t idiomatic.
I would also welcome implementations for other modes like Processing.R, JRubyArt, or p5py et cetera.
Finally, if anyone is willing to tackle extending this to Perlin noise, that would be greatly appreciated!
Not a lot of people in Processing work on things that are coordinated cross-language, but if this experiment turns out to be useful it could turn into a library.
Java
RandAnywhere rand;
void setup() {
size(200,200);
background(255);
// print a series of examples values from a specific seed
// corresponding to epoch milliseconds, as if a seed was
// requested on 2018-11-11 at 11:11:11.
rand = new RandAnywhere(1541963471000L);
println(rand.nextLong()); // 1043807117
println(rand.nextInt()); // 935077768
println(rand.random()); // 0.78921956
println(rand.random(10)); // 1.1313272
println(rand.random(0, width)); // 140.05911
// reset seed
rand.randomSeed(0);
}
/**
* Draw a series of random lines.
*/
void draw(){
if(frameCount>64){
noLoop();
}
line(rand.random(0, width), rand.random(0, height),
rand.random(0, width), rand.random(0, height));
}
/**
* a simple cross-language pseudorandom number generator (PRNG)
* based on XorShift, implemented in Processing (Java)
* inspired by https://stackoverflow.com/a/34659107/7207622
*/
class RandAnywhere {
long seed;
long a;
long c;
long m32;
/**
* Constructor
*
* Defaults to current system time in milliseconds.
*/
RandAnywhere() {
this(System.currentTimeMillis());
}
/**
* @param seed starting seed value
*/
RandAnywhere(long seed) {
this.a = 1664525;
this.c = 1013904223;
this.m32 = 0xFFFFFFFFL;
this.randomSeed(seed);
}
/**
* Updates seed based on past value, returns new value.
*
* @return unsigned 32bit value stored in a long
*/
long nextLong() {
this.seed = this.seed * a + c & m32;
return this.seed;
}
/**
* Returns next long wrapped to the max integer value.
* Implemented so that calls to nextInt or nextLong stay in sync,
* the next seed internally is always updated to the next long.
*
* @return positive
*/
int nextInt() {
return (int)(this.nextLong()%Integer.MAX_VALUE);
}
/**
* Set the current seed.
*
* @param newSeed the new seed value
*/
void randomSeed(long newSeed) {
this.seed = newSeed%Integer.MAX_VALUE;
}
/**
* @return a random float between 0 and 1
*/
float random() {
return random(0, 1);
}
/**
* @param max maximum value to return
* @return a random float between 0 and max
*/
float random(float max) {
return random(0, max);
}
/**
* @param min minimum value to return
* @param max maximum value to return
* @return a random float in the specified range (min, max)
*/
float random(float min, float max) {
return map(this.nextInt(), 0, Integer.MAX_VALUE, min, max);
}
}
p5.js
var rand;
function setup() {
createCanvas(200, 200);
background(255);
// print a series of examples values from a specific seed
// corresponding to epoch milliseconds, as if a seed was
// requested on 2018-11-11 at 11:11:11.
rand = new RandAnywhere(1541963471000);
print(rand.nextLong()); // 1043807117
print(rand.nextInt()); // 935077768
print(rand.random()); // 0.7892195325294601
print(rand.random(10)); // 1.1313271294959482
print(rand.random(0, width)); // 140.0591152441032
// reset seed
rand.randomSeed(0);
}
/**
* draw a series of random lines
*/
function draw() {
if (frameCount>64) {
noLoop();
}
line(rand.random(0, width), rand.random(0, height),
rand.random(0, width), rand.random(0, height));
}
/**
* a simple cross-language pseudorandom number generator (PRNG)
* based on XorShift, implemented in p5.js (JavaScript)
* inspired by https://stackoverflow.com/a/34659107/7207622
*/
function RandAnywhere(newSeed) {
this.a = 1664525;
this.c = 1013904223;
this.Integer_MAX_VALUE = 2147483647;
// Defaults to current system time in milliseconds.
if (newSeed === undefined) {
var d = new Date();
newSeed = d.getMilliseconds()%this.Integer_MAX_VALUE;
}
this.randomSeed(newSeed);
}
/**
* Updates seed based on past value, returns new value.
*
* Returns unsigned 32bit value stored in a long.
* (named for cross-language compatability with Java nextLong)
*/
RandAnywhere.prototype.nextLong = function() {
// The intermediate result fits in 52 bits, so no overflow
this.seed = ((this.seed * 1664525 + 1013904223) | 0) >>> 0;
return this.seed;
}
/**
* Returns next long wrapped to the max (Java) integer value.
* (for cross-language compatability)
* Implemented so that calls to nextInt or nextLong stay in sync,
* the next seed internally is always updated to the next long.
*/
RandAnywhere.prototype.nextInt = function() {
var next = this.nextLong();
return next%this.Integer_MAX_VALUE;
}
/**
* Set the current seed.
*/
RandAnywhere.prototype.randomSeed = function(newSeed) {
this.seed = newSeed%this.Integer_MAX_VALUE;
}
/**
* Return a random float in the specified range.
*
* 0 arguments: 0-1
* 1 argument: 0-max
* 2 arguments: min-max
*/
RandAnywhere.prototype.random = function(a, b) {
if(!a && !b){
rmin = 0;
rmax = 1;
} else if(!b){
rmin = 0;
rmax = a;
} else {
rmin = a;
rmax = b;
}
return map(this.nextInt(), 0, this.Integer_MAX_VALUE, rmin, rmax);
}
python
import time
def setup():
size(200, 200)
background(255)
# print a series of examples values from a specific seed
# corresponding to epoch milliseconds, as if a seed was
# requested on 2018-11-11 at 11:11:11.
global rand
rand = RandAnywhere(1541963471000)
println(rand.nextLong()) # 1043807117
println(rand.nextInt()) # 935077768
println(rand.random()) # 0.789219558239
println(rand.random(10)) # 1.13132715225
println(rand.random(0, width)) # 140.059112549
# reset seed
rand.randomSeed(0)
def draw():
"""Draw a series of random lines."""
if(frameCount > 64):
noLoop()
line(rand.random(0, width), rand.random(0, height),
rand.random(0, width), rand.random(0, height))
class RandAnywhere(object):
"""a simple cross-language pseudorandom number generator (PRNG)
based on XorShift, implemented in Processing.py (Python)
inspired by https://stackoverflow.com/a/34659107/7207622
"""
def __init__(self, newSeed=None):
"""Defaults to current system time in milliseconds."""
self.a = 1664525
self.c = 1013904223
self.Integer_MAX_VALUE = 2147483647
if newSeed is None:
newSeed = int(round(time.time() * 1000)) % self.Integer_MAX_VALUE
self.randomSeed(newSeed)
def nextLong(self):
"""Returns unsigned 32bit value.
(named for cross-language compatability with Java nextLong)
"""
# The intermediate result fits in 52 bits, so no overflow
self.seed = (self.seed * 1664525 + 1013904223) & 0xFFFFFFFF
return self.seed
def nextInt(self):
"""Returns next long wrapped to the max Java integer value.
(for cross-language compatability)
Implemented so that calls to nextInt or nextLong stay in sync,
the next seed internally is always updated to the next long.
"""
next = self.nextLong()
return next % self.Integer_MAX_VALUE
def randomSeed(self, newSeed):
"""Set the current seed."""
self.seed = newSeed % self.Integer_MAX_VALUE
def random(self, *args):
"""Return a random float in the specified range.
* 0 arguments: 0-1
* 1 argument: 0-max
* 2 arguments: min-max
"""
min = 0
max = 1
if len(args) == 1:
min = 0
max = args[0]
elif len(args) > 1:
min = args[0]
max = args[1]
return map(self.nextInt(), 0, self.Integer_MAX_VALUE, min, max)