tag:blogger.com,1999:blog-23093498680712552942024-03-04T22:29:46.662-08:00Nothing SpecialPutting the Bla in BlogSpencerhttp://www.blogger.com/profile/12246776537628500183noreply@blogger.comBlogger44125tag:blogger.com,1999:blog-2309349868071255294.post-80825658655267079462023-10-24T23:11:00.001-07:002023-10-24T23:11:28.407-07:00Markdown to PDF with Pandoc on Ubuntu 20.04<p>I've been ok on the LTS ubuntu for my daily driver for quite a while. But today I'm frustrated yet again I cannot export a wiki page from github to PDF. Maybe it's fine on newer versions.
Todays issue of the day was:</p><p><span style="font-family: courier;"> Error producing PDF.<br />! LaTeX Error: File `pdftexcmds.sty' not found.</span><br /></p><p><br /></p><p>
Fixing it depends on how you installed latex. I installed texlive-base and a couple other packages. You'll find many refrences through your search engine of using the tlmgr utility to install the missing packaged. You shouldn't do that though unless you used that for your tex installation, which I didn't; I used apt. So I must figure out what apt package has that file. To fix this particular one I just needed:</p><p><span style="font-family: courier;"> sudo apt-get install texlive-latex-recommended</span></p><p><span style="font-family: inherit;">Ok so one down. How many more to go?</span></p><p><span style="font-family: courier;"> Error producing PDF.<br />! LaTeX Error: File `letltxmacro.sty' not found.</span></p><p><span style="font-family: courier;"> </span></p><p><span style="font-family: inherit;"> ...</span></p><p><span style="font-family: inherit;"> [sounds of googling]</span></p><p><span style="font-family: inherit;">...</span></p><p><span style="font-family: inherit;"><br /></span></p><p><span style="font-family: courier;"> sudo apt-get install texlive-latex-extra</span></p><p><span style="font-family: courier;"><br /></span></p><p><span style="font-family: inherit;">Success. 2 deep isn't too bad. Hope this helps. Good luck!<br /></span></p>Spencerhttp://www.blogger.com/profile/12246776537628500183noreply@blogger.com0tag:blogger.com,1999:blog-2309349868071255294.post-60470088735435364502019-02-27T10:19:00.001-08:002019-02-27T10:19:08.922-08:00MYWIFIHIFIBIPI (Wi-Fi and bluetooth playing bi-amped hi-fi system based around a raspberry pi and 5.1 receiver) or: Another abandoned projectI first was given my aunts old Onkyo TX-SD696 5.1 receiver. Then I found a pair of JBL ARC 1000's on the local classified ads. Then I went crazy.<br /><br />See the rub was that several reviews of the loudspeakers mentioned that they really shine when bi-amped. I did a little research and learned what that means and that most people who think what that means don't actually know what that means.<br />
<br />
Since I can't help but show off my new-found knowledge I'll share it. Bi-amp, etymologically just means 2 amps, but there are bountious ways to use 2 amps incorrectly. Bi-wiring is an orthogonal idea where you run 2 lines (4 conductors) instead of 1 (+- only). You really need both to gain any advantage AND you need a preamp crossover. This is where the real advantage is to be gained: by allowing the amps to focus on amplifying their respective audio band (woofer or mids & tweeter) without the other band taking any headroom. Also the amp drives the horns directly without the other drivers electrically coupled in. The passive crossover is still there, but since all the content managed by each amp is in the passband of the channel its driving, it shouldn't have much any effect on phase or amplitude of the signal reaching the speaker. I'm not an audio expert but that jives well with the signal processing & circuit theory I learned in school. Interestingly they say these crossovers are always active, though I don't see any reason why they HAVE to be (as long as your input source has enough oomph).<br />
<br />
Since its probably a stereo system (though I did recently acquire the traditional mono version of Pet Sounds) you actually need 2 crossovers, 4 amps, and 8 wires (4 +- pairs)! Not to mention you need speakers with 4 posts on the back and not just 2.<br />
<br />
Well, with these new (to me) speakers, the 5.1 receiver, and some salvaged speaker wire I have most of those things. I don't actually have 4 amps, but I have 5 amp channels, which I can utilize.<br />
<br />
What about the crossovers?!<br />
<br />
<a name='more'></a><br /><br />
Well, first I needed to identify where the crossover point was, else you'll send information to a channel that will get filtered out by the passive crossover in the loudspeaker. Well a quick look at the manual says the sub and mids are separated at 930 hz. So now I just need an inexpensive crossover at 930 hz....<br />
<br />
Still looking...<br />
<br />
umm...<br />
<br />
Nope.<br />
<br />
I did a lot of searching for a lot of keywords but "active crossover" was the only thing that brought the right idea. After searching Linkwitz-Riley (a crossover filter design) I found <a href="https://www.amazon.com/Linkwitz-Riley-Way-Active-Crossover-Kit/dp/B00FD21I4C/ref=sr_1_7?ie=UTF8&qid=1507225160&sr=8-7&keywords=active+crossover">1 product on amazon</a> that would do what I needed, but it would more than double the cost of the project and it only had 100hz resolution. There were many others that were even more expensive or near the same price that I didn't look at much, and there were many cheap crossovers that are just not anywhere close to the frequency I need, but nothing cheap seemed like it would work. I toyed with just designing my own and trying to build it out of spare parts (since it only deals with the pre-amped signal, it doesn't need the power capabilities of those built into loudspeakers), but that seemed like more work and more prone to error/mismatches due to part tolerances etc.<br />
<br />
So (perhaps unsurprisingly) I turned to software. I could just use a raspberry pi zero W with a usb soundcard that uses an optical link to the receiver which is a win on many fronts:<br />
1. Arbitrary crossover cutoffs, and easily interchangeable filter designs.<br />
2. Optical connection uses a plastic cable eliminating any ground loop concerns<br />
3. It can serve as a bluetooth receiver (I do a LOT of listening streaming bluetooth from my phone).<br />
4. It can serve as a wifi controlled DNLA renderer allowing me to play any files on my media server without my phone's lousy bluetooth connection, but still controllable from my phone.<br />
5. Possibly might be able to stream spotify directly on the device, again skipping my ageing phone's processor.<br />
6. Cost comparable to the cheaper options for hardware crossovers<br />
7. Better hacker blog cred. I guess.<br />
<br />
I looked at some other alternative SBC boards, but it seemed the pi zero w was the most cost effective and well supported.<br />
That said though, after some discussion with the Mrs. I decided to combine this with <a href="https://familyopengameconsole.blogspot.com/">another project</a> and upgrade from the zero to the full pi 3 model B, which has 4 cores and a faster clock. This should allow for lower latency with the same filtering, and then we can play some video games on it on occasion too. This makes it more an HTPC than a dedicated audio streaming device.<br />
<br />
I also gave up on this project because the blueooth audio streaming didn't workout on the pi, nor did the games (no openGL ES support in the game engine). Now I just use the speakers with single wiring because misusing the 5.1 for bi-amping meant I had to disable the EQ for the front channels and it just didn't seem worth it. Its nice to just have a quick knob to crank the bass or treble. I could still do the pi as a crossover, but I'd have to connect my TV and blueray player audio to the pi.<br />
I just posted this for completeness.Spencerhttp://www.blogger.com/profile/12246776537628500183noreply@blogger.com1tag:blogger.com,1999:blog-2309349868071255294.post-88068308696390318102019-02-27T09:53:00.001-08:002019-02-27T09:53:52.392-08:00Setting up a Raspberry Pi 3 as a Bluetooth Speaker with NO Pulseaudio...I really don't have much against pulseaudio. I use it daily on my daily computing machine. But believe it or not, I don't do my daily computing on a bluetooth speaker. No, I'm of the persuasion that pulse is a great desktop audio system (though I have high hopes for <a href="https://pipewire.org/"> pipewire</a>), except for when it isn't. For pro-audio (making my own music) I use <a href="http://www.jackaudio.org/"> JACK</a> and it's awesome, and you already know I use pulse for daily web browsing, music listening etc, but neither of these cases are very much like a bluetooth speaker, are they?... We need an audio system that's basically going to serve as part of an appliance. But what else is there?!<br />
<br />
OSS?!?!!<br />
<br />
No, there is not oss.<br />
I mean, there is, but it was replaced for good reason.<br />
<br />
ALSA. I'm trying to lead you to alsa. Alsa is actually the audio driver behind both JACK and pulseaudio so its lower level, but you've got a headless system for crying out loud that just connects bluetooth and streams the audio, what do you need high level stuff for?
<br />
<br />
<br />
<br />
<br />
<a name='more'></a>
<br />
<br />
I was quite surprised that every blog post and stackoverflow discussing using a raspberry pi for a bluetooth speaker build uses the pulseaudio bluetooth plugin. So I'm writing one on what I consider a better way to do it.
<br />
<br />
<br /><b>
Spoiler Alert!:</b> I never got it to work well. There was always an odd rhythmic chatter sound in the background, a bit like a CD thats a little scratched by still plays. I'm posting this now for completeness, and perhaps something has changed in the last year since I was playing with it, but I've repurposed my pi for a <a href http://www.zynthian.org>zythian box</a>.
<br />
<br />
<br />
Since I'm headless and I don't have monitors and keyboards lying around I used my usual method to ssh into the pi through a hardwire connection according to <a href="https://www.interlockroc.org/raspberry-pi-macgyver.html"> the 'macgyver' method.</a>
<br />
<br />
First thing to do for me is <a href="https://www.raspberrypi.org/documentation/configuration/wireless/wireless-cli.md"> setup your wifi</a> though you might skip this if you aren't using the pi as a <a href="https://github.com/hzeller/gmrender-resurrect"> dlna renderer</a> also.
<br />
<br />
https://github.com/Arkq/bluez-alsa
<br />
<br />
<br />
follow part of this:
<br />
https://raspberrypi.stackexchange.com/questions/47708/setup-raspberry-pi-3-as-bluetooth-speaker
<br />
<br />
but ignore the pulse stuff. Instead use:
<br />
https://github.com/Arkq/bluez-alsa
<br />
<br />
<br />
#update
<br />
sudo apt-get update
<br />
sudo apt-get upgrade
<br />
sudo apt-get dist-upgrade
<br />
sudo apt-get install vim
<br />
sudo rpi-update
<br />
sudo reboot now
<br />
<br />
<br />
<br />
<br />
#install bluez-alsa from source
<br />
sudo apt-get install autoconf libsbc-dev libtool alsa-base libasound2-dev bluez bluez-tools libbluetooth-dev libglib2.0-dev
<br />
wget https://github.com/Arkq/bluez-alsa/archive/master.zip
<br />
unzip master.zip
<br />
cd bluez-alsa-master/
<br />
autoreconf --install
<br />
mkdir build && cd build
<br />
../configure --enable-debug
<br />
make
<br />
sudo make install
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
#set up bluetooth
<br />
cd
<br />
sudo vim /etc/bluetooth/main.conf
<br />
sudo vim /etc/bluetooth/audio.conf
<br />
<br />
<br />
#just run a test setup
<br />
sudo hciconfig hci0 up
<br />
sudo hciconfig hci0 piscan
<br />
sudo bluealsa &
<br />
<br />
#if you do a scan the pi should be visible to a phone's bluetooth, but it won't accept the connection yet
<br />
sudo bluetoothctl
<br />
devices #shows known devices, the phone should show up if you've tried to connect
<br />
trust XX:XX:XX:XX:XX:XX
<br />
quit
<br />
#configure wifi
<br />
sudo cat /etc/wpa_supplicant/wpa_supplicant.conf >> tmp
<br />
cat tmp
<br />
wpa_passphrase "MySSID" "mypassword" >> tmp
<br />
#remove plaintext password
<br />
vim tmp
<br />
sudo cp tmp /etc/wpa_supplicant/wpa_supplicant.conf
<br />
rm tmp
<br />
wpa_cli -i wlan0 reconfigure
<br />
ifconfig #just check you are connected
<br />
<br />
#find your device you want to output to from this list
<br />
aplay -L
<br />
#mine was iec958,... now use the bluealsa-aplay to stream to it
<br />
bluealsa-aplay -d iec958:CARD=Device,DEV=0 XX:XX:XX:XX:XX:XX
<br />
<br />
#YAY it worked!
<br />
<br />
#Now set it up to do the connection automatically at boot
<br />
sudo vim /etc/rc.local
<br />
#add these 3 lines:
<br />
#hciconfig hci0 up
<br />
#hciconfig hci0 piscan
<br />
#bluealsa &
<br />
#bluealsa-aplay -d iec958:CARD=Device,DEV=0 00:00:00:00:00:00
<br />
<br />
#the 00:00... address tells bluealsa-aplay to accept any device and play from it
<br />
<br />
#the only problem is the wifi and bluetooth don't play nicely together, so audio has a lot of xruns (dropouts/clicks/pops/studders).
<br />
#set up a script that will automatically disable wifi when the bluetooth connects.
<br />
<br />
Problem was that the sound wasn't very good. There was a chattering sound in the background all the time, like a cd thats scratched just enough that the player can read it but not cleanly.
<br />
<br />
So back to the drawing board. I'd read people used AAC for bluez-alsa. I don't know what this really mean, though I assume its the encoding for the wireless digital audio transfer. The reason I skipped it was because it was work to download and build the library necessary for it. But its not that bad:
<br />
<br />
wget https://github.com/mstorsjo/fdk-aac/archive/master.zip<br />
unzip master.zip
<br />
cd master
<br />
libtoolize<br />
autoreconf --install
<br />
mkdir build && cd build
<br />
../configure
<br />
make
<br />
make install
<br />
<br />
then go back to bluez-alsa with
<br />
../configure --enable-aac --enable-debug --disable-payloadcheck
<br />
<br />
<br />
<br />
This however did not fix the issue. :( After trying all this it seems a shame to give up, but I don't have time to fight it any more, and now I have a new TV connected to my sound system that streams from my media server anyway. Hope this will help someone.Spencerhttp://www.blogger.com/profile/12246776537628500183noreply@blogger.com0tag:blogger.com,1999:blog-2309349868071255294.post-70117318948598858522017-11-13T15:36:00.003-08:002017-11-13T15:37:28.530-08:00Installing the Ninja and Meson Build Systems on OSXI never use OSX. Doing so today has felt like trying to play guitar left handed, or say the alphabet backwards. I know what needs to be done but its unnatural and frustrating.<br />
<br />
But I had to for work to test my meson build descriptions. I won't introduce them here, but meson seems pretty awesome and it depends on ninja for what usually make is used for.<br />
<br />
If you are here you probably know that already anyway.<br />
<br />
<a name='more'></a>So 3 things to do: 1. install ninja, 2. install python3, 3. install meson.<br />
<br />
Ninja:<br />
1. download ninja binary from https://github.com/ninja-build/ninja/releases<br />
2. copy it to a place OSX will execute it: <span style="font-family: "menlo"; font-size: 11px;">sudo cp Downloads/ninja /usr/local/bin/</span><br />
<br />
Python3:<br />
1. just follow this guide:http://docs.python-guide.org/en/latest/starting/install3/osx/<br />
<br />
Meson:<br />
1. use pip: <span style="font-family: "menlo"; font-size: 11px;">pip3 install meson</span><br />
<span style="font-family: "menlo"; font-size: 11px;"><br /></span>
<span style="font-family: "menlo"; font-size: 11px;"><br /></span>
Now run wild building your meson projects (http://mesonbuild.com/Quick-guide.html if you don't know how).<br />
<div>
<br /></div>
Spencerhttp://www.blogger.com/profile/12246776537628500183noreply@blogger.com1tag:blogger.com,1999:blog-2309349868071255294.post-9253116095971313342016-06-23T15:10:00.001-07:002016-06-23T15:10:41.437-07:00Room Treatment and Open Source Room EvaluationIts hard to improve something you can't measure.<br />
<br />
My studio space is much much too reverberant. This is not surprising since its a basement room with laminate flooring and virtually no soft, absorbant surfaces at all. I planned to add acoustic treatment from the get go, but funding made me wait until now. I've been recording doing DI guitars, drum samples, and synth programming, but nothing acoustic yet until the room gets tamed a little bit.<br />
<br />
<a name='more'></a><br />
<br />
(note: I get pretty explanatory about why bass traps matter in the next several paragraphs. If you only care about the measurement stuff, skip to below the pictures.)<br />
<br />
Well, how do we know what needs taming? First there are some rules of thumb. My room is about 13'x11'x7.5' which isn't an especially large space. This means that sound waves bouncing off the walls will have some strong resonances at 13', 11', and 7.5' wavelengths which equates to about 86Hz, 100Hz, and 150Hz respectively. There will be many more resonances, but these will be the strongest ones. These will become standing waves where the walls just bounce the acoustic energy back and forth and back and forth and back and forth... Not forever, but longer than the other frequencies in my music.<br />
<br />
For my room, these are very much in the audible spectrum so this acoustic energy hanging around in the room will be covering other stuff I want to hear (for a few hundred extra ms) while mixing. In addition to these primary modes there will also be resonances at 2x, 3x, 4x, etc. of these frequencies. Typically the low end is where it tends to get harder to hear what's going on, but all the reflections add up to the total reverberance which is currently a bit too much for my recording.<br />
<br />
Remember acoustic waves are switching (or waving even) between high pressure/low speed and low pressure/high speed. Where the high points lie depends on the wavelength (and the location of the sound source). At the boundaries of the room, the air carrying the primary modes' waves (theoretically) doesn't move at all. That means the pressure is the highest there. At the very middle of the room you have a point where air carrying these waves is moving the fastest. Of course the air is usually carrying lots of waves at the same time so how its moving/pressurized in the room is hard to predict exactly.<br />
<br />
With large wavelengths like the ones we're most worried about, you aren't going to stop them with a 1" thick piece of foam hung on the wall (no matter how expensive it was). You need a longer space to act on the wave and trap more energy. With small rooms more or less the only option is through porous absorbers which basically take acoustic energy out of the room when air carrying the waves tries to move through the material of the treatment. Right against the wall air is not moving at all, so putting material there isn't going to be very effective for the standing waves. And only 1" of material isn't going to act on very much air. So you need volume of material and you need to put it in the right place.<br />
<br />
Basically thicker is better to stop these low waves. If you have sufficient space in your room put a floor-to-ceiling 6' deep bass trap. But most of us don't have that kind of space to give up. The thicker the panel the less dense of material you should use. Thick traps will also stop higher frequencies, so basically, just focus on the low stuff and the higher will be fine. Often if the trap is not in a direct reflecting point from the speaker then its advised to glue kraft paper to the material which bounces some of the ambient high end around the room so its not too dead. How dead is too dead? How much high end does each one bounce? I don't know. It's just a rule of thumb. The rule for depth is quarter wavelength. An 11' wave really will be stopped well by a 2.75' thick trap. This thickness guarantees that there will be some air moving somewhere through the trap even if you put it right in the null. Do you have a couple extra feet of space to give up all around the room? Me neither. But we'll come back to that. Also note that more surface area is more important than thickness. Once you've covered enough wall/floor/ceiling, then the next priority is thickness.<br />
<br />
Next principle is placement. You can place treatment wherever you want in the room but some places are better than others. Right against the wall is ok because air is moving right up until the wall, but it will be better if there is a little gap, because the air is moving faster a little further from the wall. So we come back to the quarter wavelength rule. The most effective placement of a panel is spaced equal to its thickness. So a 3" panel is best 3" away from the wall. This effectively doubles the thickness of your panel. Thus we see placement and thickness are related. Now your 3" panel is acting like its 6" damping pretty effectively down to 24" waves (~563Hz). It also works well on all shorter waves. Bass traps are really broadband absorbers. But... 563Hz is a depressingly high frequency when we're worried about 80Hz. This trap will do SOMETHING to even 40Hz waves, but not a whole lot. What do we do if our 13' room mode is causing a really strong resonance? <br />
<br />
You can move your trap further into the room. This makes it so there is a gap in the absorption curve, but it makes the absorption go lower. So move the 3" panel to have a 6" gap and you won't be as effective at absorbing 563Hz but now it works much better on 375Hz. You are creating a tuned trap. It still works some on 563Hz but the absorption curve will have a low point then a bump at 375. Angling the trap so the gap varies can help smooth this response making it absorb more frequencies, but less effectively for specific ones. So tradeoff smooth curve for really absorbing a lot of energy at a specific frequency if you need.<br />
<br />
The numbers here are pretty thoretical. Even though the trap is tuned to a certain frequency a lot of other frequencies will get absorbed. Some waves will enter at angles which makes it seem thicker. Some waves will bounce off. Some waves will diffract (bend) around the trap somewhat. There are so many variables that its very difficult to predict acoustics precisely. But these rules of thumb are applicable in most cases.<br />
<br />
Final thing to discuss is what material? Its best to find one that has been <a href="http://www.bobgolds.com/AbsorptionCoefficients.htm">tested</a> with published numbers because you have a good idea if and how it will work. Mineral wool is a fibrous material that resists air passing through. Fiberglass insulation can work too. Rigid fiberglass Owens Corning 703 is the standard choice but mineral wool is cheaper and just as effective so its becoming more popular. Both materials (and there are others) come in various densities, and the idea comes into play that thicker means less dense. This is because if it's <a href="https://www.gearslutz.com/board/7214576-post8.html">too dense</a> acoustic waves could bounce back out on their way through rather than be absorbed. <br />
<br />
Man. I didn't set out to give a lecture on acoustics, but its there and I'm not deleting it. I do put the bla in blog, remember? There's a lot more (and better) reading you can do at <a href="http://ethanwiner.com/acoustics.html">an acoustic expert's site</a>.<br />
<br />
For me and my room (and my budget) I started out building two 9" deep 23" wide floor to ceiling traps for the two corners I have access to (The other 2 corners are blocked by the door and my wife's sewing table). These will be stuffed with Roxul Safe and Sound (SnS) which is a lower density mineral wool. Its available on Lowes online, but it was cheaper to find a local supplier to special order it for me.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgY21m7XLI2xTtvEbYLmikwpFMRJE9ilVdQry1hBz6sq5WZnHGJ-3sEPqssVh6n0feE6Cc9KMLo8NTAZc_VA3MEXVZPNINlc_pMw90FEGufANfpzvhYcrnEDGJNpy2XXqF_iLt4GiHH3iyV/s1600/IMG_20160204_221548825.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgY21m7XLI2xTtvEbYLmikwpFMRJE9ilVdQry1hBz6sq5WZnHGJ-3sEPqssVh6n0feE6Cc9KMLo8NTAZc_VA3MEXVZPNINlc_pMw90FEGufANfpzvhYcrnEDGJNpy2XXqF_iLt4GiHH3iyV/s320/IMG_20160204_221548825.jpg" width="179" /></a></div>
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhqpvM-34O4Q4JQ24zKcBAT6i-Md9vqBNkjh4qdH1TBKIDPkP1NDFwhRO8JJ1dVoEM7P8uanoXUnfeMfGAxNiBgvjV8FrPpk5ULupXRdOzKDOLOKzEmtWs2_Y_GueKU1ApFH5yb6d1QJXv7/s1600/IMG_20160204_221850141.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="179" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhqpvM-34O4Q4JQ24zKcBAT6i-Md9vqBNkjh4qdH1TBKIDPkP1NDFwhRO8JJ1dVoEM7P8uanoXUnfeMfGAxNiBgvjV8FrPpk5ULupXRdOzKDOLOKzEmtWs2_Y_GueKU1ApFH5yb6d1QJXv7/s320/IMG_20160204_221850141.jpg" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Roxul compresses it in the packaging nicely</td></tr>
</tbody></table>
<div class="separator" style="clear: both; text-align: center;">
</div>
<br />
I will build a 6"x23" panel using whatever's left and will place it behind the listening position. I also ordered a bag of the denser Roxul Rockboard 60 (RB60). I'm still waiting for it to come in (rare stuff to find in little Logan UT, but I found a supplier kind enough to order it and let me piggy back on their shipping container so I'm not paying any shipping, thanks <a href="http://branches.lwsupply.com/building-specialties-logan-ut-84321.html">Building Specialties</a>!). I will also build four 4"x24"x48" panels out of Roxul Rockboard 60 (when it finally arrives) which is a density that more or less matches the performance of OC703. These will be hung on the walls at the first reflecting points and ceiling corners. Next year or so when I have some more money I plan to buy a second bag of the rockboard which will hopefully be enough treatment to feel pretty well done. I considered using the 2" RB60 panels individually so I can cover more surface (which is the better thing acoustically), but in the end I want 4" panels and I don't know if it will be feasible to rebuild these later to add thickness. <br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhU5fZn3oPTuCfH74ZkDAT1_PLAmLHu7oQt1usz6V3_Z4YQJig-SAWNPWlqVSSEPPv4Y3Hcr6u5hCc0MxdQZl6-VYC_aKVKKgzhCS5lfiALzZJtadYBx8RFIrdOibSgy027o_t0NnnT82AL/s1600/IMG_20160204_221901003.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="179" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhU5fZn3oPTuCfH74ZkDAT1_PLAmLHu7oQt1usz6V3_Z4YQJig-SAWNPWlqVSSEPPv4Y3Hcr6u5hCc0MxdQZl6-VYC_aKVKKgzhCS5lfiALzZJtadYBx8RFIrdOibSgy027o_t0NnnT82AL/s320/IMG_20160204_221901003.jpg" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">my stack of flashing</td></tr>
</tbody></table>
<br />
I more or less followed <a href="http://www.runet.edu/~shelm/acoustics/bass-traps.html#Broadband">Steven Helm's method</a> with some variations. The stuff he used isn't very available so I bought some 20 gauge 1.5" galvanized L-framing or angle flashing from the same local supply shop who got me . They had 25ga. but I was worried it would be too flimsy, considering even on the rack a lot of it got bent. I just keep envisioning my kids leaning against them or something and putting a big dent on the side. After buying I worried it would be too heavy, but now after the build, I think for my towering 7.5' bass traps, the thicker material was a good choice. For the smaller 2'x4' panels that are going to be hung up, I'm not sure yet.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhk_XO-oZTkI9s9mXj0xTFF4H9DZ8R2UCNt8yqakh5XE6XZNm3e9Tn6_PPmkRnHiLPbcXIGND6YoV68fMBNSmv6zKZ7atRwFq_mgfpm1-_u63Pj5mnbYcUYBS5SOXU-EAPAJQ5BsBtrEyqu/s1600/IMG_20160204_230746188.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhk_XO-oZTkI9s9mXj0xTFF4H9DZ8R2UCNt8yqakh5XE6XZNm3e9Tn6_PPmkRnHiLPbcXIGND6YoV68fMBNSmv6zKZ7atRwFq_mgfpm1-_u63Pj5mnbYcUYBS5SOXU-EAPAJQ5BsBtrEyqu/s320/IMG_20160204_230746188.jpg" width="179" /></a></div>
I chose not to do a wood trap because I thought riveting would be much faster than nailing where I don't have a compressor yet. Unfortunately I didn't forsee how long it can take to drill through 20ga steel. I found after the first trap its much faster to punch a hole with a nail then drill it to the rivet size. Its nice when you have something to push against (a board underneath) but where I was limited on workspace I sometimes had to drill sideways. A set of vice-grip pliers really made that much easier.<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj22QrTG8kmrjT_lJ3_LRzk0lSeSstDVjf2pVr92YTLO2Fg-8ZgsAXbQo6ut4AZC8C3L6NFFzjOAOC5qMnD-JFMHzY8fqIna7BpRAGh_mYN5IKtI_mgn0S8xRS1OOUokng00UshapM5NW54/s1600/IMG_20160212_090638268.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj22QrTG8kmrjT_lJ3_LRzk0lSeSstDVjf2pVr92YTLO2Fg-8ZgsAXbQo6ut4AZC8C3L6NFFzjOAOC5qMnD-JFMHzY8fqIna7BpRAGh_mYN5IKtI_mgn0S8xRS1OOUokng00UshapM5NW54/s320/IMG_20160212_090638268.jpg" width="179" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<br />
Steven's advice about keeping it square is very good, something I didn't do the best at on the first trap, but not too far off either. They key is <a href="http://www.runet.edu/~shelm/acoustics/images/DSC03207.jpg">using the square to keep your snips cutting squarely</a>. Also since my frame is so thick it doesn't bend very tightly, so I found it useful to take some pliers and twist the corner a bit to square it up.<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEihaVXDKd4TsOdOdLFHgwecgUJWdhJSSVRuiPWXeerCwf350BXzvqHRAQzrkHXjwU_O4FJMjB0GRMQBOEbp4p6L75uYo0cE1i7S5TiGa620LkNVhgWnxgRrjESKgJNam1J0C3H6g_o5ORuC/s1600/IMG_20160213_143817904.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEihaVXDKd4TsOdOdLFHgwecgUJWdhJSSVRuiPWXeerCwf350BXzvqHRAQzrkHXjwU_O4FJMjB0GRMQBOEbp4p6L75uYo0cE1i7S5TiGa620LkNVhgWnxgRrjESKgJNam1J0C3H6g_o5ORuC/s320/IMG_20160213_143817904.jpg" width="179" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Corner is a bit round</td><td class="tr-caption" style="text-align: center;"><br /></td></tr>
</tbody></table>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhL0mRHFboxFsk6IvMDEf0qgXHIxC0MmvFo4wdd4JiLs1Q6Fa_HQ5D8pgRBx8wIQpiFh0inZlaDwpr_3hnYqC-z7WJ169kwqKBRKNvqKpZP_4C6tD-MjhdHnAl8frM5NnlYfjN72YFsoAlY/s1600/IMG_20160213_143907630.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="179" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhL0mRHFboxFsk6IvMDEf0qgXHIxC0MmvFo4wdd4JiLs1Q6Fa_HQ5D8pgRBx8wIQpiFh0inZlaDwpr_3hnYqC-z7WJ169kwqKBRKNvqKpZP_4C6tD-MjhdHnAl8frM5NnlYfjN72YFsoAlY/s320/IMG_20160213_143907630.jpg" width="320" /></a></div>
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjI50nS38SmBvG6i1scp7G1aNTs8JPsngpO4zBA8-cEtwg1uRktO8mmG_873NlyIlBKzwiKSJRr5cjMl3GeuiQ6aHd0IDcLtkgpq9zr4h4iultixqe4oP66SG2LUZiRk0VHrF1eZWEi1uc6/s1600/IMG_20160213_143928434.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="179" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjI50nS38SmBvG6i1scp7G1aNTs8JPsngpO4zBA8-cEtwg1uRktO8mmG_873NlyIlBKzwiKSJRr5cjMl3GeuiQ6aHd0IDcLtkgpq9zr4h4iultixqe4oP66SG2LUZiRk0VHrF1eZWEi1uc6/s320/IMG_20160213_143928434.jpg" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">a bit tighter corner now</td><td class="tr-caption" style="text-align: center;"><br /></td></tr>
</tbody></table>
Since my traps are taller than as single SnS panel I had to stack them and cut a 6" off the top. A serrated knife works best for cutting this stuff but I didn't have an old one around, so I improvised one from some scrap sheet metal.<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQ6rdRb8BWqlauhfsfclLC4v56zTyMFipfXXByyp7X8ujt3p-8Ji000mYPRN4abZDjIi41VjwcZDVbHfEQPmK2w-QEh6Da4n4H2rA7CfNSeTvCvxVfo7eYqsGsdAClyJc1hyw3u6PjO4gT/s1600/IMG_20160204_221942476.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQ6rdRb8BWqlauhfsfclLC4v56zTyMFipfXXByyp7X8ujt3p-8Ji000mYPRN4abZDjIi41VjwcZDVbHfEQPmK2w-QEh6Da4n4H2rA7CfNSeTvCvxVfo7eYqsGsdAClyJc1hyw3u6PjO4gT/s320/IMG_20160204_221942476.jpg" width="179" /></a></div>
<br />
I staggered the seams to try to make a more homogenous material.<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj7Z6vu-0Puy2yH-Kj_vHd_BOFVihH9Gbeoe-2I52-t8wo27mU_CdeeG3bOOE8_3UrrBbXrJtNTLgPXyDq9RFC8GWyF419GXRjy033xk-BTJWvy3wciqYzDqIp0ujHBsSldRJectDWrwRbr/s1600/IMG_20160212_082042373.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj7Z6vu-0Puy2yH-Kj_vHd_BOFVihH9Gbeoe-2I52-t8wo27mU_CdeeG3bOOE8_3UrrBbXrJtNTLgPXyDq9RFC8GWyF419GXRjy033xk-BTJWvy3wciqYzDqIp0ujHBsSldRJectDWrwRbr/s320/IMG_20160212_082042373.jpg" width="179" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhKWX2hQXrVOz9Fz_SEnF0xwmzBObAViqNW4LjCHbnkcJCp9KwdVMFP5SDmAmXgCvr793TH_JldQcWdzPtCJji_6EWgIPbcVeB_3VlR2slDgNexOXAeZiGZeQmKjhYqY46n-PFIosHBF0_9/s1600/IMG_20160212_082059039.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhKWX2hQXrVOz9Fz_SEnF0xwmzBObAViqNW4LjCHbnkcJCp9KwdVMFP5SDmAmXgCvr793TH_JldQcWdzPtCJji_6EWgIPbcVeB_3VlR2slDgNexOXAeZiGZeQmKjhYqY46n-PFIosHBF0_9/s320/IMG_20160212_082059039.jpg" width="179" /></a></div>
<br />
With all the interior assembled I think the frames actually look good enough you could keep them on the outside, but my wife preferred the whole thing be wrapped in fabric. I don't care either way.<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhiUYxxY_XZMFwBn0DNe3Sf9vMPw6IZNJ49OWfHAfQsVl_056syLmb4wHPZeizvPaXqjtafoY6cCCyDpudZQmkRIYFq2Uka9fsUJUgN-auy8U2QZdff1qX1j5j1_Z8aLt91BE-Z5e-4ombZ/s1600/IMG_20160213_105021069.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhiUYxxY_XZMFwBn0DNe3Sf9vMPw6IZNJ49OWfHAfQsVl_056syLmb4wHPZeizvPaXqjtafoY6cCCyDpudZQmkRIYFq2Uka9fsUJUgN-auy8U2QZdff1qX1j5j1_Z8aLt91BE-Z5e-4ombZ/s320/IMG_20160213_105021069.jpg" width="179" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwRwTfCBHogFz9BEqVnsN2EZ973Ict8BkJ0StPb4ArkYfpP3U83nFAEPd4g109xJhtkli87Hfa0FkCVaxtf2N-K8tMMM77qN0cOWgWFptUHPoy2kY-iHzQlNBYuawJgh-fXEmFe7CAqvXp/s1600/IMG_20160213_105030478_HDR.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwRwTfCBHogFz9BEqVnsN2EZ973Ict8BkJ0StPb4ArkYfpP3U83nFAEPd4g109xJhtkli87Hfa0FkCVaxtf2N-K8tMMM77qN0cOWgWFptUHPoy2kY-iHzQlNBYuawJgh-fXEmFe7CAqvXp/s320/IMG_20160213_105030478_HDR.jpg" width="179" /></a></div>
<br />
Before covering though I glued on some kraft paper using spray adhesive. I worked from top to bottom, but some of them got a bit wrinkled.<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjldMgpcNkUocsa8BQnVr8mHIEW6lkF-LlyivrVmuBj2w4ueU9T3-Bf-grjNbicb62Co3LGXVsriujj7Lozej3rVwBVnaCjWQvJZtZurKMeU7WbITtK6YTM2O-V2MT8vQyxtmTeqBOQ1JnP/s1600/IMG_20160609_090344236.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjldMgpcNkUocsa8BQnVr8mHIEW6lkF-LlyivrVmuBj2w4ueU9T3-Bf-grjNbicb62Co3LGXVsriujj7Lozej3rVwBVnaCjWQvJZtZurKMeU7WbITtK6YTM2O-V2MT8vQyxtmTeqBOQ1JnP/s320/IMG_20160609_090344236.jpg" width="179" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjXwhoexJJJ8dH-vzaaJKwusLDqPZA94HFGv4G9h-wBVIsFx4hMOaQIa5x55w6qdjVzK4_eHuog6m1LhKSpHssd2L13Z0iP5v8xiCywhE3bJNlcZOxMLwDFwMWXKN3qdDoHnRHIcl_xUrkx/s1600/IMG_20160609_090352393.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjXwhoexJJJ8dH-vzaaJKwusLDqPZA94HFGv4G9h-wBVIsFx4hMOaQIa5x55w6qdjVzK4_eHuog6m1LhKSpHssd2L13Z0iP5v8xiCywhE3bJNlcZOxMLwDFwMWXKN3qdDoHnRHIcl_xUrkx/s320/IMG_20160609_090352393.jpg" width="179" /></a></div>
<br />
The paper was a bit wider than the frame, so I cut around the frame and stuffed it behind a bit, so it has a tidier look.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhox2VDN7HPHypPKmw88-cO0ri53e-G_jtFz-KMuwRJ7Btz0aiq1ub35PBWpIjr8JJJRnFaFr0yQJjtnxTxVaWAdENNNZZha8TrWBMAaHLQihbWLQm0uiawHGGNcunIVaK2d4TZZnsQQxU3/s1600/IMG_20160609_221618550.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhox2VDN7HPHypPKmw88-cO0ri53e-G_jtFz-KMuwRJ7Btz0aiq1ub35PBWpIjr8JJJRnFaFr0yQJjtnxTxVaWAdENNNZZha8TrWBMAaHLQihbWLQm0uiawHGGNcunIVaK2d4TZZnsQQxU3/s320/IMG_20160609_221618550.jpg" width="179" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi9Fw_vUde4lVyYlqQUXZJHa3szarGkSMVuUM_AokQV5Zn6C3mAp-JnZ-eTOPWVHcZ2RQWu5O2sRLdxgXUXcW6_S7CXv8BVnmlOSi4u6A0SpozgBrl0_j3LYpN3UhVu5Re3Kun2WIxTQOSU/s1600/IMG_20160609_221545676.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi9Fw_vUde4lVyYlqQUXZJHa3szarGkSMVuUM_AokQV5Zn6C3mAp-JnZ-eTOPWVHcZ2RQWu5O2sRLdxgXUXcW6_S7CXv8BVnmlOSi4u6A0SpozgBrl0_j3LYpN3UhVu5Re3Kun2WIxTQOSU/s320/IMG_20160609_221545676.jpg" width="179" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgXeKMjMACwhv5kb1A9ZrMjwZVIvgb4SrqRYdN7XcrdgmWOOpPdYzSzSKkmfSBaxDy-Ir8mqIqD9PmIgvQE5wVOdVFJhJHsN2hmJ_sTnc_nhCJtt7YNAp6jm9IHBj_QipxG0m2fGfvSHzOZ/s1600/IMG_20160609_221920272.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgXeKMjMACwhv5kb1A9ZrMjwZVIvgb4SrqRYdN7XcrdgmWOOpPdYzSzSKkmfSBaxDy-Ir8mqIqD9PmIgvQE5wVOdVFJhJHsN2hmJ_sTnc_nhCJtt7YNAp6jm9IHBj_QipxG0m2fGfvSHzOZ/s320/IMG_20160609_221920272.jpg" width="179" /></a></div>
<br />
<br />
I'd say they look pretty darn good even without fabric!<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgIqIWOCDDVm0KwL7RNV9SNbV7LgBATykhNBcRtXFKGKaa3CHp8T4LrefVSPiuRSFyi-Cjyllp6ZJEUbwMNEEqk5ZzzcIIMj7iAIRwmVY-9maoF8cOG-Zd3ZclWG6Z1qSBlRwT3Ea92onmT/s1600/IMG_20160609_090401943.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgIqIWOCDDVm0KwL7RNV9SNbV7LgBATykhNBcRtXFKGKaa3CHp8T4LrefVSPiuRSFyi-Cjyllp6ZJEUbwMNEEqk5ZzzcIIMj7iAIRwmVY-9maoF8cOG-Zd3ZclWG6Z1qSBlRwT3Ea92onmT/s320/IMG_20160609_090401943.jpg" width="179" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjQNyBvCXVGpf1D7NPBoiIinZA5Dl8CygXhDEL9tRAwA6rY4UpIvSx2Uu7X0HJn8f1CbD_nW0vIWgg-VqI3KeBVq7gAkIIu2x5yiXjCX8CmMMIzByw6t7A9nJq9NXXOLLDfpoZMxk1wdaDh/s1600/IMG_20160614_083930548_HDR.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="179" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjQNyBvCXVGpf1D7NPBoiIinZA5Dl8CygXhDEL9tRAwA6rY4UpIvSx2Uu7X0HJn8f1CbD_nW0vIWgg-VqI3KeBVq7gAkIIu2x5yiXjCX8CmMMIzByw6t7A9nJq9NXXOLLDfpoZMxk1wdaDh/s320/IMG_20160614_083930548_HDR.jpg" width="320" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjNOuDXBgpOqe8hCVPR-dHlipL1mA7UGz19thaTHLSygXYegPswLVw9J3__t4l1nZB4E9R43MCLUjtgj3E5Oq-vxNY3BYYmrnMsBxw2w0MTABuSdzCgmw1SyE-B-kLYyGJpahGmoLQNlibp/s1600/IMG_20160614_083949246_HDR.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="179" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjNOuDXBgpOqe8hCVPR-dHlipL1mA7UGz19thaTHLSygXYegPswLVw9J3__t4l1nZB4E9R43MCLUjtgj3E5Oq-vxNY3BYYmrnMsBxw2w0MTABuSdzCgmw1SyE-B-kLYyGJpahGmoLQNlibp/s320/IMG_20160614_083949246_HDR.jpg" width="320" /></a></div>
<br />
<br />
Anyway, so all that acoustic blabber above
boils down to the fact that even following rules of thumb, the best
thing to do is measure the room before and after treatment to see what
needs to be treated and how well your treatment did. If its good leave
it, if its bad you can add more or try to move it around to address
where its performing poorly.<br />
<br />
So as measuring is
important, and I'm kinda a stickler for open source software I will show
you today how to do it. The de-facto standard for measurement is the <a href="http://www.roomeqwizard.com/">Room Eq Wizard (REW)</a>
freeware program. Its free but not libre, so I decided to use what was
libre. Full disclosure: I installed REW and tried it but couldn't ever
get sound to come out of it, so that helped motivate the switch. I was
impressed REW had a linux installer, but I couldn't find any answers on
getting sound out. Its java based, not JACK capable, so it couldn't
talk to my firewire soundcard. REW is very good, but for the freedom
idealists out there we can use <a href="http://kokkinizita.linuxaudio.org/linuxaudio/downloads/aliki-manual.pdf">Aliki</a>.<br />
<br />
The method is the same in both, generate a sweep of sine tones with your speakers, record the room's response with your mic, and do some processing that creates an impulse response for your room. An impulse signal is a broadband signal that contains all frequencies equally for a very very (infinitely short) amount of time. True impulses are difficult to generate so its easier to just send the frequencies one at a time then combine them with some math. I've <a href="http://mountainbikesandtrombones.blogspot.com/2013/01/its-convoluted.html">talked a little about measuring impulse responses before</a>. The program I used back then (qloud) isn't compiling easily for me these days because it hasn't been updated for modern QT libraries and Aliki is more tuned for room measurement vs. loudspeaker measurement.<br />
<br />
I am most interested in 2 impulse responses: 1. the room response between my monitors and my ears while mixing, and 2. the room response between my instruments and the mic. Unfortunately I can't take my monitors or my mic out of the measurement because I don't have anything else to generate or record the sine sweeps with. So each measurement will have these parts of my signal chain's frequency response convolved in too, but I think they are flat enough to get an idea and they'll be consistent for before and after treatment comparisons. I don't have a planned position for where I will be recording in this room but the listening position won't be moving so I'm focused on response 1.<br />
<br />
The Aliki manual linked above is pretty good. For the most part I'm not going to rehearse it here. You make select a project location, and I found that anywhere but your home directory didn't work. It makes 4 folders in that location to store different audio files: sweep, capture, impulse, and edited files.<br />
<br />
We must first make a sweep, so click the sweep button. I'm going from 20Hz to 22000Hz. May as well see the full range, no? A longer sweep can actually reduce the noise of the measurement, so I went a full 15 seconds. This generates an audio file with the sweep in it in the sweep folder. Aliki stores everything as .ald files, basically a wav with a simpler header I think.<br />
<br />
Next step: capture. Set up your audio input and output ports, and pick your sweep file for it to play. Use the test to get your levels. I found that even with my preamps cranked the levels were low coming in from my mic. It was night so I didn't want to play it much louder. You can edit the captures if you need. Each capture makes a new file or files in the capture directory.<br />
<br />
I did this over several days because I measured before treatment, then with the traps in place before the paper was added and again after the paper was glued on. Use the load function to get your files and it will show them in the main window. Since my levels were low I went ahead and misused the edit functions to add gain to the capture files so they were somewhat near full swing.<br />
<br />
Next step is the convolution to remove the sweep and calculate the impulse response. Select the sweep file you used, set the end time to be longer than your sweep was and click apply and it should give you the impulse response. Be aware that if your levels are low like mine were, you'll only get the tiniest blip of waveform near zero. Save that as a new file and then go to edit.<br />
<br />
In edit, you'll likely need to adjust the gain, but you can also adjust the length, and in the end you have a lovely impulse response that you can export to a .wav file that you can listen to (though its not much to listen to) or more practically: use in your favorite impulse response like IR or klangfalter.<br />
<br />
But we don't want to use this impulse for convolving signals with. We can already get that reverb by just playing an instrument in our room! We want to analyze the impulse response to see if there's improvement or if something still needs to be changed. So this is where I imported the IR wav files into <a href="https://www.gnu.org/software/octave/">GNU Octave</a>.<br />
<br />
I wrote a few scripts to help out, namely: plotIREQ and plotIRwaterfall. They can be found in <a href="https://github.com/ssj71/octaveIRplots">their git repository</a>. I also made fftdecimate which smooths it out from the raw plotIREQ plot:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEisQRZrEgBWvqaboDTTw5zWcwr8HlFFH_sj5fu9a-Aq1G3MX5z9MK-J0RxBYm6L7EPyTyZ2w2RM4gwfOSQdVu4AjWzbKUnHI7cy6gXpYSd4HigvBkJrWuQOH2qWS5IdCLISwL_6rlRFtXH0/s1600/eq.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="480" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEisQRZrEgBWvqaboDTTw5zWcwr8HlFFH_sj5fu9a-Aq1G3MX5z9MK-J0RxBYm6L7EPyTyZ2w2RM4gwfOSQdVu4AjWzbKUnHI7cy6gXpYSd4HigvBkJrWuQOH2qWS5IdCLISwL_6rlRFtXH0/s640/eq.png" width="640" /></a></div>
<br />
<br />
<br />
to this:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjMN_HNCgxUHKdQYYIL0lRQbsnGGQW9mQ5KwsQsEijTvq6lXzyl_QS3xsl1T-u1-eK-7KAb6EXpM9LatCemFpXTtR2vj-f5o6p2uS0DJi9kUZnzmjFymoFfFIdoIrTloN-v8Z61uKtqcjg7/s1600/eq_smooth.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="480" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjMN_HNCgxUHKdQYYIL0lRQbsnGGQW9mQ5KwsQsEijTvq6lXzyl_QS3xsl1T-u1-eK-7KAb6EXpM9LatCemFpXTtR2vj-f5o6p2uS0DJi9kUZnzmjFymoFfFIdoIrTloN-v8Z61uKtqcjg7/s640/eq_smooth.png" width="640" /></a></div>
<br />
I won't go through the code in too much detail. If you'd like me to, leave a comment and I'll do another post. But look at plotMyIRs.m for useage examples of how I generated these plots.<br />
<br />
<br />
You can see the big bump from around 150hz to 2khz. And a couple big valleys at 75hz, 90hz, 110hz etc. One thing I decided from looking at these is that the subwoofer should be turned up a bit, since my Blue Sky Exo2's crossover at around 150hz, and everything below that measured rather low.<br />
<br />
I was hoping for a smoother result, especially in the low end, but I plan to build more broadband absorbers for the first reflection points. While a 4" thick panel doesn't target the really low end like these bass traps, they do have some effect, even on the very low frequencies. So I hope they'll have a cumulative effect down on that lower part of the graph.<br />
<br />
<br />
The other point that I'd like to comment on is that the paper didn't seem to make much of a difference. Its possible that since it wasn't factory glued onto the rockwool it lacks a sufficient bond to transfer the energy properly. It doesn't seem to hurt the results too much either, in fact around 90hz it seems like it actually makes the response smoother, so I don't plan to remove it (yet at least).<br />
<br />The last plots I want to look at is the waterfall plots. These show how the frequencies are responding in time so you will see if any frequencies are ringing/resonating and need better treatment.<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLhzODljKxpTGubcoZC76oIODibM-cg9MFAEp-6ngg_qmh7If7nmhJ5GLgl-1kphnK0eCtxZys3RSNZ1A2-j9GMFLbsb5Efdn1dgBx_7-_Xa-h7IGQ0VKsDbxlghFeJqGlkeRNxH-RfafX/s1600/untreated_waterfall.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="480" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLhzODljKxpTGubcoZC76oIODibM-cg9MFAEp-6ngg_qmh7If7nmhJ5GLgl-1kphnK0eCtxZys3RSNZ1A2-j9GMFLbsb5Efdn1dgBx_7-_Xa-h7IGQ0VKsDbxlghFeJqGlkeRNxH-RfafX/s640/untreated_waterfall.png" width="640" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh8SwIuIHXcFcA_cbBj2ZCOHsV5oATAjh9UxWTC0NfF90HvC5av2grG8qxyDr1fvKv5BakmNcWUFmXk-efPT9areMvAvwY0TYHI9xzA-CUvV1egRmW5-FP3xoxolTwJFPl4y1q-Lp42f397/s1600/nopaper_waterfall.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="480" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh8SwIuIHXcFcA_cbBj2ZCOHsV5oATAjh9UxWTC0NfF90HvC5av2grG8qxyDr1fvKv5BakmNcWUFmXk-efPT9areMvAvwY0TYHI9xzA-CUvV1egRmW5-FP3xoxolTwJFPl4y1q-Lp42f397/s640/nopaper_waterfall.png" width="640" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjM2uAGJ7bD2I_l6fDM2A1uDSGDQcJTDdkKReOPxXHnHCF8pb7MPppCqUH4T2AjuNYYx8g59AXOWd1fgG9EW3pHzrTwn_RzzdoND_mCx_NzzH1Stqkm4GcrPfCkX3_wPArpQ11qVwtQT7IE/s1600/paper_waterfall.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="480" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjM2uAGJ7bD2I_l6fDM2A1uDSGDQcJTDdkKReOPxXHnHCF8pb7MPppCqUH4T2AjuNYYx8g59AXOWd1fgG9EW3pHzrTwn_RzzdoND_mCx_NzzH1Stqkm4GcrPfCkX3_wPArpQ11qVwtQT7IE/s640/paper_waterfall.png" width="640" /></a></div>
Here we see some anomolies. Just comparing the first and final plots, its easy to see that nearly every frequency decays much more quickly (we're focused on the lower region 400hz and below, since thats where the rooms primary modes lie). You also see a long resonance somewhere around 110hz that still isn't addressed, which is probably the next target. I can try to move the current traps out from the wall and see if that helps, or make a new panel and try to tune it.<br />
<br />
Really though I'm probably going to wait until I've built the next set of panels.<br />
Hope this was informative and useful. Try out those octave scripts. And please comment!Spencerhttp://www.blogger.com/profile/12246776537628500183noreply@blogger.com0tag:blogger.com,1999:blog-2309349868071255294.post-29132589425733068982016-02-26T14:12:00.000-08:002016-02-26T14:12:51.371-08:00Why Does Programming Have So Many Holy Wars?I was chatting with my brother and my Dad about the silly arguments programmers get into. Editors (me: vim, bro: emacs, dad: brief). Tabs vs spaces (all: spaces), bracket placement, C vs C++, Inheritance vs composition, the list goes on and on. Why?<br />
<br />
I guess its because unlike say, space travel that was highly documented by a single agency, or say civil engineering which is regulated by building code and strict best practice rules, coding developed ad-hoc. Hackers all maker their own rules and basically these are just territory gang fights. Whatever. Spencerhttp://www.blogger.com/profile/12246776537628500183noreply@blogger.com2tag:blogger.com,1999:blog-2309349868071255294.post-63509444658876026662015-11-06T13:17:00.000-08:002015-11-06T13:17:02.641-08:00Easyish Triple Boot on a Macbook Pro, Round 2So my <a href="http://mountainbikesandtrombones.blogspot.com/2014/12/easyish-triple-boot-on-2014-macbook-pro.html">"easyish" install</a> using Wubi finally crashed when I was trying to change things and install Ubuntu 15.10. If I'd have left it at 14.04, it probably would have been ok for a long time. However I was getting tired of the poor disk performance when creating large archives for work. So once it was broken, I had no reason not to just try and get a full triple boot (luckily I had backed everything up before trying anything). I found <a href="http://www.travisllado.com/2015/05/triple-booting-2015-macbook-pro-with.html">this recent article</a> about triple booting, but its process has the deal-breaking requirement to install ubuntu before windows. What if you're handed a machine already set up for dual boot (without the recovery disks, mind you)? Funny that with 2 operating systems to choose from I wasn't happy with either....<br />
<br />
Well it can be done (at least on my MBP 11,3 with yosemite and a windows7 bootcamp partition), and the secret sauce is <a href="http://www.rodsbooks.com/refind/">rEFInd</a>. I don't understand EFI at all. And every time I start going through the docs (rEFInd has a lot of GREAT documentation) I get distracted with needing to do work or something. Anyhow, before, I couldn't boot any Ubuntu live usb drives after 14.10 (I think the switch to systemd maybe?) but once I installed rEFInd I could... When I resized the bootcamp partition using a 14.04 live usb, booting windows didn't work any more. But once I installed rEFInd it did... After installing 15.10 from the liveUSB I couldn't boot osx or windows from the grub bootloader. But once I held the option key and used apple's boot selector to boot into OSX again and installed rEFInd again I could... boot from rEFInd's boot selector that is. Somehow rEFInd is magical.<br />
<br />
So in a recipie:<br />
<ol>
<li>Boot a live usb (I had to use 14.04)</li>
<li>Use gparted to shrink down the bootcamp/windows partition (make sure you've defragmented the partition in windows first).</li>
<li>Install rEFInd by downloading in OSX and running the install.sh from the OSX terminal</li>
<li>Boot windows just to make sure you still can (when you have to)</li>
<li>Boot from a live usb of ubuntu 15.10 and install with the empty space turned into the / directory (root)</li>
<li>After the ubuntu install hold option next time you boot and boot into OSX, install rEFInd again (the grub configuration messes it up somehow)</li>
<li>Enjoy using linux but still having 2 other operating systems to choose from (though you'll have to twist my arm to get me to boot anything other than linux).</li>
</ol>
Hopefully this helps. Its not as detailed as my last post, but I'm much more satisfied with the results so far. I'm still amazed that a free and open source project does boot management so much better than the expensive proprietary options.<br />
<br />
Oh ya, and do all this at your own risk. Please make backups.Spencerhttp://www.blogger.com/profile/12246776537628500183noreply@blogger.com0tag:blogger.com,1999:blog-2309349868071255294.post-51980789320261595282015-07-28T21:31:00.000-07:002015-07-28T21:31:10.571-07:00Sense About Some SensorsSorry if this post is a sensitive subject.... (I'm terminally punny). <br />
<br />
Today I finally broke the year long record of no outdoor runs. I've been biking to work more often lately which has been a very nice return after completely falling away from it last year and through the winter and sadly much of the spring. Now I'm cycling twenty to forty miles a week. Better than nothing. But biking to work AND running is better than that. So thats what I did today.<br />
<br />
<a name='more'></a><br />
The Mrs. took the twins to see a movie and neither I nor my two year old sit through two hours very well, so we opted to go for a run together. Its just under 6 miles to go to the little reservoir at the mouth of the canyon and back, adding a little back and forth in front of my house made it a full 6. I was a little nervous going that far since I don't remember when I went running last, but really I felt pretty good the whole way (I even considered extending it a few miles). I kept my slow goal of around 10 minute miles (until I hit the half-mile of 8% grade hill on the way home). I walked about a tenth of each mile (run-walk-run) so I didn't totally thrash myself. All with a stroller. So I felt pretty good.<br />
<br />
As an experiment I had 3 different devices measure our jaunt. My wife's new Garmin 220 watch, my phone with the strava app, and my bottom-end fitness tracker made by the iFit group at work. I was curious to see how the results differed.<br />
<br />
The two GPS devices were nearly perfectly in sync in time (1:04) and distance (6.0 mi). The minute discrepancy was probably more a product of my turning them on and off one at a time. Training for our second marathon, we tried to use my wife's new (at the time) Android 2.2 smartphone. Man those results were wild. I think her device maybe had a faulty gps module or buggy stack because it was very inaccurate. So either phones have come along nicely, or I just haven't used enough phones, but now I'd really say you can totally track distance with your phone. However I really liked the gps watch for being able to track progress, mile mark notifications, and the current pace reading. It has fewer diagnostics than the phone (and I didn't feel like uploading the data to a computer for strava), but was much nicer for the actual run. I'd have trusted its distance measurement first, but the consensus was even better.<br />
<br />
Now for the fitness tracker: I know its just accelerometer data which drifts so much with temperature or saturation you can't ever rely on it for accurate distance tracking, but actually it wasn't as bad as I thought. Don't get me wrong, it was almost 15% over the actual distance I ran, but it could have been worse. And actually the calorie estimate was less than 1% away from the strava estimate (they are always estimates, lets not kid ourselves). The tracker said I took nearly 11,000 steps during the time I was running which calculates to a running stride of 3.2 ft (using its distance measurement). Maybe thats accurate. I don't know. Maybe I'll actually measure my stride sometime (its on the todo list). I'd love to see the algorithms various trackers use to estimate steps and distance, but I'll probably have to crack it myself if I really want to know.<br />
<br />
So let us reiterate: just because your fitness tracker claims you burned off that doughnut, maybe take an extra lap just to be safe, (and the apple watch won't be much better, sorry). If you want to measure distance, use a GPS. If you want to measure calories you either need to measure how much total heat your body generates during exercise or how much of the oxygen you inhaled got exchanged with carbon-dioxide. Neither sounds very comfortable, so a relative value from the fitness tracker will do fine for me.Spencerhttp://www.blogger.com/profile/12246776537628500183noreply@blogger.com0tag:blogger.com,1999:blog-2309349868071255294.post-38092509684211147872015-06-10T09:53:00.000-07:002015-06-11T10:59:50.982-07:00Using LV2 atom:Path (or how to get files safely in a plugin without a GUI)The end of last month marked 2 years since my initial commit to my first lv2 plugins. I've learned some things, but still am picking up more little by little. I was recently reminded that I don't know it all, when falkTX pointed out another <a href="http://sourceforge.net/p/infamousplugins/code/ci/68fa497165a60dc48d67b1d8f7453ffa7eae165e/tree/src/CellularAutomatonSynth/casynth.lv2/manifest.ttl?diff=92bed521fa9922bd53954fa902223bb0eb649bc7">stupid mistake</a> I'd been making that prevented my GUIs from resizing correctly.<br />
<br />
Water under the bridge though. I'm going to try to explain out how to use lv2 to have the host make a file selection dialog for you. I want all my plugins to work purely with host generated GUIs and loading files became necessary for porting the rakarrack *tron effects. The LV2 official documentation has improved a lot, but I was still struggling, so I thought I'd post this. The example to be used is <a href="http://rakarrack.sourceforge.net/effects.html#Reverbtron">rakarrack's reverbtron</a>. Its a sort of convolution using very simplified impulse response files (which are actually text files). Instead of a constant sample rate in the IR, you just keep the most important samples and interpolate between them.<br />
<br />
Regardless of the details, I have a module that needs files. All I need is to pass the file's path to the modules <code>setfile()</code> function and it takes care of the rest. How do I get that path? The whole process involves several extensions, and the most confusing part to me was figuring out what was what. But I got it, so read on...<br />
<br />
<br />
<a name='more'></a><br />
So lets start with the ttl. Quick recap, ttl is static data that the host loads to figure out what your plugin is all about (see the <a href="http://lv2plug.in/book/#_manifest_ttl_in">official examples</a> for some more info). So we need to indicate to the host in this ttl that we need files. Lets create a new statement that defines our .rvb files that hold the IR data. I usually just take the URI for the plugin and tack on some extra stuff with colons. Even though its not an actual website, the whole point of a URI is to keep it universally unique so its pretty unlikely any other plugin will name anything this same string:<br />
<pre name="code"><code>
<http://rakarrack.sourceforge.net/effects.html#Reverbtron:rvbfile>
a lv2:Parameter ;
rdfs:label "rvb file" ;
rdfs:range atom:Path ;
. </code></pre>
<br />
As you probably know already, this "sentence" in the ttl says, "I declare an lv2 parameter, labeled 'rvb file', and can be any value as long as its an <a href="http://lv2plug.in/ns/ext/atom/">atom</a> containing a path." This is all done with URIs, but most of them are shortened using prefixes at the top of the file. Because these URIs are universal, the host knows what they mean and can interpret that as "ok, they have a parameter that will be a path" (roughly).<br />
<br />
Now we need a port for this parameter to be passed into. Its not too different than other ports we've seen before, but we'll use an atom port like just we do for midi ports. So we have inside the plugin declaration we add:<br />
<pre name="code"><code>
lv2:port [
a lv2:InputPort, atom:AtomPort ;
lv2:index 4 ;
lv2:symbol "CONTROL" ;
lv2:name "Control" ;
atom:bufferType atom:Sequence ;
lv2:designation lv2:control ;
atom:supports patch:Message ;
] ; </code></pre>
<br />
This says the 4th port is an atom input. It has name and symbol "control". The host will hand us a pointer to a buffer full of a sequence of atoms (which are just structures of data). Its a designated port for the host to pass any control messages to (such as data for bpm sync, or our .rvb files). Finally, the port supports atoms carrying something called patch:message. What?<br />
<br />
<a href="http://lv2plug.in/ns/ext/patch/">Patch</a> is another extension that is used to read or manipulate properties between the host and plugin. I'm not extremely familiar with it, so I don't have any examples of what else you can do with it. Say for example is if the host wants to change the file path property of our plugin...<br />
<br />
If you expand the prefix for patch the message URI is <a href="http://lv2plug.in/ns/ext/patch/#Message">http://lv2plug.in/ns/ext/patch/#Message</a>. The docs here aren't very verbose, but feel free to see what possibilities are there. The idea is that you can pass several properties without needing different message types for everything.<br />
<br />
We want the host to know what property they can manipulate, so in our plugin declaration ttl we add:<br />
<pre name="code"><code>
patch:writable <http://rakarrack.sourceforge.net/effects.html#Reverbtron:rvbfile> ;</code></pre>
<br />
saying that the plugin has a ".rvb file" property that the host can write. The host knows that URI means the .rvb file because we declared it before we declared the plugin.<br />
<br />
See? This ttl stuff is pretty readable once you get the hang of it. You can see the <a href="https://github.com/ssj71/rkrlv2/blob/dev/lv2/ttl/reverbtron.ttl">full ttl</a> but everything else in it, we've done before. <br />
<br />
So lets jump to the code. Assuming we've got the port connecting correctly, we just need to pull the atoms out of the buffer the host gives us, make sure they're the type of atom we want, then just grab the data (which should be the file path). So the action all happens in the <code>run()</code> part of the plugin, but we need a few things setup first. <br />
<br />
We need a way to recognize the URIDs that the atoms carry, and thats through the <a href="http://lv2plug.in/ns/ext/urid/">URID:map</a>. I've described the map before, but I didn't really understand it then. Basically the host takes the URIs and says, "ok, how about from now on instead of calling that 'http://lv2plug.in/ns/ext/atom/#Object' we'll say its 4. So now whenever I pass an atom marked type 4, we both know what I mean." This mapping is generated arbitrarily by the host, they will call whatever URI any unique integer value, but you can count on it remaining unique this whole session. So always pay attention whether a function needs a URI (string) or a URID (int).<br />
<br />
Anyway so its our plugin's responsibility to look through the host_features passed into our initialization function to get and save the mapping:<br />
<pre name="code" class="cpp">
for(i=0; host_features[i]; i++)
{
if(!strcmp(host_features[i]->;URI,LV2_URID__map))
{
urid_map = (LV2_URID_Map *) host_features[i]->;data;
if(urid_map)
{
plug->URIDs.atom_Float = urid_map->map(urid_map->handle,LV2_ATOM__Float);
plug->URIDs.atom_Int = urid_map->map(urid_map->handle,LV2_ATOM__Int);
plug->URIDs.atom_Object = urid_map->map(urid_map->handle,LV2_ATOM__Object);
plug->URIDs.atom_Path = urid_map->map(urid_map->handle,LV2_ATOM__Path);
plug->URIDs.atom_URID = urid_map->map(urid_map->handle,LV2_ATOM__URID);
plug->URIDs.patch_Set = urid_map->map(urid_map->handle,LV2_PATCH__Set);
plug->URIDs.patch_property = urid_map->map(urid_map->handle,LV2_PATCH__property);
plug->URIDs.patch_value = urid_map->map(urid_map->handle,LV2_PATCH__value);
plug->URIDs.filetype_rvb = urid_map->map(urid_map->handle,RVBFILE_URI);
}
}
}</pre>
<br />
There in the last line I use a #define I created for the URI string (for readability): <br />
<pre name="code"><code>
#define RVBFILE_URI "http://rakarrack.sourceforge.net/effects.html#Reverbtron:rvbfile" </code></pre>
<br />
See how that matches the URI in the ttl? One thing not shown here is that if the host does not give you a <code>urid_map</code> you need to abort the instantiation and return a 0 to the host. Now that we have that done (along with our other instantiation tasks not shown) then we're ready for the signal processing function, <code>run()</code>. When we're running the signal processing, we can look through the atom sequence and use the URIDs to find the atoms with data we want. This uses several LV2 macros, but they're pretty readable: <br />
<pre name="code"><code>
LV2_ATOM_SEQUENCE_FOREACH(plug->atom_in_p, ev)
{
if (ev->body.type == plug->URIDs.atom_Object)
{
const LV2_Atom_Object* obj = (const LV2_Atom_Object*)&ev->body;
if (obj->body.otype == plug->URIDs.patch_Set)
{
// Get the property the set message is setting
const LV2_Atom* property = NULL;
lv2_atom_object_get(obj, plug->URIDs.patch_property, &property, 0);
if (property && property->type == plug->URIDs.atom_URID)
{
const uint32_t key = ((const LV2_Atom_URID*)property)->body;
if (key == plug->URIDs.filetype_rvb)
{
// a new file! Get file path from message (the value of the set message)
const LV2_Atom* file_path = NULL;
lv2_atom_object_get(obj, plug->URIDs.patch_value, &file_path, 0);
if (file_path)
{
// Load sample.
strcpy(plug->revtron->Filename,LV2_ATOM_BODY_CONST(file_path));
plug->revtron->setfile();
}//got file
}//property is rvb file
}//property type is a URID
}//if object is patch set
}//if atom is abject
}//each atom in sequence</code></pre>
<br />
So you see we go through each atom in the sequence. If the atom is an object containing a a patch:Set message, check if the property the patch message is setting matches our file URID. If it does then get the value the patch message is setting it to (which is really .rvb file's path the user selected).<br />
<br />
Really thats all there is to it. Except...<br />
<br />
File IO is NOT realtime safe. It could be reading off a USB 1.0 drive. Or an i2c drive. One thats networked over a dial-up connection. You never know. So unless the Reverbtron class's <code>setfile()</code> function handles it through multi-threading (and it doesn't) then this plugin is not realtime safe if we leave it like this. If you need motivation on why you should worry about being RT safe go read the <a href="http://www.rossbencina.com/code/real-time-audio-programming-101-time-waits-for-nothing">classic blogpost on the subject of realtime audio programming</a>. <br />
<br />
So LV2 conveniently has an easy way for you to do file operations or any other non-RT stuff you want safely. Its called the <a href="http://lv2plug.in/ns/ext/worker/">worker extension</a>. You can use it to calculate Fibonacci sequences or fractals, or download the internet, or whatever you want and it won't stop your RT code. Hey, we could make it calculate PI to n decimal places... I digress. The key here is that you schedule some function to be run by the "worker" (which is really just a separate thread, but you don't need to worry about it too much). This scheduling sends it off to another "place" to calculate whatever in however much time it will take. You can pass atoms to the work function for data or to indicate why the it is being run. This way you can do different tasks even though the worker always runs the same function. Once the work is complete the worker can execute a response function that can change your plugin. This second function must be RT safe. So the worker function will load the file into a struct or some useable form (not RT), then the worker response will put the struct into the plugin (RT). All you have to do is write these two functions then the host will handle the rest.<br />
Well, good luck! <br />
<br />
Just kidding. I wouldn't leave you hangin! Lets go through it together. First we need to revisit our ttl to tell the host when they load this plugin that it needs the worker extension:<br />
<pre name="code"><code>
lv2:requiredFeature urid:map , worker:schedule ;
lv2:optionalFeature lv2:hardRTCapable ;
lv2:extensionData worker:interface ; </code></pre>
<br />
The first line gets work:schedule added as the new required feature, optional features haven't changed, but we now need an extensionData struct for the worker (don't forget to add the necessary prefixes at the beginning of your ttl!). The work:interface indicates to the host that we'll use the <code>extension_data()</code> function of our plugin to hand over a struct called an <code><a href="http://lv2plug.in/doc/html/group__worker.html#structLV2__Worker__Interface">LV2_Worker_Interface</a></code> (surprise!). This struct has function pointers for the function you want the worker to run that doesn't have to be RT safe and the response function that does. Before now I haven't used the extension data in the dsp part of the plugin (just <a href="http://mountainbikesandtrombones.blogspot.com/2014/08/making-lv2-plugin-gui-yes-in-inkscape.html">in the GUI</a>), but it doesn't change too much. We need to define the struct, the functions, and put them in the plugin descriptor:<br />
<pre name="code"><code>
//forward declarations
static LV2_Worker_Status revwork(LV2_Handle handle, LV2_Worker_Respond_Function respond,
LV2_Worker_Respond_Handle rhandle, uint32_t size, const void* data);
static LV2_Worker_Status revwork_response(LV2_Handle handle, uint32_t size, const void* data);
static const LV2_Worker_Interface worker = { revwork, revwork_response, NULL };
static const void* revtron_extension_data(const char* uri)
{
if (!strcmp(uri, LV2_WORKER__interface)) {
return &worker;
}
return NULL;
}
static const LV2_Descriptor revtronlv2_descriptor={
REVTRONLV2_URI,
init_revtronlv2,
connect_rkrlv2_ports_w_atom,
0,//activate
run_revtronlv2,
0,//deactivate
cleanup_rkrlv2,
revtron_extension_data
};</code></pre>
<br />
We'll get into what the work and response functions do in a bit, but for now suffice it to say they are defined, we put them in a constant struct, and pass return that when requested by the host (when the host calls <code>revtron_extension_data(LV2_WORKER__interface);</code>). Thats not too crazy, especially if you've read my posts on UIs where I used several extensions with extension data.<br />
<br />
We next need to add an <code>LV2_Worker_Schedule*</code> member to our plugin for this will be the way to schedule the non-RT work to run. We also need to get the <code>LV2_Worker_Schedule*</code> that will come from the host during initialization. So: <br />
<pre name="code"><code>
for(i=0; host_features[i]; i++)
{
if(!strcmp(host_features[i]->URI),LV2_WORKER__schedule)
{
plug->;scheduler = (LV2_Worker_Schedule*)host_features[i]->;data;
}
else if(!strcmp(host_features[i]->URI,LV2_URID__map))
{
//urid map stuff from earlier
...</code></pre>
<br />
Now that we have that, we can use it in the <code>run()</code> function. The code is the same part as above but in the deepest part of the if tree it changes to: <br />
<pre name="code"><code>
//going through the atoms in the sequence
lv2_atom_object_get(obj, plug->URIDs.patch_property, &property, 0);
if (property && property->type == plug->URIDs.atom_URID)
{
const uint32_t key = ((const LV2_Atom_URID*)property)->;body;
if (key == plug->URIDs.filetype_rvb)
{
// a new file! pass the atom to the worker thread to load it
plug->scheduler->schedule_work(plug->scheduler->handle, lv2_atom_total_size(&ev->body), &ev->t;body);)
}//property is rvb file
}//property is URID
...</code></pre>
<br />
So instead of pulling the file path string out of the atom and calling <code>setfile(),</code> we just pass the atom to the worker. Now though, we have to be careful of multi-threading issues. If the worker is in the middle of loading the new file data while the <code>run()</code> executes, you might get some crazy behavior, reading half written variables etc. So I have to change the Reverbtron class to have 2 functions: <code>loadfile()</code> and <code>applyfile()</code>. The first will do the file operations and create a struct with the important data. <code>applyfile()</code> in turn will take the new struct and swap out the old one. <code>loadfile()</code> will be used in the work function, and the other in the RT-safe worker reply. This keeps everything clean and threadsafe. I'm not puting the load and apply code here because its very application specific, but thats the general idea. You can go look at <a href="http://github.com/ssj71/rkrlv2">the repo</a> for the full code.<br />
<br />
Once I have that in place I can write the function that the worker will run: <br />
<pre name="code"><code>
static LV2_Worker_Status revwork(LV2_Handle handle, LV2_Worker_Respond_Function respond,
LV2_Worker_Respond_Handle rhandle, uint32_t size, const void* data)
{
RKRLV2* plug = (RKRLV2*)handle;
LV2_Atom_Object* obj = (LV2_Atom_Object*)data;
const LV2_Atom* file_path;
//work was scheduled to load a new file
lv2_atom_object_get(obj, plug->URIDs.patch_value, &file_path, 0);
if (file_path && file_path->type == plug->URIDs.atom_Path)
{
// Load file.
char* path = (char*)LV2_ATOM_BODY_CONST(file_path);
RvbFile filedata = plug->revtron->loadfile(path);
respond(rhandle,sizeof(RvbFile),(void*)&filedata);
}//got file
else
return LV2_WORKER_ERR_UNKNOWN;
return LV2_WORKER_SUCCESS;
}</code></pre>
<br />
So we take that atom and just pull out the patch value, run it through <code>loadfile()</code> which returns a struct that holds all the important data from the file. We then pass the struct to the respond function, which looks like so: <br />
<pre name="code"><code>
static LV2_Worker_Status revwork_response(LV2_Handle handle, uint32_t size, const void* data)
{
RKRLV2* plug = (RKRLV2*)handle;
plug->revtron->applyfile((RvbFile*)data);
return LV2_WORKER_SUCCESS;
}</code></pre>
<br />
This simply takes the struct with the filedata and applies it. This response function is actually run between <code>run()</code> calls of your plugin so you will have predictable results (you don't have to worry about the worker finishing and changing things in the middle of your signal processing). Originally I was trying to allocate the struct in <code>loadfile()</code> so that <code>applyfile()</code> just has to swap pointers, but then you must schedule the worker thread again to delete the old struct (<code>delete/free()</code> are not RT safe either). The other obstacle is that when the host is running these functions it <i>copies</i> the data that you pass, so you have to make take care you are manipulating the data you think you are when you try to pass pointers. It can be done (the <a href="http://lv2plug.in/book/#_sampler">official example</a> does this), but I realized it made the code more complex than it needs to be. Premature optimization is the root of all evil. If profiling shows the extra memory copying is too expensive I'll worry about it then. For this example and many cases, just use plain old data (POD).<br />
<br />
So, we're done, right? Unfortunately, no. If you want the user to be able to use your plugin, they'll need to be able to load it up in their session and when they reload the session it has the same file selected. This has to be done through yet another extension: <a href="http://lv2plug.in/ns/ext/state/">state</a>.<br />
<br />
The state extension mechanics are similar to the worker extension, the host will pass in the state URI to <code>extension_data()</code> and we must return a special struct that tells the host what function to call to save and to restore the current state. Since the only state of the plugin that's not controlled by the usual controlPorts is the .rvb file thats all we need to save. So first we change the .ttl:<br />
<pre name="code"><code>
lv2:requiredFeature urid:map, worker:schedule ;
lv2:optionalFeature lv2:hardRTCapable, state:loadDefaultState ;
lv2:extensionData worker:interface, state:interface ;</code></pre>
<br />
<br />
And in the extension data, add the state interface below what we did for worker:<br />
<pre name="code"><code>
static const LV2_Worker_Interface worker = { revwork, revwork_response, NULL };
static const LV2_State_Interface state_iface = { revsave, revrestore };
static const void* revtron_extension_data(const char* uri)
{
if (!strcmp(uri, LV2_STATE__interface))
{
return &state_iface;
}
else if (!strcmp(uri, LV2_WORKER__interface))
{
return &worker;
}
return NULL;
}</code></pre>
<br />
<br />
for this extension we won't need any special mappings or anything in initialization. This is pretty much stuff we've done before, but what does the save function do? Well, something like:<br />
<pre name="code"><code>
static LV2_State_Status revsave(LV2_Handle handle, LV2_State_Store_Function store, LV2_State_Handle state_handle,
uint32_t flags, const LV2_Feature* const* features)
{
RKRLV2* plug = (RKRLV2*)handle;
LV2_State_Map_Path* map_path = NULL;
for (int i = 0; features[i]; ++i)
{
if (!strcmp(features[i]->URI, LV2_STATE__mapPath))
{
map_path = (LV2_State_Map_Path*)features[i]->data;
}
}
char* abstractpath = map_path->abstract_path(map_path->handle, plug->revtron->File.Filename);
store(state_handle, plug->URIDs.filetype_rvb, abstractpath, strlen(plug->revtron->File.Filename) + 1,
plug->URIDs.atom_Path, LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE);
free(abstractpath);
return LV2_STATE_SUCCESS;
}</code></pre>
<br />
<br />
File paths are a little unique because you don't want to store an absolute path, otherwise it won't be very portable (i.e. a preset on your machine probably won't work on mine). So there's a special part of the state extension that generates an abstract path that is more portable. Finally the big moment is calling store which comes from the state extension. You just pass in the URID for whatever you are saving, then the data you are saving, followed by the size and data type URID. The rest is just boilerplate code that you can more or less just paste and forget in most cases.<br />
<br />
From save, restore ends up being pretty predictable:<br />
<pre name="code"><code>
static LV2_State_Status revrestore(LV2_Handle handle, LV2_State_Retrieve_Function retrieve,
LV2_State_Handle state_handle, uint32_t flags, const LV2_Feature* const* features)
{
RKRLV2* plug = (RKRLV2*)handle;
size_t size;
uint32_t type;
uint32_t valflags;
const void* value = retrieve( state_handle, plug->URIDs.filetype_rvb, &size, &type, &valflags);
if (value)
{
char* path = (char*)value;
RvbFile f = plug->revtron->loadfile(path);
plug->revtron->applyfile(f);
}
return LV2_STATE_SUCCESS;
} </code></pre>
<br />
On this side, just a call to retrieve which will find the data that was saved with our URID and return it.<br />
<br />
So FINALLY we've gone from not being able to have users load files to an unsafe way to load files (patch extension), to a safe way (worker extension), to a way that allows us to reload the previous setting (state extension). Not too bad.<br />
<br />
Hopefully this was as helpful to you as it was to me as I was writing it. I was looking through the examples, but couldn't really parse out what parts applied to my plugin and what parts were for this extension or that one. I had to break it down like this to really understand whats going on.Spencerhttp://www.blogger.com/profile/12246776537628500183noreply@blogger.com1tag:blogger.com,1999:blog-2309349868071255294.post-87479876905806917812015-06-04T23:44:00.002-07:002015-06-04T23:44:50.160-07:00How to Boycott SourceForgeI was finally convinced to leave Sourceforge.net. I was holding out, trying to be true to a service that once was the best and most dedicated to free license and open source software, but finally, due largely to <a href="http://libregraphicsworld.org/blog/entry/anatomy-of-sourceforge-gimp-controversy">this editorial</a>, have decided to join the boycott. Since I don't like doing things half-way I'm taking everything down and trying to make it as easy as possible for others to do the same.<br />
<br />
If you are using git already, it won't take much to move your repositories to Github. Bitbucket will pretty much be the same commands. I already had an account so I won't describe how to sign up, but its pretty easy. Once signed in you can create a repository by clicking the little plus sign in the top right corner. Name the repo whatever your project name is and opt to leave it empty (I presume you already have the project hosted at sf.net). You should make sure you're up to date on every branch from sf.net first. Once that is done you can just go to your local repo and issue the command:<br />
<code></code><br />
git push https://github.com/[USERNAME]/[PROJECTNAME].git master<br />
<br />
and repeat for every branch besides master.<br />
<br />
You can then delete the local repo and re-clone it using the URL github give you, or just edit .git/config to update what URL origin points to, or there's some git command to do it that I can't remember. You can also go delete your project from sf.net BUT I decided to <a href="https://sourceforge.net/projects/infamousplugins/">leave the skeleton</a> of the project with links to the new home at github.<br />
<br />
To do so make sure you haven't pointed your repo's origin to github yet. If you have substitute the sf.net project git URL for origin. Go delete every branch but master on sf.net by entering:<br />
<code><br /></code>
git push origin :branch<br />
<br />
Then on master branch do (carefully):<br />
<code></code><br />
rm -rf *<br />
echo moved to [github project URL] to boycott sourceforge >> README<br />
git commit -a -m "boycott sourceforge"<br />
git push origin master <br />
<br />
<br />
Careful, because <code>rm -rf *</code> deletes everything in the current directory. This makes it so your repository is now empty except a nice little statement of what we think of the new sf.net.<br />
<br />
I only had source tarballs in the files section so I deleted them all and added the same readme as is in the repo. If you use this feature of sf.net it will be harder to find a substitute. <a href="http://michaeltunnell.com/blog/15-miscellaneous/53-14-potential-alternatives-to-sourceforge-for-binary-downloads">This blogger</a> subjectively goes through 14 alternatives and decides to stick with sourceforge. Hopefully you can find one of the alternatives workable. I also edited the project description to say that the project had moved.<br />
<br />
If your project has a website you can move it to github quite easily too. Just go to your repo that is now using github for the origin and type:<br />
<code></code><br />
git checkout -b gh-pages<br />
rm -rf *<br />
<br />
<br />The name of the branch (gh-pages) is very important. Download the site source from the sourceforge ftp if you need or if you have a local copy just copy it all into the repo. Make sure you have an index.html and then just:<br />
<code></code><br />
git add *<br />
git commit -a -m "adding site"<br />
git push origin gh-pages<br />
<br />
Your new site is at http://[USERNAME].github.io.com/[PROJECTNAME]<br />
Not too bad!<br />
<br />
I wish I had a good answer about binaries. If you use other features like mailing lists you might have to do some more homework too. If you have found good solutions, spread the word, leave a comment.<br />
<br />
Now, as a commentary, SourceForge hasn't done anything illegal. The GPL of most open software allows them completely to package it with adware, malware or whatever. But is that cool? Heck no! So on grounds of ethics, it has come time to boycott. Do not visit sf.net, do not download from there, and do not give them your code to host. I avoided Github for a long time because I felt they weren't as dedicated to open source, since their site code is not open (and partially because I tend to kick back when something seems trendy). But honestly it seems they've found a business model that is working for them and allows us more freedom for free software projects. SourceForge is not what it once was, and we owe the current management of it no loyalty. I don't really mind which service you move to, but don't stay.Spencerhttp://www.blogger.com/profile/12246776537628500183noreply@blogger.com0tag:blogger.com,1999:blog-2309349868071255294.post-90141221715317946362015-05-12T16:34:00.001-07:002015-05-12T16:34:22.464-07:00Infamous Plugins are Tiling!I'm very very excited to say this:<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhnpLhOcib-uYqtBA6krYajG-AeaIqApqer1UDSpFBe5T9qzlhAOqp82LHmxyoTNoRHR9OFN5Jt3GxAiqOBlojc2ebbVSMDSSj6-HI91lTgVmshdBfIWrFYsQvuz1_0tx5I-gxcssv8HDy9/s1600/2015-05-12-172856_2880x1800_scrot.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhnpLhOcib-uYqtBA6krYajG-AeaIqApqer1UDSpFBe5T9qzlhAOqp82LHmxyoTNoRHR9OFN5Jt3GxAiqOBlojc2ebbVSMDSSj6-HI91lTgVmshdBfIWrFYsQvuz1_0tx5I-gxcssv8HDy9/s640/2015-05-12-172856_2880x1800_scrot.png" width="640" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">1000 words</td></tr>
</tbody></table>
<br />
<br />
Thanks to falktx helping me understand what he told me months ago, the infamous plugins are all now 100% resizable. Which means I can use them comfortably in my beloved i3.<br />
<br />
These just may be the first rumblings of an early release... stay tuned.Spencerhttp://www.blogger.com/profile/12246776537628500183noreply@blogger.com3tag:blogger.com,1999:blog-2309349868071255294.post-51653896909033763442015-04-01T11:08:00.000-07:002015-04-01T11:08:24.508-07:00What a difference a new release makes (or: LTS vs Latest)I've been sticking with Ubuntu's LTS releases since I got out of school mostly because I don't have the time anymore to do a monolithic upgrade and then fix everything it breaks afterward. I've actually been championing them, because changing your whole system every 6 months is just asking for trouble. But I've been running 14.04 on this new Mac Book Pro at work and haven't been wowed by the hardware support. I think LTS is perfect for my 6 year old Thinkpad that has had full linux hardware support OOTB for 5 years or so, but with the latest greatest hardware, you need the latest greatest software.<br />
<br />
<br />
<a name='more'></a><br />
<br />
As usual the kernel has a lot of driver improvements already, but using 3.13 or whatever kernel 14.04 was stuck on didn't have them. The machine was working enough to get my tasks done but I had wifi disconnects very very frequently, I can't hot swap mini-display ports for my second monitor, and several other inconveniences reign. I'd tried just updating the kernel in 14.04 but I think I needed to recompile glibc and/or a whole bunch of other things for it to really work and I haven't had time to go figure that out either. Amazing what I'll put up with just so I can use i3wm.<br />
<br />
So yesterday I couldn't take it any more. I wanted some of those fixes in the recent kernels. So I did the monolithic upgrade to "Utopic". I did it through the terminal (sudo do-release-upgrade) and everything went quite smoothly. It took most of the day, but already I feel like it's better (placebo effect?). 14.10 is using kernel 3.16 and next month I'll go to 15.04 when its realeased which will have kernel 3.19 (which should at least fix thunderbolt/midi display hot swapping if I understand correctly).<br />
<br />
Oops, spoke too soon. Wifi just dropped. (Which, by the way, happens to the guys actually running OSX on theirs too, we've got weird wifi issues at the office). So here's hoping 3.19 will be the bee's knees. Fingers crossed.Spencerhttp://www.blogger.com/profile/12246776537628500183noreply@blogger.com0tag:blogger.com,1999:blog-2309349868071255294.post-16788150148200638822014-12-18T16:12:00.001-08:002015-10-28T12:27:25.163-07:00JACK Midi Control from your Android Phone over WiFiSince my <a href="http://infamousplugins.sourceforge.net/donate.html">fundraising</a> for my plugin GUIs is more or less bust, I've been thinking more about making music. I can't really stop developing, cause I'm fairly addicted, but I'm trying to ween myself back to just making whatever I want, rather than trying to develop stuff that will change the world. The world doesn't care that much. Anyhow, this blog is no place for bitterness.<br />
<br />
But I have been playing with synths a bit more lately and still am really missing my expression controls. Now I could try to use the old <a href="http://mountainbikesandtrombones.blogspot.com/2011/10/touchmidi.html">touchmidi app</a> I developed, but it only works with my laptop, and I now have a dedicated desktop in the studio to host my synths so I don't have a touchpad to use. I do have several android devices though. They should be great input interfaces. They can display whatever they like on the screen and have a touch interface so you can have arbitrary configurations of buttons, sliders, knobs, whatever. So I decided to figure out how.<br />
<br />
<a name='more'></a><br />
<br />
There are a few tools you need, but first: an overview. The key to sending control information from one device to another is OSC (<a href="http://en.wikipedia.org/wiki/Open_Sound_Control">Open Sound Control</a>), which in a nutshell is a way to send data over a network, designed for media applications. We need something to interpret touch input on the phone or tablet and send an OSC message. Then something needs to receive the message, interpret it, convert it to a midi message and send it out a JACK midi port. Well, we aren't inventing the wheel, there are several programs that can do these or related tasks.<br />
<br />
One closed source system is <a href="http://hexler.net/software/touchosc">TouchOSC</a>. They have a free desktop app and the android app is a few dollars. But its closed source and doesn't actually work on linux. UNACCEPTABLE.<br />
There are really several other apps, many of them free and none of them have had updates in the last few years. Ardroid is an OSC controller, but it is meant for ardour, you can't make custom interfaces like you can with TouchOSC.<br />
<br />
What we want is <a href="http://charlie-roberts.com/Control/">Control</a>. It hasn't been updated for a few years and its kinda buggy, but its OPEN. I could go fix it from the <a href="https://github.com/charlieroberts/Control">github source</a> (but it would take me a bit of research on android stuff and I'm not a java fan) but its good enough as it is to get by. It uses customizable interface layouts written in JSON and you can add scripting to the interface through javascripts, so no crazy new formats. The real bugs are just in the landscape and portrait mode switching, so I have to open and close the interface a couple times before it gets it right. It's also cross platform.<br />
<br />
I was able to make an interface after a few infuriating late night hours of trying to edit one of the examples (that I'm pretty sure had a lot of iOS specific JSON tags), then trying again next morning using the blank template and getting it done in about an hour. I never learn. Its a little involved to make a custom interface but there are good <a href="http://www.charlie-roberts.com/Control/interfaces/">examples</a> and <a href="http://charlie-roberts.com/Control/?page_id=32">decent documentation</a>. It seems after a while of development the author focused much more on iOS and neglected the Android side, so there are several tags like color that don't work on android and make the interface much buggier if you attempt to use them. If someone wants to be super rad they would take this app and fix it up, make it a bit more robust and report json errors found in layouts etc... But its good enough that I'll just keep using it as it is.<br />
<br />
For starters, you can just use the interfaces the app comes with. Go ahead and <a href="https://play.google.com/store/apps/details?id=com.charlieroberts.Control&hl=en">install it from google play</a>. They have a pretty good variety of interesting interfaces that aren't all exactly conducive to midi control (remember apps CAN have built in OSC support like ardour and non-mixer do) but any of the interfaces built into Control will work. I'll tell you a bit about some other interfaces later.<br />
<br />
UPDATE: Since I started writing this, another open source android app has come up: andrOSC. Its much simpler but allows you to edit everything in the app. Its a great option for anyone who wants a custom interface without any sort of file editing or extra uploading. Its a little too simple for what I want so I'm going to stick with Control for now.<br />
<br />
<br />
Now we just need someplace to send the OSC data that the Control interface will output. Well I thought <a href="http://das.nasophon.de/mididings/">mididings</a> was the tool for this job but it only accepts a few arbitrary OSC commands and there is no way to change the filtering through OSC aside from making scenes and switching between them. So that was a dead end.<br />
<br />
But, the libraries used by mididings are openly available, so I figured I'd just make a python script to do the same thing. Except I've only edited a few python scripts and this is a few steps beyond that. The libraries used are actually c libraries anyway, so c wins. Hold on a sec, I'll just whip up a C program.<br />
<br />
(Months go by)<br />
<br />
Ah that was quick, right? Wait, Christmas is in how many days?!<br />
Oh well. It works. And its completely configurable (and more features on the way). This new program, OSC2MIDI, gives you the flexibility to change the mapping wherever its easiest, in your OSC client app (i.e. our Control interface), in the osc2midi converter map file, or in the midi of the target program. In another post I'll describe how to use osc2midi more fully (with custom mappings) for now the default one works (because I wrote it to work with all the example interfaces that come with Control).<br />
<br />
<strike>Download a snapshot</strike>, (EDIT: I just released the first beta, from now on, <a href="https://github.com/ssj71/OSC2MIDI/releases">download the files</a>, 2nd edit, updated link) extract them, and install them:<br />
<code>
cd osc2midi<br />
cd build <br />
cmake ..<br />
make<br />
sudo make install<br />
</code>
<br />
Then run:<br />
<code>
osc2midi<br />
</code>
<br />
<br />
Not too bad (if it didn't work cmake should help you, or just check the README for the dependencies). This gives you a jack midi client and opens an OSC host at 192.168.X.X:<span class="st">57120 </span>(Your computer's IP address : Default port). If you don't know your ip address you can get it with the terminal command <code>ipconfig</code>. Connect the jack midi out port to Carla or whatever you like (I'll proceed with assuming you are using Carla).<br />
<br />
Now we need to connect the Control app to the OSC server. Click the destinations tab and the plus sign at the top to enter in the address of the OSC host. From there go back to the interfaces tab and select whatever interface sounds like it will tickle your fancy. For this exercise we'll go ahead and use the mixer interface. If it looks squashed or doesn't fit on the screen you probably have to go back to the menu and open the interface one more time. I always have to open it twice like that (obnoxious, right? you should really <a href="https://github.com/charlieroberts/Control">go fix that</a>!).<br />
<br />
In Carla, load a plugin. Simple amplifier is perfect. First connect the osc2midi output port to the simple amplifier events input port. Open the editing dialog for the plugin and click the parameters tab at the bottom. Simple amplifier only has a single parameter (gain) and so you'll see a single horizontal slider and 2 spinboxes right of it. These numbers select the midi control number (CC) and channel.<br />
<br />
Since we called osc2midi without arguments it defaults to channel 1 (or 0 if you prefer 0 indexing) and the first slider in the mixer interface on your android device will send midi CC 1 so select "cc #1" in the middle numeric box. You just bound that midi control to change the amplifier gain. If your jack ports are all set up correctly you should be able to change the first slider on your phone/tablet and watch the slider in Carla wiggle up and down with it. You have CONTROL!!!<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjMhy3ZUs5aKgQNFeha-rsbnvZqjO88nHqaYe5WULoFa3HJCOZguL8NSrSrRbghRo_qB5HdC8upI9NvspzlBOPH_llkNSHT-u2dbC3_GFU1DwJicUGCStYjrNzlsog3LMj-ioWLkT67HRd5/s1600/2014-12-18-160228_2880x1800_scrot.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjMhy3ZUs5aKgQNFeha-rsbnvZqjO88nHqaYe5WULoFa3HJCOZguL8NSrSrRbghRo_qB5HdC8upI9NvspzlBOPH_llkNSHT-u2dbC3_GFU1DwJicUGCStYjrNzlsog3LMj-ioWLkT67HRd5/s1600/2014-12-18-160228_2880x1800_scrot.png" width="640" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">The setup</td></tr>
</tbody></table>
The other sliders are just sequentially mapped through CC 12. You can figure out what anything is mapped to by either using kmidimon to look at the midi output or running osc2midi in verbose mode (<code>osc2midi -v</code>). You can add other plugins and control them by the same method. Get creative with it.<br />
<br />
For my next trick, we'll make some sounds with Control. Hit the menu button on Control and select the Conway's Game of Life interface. The default mapping works but I include a better one with the installation. So in the terminal hit Ctl+C to stop osc2midi. We'll start it again specifying a different map file. Type in:<br />
<code>
osc2midi -m gameOfLife.omm<br />
</code>
And the server will be restarted on the same port so you won't need to change anything else.<br />
<br />
Now lets load an instrument plugin into Carla. I like amsynth but you can use any you like (though a polyphonic one will probably be more fun for this exercise). With an instrument plugin loaded just connect the midi-in port of the instrument to the osc2midi output the the audio output from your plugin/carla to your soundcard. Click several of the buttons on the grid in Control and you should hear some notes start playing. They will stay on till you click it again. Or click the start button and the notes start toggling themselves and you have a nice random sequencer.<br />
<br />
This isn't quite enough for simulating all the expression you can get out of a good synthesizer or midi controller, but its a start. The best part is these open tools are all highly configurable, so we have a working system with just using all the defaults. But we can also setup very powerful and custom configurations by tweaking the interface in Control, the mapping in osc2midi, and often the midi bindings in your destination program (like Carla). But we'll save the rest for another post. In the mean time, try all the templates and get creative with these tools.Spencerhttp://www.blogger.com/profile/12246776537628500183noreply@blogger.com5tag:blogger.com,1999:blog-2309349868071255294.post-58331907589575816712014-12-08T21:57:00.002-08:002015-11-06T13:18:20.136-08:00Easy(ish) Triple Boot on 2014 Macbook ProUPDATE* Feel free to read, but I've since renounced this process and made a new one <a href="http://mountainbikesandtrombones.blogspot.com/2015/11/easyish-triple-boot-on-macbook-pro.html">here</a>.<br />
Nothing is easy. Or perhaps everything is. Regardless, here is how I did it, but first a little backstory:<br />
<br />
I got a macbook pro 11,3 from work. I wanted a lenovo, but the boss wants me to do some iOS stuff eventually. Thats fine, cause I can install linux just as easily on whatever. Oh wait.. There are some caveats. Boot Camp seems to be a little picky. Just as well. MIS clowns set up boot camp so I had windows 7 and Yosemite working, but they told me I'm on my own for linux. It seems from the posts I've read about triple booting is that you have to plan it out from the get-go of partitioning, not just add it in as an afterthought. But I also found suggestions about wubi.<br />
<br />
<a name='more'></a><br />
<br />
I've used wubi and didn't really understand what it did, but its actually perfect for setting up a triple boot system in my situation (where it's already dual boot and I want to tack on linux and ignore the other two). There is a lot of misunderstanding that wubi is abandoned and no longer supported bla bla. The real story is that the way wubi works doesn't play nicely with windows 8. Therefore if it doesn't work for everybody Ubuntu doesn't want to advertise it as an option. Its there, but they'd rather have everyone use the most robust method known: full install from the live cd/usb. Not that wubi is rickety or anything, but only works in certain situations (windows 7 or earlier). The reality is its on every desktop ISO downloaded, including latest versions (more on that later).<br />
<br />
The way wubi works is important to note too (and its the reason that its perfect for this situation). Wubi creates a virtual disk inside the NTSC (windows) partition of the disk. So instead of dividing the hard drive space into two sections (one for linux, one for windows, and/or a third for OSX if triple boot) it doesn't create disk partitions at all, just a disk file inside the existing windows partition. The windows bootloader is configured to open the windows partition then mount this file as another disk in whats called a loopback mode. This is distinctly contrasted to a virtualized environment where often a virtual disk is running on virtual hardware. You are using your actual machine, just your disk is kinda configured in a unique but clever way.<br />
<br />
The main downside it sounds like is that you could have poor disk performance. It sounds like in extreme cases, VERY poor performance. Since this machine was intended for development its maxed out with 16GB ram, so I'm not even worrying about swap, and the 1TB hdd has plenty of space for all 3 OSes and its a fresh install so shouldn't be too fragmented. These are the best conditions for wubi. So far it seems to be working great. Install took a little trial and error though.<br />
<br />
So I had to at least TRY to teach you something before giving you the recipe, but here goes:<br />
<br />
<ol>
<li>I had to install bootcamp drivers in windows. MIS should have done that but they're clowns. You'll have to learn that on your own. There are plenty of resources for those poor mac users. This required a boot into OSX.</li>
<li>Boot into windows.</li>
<li>Use the on screen keyboard in the accessibility options of the windows to be able to hit ctl+alt+delete to make up for the flaw that macbooks have no delete key (SERIOUSLY?) Also don't get me started on how I miss my lenovo trackpoints.</li>
<li>I installed <a href="http://www.randyrants.com/sharpkeys/">sharpkeys</a> to remap the right alt to be a delete key so I could get around this in the future. I know sooner or later Cypress will make me boot into windoze.</li>
<li>Download the <a href="http://www.ubuntu.com/download/desktop">Ubuntu desktop live CD ISO</a> (I did the most recent LTS. I'm not in school any more, gone are the days where I had time to change everything every 6 months).</li>
<li>In windows install something that will let you mount the ISO in a virtual cd drive. You could burn it to CD or make a live USB, but this was the quickest. I used <a href="http://wincdemu.sysprogs.org/">WinCDEmu</a> as it's open source.</li>
<li>Mount the ISO and copy wubi.exe off of the ISO's contents and into whatever directory the ISO is actually in (i.e. Downloads).</li>
<li>Unmount the ISO. This was not obvious to me and caused an error in my first attempt.</li>
<li>Disable your wifi. This was not obvious to me and caused an error in my second attempt. This forces wubi to look around and find the ISO that is in the same folder rather than try to re-download another ISO.</li>
<li>Run wubi.exe .</li>
<li>Pick your install size, user name, all that. Not that it matters but I just did vanilla ubuntu since I was going to install i3 over the Unity DE anyway. Historically I always like to do it with xubuntu, but I digress.</li>
<li>Hopefully I haven't forgotten any steps, but that should run and ask you to reboot. (I'd re-enable the wifi before you do reboot, or else you'll forget like I did and wonder why its broken next windows boot).</li>
<li>The reboot should complete the install and get you into ubuntu.</li>
<li>I believe the next time you reboot it will not work. For me it did not. Its due to a grub2 bug I understand. Follow the solutions in these two threads: </li>
<ol>
<li><a href="http://ubuntuforums.org/showthread.php?t=2218439&p=13094149#post13094149">http://ubuntuforums.org/showthread.php?t=2218439&p=13094149#post13094149</a></li>
<li><a href="http://ubuntuforums.org/showthread.php?t=2217829&p=12996954#post12996954">http://ubuntuforums.org/showthread.php?t=2217829&p=12996954#post12996954</a></li>
</ol>
<li>To roughly summarise the process, hit the e key to edit the grub config that will try to load ubuntu. Edit the line <span style="color: black;"><span style="font-family: "ubuntu mono";"><br />
linux /boot/vmlinuz-3.13.0-24-generic root=UUID=bunchofhexidec loop=/ubuntu/disks/root.disk <u><b>ro</b></u></span></span> <br />ro should be changed to rw. This will allow you to boot. The first post tells you to edit an auto-generated file. Thats silly. what happens when it gets auto-generated and again and overwrites your fix? It even says not to edit it in the header. Instead you need to make a similar change to the file that causes it to have that error and then generate those files again as described in the second link.</li>
<li>Once that is sorted out you'll probably notice that the wifi is not working. You can either use an ethernet port adapter or a USB wifi card (or figure out another way) but get internet somehow and install bcmwl-kernel-source and it should start working (maybe after a logout. I don't remember).</li>
<li>Another tweak you will need is that this screen has a rediculously high DPI so the default fonts are all teensy-tiny. The easiest workaround is just to lower the screen resolution in the displays setting of unity-command-center, but you can also edit the font sizes in that dialog and/or using unity-tweak-tool. I'm still ironing that out. Especially since my secondary monitors are still standard definition. xrandr --scale is my only hope. Or just lower the resolution.</li>
<li>You might find that the touchpad click doesn't work as you expect. Try running the command:<br /><span style="font-family: "courier new" , "courier" , monospace;">synclient ClickPad=0</span><br />and see if you like it better. I sure do. Also enable two finger scrolling in the unity-control-center.</li>
<li>Also, importantly, wubi only allows up to 30GB of virtual disk to be created. I wanted a lot more than that. So I booted off a USB live stick I had laying around and followed the <a href="https://help.ubuntu.com/community/ResizeWubiDisk">instructions here</a> to make it a more reasonable 200GB.</li>
<li>Finally install i3-wm, vifm, eclipse, the kxstudio repos and everything else you love about linux.</li>
</ol>
So I love my macbook. Because its a linux box. Spencerhttp://www.blogger.com/profile/12246776537628500183noreply@blogger.com0tag:blogger.com,1999:blog-2309349868071255294.post-64848565448054367792014-11-21T12:58:00.002-08:002014-11-21T15:39:35.777-08:00Common Synthesizer Envelope TypesThe term "infographic" seems like such a marketing buzzword it makes me want to ignore whomever says it. But I'm all for the presentation of information in a logical and clear manner. As I was pondering on how to increase interest in <a href="http://infamousplugins.sourceforge.net/">my audio plugins</a> I thought about my ADBSSR envelopes that I love in the <a href="http://mountainbikesandtrombones.blogspot.com/2013/07/the-infamous-cellular-automaton-synth.html">cellular automaton synth</a>. I wanted some way to present quickly both how to use them and why they offer advantage over traditional ADSR envelope generators.They do add complexity but there's really no way to add a swell as we horn players do all the time to a patch without it. It even can look exactly like an ADHR when the Breakpoint is set to 1. Anyway I thought a diagram with explanatory text would do the trick. So I made one:<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgbmFXn7mOmvRjTi5JOU6MCoWkCDk3K-Rs6eXRQIWwfF9012S5HGbBWDLoObuxU3T84J54CoHHG05xciwwHc-8YXJOCKfOsIyC5Jsot9QNhNWjFFrC7kOaRLWqttZXbFtTW7fSE49D-euoQ/s1600/envtypes.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgbmFXn7mOmvRjTi5JOU6MCoWkCDk3K-Rs6eXRQIWwfF9012S5HGbBWDLoObuxU3T84J54CoHHG05xciwwHc-8YXJOCKfOsIyC5Jsot9QNhNWjFFrC7kOaRLWqttZXbFtTW7fSE49D-euoQ/s1600/envtypes.png" height="640" width="436" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Figure 1: Not an infographic</td></tr>
</tbody></table>
This isn't an infographic though. I don't really know why I think that word is so stupid. Probably just because I tend to hate on anything trendy and hyped. Anyhow. Now you know how I feel. This image is in the public domain though I would appreciate citation. Spencerhttp://www.blogger.com/profile/12246776537628500183noreply@blogger.com0tag:blogger.com,1999:blog-2309349868071255294.post-69288189010677580362014-11-11T11:38:00.002-08:002014-11-11T12:57:31.161-08:00FIX IS VERY SWEET!One of my mission companions would say that a lot. He threatened (jokingly) to fix people. He was Tongan and barely spoke English or Filipino but we had fun and got some good work done.<br />
<br />
Regardless of that, my Presonus Firebox had been making a strange whining sound for a while. I started a session a few weeks ago to finally begin a new song but (as usual) didn't get very far before fatherly duty called and the session sat for at least 2 weeks or so. This happens as regularly as I record which is roughly every few weeks. I eventually passed through the studio to grab something out of our food storage in the next room and realized that the firebox was completely dark. "Strange," I thought so I went and tried to restart JACK. The firebox wheezed and whined and eventually the blue LED slowly lit up. "Not good," I thought (I usually think very tersely), but I had to get that food upstairs and get dinner on for the kids.<br />
<br />
<a name='more'></a><br />
<br />
I didn't read too much into it at first since I was pretty busy. But when I came back to it after a few days, I tried it again with the wall power. It powered up more quickly but still not anything like the instant blue light it used to bring up and it was still very audibly whining during operation. I tried plugging the firewire cable into the other ports on the computer and the interface. I tried changing some jack settings and eventually came to the discovery that it was making this audible noise even when nothing was plugged into it except AC power, not even attached to the computer. It sounded like some communication noise I've heard before, like a uart running at 9600 baud or something, but I reaffirmed that it wasn't a good thing. I had noticed it making this noise quietly for the last few months but now it was very noticeable. Playing with my condenser mic a little showed that whatever had changed raised my noise floor significantly.<br />
<br />
The firebox works just fine in Linux using the FFADO drivers. It's not feature complete since I can't do the hardware monitoring that I'm pretty sure the firebox is capable of, but it's plenty good for me and my one man band recording methods. It has clean preamps (relative to the internal sound card of my laptop) and is useable at whatever rates I need (usually just 24bit 44.1khz though). And finding a new audio interface that works on linux is no small task. It was especially painful to think of needing to replace it because I'm finally about to have enough money in the budget to buy my first studio monitors, and even just meeting another guy in some middle school parking lot to buy a replacement interface for $60 again would threaten putting off the monitors for some time more.<br />
<br />
So with heavy heart I pulled it apart this morning. Actually I had already fixed a broken chair I'd been meaning to get to and a tape measure that I dropped off the roof while re-shingling a few months ago, so I was on a roll. But I pulled it apart without too much trouble and tried to see what was going on. Luckily the damage was fairly noticeable. The insulation on the wire that connects the headphone jack to the upper PCB had melted to the cap on the lower board.<br />
<br />
Most of the info I could find about problems with the firebox had a cap completely blow with some charring etc. This seemed much less dramatic and I was concerned that this one slightly marred cap wasn't fully the problem. But it was the best I had to go off of.<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjIbwjxvSfIRQacmm9_EvryZGWh8KjtShcfQaIEj8PeC8QSNiHYoR_4q7dETtgKd2g9dv_Zdh5dfxcUiDrrn5YUIh3lX4E4Lgqjp24pImJ0id03JnyNfShXB5exdz32E2lJArGqzwnDj7u6/s1600/IMG_20141110_151612473%5B1%5D.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjIbwjxvSfIRQacmm9_EvryZGWh8KjtShcfQaIEj8PeC8QSNiHYoR_4q7dETtgKd2g9dv_Zdh5dfxcUiDrrn5YUIh3lX4E4Lgqjp24pImJ0id03JnyNfShXB5exdz32E2lJArGqzwnDj7u6/s1600/IMG_20141110_151612473%5B1%5D.jpg" height="223" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">The damaged capacitor, Notice the melted plastic and the top seems slightly bubbled</td><td class="tr-caption" style="text-align: center;"><br /></td></tr>
</tbody></table>
<br />
<br />
I had a Nicholsen PW(M) cap that was 470uF but it was 25V instead of the 10V that the Chang cap I was replacing. The PW series aren't audio grade but I think the Chang wasn't either since this was near the power section anyway. I was glad to be upgrading the rated voltage of the cap, but this meant the new one was much larger. I had to get creative with the placement to keep it out of the way of the headphone jack and not touch the chassis or other components.<br />
<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEieMpOhb8BRSCOYMGr07Mmj9To6zAlWQfVTPsX-_QZWlCiyFdZkN9dTpotRmUubHkBNEVT_aZ1iJVC8vl2xmKy9RdxCYzqKiOyRnhfN9ZKKEUo_HXbtNfbPM_65mvtZpH2ONdojdXDQeQwB/s1600/IMG_20141110_151647588%5B1%5D.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEieMpOhb8BRSCOYMGr07Mmj9To6zAlWQfVTPsX-_QZWlCiyFdZkN9dTpotRmUubHkBNEVT_aZ1iJVC8vl2xmKy9RdxCYzqKiOyRnhfN9ZKKEUo_HXbtNfbPM_65mvtZpH2ONdojdXDQeQwB/s1600/IMG_20141110_151647588%5B1%5D.jpg" height="640" width="358" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">The new capacitor in place. You can also see the slightly melted insulation on the middle white headphone jack wire.</td><td class="tr-caption" style="text-align: center;"><br /></td><td class="tr-caption" style="text-align: center;"><br /></td><td class="tr-caption" style="text-align: center;"><br /></td></tr>
</tbody></table>
The soldering was fairly trivial. Wick the old solder, pull the part, solder in the new one. I put some electrical tape on the headphone jack wires to help prevent them getting further compromised. I got lucky because I had left one of the leads just a bit too long and the first time I plugged it in they were shorting to the chassis, so when I plugged it in I got no power light at all. In retrospect I was lucky it didn't blow up something, but it was very disappointing. I took it all apart trying to figure out what had gone wrong and ohmed out all the transistors I could find to see if any had shorted.<br />
<br />
When that proved fruitless I plugged it in without the chassis and it worked! I then just added one screw at a time and tested if it still turned on. Next screw. Test. Etc. When I got to the front part of the chassis attached, thats when I realized I didn't have good clearance on that lead of the new cap. I trimmed it and put the front on. Test. SUCCESS! I continued doing this through the rest of the assembly to be safe but in the end I had it running perfectly silently, fully assembled, there on the workbench.<br />
<br />
I think this is the first time I've had a complex electronic item, took it apart, and was able to fix it without a schematic. It felt so awesome! Fixing the house, the chair, my remote control helicopter motor... mechanical issues are easy to diagnose and fix. Electronics though take either detailed documentation and knowledge, or a little luck. So when it works, fix is very sweet.<br />
<br />
I took it home and just as a test hooked up my condenser, cranked the gain and added a simple amplifier plugin for more gain. Silence. I am back to my original noise floor! From now on I think I'll shut down my computer between sessions and use the AC power instead of just bus power.<br />
<br />
Now I just need to actually make a recording again.Spencerhttp://www.blogger.com/profile/12246776537628500183noreply@blogger.com0tag:blogger.com,1999:blog-2309349868071255294.post-80089169815465321812014-10-24T10:25:00.000-07:002014-10-25T10:12:33.028-07:00My First Linux Audio Tutorial VideoI like that title. It makes me think of chunky toys and blocky characters with big eyes. But this is much less cute than my kids playthings: I made a video to introduce my infamous plugins. Here is the video. How I did it is below.<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen='allowfullscreen' webkitallowfullscreen='webkitallowfullscreen' mozallowfullscreen='mozallowfullscreen' width='320' height='266' src='https://www.youtube.com/embed/oHCPgh9HRAQ?feature=player_embedded' frameborder='0'></iframe></div>
(if you can't see the embeded player here's the link: <a href="http://youtu.be/oHCPgh9HRAQ">http://youtu.be/oHCPgh9HRAQ</a> )<br />
EDIT: The second one is done now too. See <a href="http://youtu.be/izyf27eLPPA" target="_blank">http://youtu.be/izyf27eLPPA</a><br />
<br />
<a name='more'></a><br />
<br />
This example isn't perfect, since I had to do a little editing in kdenlive and chose a poor output resolution and format (720p mp4). It looks ok in small screens but full screen looks pretty rough. But the point is that it is perfectly synchronized to the jack audio. The raw clips look great. This can only be accomplished through ffmpeg. With a patch. But it IS possible and here's how (using kxstudio):<br />
<br />
<code>#get tools and source<br />sudo apt-get install yasm libvorbis-dev libx263-dev libxfixes-dev </code><code><code>libmp3lame-dev </code></code><br />
<code><code> </code>wget http://ffmpeg.org/releases/ffmpeg-2.4.2.tar.bz2<br />tar -xvf ffmpeg-2.4.2.tar.bz2<br /><br />#patch the source<br />cd ffmpeg-2.4.2/libavdevice/<br />wget
http://sourceforge.net/p/infamousplugins/code/ci/2c97af07ea6fb54eca55f6bcdd707a3ad60c0325/tree/test/ffmpeg-2.4.2-jack.patch?format=raw<br />mv ffmpeg-2.4.2-jack.patch?format=raw ffmpeg-2.4.2-jack.patch<br />patch < ffmpeg-2.4.2-jack.patch<br /><br />#build ffmpeg<br />cd ..<br />./configure --enable-x11grab --enable-libvorbis --enable-libx264 --enable-indev=jack </code><code><code>--enable-libmp3lame</code></code><br />
<code><code> </code>--enable-gpl<br />make<br />sudo make install<br />sudo ln -s /usr/local/bin/ffmpeg /usr/local/bin/ffmpeg_harvid<br /><br />#record a screencast using the current screen<br />ffmpeg
-fflags +genpts+igndts -f x11grab -vsync 0 -r 30 -s 1440x900 -i :0
-vcodec h264 -f jack -ac 2 -r:a 48000 -i screencast -acodec pcm_s16le
-r:v 30 -vsync 2 -async 1 -map 0:0,1,0 -map 1:0 -preset ultrafast -qp 0
"thisisonlyatest.mkv"</code><br />
<br />
<code><code>#compress it for youtube<br />ffmpeg -i "thisisonlyatest.mkv" -acodec mp3 -ab 160000 -vcodec h264 "thisisonlyatest-final.mkv"</code> </code><br />
<br />
The nice thing is that ubuntu 14.04 and variants use avconv so installing an ffmpeg binary shouldn't get in the way of anything. Alternatively to that last command to record if you use kxstudio or the ppa's use the kxstudio scripts as falkTX described <a href="http://linuxmusicians.com/viewtopic.php?f=47&t=9661">here.</a><br />
<br />
I wrote the patch, but the solution really comes from a patch made by <a href="http://www.linuxmusicians.com/viewtopic.php?f=19&t=11352">male last year</a>. The patch was incorporated into the 12.04 ppas ffmpeg build, but I am running 14.04 so it no longer applied. Necessity is the mother of invention. And updates. So I just adapted the changes to ffmpeg 2.4.2 (the current release) and figured out how to make it work so that I could start making videos about my plugins. Anyway the patch should apply to any distro and hopefully this will open the floodgates for many beautiful, perfectly sync'd linux audio tutorials and demo videos.<br />
<br />Spencerhttp://www.blogger.com/profile/12246776537628500183noreply@blogger.com2tag:blogger.com,1999:blog-2309349868071255294.post-62554592315590780892014-09-10T14:56:00.000-07:002014-09-10T15:28:55.501-07:00Adding Blur in CairoI've made several widgets now that light up. I've worked around it mostly through gradients. Radial gradients are perfect for a circular LED. A linear gradient filling a carefully shaped path got me by for a square LED. But now I'm making an analog oscilloscope type widget, and a 7 segment LED and gradients aren't cutting it. If only you could do blur in Cairo.<br />
<br />
Oh, but you can...<br />
<a name='more'></a><br />
<br />
Cairo doesn't NATIVELY support blur, but you can totally blur in Cairo. In fact there is <a href="http://cairographics.org/cookbook/blur.c">an "official" blur example</a> in the cairo cookbook on their site. I'm no Cairo master, so it took me a bit to figure out how to really apply it to my svg2cairo converted code, dispite having several other examples. But if you've been following along on my ffffltk development you'll also find this post useful. If you haven't... then this is just another example, (though the only one I know of using the "official" blur function).<br />
<br />
The goal of ffffltk is to get from inkscape drawing to GUI binary with as little coding as possible, and its not terrible. You do have to do some coding, but its much better than my last attempt was using Qt (more because I've learned a lot than any shortcoming of Qt). Blur unfortunately departs slightly from that goal, but its useful enough that it's worth it. Overall the process is to make your drawing with blur, remove the blur before you convert to cairo (else svg2cario generates unbuildable code), convert to cairo, add the blur in the code, proceed making your gui in ntk-fluid. We'll break down what "add the blur in the code" constitutes as we go.<br />
<br />
To make the cairo code generated by svg2cairo work as an ffffltk widget you already have to get your hands dirty editing the code, and adding blur is just a little bit extra work. So lets play with the 16 segment LED display I'm currently working with. Here's the drawing with and without blur:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEis2LLmmCGNwjx1j6I3Txk-3wWDZIrvuEk1U5nP56F6-G-qqMUpXtvgOE_4NffU0oQ3DWdJ9wuAI0FTXZJ_uMZYXHX3OXVxb678GAtWjXNhnf_vfl6KZRfdsBa2qXG-E0hvFqwkRyhPcCSk/s1600/segleds.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEis2LLmmCGNwjx1j6I3Txk-3wWDZIrvuEk1U5nP56F6-G-qqMUpXtvgOE_4NffU0oQ3DWdJ9wuAI0FTXZJ_uMZYXHX3OXVxb678GAtWjXNhnf_vfl6KZRfdsBa2qXG-E0hvFqwkRyhPcCSk/s1600/segleds.png" height="172" width="320" /></a></div>
<br />
<br />
You see even a 3 pixel blur makes a nice difference to make it look like its glowing. Its also perfect for shadows. The code generated by the non-blurred version (blur doesn't work with svg2cairo) (and run through the script I gave you in <a href="http://mountainbikesandtrombones.blogspot.com/2014/07/creating-gui-in-inkscape.html">this previous post</a>) looks roughly like this:
<br />
<pre><code>#ifndef DRAW_BLUE16SEG_H
#define DRAW_BLUE16SEG_H
#include"blur.h"
inline int cairo_code_draw_16seg_get_width() { return 30; }
inline int cairo_code_draw_16seg_get_height() { return 45; }
inline short char2seg(char c);
inline void cairo_code_draw_16seg_render(cairo_t *cr, char num, unsigned char color) {
unsigned short val = char2seg(num);
cairo_surface_t *temp_surface;
cairo_t *old_cr;
cairo_pattern_t *pattern;
cairo_matrix_t matrix;
cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
pattern = cairo_pattern_create_rgba(0.4,0.4,0.4,1);
cairo_set_source(cr, pattern);
cairo_pattern_destroy(pattern);
cairo_new_path(cr);
cairo_move_to(cr, 3.753906, 0.761719);
cairo_line_to(cr, 26.492188, 0.761719);
cairo_curve_to(cr, 28.148438, 0.761719, 29.492188, 2.101562, 29.492188, 3.757812);
cairo_line_to(cr, 29.492188, 41.550781);
cairo_curve_to(cr, 29.492188, 43.207031, 28.148438, 44.550781, 26.492188, 44.550781);
cairo_line_to(cr, 3.753906, 44.550781);
cairo_curve_to(cr, 2.097656, 44.550781, 0.757812, 43.207031, 0.757812, 41.550781);
cairo_line_to(cr, 0.757812, 3.757812);
cairo_curve_to(cr, 0.757812, 2.101562, 2.097656, 0.761719, 3.753906, 0.761719); cairo_close_path(cr);
cairo_set_tolerance(cr, 0.1);
cairo_set_antialias(cr, CAIRO_ANTIALIAS_DEFAULT);
cairo_set_fill_rule(cr, CAIRO_FILL_RULE_WINDING);
cairo_fill_preserve(cr);
/********************/
cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
cairo_set_line_width(cr, 1.5);
cairo_set_miter_limit(cr, 4);
cairo_set_line_cap(cr, CAIRO_LINE_CAP_SQUARE);
cairo_set_line_join(cr, CAIRO_LINE_JOIN_MITER);
pattern = cairo_pattern_create_rgba(0,0,0,1);
cairo_set_source(cr, pattern);
cairo_pattern_destroy(pattern);
cairo_pattern_set_matrix(pattern, &matrix);
/********dot************/
cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
pattern = cairo_pattern_create_rgba(0,0,.3333,1);
cairo_set_source(cr, pattern);
cairo_pattern_destroy(pattern);
cairo_new_path(cr);
cairo_move_to(cr, 27.773438, 39.507812);
cairo_curve_to(cr, 27.773438, 40.652344, 26.847656, 41.578125, 25.707031, 41.578125);
cairo_curve_to(cr, 24.5625, 41.578125, 23.636719, 40.652344, 23.636719, 39.507812);
cairo_curve_to(cr, 23.636719, 38.367188, 24.5625, 37.441406, 25.707031, 37.441406);
cairo_curve_to(cr, 26.847656, 37.441406, 27.773438, 38.367188, 27.773438, 39.507812);
cairo_close_path(cr);
cairo_set_tolerance(cr, 0.1);
cairo_set_antialias(cr, CAIRO_ANTIALIAS_DEFAULT);
cairo_set_fill_rule(cr, CAIRO_FILL_RULE_WINDING);
cairo_fill_preserve(cr);
/********************/
cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
pattern = cairo_pattern_create_rgba(0,0,.3333,1);
cairo_set_source(cr, pattern);
cairo_pattern_destroy(pattern);
cairo_new_path(cr);
cairo_move_to(cr, 18.671875, 3.734375);
cairo_line_to(cr, 18.234375, 6.359375);
cairo_line_to(cr, 19.601562, 8.484375);
cairo_line_to(cr, 23.050781, 8.484375);
cairo_line_to(cr, 26.503906, 4.953125);
cairo_line_to(cr, 25.71875, 3.734375);
cairo_close_path(cr);
cairo_set_tolerance(cr, 0.1);
cairo_set_antialias(cr, CAIRO_ANTIALIAS_DEFAULT);
cairo_set_fill_rule(cr, CAIRO_FILL_RULE_WINDING);
cairo_fill_preserve(cr);
/********************/</code></pre>
<br />
<pre>...
</pre>
I added the #include blur.h which is really the blur.c in the cookbook edited just a bit. The full code has 16 dark segments, and 16 identically shaped light ones on top. To make this work as a widget I edited the render function to accept a character and either draw or don't draw light segments based on that character. The full implementation of course is available in <a href="http://sourceforge.net/p/infamousplugins/code/ci/gui/tree/src/draw/">my infamous repository</a>. I have a function that encodes the character to a bit sequence where each bit corresponds to a segment (char2seg()). Therefore I use an if to decide if the segment should be drawn like so:<br />
<pre><code>... if(val&0x4000){
cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
pattern = cairo_pattern_create_rgba(.3,.6,1,1);
cairo_set_source(cr, pattern);
cairo_pattern_destroy(pattern);
cairo_new_path(cr);
cairo_move_to(cr, 18.671875, 3.734375);
cairo_line_to(cr, 18.234375, 6.359375);
cairo_line_to(cr, 19.601562, 8.484375);
cairo_line_to(cr, 23.050781, 8.484375);
cairo_line_to(cr, 26.503906, 4.953125);
cairo_line_to(cr, 25.71875, 3.734375);
cairo_close_path(cr);
cairo_set_tolerance(cr, 0.1);
cairo_set_antialias(cr, CAIRO_ANTIALIAS_DEFAULT);
cairo_set_fill_rule(cr, CAIRO_FILL_RULE_WINDING);
cairo_fill_preserve(cr);
}
/********************/
if(val&0x8000){
cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
pattern = cairo_pattern_create_rgba(.3,.6,1,1);
cairo_set_source(cr, pattern);
cairo_pattern_destroy(pattern);
cairo_new_path(cr);
cairo_move_to(cr, 10.835938, 3.734375);
cairo_line_to(cr, 9.613281, 4.984375);
cairo_line_to(cr, 11.871094, 8.484375);
cairo_line_to(cr, 15.34375, 8.484375);
cairo_line_to(cr, 17.421875, 6.359375);
cairo_line_to(cr, 17.859375, 3.734375);
cairo_close_path(cr);
cairo_set_tolerance(cr, 0.1);
cairo_set_antialias(cr, CAIRO_ANTIALIAS_DEFAULT);
cairo_set_fill_rule(cr, CAIRO_FILL_RULE_WINDING);
cairo_fill_preserve(cr);
}
/********************/
...</code></pre>
<br />
Now we have a draw function that we can use with the ffffltk_display widget. But it's not blurred. We want the light segments to be blurred and the dark segments and the frame/background to remain solid. The function provided by the cairo cookbook blurs the whole surface that we're drawing to, so we need to create a new, separate surface, draw the things to be blurred on it, blur the new surface, then embed the temporary surface onto the target surface which is really our window. It looks a little like:
<br />
<pre><code>...
/********************/
cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
pattern = cairo_pattern_create_rgba(0,0,.3333,1);
cairo_set_source(cr, pattern);
cairo_pattern_destroy(pattern);
cairo_new_path(cr);
cairo_move_to(cr, 7.144531, 36.796875);
cairo_line_to(cr, 3.71875, 40.296875);
cairo_line_to(cr, 4.527344, 41.546875);
cairo_line_to(cr, 11.542969, 41.578125);
cairo_line_to(cr, 11.976562, 38.984375);
cairo_line_to(cr, 10.566406, 36.796875);
cairo_close_path(cr);
cairo_set_tolerance(cr, 0.1);
cairo_set_antialias(cr, CAIRO_ANTIALIAS_DEFAULT);
cairo_set_fill_rule(cr, CAIRO_FILL_RULE_WINDING);
cairo_fill_preserve(cr);
/********************/
//blur
temp_surface = </code></pre>
<pre><code> cairo_image_surface_create( CAIRO_FORMAT_ARGB32,</code></pre>
<pre><code> 30,45);
old_cr = cr;
cr = cairo_create(temp_surface);
/********dot************/
if(num < 0){
cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
pattern = cairo_pattern_create_rgba(.3,.6,1,1);
cairo_set_source(cr, pattern);
cairo_pattern_destroy(pattern);
cairo_new_path(cr);
cairo_move_to(cr, 27.773438, 39.507812);
cairo_curve_to(cr, 27.773438, 40.652344, 26.847656, 41.578125, 25.707031, 41.578125);
cairo_curve_to(cr, 24.5625, 41.578125, 23.636719, 40.652344, 23.636719, 39.507812);
cairo_curve_to(cr, 23.636719, 38.367188, 24.5625, 37.441406, 25.707031, 37.441406);
cairo_curve_to(cr, 26.847656, 37.441406, 27.773438, 38.367188, 27.773438, 39.507812);
cairo_close_path(cr);
cairo_set_tolerance(cr, 0.1);
cairo_set_antialias(cr, CAIRO_ANTIALIAS_DEFAULT);
cairo_set_fill_rule(cr, CAIRO_FILL_RULE_WINDING);
cairo_fill_preserve(cr);
}
/********************/
...</code></pre>
<pre> </pre>
So you see the last dark segment being drawn, the new surface is created (in temp surface), an associated cairo context is made and then all the light segments are drawn into it. Then as we get to the last light segment:<br />
<pre><code></code></pre>
<pre><code>...</code></pre>
<pre><code> /********************/
if(val&0x0002){
cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
pattern = cairo_pattern_create_rgba(.3,.6,1,1);
cairo_set_source(cr, pattern);
cairo_pattern_destroy(pattern);
cairo_new_path(cr);
cairo_move_to(cr, 7.144531, 36.796875);
cairo_line_to(cr, 3.71875, 40.296875);
cairo_line_to(cr, 4.527344, 41.546875);
cairo_line_to(cr, 11.542969, 41.578125);
cairo_line_to(cr, 11.976562, 38.984375);
cairo_line_to(cr, 10.566406, 36.796875);
cairo_close_path(cr);
cairo_set_tolerance(cr, 0.1);
cairo_set_antialias(cr, CAIRO_ANTIALIAS_DEFAULT);
cairo_set_fill_rule(cr, CAIRO_FILL_RULE_WINDING);
cairo_fill_preserve(cr);
}
/********************/
blur_image_surface(temp_surface,3);
cairo_set_source_surface(old_cr,temp_surface,0,0);
cairo_paint(old_cr);
cairo_surface_destroy(temp_surface);
cairo_destroy(cr);
}</code>
</pre>
<br />
We blurred the new surface (with blur radius 3 pixels) and the "embeded" the surfaces together using cairo_set_source_surface, then painted the original source surface. After that its just a bit of cleanup. The <code>blur_image_surface()</code>function is where the magic happens and its provided in blur.c in the cairo cookbook. All I did was rename the file to .h and change the functions to be inline. I guess its not really an official cairo function since it doesn't start with cairo. A native implementation is on their todo list, but for now, here's a workaround.<br />
<br />
That's all. Pretty simple really. Hopefully that helps someone out there. I can't make any guarantees about performance or anything, it seems like this is doing it fairly manually, so there might be a cheaper way to do it. All of ffffltk could probably use some optimization techniques. If you have tips or tutorials on how to make light cairo programs please comment!Spencerhttp://www.blogger.com/profile/12246776537628500183noreply@blogger.com0tag:blogger.com,1999:blog-2309349868071255294.post-89440655980190990122014-08-19T12:54:00.000-07:002015-09-17T09:46:09.841-07:00Making an LV2 plugin GUI (yes, in Inkscape)Told you I'd be back.<br />
<br />
So I made this pretty UI <a href="http://mountainbikesandtrombones.blogspot.com/2014/08/drawing-gui-in-inkscape.html">last post</a> but I never really told you how to actually use it (I'm assuming you've read the previous 2 posts, this is the 3rd in the series). Since I'm "only a plugin developer," that's what I'm going to apply it to. Now I've been making audio plugins for longer than I can hold my breath, but I've never bothered to make one with a GUI. GUI coding seems so boring compared to DSP and it's so subjective (user: "that GUI is so unintuitive/natural/cluttered/inefficient/pretty/ugly/slow etc. etc....") and I actually like the idea of using your ears rather than a silly visual curve, but I can't deny, a pretty GUI does increase usership. Look at the <a href="http://calf.sourceforge.net/">Calf plugins</a>...<br />
<br />
Anyhow, regardless of whether its right or wrong I'm going to make GUIs (that are completely optional, you can always use the host generated UI). I think with the <a href="http://infamousplugins.sourceforge.net/plugs.html#casynth">infamous cellular automaton synth</a> I will actually be able to make it easier to use, so the GUI is justifiable, but other than that they're all eye candy, so why not make 'em sweet? So I'll draw them first, then worry about making them an actual UI. I've been trying to do this drawing-first strategy for years but once I started toying with svg2cairo I thought I might actually be able to do it this time. Actually as I'm writing this paragraph the ball is still up in the air, so it might not pan out, but I'm pretty confident by the time you read the last paragraph in this almost-tutorial I'll have a plugin with a GUI.<br />
<br />
(*EDIT 14 Sept 2015 - a big mistake was pointed out to me in my LV2_UI instantiation, updated below). <br />
<br />
So lets rip into it:<br />
<a name='more'></a><br />
<br />
One challenge I have is that I really don't like coding C++ much. I'm pretty much a C purist. So why didn't I use gtk? Well, cause it didn't have AVTK. Or ntk-fluid. With that fill-in-the-blank development style fluid lends to, I barely even notice that its C++ going on in back. Its a pretty quick process too. I had learned a fair bit of QT, but was forgoing that anyway, but with these new (to me) tools I had a head start and got to where I am relatively quickly (considering my qsvg widgets are now 3 years old and unfinished).<br />
<br />
The other good news is that the DSP and UI are separate binaries and can have completely separate codebases, so I can still do the DSP in my preferred language. This forced separation is very good practice for realtime signal processing. DSP should be the top priority and should never ever ever have to wait for the GUI for anything.<br />
<br />
But anyway, to make an LV2 plugin gui we'll need to add some extra .ttl stuff. So in manifest.ttl:<br />
<pre>@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .</pre>
<pre>@prefix ui: <http://lv2plug.in/ns/extensions/ui#> .</pre>
<pre> </pre>
<pre> <http://infamousplugins.sourceforge.net/plugs.html#stuck>
a lv2:Plugin, lv2:DelayPlugin ;
lv2:binary <stuck.so> ;
rdfs:seeAlso <stuck.ttl> .</pre>
<pre> </pre>
<pre><http://infamousplugins.sourceforge.net/plugs.html#stuck_ui></pre>
<pre> a ui:X11UI;</pre>
<pre> ui:binary <stuckui.so></pre>
<pre> lv2:extensionData ui:idle; . </pre>
<br />
Thats not a big departure from the no-UI version, but we'd better make a stuckui.so to back it up. We've got a .cxx and .h from ntk-fluid that we made in the previous 2 posts, but its not going to be enough. The callbacks need to do something. But what? Well, they will be passing values into the control ports of the plugin DSP somehow. OpenAVproductions genius Harry Haaren wrote a little <a href="http://harryhaaren.blogspot.com/2012/07/writing-lv2-guis-making-it-look-snazzy.html">tutorial</a> on it. The thing is called a write function. Each port has an index assigned by the .ttl and the dsp source usually has an enum to keep these numbers labeled. So include (or copy) this enum in the UI code, declare an <code>LV2UI_Write_Function </code>and also an <code>LV2UI_Controller</code> that will get passed in as an argument to the function. Both of these will get initialized with arguments that get passed in from the host when the UI instantiate function is called. The idea is the LV2_Write_Function is a function pointer that will call something from the host that stuffs data into the port. You don't need to worry about how that function works, just feel comfort knowing that where ever that points, it'll take care of you. In a thread safe way even.<br />
<br />
Another detail (that I forgot when I first posted this yesterday) is declaring that this plugin will use the UI you define in the manifest.ttl. What that means is in the stuck.ttl you add the ui extension and declare the STUCKURI as the UI for this plugin:<br />
<pre>@prefix doap: <http://usefulinc.com/ns/doap#> .
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
@prefix ui: <http://lv2plug.in/ns/extensions/ui#> .
<http://infamousplugins.sourceforge.net/plugs.html#stuck>
a lv2:Plugin, lv2:DelayPlugin ;
doap:name "the infamous stuck" ;
doap:maintainer [
foaf:name "Spencer Jackson" ;
foaf:homepage <http://infamousplugins.sourceforge.net> ;
foaf:mbox <ssjackson71@gmail.com> ;
] ;
lv2:requiredFeature <http://lv2plug.in/ns/ext/urid#map> ;
lv2:optionalFeature lv2:hardRTCapable ;
ui:ui <http://infamousplugins.sourceforge.net/plugs.html#stuck_ui> ;
lv2:port [</pre>
<pre>... </pre>
<span class="n"><br />
<span class="n">So enough talk. Lets code.</span><br />
<span class="n">For LV2 stuff we need an additional header. So in an extra code box (I used the window's):</span><br />
<code>#include "lv2/lv2plug.in/ns/extensions/ui/ui.h"</code></span><br />
<span class="n"><br /></span>
It will be convenient to share a single source file for which port is
which index. That eliminates room for error if anything changes. So in
an additional code box (the Aspect Group's since the window's are all
full):<br />
<code>#include"stuck.h" </code><br />
<span class="n"> </span><br />
<span class="n">We also will need 2 additional members in our StuckUI class. Do this by adding 2 "declarations" in fltk. The code is:<br /><code>LV2UI_Write_Function write_function;</code></span><br />
<span class="n">and</span><br />
<span class="n"><code>LV2UI_Controller controller; </code></span><br />
<br />
And finally in each callback add something along the lines of (i.e. for the Stick It! port):<br />
<code>write_function(controller,STICKIT,sizeof(float),0,&stickit->floatvalue);</code><br />
<br />
This is calling the write function with the controller object, port number, "buffer" size (usually the size of float), protocol (usually 0, for float), and pointer to a "buffer" as arguments. So now when the button is clicked it will pass the new value on to the DSP in a threadsafe way. The official documentation of write functions is <a href="http://lv2plug.in/doc/html/ui_8h.html#ab67815cddcc966eef5d55b570b4f303f">here</a>. The floatvalue member of dials and buttons is part of ffffltk (which was introduced in the other parts of this series) which was added exclusively for LV2 plugins. Cause they always work in floats. Or in atoms, which is a whole other ball of wax. Really though, its really easy to do this as long as you keep it to simple float data like a drone gain.<br />
<br />
Another important thing you must add to the fluid design is a function called <code>void idle()</code>. In this function add a code block that has these 2 lines:<br />
<code>
Fl::check();<br />
Fl::flush(); <br />
</code>
<br />
<br />
To help clarify everything here's a screenshot of ntk-fluid once I've done all this. Its actually a pretty good overview what we've done so far:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjhGm4H5ZQVY8pPGtPz8jQnsVV2UBL7HtKsDTsZmcC-DFK1GBtxThPvhU7GC9blADdMYcBtDWGDk8i_5GIM8McoUrCZbpwrKa4aVflyJJHH15XjhXvKPxran8glZzXFMfzKo-WFk6K1Rie9/s1600/fluid.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="360" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjhGm4H5ZQVY8pPGtPz8jQnsVV2UBL7HtKsDTsZmcC-DFK1GBtxThPvhU7GC9blADdMYcBtDWGDk8i_5GIM8McoUrCZbpwrKa4aVflyJJHH15XjhXvKPxran8glZzXFMfzKo-WFk6K1Rie9/s1600/fluid.png" width="640" /></a></div>
<br />
<br />
Possibly the biggest departure from what we've done previously is now the program will not be a stand-alone binary, but a library that has functions to get called by the host (just like in the DSP). This means some major changes in our stuck_ui_main.cxx code.<br />
<br />
For the GUI the most important functions are the instantiation, cleanup, and port event. To use NTK/fltk/ffffltk you will need to use some lv2 extensions requiring another function called extension_data but we'll discuss it later. The instantiation is obviously where you create your window or widget and pass it back to the host, cleanup deallocates it, and the port event lets you update the GUI if the host changes a port (typically with automation). We'll present them here in reverse order since the instantiation with NTK ends up being the most complex. So port event is fairly straightforward:<br />
<pre></pre>
<code>
void stuckUI_port_event(LV2UI_Handle ui, uint32_t port_index, uint32_t buffer_size, uint32_t format, const void * buffer)<br />{<br /> StuckUI *self = (StuckUI*)ui;<br /> if(!format)<br /> {<br /> float val = *(float*)buffer;<br /> switch(port_index)<br /> {<br /> case STICKIT:<br /> self->stickit->value((int)val);<br /> self->led->value((int)val);<br /> break;<br /> case DRONEGAIN:<br /> self->volume->value(val);<br /> break;<br /> case RELEASE:<br /> self->time->value(val);<br /> break;<br /> }<br /> }<br />}<br />
</code>
<br />
<br />
The enlightening thing about doing a UI is that you get to see both sides of what the LV2 functions do. So just like in the widget callbacks you send a value through the write_function, this is like what the write function does on the other side, first you recast the handle as your UI object so you can access what you need, then make sure its passing the format you expect (0 for float, remember?). Then assign the data corresponding to the index to whatever the value is. This keeps your UI in sync if the host changes a value. Nice and easy.<br />
<br />
Next up is the simplest: Cleanup:<br />
<code>
void cleanup_stuckUI(LV2UI_Handle ui)<br />{<br /> StuckUI *self = (StuckUI*)ui;<br /><br /> delete self;<br />}<br />
</code><br />
<pre></pre>
<code>
</code>
<br />
<br />
No explanation necessary. So that leaves us with instantiation. This one is complex enough I'll give it to you piece by piece. So first off is the setup, checking that we have the right plugin (this is useful when you have a whole bundle of plugins sharing code), then dynamically allocating a UI object that will get returned as the plugin handle that all the other functions use, and declaring a few variables we'll need temporarily:<br />
<code>
static LV2UI_Handle init_stuckUI(const struct _LV2UI_Descriptor * descriptor,<br /> const char * plugin_uri,<br /> const char * bundle_path,<br /> LV2UI_Write_Function write_function,<br /> LV2UI_Controller controller,<br /> LV2UI_Widget * widget,<br /> const LV2_Feature * const * features) <br />{<br /> if(strcmp(plugin_uri, STUCK_URI) != 0)<br /> {<br /> return 0;<br /> }<br /><br /> StuckUI* self = new StuckUI();<br /> if(!self) return 0;<br /> LV2UI_Resize* resize = NULL;<br />
</code>
<br />
<br />
Then we save the write_function and controller that got passed in from the host so that our widgets can use them in thier callbacks:<br />
<code>
self->controller = controller;<br /> self->write_function = write_function;<br />
</code>
<br />
<br />
Next stop: checking features the host has. This is where using NTK makes it a bit more complicated. The host should pass in a handle for a parent window and we will be "embedding" our window into the parent. Another feature we will be hoping the host has is a resize feature that lets us tell the host what size the window for our plugin should be. So we cycle through the features and when one of them matches what we're looking for we temporarily store the data associated with that feature as necessary:<br />
<code>
void* parentXwindow = 0;<br /> for (int i = 0; features[i]; ++i)<br /> {<br /> if (!strcmp(features[i]->URI, LV2_UI__parent)) <br /> {<br /> parentXwindow = features[i]->data;<br /> }<br /> else if (!strcmp(features[i]->URI, LV2_UI__resize)) <br /> {<br /> resize = (LV2UI_Resize*)features[i]->data;<br /> }<br /> }<br />
</code>
<br />
<br />
Now we go ahead and startup our UI window, call the resize function with our UI's width and height as arguments and call a special NTK function called fl_embed() to set our window into the parent window. It seems this function was created specially for NTK. I haven't found it in the fltk source or documentation so I really don't know much about it or how you'd do it using fltk instead of NTK. But it works. (You can see the <a href="https://github.com/original-male/ntk/blob/master/src/Fl_x.cxx#L919">NTK source</a> and just copy that function). EDIT: one important detail that I missed is that you are supposed to fill in the LV2UI_Widget that the host passes with your UI widget. When your UI is x11 based you pass in the xid from the x window or at least set it to zero. This is done below after fl_embed(). Once that's done we return our instance of the plugin UI object:<br />
<code>
self->ui = self->show();<br /> fl_open_display();<br /> // set host to change size of the window<br /> if (resize)<br /> {<br /> resize->ui_resize(resize->handle, self->ui->w(), self->ui->h());<br /> }<br /> fl_embed( self->ui,(Window)parentXwindow);</code><br />
<code> </code><code> *widget = (LV2UI_Widget)fl_xid(self->ui);</code><br />
<code><br /><br /> return (LV2UI_Handle)self;<br />}<br />
</code>
<br />
<br />
Ok. Any survivors? No? Well I'll just keep talking to myself then. We mentioned the extension_data function. This function gets called and can do various special functions if the host supports them. Similar to the port event, the same extension_data function gets called with different indexed functions and we can return a pointer to a function that does what we want when an extension we care about gets called. Once again we get to see both sides of a function we called. The resize stuff we did in instantiate can be used as a host feature like we did before or as extension data. As extension data you can resize your UI object according to whatever size the host requests. This extension isn't necessary for an NTK GUI but since the parent window we embedded our UI into is a basic X window, its not going to know to call our fltk resize functions when its resized.<br />
<br />
In contrast, a crucial extension for an NTK GUI is the idle function. Because similarly the X window doesn't know anything about fltk and will never ask it to redraw when something changes. So this LV2 extension exists for the host to call a function that will check if something needs to get updated and redrawn on the screen. We made an idle function already to call in our StuckUI object through fluid, but we need to set up the stuff to call it. Our extension_data function will need some local functions to call:<br />
<code>
static int<br />idle(LV2UI_Handle handle)<br />{<br /> StuckUI* self = (StuckUI*)handle;<br /> self->idle();<br /> <br /> return 0;<br />}<br /><br />static int<br />resize_func(LV2UI_Feature_Handle handle, int w, int h)<br />{<br /> StuckUI* self = (StuckUI*)handle;<br /> self->ui->size(w,h);<br /> <br /> return 0;<br />}</code><br />
<code><br />
</code>
<br />
Hopefully its obvious what they are doing. The LV2 spec has some stucts that are designed to interface between these functions and the extension_data function, so we declare those structs as static constants, outside of any function, with pointers to the local functions :<br />
<code>
static const LV2UI_Idle_Interface idle_iface = { idle };<br />static const LV2UI_Resize resize_ui = { 0, resize_func };<br />
</code>
<br />
<br />
And now we are finally ready to see the extension_data function:<br />
<code>
static const void*<br />extension_data(const char* uri)<br />{<br /> if (!strcmp(uri, LV2_UI__idleInterface))<br /> {<br /> return &idle_iface;<br /> }<br /> if (!strcmp(uri, LV2_UI__resize))<br /> {<br /> return &resize_ui;<br /> }<br /> return NULL;<br />}<br />
</code>
<br />
You see we just check the URI to know if the host is calling the extension_data function for an extension that we care about. Then if it is we pass back the struct corresponding to that extension. The host will know how these structs are formed and use them to call the functions to redraw or resize our GUI when it thinks its necessary. We aren't really guaranteed timing for these but most hosts are gracious enough to call it at a frequency that gives pretty smooth operation. Thanks hosts!<br />
<br />
<br />
So, its now time for the ugly truth to rear its head. Full disclosure: this implementation of the resizing extension code doesn't work at all. The <a href="http://lv2plug.in/doc/html/structLV2UI__Resize.html">official documentation</a> describes this feature as being 2 way, host to plugin or plugin to host. We've already used it as plugin to host and that works perfectly, but when trying to go the other way I can't get it to work. The trouble is when we declare and initialize the LV2UI_Resize object. The first member of the struct is type LV2UI_Feature_Handle which is really just a void* which should really just be a pointer to whatever data the plugin will want to use when the function in the 2nd member of the struct gets called. Well for us when resize_func gets called we want our instance of the StuckUI that we created in init_stuckUI(). That would allow us to call the resize function. But we can't because its out of scope, and the struct must be a constant so it can't be assigned in the instantiate function. So I just have a 0 as that first argument and actually have the call to size() commented out. <br />
<br />
Perhaps there's a way to do it, but I can't figure it out. I included that information because I hope to figure out how and someday make my UI completely resizable. The best way to find out, I figure, is to post fallacious information on the Internet and pretty soon those commenters will come tell me how wrong and stupid I am. Then I can fix it.<br />
<br />
<br />
As a workaround you can put in your manifest.ttl this line:<br />
<code>
lv2:optionalFeature ui:noUserResize ;<br />
</code>
<br />
Which will
at least make it not stupidly sit there the same size all the time even
when the window is resized. If the host supports it.<br />
<br />
EDIT: I understand that returning the correct LV2UI_Widget
from instantiate should allow the plugin to resize without using the
resize extension. It also allows for keyboard entry or modifiers. Then the workaround is unnecessary.<br />
<br />
"So if its not even resizable why in the world did you drag us through 3 long detailed posts on how to make LV2 GUIs out of SCALABLE vector graphics?!" you ask. Well, you can still make perfectly scalable guis for standalone programs, and just having a WYSIWYG method of customized UI design is hopefully worth something to you. It is to me, though I really hope to make it resizable soon. It will be nice to be able to enlarge a UI and see all the pretty details, then as you get familiar with it shrink it down so you can just use the controls without needing to read the text. Its all about screen real estate. And<a href="http://i3wm.org/"> tiling window managers</a> for me.<br />
<br />
<br />
So importantlyin LV2 we need to have a standard function that passes to the host all these functions so the host can call them as necessary. Similar to the DSP side you declare a descriptor which is really a standard struct that has the URI and function pointers to everything:<br />
<code>
static const LV2UI_Descriptor stuckUI_descriptor = {<br /> STUCKUI_URI,<br /> init_stuckUI,<br /> cleanup_stuckUI,<br /> stuckUI_port_event,<br /> extension_data<br />};<br />
</code>
<br />
<br />
And lastly the function that passes it back. Its form seems silly for a single plugin, but once again you can have a plugin bundle (or a bundle of UIs) sharing source that passes the correct descriptor for whichever plugin is requested (by index). It looks like this:<br />
<code>
LV2_SYMBOL_EXPORT <br />const LV2UI_Descriptor* lv2ui_descriptor(uint32_t index) <br />{<br /> switch (index) {<br /> case 0:<br /> return &stuckUI_descriptor;<br /> default:<br /> return NULL;<br /> }<br />}<br />
</code>
<br />
<br />
As a quick recap, here are the steps to go from Inkscape to <a href="http://kxstudio.sourceforge.net/Applications:Carla">Carla </a>(or your favorite LV2 plugin host):<br />
1. Draw a Gui in Inkscape<br />
2. Save the widgets as separate svg files<br />
3. Convert to cairo code header files<br />
4. Edit the draw functions to animate dials, buttons, etc. as necessary.<br />
5. Create the GUI in ntk-fluid with the widgets placed according to your inkscape drawing<br />
6. Include the ffffltk.h and use ffffltk:: widgets<br />
7. Assign them their respective draw_functions() and callbacks<br />
8. Add the write_function, controller members, and the idle() function<br />
9. Export the source files from fluid and write a ui_main.cxx<br />
10. Update your ttl<br />
11. Compile, install, and load in your favorite host.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi88VVg_qPImFFF1hHKkVPRnC91bkANsSld2Q3jq6vYRZmWFs-OxRhMyAUWvFnrB7SrJKwFTRueM3OtBdcI76IALLm-x_8EfM5-iLnBkYhSemDUZPqfifGhFUzNzOr-nMGA8IxeyoNGZeib/s1600/stuck.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi88VVg_qPImFFF1hHKkVPRnC91bkANsSld2Q3jq6vYRZmWFs-OxRhMyAUWvFnrB7SrJKwFTRueM3OtBdcI76IALLm-x_8EfM5-iLnBkYhSemDUZPqfifGhFUzNzOr-nMGA8IxeyoNGZeib/s1600/stuck.png" width="199" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Our plugin in Jalv.gtk</td></tr>
</tbody></table>
<br />
<br />
So you now have the know-how to create your own LV2 plugin GUIs using Inkscape, svg2cairo, ffffltk, ntk-fluid, and your favorite editor. In 11 "easy" steps. You can see the source for the infamous Stuck that I developed this workflow through in my <a href="http://sourceforge.net/p/infamousplugins/code/ci/gui/tree/src/Stuck/">infamous repository</a>. And soon all the plugins will be ffffltk examples. I'll probably refine the process and maybe I'll post about it. Feel free to ask questions. I'll answer to the best of my ability. Enjoy and good luck. <br />
<br />
As an aside, in order to do this project. I ended up switching build systems. Qmake worked well, but I mostly just copied the script from Rui's <a href="http://synthv1.sourceforge.net/synthv1-index.html">synthv1</a> source and edited it for each plugin. Once I started needing to customize it more to generate separate dsp and ui binaries I had a hard time. I mostly arbitrarily decided to go with cmake. The fact that <a href="https://github.com/nicklan/drmr">drmr</a> had a great cmake file to start from was a big plus. And the example waf file I saw freaked me out so I didn't use waf. I guess I don't know python as much as I thought. Cmake seemed more like a functional programming language, even if it is a new syntax. I was surprised that in more or less a day I was able to get cmake doing exactly what I wanted. I had to fight with it to get it to install where I wanted (read: obstinate learner), but now its ready for whatever plugins I can throw at it. So that's what I'm going to use going forward. I'll probably leave the .pro files for qmake so if you want to build without a GUI you can. But maybe I won't. Complain loudly in the comments if you have an opinion.Spencerhttp://www.blogger.com/profile/12246776537628500183noreply@blogger.com0tag:blogger.com,1999:blog-2309349868071255294.post-44643333254401454552014-08-11T14:50:00.000-07:002014-08-11T16:11:08.814-07:00Drawing a GUI in InkscapeSo I got all excited and posted <a href="http://mountainbikesandtrombones.blogspot.com/2014/07/creating-gui-in-inkscape.html">that last article</a> about a lot of the technical stuff to make a GUI in Inkscape, but after posting it I realized it makes quite a few assumptions, so I'm going to add a few follow on posts, some of which (mostly this one) should have been written first. So its a prequel. Or a Part 0. Something like that.<br />
<br />
Anyway the biggest assumption I made is that you are proficient in inkscape. Perhaps you are, but I wasn't until I started trying to draw GUIs in it. So basically all my skills are honed to this purpose. I'm not qualified to give a real inkscape tutorial so you should probably stop reading right now, but just in case I can help someone out there make a cool GUI I'll press onward.<br />
<br />
<br />
<a name='more'></a><br />
I think I originally found inkscape when trying to draw a cover for my bands forthcoming album (which has been forthcoming for several years). I started with GIMP, but I couldn't really draw very well with it. I also really liked the idea of scalable drawings rather than pixmaps, so I stumbled upon inkscape. Not long after I read the rakarrack team was looking for new GUIs so I volunteered that I didn't know what I was doing, but I would try anyway. I drew some 30 svg <a href="http://mountainbikesandtrombones.blogspot.com/2011/09/rakarrack-gui.html">drawings</a> in inkscape of "stompboxes" for the various rakarrack effects and wrote the qsvgwidget library thing that I mentioned before. That faded as the programming got complex, but I have since used inkscape for my <a href="http://digitalcommons.usu.edu/etd/1746/">masters thesis diagrams</a>, work documents, and even for sketching up layouts for remodeling my kitchen.<br />
<br />
I'm about to give a bunch of information that may not even be helpful, but its what I use all the time when drawing stuff, but first (before you get scared) lets have an overview. You can make it pretty simple and have some good looking results. You are drawing lines and shapes. Shapes are just lines that connect together to form a boundary (so really you are just drawing lines). These lines and shapes become objects you can copy and paste, move around, shape, layer and do all sorts of things to make them look how you want, but in the end you are just defining lines and shapes. <br />
<br />
I'd read an watched some tutorials that helped a lot and you'll do better with those, but here's a crash course (more like list) of basic Inkscape "moves" that help a lot in drawing GUI elements.<br />
Need to know skills:<br />
<br />
Ctl+D - duplicate, creates a copy exactly over the original. Use it then the arrow keys to make identical copies that are aligned, i.e. a grid of dials<br />
<br />
(arrow key) - move object <br />
Alt+(arrow key) - move object small step<br />
Shift+(arrow key) - move object large step<br />
<br />
Ctl+g - turn objects into a group. Nothing is worse than making an intricate object then accidentally missing one piece when selecting it to move to its place. Ctl+z fixes it by undoing, starting you over, but its much nicer just to have one object (group really) that you just click and drag. You can have groups of groups, groups of mixed groups and objects etc.<br />
Ctl+Shift+g - separate the topmost group back into individual objects/groups, sometimes you need to go change a detail, so you ungroup, change, regroup<br />
<br />
Ctl+(rotate, resize, etc) - holding control usually makes things work in nice increments, i.e. when drawing a rectangle, holding ctl forces it to a square, rotations while holding ctl go in 30 degree increments (or something like that).<br />
<br />
Ctl+Shift+C - converts the object/shape/text to a path so you can manipulate its little details. For text especially this matters because if the target doesn't have the correct font installed it won't appear correctly, but once its a path, it just draws that shape. Make sure your text is exactly how you want it first though.<br />
<br />
Rectangle drawing tool, circle drawing tool, gradient tool, and node editing tool; align and distribute menu, fill and stroke menu; etc. etc. etc.<br />
<br />
Ok there's probably too much to mention. But these are the ones I use the most. You can see there are a lot of things you can do with inkscape and this doesn't scratch the surface.<br />
<br />
The node editing tool is especially powerful. In this almost-tutorial I'm going to make a GUI and I went through the pain of visually documenting each step for one of the shapes:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhDKedOQ0tKqOsnfHBobuRZtjPX3FMOf5BWMVc7EmMQFSzQdahcKrJcxkRdrpWtNkIMtgZPFkU4YvVHo6YF3VLIUmJVJjGvUHol9ic-2rysykE-QY8gEeWOogiiKUr_YsajEWJuu4Mk6d3_/s1600/drawing+steps1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhDKedOQ0tKqOsnfHBobuRZtjPX3FMOf5BWMVc7EmMQFSzQdahcKrJcxkRdrpWtNkIMtgZPFkU4YvVHo6YF3VLIUmJVJjGvUHol9ic-2rysykE-QY8gEeWOogiiKUr_YsajEWJuu4Mk6d3_/s1600/drawing+steps1.png" height="344" width="640" /></a></div>
<br />
You see first it was a rectangle, then I converted it to a path, and used the node editing tool. I added some nodes, deleted the ones I didn't want, then changed the node curvature properties so the middle 2 nodes on the sides are corners, and the top one is a symmetrical curve. In the bottom row I drew a line, centered it, duplicated the lines and moved them to one side, duplicated the 3 lines, mirrored it horizontally and moved them to the other side. For the screenshot I selected everything with the node adjust tool so you can hopefully see what I was doing. Also take note how nicely aligned the steps are are due to the duplicate/move-with-arrow combo.<br />
<br />
The node tool is pretty advanced. You don't have to go that far. Here's a simple dial:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj4GaLbKXk1a8Y6gqo3uMc2ocAJaJISKmB0yCWdZ5k-iIVEtxildXqMtVSfJwTrnIQxkaEyTNejkOGRvbu3LwjCBrJbgPKTLsQAi-fk4r_Gt3wzmqQxCZ6zY2sv9kLeYz8tlWixkZZ5HitO/s1600/drawing+steps2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj4GaLbKXk1a8Y6gqo3uMc2ocAJaJISKmB0yCWdZ5k-iIVEtxildXqMtVSfJwTrnIQxkaEyTNejkOGRvbu3LwjCBrJbgPKTLsQAi-fk4r_Gt3wzmqQxCZ6zY2sv9kLeYz8tlWixkZZ5HitO/s1600/drawing+steps2.png" height="344" width="640" /></a></div>
<br />
So make a circle, add a radial gradient, edit the gradient stops (colors), then add (and align) a rectangle for a pointer. Easy as PI. You can even skip the gradient.<br />
<br />
The point is its a little easier (because its WYSIWYG) than trying to edit style files or blindly using a drawing API like cairo. That is why I started ffffltk (and qsvgwidgets before that).<br />
<br />
So I'm not going to describe how to draw every single widget, you can go learn more about drawing on your own (or just ask for specifics in the comments below). But here are some tips on how to make your life easier when you convert them to Cairo code.<br />
<br />
Symmetry is nice, but some things are asymmetrical. For dials you will need to know what point you want to rotate around in the drawing so you can do it in cairo, typically its the middle so just keep the width and height the same and divide by 2. Also important is that the original drawing or "canvas" size will fit everywhere the drawing could be. For example this switch:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiD_UrZ8WcKV6ox67TIvakVEfgMN4AG1Htoas-ae6HtChwkaMZNe1ywk4Hlf8EVemytJ8_ZGcCC7jczlUtepRCovlhS1fEIyaoFjs4586oipBXkB0OnNG7Rfm5dG9_AHW1K4XIsA6hItb2P/s1600/switchbad.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiD_UrZ8WcKV6ox67TIvakVEfgMN4AG1Htoas-ae6HtChwkaMZNe1ywk4Hlf8EVemytJ8_ZGcCC7jczlUtepRCovlhS1fEIyaoFjs4586oipBXkB0OnNG7Rfm5dG9_AHW1K4XIsA6hItb2P/s1600/switchbad.png" /></a></div>
<br />
Once toggled the other way it's going to go off the other side of the canvas. Like this:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgv5TZOcypSof5DF7EloOcUjgtRHwvhXCz9wbp2Tjams2fxdPP-LPspNLxeDVfwkaXA6-HoY-surU5Tp8iTJeB1RUUSyZ2Z8nTZXvNO7T3loHhFf3kI4U4O6e_cfUtMBK9W4PlgS7AUUBBs/s1600/switchcut.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgv5TZOcypSof5DF7EloOcUjgtRHwvhXCz9wbp2Tjams2fxdPP-LPspNLxeDVfwkaXA6-HoY-surU5Tp8iTJeB1RUUSyZ2Z8nTZXvNO7T3loHhFf3kI4U4O6e_cfUtMBK9W4PlgS7AUUBBs/s1600/switchcut.png" /></a></div>
<br />
So instead, change the size of the canvas so that it doesn't. To get the right size of canvas: duplicate the whole switch (Ctl+D), flip the copy vertically (V), and carefully move it (using only up and down arrow keys) so that the circle parts are aligned. I find it helpful to temporarily change the opacity of the top one so you can see the one beneath. Zoom way in and use the Alt+Down key to make those fine detailed moves till it fits. Then press Ctl+Shift+D to change the canvas size. Click the resize page to drawing or selection button in the custom size box and it will resize it perfectly:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgROloFZ4Q1VcdEz_dqVacEOVaBNofT-i0bfK0fTia1LVDxXogiq1mXkPWHREHRJ-67LEWB-Nh4G-L2CkHeTAafGlqWox0i-sp-PVdnhLrNPpBA1jTEg4BV7ljydAooQPXc5k98uubctg9F/s1600/switchboth.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgROloFZ4Q1VcdEz_dqVacEOVaBNofT-i0bfK0fTia1LVDxXogiq1mXkPWHREHRJ-67LEWB-Nh4G-L2CkHeTAafGlqWox0i-sp-PVdnhLrNPpBA1jTEg4BV7ljydAooQPXc5k98uubctg9F/s1600/switchboth.png" /></a></div>
<br />
<br />
Now if you delete one copy you get the same thing, but it won't get cut off:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjifqsCQotGefAEn_-ZJp_SlHbFs2FMdf2_aXtFcw2R2VwZ6u2v5C-QrFVrlco8HAUyfvVmZrunjRBeFF7GscbCd4xy3Y9ZcAEc1aEQA8VH-jvYtY4BrnnQFVfpfwTy4kimoNk9eEsc94Nm/s1600/switchgood.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjifqsCQotGefAEn_-ZJp_SlHbFs2FMdf2_aXtFcw2R2VwZ6u2v5C-QrFVrlco8HAUyfvVmZrunjRBeFF7GscbCd4xy3Y9ZcAEc1aEQA8VH-jvYtY4BrnnQFVfpfwTy4kimoNk9eEsc94Nm/s1600/switchgood.png" /></a></div>
<br />
When using the ffffltk button widget to display this, you will convert to cairo and then you need to "animate" the drawing. With this one you could probably get away with doing a cairo_rotate(cr,PI) before drawing the top object (the switch), but I chose to leave both copies of the switch in the drawing and use an if statement to toggle which one will actually be drawn. Having the drawing like this also helps when prototyping the layout of the window in inkscape, so you can see if you are going to cover up some text or something once the switch is thrown (because the drawing shows both positions). If this paragraph is completely confusing, you probably should review my last blog post. Or I'm just a terrible writer.<br />
<br />
The same sort of thing goes with dials. In the example "vintage" dial I used in the last post the canvas was made by duplicating and rotating the needle 90 degrees and resizing the canvas so the point doesn't get cut off when you turn it sideways. Hopefully that covers this point.<br />
<br />
Another widget I'm using quite a bit in these skeuomorphic designs is an l.e.d.. Because you can't use Gaussian blur natively in cairo, I achieve it with a radial gradient. In this UI design there's a blue l.e.d. so I make a drawing of an unlit l.e.d. and then a lit version. Below you see them side by side. The "light" is just a lighter blue circle over top with a multi-stop radial gradient centered on the circle so that the edges are becoming transparent. It works. These are used in a toggle button draw function that just removes the "light" circle when passed a 0.<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjzx4xZUOV8AmlmE7YaaocfV4WskJnOrwKs7AHSv7hpMeHlh6mMLP7KMT-DAZWqusbFBfa-pnIPh0lLfdLGprjyZR0mRjBZFOMT3PrRBlJDj8jtTg6XClZNp6FSba0DeVaP_6fkgZxf85hS/s1600/led.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjzx4xZUOV8AmlmE7YaaocfV4WskJnOrwKs7AHSv7hpMeHlh6mMLP7KMT-DAZWqusbFBfa-pnIPh0lLfdLGprjyZR0mRjBZFOMT3PrRBlJDj8jtTg6XClZNp6FSba0DeVaP_6fkgZxf85hS/s1600/led.png" height="344" width="640" /></a></div>
<br />
<br />
So once you've drawn a bunch of widgets and saved them as separate svgs, its probably worth throwing them all together into a layout. So for this example here are the pieces:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjEFnvlAw6BiwXG7lbV7JsTKoBKPNa6XuLWWEkFbEz7Jq4mqzsm_tRcds0wOqiC0DMCRpKgS_ilSrfcbmByqm44R7ROSfpCLsvf60OTGrNWtB-NpldU88MqaLy-uSCFL75mIM0smilu9GQF/s1600/parts.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjEFnvlAw6BiwXG7lbV7JsTKoBKPNa6XuLWWEkFbEz7Jq4mqzsm_tRcds0wOqiC0DMCRpKgS_ilSrfcbmByqm44R7ROSfpCLsvf60OTGrNWtB-NpldU88MqaLy-uSCFL75mIM0smilu9GQF/s1600/parts.png" height="215" width="400" /></a></div>
<br />
And I arrange them like so:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhFjYX8nKQP9EcEUX1U3FBcn22SKJNWuxx28llHvWmXQ33-xZxB-rxSOu5gl_gQvu2adFDbjxTxnCx75Ah5103ZcwDOD16j9E9fHDlKavptV9-J9LjIOP4GDDtii5QAHbuR_7bAPAIEQMj_/s1600/stuckMockup.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhFjYX8nKQP9EcEUX1U3FBcn22SKJNWuxx28llHvWmXQ33-xZxB-rxSOu5gl_gQvu2adFDbjxTxnCx75Ah5103ZcwDOD16j9E9fHDlKavptV9-J9LjIOP4GDDtii5QAHbuR_7bAPAIEQMj_/s1600/stuckMockup.png" height="215" width="400" /></a></div>
<br />
<br />
One trick I use is that I make a group of the 2 dials and center the group with respect to the background, then ungroup them so that they are equally spaced, then center their respective labels to the knobs.<br />
<br />
This part is the most complicated, but can be bypassed by using simpler backgrounds, using labels on the widgets rather than fancy text drawn into the background, and just laying it out in ntk-fluid. We want to take note of the size of the background canvas and where each widget falls so that we can reproduce the inkscape drawing in ntk-fluid. The trouble is SVG/Inkscape defines the origin as the bottom left corner and fltk defines the origin as the top left corner with the y axis inverted. So to really reproduce the drawing we can't just copy the numbers, we have to take each widget and recalculate it. So for example the background is 128x200 width x height and in Inkscape the switch falls at a position (x,y,w,h) of about (20,71,11,36). So once that's switched to fltk the y changes to 200-(71+36) = 93. Another tip is select all the objects, then in the bar above the drawing, lock the width and height and then type a nice round number into one of them (such as 200). This scales everything respectively. You could even move and scale each object to nice integers by directly entering numbers this way, but I just figure the nearest pixel is close enough.<br />
<br />
Once you have a mockup and note all the important numbers, save each object that will become a widget separately (delete everything but the widget of interest, resize canvas, save, undo till the other widgets are back, repeat). So here I generate 4 files, the dial, the switch, the l.e.d. and everything else gets saved as the background file (remember to convert text to paths!). Convert those files to cairo code as described in the last post and make the switch and l.e.d. render functions have an additional argument for being on or off (dials were covered last time, follow that). Now, continue with that guide to make a UI in ntk-fluid.<br />
<br />
I'm doing some things differently from the previous guide. One is setting all the widget styles such that they have no boxes. I was going to do the window with no box so that it was a "non-rectangular" window, but then it requires a window manager with compositing. In the extra code section for the window I include ffffltk.h and in another extra code box enter "using namespace ffffltk;" This way I don't have to declare each widget a ffffltk::Dial etc. only "Dial" (else it will look for a non-existent "ffffltk::Fl_Dial").<br />
<br />
Also not mentioned is that the ffffltk::Background widget has a property "stretch" that is set true by default; in this UI I would set it to false so that the background height and width are always scaled proportionally, preserving the shape. This good for decorations but not really the best for this UI because the other widgets scale and move with respect to the window not the drawing. To make up for this I created a new ffffltk widget: Aspect_Group. Clever name huh? Its a group that always preserves aspect ratio. So you assign the draw function to the background drawing and then put all the other widgets inside the group. They then scale and move proportionally to the background rather than the whole window. So if you want the window to be able to change shape (i.e. be tall and narrow or short and wide), use a Background widget and put your text labels as separate Backgrounds with stretch disabled. If you want it to maintain aspect ratio, use an Aspect_Group.<br />
<br />
The l.e.d. is an inactive button and gets its value set by the callback of the other button with "led->value(stickit->value());". I also set the buttons to toggle buttons using "o->type(FL_TOGGLE_BUTTON)" in initialization since there is no ffffltk::Toggle_button to instantiate.<br />
<br />
This UI design is obviously impractical. More than half
the window is wasted simply for decoration, when there are only 3 real
widgets, and one redundant indicator. But this is going to a plugin that
can have a host generated UI and be perfectly functional with that, so
the only reason you would use this GUI is because you want eye-candy. So
now you've got some:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjgT9_Hrh-jH0OqhfpBU_q8ezBaCk4CzlQII_HxH7lAtUj9K_mffWJRTPrBNteKjOupnedQOy2PBpCwPjdxIL_FX2SC9Ax-B8aQ9XlDw2QGhmOlhVlHuMe9XMCTuoDim56qcEa2pTwAxFHV/s1600/result.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjgT9_Hrh-jH0OqhfpBU_q8ezBaCk4CzlQII_HxH7lAtUj9K_mffWJRTPrBNteKjOupnedQOy2PBpCwPjdxIL_FX2SC9Ax-B8aQ9XlDw2QGhmOlhVlHuMe9XMCTuoDim56qcEa2pTwAxFHV/s1600/result.png" height="225" width="400" /></a></div>
<br />
<br />
There you have it. Overall this is another almost-tutorial that is probably more confusing than helpful. The last post I wrote to help myself remember, this one I kinda wrote for you readers. If you really exist. Hope it helps. Ask for clarification and I'll either elucidate or write a separate post detailing whatever it is. But also hang on for a follow up on how to apply this UI to an LV2 plugin. That post might be more thorough since I'll be writing for myself again. Sorry but it's true.Spencerhttp://www.blogger.com/profile/12246776537628500183noreply@blogger.com0tag:blogger.com,1999:blog-2309349868071255294.post-21687389332869504192014-07-30T14:43:00.001-07:002014-08-15T20:02:19.950-07:00Creating A GUI in InkscapeLong time no post eh? Busy busy times. The roof of my house is naked and I'm trying to get it covered up, so not much free time. But I was trying this while waiting for a build at work. I still owe you dedicated reader(s) a discussion on some of the production techniques on my new song, but it will be a while.<br />
<br />
Anyhow, I fiddled for a year or more with creating QT widgets that display svg drawings of primitives I draw in inkscape. I was trying to do this for rakarrack and at the time it was the best way I could reconcile my infantile GUI knowledge with my infantile art skills (inkscape is manageable for me. You just draw shapes. Its geometry!). I had moderate success, the widgets are functional, (and can be found in my <a href="https://sourceforge.net/p/infamousplugins/code/ci/gui/tree/src/qsvgui/">infamous repository</a>) but lacking some significant features, namely, due to QT, no floating point representation in dials. Also I have to manually create a file to dictate what primitive files are used and where they are placed in the window. The svg files are loaded at run time after this descriptor file is parsed allowing for anyone to customize the look, which is kinda cool, but not really necessary. In the end, it was over complicated, and I didn't really like working on it (why I haven't released a single program with a fancy stylized gui yet).<br />
<br />
Enter <a href="https://github.com/harryhaaren/openAV-AVTK">AVTK</a>.<br />
<br />
<a name='more'></a>Wait. AVTK is just a set of <a href="http://non.tuxfamily.org/ntk/">NTK </a>widgets that get drawn with <a href="http://cairographics.org/">cairo</a>; NTK in turn is an <a href="http://www.fltk.org/index.php">FLTK</a> fork that renders the FL widget drawing with cairo. And cairo doesn't have tools for drawing svg content anyway. So how will that help? Somehow I need to draw svg files! <br />
<br />
Enter <a href="https://github.com/akrinke/svg2cairo">svg2cairo</a>.<br />
<br />
Oh, ok. That makes sense.<br />
<br />
I'm losing my runtime loading/skinning ability, but I think the ease of programming will make it so I ACTUALLY release something with a GUI. Its something I've wanted to have done for quite some time, without actually doing it.<br />
<br />
Anyhow, we'll talk about specific implementations later, for now lets have an almost tutorial on how to do it.<br />
<br />
I assume you already have drawn an svg that you want to use so first lets install the tools (on ubuntu) **WARNING: if you aren't careful, you could make it so that the xserver doesn't start on your system. It happened to me. Its fixable, but a pain. If you follow these instructions in this order you will be ok:<br />
<br />
<code>
sudo apt-get install librsvg2-dev</code><br />
<code>git clone https://github.com/original-male/ntk.git ntk-source</code><br />
<code>cd ntk-source</code><br />
<code>./waf configure</code><br />
<code>./waf</code><br />
<code>sudo ./waf install</code><br />
<br />
Cairo unfortunately has to be built with a special flag for the converter to work, so download the source of a release:<br />
<code>mkdir cairo<br />
cd cairo <br />
wget http://cairographics.org/releases/cairo-1.12.16.tar.xz<br />
tar xf cairo-1.12.16.tar.xz<br />
cd cairo-1.12.16<br />
./configure --enable-xml=yes</code><br />
<code>make</code><br />
<code>sudo make install</code><br />
<br />
IMPORTANT UPDATE: I found that installing NTK from source always breaks my X setup so that I can't login with the display manager (read: bad). The easiest workaround is after installing (before rebooting!) run the commands:<br />
<code>sudo apt-get install --reinstall libcairomm-1.0-1<br />
rm ~/.Xauthority</code><br />
<br />
This doesn't replace what you just installed, it just reconfigures some stuff or something. If you can explain it to me please do. I suppose an easier workaround is just use NTK from the kxstudio ppas.<br />
<br />
I then had to make sure building the program looks at this local library rather than the one installed from the distribution (don't copy over the distro version, it breaks a lot of stuff). Run the command:<br />
<code>export LD_LIBRARY_PATH=/usr/local/lib </code><br />
<br />
Once you're done converting you'll want to set the LD_LIBRARY_PATH back to usr/lib or it will break some stuff.<br />
Now you can download and install svg2cairo<br />
<code>git clone https://github.com/akrinke/svg2cairo.git</code><br />
<code>cd svg2cairo</code><br />
<code>make</code><br />
<br />
After all that work I'm hungry.<br />
<br />
Enter lunch.<br />
...<br />
Exit lunch.<br />
<br />
Now lets convert an svg. For the first example here's a vintage dial I drew:<br />
<br />
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg height="92.838585" id="svg12890" inkscape:version="0.48.4 r9939" sodipodi:docname="ivoryDialCombinedplain.svg" version="1.1" width="92.838585" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg">
<defs id="defs12892">
<radialgradient cx="78.743378" cy="172.53114" fx="78.743378" fy="172.53114" gradienttransform="matrix(1.1844518,-1.0951232,1.0819464,1.1702004,-193.92761,61.887175)" gradientunits="userSpaceOnUse" id="radialGradient4575" inkscape:collect="always" r="20.063856" xlink:href="#linearGradient3356-7">
<lineargradient id="linearGradient3356-7">
<stop id="stop3358-7" offset="0" style="stop-color: #fff6ae; stop-opacity: 1;">
<stop id="stop3364-1" offset="1" style="stop-color: #ffffff; stop-opacity: 1;">
</stop></stop></lineargradient>
<radialgradient cx="78.743378" cy="172.53114" fx="78.743378" fy="172.53114" gradienttransform="matrix(1.1844518,-1.0951232,1.0819464,1.1702004,-193.92761,61.887175)" gradientunits="userSpaceOnUse" id="radialGradient12913" inkscape:collect="always" r="20.063856" xlink:href="#linearGradient3356-7">
<lineargradient id="linearGradient3356-7-4">
<stop id="stop3358-7-2" offset="0" style="stop-color: #383838; stop-opacity: 0.67543858;">
<stop id="stop3364-1-0" offset="1" style="stop-color: #ffffff; stop-opacity: 0;">
</stop></stop></lineargradient>
<lineargradient gradientunits="userSpaceOnUse" id="linearGradient14770" inkscape:collect="always" x1="52.458691" x2="40.941853" xlink:href="#linearGradient3356-7" y1="825.30457" y2="782.5293">
<lineargradient gradientunits="userSpaceOnUse" id="linearGradient4098" inkscape:collect="always" x1="106.70766" x2="39.533524" xlink:href="#linearGradient3356-7" y1="840.09515" y2="789.03552">
<lineargradient gradientunits="userSpaceOnUse" id="linearGradient4110" inkscape:collect="always" x1="85.405975" x2="6.097508" xlink:href="#linearGradient3356-7-4" y1="866.26501" y2="715.98151">
<lineargradient gradientunits="userSpaceOnUse" id="linearGradient4130" inkscape:collect="always" x1="52.458691" x2="40.941853" xlink:href="#linearGradient3356-7" y1="825.30457" y2="782.5293">
<lineargradient gradientunits="userSpaceOnUse" id="linearGradient4134" inkscape:collect="always" x1="52.458691" x2="40.941853" xlink:href="#linearGradient3356-7-4" y1="825.30457" y2="782.5293">
<lineargradient gradientunits="userSpaceOnUse" id="linearGradient3827" inkscape:collect="always" x1="52.458691" x2="40.941853" xlink:href="#linearGradient3356-7" y1="825.30457" y2="782.5293">
<lineargradient gradientunits="userSpaceOnUse" id="linearGradient3841" inkscape:collect="always" x1="52.458691" x2="40.941853" xlink:href="#linearGradient3356-7" y1="825.30457" y2="782.5293">
<lineargradient gradientunits="userSpaceOnUse" id="linearGradient3843" inkscape:collect="always" x1="52.458691" x2="40.941853" xlink:href="#linearGradient3356-7" y1="825.30457" y2="782.5293">
</lineargradient></lineargradient></lineargradient></lineargradient></lineargradient></lineargradient></lineargradient></lineargradient></radialgradient></radialgradient></defs>
<sodipodi:namedview bordercolor="#666666" borderopacity="1.0" fit-margin-bottom="0" fit-margin-left="0" fit-margin-right="0" fit-margin-top="0" id="base" inkscape:current-layer="layer1" inkscape:cx="8.7999107" inkscape:cy="39.712953" inkscape:document-units="px" inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:window-height="861" inkscape:window-maximized="0" inkscape:window-width="1596" inkscape:window-x="0" inkscape:window-y="18" inkscape:zoom="5.6" pagecolor="#ffffff" showgrid="false">
<metadata id="metadata12895">
<rdf:rdf>
<cc:work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage">
<dc:title></dc:title>
</dc:type></cc:work>
</rdf:rdf>
</metadata>
<g id="layer1" inkscape:groupmode="layer" inkscape:label="Layer 1" transform="translate(-330.72572,-480.22646)">
<path d="m 99.285717,170.57646 c 0,10.84845 -8.794406,19.64286 -19.642857,19.64286 -10.84845,0 -19.642856,-8.79441 -19.642856,-19.64286 0,-10.84845 8.794406,-19.64285 19.642856,-19.64285 10.848451,0 19.642857,8.7944 19.642857,19.64285 z" id="path5187-5" sodipodi:cx="79.64286" sodipodi:cy="170.57646" sodipodi:rx="19.642857" sodipodi:ry="19.642857" sodipodi:type="arc" style="color: black; display: inline; enable-background: accumulate; fill-opacity: 1; fill: url(#radialGradient12913); marker: none; overflow: visible; stroke-dasharray: none; stroke-dashoffset: 0; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 4; stroke-opacity: 1; stroke-width: 0.84210312; stroke: #000000; visibility: visible;" transform="matrix(1.178003,0,0,1.178003,283.32333,325.70831)">
<g id="g3831">
<path d="M 52.857142,830.93362 C 48.304323,832.31926 8.3256156,794.94384 9.4020252,790.30816 10.478435,785.67248 62.835853,769.73762 66.312263,772.98765 c 3.476409,3.25004 -8.902302,56.56033 -13.455121,57.94597 z" id="path4533" inkscape:flatsided="true" inkscape:randomized="0" inkscape:rounded="0.08" sodipodi:arg1="1.2753555" sodipodi:arg2="2.3225531" sodipodi:cx="42.857143" sodipodi:cy="798.07648" sodipodi:r1="34.345184" sodipodi:r2="29.743805" sodipodi:sides="3" sodipodi:type="star" style="color: black; display: inline; enable-background: accumulate; fill-opacity: 1; fill: url(#linearGradient3843); marker: none; overflow: visible; stroke-dasharray: none; stroke-dashoffset: 0; stroke-linecap: round; stroke-linejoin: miter; stroke-miterlimit: 4; stroke-opacity: 1; stroke-width: 2.42824578; stroke: #4d4d4d; visibility: visible;" transform="matrix(0.30403125,-1.1134137,0.28537822,1.1861892,136.34556,-360.85011)">
<path d="m 377.14347,484.03115 0,19.84309" id="path4535" inkscape:connector-curvature="0" sodipodi:nodetypes="cc" style="fill: none; marker-start: none; stroke-dasharray: none; stroke-linecap: round; stroke-linejoin: miter; stroke-miterlimit: 4; stroke-opacity: 1; stroke-width: 1.54299998; stroke: #000000;">
</path></path></g>
</path></g>
</sodipodi:namedview></svg>
<br />
<br />
If you view the source of this page and copy the svg code into a file, you can repeat this almost-tutorial yourself. Now lets convert it:<br />
<code>./svg2cairoxml vintagedial.svg vintagedial.xml<br />
lua cairoxml2cairo.lua -f c vintagedial.xml draw_vintagedial.c</code><br />
<br />
This creates a c source file that has 3 functions:<br />
<code>cairo_code_draw_vintagedial_height();<br />
cairo_code_draw_vintagedial_width();<br />
cairo_code_draw_vintagedial_render(cairo_t * cr); </code><br />
<br />
It always names the functions to match the name of the file. You can test it out and see the result by editing the test-c.c file to include the generated c (which is terrible code style, never include a .c) and function names then compiling and running it:<br />
<code>sed -i s/<span class="o"><</span><span class="n">name</span> <span class="n">of</span> <span class="n">the</span> <span class="n">file</span><span class="o">>/draw_vintagedial/ test-c.c</span><br />
gcc test-c.c -I /usr/include/cairo -L /usr/lib -lcairo<br />
./a.out</code><br />
<br />
This generates a nice PNG:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgrfIJdb7cEgEjBHSw7hm3QKfncFHqTeVVakgRrwHscNehT0VSgt8EfDNWYEFUwWX7uLCLepv3rferITmwQ4Yua-Z6W92LEP0e3RetYQsmNAUQR2Q_XmoUv2mVlE-q0mTVy9qyrbA0Zsr19/s1600/test-c.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgrfIJdb7cEgEjBHSw7hm3QKfncFHqTeVVakgRrwHscNehT0VSgt8EfDNWYEFUwWX7uLCLepv3rferITmwQ4Yua-Z6W92LEP0e3RetYQsmNAUQR2Q_XmoUv2mVlE-q0mTVy9qyrbA0Zsr19/s1600/test-c.png" /></a></div>
<br />
I'm impressed. Theoretically you can convert to some other formats that might make previewing easier, but the conversion from cairo xml throws more errors (warnings?). In the end I really just want a c function anyway. One major setback is that there is no gaussian blur so make sure you take any blurs out of the svg before converting. This makes highlights and shadows more difficult. They sometimes can be done with gradients, but only if you have a very simple shape. But blur in the file makes the generated c uncompilable.<br />
<br />
If you are following along it may not have worked. Make sure your LD_LIBRARY_PATH variable is set correctly as mentioned above. With all these details I made a script that would batch convert everything in a local directory:<br />
<code>#!/bin/bash</code><br />
<pre><div class="code_block" id="l10">
<span class="nb">export </span><span class="nv">LD_LIBRARY_PATH</span><span class="o">=</span>/usr/local/lib
</div>
<div class="code_block" id="l11">
</div>
<div class="code_block" id="l12">
<span class="k">if</span> <span class="o">[[</span> -z <span class="nv">$1</span> <span class="o">]]</span>
</div>
<span class="k">then</span>
<div class="code_block" id="l15">
<span class="nb">cd </span>svgfiles
</div>
<div class="code_block" id="l16">
mkdir formats
</div>
cp ../formats/* formats/
<span class="k">for </span>file in *.svg;
<span class="k">do</span>
<span class="k"> </span><span class="nb">echo</span> <span class="s2">"converting $file"</span>
./../svg2cairoxml <span class="nv">$file</span> <span class="k">${</span><span class="nv">file</span><span class="k">}</span>.xml
lua ../cairoxml2cairo.lua -f c <span class="k">${</span><span class="nv">file</span><span class="k">}</span>.xml draw_<span class="k">${</span><span class="nv">file</span><span class="p">/.svg/</span><span class="k">}</span>.h
<span class="nv"> file</span><span class="o">=</span>draw_<span class="k">${</span><span class="nv">file</span><span class="p">/.svg/</span><span class="k">}</span>.h
<span class="nv"> size</span><span class="o">=</span><span class="k">$(</span>file <span class="nv">$file</span> | grep <span class="s2">"empty"</span><span class="k">)</span>
<span class="k"> if</span> <span class="o">[[</span> -z <span class="s2">"$size"</span> <span class="o">]]</span>;
<span class="k"> then</span>
<span class="k"> </span><span class="nb">echo </span>success
sed -i s/CAIRO_DEFAULT/CAIRO_ANTIALIAS_DEFAULT/ <span class="nv">$file</span>
sed -i <span class="s2">"1s,^,inline ,"</span> <span class="nv">$file</span>
sed -i <span class="s2">"2s,^,inline ,"</span> <span class="nv">$file</span>
sed -i <span class="s2">"3s,^,inline ,"</span> <span class="nv">$file</span>
<span class="nv"> caps</span><span class="o">=</span><span class="sb">`</span><span class="nb">echo</span> <span class="k">${</span><span class="nv">file</span><span class="p">^^</span><span class="k">}</span><span class="sb">`</span>
<span class="nv"> caps</span><span class="o">=</span><span class="sb">`</span><span class="nb">echo</span> <span class="nv">$caps</span> | sed s/<span class="o">[</span>.<span class="o">]</span>/_/<span class="sb">`</span>
sed -i <span class="s2">"1s,^,#ifndef $caps\n#define $caps\n,"</span> <span class="nv">$file</span>
sed -i -e <span class="s2">"\$a#endif"</span> <span class="nv">$file</span>
<span class="k"> else</span>
<span class="k"> </span>rm draw_<span class="k">${</span><span class="nv">file</span><span class="p">/.svg/</span><span class="k">}</span>.c
<span class="k"> fi</span>
<span class="k">done</span>
<div class="code_block" id="l41">
rm -rf formats/
</div>
rm *.xml
<span class="nb"> cd</span> ..
<span class="k"> </span></pre>
<pre><span class="k">else</span>
<span class="k"> </span><span class="nb">echo</span> <span class="s2">"converting $file"</span> </pre>
<pre> ./svg2cairoxml <span class="nv">$1</span> <span class="k">${</span><span class="nv">1</span><span class="k">}</span>.xml
lua cairoxml2cairo.lua -f c <span class="k">${</span><span class="nv">1</span><span class="k">}</span>.xml draw.c
sed -i s/CAIRO_DEFAULT/CAIRO_ANTIALIAS_DEFAULT/ draw.c
<span class="k">fi</span>
<div class="code_block" id="l53">
<span class="nb">export </span><span class="nv">LD_LIBRARY_PATH</span><span class="o">=</span>/usr/lib
</div>
</pre>
<code><br /> </code><br />
<br />
Once you have the source file, you can draw the thing to your hearts delight, but we want to make it an interactive widget. So the important thing you'll have to do is animate the movement (more or less). There are a couple of approaches. One (that I took before in my QT attempts) is make separate svg files for the primitives and draw them separately, i.e. one for the round part of the knob that doesn't move and one for the needle that spins. The conversions will make 2 separate files with 2 separate functions that you'll need to call. Before calling the function that draws the needle do some transforms that will turn the drawing. Something like:<br />
<code>cairo_code_draw_vintagedial_render(cr);<br />
cairo_translate(cr,width/2,height/2);<br />
cairo_rotate(cr,3*PI/2*value - 3*PI/4); <br />
cairo_translate(cr,-width/2,-height/2);<br />
cairo_code_draw_vintageneedle_render(cr);<br />
</code>
<br />
<br />
I haven't tested this, so no copy pasting but hopefully that gets the idea across. I however am opting for making a single drawing, then editing the render function to rotate it. To do so you'll have to figure out whats going on in the generated function. I don't know cairo hardly at all, but I was able to figure it out, so its not that bad. More or less it just draws each object in the svg from bottom layer to top. It draws the fill then the outline. And each part is conveniently separated with a comment line.<br />
<br />
So edit draw_vintagedial.c to become draw_vintagedial.h with the following changes: <code><br /></code><br />
<code>diff --git a/draw_</code><code><code>vintagedial</code>.c b/draw_</code><code><code>vintagedial</code>.h<br />index 44f6b35..8246911 100644<br />--- a/draw_</code><code><code>vintagedial.c</code><br />+++ b/draw_vintagedial.h<br />@@ -1,6 +1,7 @@</code><br />
<code>+#define PI 3.1415926535897932384626433832795</code><br />
<code> int cairo_code_draw_</code><code><code>vintagedial</code>_get_width() { return 91; }<br /> int cairo_code_draw_</code><code><code>vintagedial</code>_get_height() { return 91; }<br />-void cairo_code_draw_</code><code><code>vintagedial</code>_render(cairo_t *cr) {<br />+void cairo_code_draw_</code><code><code>vintagedial</code>_render(cairo_t *cr, float val) {<br /> cairo_surface_t *temp_surface;<br /> cairo_t *old_cr;<br /> cairo_pattern_t *pattern;<br />@@ -59,6 +60,11 @@ cairo_pattern_set_extend(pattern, CAIRO_EXTEND_PAD);<br /> cairo_pattern_set_filter(pattern, CAIRO_FILTER_GOOD);<br /> cairo_set_source(cr, pattern);<br /> cairo_pattern_destroy(pattern);<br />+<br />+cairo_translate(cr,45.5,45.5);<br />+cairo_rotate(cr,3*PI/2*val - 3*PI/4);<br />+cairo_translate(cr,-45.5,-45.5);<br />+<br /> cairo_new_path(cr);<br /> cairo_move_to(cr, 57.8125, 84.710938);<br /> cairo_curve_to(cr, 56.824219, 91.421875, 34.003906, 91.601562, 33.007812, 84.902344);</code><code><br /></code><br />
<br />
If you haven't seen a git diff before, its showing what lines get removed (-) and what gets added (+) with some unchanged lines above and below. <br />
<br />
So I actually lied a little. We won't be using AVTK at all, we'll be using <a href="http://sourceforge.net/p/infamousplugins/code/ci/gui/tree/src/ffffltk/">FFFFLTK</a>: the forked-forked-forked-fast and light toolkit! Which I invented. Just now. Its, (you couldn't guess?) a fork of AVTK. The primary difference is rather than a built in draw function (which is used as a fallback default) it just calls a function pointer you pass to it.<br />
<br />
So in the end (for a dial or slider) you just need some function that takes
a value from 0-1 (min to max) as the argument and draws the drawing
accordingly and pass a pointer to it into the ffffltk widget. Alternatively you can just copy and paste the guts of the
render function into the draw function of the AVTK widget, but I'm going
to do it with function pointers. This is because I want my plugins to each be customized, so they'll be using lots of different looking knobs, and I don't want to copy all the other stuff over and over. Instead I'll just repeat this process for each drawing and generate a bunch of headers that I can include at will.<br />
<br />
So get a copy of the ffffltk dial widget. There's a button in the link above to download a snapshot. Do that, open the archive and extract src/ffffltk/ffffltk_dial.h to the folder we're using for this little project.<br />
<br />
So lets look at how to make a dial and assign the drawing function:<br />
<code>ffffltk::Dial *dial = new ffffltk::Dial;<br />
dial->drawing_h = cairo_code_draw_vintagedial_height();<br />
dial->drawing_w = cairo_code_draw_vintagedial_width();<br />
dial->drawing_f = &cairo_code_draw_vintagedial_render; </code><br />
<br />
and thats it. With various drawing functions you can have loads of different looking knobs on the same GUI.<br />
<br />
So lets make a test. One of the programs installed when you installed NTK is ntk-fluid which is the NTK version of the <a href="http://www.fltk.org/doc-1.1/fluid.html">fast-light-user-interface-designer</a>. This makes designing the gui a breeze because much of it is drag and drop and the rest is fill in the blank. It might help to review the official fluid tutorial on that page linked above. I'm just going to breeze through it.<br />
<br />
First make a new class, name it "FancyUI" and add a function and window named whatever the default is. In the C++ tab add in an extra code box:<br />
<code>o->show();</code><br />
<br />
Add a single dial to the window and we'll edit its properties. Label it "Fancyness" and move it and resize it however you like. In the C++ tab type in the class box "ffffltk::Dial" in an extra code box type <br />
<code></code><code>#include "ffffltk_dial.h" #include "draw_vintagedial.h"</code><br />
<code><br /></code>
and finally in another extra code box type:<br />
<code>o->drawing_h = cairo_code_draw_vintagedial_get_height(); o->drawing_w =
cairo_code_draw_vintagedial_get_</code><code><code>width(); o->drawing_f =
&cairo_code_draw_vintagedial_render</code>; </code><br />
<br />
Save it as test.fl then go to file>write code and it will spit out test.h and test.cxx that you can include in any project. For the test, make a main.cxx file with the following:<br />
<code>#include <FL/Fl.H><br />#include <FL/Fl_Double_Window.H><br />#include "test.h"<br /><br />int main()<br />{<br /> FancyUI *a = new FancyUI;<br /> a.make_window();<br /> <br /> Fl::run();<br /> <br /> return 0;<br />}</code><br />
<br />
<br />
Now compile and run it:<br />
<code>g++ -fpermissive *.cxx -lGL -lntk `pkg-config --cflags --libs cairo ntk` -o test_ui</code><br />
<code>./test_ui </code><br />
<br />
And you should see something like this:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj8K-SriK6CsmOVdIS2J9ln2XsE11PtiwzLthrTvhtin9VtJL1r_PfB4BN1R5pVujhQUW_4mS6CBkWyTZAglDtPFs0uURzqtRbL_QQmxfce8fSg2F1MdBWJ2-u-SOoOqHGSidpm0m6fjx87/s1600/fancyshot.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj8K-SriK6CsmOVdIS2J9ln2XsE11PtiwzLthrTvhtin9VtJL1r_PfB4BN1R5pVujhQUW_4mS6CBkWyTZAglDtPFs0uURzqtRbL_QQmxfce8fSg2F1MdBWJ2-u-SOoOqHGSidpm0m6fjx87/s1600/fancyshot.png" height="400" width="370" /></a></div>
Congratulations! You are on the bleeding edge of technology. I am literally writing ffffltk as I write this, but I think the dial is more or less ready. Eventually I'll make a background, button, slider and whatever other widgets I'll need. Feel free to pitch in. I ought to mention too, big thanks to Harry Haaren of openAV productions for helping me out and making AVTK so simple to fork. And really to the creators of all these many tools I'm using. Thats the beauty of FLOSS: you really get to stand on the shoulders of giants.<br />
<br />
Some issues:<br />
One thing is that drawings don't actually scale well. At some point as it shrinks you need to remove details to keep it clear, and as it grows you should add detail. This method simply scales everything. It works ok within limits. If you know you are going to need very small or very large then you can change your drawing function accordingly. Or fork it and add height and width arguments to the draw function call. Its hard to keep it simple and make it flexible at the same time. The point was to be able to just use an svg with as little coding as possible. And there you have it.<br />
<br />
Any questions?<br />
<br />Spencerhttp://www.blogger.com/profile/12246776537628500183noreply@blogger.com0tag:blogger.com,1999:blog-2309349868071255294.post-63136542088657312362014-07-23T14:24:00.000-07:002014-07-23T14:41:36.486-07:00How to Export a Github Wiki to PDFs and/or a WebsiteI recently needed to forward some documentation for work. This documentation had been done wisely in a wiki in Github. Unfortunately it was an enterprise account and we couldn't just give them a link. I just needed to put the important sections into a pdf or website and send it along.<br />
<br />
Unfortunately I couldn't figure out how to do it.<br />
<br />
After some pain and suffering I got it the way I wanted, and I'm sharing it here so I don't forget.<br />
<br />
<a name='more'></a><br />
<br />
First clone the wiki by adding .wiki to the repository url:<br />
<code>git clone git@github.com/user/repository.wiki.git<br />
cd repository.wiki</code><br />
<br />
I had to remove a bunch of files so that only a particular section was left so I first made a branch and did the removal there.<br />
<code>git checkout -b one_section_only<br />
<br />
rm unimportant_sections*.md</code><br />
<br />
Next up you'll need a recent version of pandoc. Now pandoc converts markdown files (like github's wiki pages) to other markdown formats, or latex, or html, or some other things. I love latex. Thats another story. I needed something more easily viewable for the other company I was sending it to. My ubuntu 12.04 LTS didn't have recent enough so it didn't understand the github style markdown, but 14.04 worked OOTB.<br />
<code>sudo apt-get install pandoc </code><br />
<br />
Now pandoc will conglomerate multiple files into a single file, but it just does it in the order received (alphabetical if you just give *.md as the input). This yielded a document that was impossible to follow as the pages are in a non-logical order. If it did a pdf with internal links and you could specify the order more easily that would have worked well for me. I dream of a recursive version that just finds local linked files and concatenates them with links between articles/pages. But there isn't anything like that that I could find. And I tried several different converters. Pandoc is good enough (and the best given my requirements).<br />
<br />
You can convert each page to pdf separately and then the links work. You just need to tell the reader which file to start with. One problem with the pdf output is that it converts the markdown to
latex then runs pdflatex. This results in some rather fine looking
documents, but I ran into trouble with tables: they're put in as floats
and the wiki depends on the tables appearing in place.<br />
<br />
You can also convert to HTML5 which yeilds a local website which is a little nicer to navigate because you don't end up with 30 pdf reader instances running. The trouble there is that the links don't append .html to the target so you have to leave them named without a file extension which means you can't just double click to open in a browser. Also they're encoded in UTF-8 so you might have to set the browser to view in Unicode if something looks wrong (ours did).<br />
<br />
I just made both pdf and html versions and sent them along. They haven't complained so it must have been useable.<br />
<br />
Here's how to do the conversions:<br />
<code>find -name "*.md" -exec pandoc -o {}.pdf {} \;<br />
mkdir doc <br />
for file in *.md.pdf; do mv "$file" "doc/${file/.md/}";done</code><br />
<br />
or for html:<br />
<code>find -name "*.md" -exec pandoc -o {}.html {} \;<br />
mkdir doc <br />
for file in *.md.html; do mv "$file" "doc/${file/.md.html/}";done</code><br />
<br />
As I mentioned it has to strip the html extension for the links to work. I actually had to sed -i to fix a lot of the links as people had put "../data/page" as the link when they could just put "page". But it worked in the end. It took me a whole day too, ironing out the process. But we ended up with an easy to share file that has the same structure and organization as we'd been taking care to write into the wiki.<br />
<br />
If you have a better way let me know! <br />
<br />
<br />Spencerhttp://www.blogger.com/profile/12246776537628500183noreply@blogger.com4tag:blogger.com,1999:blog-2309349868071255294.post-34496949126634291452014-01-27T14:24:00.000-08:002014-01-28T11:19:57.000-08:00LV2 forge and Tempo syncronization, or: More LV2 Learnings (still not quite a tutorial)Ah so the urid map must be persistent...<br />
<br />
<a name='more'></a><br />
I was having a hard time getting an atom forge to generate midi events for me. The secret was when you intialize the forge, you must point to to a location thats not about to get deallocated. While this is done in the examples, it isn't explicitly stated that this is required. Since I have a way of wanting to write everything myself rather than copy and paste, it was a detail I missed. To show you some code, this part is where we get the urid maps and initialize the forge (done in the plugin initialization function):
<br />
<pre><div class="code_block" id="l95">
<span style="font-family: "Courier New",Courier,monospace;">
<span class="p" style="font-size: x-small;">
ENVFOLLOWER* plug = malloc(sizeof(ENVFOLLOWER));
LV2_URID_Map *urid_map;
...
//get urid map value for midi events
for (int i = 0; host_features[i]; i++)
{
if (strcmp(host_features[i]->URI, LV2_URID__map) == 0)
{
plug->urid_map = (LV2_URID_Map *) host_features[i]->data;
if (plug->urid_map)
{
plug->midi_event_type = plug->urid_map->map(plug->urid_map->handle, LV2_MIDI__MidiEvent);
break;
}
}
}
lv2_atom_forge_init(&plug->forge,plug->urid_map);
return plug;
</span></span></div>
</pre>
(If you didn't notice already this is from my envelope follower plugin). So we have a member of the plugin structure to store the forge and the map, and pass them both in to the library function lv2_atom_forge_init. That's not too bad. Just make sure you keep that urid_map variable around, even if you aren't ever going to access it directly.<br />
<br />
So a forge is (according to my understanding) just an object that creates atoms or other objects to be output from the plugin, including midi messages (which is the most common use I'd venture). I'm out on a limb here, but I'm guessing its the safe way to allocate memory and pass it out of the plugin so that you aren't trying to malloc in the RT thread, and so it can still be in scope once it leaves the plugin.<br />
<br />
Now in the run function of the plugin, we first associate the forge with the midi output port, and prepare a local atom to hold our midi message:
<br />
<pre><div class="code_block" id="l95">
<span style="font-family: "Courier New",Courier,monospace;">
<span class="p" style="font-size: x-small;">
LV2_Atom midiatom;
unsigned char msg[3];
...
//get midi port ready
const uint32_t capacity = plug->midi_out_p->atom.size;
lv2_atom_forge_set_buffer(&plug->forge,(uint8_t*)plug->midi_out_p, capacity);
lv2_atom_forge_sequence_head(&plug->forge, &plug->frame, 0);
midiatom.type = plug->midi_event_type;
midiatom.size = 3;//midi CC
</span></span></div>
</pre>
Note that the member of the plugin structure midi_out_p is declared as an LV2_Atom_Sequence*. I have no idea what exactly the function call lv2_atom_forge_sequence_head does, but I assume its a sort of syncronization.<br />
<br />
With this plugin we send a midi CC proportional to the envelope of the audio input signal. Once we've calculated what the amplitude is/what the CC value should be we load the message with its parameters (read about midi spec if you need to know more here, but the first byte is the channel, second the control number, third the value). We forge a new message into the port buffer through several steps. First set the time, or the frame index this message corresponds to in this period (below it's variable i). Next you forge on the local atom which has the members showing its a midi event type with size 3. Then you forge the bytes that the message should carry. Finally we pad the atom which I think separates the events in the buffer. I believe this must be done before another message can be forged. The whole process looks like this:
<br />
<pre><div class="code_block" id="l95">
<span style="font-family: "Courier New",Courier,monospace;">
<span class="p" style="font-size: x-small;">
//make event
msg[0] = MIDI_CONTROL_CHANGE + (unsigned char)(*plug->channel_p - 1);
msg[1] = MIDI_DATA_MASK & (unsigned char)*plug->control_p;
msg[2] = MIDI_DATA_MASK & plug->mout;
midiatom.type = plug->midi_event_type;
midiatom.size = 3;//midi CC
lv2_atom_forge_frame_time(&plug->forge,i);
lv2_atom_forge_raw(&plug->forge,&midiatom,sizeof(LV2_Atom));
lv2_atom_forge_raw(&plug->forge,msg,3);
lv2_atom_forge_pad(&plug->forge,3+sizeof(LV2_Atom));
</span></span></div>
</pre>
And that's it. You can repeat that last block as many times as you need (so long as you aren't producing more messages than frames or samples in the buffer period) and the LV2 framework handles the rest. Sending midi notes or program changes all go right along the same lines, you'd just have different constants in the second byte of the message. I got this from example code and without a better understanding of the forges, and atoms, and atom ports, you're stuck with my marginally accurate explanations. Perhaps you're better off just looking at the example plugins, but I'm trying my best. My code is GPL so you are free to copy this directly.<br />
<br />
For my next trick I'll try to explain how to tempo sync parameters. This was a feature added upon request to the Cellular Automaton Synth. Its the type of feature that makes a user feel like they're really good (since its a lot easier to make patches that fit in a song) and its really not hard to do. In a synth you have an atom input port anyway for the midi input to control it, but you can have an atom input that only passes the time info. I'm not going to show you how, because I've never done it myself. But here's how with a full midi input port and the principles stand alone.<br />
<br />
The idea is that the host passes timing information to your plugin through the atom port. So instead of a midi atom you suddenly receive this other kind of atom. To recognize it, you'll need to have the URID mapping:
<br />
<pre><div class="code_block" id="l95">
<span style="font-family: "Courier New",Courier,monospace;">
<span class="p" style="font-size: x-small;">
//get urid map value for midi events
for (int i = 0; host_features[i]; i++)
{
if (strcmp(host_features[i]->URI, LV2_URID__map) == 0)
{
LV2_URID_Map *urid_map = (LV2_URID_Map *) host_features[i]->data;
if (urid_map)
{
synth->midi_event_type = urid_map->map(urid_map->handle, LV2_MIDI__MidiEvent);
synth->other_type = urid_map->map(urid_map->handle, LV2_ATOM__Blank);
synth->long_type = urid_map->map(urid_map->handle, LV2_ATOM__Long);
synth->float_type = urid_map->map(urid_map->handle, LV2_ATOM__Float);
synth->time_info_type = urid_map->map(urid_map->handle, LV2_TIME__Position);
synth->beatsperbar_type = urid_map->map(urid_map->handle, LV2_TIME__barBeat);
synth->bpm_type = urid_map->map(urid_map->handle, LV2_TIME__beatsPerMinute);
synth->speed_type = urid_map->map(urid_map->handle, LV2_TIME__speed);
synth->frame_type = urid_map->map(urid_map->handle, LV2_TIME__frame);
synth->framespersec_type = urid_map->map(urid_map->handle, LV2_TIME__framesPerSecond);
break;
}
}
}
</span></span></div>
</pre>
So you can see in addition to the midi_event_type URID* there are a lot of other type of atoms' URIDs, the most important one in this context being the beats per minute, but to get that actual data, you'll need several of the other atom types' URIDs as well. We treat the input from the atom port the same as we did before, checking to see if there's a midi message, but now we also check for this timing information:
<br />
<pre><div class="code_block" id="l95">
<span style="font-family: "Courier New",Courier,monospace;">
<span class="p" style="font-size: x-small;">
LV2_ATOM_SEQUENCE_FOREACH(synth->midi_in_p, event)
{
if (event)
{
if(event->body.type == synth->midi_event_type)//make sure its a midi event
{
...
}//actually midi
else if(event->body.type == synth->other_type)
{
// Received new transport position/speed
const LV2_Atom_Object *obj = (LV2_Atom_Object*)&event->body;
if(obj->body.otype == synth->time_info_type)
{
LV2_Atom *beat = NULL, *bpm = NULL, *speed = NULL;
LV2_Atom *fps = NULL, *frame = NULL;
lv2_atom_object_get(obj,
synth->beatsperbar_type, &beat,
synth->bpm_type, &bpm,
synth->speed_type, &speed,
synth->frame_type, &frame,
synth->framespersec_type, &fps,NULL);
if (fps && fps->type == synth->float_type)
{
synth->sample_rate = ((LV2_Atom_Float*)fps)->body;
}
if (bpm && bpm->type == synth->float_type)
{
// Tempo changed, update BPM
synth->ibpm = 60/(((LV2_Atom_Float*)bpm)->body);
synth->cell_lifetime = synth->sample_rate*(*synth->cell_life_p)*synth->ibpm;
}
}//if blank type
}//actually is event
}//for each event
...
}
</span></span></div>
</pre>
If its not a midi message, check to see if its an "other message" (really called a blank atom by convention*). If so, cast the atom data into another atom object, and see if it matches the time_info_type URID you got from the mapping in the initialization. If its a match, we can finally extract the data using the function lv2_atom_object_get which is in the LV2 library and pulls various data out that matches the URIDs you pass in. I then look at the sample rate (though I anticipate it won't change, but just in case), but more significantly the beats per minute. These bits of data in the "blank" atom (or "other" as I like to think of it) are also in atoms (from atoms in atoms in an atom) but you can access the data as shown. The math isn't hard, but in general you're probably most interested in knowing how many frames per beat. (60 sec/min )/(beats/min)*(frames/sec) == frames/beat (to spell it out for you). I have an input port where the user can select how many beats are in the cell lifetime (i.e. cell state switches every beat, half beat, 4 beats, etc.) so this scales the result to make the final value that I store the number of frames per cell lifetime.<br />
<br />
I employ this value when processing the frames, I count the number that have elapsed and when the time expires, I just reset a counter. My implementation is flawed in that if it is a non-integer number of frames I don't account for that. I should keep track and have a "leap frame" that keeps it on track. So the sound will actually drift over time, but I have yet to have noticed when playing with it. But this is great for LFOs and other time dependent parameters.<br />
<br />
This post has been kicking around half finished in the drafts pile for months now, and I've just about forgotten how to do it. So I need to write this down for myself more than for anybody else. But, Mr. Else, I hope you find it helpful too. As always see my plugins' source at <a href="http://sourceforge.net/projects/infamousplugins/"></a> and feel free to ask questions.<br />
<br />
*EDIT: This section has been revamped a bit to try to make it more accurrate. Thanks to Drobilla for helping me understand (see his comment below).Spencerhttp://www.blogger.com/profile/12246776537628500183noreply@blogger.com3tag:blogger.com,1999:blog-2309349868071255294.post-82158797036920372932013-09-03T11:37:00.003-07:002013-09-03T11:41:01.853-07:00Top 500 Greatest Albums of All Time (with a question mark)I've been going through and listening to <a href="http://www.rollingstone.com/music/lists/500-greatest-albums-of-all-time-20120531" target="_blank">the Rolling Stone Magazines list of the greatest albums</a>. I just search the album on youtube with "full album" and nearly all of them have a hit. Its been interesting in many many ways.<br />
<br />
<a name='more'></a><br /><br />
One is simply that there are a lot of pop songs I've heard on the radio and recognize, but couldn't have sung one word of let alone name the artist and album. Its kinda fun thinking, "oh this is where that song comes from."<br />
<br />
Another way its been interesting is which albums AREN'T available on youtube. Now I recognize this is more likely due to the label than the artist, but I can't help but think differently of the artist, when I go to listen to an album I've never heard and the big label pops up that more or less means you can't listen unless you pay. Well, I'll never know how that artist sounds. I'm much less inclined to ever buy it. I'm only through the first 10 so far (500-490) and only the White Stripes hasn't been available, though I had find a playlist for KISS's "Destroyer", the full album video exists but has been muted due to copyright.<br />
<br />
Another interesting thing is to see the comments and how many "Artist/Album XXX isn't on the list? What a crappy list!" comments there are. Some I agree with. Overall the list is pretty subjective, but I think most of the albums are worth a listen for educational purposed. There's lots of stereotyped "genre XXX stinks" in the comments, and the results do seem to have some bias, but it set me to thinking, how should such an album list be generated? Also what makes an album great?<br />
<br />
The best way to make a "greatest" list is to find a valid metric. Votes will end up subjective and show selection biases unless a HUGE sampling is taken. Album sales could be a good metric. Especially in my opinion, album sales over time. Some albums skyrocket then fall into obscurity. Other albums tell their value over time (i.e. the ramones have sold more albums after they broke up than when they were together. Just like in other mediums some painters/poets/writers are entirely unrecognized during their lifetime). So put some weighting to the number of sales per year based on how many years past the release. In reality its difficult to see the value of an album immediately after its release, at least for me, its the timelessness of an album that makes it valuable. Another metric could be the number of plays from tracks that are still happening on the radio, but that can also favor latest hits that will only hold nostalgic value in a few years. If a database of such information were available then these lists could be easily generated and reviewed to see if the results seem reasonable. Really it would be the outliers, the surprises, that would be fun to see.<br />
<br />
Finally I'd just like to explore what makes an album great. In the simplest form, a great album is appealing. Beyond that it starts getting really subjective. Mass appeal? The numbers suggested, and the RS list seem to favor this, the result being most selections coming from mainstream or pop music. That's ok, but you have to realize that. Another way to consider what makes an album great, its influence on other artists/music in general. Again, this will bias mainstream, because once broken into subgenres, the realm of influence is less (though not constricted arbitrarily) as country music artist are more likely to influence other country music artists. Also this is impossible to meter, though I'd love to hear suggestions. I won't take cited influences seriously as a metric, as I can't really sum up all the artists who have influenced my music, let alone how much each one had. Another thing that is unclear about album greatness, does one bad song make the album a flop? Does a couple of big hits make one great? If the whole thing doesn't have any continuity should it even be considered on the album level? Pink Floyd's Dark Side of the Moon is obviously a single work, and its great to be considered on the whole, but another one of my favorite albums is Smashmouth's Astrolounge. I just like every single song on there. Actually, the radio hit All-Star isn't that great though. Meh. But I can't really identify exactly why these albums are great. Part of its nostalgia, the sound, the lyrics, the emotion I feel when listening... They're just appealing.<br />
<br />
If I think about some of my favorite albums, some jazz-rock like Chicago II, or deep funk like the Funky 16 Corners, Neil Diamond, or ska, or Jazz, its pretty hard to come up with a metric or anything that would be able to encompass everything I like and declare them great. Maybe I just like some lame albums. Maybe music is subjective by nature and should be enjoyed, considered, embraced, rejected etc. on an individual level. Maybe every such list is more objectively telling about the list creator than the music cited.<br />
<br />
So in summary, this list, and any similar list of the greatest albums, is a valid thing to consider and enjoy, with a grain of salt. Its been a great avenue for me to listen to some stuff I never would have otherwise. Its a subjective field, since music is largely created with emotion, intended to illicit emotion, how could it not be individually received and acclaimed? As a result, its hard not to take music personally, but lets try to realize other people should not be clones of ourselves. Anyhow, try new stuff with an open mind, don't be a hater, but its ok to find something you don't like. Spencerhttp://www.blogger.com/profile/12246776537628500183noreply@blogger.com0tag:blogger.com,1999:blog-2309349868071255294.post-29517755323969747932013-07-13T21:46:00.001-07:002014-06-30T13:57:26.187-07:00The Infamous Cellular Automaton SynthIt started with the <a href="http://www.ioccc.org/" target="_blank">obfuscated C competition</a>... and a <a href="http://www.youtube.com/watch?v=A8B5MbHPlH0" target="_blank">youtube video</a>. Well, no it really started with learning about Conway's Game of Life. Err.. It started with a <a href="http://www.youtube.com/watch?v=a35XINnYFtA" target="_blank">video about a 13 year old girl who was implementing Conway's Game of Life on the raspberry pi</a>... Ahem. I became interested in cellular automata and especially linking it to music. I originally tried to emulate this one liner that was getting passed around on IRC: <br />
<pre><div class="code_block" id="l95">
<span class="p">
echo "main(i){for(i=0;;i++)putchar(((i*(i>>8|i>>9)&46&i>>8))^(i&i>>13|i>>6));}" | gcc -x c - && ./a.out | aplay</span></div>
</pre>
I tried to implement my own version that used an automaton to do the same sort of thing (I'll let you scratch your head on what it does, or just copy and paste it into your terminal). It took me a while to get it working, but I eventually got an automaton operating successfully on a torus. I then went and obfuscated it thinking of entering it into the IOCC. It looks pretty good: <br />
<pre><div class="code_block" id="l95">
<span class="p">
echo "main(_i_,i,_,i_i){i_i=30;for(i=7;;i=_i_<<1|_i_>>7)
{_i_=0;for(_=0;_<8;_++)_i_|=((i_i&1<<((i>>_|i<<(1+~_&7))&7))>0)
<<_;putchar(_i_);}}" | gcc -x c - && ./a.out | aplay</span></div>
</pre>
Got it? Simple enough. So where did I go with this?<br />
<br />
<a href="http://www.blogger.com/null" name="more"></a><br />
<a name='more'></a><br />
<br />
It works. Well. It compiles. Problem is it sounded awful. I was hoping for some really great sound, but just got an annoying buzz. Oh well. I let it rest there.<br />
<br />
Then several months later, I contributed a filter to the LV2 Midi filters project using a normal random distribution approximation rather than the uniform distribution method employed initially. I had already looked at the source of many plugins but never got the nerve to jump into developing, but this broke down the wall.<br />
<br />
I knew I wanted to make a synth. I'm not sure how exactly I came to decide the way to do it, but eventually I decided to use the cellular automaton code I'd developed in it. I thought for a while, whether each cell would be another note or what, but I eventually settled on making an additive synth with each cell being a harmonic, that sounds when the cell is alive and is silent when the cell is dead. This works pretty well, especially to get pulsating sounds, though the synth isn't limited to those sort of things. The fundamental is always on, and you can easily create a patch that all the harmonics are on or off and don't change. If that doesn't sound so easy, then read on.<br />
<br />
An elementary cell automaton basically is a sort of state machine where a number of cells will change in the next time step according to their current state and that of their neighbors. Since this is operating on a toroid this means that the edge cell wraps and is neighbors with the other edge. We use 16 cells, though actually any number is doable by the code. How cells respond is determined by a rule, which is basically an 8 bit number (0-255) where each bit value corresponds to the next state and the bit position is the current state. So for example rule 30 is hex value 0x1E or binary 00011110b. Which means the cells operate as follows:<br />
<span style="font-family: "Courier New",Courier,monospace;"><br /></span>
<span style="font-family: "Courier New",Courier,monospace;">111 110 101 100 011 010 001 000</span><br />
<span style="font-family: "Courier New",Courier,monospace;"> 0 0 0 1 1 1 1 0 </span><br />
<br />
So a cell sequence of three cells in a row alive, the middle one will die for the next state. If the inital conditon is 0x0100 or 0000000100000000b (16 cells middle one alive) then the sequence looks like:<br />
<span style="font-family: "Courier New",Courier,monospace;"> 0 <br /> 000 <br /> 00 0 <br /> 00 0000 <br /> 00 0 0 <br /> 00 0000 000 <br /> 00 0 0 0 <br />00 0000 000000 <br />0 0 000 <br />00000 00 0 0<br /> 0 0000 00<br />0 00 0 000 <br />00 00 00 00 <br />0 000 000 0 0 0<br /> 0 0 000 0 0<br />0000 00000 0 0<br /> 0 0 00 0<br />0 000 00 0 0<br /> 0 00 000 0000<br /> 0 0 000 000 <br />00 0 0 000 0 <br />0 0 0000 00000</span><br />
where a 0 is a cell and blank is no cell. I cut it off of course but it will go on indefinitely unless it reaches a stable state (i.e all cells are dead and the last bit of the rule is zero). Wikipedia has a <a href="http://en.wikipedia.org/wiki/Cellular_automaton" target="_blank">nice article with some great graphics from elementary cell automata</a>, though the images are generated from random initial conditions and not on a torus.<br />
<br />
If you want to do some great sound design using the automaton, I recommend getting a pencil and paper and writing out the states and filling in what you want the next state to do:<br />
<br />
<span style="font-family: "Courier New",Courier,monospace;">111 110 101 100 011 010 001 000</span><br />
<span style="font-family: "Courier New",Courier,monospace;"> 1 1 1 1 0 0 0 0 </span><br />
<br />
This rule is 0xF0 = 240. In this example I made it so each cell becomes the state its left neighbor is in. Coupled with an input like 0xAFAA you get diagonal stripes going down:<br />
<span style="font-family: "Courier New",Courier,monospace;">0 0 00000 0 0 0 <br /> 0 0 00000 0 0 0<br />0 0 0 00000 0 0 <br /> 0 0 0 00000 0 0<br />0 0 0 0 00000 0 <br /> 0 0 0 0 00000 0<br />0 0 0 0 0 00000 <br /> 0 0 0 0 0 00000<br />0 0 0 0 0 0 0000<br />00 0 0 0 0 0 000<br />000 0 0 0 0 0 00<br />0000 0 0 0 0 0 0<br />00000 0 0 0 0 0 <br /> 00000 0 0 0 0 0<br />0 00000 0 0 0 0 <br /> 0 00000 0 0 0 0</span><br />
This gives an interesting phasor sort of sound. These sequences shown are generated by the rule utility provided with the source of Infamous Plugins, which will help you in your explorations. I used this methodology to make all the presets.<br />
<br />
The synth has no filter, though filter like effects can be attained through adjusting the number of harmonics played. If you use this parameter to turn off the harmonics because you don't want that sort of sound I reccommend you also turn the cell lifetime up to the max because the cells are calculated even when they are not going to be played (this way you can "open up" the number of harmonics like a filter for some nice effects). Nothing bad happens if you don't, just wasted CPU cycles.<br />
<br />
You can also use the lfos for some FM synthesis or to get some good drum sounds like I do on the kick drum of the demo. The demo is entirely made with the Infamous Cellular Automaton Synth plugin. Have a listen and feel free to comment, make suggestions, ask questions, etc. Oh ya and <a href="https://sourceforge.net/projects/infamousplugins/" target="_blank">download the source and install it</a>!<br />
<br />
*note: I accidentally deleted this post, but luckily was able to recover it from the linux audio planet! whew.Spencerhttp://www.blogger.com/profile/12246776537628500183noreply@blogger.com2