`showcqt’ (crop between A0(21) and C8(108))

Watch on youtube.com
doc

https://ffmpeg.org/ffmpeg-filters.html#showcqt

  • MIDI note number to freq: \(440 \times 2^{(nn - 69) / 12}\)

  • freq to MIDI note number: \(69 + 12 \times \log_2\left(f / 440\right)\)

(see MIDI tuning standard)

If you have Python, you can calculate these like this:

>>> import math
>>>
>>> _KTAB = [
...     "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"
...     ]
>>>
>>> def nn2scale(nn):
...     return _KTAB[int(nn) % 12], int(nn / 12) - 1
...
>>> def ok2nn(k, oct):
...     return (oct + 1) * 12 + _KTAB.index(k)
...
>>> def nn2freq(nn):
...     return math.pow(2, (nn - 69) / 12.) * 440
...
>>> def freq2nn(f):
...    return 69 + 12 * math.log(f / 440., 2)
...
>>> ok2nn("A", 0)
21
>>> ok2nn("C", 8)
108
>>> nn2scale(108)
('C', 8)
>>> nn2freq(ok2nn("A", 0))
27.5
>>> nn2freq(ok2nn("C", 8))
4186.009044809578

Default value of basefreq is 20.01523126408007475, which is frequency 50 cents below E0, and default value of endfreq is 20495.59681441799654, which is frequency 50 cents above D#10. So, to crop between A0(21) and C8(108), you must calculate the range for cropping using these infomations, for example (with Python):

>>> import math
>>>
>>> _KTAB = [
...     "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"
...     ]
>>>
>>> def nn2scale(nn):
...     return _KTAB[int(nn) % 12], int(nn / 12) - 1
...
>>> def ok2nn(k, oct):
...     return (oct + 1) * 12 + _KTAB.index(k)
...
>>> def nn2freq(nn):
...     return math.pow(2, (nn - 69) / 12.) * 440
...
>>> def calc_crop(kb, octb, ke, octe):
...     bfd = 20.01523126408007475
...     efd = 20495.59681441799654
...     bf = nn2freq(ok2nn(kb, octb))
...     ef = nn2freq(ok2nn(ke, octe))
...     #
...     orig_w_r = math.log(efd, 2) - math.log(bfd, 2)
...     crop_x = int(1920 * (math.log(bf, 2) - math.log(bfd, 2)) / orig_w_r)
...     crop_w = int(1920 * (math.log(ef, 2) - math.log(bf, 2)) / orig_w_r)
...     return crop_w, crop_x
...
>>> calc_crop("A", 0, "C", 8)
(1392, 88)

Thus, script for “crop between A0(21) and C8(108)” is like this:

#! /bin/sh
base="Air on the G String (from Orchestral Suite no. 3, BWV 1068)"

# orig (20.01523126408007475Hz to 20495.59681441799654Hz)
# crop between A0(21) and C8(108)
ffmpeg \
  -y -i "${base}.mp3" \
  -filter_complex "
[0:a]showcqt=s=1920x1080,crop=1392:1080:88:0,setsar=1[v]" \
    -map '[v]' -map '0:a' -c:a copy "${base}"_out.mp4

Originally, you can pass “basefreq” and “endfreq” to “showfreq”. However, “showfreq” will immediately give up drawing axes if you passes a value slightly different from the default. Therefore, if you do not want to give up drawing axes, you need to “crop” as we did here, or you need to do some fine control, such as passing your own axes to “axisfile”.