понедельник, 30 декабря 2024 г.

[life.audiophilia.diy] Итоги уходящего года в сфере бюджетной аудиофилии

Попробую подвести итоги уходящего года с точки зрения своего увлечения бюджетной аудиофилией. Сперва расскажу о том, что удалось попробовать и даже распробовать заново. А потом рискну описать в какой странной ситуации оказался.


Итак, начнем с побывавших в моих руках ЦАПах.

Дешевый свисток на CX31993. Пару лет назад у меня уже была аналогичная модель, но тогда не зашла. А сейчас мне в руки попал девайс без 3.5mm джека и для подключения наушников к нему потребовался свисток с Type-C. И вот на этот раз звук меня порадовал. Да, все простенько и бюджетненько, если сравнивать с гораздо более дорогими источниками. Да, с нейтральными наушниками получается плоско и скучно. Но если взять что-то басовитое, то очень даже ничего. Плюс поддержка гарнитуры, плюс самое низкое энергопотребление из виденных мной. В общем, слушать музыку исключительно в фоновом режиме в хороших наушниках с собственным характером и не переживать за расход батареи -- самое то.

VE Megatron. Мои впечатления полностью совпадают с тем, что было рассказано в обзоре от Soundcheck39: отличное качество звука и огромная мощность за смешные по аудиофильским меркам деньги. Правда, в моем случае Megatron не работает от моего смартфона, видимо не хватает питания, которое телефон выдает по USB. Ну и у меня нет (пока?) настолько высокоомных наушников, для которых потребовалась бы вся мощь Megatron-а -- даже 300омные вкладыши с LCP-диафрагмой приходится слушать на минимальной громкости. Так что всю прелесть Megatron-а еще не оценил.

Hiby FC6. Просто лучшее, что побывало у меня в руках. Знаю, что есть и отрицательные отзывы, но мне нравится то, что слышу. Отдельный вопрос "а стоит ли он своих денег в сравнении с конкурентами?" Но у меня нет на него ответа, т.к. я на него собственных денег не тратил. И, наверное, за свои кровные не купил бы.

Главная проблема с FC6 в том, что в реальности он прожорливый -- по моим замерам 0.14A на 5V. Что сравнимо с Colorfly CDA M1, который выжирал более 10% аккумулятора за час. А это очень много для меня. Так что если много слушать FC6, то телефон приходится заряжать каждый день 🙁


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

14.2mm от Huayunxin на 16 ом. Отличные по техничности динамики с шикарной серединой, отменной 3-х мерностью, но с проваленными самыми низкими частотами (по ощущениям, все что ниже 50Гц играет очень тихо). Как попробовал их, так и использовал на ежедневной основе месяца полтора. Пока не возникло ощущение, что звук детальный, приятный по тоналке, с отличными атаками... Но какой-то пресный. Взял после них более басовитые модели и понял в чем дело: самых-самых низких НЧ то и нет. Осознал это не сразу, но когда осознал, то это начало преследовать, поэтому редко их слушаю. Так что любителям хороших НЧ не советую. А вот ценителям светлой подачи вполне может зайти. Главная проблема -- это найти корпуса для динамиков такого диаметра, шеллы на 14.2mm редкая штука на Aliexpress, поэтому я в качестве доноров использовал какие-то совсем дешевые наушники за один или полтора доллара.

15.4mm динамики на 18 ом с "рупорной" (horn) композитной диафрагмой. Скажу так: бывают динамики для выслушивания деталек на ВЧ, а это динамики для выслушивания деталей на НЧ. Сильный упор на НЧ и СЧ, высокие отведены далеко назад, хотя с сохранением детальности, но эти самые детали на ВЧ нужно очень и очень тщательно выслушивать и даже разыскивать. Зато НЧ мощные, глубокие и ни капли не ватные. В общем, динамики для настоящих басхэдов и ВЧ-фобов. Стоят вменяемых денег, плюс идут сразу с MX500 корпусами.

