47
\$\begingroup\$

Sound synthesizers use envelope generators to control how certain parameters of the sound (such as overall loudness) change with time. In many synthesizers an envelope is defined by four parameters, as represented in the following figure from Wikipedia:

  • Attack time (A): time taken for the envelope to reach its maximum level, starting from zero, when the key is first pressed.
  • Decay time (D): time taken for the envelope to reach the specified sustain level.
  • Sustain level (S): level which is maintained, after the initial attack and decay, for as long as the key is being pressed.
  • Release time (R): time taken for the envelope to reach zero when the key is released.

enter image description here

The challenge

Input the four parameters A, D, S, R and plot the envelope.

Parameters will be integer values from 0 to 127.

The maximum level (reached at the end of the attack phase) will be assumed to be 127.

The horizontal segment at the sustain level will be assumed to have duration 64 (in the actual sound this duration is not fixed, but is determined by the amount of time that the key is held).

Format and further details

The output should be an image in raster or vector format. If it's raster, the polygonal line should occupy at least 50 pixels vertically an horizontally.

The image can be either displayed or produced as a file in a standard image format. The file can be written to disk, or its exact contents can be output, either to STDERR or as function return argument.

The graph need only contain the polygonal line that defines the envelope. The scale of each axis can be freely chosen. Other elements such as axis lines, numeric labels or line colors are optional.

Input means and format are flexible as usual. For example, you can take the four numbers in any order, or an array containing them. A program or a function can be provided. Standard loopholes are forbidden.

Shortest code in bytes wins.

Test cases

Input is in the format [A D S R]. Note that the scale is different in each figure (in accordance with the rule that scale can be freely chosen)

[15 30 70 40]

enter image description here

[64 64 64 64]

enter image description here

[0 10 50 80]

enter image description here

[0 0 90 80]

enter image description here

[5 50 0 0]

enter image description here

[5 50 0 80]

enter image description here

[24 32 127 48]

enter image description here

\$\endgroup\$
2

16 Answers 16

44
+50
\$\begingroup\$

TikZ, 195 193 181 177 172 167 163 160 159 bytes

Thanks to David Carlisle for his helpful answer here

\documentclass[tikz]{standalone}\begin{document}\tikz\def~#1{\typein[#1];}~\a~\s~\d~\r\def\u{)--(\a+\d}\draw(,)--(\a,127\u,\s\u+64,\s\u+\r+64,);\end{document}

Yes, you heard right TikZ.

Explanation

This uses a couple of techniques to achieve its goal. The first thing is input. Most people might not know that LATEX can take input. Well it can. This is achieved with the command \typein. This will halt the compiler and request input from the user as assign it to a variable.

