Several people have asked for the code that I used in a previous post to calculate and plot first-reflection delay times as a function of listener- and loudspeaker-placement. So here it is.
Running the script below will produce pdf or postscript graphics output that looks like this:
The graph shows a rectangular room plan with superimposed contours that show, for a fixed listening position, the delay time for first-order sound reflections as a function of loudspeaker placement. Each red curve indicates placements that yield exactly 6ms delay for reflection from a particular wall. The dashed lines are a guide: a pair of loudspeakers placed symmetrically on these lines will form an equilateral triangle with the listener. Also shown is the largest possible stereo separation (1.86m in the diagram above) that yields at least 6ms delay.
The script is written in the asymptote vector graphics language. The variables defining room dimensions and listening position are near the top. Change these to match your own room. For now the code only works with rectangular rooms.
/* relftime.asy Richard Taylor 03.11.2014 An asymptote (asymptote.sourceforge.net) script. This draws a rectangular room plan with superimposed contours that show, for any given loudspeaker placement, the resulting arrival time delay (for a fixed listening position) between the direct sound and the first lateral reflection. Additionally, for each of the four walls a red contour is drawn to show the loudspeaker placements that give exactly 6ms delay for reflection from that wall. The shaded region indicates speaker placements for ≥6ms delay from all walls. The listening position is marked with a green triangle. Dashed lines are shown as a guide: a pair of loudspeaker speakers placed symmetrically on these lines will form an equilateral triangle with the listening position. */ import contour; import graph; unitsize(3cm); // room dimensions [m]: real Lx = 4.5; real Ly = 5.5; // listening position [m] (relative to back-left corner): real ly = 1.5; real lx = 0.5*Lx; // target minimum delay [ms]: real mindelay = 6.0; pair listener = (lx,ly); // function to calculate arrival time difference [ms] for direct and // reflected sound. // x1 = distance from source to wall [m] // x2 = distance from listener to wall [m] // y = source-listener lateral separation parallel to wall [m] real rt(real x1, real x2, real y) { real dd = sqrt( (x1-x2)^2 + y^2 ); // direct-sound path length real dr = sqrt( (x1+x2)^2 + y^2 ); // reflected-sound path length return (dr-dd)/0.343; } real rtT(real x, real y) { // refl. from top, source at (x,y) return rt( Ly-y, Ly-ly, x-lx ); } real rtB(real x, real y) { // refl. from bottom, source at (x,y) return rt( y, ly, x-lx ); } real rtR(real x, real y) { // refl. from right, source at (x,y) return rt( Lx-x, Lx-lx, y-ly ); } real rtL(real x, real y) { // refl. from right, source at (x,y) return rt( x, lx, y-ly ); } real rtmin(real x, real y) { // min. delay for all boundaries return min( rtT(x,y), rtB(x,y), rtL(x,y), rtR(x,y) ); } // shade region where delay is at least 6ms: guide[][] g=contour(rtmin,(0,0),(Lx,Ly),new real[] {mindelay}); fill(g[0],mediumgrey); // guide lines for equilateral loudspeaker placement: picture equiguide=new picture; real L=sqrt(Lx^2+Ly^2); path equilateral = (listener+L*dir(120))--listener--(listener+L*dir(60)); draw(equiguide, equilateral, dashed ); clip(equiguide,(0,0)--(Lx,0)--(Lx,Ly)--(0,Ly)--cycle); add(equiguide); // mark listening position: draw( listener, marker(scale(2mm)*polygon(3),FillDraw(green) ) ); // draw 6ms contour for each reflection: draw( new Label[] {Label("FRONT WALL",Relative(0.6),Center,UnFill(1bp))}, contour(rtT,(0,0),(Lx,Ly),new real[] {mindelay}), red+linewidth(2) ); draw( new Label[] {Label("REAR WALL",Relative(0.4),Center,UnFill(1bp))}, contour(rtB,(0,0),(Lx,Ly),new real[] {mindelay}), red+linewidth(2) ); draw( new Label[] {Label("LEFT WALL",Relative(0.15),Center,UnFill(1bp))}, contour(rtL,(0,0),(Lx,Ly),new real[] {mindelay}), red+linewidth(2) ); draw( new Label[] {Label("RIGHT WALL",Relative(0.15),Center,UnFill(1bp))}, contour(rtR,(0,0),(Lx,Ly),new real[] {mindelay}), red+linewidth(2) ); // add contours for first-reflection delay times: int n=5; real[] c=new real[n]; for(int i=0; i < n; ++i) c[i]=i+1; // contour levels Label[] Labels=sequence(new Label(int i) { return Label(c[i] != 0 ? format("$%0.0f$\,ms" ,c[i]) : "", Relative(0.49 - 0.01*i/n),(0,0), UnFill(1bp)); },c.length); draw( Labels, contour(rtmin,(0,0),(Lx,Ly),c, nx=100, ny=100) ); // draw room boundaries: draw( (0,0)--(Lx,0)--(Lx,Ly)--(0,Ly)--cycle, blue+linewidth(2) ); // add some axes: xaxis(Label("$x$ [meters]",Relative(0.5)),BottomTop,RightTicks,xmin=0); yaxis(Label("$y$ [meters]",Relative(0.5)),LeftRight,LeftTicks,ymin=0); // show max. stereo separation with equilateral placement and 6ms delay: path contour6 = g[0][0]; pair[] furthestpoints = intersectionpoints(equilateral, contour6); pair p1 = furthestpoints[0]; pair p2 = furthestpoints[1]; draw( Label(format("$%0.2f$\,m", length(p2-p1)), LeftSide), p1--p2, dashed, Arrows );
Update 13.11.2014: Added code to show maximum stereo separation with equilateral placement and 6ms delay.
Update 02.03.2015: Fixed a bug that affected showing the maximum stereo separation.
How do I change speaker position ?
I understand about the editing of “room dimensions” & “listening position”.
// room dimensions [m]:
real Lx = 4.5; -> real Lx = 3.45;
real Ly = 5.5;
// listening position [m] (relative to back-left corner):
real ly = 1.5; -> real ly = 2.5;
real lx = 0.5*Lx;
Speaker position isn’t constant here. The graph shows the direct- to reflected-sound delay for *every* possible speaker position.
Great work!
It seems to me that the variable “equilateral” is used but not defined in this script?
Oops, that was a bug. Fixed now. Thanks for spotting it!
Great reading on your blog. For those who wish to use your script here is a quick guide:
Download: ProTeXt-3.1.4-020114.exe 1.6GB, asymptote-2.32-setup.exe 5MB
Install the first one chosing MiKTex > Basic
Install asymptote and make a config.asy file in its program files folder with notepad:
Put these two lines in the file
import settings;
texpath=”C:\Program Files\MiKTex 2.9\miktex\bin\x64″;
you might have to start notepad with administrator permissions to save this file. Be sure tho choose Save as type: all files *.*
Double click the relftime.asy and it will make you a relftime.eps file and two logs
Viewing the EPS: If you have irfanview then get the postscript.dll file plugin and install it. Otherwise just drag and drop into microsoft word – looks ugly but does the trick. Deluxe viewing install ghostscript and ghostscript viewer. Set ghostscript viewer to Options > EPS clip to stop it from clipping off the right side of large rooms.
gs915w32.exe
Thanks lots Richard, for posting the script!
Now I have to admit my defeat, that I’ve followed Chris’s instructions closely, and still only have errors not room graphs.
Win7 64 system, followed steps up to installing GS, get error msgs when running refltime.asy.
config.asy: 2.9: invalid token
config.asy: 2.11: syntax error
error: could not load module ‘config.asy’
Tried many things, always using Admin mode with Notepad, saving all files… tried saving as ASCI, Unicode types. Thought I might have it when I noticed the path in Chris’s config.asy is slightly different from the MikTeX install dir, with no capital X at the end of MikTeX… but no.
Hope someone can show me the error of my ways here….
I don’t have experience running this stuff under Windows (I use a linux machine for all my work so TeX, ghostscript, etc. are set up to work out-of-the-box) so sorry I can’t help here. Perhaps someone else can weigh in?
config.asy: 2.9: invalid token
config.asy: 2.11: syntax error
error: could not load module ‘config.asy’
the problem is with line 2 character 9 in config.asy. Copy pasting from my message results in an incorrect double quote (the left double quote). Just replace the funny double quote with a real one.