14.2mm динамики на 32 ома с пометкой 6H. Внезапное и приятное открытие. Стоят недорого, а звучат раза в два-три дороже. По крайней мере я для себя отличия этих динамиков от более дорогих с LCP и DLC диафрагмами слышу, но не уверен, оправдывают ли эти отличия разницу в цене. По звучанию у них нет уклона ни в светлую, ни в темную сторону, хотя они чуть потемнее и пожирнее, чем DLC. Все так, как мне бы и хотелось. ИМХО, просто отличная модель за свои деньги, а особенно хороши будут для людей с небольшими ушами, которым вкладыши с 15.4-мм динамиками носить не удобно.

Ну и повторно открыл для себя динамики, про которые уже неоднократно упоминал, но не грех сказать о них еще раз. Вот эти, с синим лаком. Они есть в разных магазинах по плюс-минус одной цене, ссылку даю на магазин в котором брал их в последний раз. Как раз с них и началось мое увлечение самосборными вкладышами. И временами жалею, что на них не остановился, сэкономил бы себе кучу времени, нервов и денег, но при этом не потерял бы ничего в удовольствии от прослушивании музыки.


Пару слов о корпусах и кабелях.

У меня побывало штук пять или шесть разных типов корпусов для динамиков размером 15.4mm. Самыми удобными в итоге оказались вот эти металлические корпуса. Причем, как мне показалось, они делают звук более глубоким и басовитым по сравнению с деревянными и пластиковыми корпусами. На втором месте с большим отрывом от всего остального классические черные MX500.

Но с корпусами MX500 нужно быть внимательнее. Черные, которые попадали мне в руки, более менее одинаковые, с достаточно широким каналом для кабеля. А вот прозрачные оказывались на доли миллиметра шире и у них кабель-канал более узкий, поэтому не каждый провод в них можно запихнуть, тогда как в классические черные MX500 корпуса такой же провод входит без проблем.

И вот о кабелях: за год переделал/починил несколько пар наушников используя беспонтовые кабеля из магазина 3C-Accessories Store и пришел к выводу, что сменные кабеля от NiceHCK, OpenHeart или ivipQ, за $10 или $20, -- это хорошо, конечно же, но и простой кабель за $4 делает свое дело ничуть не хуже.

Ну и в повседневной жизни кабель с простым 3.5мм джеком оказывается гораздо практичнее, чем в 4.4мм балансным 🙁


А теперь о неожиданном состоянии, в котором оказался после того, как попользовался Hiby FC6 с месяц или около того.

Перестал понимать какие динамики звучат лучше, а какие хуже.

Если раньше брал новые динамики и слышал, что где-то они дают чуть больше, чем предыдущие, то понимал, что эти динамики звучат лучше. Например, более хлесткий и менее бубнящий бас. Это же хорошо, значит динамики с таким более хлестким басом лучше. А если у новых динамиков плюс к басу еще и деталей на высоких больше, то это же еще лучше.

Тогда как с Hiby FC6 практически все, что у меня есть в наличии, звучит круто.

Да, звучит по разному.

Если у динамиков нет НЧ, то FC6 не совершит магию и не возьмет НЧ ниоткуда. А если ВЧ задвинуты далеко назад, то вперед их FC6 не выдвинет.

Но все равно наушники звучат круто. По разному, но круто.

И в итоге я перестал понимать что лучше, а что хуже.

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

И вот теперь как-то с этим нужно жить. Раньше были поиски лучшего звука. Теперь же непонятно что искать, т.к. уже имеющееся уже звучит 🙂

Поэтому теперь часто слушаю музыку просто с телефона, без каких-либо свистков. Чего со мной не было уже года два. Удивительно.

Думаю, что единственное, что еще хочется попробовать -- это нормальный плеер с Android-ом. Что-то вроде Tempotec V6 или уровнем повыше. Чтобы можно было и файлы с SD-карты, и Yandex.Music, и из Telegram, и из VK. Как раз то, что дает смартфон, но чтобы качеством уровня FC6.

