написать впереди
Мне всегда нравилось играть в музыкальные игры, от самого раннего ритм-мастера до 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 ударов в минуту, т.е.Секунды в такт, то это 27 от
секунд до
в этот раз.
Давайте посмотрим на значение 2 и 4 в этом [27, 2, 4]: последние 4 на самом деле представляют маленькие доли в доле, здесь всего 4 маленьких доли, так что доля делится на четыре части, а 2 — третий тик (считая с 0).
Таким образом, это [27, 2, 4] представляетсекунд в этот момент времени.
mc_data["note"][-1]
В последнем элементе заметки он особенный, он загружает музыку и устанавливает смещение.
offset представляет собой минимальное значение опережения в миллисекундах, чтобы выровнять линию ритма ритма с ритмом музыки. На самом деле это значение выравнивания. Для получения подробной информации см.:воооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооо
Смещение здесь составляет 315 мс, так что предыдущий [27, 2, 4] фактически представляетсекунд этой точки.
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 типа:
- 
- пустой, без удара
 
- 
- обратите внимание, что является точкой атаки
 
- 
- начало длинной ноты, начало длинной ноты
 
- 
- длинная нота продолжать, непрерывная длинная нота
 
В функции эти три случая могут быть представлены 0, 1, 2, 3.
Чтобы упростить здесь, мы рассматриваем начало длинной ноты и продолжение как ключ, так что выходные результаты равны 0, 1 и 2.
ключевое кодирование
Поскольку это аудиоигра с 4 клавишами, для каждой позиции есть 3 ситуации: пустая, забастовка и полоса.
Таким образом, в общей сложностиВ среднем случае это может быть представлено 4-значным 3-значным числом, которое составляет от 0 до 80 при преобразовании в десятичное число.
Например, эта ключевая форма:
Другой пример следующий:
Формат данных
Таким образом, требуется много комплексного анализа данных.
Тогда каждый вход представляет собой матрицу 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 песни, и шум данных будет относительно большим.
Не буду здесь вдаваться в подробности, код тот же, что и выше, но есть еще несколько копий. Ниже приводится краткое изложение того, что я сделал.
- 
- В целях снижения сложности я отделил полоски от хитпойнтов, чтобы от предыдущегокласс становится классов, что значительно снижает сложность. 
 
- В целях снижения сложности я отделил полоски от хитпойнтов, чтобы от предыдущего
- 
- От одной модели к двум моделям. Раньше напрямую оценивался тип ключа, но во многих случаях нулей будет больше, поэтому эффект нехороший. Теперь я добавил классификатор, чтобы сначала определить, есть ли полоса или точка попадания, а затем определить тип ключа. Таким образом, если есть хитпойнты или полоски, модель типа ключа обязательно сгенерирует тип ключа, а не будет напрямую пустой.
 
- 
- Упростите операцию, сначала создайте очки жизни, а затем регенерируйте длинные полоски. Если они перекрываются, полоса перекрывает точку попадания. Однако вероятность, конечно, невелика, и на большой эффект это не влияет.
 
Конечный эффект такой же, как на видео моей станции B. Всего сгенерировано три песни:China-P,Цзинчжэ,весеннее равноденствие. Общий эффект вроде нормальный.
Код для этой статьи:GitHub.com/yourado/AI_no…
(Этот код написан очень беспорядочно. Я чувствую, что большинство людей не будут его изучать, поэтому нет сортировки и оптимизации. Базовый код — это seq2seq, а затем есть куча данных для построения и разбора кода .)