The other main technique is the use of \def. \def in Tikz is absurdly powerful. It essentially defines a piece of code and pastes it everywhere you call the variable. In this code we define two things \typein[#1]{} so that we can assign our variables golfily and )--(\a+\d because this code shows up a bunch of times in the ungolfed version.

Here are two versions of the code (without the wrapper):

Golfed:

\documentclass[tikz]{standalone}\begin{document}\tikz\def~#1{\typein[#1];}~\a~\s~\d~\r\def\u{)--(\a+\d}\draw(,)--(\a,127\u,\s\u+64,\s\u+\r+64,);\end{document}

Ungolfed:

\typein[\a]{}\typein[\s]{}\typein[\d]{}\typein[\r]{}\draw(0,0)--(\a,127)--(\a+\d,)--(\a+\d+64,)--(\a+\d+\r+64,0);

Image:

Since I cannot upload a pdf image directly, and converting it to any other format seems to cause the line to disappear altogether, here is what an image might look like when opened in Preview (input for this image is [64 64 64 64]):

Preview image

As you can see it is very thin. However because it is a PDF image and not a raster image does not have to comply with thickness requirements.

\$\endgroup\$
8
  • 12
    \$\begingroup\$ I sincerely admire the fact that you know TikZ so well that it's the first solution that comes into your mind. (And if it wasn't, that's what legend will remember) \$\endgroup\$
    – Right Leg
    Feb 15, 2017 at 2:05
  • 1
    \$\begingroup\$ Doesn't input tikz\begin{document} work? \$\endgroup\$
    – Fatalize
    Feb 15, 2017 at 7:50
  • 5
    \$\begingroup\$ how about pictures? 😉 \$\endgroup\$ Feb 15, 2017 at 12:25
  • \$\begingroup\$ How do you actually render this? pdflatex adsr.tex doesn't seem to work. — Oh wait, it does work, I just didn't expect it would request interactive input! \$\endgroup\$ Feb 15, 2017 at 12:52
  • \$\begingroup\$ @WheatWizard imagemagick can convert PDF reasonably well if you do it right \$\endgroup\$ Feb 15, 2017 at 15:24
20
\$\begingroup\$

Python 2, 83 80 79 bytes

from turtle import*
def f(A,D,S,R):X=A+D+64;map(goto,[A,A+D,X,X+R],[127,S,S,0])

Try it online (83-byte version, because it runs online)

Note that certain outputs may not be completely visible using Trinket, because of the way the canvas works. You'll need to download and install Python if you want it to work better.

Ungolfed:

from turtle import*
A,D,S,R=input()
goto(A,127)
goto(A+D,S)
goto(A+D+64,S)
goto(A+D+64+R,0)

This version doesn't work in Trinket, because Trinket doesn't support value unpacking of input.

\$\endgroup\$
5
  • \$\begingroup\$ You can use map in Python 2: lambda A,D,S,R:map(goto,[A,A+D,A+D+64,A+D+R+64],[127,S,S,0]) \$\endgroup\$
    – xnor
    Feb 14, 2017 at 18:19
  • \$\begingroup\$ @xnor I get SuspensionError: Cannot call a function that blocks or suspends here on line undefined in main.py on Trinket. Have you confirmed that it works? I'm not sure if the error is unique to Trinket or not. \$\endgroup\$
    – mbomb007
    Feb 14, 2017 at 19:06
  • \$\begingroup\$ Works for me in 2.7.12. \$\endgroup\$
    – xnor
    Feb 14, 2017 at 19:11
  • \$\begingroup\$ @xnor Mmk, thanks. It's probably a limitation in Skulpt. \$\endgroup\$
    – mbomb007
    Feb 14, 2017 at 19:12
  • \$\begingroup\$ I submitted a simpler version of this program as an example to the Skulpt github repo, and they thought it was a nice find. Hopefully they'll be able to fix it, but the pace of their "turtle sprint" seems to be more of a walk. \$\endgroup\$
    – mbomb007
    Feb 16, 2017 at 17:15
17
\$\begingroup\$

Mathematica, 61 58 bytes

ListLinePlot[{Accumulate@{0,##3,64,#2},{0,127,#,#,0}}]&

is the operator for Transpose and is rendered as a superscript T by Mathematica.

Takes the arguments in the order S, R, A, D and returns a vector graphics object.

Results for all seven test cases:

enter image description here Click for larger version.

\$\endgroup\$
2
  • \$\begingroup\$ That was quick! \$\endgroup\$
    – Luis Mendo
    Feb 14, 2017 at 15:45
  • \$\begingroup\$ +1 for teaching me ListLinePlot :) And there's a ListStepPlot as well—so useful! \$\endgroup\$ Feb 14, 2017 at 18:46
12
\$\begingroup\$

R, 66 63 bytes

function(A,D,S,R)plot(cumsum(c(0,A,D,64,R)),c(0,127,S,S,0),"l")

Displays the image, with axis labs being cumsum(c(0,A,D,64,R)) and c(0,127,S,S,0), as well axis lines and numeric labels.

Thanks to @Zahiro Mor for shaving off 3 bytes !

Previous answer :

function(A,D,S,R)plot(c(0,A,A+D,b<-A+D+64,b+R),c(0,127,S,S,0),"l")
\$\endgroup\$
1
  • 2
    \$\begingroup\$ you can use cumsum(c(0,A,D,64,R)) to replace the first term in the plot. with cumsum you will not need the b trick and still shave 3 bytes :) function(A,D,S,R)plot(cumsum(c(0,A,D,64,R)),c(0,127,S,S,0),"l") \$\endgroup\$
    – Zahiro Mor
    Feb 15, 2017 at 16:46
11
\$\begingroup\$

Matlab, 50 bytes

@(A,D,S,R)plot(cumsum([0,A,D,64,R]),[0,127,S,S,0])

This yields an anonymous function "ans" that has to be called as ans(A,D,S,R).

\$\endgroup\$
0
9
\$\begingroup\$

JavaScript (ES6) + HTML, 126 + 23 = 149

c=O.getContext`2d`;c.moveTo(a=0,z=O.height=128);g=(y,x=prompt())=>c.lineTo(a+=+x,y)||g;g(0)(s=z+~prompt())(s,64)(z);c.stroke()
<canvas id=O width=512>

Takes input one at a time in the order A, S, D, R.

\$\endgroup\$
9
\$\begingroup\$

JavaScript (ES6), 114 111 bytes

f=(a,d,s,r)=>`<svg width=446 height=128><path stroke=red fill=none d=M0,127L${a},0l${d},${127-s}h64l${r},${s}>`
<div oninput=o.innerHTML=f(a.value,d.value,s.value,r.value)><input type=number value=0 min=0 max=127 id=a><input type=number value=63 min=0 max=127 id=d><input type=number value=127 min=0 max=127 id=s><input type=number value=0 min=0 max=127 id=r><div id=o><svg width=446 height=128><path stroke=red fill=none d=M0,127L0,0l63,0h64l0,127 /></svg>

Returns an SVG image suitable for innerHTML. Add 18 bytes for valid XML.

\$\endgroup\$
4
  • \$\begingroup\$ It's great how the figure is updated in real time! \$\endgroup\$
    – Luis Mendo
    Feb 15, 2017 at 10:41
  • \$\begingroup\$ This is great, I should learn SVG syntax sometime. Perhaps add default values to the <input>s? \$\endgroup\$ Feb 15, 2017 at 20:54
  • \$\begingroup\$ @ETHproductions Did you have any particular defaults in mind? (Given that setting defaults won't actually trigger the initial graph anyway...) \$\endgroup\$
    – Neil
    Feb 15, 2017 at 20:59
  • \$\begingroup\$ I was thinking perhaps just 64,64,64,64. Normally I'd hardcode the correct output for the default input, but I see how that would be difficult here... \$\endgroup\$ Feb 15, 2017 at 21:16
8
\$\begingroup\$

Haskell, 112 110 bytes

import Graphics.Gloss
(a#d)s r=display(InWindow""(600,300)(0,0))red$line$zip(scanl(+)0[a,d,64,r])[0,127,s,s,0]

Usage example: (0#10) 50 80.

adsr envelope

This uses the Gloss library. The display function expects a window to plot in (here: a window without title (""), size 600x300, position (0,0) on desktop), a background color (red) and picture witch is a line along the path made by zipping the cumulative sum of [0,a,d,64,r] = [0,a,a+d,a+d+64,a+d+64+r] as the x coordinates and [0,127,s,s,0] as the y coordinates.

Edit: Thanks @xnor for 2 bytes!

\$\endgroup\$
1
  • \$\begingroup\$ Should be shorter to write scanl(+)0[a,d,64,r] \$\endgroup\$
    – xnor
    Feb 14, 2017 at 19:23
7
\$\begingroup\$

MATL, 31 22 bytes

Oii64ivYsO127itO5$h&XG

Accepts four separate inputs ordered as A, D, R, S

Try it out at MATL Online

Explanation

0   % Push a literal 0 to the stack
ii  % Grab the first two inputs
64  % Push the number literal 64 to the stack
i   % Grab the third input
v   % Vertically concatenate all values
Ys  % Compute the cumulative sum
0   % Push the number literal 0 to the stack
127 % Push the number literal 127 to the stack
i   % Grab the fourth input
t   % Duplicate the fourth input
O   % Push the number literal 0 to the stack
5$h % Concatenate the last 5 elements on the stack
&XG % Plot the result using the first array as x and second array as y

enter image description here

\$\endgroup\$
5
\$\begingroup\$

Processing, 134 108 + 14(call to size) = 148 122 bytes

First we need a call to size somewhere in the program so that the output will fit in the window (default at 100x100).

size(400,400);

And here's the actual function:

void g(int a,int b,int c,int d){line(0,127,a,0);line(a,0,b+=a,c=127-c);line(b,c,b+=64,c);line(b,c,b+d,127);}

Call it like f(15,20,70,40);

Screenshot

15, 20, 70, 40

image


My newer answer is more straightforward than the older answer, but I like the older one more (even if it is bigger).

Old answer (148 bytes)

size(400,400);

and the two functions

void f(int[]v){translate(0,127);l(v[0],-127);l(v[1],127-v[2]);l(64,0);l(v[3],v[2]);}void l(int x,int y){line(0,0,x,y);translate(x,y);}

Call it like f(int_array_containing_values); and the result will look something like: f(new int[]{15,20,70,40});

\$\endgroup\$
4
\$\begingroup\$

SmileBASIC, 90 bytes

INPUT A,D,S,R
B=A+D+64W=#Y-S
GLINE.,#Y,A,0GLINE A,0,A+D,W
GLINE A+D,W,B,W
GLINE B,W,B+R,#Y
\$\endgroup\$
4
\$\begingroup\$

PHP, 149 130 bytes

[,$a,$d,$s,$r]=$argv;imagepolygon($i=imagecreatetruecolor(446,127),[0,127,$a,0,$d+=$a,$s,$d+=64,$s,$d+$r,127],5,999);imagepng($i);

takes input from command line arguments, writes image (PNG with graph blue on black) to stdout. Requires PHP 7.1 or later.

usage e.g.

# any OS with ImageMagick:
php -r '<code>' <parameters> | display

# linux with feh:
php -r '<code>' <parameters> | feh

+4 bytes for older PHP: Replace [,$a,$d,$s,$r] with list(,$a,$d,$s,$r).


There is a small hack in there: instead of using imageopenpolygon to hide the base line, the finishing polygon line is drawn outside the canvas. (y=127 would only display on an image with height >= 128.)

I could have saved more with color 99 or 9 instead of 999; but those are pretty hard to see on black. :)

\$\endgroup\$
3
\$\begingroup\$

Bash + grace, 70 bytes

t=$[$1+$2]
echo "0 0
$1 127
$t $3
$[t+64] $3
$[t+64+$4] 0">f
xmgrace f

The script writes to file f the coordinates of each point, and xmgrace (the GUI version) reads the file and displays the plot using lines by default.

Run:

./plot_ADSR.sh 15 30 70 40

Output: (print screen)

15 30 70 40

I think this can be done directly by a grace script, if it can accept input, but I'm not familiar with its syntax. I'll look into it.

Explanation:

t=$[$1+$2]          # store the value of (A+D) for later usage
echo "0 0           # start writing the coordinates to file "f", first the origin
$1 127              # then (A, 127)
$t $3               # then (A + D, S)
$[t+64] $3          # then (A + D + 64, S)
$[t+64+$4] 0">f     # then (A + D + 64 + R, 0)
xmgrace f           # call xmgrace to plot the generated XY file
\$\endgroup\$
2
\$\begingroup\$

Go, 947 915 506 bytes

This is far from optimized, trying to learn the language while participating in these questions. Feel free to point out what I can do.

fixed image

Condensed:

package main;import (."os";."image";k"image/png";c"image/color";."strconv";."math");func main(){g:=NewRGBA(Rect(0,0,127*4,127));a,_:=ParseFloat(Args[1],4);d,_:=ParseFloat(Args[2],4);s,_:=ParseFloat(Args[3],4);r,_:=ParseFloat(Args[4],4);z:=[5][]float64{{0,0},{a,127},{a+d,s},{a+d+64,s},{a+d+64+r,0}};for i:=1;i<len(z);i++{v,w,x,y:=z[i-1][0],z[i-1][1],z[i][0],z[i][1];m:=(y-w)/(x-v);t:=y-m*x;for v<=x{g.Set(int(Ceil(v)),127-int(Ceil(w)),c.RGBA{0,0,0,255});v+=.01;w=m*v+t}};f,_:=Create("o.png");k.Encode(f,g)}

Uncondenced:

package main

import (
    ."os"
    ."image"
    k"image/png"
    c"image/color"
    ."strconv"
    ."math"
    "fmt"
)

func main(){
    g := NewRGBA(Rect(0, 0, 127*4, 127))

    a, _ := ParseFloat(Args[1], 4)
    d, _ := ParseFloat(Args[2], 4)
    s, _ := ParseFloat(Args[3], 4)
    r, _ := ParseFloat(Args[4], 4)

    z := [5][]float64{{0,0},{a,127},{a+d,s},{a+d+64,s},{a+d+64+r,0}}
    for i:=1;i<len(z);i++{
        v,w,x,y:=z[i-1][0],z[i-1][1],z[i][0],z[i][1]
        m:=(y-w)/(x-v)
        t:=y-m*x
        for v<=x{
            g.Set(int(Ceil(v)),127-int(Ceil(w)), c.RGBA{0,0,0,255})
            v+=.01
            w=m*v+t
        }
    }
    f,_:=Create("o.png")
    k.Encode(f,g)
}
\$\endgroup\$
10
  • \$\begingroup\$ @LuisMendo it is. By default 0,0 is top left. Ill inverse everything as soon as I can. \$\endgroup\$ Feb 16, 2017 at 18:04
  • 1
    \$\begingroup\$ I never coded in go, so I don't know. Users here golf their code manually because they can save more bytes than a general minifier. Bad coding practices and tricks are welcomed here. For ex, wouldn't replacing struct objects with variables (like l1x, l1y, l1X, l1Y) be golfier perhaps? \$\endgroup\$
    – seshoumara
    Feb 17, 2017 at 11:40
  • 1
    \$\begingroup\$ @rugdealer This may help, in case you haven't seen it \$\endgroup\$
    – Luis Mendo
    Feb 17, 2017 at 14:37
  • 1
    \$\begingroup\$ Lost almost 400 bytes thanks to your link @LuisMendo \$\endgroup\$ Feb 17, 2017 at 15:56
  • 1
    \$\begingroup\$ @rugdealer Wow, that's a lot \o/ \$\endgroup\$
    – Luis Mendo
    Feb 17, 2017 at 16:11
1
\$\begingroup\$

dc, 120 bytes

Initially I thought I can't answer in dc, but I see that printing the syntax of a vector image is allowed.

?sR127r-sS[<svg><path d="M0 127 L]nrdn[ 0 L]n+dn32PlSn[ L]n64+dn32PlSn[ L]nlR+n[ 127" fill="none" stroke="red"/></svg>]p

The code calculates the translated coordinates of each point and generates the SVG syntax for the plot. Since an image editor has the origin at the top-left corner, I had to subtract the y values from height, 127 in this case, so that the image is shown as if the origin is at the bottom-left corner.

Run example: or Try it Online!

dc -f plot_ADSR.dc <<< "15 30 70 40"

Output:

<svg><path d="M0 127 L15 0 L45 57 L109 57 L149 127" fill="none" stroke="red"/></svg>

To display the image plot, save that exact output to a file and open it with Gimp, for example, or enter the text in an html page as I did above.

Explanation: dc is a reverse-polish desk calculator stack language

The script is a long concatenation of the SVG syntax string. The keyword M stands for move to coordinate and L stands for draw line from current position to given coordinate.

?                           # read input (in reverse order by default). Stack: RSDA
sR                          # pop top value, store it in register 'R'. Stack: SDA
127r-sS                     # push 127, swap top 2 values, pop them and push
                            #subtracting result, save it to 'S', pop it. Stack: DA
[<svg><path d="M0 127 L]n   # [..]n print string (push then pop). Stack: unchanged
rdn                         # swap, duplicate top, print (A) and pop it. Stack: AD
[ 0 L]n                     # printing
+dn                         # pop top 2 values, push addition result, duplicate it,
                            #print and pop it. Stack: (A+D)
32P                         # print a space
lSn                         # push value from register 'S', print and pop it.
                            #Stack: unchanged
[ L]n                       # printing
64+dn                       # push 64, pop top 2 values, push addition result,
                            #duplicate it, print and pop it. Stack: (A+D+64)
32PlSn[ L]n                 # print space, print register 'S', more printing
lR+n                        #push value from register 'R', pop top 2 values, push
                            #addition result, print it and pop it. Stack: empty
[ 127" fill="none" stroke="red"/></svg>]p   # printing
\$\endgroup\$
1
\$\begingroup\$

Processing, 111 bytes

size(400,128);line(0,127,a,0),line(a,0,a+d,127-s),line(a+d,127-s,a+d+64,127-s),line(a+d+64,127-s,a+d+64+r,127);

Used a simpler method of input compared to user41805.

Output (formatted)

0,0,90,80

enter image description here

0,10,50,80

enter image description here

5,50,0,0

enter image description here

5,50,0,80

enter image description here

15,30,70,40

enter image description here

24,32,127,48

enter image description here

64,64,64,64

enter image description here

Ungolfed code with formatting

void settings() {
  //size adjusts according to params
  size(a+d+64+r, 129);
}
void setup() {
  translate(1,0);
  background(255); 
  strokeWeight(3);
  
  //axes
  line(0,127,600,127);
  line(0,0,0,127);
  
  //graph
  stroke(53,113,9);
  line(0,127,a,0);
  stroke(228,77,40);
  line(a,0,a+d,127-s);
  stroke(26,39,232);
  line(a+d,127-s,a+d+64,127-s);
  stroke(110,27,109);
  line(a+d+64,127-s,a+d+64+r,127);
  
  //save to file
  save("a"+a+"d"+d+"s"+s+"r"+r+".png");
}
int a=24,d=32,s=127,r=48;

This program will save your images to disk in the same folder.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.