среда, 25 декабря 2024 г.

[prog.c++.sobjectizer] Улыбнуло. А как прореагировать иначе даже не знаю...

Найдено на просторах Интернета:

"Выглядит монстровито", да.

Ирония в том, что SObjectizer -- это минималистичные (местами даже примитивные) реализации только тех фич, которые были нужны нам самим или о которых у нас спрашивали. Т.е. может быть и хотелось бы еще меньше, но не получается.

Кстати говоря, вчера в рабочей ветке при расширении одной из фич довелось задействовать совсем уж редкую штуку -- layer-ы, которые когда-то были нам нужны, но в последнее время нами практически не применялись. А тут раз, и потребовалась.
Внезапно (tm), ага 🤓

PS. Что радует, так это то, что люди знают про SObjectizer и делятся ссылками. На данный момент это самое лучшее подспорье для SObjectizer-а -- когда про него рассказывают и когда на него дают ссылки. Это именно то, что больше всего нам, как разработчикам, нужно. Большое спасибо за такие рассказы и за такие рекомендации.

понедельник, 23 декабря 2024 г.

[prog.c++] Возвращаясь к теме std::vector-а с назначаемой пользователем политикой роста

Вернусь к теме, поднятой пару месяцев назад: В std::vector не хватает вот какой штуки... Речь там шла о том, что хорошо было бы в std::vector иметь возможность управлять политикой роста объема вектора когда он полностью заполняется.

Часть комментариев к этой заметке (не только в блоггере, но и в других соцсетях) произвела на меня впечатление "ты и сам дурак, и хочешь странного, и вообще в std::vector все сделано зашибись, а если тебе чего-то не хватает, то сделай сам" (наверняка подразумевалось совсем другое, но я, как художник, которого каждый может обидеть, воспринял именно так, прошу понять и простить).

Ну и сделал в итоге, тогда же, в октябре. А вот возможность и время поделится впечатлениям нашлась только сейчас 🙁 Так что дико извиняюсь, что не по горячим следам, но лучше уж поздно, чем никогда.

Итак, был сделан шаблон, который параметризуется несколькими параметрами: исходный тип вектора (например, std::vector или folly::fbvector), типом хранящихся в векторе значений, типом политики роста объема вектора, типом аллокатора (по дефолту std::allocator).

Этот шаблон наследуется от типа исходного вектора. Т.е. если сказали, что нужно за основу брать std::vector, то vector_with_custom_growth_policy будет наследоваться от std::vector. Поэтому большинство методов из исходного типа вектора доступны благодаря наследованию.

Но часть методов реализуется в самом vector_with_custom_growth_policy. Это такие методы как: emplace_back, push_back (две штуки), emplace, insert (две штуки, обе для одиночного элемента). При этом я даже не брался за insert, который получает подпоследовательность в виде [begin, end).

Получилось где-то порядка 180 строк с комментариями. При этом моя реализация предоставляет только базовую, а не строгую, гарантию безопасности исключений. Т.е. если во время insert-а выброшено исключение, то ничего не протечет, но не факт, что вектор вернется в предшествующее состояние. Вероятно, более сильную гарантию можно было бы прикрутить, если покурить бамбук подольше. Но нельзя же постоянно курить бамбук за счет работодателя 😉

Общие впечатления: изначально кажется, что все просто и понятно. Затем внезапно (тм) тратишь время и силы на то, чтобы поддержать вот такую вот простую конструкцию: v.insert(v.end(), v[0]);

Если кто-то не знал, то нормальная реализация std::vector-а должна это поддерживать. Т.е. когда в insert/push_back прилетает ссылка на объект, который уже лежит в векторе, то нужно проявлять осторожность, чтобы при реаллокации вектора не инвалидировать эту ссылку.

Кстати говоря, я слышал, что такой вопрос иногда задают на собеседованиях по C++. Так что если не знали, то можете заучить, вдруг пригодится.

