Хотел бы показать, как быстра может быть эволюция от идеи до готового кода с небездумным применением искусственного интеллекта (ИИ).
По работе у меня в road map (это, кто пока не в курсе, дорожная карта дел на год)
освоение ИИ в целях оптимизации тестирования;
написание автотестов в проекте на Kotlin.
Надо бы в этом всём прокачаться. Надо-надо... Надо подкачаться, вот и решил написать код на Kotlin с консультированием в текстовом чате с ИИ.
Давно я чаял идею написать утилиту для создания музыки, т.к. уж 20 лет как ею занимаюсь и ей обучаюсь любительски то тут, то там.
Любая музыка - суть последовательность аккордов. Если глубже, то последовательность функциональных ступеней в разных комбинациях, подчинённых законам муз.гармонии, описанных уже давно Хуго Риманом и уточнённых его последователями, но мы не будем в этот раз слишком глубоко нырять в гармонию).
Нам пока достаточно того, что упрощённо музыка - последовательность аккордов. Стало быть, чтобы создать музыку нужно эту последовательность создать = сгенерировать.
Спойлер:
Всё получилось. Утилиту сможете легко применить в вашем браузере. В статье описал как. Если чешутся руки погенерить аккорды мотайте сразу вниз на раздел "Как запустить утилиту?"! В три простых действия получите аккорды и удовольствие :)
Первый блин.com
Сперва я составил промпт для текстового ИИ-ассистента (Qwen):
Помоги в простом формате написать программу на Kotlin, которая:
Запрашивает и принимает у пользователя набор аккордов (string-слов) через пробел.
Если пользователь не задаёт, по дефолту используем базовую белоклавишную тональность ля-минор/до-мажор (именно в ней те самые "блатные аккорды"):
Am Bdim С Dm Em F G.Уточняет сколько аккордов должно быть в последовательности: 2, 3, 4 или больше (по дефолту - 3)
Выдаёт как финальный результат рандомную последовательность из предложенных пользователем аккордов.
Предлагает повторить сценарий.
ИИ сгенерил код:
import kotlin.random.Random fun main() { while (true) { // 1. Получаем аккорды от пользователя println("Введите аккорды через пробел (или нажмите Enter для использования по умолчанию):") val input = readLine() val chords = if (input.isNullOrBlank()) { listOf("Am", "Bdim", "C", "Dm", "Em", "F", "G") } else { input.split(" ").filter { it.isNotEmpty() } } if (chords.isEmpty()) { println("Нет аккордов для обработки. Попробуйте снова.") continue } // 2. Получаем количество аккордов в последовательности println("Сколько аккордов в последовательности? (2, 3, 4 или больше, по умолчанию 3):") val sequenceLength = try { val lengthInput = readLine() when { lengthInput.isNullOrBlank() -> 3 else -> lengthInput.toIntOrNull() ?: 3 } } catch (e: NumberFormatException) { 3 } // 3. Генерируем случайную последовательность val randomSequence = (1..sequenceLength).map { chords[Random.nextInt(chords.size)] } // 4. Выводим результат println("Случайная последовательность аккордов: ${randomSequence.joinToString(" ")}") // 5. Спрашиваем о повторении println("Хотите повторить? (y/n):") val repeat = readLine()?.lowercase() if (repeat != "y" && repeat != "yes") { break } } println("Спасибо за интерес к созданию музыки! Впереди всегда много интересного.") }
Поскольку сначала хотелось прогнать код в браузере онлайн, а не в IDE (среде разработки), попробовал исполнить его в https://play.kotlinlang.org/. Взаимодействия через текстовый диалог, как я задумал, со мной не произошло: этот онлайн-компилятор не рассчитан на вводы пользователя. Зато сработал прописанный в коде механизм предварительно заданных (=дефолтных) значений. Выполнена цель генерации рандомных аккордов из "умолчательного" набора тональности ля-минор.

При уточнении в чате с ИИ, стало ясно, что дело в ограниченности исполнения кода в "песочнице" (play.kotlinlang.org/). ИИ предложил в этом онлайн-компиляторе исполнить упрощённый код:
import kotlin.random.Random fun main() { // Используем стандартный набор аккордов val defaultChords = listOf("Am", "Bdim", "C", "Dm", "Em", "F", "G") val chords = defaultChords // Задаем длину последовательности val sequenceLength = 3 // Генерируем случайную последовательность val randomSequence = (1..sequenceLength).map { chords[Random.nextInt(chords.size)] } // Выводим результат println("Случайная последовательность аккордов: ${randomSequence.joinToString(" ")}") println("Спасибо за неподдельное любопытство в создании музыки!") }
Таким образом, вручную меняя значения в функции listOf из инициатора для значения defaultChords, можно без проблем получить желаемую случайную последовательность аккордов средствами Kotlin. Число аккордов в последовательности можно регулировать через значение sequenceLength.
Код получился лаконичный и интуитивно понятный, за что и любят язык программирования Kotlin. Однако в целом заставлять вчитываться в код - это не очень-то дружелюбно к пользователю, если он не знаком с программированием или алгоритмами. Не всем своим приятелям-музыкантам я могу дать в пользование такую утилиту. Вместе с ней нужно высылать пояснительные бригады, а их не напасёшься.
Я захотел сделать что-то более универсальное, гибкое и дружелюбное.
И кстати... откуда я взял изначальные аккорды? Почему именно такие? Гитаристы знают, что есть ещё и другие ...
Можно масштабировать этот мой первый опыт для всех тональностей.
Утилита для любой тональности
Ах да, тональностей :D Упоминаю в седьмой раз тональность, но не расшифровываю, что ж...
Тональность - это (опять же упрощённо) набор из 7 нот. Наш с вами друг Иоган Себастьян Бах выбирал определённые 7 нот из 12 нот хроматического ряда. Да-да, нот далеко не 7! ? В каждой из тональностей он написал лютейшую красоту, разобранную музыкантами-наследниками на цитаты (см. и слушай "Хорошо темперированный клавир").
7 нот тональности группируются в эдакие комплексы - трезвучия или всем известные аккорды. Мы в этом маленьком исследовании будем строить базовые трезвучия. Базовых трезвучий, ожидаемо для великих комбинаторов, - 7. Строятся они от каждой из нот по терциям (то есть упрощённо через одну белую клавишу на фортепиано). Короче говоря, в каждой тональности по 7 базовых аккордов.
Собственно таблица тональностей, с которой пользователь может легко и удобно взаимодействовать через простой ввод числа и буков, и является самым большим достижением моего диалога с ИИ.

Те, кто прошёл музыкальную школу жизни отметят, что таблица упорядочена по родственным тональностям, имеющим одинаковое число знаков при ключе (♯/♭), что достаточно удобненько само по себе.
Как это получилось?
В чате у ИИ я попросил дать возможность пошагово сгенерировать последовательность аккордов:
Составить таблицу тональностей, и словарь их аккордов.
Дать пользователю возможность выбирать тональность и получить набор её базовых аккордов.
Выбрать аккорды и задать число их случайных появлений в последовательности.
Пользователь имеет возможность повторить ключевые шаги программы, то есть пересоздать последовательность аккордов по-другому.
Тут я включу skip и перемотаю мои мытарства в диалоге с юным ИИ (я, кстати, сменил Qwen на Perplexity), не особо грамотным в муз.теории, а также в тестировании и отладке программы. Покажу вам сразу самую мякотку - готовую программу, которую можно успешно запустить, задав все нужные параметры на https://www.codechef.com/kotlin-online-compiler:
fun main() { val app = ChordSequenceGenerator() app.run() } class ChordSequenceGenerator { private enum class RepeatMode { SAME_CHORDS, SAME_KEY_NEW_CHORDS, NEW_KEY, EXIT } fun run() { println( "=== Генератор последовательностей аккордов ===\n" + "В каждой тональности есть по 7 аккордов, связанных музыкльной гармонией.\n" + "Давайте сгенерируем случайную последовательность аккордов в любой из тональностей!" + "*-dur - мажорные тональности, *-moll - минорные\n" ) while (true) { try { val key = step1_chooseKey() var selectedChords = step2_chooseChords(key) var count = step3_askCount() while (true) { val sequence = generateSequence(selectedChords, count) displaySequence(sequence,key) when (step4_askRepeatMode()) { RepeatMode.SAME_CHORDS -> { continue } RepeatMode.SAME_KEY_NEW_CHORDS -> { selectedChords = step2_chooseChords(key) count = step3_askCount() } RepeatMode.NEW_KEY -> { break } RepeatMode.EXIT -> { println("Спасибо за использование!") return } } } } catch (e: Exception) { println("Ошибка: ${e.message}") if (!step5_askRepeatAfterError()) break println() } } println("Спасибо за использование!") } private fun step1_chooseKey(): String { println("Шаг 1: Выбор тональности") println() println(" № | Мажор (M) | минор (m) | Знаки альтерации") println("------|-----------------|-----------------|-----------------") val majors = mutableListOf<String>() val minors = mutableListOf<String>() Vocabulary.keyPairs.forEachIndexed { idx, (maj, min) -> majors.add(maj) minors.add(min) val sigMaj = Vocabulary.keySignatures[maj] ?: "?" val numMaj = idx + 1 println( "${numMaj.toString().padStart(5)} | ${maj.padEnd(15)} | " + "${min.padEnd(15)} | $sigMaj" ) } print("\nВведите номер строки: ") val choice = readLine()?.toIntOrNull() if (choice != null && choice in 1..majors.size) { val index = choice - 1 print("Мажор (M) или минор (m): ") val type = readLine()?.lowercase()?.trim() return if (type == "m" || type == "минор" || type == "moll") { minors[index] } else { majors[index] } } println("Неверный выбор, используется C-dur") return "C-dur" } private fun step2_chooseChords(key: String): List<String> { val availableChords = Vocabulary.allChords[key] ?: error("Неизвестная тональность: $key") println("\nШаг 2: Доступные аккорды выбранной тональности: $key") availableChords.forEachIndexed { index, chord -> println("${index + 1}. $chord") } print("Введите номера аккордов через пробел (Enter для всех): ") val input = readLine()?.trim() if (input.isNullOrBlank()) { return availableChords } val selected = mutableListOf<String>() input.split("\\s+".toRegex()) .forEach { num -> val index = num.toIntOrNull() if (index != null && index in 1..availableChords.size) { selected.add(availableChords[index - 1]) } } return if (selected.isEmpty()) availableChords else selected.distinct() } private fun step3_askCount(): Int { println("\nШаг 3: количество аккордов в последовательности") print("Сколько аккордов в последовательности? (по умолчанию 4): ") val input = readLine()?.trim() return input?.toIntOrNull() ?: 4 } private fun generateSequence(chords: List<String>, count: Int): List<String> { return (1..count).map { chords.random() } } private fun displaySequence(sequence: List<String>, key: String) { println("\n=== Последовательность в $key, которую мы заслужили ===") sequence.chunked(4).forEach { chunk -> println(chunk.joinToString(" ")) } } private fun step4_askRepeatMode(): RepeatMode { println("\nЕщё?") println("1 — сгенерировать ещё раз с теми же аккордами") println("2 — выбрать другие аккорды в той же тональности") println("3 — выбрать новую тональность") println("4 — выйти") print("Ваш выбор: ") return when (readLine()?.trim()) { "1" -> RepeatMode.SAME_CHORDS "2" -> RepeatMode.SAME_KEY_NEW_CHORDS "3" -> RepeatMode.NEW_KEY "4" -> RepeatMode.EXIT else -> RepeatMode.EXIT } } private fun step5_askRepeatAfterError(): Boolean { println("\nХотите попробовать снова? (y/n): ") val input = readLine()?.lowercase()?.trim() return input == "y" || input == "yes" || input == "д" || input == "да" } } object Vocabulary { val keySignatures = mapOf( // мажоры "C-dur" to "0", "G-dur" to "1♯", "D-dur" to "2♯", "A-dur" to "3♯", "E-dur" to "4♯", "B-dur" to "5♯", "C♭-dur" to "7♭", "F♯-dur" to "6♯", "G♭-dur" to "6♭", "D♭-dur" to "5♭", "C♯-dur" to "7♯", "A♭-dur" to "4♭", "E♭-dur" to "3♭", "B♭-dur" to "2♭", "F-dur" to "1♭", // миноры "A-moll" to "0", "E-moll" to "1♯", "B-moll" to "2♯", "F♯-moll" to "3♯", "C♯-moll" to "4♯", "G♯-moll" to "5♯", "A♭-moll" to "7♭", "E♭-moll" to "6♭", "D♯-moll" to "6♯", "B♭-moll" to "5♭", "A♯-moll" to "7♯", "F-moll" to "4♭", "C-moll" to "3♭", "G-moll" to "2♭", "D-moll" to "1♭" ) val keyPairs = listOf( "C-dur" to "A-moll", "G-dur" to "E-moll", "D-dur" to "B-moll", "A-dur" to "F♯-moll", "E-dur" to "C♯-moll", "B-dur" to "G♯-moll", "C♭-dur" to "A♭-moll", "G♭-dur" to "E♭-moll", "F♯-dur" to "D♯-moll", "C♯-dur" to "A♯-moll", "D♭-dur" to "B♭-moll", "A♭-dur" to "F-moll", "E♭-dur" to "C-moll", "B♭-dur" to "G-moll", "F-dur" to "D-moll" ) val allChords = mapOf( "C-dur" to listOf("C", "Dm", "Em", "F", "G", "Am", "B°"), "G-dur" to listOf("G", "Am", "Bm", "C", "D", "Em", "F♯°"), "D-dur" to listOf("D", "Em", "F♯m", "G", "A", "Bm", "C♯°"), "A-dur" to listOf("A", "Bm", "C♯m", "D", "E", "F♯m", "G♯°"), "E-dur" to listOf("E", "F♯m", "G♯m", "A", "B", "C♯m", "D♯°"), "B-dur" to listOf("B", "C♯m", "D♯m", "E", "F♯", "G♯m", "A♯°"), "F♯-dur" to listOf("F♯", "G♯m", "A♯m", "B", "C♯", "D♯m", "E♯°"), "C♯-dur" to listOf("C♯", "D♯m", "E♯m", "F♯", "G♯", "A♯m", "B♯°"), "C♭-dur" to listOf("C♭", "D♭m", "E♭m", "F♭", "G♭", "A♭m", "B♭°"), "G♭-dur" to listOf("G♭", "A♭m", "B♭m", "C♭", "D♭", "E♭m", "F°"), "D♭-dur" to listOf("D♭", "E♭m", "Fm", "G♭", "A♭", "B♭m", "C°"), "A♭-dur" to listOf("A♭", "B♭m", "Cm", "D♭", "E♭", "Fm", "G°"), "E♭-dur" to listOf("E♭", "Fm", "Gm", "A♭", "B♭", "Cm", "D°"), "B♭-dur" to listOf("B♭", "Cm", "Dm", "E♭", "F", "Gm", "A°"), "F-dur" to listOf("F", "Gm", "Am", "B♭", "C", "Dm", "E°"), "A-moll" to listOf("Am", "B°", "C", "Dm", "Em", "F", "G"), "E-moll" to listOf("Em", "F♯°", "G", "Am", "Bm", "C", "D"), "B-moll" to listOf("Bm", "C♯°", "D", "Em", "F♯m", "G", "A"), "F♯-moll" to listOf("F♯m", "G♯°", "A", "Bm", "C♯m", "D", "E"), "C♯-moll" to listOf("C♯m", "D♯°", "E", "F♯m", "G♯m", "A", "B"), "G♭-moll" to listOf("G♭m", "A♭°", "B♭", "C♭m", "D♭m", "E♭", "F"), "D♭-moll" to listOf("D♭m", "E♭°", "F", "G♭m", "A♭m", "B♭", "C"), "A♭-moll" to listOf("A♭m", "B♭°", "C♭", "D♭m", "E♭m", "F", "G"), "E♭-moll" to listOf("E♭m", "F°", "G♭", "A♭m", "B♭m", "C", "D"), "D♯-moll" to listOf("D♯m", "E♯°", "F♯", "G♯m", "A♯m", "B♭", "C♯"), "B♭-moll" to listOf("B♭m", "C°", "D♭", "E♭m", "Fm", "G", "A"), "A♯-moll" to listOf("A♯m", "B♯°", "C♯", "D♯m", "E♯m", "F♯", "G♯"), "F-moll" to listOf("Fm", "G°", "A♭", "B♭m", "Cm", "D", "E"), "C-moll" to listOf("Cm", "D°", "E♭", "Fm", "Gm", "A♭", "B♭"), "G-moll" to listOf("Gm", "A°", "B♭", "Cm", "Dm", "E♭", "F"), "D-moll" to listOf("Dm", "E°", "F", "Gm", "Am", "B♭", "C") ) }
Как запустить утилиту?
Программу выше можно скопировать и вставить в левое поле интерфейса codechef (1), ...

Собственно запуск происходит по нажатии на синюю кнопку Run.
Параметры для запуска можно задать в правом окне (2): каждой параметр (13, m и т.д.) на отдельной строке представляют собой ответы на вопросы из программы, где строки пронумерованы по порядку ввода:
Шаг 1: Выбор тональности:
Введите номер строки:
в примере 13Мажор (M) или минор (m):
в примере m
(если ничего не задать, оставив строку пустой будет Мажор)
Шаг 2: Доступные аккорды выбранной тональности:Введите номера аккордов через пробел (Enter для всех):
в примере 1 2 3 4 5 6 7 (но лучше для минора 2-ую ступень не выбирать ;) )
(если ничего не задать, оставив строку пустой будут все 7 аккордов)
Шаг 3: количество аккордов в последовательностиСколько аккордов в последовательности? (по умолчанию 4):
в примере ничего не задано, просто по Enter оставил строку пустой.-
Дальше блок повторных вводов: там пока имеет смысл вводить только повторные генерации в той же тональности через ввод "1". Я 3 раза ввёл "1" в своём примере и получил 3 последовательности.

Входные параметры (Input) для взаимодействия с программой.
Результат смотреть в нижней правой части экрана. Там можно промотать.
Итак, последовательности в выбранной мной тональности, которые я заслужил.
=== Последовательность в C-moll, которую мы заслужили === E♭ Fm E♭ Cm Ещё? 1 — сгенерировать ещё раз с теми же аккордами 2 — выбрать другие аккорды в той же тональности 3 — выбрать новую тональность 4 — выйти ... === Последовательность в C-moll, которую мы заслужили === B♭ Cm Gm E♭ ... === Последовательность в C-moll, которую мы заслужили === E♭ A♭ E♭ Gm
Умудрённые музыканты увидят, что первую из последовательностей утилита даже кононично закончила на тоническом трезвучии (T). Совпадение? В этом случае точно - да!
Попробовал сыграть все три последовательности на гитаре. Всё звучит классно! ? ? ?
Можно развивать последовательности, меняя ритмику, длительности игры аккорда и даже сам аккорд: был Xm - а стал Xm7 или Xm maj7 или Xsus2/4 или Xm9 или Xaug и т.д.).
Диезы и бемоли не пугают прошаренных гитаристов, ведь последние знают, что:
аппликатуры гитарного аккорда с бемолем (♭) левее на 1 лад своего "обычного" представления на грифе:
например, E♭ ← E.аппликатуры с диезом (♯) - правее на 1 лад:
например, Em → E♯m.
Пути развития утилиты
Надо в github такое оформлять, знаю. Пока ещё не сделал, ибо новичок в создании кода с нуля. Ещё бы и красиво портянку кода на классы разделить. Когда сделаю, обновлю эту статью.
Можно добавить и табулатуры аккордов, и иные представления аккордов, но это, пожалуй, в отдельном этапе.
Хотел подарить вам эту утилиту в виде Telegram-бота. Научился разворачивать, подтягиваю зависимости либы com.github.kotlintelegrambot% через gradle и даже запустил бота с токеном от @BotFather из Телеграм, но, увы, сейчас Телега движется туго, надо как-то через прокси или не с российским IP разворачивать удалённо на сервере, поэтому реализация через ТГ-бот отложена.
Бот в Макс только через ЮЛ, у меня только самозанятость оформлена пока.
Как использовать
Индивидуальная практика в музыке. На гитаре, укулеле или клавишах сыграйте все аккорды - получите опыт и навык и хвастайтесь друзьям ачивкой!
Практика в коллективе. Сыграйте с вашим рок-бэндом, докрутите последовательность, как вам надо - и играйте новую песни с вашими аккордами, ведь мне они не принадлежат, музыка - общее достояние.
-
Можете "докрутить" сам код утилиты и сделать вашу утилиту, которая, например,
будет генерировать последовательности по строгим законам гармонии (T-S-D) или допускать, как в блюзе нарушения строгости (T-D-S),
будет давать некие отклонения или модуляции из тональности в тональность.
Будет транспонировать цепочку сгенерированных аккордов (=переводить в другую тональность)
... Ещё масса вариантов. Делитесь вашими идеями, и успехов их в реализации!
Желаю интересных генераций, а также хорошей и - главное - качественной музыки!

Дмитрий Исанин
Тестировщик ПО, сонграйтер и исполнитель, изучаю и применяю ИТ и ИИ для создания песен и музыки, а также озвучки
Про то, как делаю музыку в последние 2 года читайте статьи на Хабре:
https://habr.com/ru/users/Dmitry_Isanin/articles/,-
среди которых "Почему я выбрал Suno AI для создания мемного альбома «Вася Тестировщик»?"
Слушайте на всех популярных площадках мой альбом в духе хип-хопа
"Лоскутное одеяло": https://band.link/penchwork
А также альбом с утяжелённым звучанием гитар
"Хлад металлов": https://band.link/hlad_metallov
Также я публикуюсь и в других местах:
ВК: https://vk.com/dmisanin
Max: https://max.ru/join/eplS_t3xAWUrdsb52ojWxP3_gc1mbE3pE6c-Jv2-eE8
Telegram: https://t.me/shine_sean (Дзен прицепом)
Suno: https://suno.com/@dmisanin3
Sferoom: https://lk.sferoom.space/artists/117182/feed
Boosty: https://boosty.to/dmisanin (тут можно задонатить)
YouTube: https://www.youtube.com/@dmisanin (не забудьте про VPN)
Комментарии (2)

Kalobok
27.05.2026 16:33F C B° B°Замечательная последовательность.
Надо в github такое оформлять, знаю.
Точно не надо. Надо сначала сделать что-то чуть большее, чем герератор случайных чисел.
ris58h
Мало что понял после прочтения. И после того как запустил.
Что утилита делать должна и зачем? Просто генерировать случайные последовательности?
Почему нет GUI? Она нацелена на пользователей консоли?
Зачем Kotlin, в конце концов?
Вы могли бы точно также навайбкодить это в виде статичного HTML/JS/CSS причём вместе с GUI. Разместить можно на GitHub (как и код, так и сам сайт - есть там такая функция). Тогда и пользователям можно было бы просто скинуть ссылку.