Главная » Хабрахабр » [Перевод] Воссоздание звука Deep Note от THX

[Перевод] Воссоздание звука Deep Note от THX

Если вы когда-нибудь посещали кинотеатр, то наверняка слышали Deep Note — звуковой трейдмарк компании THX. Это один из первых звуков, который раздаётся в начале трейлеров в залах, сертифицированных THX. Мне всегда нравилось его узнаваемое крещендо, начиная с жуткого смешения нот и заканчивая ярким и грандиозным финалом (звук). Какая услада для уха!

Меня глубоко тронула его история, которой хочу поделиться с вами. Вчера (вероятно) без всяких причин меня заинтересовало происхождение этого звука, и я провёл небольшое исследование. Вот ссылка на сообщение. Затем мы продолжим — и сами создадим этот звук, готовьте ножницы и клей!
Лучший источник информации о звуке, который я смог найти — по-моему, это его полная электроакустическая композиция, опубликованная в отличном блоге Music Thing Blog в 2005 году.

Некоторые факты о звуке:

  • Его создал д-р Джеймс Энди Мурер в 1982 году.
  • В один из дней в истории его проигрывали 4000 раз в день, почти каждые 20 секунд! Цитата доктора Мурера:

    Это может быть правдой или нет, но звучит круто!» «Хотел бы сказать, что звук THX — самое популярное произведение компьютерной музыки в мире.

  • Он создан на компьютере ASP (Audio Signal Processor), способном синтезировать звуки в реальном времени.
  • Программа из 20 000 строк кода на C генерировала данные для воспроизведения на ASP. Сгенерированные данные состояли из 250 000 строк, которые обрабатывались на ASP.
  • Осцилляторы голосов используют в качестве сигнала оцифрованный виолончельный тон. Доктор Мурер вспоминает, что в сэмпле было около 12 гармоник. ASP мог запустить 30 таких осцилляторов в реальном времени (для сравнения, мой ноутбук прямо сейчас может обрабатывать без сбоев более 1000 таких).
  • Сам звук защищён авторским правом, но вот проблема: код доктора Мурера полагается на генераторы случайных чисел (генеративный процесс) и каждый раз звук несколько отличается. Поэтому я не думаю, что можно с уверенностью сказать, что сам процесс является или может быть «защищён авторским правом». Сам звук, да, конкретный сэмпл защищён.
  • Звук дебютировал в трейлере THX «Возвращение джедая» перед его премьерой в 1983 году.
  • Генеративные характеристики процесса в какой-то момент стали проблематичными. После выхода «Возвращения джедая» оригинальная запись Deep Note была утеряна. Доктор Мурер воссоздал произведение для компании, но те постоянно жаловались, что оно звучит не так, как оригинал. В конце концов, оригинальную запись нашли и сохранили в безопасном месте.
  • Dr. Dre попросил разрешения на использование сэмпла в своей музыке, но ему отказали. Он всё равно использовал его и получил судебный иск.
  • В произведении Metastaseis Яниса Ксенакиса (1954) есть очень похожее вступительное крещендо (как и в других произведениях различных композиторов). Но оно начинается с одного тона и завершается полустройным тональным кластером вместо полностью созвучного, как в Deep Note. Звукозапись из патентной заявки можно прослушать здесь.

Обязательно послушайте звук, потому что при воссоздании Deep Note мы будем обращаться к этой конкретной записи.