При этом сил на то, чтобы поддержать версию insert(pos, first, last) и версию insert(pos, initializer_list) уже не хватило 🙁

А из того, что было сделано, у меня сложилось впечатление, что прикручивать кастомную политику роста к std::vector снаружи такое себе занятие, т.к. внутри вектора при переаллокации содержимого можно делать все это проще и эффективнее.

В общем, после такого эксперимента еще больше стало не хватать того, что в std::vector нельзя добавить еще один параметр шаблона, который бы определял как именно нужно расти при заполнении вектора.

PS. Показать реализацию не могу, т.к. делалось это все в рамках закрытого заказного проекта.

вторник, 17 декабря 2024 г.

[prog.thoughts] Программист: хороший, плохой, крутой. Что под этим вообще понимается?

Пост, можно сказать, в догонку к предыдущему. Решил порассуждать на вынесенную в заголовок тему, т.к. есть ощущение, что понятие "крутой программист" вряд ли как-то строго определено и каждый может понимать под этим что-то свое.

Но начну с определения "хороший программист". Хотя, полагаю, оно также нестрогое и допускает разные трактовки. Как по мне, хорошим можно считать программиста, который:

  • решает поставленную перед ним задачу (хотя и не обязательно "в лоб");
  • пишет работающий код;
  • этот работающий код оказывается протестированным;
  • этот работающий код содержит полезные комментарии;
  • этот работающий код не вызывает проблем при отладке, исправлениях и сопровождении;
  • этот работающий код был получен в приемлемые сроки, желательно в заранее оговоренные ;)
  • сделано это было по большей части самостоятельно, с минимально необходимыми обращениями за помощью/разъяснениями.

Соответственно, с плохим программистом все просто: это противоположность "хорошего программиста".

Плохой программист, решает вообще не ту задачу, написанный им код толком не работает (а то и не работает вовсе), в коде хрен разберешься, а изменить этот код сложнее, чем переписать. При этом плохой программист успевает задолбать всех вопросами "а как сделать вот это?" или "а правильно ли я делаю вот это?"

Так что, думаю, что с "хороший" и "плохой" все более-менее просто (хотя, т.к. это не количественные оценки, а качественные, то всегда остается вопрос того "а где же грань между хорошим и плохим?"). А вот что такое "крутой"?

С моей точки зрения, "крутой программист" -- это тот, кто дает результат в несколько раз лучше, чем просто "хороший программист". Т.е. решение он получает гораздо быстрее, его код понятнее и проще (в том числе и для последующих изменений), эффективнее и надежнее.

Как один из вполне себе работающих критериев: вот вы смотрите в код "крутого программиста" и не видите возможностей улучшить его. Тогда как условный "крутой программист" берет ваш код и улучшает, причем когда вы оцениваете результат, то сами удивляетесь, почему же вы этих путей улучшения не видели раньше?

Может показаться, что "крутой программист" -- это как "сын маминой подруги", понятие скорее вымышленное, нежели реальное. Но мне повезло с несколькими такими столкнуться, а с кем-то из них даже поработать. Так что нет, это не фантастика, они существуют.


К сказанному выше нужно добавить одно важное дополнение: по моим впечатлениям, никто не способен выдавать "идеальный" код всегда. Поэтому в любом более-менее объемном проекте, какие бы крутые программисты его не писали, всегда найдутся куски, которые написаны не то, чтобы "на отвали", но явно с меньшим тщанием, чем остальная часть проекта. Где-то это объясняется обычными факторами (недостаток времени или плохое самочувствие, накопленная усталость), где-то меньшей значимостью (куда исполнение доходит один раз из тысячи, при определенной фазе Луны), где-то фрагмент представляет из себя временную заглушку, до которой еще не дошли руки.

В общем, хочу сказать, что в любом проекте найдется если не откровенная какашечка, то точно кусок кода, за который будет стыдно. Поэтому не стоит увидев такой фрагмент сразу же начинать кричать "это говнокод, который наколхозили криворукие говнокодеры".


