I'm part of a band that has an album out now. I know, right? (links: excuse-for-a-site, amazon, itunes).
I wanted to put up all the songs on the site, but seems like there's a little dissonance in the band whether this is a good idea. Plan B: 30s samples. Like the bigwigs do on Amazon and iTunes.
But while their samples are random, a band can do a better job in picking parts that are representative of the overall sound. I though - let me pick my solo stuff only as an exercise. So there: Anaconda Limousine: the guitar parts.
I wanted to use command-line ffmpeg, of course, because all music software is like Photoshop to me, just can't figure out what's going on with so much UI. Turned out I needed sox too.
And then I want to use HTML5 Audio to play the samples.
I thought: an audio sprite would be a good idea, put all samples in one file, then JS can update the UI depending on which sample is playing. And I thought might be neat to have the JS turn the volume up and down to fade in/out the samples, like iTunes does. Turns out sox
is doing this so nicely, that I let it do it.
Samples
I started by listening to the songs and taking notes with song #, start and end.
var slices = [ {song: 1, start: 8, end: 21}, {song: 1, start: 301, end: 323}, // from 3:01 to 3:23 {song: 1, start: 405, end: 0}, // 0 means till the end {song: 2, start: 0, end: 30}, {song: 2, start: 305, end: 318}, {song: 2, start: 330, end: 0}, {song: 3, start: 0, end: 20}, {song: 3, start: 333, end: 0}, {song: 4, start: 303, end: 0}, {song: 5, start: 0, end: 20}, {song: 5, start: 300, end: 333}, {song: 7, start: 0, end: 20}, {song: 7, start: 340, end: 0}, {song: 8, start: 0, end: 25}, {song: 8, start: 313, end: 0}, {song: 9, start: 155, end: 239}, {song: 9, start: 350, end: 0} ];
Start 0 means start from the beginning of the song, end 0 means go to the end.
The time format is optimized for easy typing (I was walking, typing in Notes app on the iPhone). Turned out I need to convert the times to seconds:
function secs(num) { if (num <= 60) { return 1 * num } num += ''; return num[0] * 60 + num[1] * 10 + num[2] * 1; }
And I need album meta data too, with name of the song, filename and duration:
var songs = [ {name: "Virus", fname: "01-virus", duration: 436}, {name: "Yesterday", fname: "02-yesterday", duration: 346}, {name: "All for you", fname: "03-all4u", duration: 404}, {name: "Damage", fname: "04-damage", duration: 333}, {name: "Everyday", fname: "05-everyday", duration: 444}, {name: "Girl of mine", fname: "06-girlomine", duration: 338}, {name: "Fool on the hill", fname: "07-fool", duration: 413}, {name: "Faultline", fname: "08-faultline", duration: 347}, {name: "Parting is such sweet sorrow", fname: "09-parting", duration: 420} ];
Ripping the album
In the interest of quality I wanted to work with WAV and then encode in m4a, ogg and mp3.
On TuneCore.com there's a nice step-by-step instruction how to rip a CD to WAV in iTunes, because it uses mp3 by default.
So then I had the files:
01-virus.wav 02-yesterday.wav 03-all4u.wav 04-damage.wav 05-everyday.wav 06-girl.wav 07-fool.wav 08-faultline.wav 09-parting.wav
Slicing and fading
I used ffmpeg to do the slicing, like for example the first sample:
$ ffmpeg -i 01-virus.wav -ss 5 -t 20 ff-0.wav
-ss
is start time and -t
is duration. As you see instead of starting at 8 seconds (as described in the samples array) I start earlier. This is to give some more music for fade in/out.
For fade in/out I used sox
. I didn't know this awesome command line tool existed, but found out while searching how to combine wav files (I know for mp3 you can just `cat`
)
I don't want to fade in when the sample starts at the beginning. Or fade out then the sample happens to end at the end of the song. sox takes fade-in duration (or 0 for no fade in), stop time (0 for till the end of the file/sample) and fade out duration (again 0 is no fade out)
I used 3 secods fade in, 4 fade out. The sox commands are one of:
$ sox in.wav out.wav fade 3 0 0 $ sox in.wav out.wav fade 3 0 4 $ sox in.wav out.wav fade 0 0 4
And since I have all the configs in JS, JS makes perfect sense to generate the commands. Hope you can make sense of the comments:
var fadein = 3; var fadeout = 4;
var merge = ['sox']; slices.forEach(function(s, index){ var ff = ['ffmpeg -i']; ff.push(songs[s.song - 1].fname + '.wav'); // in file ff.push('-ss'); ff.push(s.start ? secs(s.start) - fadein : 0); // start of the slice ff.push('-t'); ff.push(!s.end ? 1000 : secs(s.end) - secs(s.start) + fadein + fadeout); // end slice ff.push('ff-' + index + '.wav'); // out file var sox = ['sox']; sox.push('ff-' + index + '.wav'); // in file sox.push('s-' + index + '.wav'); // out file sox.push('fade'); sox.push(s.start ? fadein : 0); // fade in, unless it;s the beginning of the song sox.push(0); // till the end of the slice sox.push(s.end ? fadeout : 0); // fade out unless it's The End console.log(ff.join(' ')); console.log(sox.join(' ')); merge.push('s-' + index + '.wav'); }); merge.push('_.wav'); console.log(merge.join(' '));
Running this gives me a bunch of commands:
ffmpeg -i 01-virus.wav -ss 5 -t 20 ff-0.wav sox ff-0.wav s-0.wav fade 3 0 4 ffmpeg -i 01-virus.wav -ss 178 -t 29 ff-1.wav sox ff-1.wav s-1.wav fade 3 0 4 [....] sox s-0.wav s-1.wav s-2.wav s-3.wav [...] s-16.wav _.wav
2 for each sample (slice and fade) and one last one to merge all faded results. Nothing left to do but run the generated commands.
Save for Web
Final step is to convert the result _.wav
to a web-ready format: m4a, ogg or mp3:
$ ffmpeg -i _.wav _.m4a $ ffmpeg -i _.wav _.mp3 $ ffmpeg -i _.wav -acodec vorbis -aq 60 -strict experimental _.ogg
Turn it up!
Enjoy Anaconda Limousine: The Guitar Parts (ogg, m4a or mp3) with all samples in one file.
And come back later for the JS player part.
See you!
Comments? Feedback? Find me on Twitter, Mastodon, Bluesky, LinkedIn, Threads