Попытка создать графики музыкальных игр с помощью нейронных сетей.

NLP игра

написать впереди

Мне всегда нравилось играть в музыкальные игры, от самого раннего ритм-мастера до cytus2, malody, lanota и так далее. Но музыкальную диаграмму музыкальной игры создать сложно, и ее нужно сопоставлять, настраивать и т. д., что очень хлопотно, поэтому я подумал об использовании ИИ для ее создания.

Поскольку объем данных относительно невелик и не считается помеченными данными, это всего лишь попытка.

Видео эффекта:воооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооо

(Кстати, обратите внимание на волну на станции B)

Есть много видов музыкальных игр, вот режим падения 4K от Malody, который можно попробовать.

О Малоди

Malody – это аудиосимулятор со множеством режимов. Если вы играли в Rhythm Master, то знаете, что в Rhythm Master есть три режима: 4K, 5K и 6K. Здесь я попробую только режим 4K или режим 4K Mania.

По сравнению с ритм-мастером, Malody не меняет трек, то есть слайдера нет. Но у Malody будет три ставки и четыре ставки, а мастер ритма обычно может играть только двумя пальцами, а режим 4k Malody должен использовать четыре пальца.

В malody есть много ключевых форм, следующие примеры:

Многоуровневый ключ

ключевой разрез

Тип вертикального ключа

Тип ключа лапши

Есть и другие типы ключей, которые здесь не перечислены.

Анализ файла спектра

Карта битов Malody заканчивается суффиксом mcz, что на самом деле является zip-архивом.

После выбора карты ударов и распаковки файла mcz с помощью zip-файла Python вы можете увидеть, что есть три файла.

jpgфоновое изображение карты битов,mcФормат - это диаграмма, иoggэто музыка.

Здесь мы в основном смотрим на файл mc.

Этот файл на самом деле в формате json. Преобразуйте его в словарь Python. Вы можете видеть, что этот словарь имеет всего 4 ключа.

mc_data["meta"]

Основная информация о песне

mc_data["time"]

В основном это информация об изменении BPM во времени, потому что у malody переменная скорость, поэтому мы не рассматриваем здесь переменную скорость, просто посмотрите на первую, вы можете видеть, что BPM этой песни равен 180.

mc_data["note"]

Нота — это наш ключ, есть два типа: одна — нота, и длинная нота — длинная полоса.

Вы можете видеть, что если внизу есть эндбит, то есть длинный такт.

Давайте посмотрим на этот бит, состоящий из трех чисел, в качестве примера возьмем [27, 2, 4]. 27 представляет собой 28-ю долю (считая с 0), эта доля должна быть рассчитана по BPM, BMP этой песни 180, то есть 180 ударов в минуту, т.е.\frac{60}{180} = \frac{1}{3}Секунды в такт, то это 27 от\frac{27}{3}секунд до\frac{28}{3}в этот раз.

Давайте посмотрим на значение 2 и 4 в этом [27, 2, 4]: последние 4 на самом деле представляют маленькие доли в доле, здесь всего 4 маленьких доли, так что доля делится на четыре части, а 2 — третий тик (считая с 0).

Таким образом, это [27, 2, 4] представляет\frac{27}{3}+\frac{2}{3*4} = \frac{55}{6} \approx 9.167секунд в этот момент времени.

mc_data["note"][-1]

В последнем элементе заметки он особенный, он загружает музыку и устанавливает смещение.

offset представляет собой минимальное значение опережения в миллисекундах, чтобы выровнять линию ритма ритма с ритмом музыки. На самом деле это значение выравнивания. Для получения подробной информации см.:воооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооо

Смещение здесь составляет 315 мс, так что предыдущий [27, 2, 4] фактически представляет9.167-0.315=8.852секунд этой точки.

mc_data["extra"]

Кажется, это не работает, так что мне все равно.

построение набора данных

Поскольку ИИ должен использоваться в качестве партитуры, часть данных должна быть подготовлена ​​в первую очередь.Здесь в качестве обучающих данных выбраны таблицы партитуры Lv20-Lv25 в более чем 20 malodys (все они очень простые песни).

определение проблемы

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

построение входных признаков

Основные сведения об извлечении признаков из музыки см. в моей предыдущей статье:Извлечение признаков аудио с помощью Python

Здесь разделение последовательности будет основано на времени, и каждый удар можно разделить на четыре временные точки. То есть в каждом такте не более 4 хитов.

Взяв в качестве примера BMP 180, один удар равен 60/180 = 0,333 секунды. Интервал времени между каждой точкой попадания составляет 0,333/4=0,08333 секунды.

Тогда характеристикой точки попадания является характеристика 0,08333 секунды, которая здесь извлекается с помощью mfcc, а 0,08333 секунды делится на две части, характеристика mfcc извлекается отдельно, а затем объединяется как характеристика звука.

# x为音乐的时域信息,也就是一个列表
# sr为音频的采样频率
# position为第几个打击点
# offset为谱面的偏移
def get_audio_features(x, sr, bpm, position, offset):
    one_beat = 60 / bpm
    beat = position * one_beat / 4 - offset/1000
    
    start = beat
    end = start + one_beat / 8
    
    end2 = start + one_beat / 4
    if start < 0:
        start = 0
    
    start_index = int(sr * start)
    end_index = int(sr * end)
    end_index2 = int(sr * end2)
    
    features = []
    mfcc1 = librosa.feature.mfcc(y=x[start_index:end_index], sr=sr, n_mfcc=32)
    mfcc2 = librosa.feature.mfcc(y=x[end_index:end_index2], sr=sr, n_mfcc=32)
    
    features += [float(np.mean(e)) for e in mfcc1]
    features += [float(np.mean(e)) for e in mfcc2]
    
    return features

