A better multichannel waveform~ replacement

So the native waveform~ object doesn’t play nice multichannel files (it doesn’t seem to retain their channel-to-display settings on save, and/or those settings don’t exist if a buffer is empty. So this means doing the set bufferName $1 to view the individual dicts. Not useful for comparing multiple dicts, or getting an overview of the decomposition.

The jsui from the “bird finder” (?!) patch is a good solution in that you can view a bunch of stuff, and vzoom to bring out quieter decompositions.

BUT it fails in a couple big ways.

  1. It doesn’t work (at all?) with really short buffers (i.e. @filterbuf dicts). Don’t know if there’s some kind of minimum size parameter in there or what, but it just displays “black” if you try to feed it a very short (64samples) dict.

Here’s a relevant bit of code to show what I mean:


----------begin_max5_patcher----------
6195.3oc6cs0qaajj9Y6eEDB69xtmH22ax7x5LIYx7vjcBhmcGrHy.CJIpyg
1TjBjT9XOAi+su8ERIdmMkXSq.DGDcjXyKU80UUc0cWUwe8kuX0ljOFjsx4q
c9EmW7he8ku3EpCIOvKJ98KVcv+iai7yTm1p3fmS17tUOnaJO3i4pCm6ro7X
G8y29TX7iuMMXat9V64tF7fChhU+AU9oy+n3RhOcHLNJHW8HfWNXxo7lGMbm
54Inguxq7IpOs7OcLP+3VswO9wUN+CYq+qW9R4GOXH68trSgixHXDeM8AGFP
wIXLnBqrOLJH1+fhRVsO5T3t0aNs+CgAOu9cYqtI9kvtPYohGQdP5aEOqMQp
GFnOv35.hCAYY9OFzpiNKHWvO6CRc1kd5vWsKbad1387t70b...QH.GfgHA5
AgjgkCPlhK7UyKquOJQ7LKuoevO8b+o3vaBRGfcQDE+xHqcobHmywDLWvrTP
KdceR5Ae0kwFRpPbnvCBpQ1CuFzIjTBTBln7TofKmbEjBMj.DrWT7gyZTEmR
l+GB18V+77zvMmxCt7srBXs.WknWzofj8kGt73UofrmRRy6FhqcdkTDni1D8
XOZvsPfk4g9QsY5Zm0AAP1.D65tnQGLPKiUHkU9mIJx0iU0iNajxX+TZxVg5
3eLIZ2fReXlTJCJLKQXPHBv.TByUJ+gvvgU1vCJYU0HT+VckxIaiB7SiRd9h
rhhNEjcCACocxODjlElDWseXk+wiUNbcYlC9uKQcibO2qHXA8gtzQkF7gvxq
Gc9n9oBzJW.UmR0RIejQtHhbHQfrwBS+RRor6rjjTcSRoqri9a0Wrr2rr4JZ
+tpA3ft59ALRA5trJiOHDDdLJY66C1UknWkbLHNL9XZPVPbtedA0et4cA68O
Ek+18Iw4Yg+SsjKZcmsuufF6rwy5HeSpTBt7TdLMbWRrjHp0WHOb4i6WbfTE
WQqxLpyH1+XGWrPXQ.L8zXlfIOkswOU1UUnJV1WsJOIIpdSmutnf84EMeLLN
tAJlmbr+FSCe7oAt1MIhFOLz8V0R1aOEqa8sBoh72JsDV+77ihJzlqe6+nuv
PtedPdntK.AN2n1bzSYaSShhpwu5V9PGsrSHkuM34vc4OUarAYKhSO7XoPzp
y8x6BeLHKu9wx8eLq9Qxx+jFzqbnSaJzheadvgiQBtn9ITyKxpprUs6U63CY
+qtMv+SGd0i2gYONDKcGCS0NT3I+QUKc85ZQ+tWzzpGrJIzxvWXb9pxQANON
Po4+YFNzNe8YGYGgCZDjw0CJACNP3OhKB6REtf6AXR2uz1nFFlf8BSndfIvf
3jvwJgGOOTy+7YBw1lb3fvzYmP1YuvyS8iyBEmVVjP24yNuNIUX1WpI575cA
aRNEuMP3WJ.675i961IvRQaDWmWm89fmcDbmyqyeRXi9oM9aeuPkiT968OuS
7SwI9bX7tjmk1LcfhV2HM0q9k79Tk3hBiC1JdfJJjObuHD.UxzbHXMk.gdtb
LnXfcn1yRH.bccjft6H4qV59l3C6EcHOkbTAWXjBKUemI.x86yO+cwn3opgH
yjrsyqEcpu2AJNmvHQKhakR2nWvFMLXyzSRkyz5Gdd5o3MqnKbVg2ALVbTJm
F5bQt2Yu.F14DF6jMloiBCDT20HOBD4RfbuRYN331NnS0Dq6v1XWLCrGSEly
c99zzjTyfHrveaOpDjb8HT8jaYnaw5ZeJkKkXSVPjCd035IxwYEi8xvBtWHa
fXPNz0UfHjw4dzTGagyFT9PMjxCKnbxiA4xo85j4e3XlYfEj4t1iyHbfGgiX
T0.wPvMIqf6AsFdjXERsjnUoaKpQdEd+mlOJl4oluQmBXP.0BduvIeo7dY.f
K2YiSngN.6gVi3.pmGlQQEq7jItCOYjh4Yjt3R5WrZnti5EJQszjYSQVSqex
8VynBGkEt.R8nxEpTHpwwiKpgm5fcLvcxfcAAu+ySGn5ZHOkKnyOPQMPmb4z
EgFNpXGdMgtIy78AOr6lIl1+rrNO0JBo9DDGXjRDYMFJFpDITF8fDwbVkXGy
FPHebaYK1TUEVsxSb7i8i9z+LXDnxCq2LJtZQF0Vol2YAR8lSGNUDxX7jdfc
rdi0.ke1OOAlZ2MkOlk2JMW1UMXWiXJ+AJ9GuLJZGxxSTNc9Yy.yN0k3bar7
O3Aw1rvGikK5rULau4TddRrYVWH54mb9yYzZBoNEz61zdOXrYqez1SxEyUHX
m6G4r6jdcUt1kNApWrDtDdarLUt.KrLJT5RsJU50DX6ozTwY3H25pQDzHL7Z
05.vU1n8f1v5LYoXe8JtIDSdJM4ziOo.fw7TDqEFnb3ZgcHWWL2sXkmgPqfF
fkBM1lFHAC+ihYaHDLTe+fTEwIYuy1m7iiChxjKwldNuWq1jPCRIBgc458cC
aAUHxhsPu9614HCVD4fnGNEkGVfT2HJw4LshFDslwHLNxkBKVXEHxFH1ho0U
3pgyyOkDET.SNmxj6JQRbzmb1mlbv4O6jj57ykhcWKJ5RUdVSvv0j1q1K2Fv
3hot97SAwN6RhCbDNcn1SmN7gaRCyIlbmmZsUH54zULy2YFgvKlflDWT1zEF
sDZm2lBoq1AdsQKlMrYgWLQmmRd14fe7mzC38ec0BLnRAFDEsl2P+hYC0KDs
IC8tSY4g6C2dN3RPKkuS9mxjdX5rMJQ7kcg9QIOd8PodQl.tqgMWTXtM.Rn6
cCPlEDIvAGem8UiCroCgD1Zkccn6Z2lSKzJNY.I2MXXoSGZrLb7ILJ7bWBV5
EYABrgmqPzRYOqSi6cv0Tnhc4Etvir.S6tT7bWSpsqoq.8rMKuXyVUtJBRCt
EFLZatnq.WFizy1P6QiV.XlA.7pkaKy1LV+Mfnm2.CrFgvTHfSPP8.xPKrzX
Xy185EdqECk+2XHkvit9PJWKry0XznaowC09yRAW6BDVNkg173qwXO3EkXg8
u.dmD2LFBOkJdDW3Z4dsRYtXp16Xq.OH98A7jahpVI1fYq8Zs+gTfErKgXlp
ssbaj+6cjw5WXrZSNbfNitogDLnOYJHhXgckFg+MkNWwBy2YnzXEkNN9dJV9
96q9CRLw4ul5GFKPl+9pQkmz5gcE2zky.clCsOzB4aTngidQkwvGgCntvKIQ
jMhrd98Sn0ezQlaFi5orKREqPDuVAisnE97iPL2Iri0Mx1p5HQuYcUeYdUiK
uqLvpmrvp+LwZ7rwpuLxpVVYYPlY0H6rnd54vqL3UjrHXVik5n67yxfbzxj7
zZzb0xv70Zfb1xr71Zjb2Zz72Zzb3Zj73Z7b4Zz74xfb5xj75ZJ410.420n4
30v440v450v460f47Ue48U249UO4+kQ4.V24AVSCHMsR2p8wrV2b78.gj4Nm
rZAVTutAIbODK9GD4IFU2UE0hxDikxgDDC6AY.4VGJrs2Yn5M3v7CaNu0RAg
aRtC4iTsQ9pO5mkw2sIG1DFG3bP7iLm0NGxpjYTN.0+2hU5awnmR2h7XxdEt
GjSbkiuJi5askLLcr9E5f8Kn96WHsXlArj2ef02h55vT9W9N2SwuKIL1gXhl
CGr1sYWDAnJxAXnG0E.3hi12ZmMUMGZ+8PHxJi5Bdnybb3KGXq7rz405h9fy
SO80GN70YYF.8UxuOMlaD9htVM.333647fXY.1lgM5v.EAzejiNXziNMC30h
e+AikuFmV0HJs4s3bfjBuifUHGtj3Jzl3JZAvUM8dsdivYJuQfPHt3PlB4Ca
QEL.jiVM2xn0mmgzefQbJTAOxyaXfNK4T51R4fRwEm1.iva37v3ySQ6WtXZy
Adk1rtZJDLUJDrvTnzWTioPovxhSgjoPgkryhRgnBQKinPxWLJDMEJD8kfBw
SgBIKLEBmRuLZbc4JMVcYsJptV50eoiRqUy0z6pl6+iQIa7iJlQ+4olzyxC7
x1Lvxjsv573T376X6.DjhVqy20NSBVwvoVXuynr6v7EtbIQD+IRt5hFiaD9Z
FDxoTLGBzadlG0B6MDEeurgraD5mabTUrLmy0sLyRwkNRTXr2MIiQ6AsPFmo
0Ob4mJtoQ4XaYE+D+ce3GMCM6rrH3xsfrG169P1Std55.gUEcVFBSckGedVI
n.PiV7HV3.URsdhNQFtO2cj80TaDTWng26R8hf9vcZPlPQr0HgN245wJvFhR
X98V7kHKtMi4NAhSV61q2DD203aYed6qnZbeUeaxBhLMFJgz0TFg4Uol1.rP
E.BdmgPJ86QiWfy5abPyLgjCtk5nEoGMNnIZbk++Rp4kJHl.mBqhFp+0gWob
nEjsPjwqWRyJNc5PP7oQEc5q9hgX30tcZCpvwK4i3G82JSnqrmb9Se2W+p+m
rfzrWklrKM7wjW8lOEu8U+TZhZO9e0eL5z2l7i9u5mTyGL6U+bfejbGKbhOr
+UkgMky24m6+puSNkrW8FwuiBb9SghKtVsPHO3PwZ8sB.c9JmuMPphr9Y+OH
gvGje.Phi+86dLn9Qwhi9ygGxdJIuyqhTo81WMU2pyeM7X8FXEM7lmRNIiC9
5sxkjXZRVlyaxC2995M5JZ767ObLXWGOPOQipqQRP0ZBBjM8TPTj5vyXnQC8
lR0Ev+TdxwjimJ2pdnolEpQwxyQKw929l+2ueopagNaBBNZng0thROB0FAoG
c0uGoUlDoUnh52BRmQdE0ZDJ52C0peOTq98Ps5NMTqp+B+nsM4u6u7e+8lrs
l523G55QEAOGgqv.6WL42FwQkAwg9H3IT3vGS7Ow7rDNBRFLVzmwMGFdGDmR
6729YGnCx.vpnvypCwWJcND9F.b336.z4+3yFiKcJC4Q6Jn9mQUT93gzQQgA
6tHhG+z1nfO635BLFV0l5XLKapiCtqvwwFtPJu4Pc.NTSPRnrz1eFJoVFJqU
9ZuiG1PtG0e1bzChvpHJhfo.paQBv0cs3dFiXvZ0OyAkKausA2Cw2lF6P3EI
913juTwM3hESVbxThmHuNOY06kiqORHWDRmLLkCVVJeJABGGeWA5Slzg2Mn9
TBtuR9bYC+PuonLx+RPg7ohgKcHbNEiBveOnut9MbTMK48mKh+p2vU.muBJ9
.5H+7eemNhvzu0Qq9cSypYNaMT3ajG0yiUrcRXrMJL6P58QjlbtprKbf3XRV
vmMs5k.cWCaTOMAt1HjbnKb4WeZuCITxegFU2+6sxc.Eyw1c7.FfMUny6NSD
6d5U3kpyf6tVlaEH.BRbKhcC86Pfl4P3sVjYVXYXSJk9bOhtz0YuZoOAYPsa
nmxo+fUhu1YryBAZkQPF2dXF1DYkd.sQqrasmJ3L.bsRooNS+N86IGoTGpwa
5aSvwoW.gqMtQUPpypbnUjnLBXJqWHKFv31GvzUoPbPAlKumeasdAWfptbqt
Bf0xc5VyHouDnntqzC1cMUZvEZHQv4VjJjuQ6pOY1dnBWnsoBSvBFylTA1Tw
BqhElJWHcu2dTAvToSlsoBSjN4DKRELSMW3Yah.ZHQfrEQ3ZHRPbsITvMU1D
XSpvXaV1TOs7lOt0BaRDlZrfYygxLcjLjMsXQMU1DhsIUXJVXSylTSGNkZ0N
DTyjNtOYy111NDt6XhXFjYkuNgTwo.VWH1Q5JOb8eww54t4gN+KaxWDS5ho1
FcMQPSlQ.M5FZhtEuChc0U+7hXYn1un5R6CV.qpURU9SaxXF4KHx1vK1DqId
iI7RzHo7ERjqLCPfT..QHPhJsQWS6rIKwVDisLXSvkflfacDaQDlNxoMckAa
pCtbaNnE1zgNwPaSElXQCxpuOfyKUXpykbaZcGapaDpwNsFVXrKtVUtvT+H3
HaSEFMgXXqSr4.BP8nnbBs46H.YwtXz1nxvUpQ0Ylww8zlMwCvrL.ItXuJXx
zssNOiPqUqU5.Mg7fsRPN8vpsawVXgwKbjM82FYrUbaNWPjo1OQVmJLZJoXK
tnMHi8wvlPgot7Qni5IqPuRWNEXqEJsPLgdYuUo5c5ni1rEiYpEHrMWcPDb1
rCJeu1UX1BJr0gKC8hKVypbXaxNv4gcHfu7rioNyYSGXfdSYmdpuxBsiswuL
jVGKoT6HFc9IMSb6CYykRCZ5vYXlsoBiVrGa5BLzzoCfs4Tif3IrcLXaQDvI
Ni49MSxXnBue0gPgtrfz3W.ZkE2r9uJWZNHVO1rEWZNSYZV+C3dInBTo+ZOQ
Uvkrw9ztvj2nx+429i0JYFkYuc2j9lG2GFEsMIJo2TzuLraVoOqZo6d408KN
.UsjB5J3m0BHliXpuI9Bk1LVz0WGr7BIThG.IOcBCIlOm5atxg7jeCzwkht7
LAPO8SB34BH5uINDT7Lado9xhwgJ3V3MRVY4qn9jzyIPuvurZW2o7jGS82EV
DgHfNi42GpHsjJNytxZ4UpWo6UuGcIsctS8OeZquocj05JHbJDqvQjG10S+M
wbBYdsfkUmirrZ2ANRbxpNSHiizcDt..jz9FHivQ0091vXY8ZH3bmC1CAY5m
cYOLhK7zscmiPNr1i2CHiKT4EHndBmq9FA4wUrBk03ZMWF9b+3.hwSPjptfr
6YwWW4BK3p+VmLbSAYFEngaYlmx35uouaKhbb0Bhx1shSnFlfkcfJffTRmRE
aRGhSBooCMubtqfkz3B.55p+l3NgPsu9p0XBgUx+xwfXm23Gm47lfCgajUAv
plKsfR26yhBE9q82jkCjqP4SMFSsOrL8dv+irZ0XjgI0d0VAOT4iAUOavhB8
wZmaGxOZMgylC3EJVKC3zHfVmhYTj70.XUocoALBEtTjdqrG1N8syg4yJFAK
Mf09aiXDjKqOQJqCs+V2W5uo7Cnsl5zkHZFKsSPZV0+WwWKANg8VBIYcRQ7G
hNE7UPSo7tFDRVTW0iLdlIT0CW3BxDoIOGeSbwEZmd1lBdg4hu8S92FS.PBu
xzlx4XLRqiijuNNVNl3GRCBtQtP0KHKE.XsitR+2VNF3+KHJJ4Yy4fpkvqeQ
UDund.f6ni8Jb7mqsjBcgpw.nhdO5hvnmNrIH8GDdscScTphdsRbiJbgfp3F
XUq6VkIR0ND9C0p.0SvtKivXXuBUDdwbYDc.d3QmFzBv3E2ixavXIXaAS1ah
01UR0NRB01LYZ0Clp9T2undlUx0DMMTOGSJSfiBtnZ44sUJ.0YVx0YFxUrJR
qwTtrm6RgMVEhWW565LWT5NOTpkCJfUWAqV2ovJ0M1ZuJC5LGazTNjfWS3Ba
GmKCjsxX0NK5Icm4Vc9ZQq27rbl30hWPbp2cDXwnoxxHo7g47rP78AmmSuL4
sg.BWQWqKw0ky7J1PbnGZtvB2YFKpkao87R.oVZdO.mKLDI8YlyOuqjhAuGl
w6VpF0lwcQ8x3c8t9XlTzAfuVlK1d7AXdLqSwesZNBby5yLuqQe9ne74ZH9s
Q0oBaZ6zUSRxPLxlK6Hkor14xpUwE21QpNldoploV8F10LuZszedLfZMytLm
aSNT0GayowQuhdEwnd4myAv96VXD0a2yKCNfzERJxzDl5v9AFe4g2aELdvrn
dhbb0RscmI8KA0IG2QUruVAt969ouQtas.FnrzO+lSwOkbRt7DS.hvcL9I+p
fn5Ew4qwRrxs1gjMbKGEAiYButYEUQPJXlrwhnWEmeald6avXoGc0p3H+avw
wFJYMAvEt0dQNBAmogdQfYdnWoWERWN9737kKqtYZrVqg6Ugq5dKWTEDC0Zb
H26.8xaz9PXZsiUaGx57UhPC3h0gZz4tKsuDm8PuCWJ5eH8A9RTXVdsRltpr
Fjreu7ERd4rQJ5RTmR8DoV2C0bOXK5mFNwo6KJVtrsqsjDL84PL4AU+bpGiJ
W8SlavClvlKVDNxSxEsTfIqGzDdaOZifSuKQO9U+jvXSeR2JbhnF7jPf43IA
LgmfywSxTvCciOHnqAOoYQpGRttmj1FYiWOBxGRiWGBMdMHz90eP+u1CZ95N
Ps9PMBEEMyNRHnzXgx90W12dNUtxRcGtIWSnlbkgYxUFhIcusR8ukR8rcRkq
90Cur+0xq953UUxqiPGo6NfqIbQtoPE4lCSjqMDQLRVq49Zdc6t3UFNHWWnf
LaxakyM+ZC6iaIjOLNbOtYEh1g0wnJF8EJG2LsTaif6lLl7V4aZHZbMgmwrv
vUBCCSLIYPnWLKjUsI0Ne8EWqImIGFEWYHTbkF3Vhw3pqcXVuX0vhv.oqwBE
haVxpcHOzMUcsg4vbQfUCmAioPiBggYhBqFpBFSfFEdByDAVKLDLmBGIzClI
hqdHFzM0MRXEbkgTvsy.sBc.iAWi107al.aEV.FX2wvPA3pBCfalgtvOCi3M
7BmCAJBxCSX.uBuv4v5i2rPcS5IF2yKguW9ud4+Ojmcgn
-----------end_max5_patcher-----------

(just feed a folder of short samples)

  1. A more niche, but still useful case is where you have buffers with bipolar signals. (as in the CV splitter example). In that case, seeing a unipolar/abs’d buffer doesn’t really communicate what’s happening. So having an optional flag for @bipolar would be great. You would obviously lose out on half of your vertical screen real estate, so the default should be unipolar, but it would be useful in other cases.

will see how I can adapt the code - which is already adapted from cycling’s. More soon

1 Like

I have a prototype implementing anysize buffer and a bipolar flag, and a dirty helpfile in progress… let me know if you want it posted here.

1 Like

I dare you to post it.

1 Like

I’d offer forward a double dog dare, but don’t know if there’s a cultural translation for that.

dares don’t work with me. if it works with you: I dare you not moan about the helpfile in progress :wink:

Archive.zip (4.2 KB)

p

1 Like

Dares, traditionally speaking, have to be escalated. So a normal dare can’t, successfully, be called after a double dog dare. A triple dog dare, at minimum, would be required to even be brought into discussion. And even then it would be seen as the bare minimum.

That being said, it looks great!

(the helpfile on the other hand…)

1 Like

The new version is really nice. Very useful for dicts and such.

I don’t know if it’s worth adding exposed control for it, but if possible, you should make the colors more high contrast (maybe using the default colors from waveform~.

1 Like

Thanks!

I was thinking of that - as I said above in the thread, feel free to propose a colour scheme (the 4 rgb is in an array on line 13, and the background is at line 25)

Even better if you have more than 4 colours, we could put the number of groups higher than 3 :wink:

//adapted from warpy2
//canvas setup
mgraphics.init();
mgraphics.relative_coords = 0;
mgraphics.autofill = 0;
var w = box.rect[2] - box.rect[0];
var h = box.rect[3] - box.rect[1];
var vz = 1.;
var nchan = 1;
var bufsize = 1.;
var dsbufamp = [0];
var groups = [0];
var groupscolours = [[0.808,0.898,0.910],[0.5,0.,0.],[0.,0.5,0.],[0.,0.,0.5]];
var ngroup = 1;
var isBipolar = 0;

function loadbang(){
	bang();
}

function paint(){
	var u,v;
	with (mgraphics) {
		// background
		set_source_rgb(0.2,0.2,0.2);
		rectangle(0,0,w,h);
		fill();
		// wave
		if (isBipolar) {
			for (v = 1; v <= nchan; v++){
				set_source_rgb(groupscolours[groups[v-1]]);
				for (u = 0; u < w; u++){
					move_to(u,h*(v - 0.5)/nchan);
					line_to(u,h*(v - 0.5)/nchan - Math.max(Math.min(dsbufamp[u+(w*(v-1))]*(h/nchan)*vz*0.5, h*0.5/nchan),h * -0.5/nchan));
					stroke();
				}
			}
		} else {
			for (v = 1; v <= nchan; v++){
				set_source_rgb(groupscolours[groups[v-1]]);
				for (u = 0; u < w; u++){
					move_to(u,v*h/nchan);
					line_to(u,v*h/nchan - Math.min(dsbufamp[u+(w*(v-1))]*(h/nchan)*vz, h/nchan));
					stroke();
				}
			}
		}
	}
}

function onclick(x, y, button, mod1, shift, caps, opt, mod2){
	var track;
	if (shift){
		track = Math.floor(y*nchan/h);
		if (groups[track]){
			groups[track] = 0;
		} else {
			groups[track] = ngroup;
		}
	}
	bang();
}

function bang()
{
	mgraphics.redraw();
}

function bipolar(flag)
{
	isBipolar = (flag != 0);
	downsamplebuffer();
}

function vzoom(x){
	vz = Math.max(x, 0.);
	bang();
}

function getgroups(){
	outlet(0,groups);
}

function nextgroup(x){
	ngroup = Math.max(Math.min(3,Math.floor(x)),1);
}

function setbuffer(buffer){
	var u;

	buf = new Buffer(buffer);
	bufsize = buf.framecount();
	nchan = buf.channelcount();
	groups.length = 0;
	for (u = 0; u < nchan; u++){
		groups.push(0);
	}
	downsamplebuffer();
}

function downsamplebuffer(){
	var u,v,x, bank;
	var samperpix = bufsize/w;
	var nsamps = Math.max(Math.floor(samperpix),1);
	dsbufamp.length = 0;
	for(x = 1; x <= nchan; x++){
		if (isBipolar) {
			for(u = 0; u<w; u++){
				var accum = 0, accumN = 0;
				bank = buf.peek(x, Math.floor(u*samperpix), nsamps);
				// compensating for Max's api not returning arrays of 1
				if (!Array.isArray(bank)) {
					bank = [bank];
				}
				for(v = 0; v<nsamps; v++){
					if (bank[v] >= 0) {
						accum = Math.max(accum, bank[v]);
					} else {
						accumN = Math.min(accumN, bank[v]);
					}
				}
				if (Math.abs(accumN)> accum) {
					dsbufamp.push(accumN);
				} else {
					dsbufamp.push(accum);
				}
			}
		} else {
			for(u = 0; u<w; u++){
				var accum = 0;
				bank = buf.peek(x, Math.floor(u*samperpix), nsamps);
				// compensating for Max's api not returning arrays of 1
				if (!Array.isArray(bank)) {
					bank = [bank];
				}
				for(v = 0; v<nsamps; v++){
					accum = Math.max(accum, Math.abs(bank[v]));
				}
				dsbufamp.push(accum);
			}
		}
	}
	bang();
}

function onresize(){
	w = box.rect[2] - box.rect[0];
	h = box.rect[3] - box.rect[1];
	downsamplebuffer();
}

1 Like

Yeah that’s loads better as is.

I’m still open to less brutal more hypster slick colour scheme…

If you’re going for slick, I would just expose the parameters as what people think is slick (and prefer) will be different to different people.

I would go with some “standard” color scheme (Max, Reaper, Logic, etc…) and leave it at that OR just expose them.

This exposes set_bg and set_fg as arguments.

//adapted from warpy2
//canvas setup
mgraphics.init();
mgraphics.relative_coords = 0;
mgraphics.autofill = 0;
var w = box.rect[2] - box.rect[0];
var h = box.rect[3] - box.rect[1];
var vz = 1.;
var nchan = 1;
var bufsize = 1.;
var dsbufamp = [0];
var groups = [0];
var groupscolours = [[0.808,0.898,0.910],[0.5,0.,0.],[0.,0.5,0.],[0.,0.,0.5]];
var ngroup = 1;
var isBipolar = 0;
var bc = [0.2, 0.2, 0.2]

function loadbang(){
	bang();
}

function paint(){
	var u,v;
	with (mgraphics) {
		// background
		set_source_rgb(bc[0],bc[1],bc[2]);
		rectangle(0,0,w,h);
		fill();
		// wave
		if (isBipolar) {
			for (v = 1; v <= nchan; v++){
				set_source_rgb(groupscolours[groups[v-1]]);
				for (u = 0; u < w; u++){
					move_to(u,h*(v - 0.5)/nchan);
					line_to(u,h*(v - 0.5)/nchan - Math.max(Math.min(dsbufamp[u+(w*(v-1))]*(h/nchan)*vz*0.5, h*0.5/nchan),h * -0.5/nchan));
					stroke();
				}
			}
		} else {
			for (v = 1; v <= nchan; v++){
				set_source_rgb(groupscolours[groups[v-1]]);
				for (u = 0; u < w; u++){
					move_to(u,v*h/nchan);
					line_to(u,v*h/nchan - Math.min(dsbufamp[u+(w*(v-1))]*(h/nchan)*vz, h/nchan));
					stroke();
				}
			}
		}
	}
}
function set_bg(r, g, b) {
	bc[0] = r;
	bc[1] = g;
	bc[2] = b;
	mgraphics.redraw()
}

function set_fg(r, g, b) {
	groupscolours[0][0] = r;
	groupscolours[0][1] = g;
	groupscolours[0][2] = b;
	mgraphics.redraw()
}
function onclick(x, y, button, mod1, shift, caps, opt, mod2){
	var track;
	if (shift){
		track = Math.floor(y*nchan/h);
		if (groups[track]){
			groups[track] = 0;
		} else {
			groups[track] = ngroup;
		}
	}
	bang();
}

function bang()
{
	mgraphics.redraw();
}

function bipolar(flag)
{
	isBipolar = (flag != 0);
	downsamplebuffer();
}

function vzoom(x){
	vz = Math.max(x, 0.);
	bang();
}

function getgroups(){
	outlet(0,groups);
}

function nextgroup(x){
	ngroup = Math.max(Math.min(3,Math.floor(x)),1);
}

function setbuffer(buffer){
	var u;

	buf = new Buffer(buffer);
	bufsize = buf.framecount();
	nchan = buf.channelcount();
	groups.length = 0;
	for (u = 0; u < nchan; u++){
		groups.push(0);
	}
	downsamplebuffer();
}

function downsamplebuffer(){
	var u,v,x, bank;
	var samperpix = bufsize/w;
	var nsamps = Math.max(Math.floor(samperpix),1);
	dsbufamp.length = 0;
	for(x = 1; x <= nchan; x++){
		if (isBipolar) {
			for(u = 0; u<w; u++){
				var accum = 0, accumN = 0;
				bank = buf.peek(x, Math.floor(u*samperpix), nsamps);
				// compensating for Max's api not returning arrays of 1
				if (!Array.isArray(bank)) {
					bank = [bank];
				}
				for(v = 0; v<nsamps; v++){
					if (bank[v] >= 0) {
						accum = Math.max(accum, bank[v]);
					} else {
						accumN = Math.min(accumN, bank[v]);
					}
				}
				if (Math.abs(accumN)> accum) {
					dsbufamp.push(accumN);
				} else {
					dsbufamp.push(accum);
				}
			}
		} else {
			for(u = 0; u<w; u++){
				var accum = 0;
				bank = buf.peek(x, Math.floor(u*samperpix), nsamps);
				// compensating for Max's api not returning arrays of 1
				if (!Array.isArray(bank)) {
					bank = [bank];
				}
				for(v = 0; v<nsamps; v++){
					accum = Math.max(accum, Math.abs(bank[v]));
				}
				dsbufamp.push(accum);
			}
		}
	}
	bang();
}

function onresize(){
	w = box.rect[2] - box.rect[0];
	h = box.rect[3] - box.rect[1];
	downsamplebuffer();
}

2 Likes

I was contemplating making the number of groups open but I need to check how max’s JS handles arrays of colors of dynamic lengths…

edit: I’m on it. It works simply. Update soon.

edit-squared: now this code works with the patch. All sexy and dynamic.


----------begin_max5_patcher----------
1508.3oc2ZssiaaCD8Y6uBBg9PKfWWQRcMOWfh7P+BRBBnsos4FIQAIYudSP
x2d4M60ahVuTRTJoEKf7ZJZNbNblybQ5Kym4shehV6AdC3cfYy9x7YyTCIGX
l46y7xImVmQpUSyq3P9JZk2B8sDeienIi1ntIxLZcyiYT0rOOO8jZdrjpkk3
F.uUjhcdfOXlRIoY8dVwtOVQW2nmUhOZo+B.FJuF5KuhDib4mHjNq3rvglAY
aTRlu596BB8tr3UjbZCs5izBxJ8lyWdquNet7xBK09sYbgPurpUzZZQCogwK
tZamFhka0X0Ueykq2zVAYa4U4D0JFMHPLMMRtEhh5KH5+SADShUm8w9+Z.hv
PEHh6KHB+4.hv.01N4WBPLILVsa5s6Lx0fXA8AwBedUanmT6Twx+IPMsYWE+
P4ZdFuBv.ak+0NsGrCzd2ltKcY3B.Dpr7gXzsQnvVPHrWePgmQo+BlRwZRMX
BbP1R8yx4hvQcwzQLDKW3izt0DNx0VS4z5ZxN5OXNILk9Gxo+VZMUC9Mnc.M
BZCP6JSuKBUa54GdaDF0BbF6MXVrolPJJQQLBi6KsNdjSvXxAD+XMf32W.A+
+K.ILHUAH5Hn8APfSaLqU6lhHUgAAxHUF5hD7sgkfeDVPIdNjfcMOujkQGWc
FEp4FSTVDgnNyPBS6iNKzsbQThV0YdMETKrnnbvpCa2RqpABCe.8TIshI+UB
MncPw+EAk1xKQmJdn9zFYxKwuCdAHmp5h3lkhMG6HEnRQC71+BHzXv58jhBZ
lCz3PU7u3zqS4oaJbrKsui.9ir+rVeiTr+HMmWmrsQQCOwywMEx.TpySgD47
BRZ3614.hLlvq4lbYoJSZigcv4qVadCwSUtyqXk7LR0UoMORr65fXPXxS042
IO.m5vKCjqHzA04jrrwU0MY6A8UptNk3No5gtT0Kq3qE2Rq3.evcPwEn7CLR
7od3I.UR80soHVWJjtGjciSLXLvEgyvNJ5Y.CB4GJ+W0szWG4FUXRH.qsUBC
5N1zuFUzdpuYbxFE++PU5WuMf3Hc4.pF0D08xAPPGp2ayNv1rTPTXxE7aiO.
XN4iPvqnL5FB36ThR1morhZRdYs1MXAXKSRPvJ.wiLAgtalXATHaZ2qYLzVA
AINzXPGu3aF9gAmSknraQ8yV0ZbCNDDJwgjvNaQ.iGKKhn.i4.bAfTVl8Hno
hIzmLJXOIajqQ1vUfShUkJi5QAigiEvDD.8u1UQDpZbwBSNlXr1WIt6XQvH3
qbizHFGWEsIQ.TQYDm1cWEzXwXLknfwXHQYLD28PHPnSajRFa8m.qn6IGY7C
UuATumss4N8v7B.4beE.Mbc+F9yCEpOW99h2W71shAoOt.vZ.OH8mVY5JAci
7GzrmJaOgXWz.jc6WN16JDxVMmOrTs.jrJJYyilUW9DmNTQ2rPud63zZvJhX
yH9oanaIGxZL88328+iypSFqftlenn45FtMfNfDfBe5Y0g0c6JpKc.A5z37W
PrQuPPSeBN2Zytm1ev3TH3DjVOLPwKmnbOS5NGMxkZ9NyyesdrOtU14P8odT
2qjA5Rk93m477Q2DW27KHpus5.F8euG0mQm866C1Jw0M7595CrKJIKSrX4zm
WU2QF8gk2+J1+84jGIHxkQd0uRPAxOr+cvv5mZtZIUwk9t2uL09RN9yQmZQP
u0m0ByKyvBvS6rMz5FVg5oke8jhzSp0S.qkTnMRRtcfCUR91JofgJInsRBOT
IgrURnAJIb7TYQfirPRm2NCSR1X6gRbv4DFaqjF74DzVIMT+I0hLIVDvzoRR
1nRPWHnIyaJbxHxswsEE4Bh7oRkP1HIjuKjDdxjjUQLbgfrgHB9bGNd0FZ0K
mUmaEcX6hFNAhNncQiFlnmL5XqXIcQ.ZXzjoS1vSBQtPR1Pp.gtPR1Pp3jio
9cJoqRgTVdjVUalsRFhp0tmq7HhWLW+lfn+JV80J5Q144qdQr8HUhpsZDkZc
nRWH2oHcW08x4BeqBQ4dZOagj+57+Ebq9oeK
-----------end_max5_patcher-----------

and

//adapted from warpy2
//canvas setup
mgraphics.init();
mgraphics.relative_coords = 0;
mgraphics.autofill = 0;
var w = box.rect[2] - box.rect[0];
var h = box.rect[3] - box.rect[1];
var vz = 1.;
var nchan = 1;
var bufsize = 1.;
var dsbufamp = [0];
var groups = [0];
var bc = [0.1, 0.1, 0.1]
var groupscolours = [[0.8,0.8,0.8],[0.5,0.,0.],[0.,0.5,0.],[0.,0.,0.5],[0.5,0.5,0.],[0.,0.5,0.5],[0.5,0.,0.5]];
var ngroup = 1;
var isBipolar = 0;

function loadbang(){
	bang();
}

function paint(){
	var u,v;
	with (mgraphics) {
		// background
		set_source_rgb(bc);
		rectangle(0,0,w,h);
		fill();
		// wave
		if (isBipolar) {
			for (v = 1; v <= nchan; v++){
				set_source_rgb(groupscolours[groups[v-1]]);
				for (u = 0; u < w; u++){
					move_to(u,h*(v - 0.5)/nchan);
					line_to(u,h*(v - 0.5)/nchan - Math.max(Math.min(dsbufamp[u+(w*(v-1))]*(h/nchan)*vz*0.5, h*0.5/nchan),h * -0.5/nchan));
					stroke();
				}
			}
		} else {
			for (v = 1; v <= nchan; v++){
				set_source_rgb(groupscolours[groups[v-1]]);
				for (u = 0; u < w; u++){
					move_to(u,v*h/nchan);
					line_to(u,v*h/nchan - Math.min(dsbufamp[u+(w*(v-1))]*(h/nchan)*vz, h/nchan));
					stroke();
				}
			}
		}
	}
}

function onclick(x, y, button, mod1, shift, caps, opt, mod2){
	var track;
	if (shift){
		track = Math.floor(y*nchan/h);
		if (groups[track]){
			groups[track] = 0;
		} else {
			groups[track] = ngroup;
		}
	}
	bang();
}

function bang()
{
	mgraphics.redraw();
}

function bipolar(flag)
{
	isBipolar = (flag != 0);
	downsamplebuffer();
}

function vzoom(x){
	vz = Math.max(x, 0.);
	bang();
}

function getgroups(){
	outlet(0,groups);
}

function nextgroup(x){
	ngroup = Math.max(Math.min((groupscolours.length - 1),Math.floor(x)),1);
}

function setMaxGroups(nbarrays){
	if (groupscolours.length <= nbarrays) {
		for (i = groupscolours.length; i <= nbarrays; i++) {
			groupscolours[i] = [1.,1.,1.]
		}
	}
}

function setbg(r, g, b) {
	bc[0] = r;
	bc[1] = g;
	bc[2] = b;
	bang()
}

function setgroupcolor(i, r, g, b) {
	if (i < groupscolours.length) {
		groupscolours[i][0] = r;
		groupscolours[i][1] = g;
		groupscolours[i][2] = b;
		bang()
	}
}

function setbuffer(buffer){
	var u;

	buf = new Buffer(buffer);
	bufsize = buf.framecount();
	nchan = buf.channelcount();
	groups.length = 0;
	for (u = 0; u < nchan; u++){
		groups.push(0);
	}
	downsamplebuffer();
}

function downsamplebuffer(){
	var u,v,x, bank;
	var samperpix = bufsize/w;
	var nsamps = Math.max(Math.floor(samperpix),1);
	dsbufamp.length = 0;
	for(x = 1; x <= nchan; x++){
		if (isBipolar) {
			for(u = 0; u<w; u++){
				var accum = 0, accumN = 0;
				bank = buf.peek(x, Math.floor(u*samperpix), nsamps);
				// compensating for Max's api not returning arrays of 1
				if (!Array.isArray(bank)) {
					bank = [bank];
				}
				for(v = 0; v<nsamps; v++){
					if (bank[v] >= 0) {
						accum = Math.max(accum, bank[v]);
					} else {
						accumN = Math.min(accumN, bank[v]);
					}
				}
				if (Math.abs(accumN)> accum) {
					dsbufamp.push(accumN);
				} else {
					dsbufamp.push(accum);
				}
			}
		} else {
			for(u = 0; u<w; u++){
				var accum = 0;
				bank = buf.peek(x, Math.floor(u*samperpix), nsamps);
				// compensating for Max's api not returning arrays of 1
				if (!Array.isArray(bank)) {
					bank = [bank];
				}
				for(v = 0; v<nsamps; v++){
					accum = Math.max(accum, Math.abs(bank[v]));
				}
				dsbufamp.push(accum);
			}
		}
	}
	bang();
}

function onresize(){
	w = box.rect[2] - box.rect[0];
	h = box.rect[3] - box.rect[1];
	downsamplebuffer();
}

Out of curiosity why bang() instead of mgraphics.redraw()?

Just discipline: I keep all my status update routine in that function so if/when it grows I call them all without any omission…

Useless post just for your information: plot~, used as a single channel buffer display, is still buggy with Max 8.0.3. It only shows channel 1.

1 Like

this is where supercollider is better than max: systematic visualisation of buffers.