Я долго искал для Мака простой плеер в духе старого 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 со скином и кириллицей в тегах

Медиатека MacAmp со скином и кириллицей в тегах

Медиатека в стандартном скине, кириллица в тегах отображается без проблем

Снаружи классика, внутри современность

Я старался, чтобы всё, что было в Winamp 2.95, выглядело и работало так же, как там. Скриншоты оригинала лежат у меня рядом с кодом, и я сверяюсь с ними. Новое либо незаметно работает внутри, как поддержка современных форматов и смена устройства вывода, либо уходит в отдельную группу настроек. Облачных медиатек и подписок не будет.

Главное меню MacAmp с пунктами и хоткеями как в Winamp

Главное меню MacAmp с пунктами и хоткеями как в Winamp

Окно Track Info с редактором ID3-тегов

Окно Track Info с редактором ID3-тегов

Track Info: редактор ID3v1 и ID3v2, как в оригинале

Вторая цель в том, чтобы плеер спокойно висел в углу и не садил батарею. На M1 на паузе он берёт около 0,01% процессора, а при игре со спектроанализатором 2–3%. Когда ничего не играет, движок останавливается, а анализатор через две секунды тишины перестаёт присылать кадры. Этих двух секунд хватает, чтобы столбики спектра и пики успели упасть.

Пара технических деталей

Окна двигаются как одно целое

Главное окно, эквалайзер и плейлист — это три отдельных безрамочных NSWindow, и сгруппировать окна в macOS нечем. Когда тянешь ведущее окно, остальные должны ехать вместе с ним и не разъезжаться. Поэтому позицию каждого окна я считаю заново от того места, где оно стояло в начале перетаскивания, прибавляя к нему общий сдвиг. Если же добавлять к текущей позиции маленькие шажки на каждое движение мыши, ошибки округления накапливаются. Магнитное притяжение к краям экрана и соседним окнам считается один раз по предполагаемым рамкам, и только потом результат применяется ко всей группе:

let totalShiftX = roundedDeltaX + bestSnapX
let totalShiftY = roundedDeltaY + bestSnapY

for (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 с выбором иконок

Настройки MacAmp, страница File types с выбором иконок

Preferences › File types: те же ассоциации и смена иконок, что в Winamp

Нужна помощь с проверкой

Плеером я пользуюсь каждый день, но на одном компьютере и с одной фонотекой, поэтому многое ещё не проверено вручную, и я буду рад вашим отчётам. Самое интересное для меня: кроссфейд на реальных треках (с паузой и перемоткой в его середине), альбом в одном FLAC с .cue, радиостанции с переподключением после обрыва сети, Bluetooth-наушники во время игры и сон с пробуждением Мака. Ещё не гонял плейлисты на тысячи треков (прокрутка, поиск, сортировка, загрузка процессора с открытыми окнами), старые скины из Winamp Skin Museum с нестандартной графикой и Intel-Маки с macOS 14.

Ссылки

Приложение бесплатное, лицензия MIT. Если что-то пойдёт не так, пишите в комментариях или заводите issue на GitHub.