Я долго искал для Мака простой плеер в духе старого Winamp и не нашёл: современные либо разрослись до комбайнов со стримингами и медиатеками, либо написаны на Electron и грузят процессор впустую. Года три-четыре назад я начал писать такой плеер сам, но дальше первого экрана и базового проигрывания дело не дошло. Проект так и лежал, а когда я натыкался на эту папку, то вспоминал о нём и думал, что когда-нибудь доведу его до конца. С приходом нейросетей мне это наконец удалось.
Получился MacAmp: открытая репродукция Winamp 2.95 для macOS на Swift, SwiftUI и AppKit. Электрона и веб-вью в интерфейсе нет, сборка универсальная (Apple Silicon и Intel), нужна macOS 14 или новее.
Функционал
Окна, меню, настройки, диалоги и горячие клавиши у MacAmp такие же, как в Winamp 2.95. Всё современное спрятано внутрь или вынесено в отдельную группу в конце страницы настроек.
-
Главное окно, эквалайзер и плейлист магнитно стыкуются друг с другом, есть режим Shade и Double Size (2x).
-
Подходят любые скины Winamp 2.x (
.wszили папка), они читаются на лету. Базовый скин 2.91 встроен. -
Играет MP3, AAC/M4A, FLAC, WAV/AIFF, Ogg Vorbis/Opus, MP2, AU и MIDI. Есть кроссфейд, а при смене наушников или колонок звук идёт на новое устройство сам.
-
Десятиполосный эквалайзер с предусилителем, пресетами и автозагрузкой по жанру, плюс pitch/tempo и реверберация.
-
Плейлисты M3U, PLS, XSPF и CUE, закладки, Jump to File.
-
Интернет-радио SHOUTcast и Icecast, каталог станций берётся из radio-browser.info.
-
Аудио-CD можно слушать и копировать в FLAC, ALAC, AAC, WAV или AIFF.
-
Медиатека, Minibrowser, окно видео, Track Info с редактором ID3, спектроанализатор и осциллограф.
-
Хоткеи из 2.95 работают и на русской раскладке (этого пришлось добиваться отдельно).
-
Необязательный FFmpeg-плагин добавляет MKV, WebM, AVI, WMV, WMA, APE и WavPack.
Медиатека MacAmp со скином и кириллицей в тегах
Медиатека в стандартном скине, кириллица в тегах отображается без проблем
Снаружи классика, внутри современность
Я старался, чтобы всё, что было в Winamp 2.95, выглядело и работало так же, как там. Скриншоты оригинала лежат у меня рядом с кодом, и я сверяюсь с ними. Новое либо незаметно работает внутри, как поддержка современных форматов и смена устройства вывода, либо уходит в отдельную группу настроек. Облачных медиатек и подписок не будет.
Главное меню MacAmp с пунктами и хоткеями как в Winamp
Окно Track Info с редактором ID3-тегов
Track Info: редактор ID3v1 и ID3v2, как в оригинале
Вторая цель в том, чтобы плеер спокойно висел в углу и не садил батарею. На M1 на паузе он берёт около 0,01% процессора, а при игре со спектроанализатором 2–3%. Когда ничего не играет, движок останавливается, а анализатор через две секунды тишины перестаёт присылать кадры. Этих двух секунд хватает, чтобы столбики спектра и пики успели упасть.
Пара технических деталей
Окна двигаются как одно целое
Главное окно, эквалайзер и плейлист — это три отдельных безрамочных NSWindow, и сгруппировать окна в macOS нечем. Когда тянешь ведущее окно, остальные должны ехать вместе с ним и не разъезжаться. Поэтому позицию каждого окна я считаю заново от того места, где оно стояло в начале перетаскивания, прибавляя к нему общий сдвиг. Если же добавлять к текущей позиции маленькие шажки на каждое движение мыши, ошибки округления накапливаются. Магнитное притяжение к краям экрана и соседним окнам считается один раз по предполагаемым рамкам, и только потом результат применяется ко всей группе:
let totalShiftX = roundedDeltaX + bestSnapXlet totalShiftY = roundedDeltaY + bestSnapYfor (gid, startOrigin) in startOrigins { guard let win = windows[gid], win.isVisible else { continue } win.setFrameOrigin(NSPoint( x: round(startOrigin.x + totalShiftX), y: round(startOrigin.y + totalShiftY) ))}
Координаты округляются, потому что в пиксельном интерфейсе стыки окон не должны съезжать даже на полпикселя, особенно в режиме 2x.
Пиксели без сглаживания
Скины Winamp — это растровые BMP, нарисованные для экранов старой плотности (MAIN.BMP, EQMAIN.BMP, PLEDIT.BMP, TEXT.BMP). Если выводить их стандартным способом, на Retina шрифт и цифры таймера расплываются. Поэтому окна рисуют себя сами и с выключенной интерполяцией:
public override func draw(_ dirtyRect: NSRect) { guard let context = NSGraphicsContext.current?.cgContext else { return } context.interpolationQuality = .none // ...}
Спрайты вырезаются точно по пикселям оригинала, координаты хранятся в SpriteCatalog. Сверял я их с исходниками Webamp.
Эквалайзер
Десять полос с частотами как в Winamp (от 60 Гц до 16 кГц) и предусилитель живут в обычном AVAudioUnitEQ: полосы там параметрические фильтры, а предусилитель — это его globalGain. Звук идёт через AVAudioEngine, поэтому плеер сам следует за системным устройством вывода.
Как это писалось с ИИ
Большую часть кода написали ИИ-агенты. Я придумывал, ставил задачи, смотрел результат и решал, что выпускать. Такая схема сработала: я понимаю, что должно получиться, и замечаю, когда архитектура уезжает в сторону, а конкретные вызовы AppKit знает агент.
Мне помогли три вещи. Во-первых, в проекте лежат документы: видение, план по этапам, чек-лист и справочник по размерам и поведению Winamp 2.x, а рядом скриншоты оригинала. Агент читает всё это перед работой, и мне не приходится каждый раз объяснять, что «как в Winamp» значит буквально. Во-вторых, правила в AGENTS.md и несколько скиллов про структуру проекта и про то, как безопасно менять эквалайзер и плейлист: благодаря им агент не ломает то, что уже работает. И наконец, все тесты запускаются одной командой, ./build.sh test.
Отдать агентам не вышло одно: ощущения. Как ложится в руку перетаскивание, не дрожит ли поле, тот ли цвет, как звучит кроссфейд на живых треках — это можно проверить только руками и ушами. Часть такой проверки у меня до сих пор в списке «не сделано», о ней ниже.
Чего пока нет
Пока нет визуализаций Milkdrop через projectM, а также MP3- и Ogg-кодировщика для копирования CD (в 2.95 был Ogg Vorbis). Трекерные модули MOD, XM, S3M и IT тоже не играют: для них нужна libopenmpt, и я пока думаю, стоит ли тащить такую зависимость. Скробблера Last.fm тоже нет, а если он появится, то отдельной опцией и выключенным по умолчанию.
Почему нет подписи Apple Developer ID
Я не подписывал приложение намеренно. В Winamp на странице File types можно выбрать, какие форматы открывает плеер, и тут же поменять иконки файлов и плейлистов. Менялись они на лету. На Маке иконки документов Finder берёт из самого приложения, поэтому MacAmp подменяет иконку внутри MacAmp.app и заново подписывает себя локальной (ad-hoc) подписью. С подписью Developer ID это не получилось бы: изменённый бандл сломал бы её, и иконки поменять было бы нельзя.
Платить за это приходится предупреждением Gatekeeper при первом запуске. Открыть приложение можно через Системные настройки › Конфиденциальность и безопасность › Всё равно открыть или так:
xattr -dr com.apple.quarantine /Applications/MacAmp.app
Настройки MacAmp, страница File types с выбором иконок
Preferences › File types: те же ассоциации и смена иконок, что в Winamp
Нужна помощь с проверкой
Плеером я пользуюсь каждый день, но на одном компьютере и с одной фонотекой, поэтому многое ещё не проверено вручную, и я буду рад вашим отчётам. Самое интересное для меня: кроссфейд на реальных треках (с паузой и перемоткой в его середине), альбом в одном FLAC с .cue, радиостанции с переподключением после обрыва сети, Bluetooth-наушники во время игры и сон с пробуждением Мака. Ещё не гонял плейлисты на тысячи треков (прокрутка, поиск, сортировка, загрузка процессора с открытыми окнами), старые скины из Winamp Skin Museum с нестандартной графикой и Intel-Маки с macOS 14.
Ссылки
-
Исходники: github.com/alexrel/macamp
-
Готовая сборка (DMG): github.com/alexrel/macamp/releases
Приложение бесплатное, лицензия MIT. Если что-то пойдёт не так, пишите в комментариях или заводите issue на GitHub.
Источник: habr.com