И еще одно важное дополнение: иногда для понимания красоты и качества кода требуется хорошие знания как самого языка программирования, так и предметной области. Грубо говоря, в C++ несколько десятков строк с хардкорными шаблонами могут успешно заменять сотни строк "Си с классами", но чтобы разобраться с этой парой десятков строк могут потребоваться знания и навыки, которые приобретаются годами. Так что степень "хорошести", "плохости" и "крутизны" может зависеть от профессионального уровня оценивающего.

пятница, 13 декабря 2024 г.

[prog.self.reflection] Официальный ответ на претензию "Да вовсе ты и не крутой программист"

Я регулярно спорю на профильных форумах о разных связанных с программированием вещах. И время от времени меня обвиняют в том, что я выдаю себя за крутого программиста, а на самом деле таковым не являюсь. Причем делают это люди, которые меня лично не знают, со мной никогда не работали и, скорее всего, даже никогда не заглядывали в написанный мной код (хотя его легко найти на github-е).

На что официально заявляю: я не считаю себя крутым программистом и никогда ничего подобного о себе не говорил. Ну на сколько помню.

Если это не так, и я где-то что-то подобное ляпнул, то дайте, плз, ссылку. В назидание.

Более того, если говорить о том, как я сам себя оцениваю, то это будет что-то чуть повыше среднего. В верхние 20% точно не попадаю.

Отчасти это компенсируется тем, что я еще немного могу в тестирование и документирование. Т.е. делаю разное, хоть и посредственном уровне.

Вот в чем, скорее всего, превосхожу многих, так это в упорстве при достижении целей. Что и позволяет "брать и делать", даже если задача находится за пределами моих текущих возможностей.

Короче: обычный середняк, который компенсирует отсутствие мозгов упорством, прилежанием и способностью связно излагать свои мысли в письменном виде.

четверг, 12 декабря 2024 г.

[prog.flame] Иногда просто поражаешься трудолюбию программистов

Недавно довелось увидеть что-то вроде вот такого:

void data_holder::validate_data()
{
  std::cout << "*** Data validation: 1/15 (checking) ***" << std::endl;
  check();
  std::cout << "*** Data validation: 2/15 (sorting) ***" << std::endl;
  sorting();
  std::cout << "*** Data validation: 3/15 (deduplication) ***" << std::endl;
  deduplicate();
  std::cout << "*** Data validation: 4/15 (normalization) ***" << std::endl;
  normalize();
  ... // И так еще несколько строк пока не будет сделан шаг 15 из 15.
}

Вот честно, удивлен такому трудолюбию, т.к. меня бы быстро задолбало выписывать однотипные строки печати в std::cout с инкрементом значений в них. Особенно с учетом того, что количество этих шагов увеличивается по мере развития проекта :)

Так что я бы чуть ли не сразу написал бы что-то вроде:

void data_holder::validate_data()
{
  constexpr int total_steps = 15;
  auto inform = [step = int{1}](const char * name) mutable {
    std::cout << "*** Data validation: " << step << "/" << total_steps
      << " (" << name << ") ***" << std::endl;
    ++step;
  };

  inform("checking");
  check();
  inform("sorting");
  sorting();
  inform("deduplication");
  deduplicate();
  inform("normalization");
  normalize();
  ... // И так еще несколько строк пока не будет сделан шаг 15 из 15.
}

ИМХО, программист должен быть достаточно ленивым, чтобы не повторять однообразную рутинную работу. И еще он должен видеть повторяющиеся паттерны, которые можно преобразовать в повторно используемый код.

Ну и да, исходный пример заставляет вспомнить народную мудрость "простота хуже воровства".

среда, 11 декабря 2024 г.

[prog.flame] Комментарии в коде нужны. Без них тяжено, проверено на собственном опыте