Вот некоторые технические/теоретические факты, прежде чем приступить к синтезу звука:

  • Моё наблюдение: на оригинальной записи с сайта патентного ведомства основной тон находтся между D и Eb, а в более новых вариантах фундаментальное значение между E и F. Мы будем использовать оригинальную константу D/Eb. Новые варианты обычно короче, если не ошибаюсь. Очевидно, что мне больше нравится тот вариант, что подавали в патентное ведомство.
  • По словам доктора Мурера (и также подтверждено моими ушами), фрагмент начинается с осцилляторов, настроенных на случайные частоты между 200 Гц и 400 Гц. Но осцилляторы не просто гудят — их частоты модулируются случайным образом, и они используют сглаживающие фильтры для сглаживания случайных переходов тонов. Это продолжается до начала крещендо.
  • Внутри крещендо и в конце звукового фрагмента рандомизаторы всё ещё модулируют частоты осцилляторов, поэтому ни один из них не является стабильным в какой-то момент времени. Но диапазон случайной развёртки настолько узкий, что просто добавляет естественное/хоровое звучание.
  • Доктор Мурер вспоминает, что в спектре оцифрованного звука виолончели было около 12 внятных гармоник.
  • Насколько мне известно, значения для генератора (которые использовались для получения авторских прав) в письменном виде так никогда и не публиковались. Д-р Мурер говорит, что может записать их, если мы получим разрешение от THX. Но я думаю, что это необязательно для воссоздания звука.
  • Звук в финале (технически не аккорд) — на мой слух, просто сложение октав основного тона. Так что при воссоздании начнём со случайно настроенных (между 200 и 400 Гц) осцилляторов, сделаем более-менее сложную развёртку и завершим наложением октав на основной тон между низкими D/Eb.

Итак, приступим. Здесь мой рабочий инструмент — SuperCollider. Начнём с простого сэмпла. В качестве источника хочу использовать пилообразную волну, у неё богатый и гармонический спектр из чётных и нечётных компонентов. Позже я планирую отфильтровать вершины. Вот фрагмент из начальной части кода:

//30 осцилляторов, распределённых по стереополю
(
!numVoices; Mix ({|numTone| var freq = fundamentals[numTone]; Pan2.ar ( Saw.ar(freq), rrand(-0.5, 0.5), //stereo placement of voices numVoices.reciprocal //scale the amplitude of each voice ) }!numVoices);
}.play;
)

Я выбрал 30 осцилляторов для генерации звука, в соответствии с возможностями компьютера ASP, как рассказывал д-р Мурер. Создал массив из 30 случайных частот между 200 и 400 Гц, распределил их случайным образом по стереополю с помощью Pan2.ar с аргументом rrand(-0.5, 0.5), назначил частоты пилообразным осцилляторам (30 экземпляров). Вот как это звучит.

Хочется добавить этот эффект для более органичного звучания. Если изучить информацию от д-ра Мурера и/или внимательно прислушаться к оригинальному фрагменту, то можно услышать, что частоты осцилляторов случайным образом смещаются вверх и вниз. Такое можно реализовать сортировкой наших случайно сгенерированных частот с присвоением LFNoise2 (который генерирует квадратично интерполированные случайные значения) аргументов mul по порядку внутри нашего макроса Mix. Частотная шкала логарифмическая, поэтому на низких частотах должны быть более узкие диапазоны колебаний, чем на более высоких. И я ещё добавил для осцилляторов фильтр нижних частот с частотой среза по пятикратной частоте осциллятора и умеренным 1/q:

//добавление к частотам случайных колебаний, сортировка, фильтр низких частот
(
{ var numVoices = 30; //sorting to get high freqs at top var fundamentals = ({rrand(200.0, 400.0)}!numVoices).sort; Mix ({|numTone| //fundamentals are sorted, so higher frequencies drift more. var freq = fundamentals[numTone] + LFNoise2.kr(0.5, 3 * (numTone + 1)); Pan2.ar ( BLowPass.ar(Saw.ar(freq), freq * 5, 0.5), rrand(-0.5, 0.5), numVoices.reciprocal ) }!numVoices);
}.play;
)

Вот как звучит образец с последними правками.

Чтобы реализовать развёртку, сначала нужно определить окончательные частоты для каждого осциллятора. Это уже выглядит хорошей отправной точкой, поэтому приступим к реализации развёртки, сначала очень грубо. Основной тон должен находиться между низкими D и Eb, так что средней частотой для этого тона будет 14,5 (0 — это C, отсчитывая хроматически, без первой октавы). Это не очень просто, но и не очень сложно. На слух я выбрал первые 6 октав. Так что для 30 осцилляторов переводим случайные частоты между 200 и 400 Гц в значение 14,5 и соответствующие октавы. Итак, конечный массив частот получается таким:

(numVoices.collect({|nv| (nv/(numVoices/6)).round * 12; }) + 14.5).midicps;

Будем использовать развёртку от 0 до 1. Случайные частоты умножаются на значение (1 − развёртка), а целевые частоты умножаются на саму развёртку. Поэтому когда развёртка равна 0 (начало), то частота будет случайной. Когда развёртка 0,5, то получается ((рандом + целевая частота) / 2), а когда равна 1, то частота и будет конечным значением. Вот модифицированный код:

//создаём начальную развёртку (грубо), создаём финальные частоты
(
{
var numVoices = 30;
var fundamentals = ({rrand(200.0, 400.0)}!numVoices).sort;
var finalPitches = (numVoices.collect({|nv| (nv/(numVoices/6)).round * 12; }) + 14.5).midicps;
var sweepEnv = EnvGen.kr(Env([0, 1], [13]));
Mix
({|numTone| var initRandomFreq = fundamentals[numTone] + LFNoise2.kr(0.5, 3 * (numTone + 1)); var destinationFreq = finalPitches[numTone]; var freq = ((1 - sweepEnv) * initRandomFreq) + (sweepEnv * destinationFreq); Pan2.ar ( BLowPass.ar(Saw.ar(freq), freq * 5, 0.5), rrand(-0.5, 0.5), numVoices.reciprocal //scale the amplitude of each voice )
}!numVoices);
}.play;
)

Звук здесь.

Она линейно повышается от 0 до 1, что не согласуется с оригинальной композицией. Как я уже говорил, это очень грубая развёртка. Мы это исправим, добавив случайное колебание на финальном этапе — так же, как делалось вначале, и это будет звучать гораздо более органично. Также вы могли заметить, что последние октавы звучат ужасно, потому что они настроены на идеальные октавы и сливаются друг с другом как базовые тона и овертоны.

Предыдущая была просто для пробы. Сначала нужно исправить общую формулу частотной развёртки. После этого происходит быстрая и экспоненциальная развёртка, которая уводит осцилляторы в конечные октавные интервалы. Если посмотрим на оригинал, то заметим, что в первые 5−6 секунд в звучании очень мало изменений. Вот вариант, который я выбрал:

sweepEnv = EnvGen.kr(Env([0, 0.1, 1], [5, 8], [2, 5]));

Здесь переход от 0 до 0,1 занимает 5 секунд, а переход от 0,1 до 1 занимает 8 секунд. Курватуры для этих сегментов установлены в 2 и 5. Позже послушаем, что получилось, но сначала надо исправить ещё окончательные интервалы. Как и раньше, добавим случайные колебания с LFNoise2, диапазон которого пропорционален конечной частоте осциллятора. Это сделает финал более органичным. Вот изменённый код:

//настройка развёртки и финального аккорда
(
{
var numVoices = 30;
var fundamentals = ({rrand(200.0, 400.0)}!numVoices).sort;
var finalPitches = (numVoices.collect({|nv| (nv/(numVoices/6)).round * 12; }) + 14.5).midicps;
var sweepEnv = EnvGen.kr(Env([0, 0.1, 1], [5, 8], [2, 5]));
Mix
({|numTone| var initRandomFreq = fundamentals[numTone] + LFNoise2.kr(0.5, 3 * (numTone + 1)); var destinationFreq = finalPitches[numTone] + LFNoise2.kr(0.1, (numTone / 4)); var freq = ((1 - sweepEnv) * initRandomFreq) + (sweepEnv * destinationFreq); Pan2.ar ( BLowPass.ar(Saw.ar(freq), freq * 8, 0.5), rrand(-0.5, 0.5), numVoices.reciprocal )
}!numVoices);
}.play;
)

Здесь я ещё подкорректировал частоту среза фильтра нижних частот на свой вкус. Мне нравится подправлять вещи, если результат не ухудшается… В любом случае, вот что получилось.

Нужно растянуть начало и ускорить финиш. Мне не очень нравится эта схема развёртки. Совершенно нет! Или подождите… разве обязательно реализовать одинаковую схему для всех осцилляторов? Ещё немного раздражают высокочастотные обертоны случайного пилообразного кластера, поэтому добавляем к общему результату фильтр нижних частот, отсечение которых контролируется глобальным «внешним» значением, не имеющим ничего общего со схемами осцилляторов. Каждый осциллятор должен иметь собственную схему с немного разными значениями времени и курватуры — я уверен, что это будет интереснее. Вот измененный код:

//кастомные схемы. Фильтр нижних частот в конце
(
{
var numVoices = 30;
var fundamentals = ({rrand(200.0, 400.0)}!numVoices).sort;
var finalPitches = (numVoices.collect({|nv| (nv/(numVoices/6)).round * 12; }) + 14.5).midicps;
var outerEnv = EnvGen.kr(Env([0, 0.1, 1], [8, 4], [2, 4]));
var snd = Mix
({|numTone| var initRandomFreq = fundamentals[numTone] + LFNoise2.kr(0.5, 3 * (numTone + 1)); var destinationFreq = finalPitches[numTone] + LFNoise2.kr(0.1, (numTone / 4)); var sweepEnv = EnvGen.kr( Env([0, rrand(0.1, 0.2), 1], [rrand(5.0, 6), rrand(8.0, 9)], [rrand(2.0, 3.0), rrand(4.0, 5.0)])); var freq = ((1 - sweepEnv) * initRandomFreq) + (sweepEnv * destinationFreq); Pan2.ar ( BLowPass.ar(Saw.ar(freq), freq * 8, 0.5), rrand(-0.5, 0.5), numVoices.reciprocal )
}!numVoices);
BLowPass.ar(snd, 2000 + (outerEnv * 18000), 0.5);
}.play;
)

Небольшое изменение сделало развёртку немного более интересной. Фильтр нижних частот на 2000 Гц помогает укротить начальный кластер. Вот как это звучит.

Помните, мы сортировали случайные осцилляторы в начале? Осталось ещё одно, что сделает процесс интереснее. Это добавит в крещендо больше «движения» и вполне согласуется с тем, как структурирован оригинальный фрагмент. Ну, теперь мы можем отсортировать их в обратном порядке и убедиться, что осцилляторы на более высоких случайных частотах в конечном итоге попадают в нижние голоса после крещендо, и наоборот. (О, я это сказал? Не уверен, что доктор Мурер запрограммировал его именно так, но на записи есть этот процесс, и она звучит круто, будь то случайный продукт генеративного процесса или специальный выбор. Таким образом, изменим порядок сортировки и структуру кода, чтобы зубья пилы с более высокими частотами попадали в более низкие голоса в финале, и наоборот. Если процесс предусматривает такой вариант, то это и есть выбор… или нет?).

Сейчас у всех голосов одинаковая амплитуда. Ещё одно: нужен более громкий бас. Поэтому соответствующим образом изменим аргумент mul для Pan2. Я хочу, чтобы низкие звуки звучали чуть громче и затухали пропорционально увеличению частоты. И я собираюсь добавить схему масштабирования амплитуды, которая будет плавно вступать в действие и исчезать к финалу, и освободиться от scserver. Повторно настроим частоты среза фильтров нижних частот для отдельных осцилляторов. Еще несколько численных настроек там и тут — и вот окончательный код:

//инвертирование init sort, более громкий бас, финальная схема громкости, несколько маленьких подкруток
(
{
var numVoices = 30;
var fundamentals = ({rrand(200.0, 400.0)}!numVoices).sort.reverse;
var finalPitches = (numVoices.collect({|nv| (nv/(numVoices/6)).round * 12; }) + 14.5).midicps;
var outerEnv = EnvGen.kr(Env([0, 0.1, 1], [8, 4], [2, 4]));
var ampEnvelope = EnvGen.kr(Env([0, 1, 1, 0], [3, 21, 3], [2, 0, -4]), doneAction: 2);
var snd = Mix
({|numTone| var initRandomFreq = fundamentals[numTone] + LFNoise2.kr(0.5, 6 * (numVoices - (numTone + 1))); var destinationFreq = finalPitches[numTone] + LFNoise2.kr(0.1, (numTone / 3)); var sweepEnv = EnvGen.kr( Env([0, rrand(0.1, 0.2), 1], [rrand(5.5, 6), rrand(8.5, 9)], [rrand(2.0, 3.0), rrand(4.0, 5.0)])); var freq = ((1 - sweepEnv) * initRandomFreq) + (sweepEnv * destinationFreq); Pan2.ar ( BLowPass.ar(Saw.ar(freq), freq * 6, 0.6), rrand(-0.5, 0.5), (1 - (1/(numTone + 1))) * 1.5 ) / numVoices
}!numVoices);
Limiter.ar(BLowPass.ar(snd, 2000 + (outerEnv * 18000), 0.5, (2 + outerEnv) * ampEnvelope));
}.play;
)

А вот и окончательная запись произведения.

Можете сравнить с оригиналом.

И конечно, её можно оптимизировать до смерти, меняя схемы, частоты, распределение, что угодно… тем не менее, я думаю, что это достойная попытка сохранить звуковое наследие. Да, это моя интерпретация. Хотелось бы услышать ваши комментарии и/или собственные попытки синтеза этого крещендо.

Да, и вот ещё одна вещь, которую я сделал ради удовольствия. Помните, я рассказывал, что для генерации оригинала понадобилось 20 000 строк кода на С. Я почти уверен, что доктору Муреру пришлось всё писать вручную, так что такая цифра неудивительна. Но вы знаете, в связи с популярностью твиттера мы пытаемся всё втиснуть в 140 символов кода. Для удовольствия я попытался воспроизвести основные элементы композиции в 140 символах кода. Думаю, что сэмпл ещё звучит круто, вот код (здесь с основным тоном F/E):

play{Mix({|k|k=k+1/2;2/k*Mix({|i|i=i+1;Blip.ar(i*XLine.kr(rand(2e2,4e2),87+LFNoise2.kr(2)*k,15),2,1/(i/a=XLine.kr(0.3,1,9))/9)}!9)}!40)!2*a}

И вот звук, который генерирует эта версия.

В одном документе — весь код с этой страницы для ваших экспериментов.

Удачного крещендо, друзья!


Оставить комментарий

Ваш email нигде не будет показан
Обязательные для заполнения поля помечены *

*

x

Ещё Hi-Tech Интересное!

Дайджест свежих материалов из мира фронтенда за последнюю неделю №338 (5 — 11 ноября 2018)

Предлагаем вашему вниманию подборку с ссылками на новые материалы из области фронтенда и около него.     Медиа    |    Веб-разработка    |    CSS    |    Javascript    |    Браузеры    |    Занимательное Медиа • Подкаст «Веб-стандарты», Выпуск №146: Веб-приложения на десктопе, безопасность и фронтопсы, Test262, Babel и Webpack, вопросы к HolyJS.• Подкаст «Frontend Weekend» #78 – ...

Расходы на Tesla

В закрытой группе Tesla Model 3 на Facebook (37,457 участников) один из её членов поднял интересную тему: Сколько на данный момент составили Ваши расходы с момента покупки автомобиля?» «Вопрос для всех владельцев Tesla. Всего написали уже более 100 комментариев примерно, ...