разделение входной последовательности

Здесь, поскольку оценка будет относительно длинной, будут тысячи очков жизни для суждения, поэтому очки суждения должны быть разделены на 50 очков в каждом раунде.

Выходной формат

Выход можно разделить на 4 типа:

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

В функции эти три случая могут быть представлены 0, 1, 2, 3.

Чтобы упростить здесь, мы рассматриваем начало длинной ноты и продолжение как ключ, так что выходные результаты равны 0, 1 и 2.

ключевое кодирование

Поскольку это аудиоигра с 4 клавишами, для каждой позиции есть 3 ситуации: пустая, забастовка и полоса.

Таким образом, в общей сложности3^4=81В среднем случае это может быть представлено 4-значным 3-значным числом, которое составляет от 0 до 80 при преобразовании в десятичное число.

Например, эта ключевая форма:

В троичном представлении это:1101=3^3+3^2 +3^0=37

Другой пример следующий:

В троичном представлении это:1222=3^3+2*3^2+2*3^1 +2*3^0=53

Формат данных

Таким образом, требуется много комплексного анализа данных.

Тогда каждый вход представляет собой матрицу 40 * 64. (40 — длина последовательности, 64 — размер функции)

Каждый вывод представляет собой список 40*1.

дизайн модели

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

Encoder

class EncoderRNN(nn.Module):
    def __init__(self, hidden_size):
        super(EncoderRNN, self).__init__()
        self.hidden_size = hidden_size

        self.gru = nn.GRU(Feature_DIM, hidden_size)

    def forward(self, input_, hidden):
        input_ = input_.view(1, 1, -1)
        output, hidden = self.gru(input_, hidden)
        return output, hidden

    def initHidden(self):
        return torch.zeros(1, 1, self.hidden_size, device=device)

hidden_size = 128
encoder = EncoderRNN(hidden_size).to(device)  

Кодировщик здесь представляет собой простой уровень GRU, потому что входные данные — это функция, поэтому кодировщик seq2seq не нуждается во встраивании по сравнению с машинным переводом.

процесс распространения энкодера

Для энкодера нужно один раз запустить цикл, а потом получить скрытый параметр последнего элемента.

x1 = torch.from_numpy(np.array(X1[index])).to(device).float()  # 输入特征
y1 = torch.from_numpy(np.array(Y1[index])).to(device).long() # label

encoder_hidden = encoder.initHidden()
for ei in range(max_length):
    _, encoder_hidden = encoder(
        x1[ei], encoder_hidden)

Decoder

class DecoderRNN(nn.Module):
    def __init__(self, embedding, hidden_size, output_size):
        super(DecoderRNN, self).__init__()
        hidden_size = hidden_size

        # self.embedding = nn.Embedding(output_size, hidden_size)
        self.embedding = embedding
        self.gru = nn.GRU(50, hidden_size)
        self.out = nn.Linear(hidden_size, output_size)
        self.softmax = nn.LogSoftmax(dim=1)
        self.dropout = nn.Dropout(0.2)

    def forward(self, input_, hidden):
        output = self.embedding(input_).view(1, 1, -1)
        output = self.dropout(output)

        # output = F.relu(output)
        output, hidden = self.gru(output, hidden)
        output = self.softmax(self.out(output[0]))
        return output, hidden
        
        
hidden_size = 128
decoder = DecoderRNN(embedding, hidden_size, 81).to(device) 

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

«Ключевая» языковая модель

Здесь отмечается, что в декодере есть встраивание, которое на самом деле очень похоже на встраивание НЛП. Вектор слова на самом деле выражает, какое слово ближе всего к слову.

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

Это также языковая модель в НЛП.

декодер процесс декодирования

Процесс декодирования декодера на самом деле является циклом, но первый скрытый вход ввода не 0, а скрытый скрытый кодировщика.

decoder_input = torch.tensor([[0]], device=device)
decoder_hidden = encoder_hidden
for di in range(max_length):

    decoder_output, decoder_hidden = decoder(
        decoder_input, decoder_hidden)
    target = y1[di].view(-1)
    # print(decoder_output)
    # print(target)
    loss += F.nll_loss(decoder_output, target)
    decoder_input = target  # Teacher forcing

Модель 2.0

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

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

    1. В целях снижения сложности я отделил полоски от хитпойнтов, чтобы от предыдущего3^4=81класс становится2^4=16классов, что значительно снижает сложность.
    1. От одной модели к двум моделям. Раньше напрямую оценивался тип ключа, но во многих случаях нулей будет больше, поэтому эффект нехороший. Теперь я добавил классификатор, чтобы сначала определить, есть ли полоса или точка попадания, а затем определить тип ключа. Таким образом, если есть хитпойнты или полоски, модель типа ключа обязательно сгенерирует тип ключа, а не будет напрямую пустой.
    1. Упростите операцию, сначала создайте очки жизни, а затем регенерируйте длинные полоски. Если они перекрываются, полоса перекрывает точку попадания. Однако вероятность, конечно, невелика, и на большой эффект это не влияет.

Конечный эффект такой же, как на видео моей станции B. Всего сгенерировано три песни:China-P,Цзинчжэ,весеннее равноденствие. Общий эффект вроде нормальный.

Код для этой статьи:GitHub.com/yourado/AI_no…

(Этот код написан очень беспорядочно. Я чувствую, что большинство людей не будут его изучать, поэтому нет сортировки и оптимизации. Базовый код — это seq2seq, а затем есть куча данных для построения и разбора кода .)