Недавно в LinkedIn поделился эмоциями о том, что в большинстве случаев при работе с внешними заказчиками в коде нет комментариев. Вообще нет комментариев. И как же после этого приятно заглядывать в код, в котором комментарии есть.

К моему удивлению, там случился срач в котором мне пытались доказать, что комментарии врут, поэтому их писать не нужно.

Аргументы были не просто старые, а совсем уж древние, впервые их услышал лет 35 назад, еще когда только-только учился программировать. И с тех пор выслушивал их неоднократно.

Разбирать подобные домыслы в очередной раз нет ни сил, ни времени, ни желания. Если кто-то убежден, что есть самодокументирующийся код, то бог вам судья. Позволю себе только напомнить, что хорошо написанный код показывает только то, что и как сделано, но не может ответить на два очень важных вопроса:

  • почему это сделано именно так, а не иначе?
  • зачем вообще это было сделано?

Никакой "типа самодокументирующийся код" не может ответить на эти вопросы. А уж плохо написанный код не сможет вменяемо рассказать даже о том что именно было реализовано.

Первоначально я хотел на этом и закончить, но встретил в статье про переиздание знаменитой книги "Мифический человеко-месяц" вот такой абзац:

Однако есть и другая точка зрения на этот вопрос. Например, Мартин (Роберт, а не Джордж), автор книги «Чистый код», утверждает, что комментарии — это зло. Даже будучи среди исходного кода они всё равно могут стать неактуальными. «Не трать время на написание комментариев», – говорит Мартин. Вместо этого он предлагает более тщательно подходить к процессу именования переменных, методов и классов. Если всё делается правильно, то и необходимость в комментариях отпадает.

и подумал, что если уж так все запущено, то почему бы не попробовать привести пример кода с комментариями и без? Просто для того, чтобы каждый мог сделать собственные выводы.

Disclaimer. Приведенный код -- это черновик, который еще не компилировался и не тестировался.

Итак, вот фрагмент без комментариев:

template<typename T>
void
doAttemptToSwitchFromReadOnlyToReadWrite(T * objectToLock)
{
   auto & globalList = getGlobalListFor(objectToLock);
   std::unique_lock globalListLock{ globalList._lock };
   auto it = globalList._objects.find(objectToLock);
   if(it != globalList._objects.end())
   {
      auto & lockInfo = it->second;
      if(AccessMode::ReadOnly != lockInfo._currentMode)
         throw std::runtime_error{
               "doAttemptToSwitchFromReadOnlyToReadWrite: "
               "lockInfo._currentMode != AccessMode::ReadOnly "
               "in the global lock list"
            };

      if(1u == lockInfo._ownersCount)
      {
         lockInfo._currentMode = AccessMode::ReadWrite;
      }
      else if(isWaitingQueueEmpty(lockInfo))
      {
         OwnerCountForLockingUpgrateTrx ownerCounterTrx{ lockInfo };

         const auto requestResult = makeWaitingAccessorInfoThenWait(
               globalListLock,
               lockInfo,
               AccessMode::ReadWrite);
         if(AccessRequestResult::granted != requestResult)
            throw PossibleDeadlock{
                  "doAttemptToSwitchFromReadOnlyToReadWrite: "
                  "unable to upgrade ReadOnly lock to ReadWrite lock even "
                  "after waiting"
               };

         ownerCounterTrx.commit();
      }
      else
      {
         throw PossibleDeadlock{
               "doAttemptToSwitchFromReadOnlyToReadWrite: "
               "there is no possibility to upgrade ReadOnly lock "
               "to ReadWrite lock"
            };
      }
   }
   else
   {
      throw std::runtime_error{
            "doAttemptToSwitchFromReadOnlyToReadWrite: there is no "
            "information about objectToLock in the global lock list"
         };
   }
}

А под катом он же, но в своем исходном виде, с комментариями. Посмотрите, подумайте, с каким кодом вам было бы комфортнее работать, если бы он вам достался на сопровождение. Я-то свои выводы уже давно сделал, чего и вам желаю.