Imprima a letra de "Twinkle Twinkle Little Star"


24

Seu objetivo é imprimir a letra da música "Twinkle Twinkle Little Star" à medida que cada nota é tocada.

O microfone do computador ouvirá anotações. Se o tom (mas não necessariamente o comprimento) da nota estiver correto, imprima a sílaba apropriada. Caso contrário, não faça nada. Cada nota terá a duração de pelo menos meio segundo e haverá uma pausa de pelo menos um quarto de segundo entre as notas.

Use as notas musicais fornecidas aqui e as seguintes letras: (Linhas verticais representam quebras de sílabas.)

Gêmeo | kle, gêmeo | kle, iluminado | estrela tle,

Como eu vou saber o que você é.

Até um mundo tão alto,

Como um dia | segundo no céu.

Gêmeo | kle, gêmeo | kle, iluminado | estrela tle,

Como eu vou saber o que você é.

Uma gravação da música pode ser encontrada aqui .

Exemplo

O computador ouve um C médio e imprime "Twin"

Ouve outro C do meio e imprime "kle"

Então ouve outro C do meio (nota errada) e não faz nada.

Então ele ouve o G acima do meio C e imprime "gêmeo" e assim por diante.

Regras

  • A pontuação deve ser como mostrada.
  • O espaçamento deve ser como mostrado (com espaços e novas linhas).
  • O espaço em branco pode ser impresso junto com a sílaba anterior ou a próxima.

2
Existe alguma maneira de relaxar "deve ser impresso antes do final da nota?" Com notas de 1/16 de segundo, mesmo que você dedique 3/4 desse tempo à amostragem, você terá apenas 47ms de som para trabalhar. Isso fornece uma resolução de frequência bastante sombria para notas de médio alcance.
Geobits 14/07

@Geobits Bom ponto; Eu removi essa regra.
Ypnypn

11
Este é o primeiro quebra-cabeça usando a entrada de áudio que eu pude encontrar! Parabéns!
Não que Charles,

11
O título foi escrito incorretamente de propósito para diferenciar os dois twinkles?
Rainbolt 14/07/2014

11
Poderíamos ter um link para um arquivo de áudio para teste?
9788 Calvin's Hobbies

Respostas:


7

Python 3 - Solução parcial ( 760 742 734 710 705 657 caracteres)

(Última edição; prometo)

Parece um problema muito, muito, muito difícil (especialmente para reconhecer onde as notas começam ou terminam). A transcrição automática de música parece um tópico de pesquisa aberto (não que eu saiba nada sobre isso). Então, aqui está uma solução parcial que não faz nenhuma segmentação de nota (por exemplo, imprime "Twinkle" de uma só vez quando ouve a frequência) e provavelmente só funciona para esse arquivo ogg específico:

A=-52
F=44100
C=4096
import pyaudio as P
import array
import scipy.signal as G
import numpy as N
import math
L=math.log
i=0
j=[9,2,0,2,4,5,7,9]
k=[2,4,5,7]
n=j+k+k+j
w="Twinkle, |twinkle, |little |star,\n|How I |wonder |what you |are.\n|Up a|bove the |world so |high,\n|Like a |diamond |in the |sky.\n".split('|')
w+=w[:8]
e=P.PyAudio().open(F,1,8,1,0,None,0,C)
while i<24:
 g=array.array('h',e.read(C));b=sum(map(abs,g))/C
 if b>0 and 20*L(b/32768,10)>A:
  f=G.fftconvolve(g,g[::-1])[C:];d=N.diff(f);s=0
  while d[s]<=0:s+=1
  x=N.argmax(f[s:])+s;u=f[x-1];v=f[x+1]
  if int(12*L(((u-v)/2/(u-2*f[x]+v)+x)*F/C/440,2))==n[i]+15:print(w[i],end='',flush=1);i+=1

Isto exige...

Altere A = -52 (amplitude mínima) na linha superior, dependendo do microfone, quantidade de som ambiente, quão alta a música está tocando etc. No meu microfone, menos de -57 parece captar muito ruído estranho. e mais de -49 exige que você toque muito alto.

Isso poderia ser muito mais jogado; Tenho certeza de que existem maneiras de salvar um monte de caracteres na matriz de palavras em particular. Este é o meu primeiro programa não trivial em python, então ainda não estou muito familiarizado com a linguagem.

Roubei o código para detecção de frequência via autocorrelação em https://gist.github.com/endolith/255291

Ungolfed:

import pyaudio
from array import array
import scipy.signal
import numpy
import math
import sys

MIN_AMPLITUDE = -52
FRAMERATE = 44100

def first(list):
    for i in range(len(list)):
        if(list[i] > 0):
            return i
    return 0

# Based on: https://en.wikipedia.org/wiki/Decibel#Acoustics
def getAmplitude(sig):
    total = 0;
    elems = float(len(sig))
    for x in sig:
        total += numpy.abs(x) / elems
    if(total == 0):
        return -99
    else:
        return 20 * math.log(total / 32768., 10)    

# Based on: https://en.wikipedia.org/wiki/Piano_key_frequencies
def getNote(freq):
    return int(12 * math.log(freq / 440, 2) + 49)

# --------------------------------------------------------------------------
# This is stolen straight from here w/ very slight modifications: https://gist.github.com/endolith/255291
def parabolic(f, x):
    return 1/2. * (f[x-1] - f[x+1]) / (f[x-1] - 2 * f[x] + f[x+1]) + x

def getFrequency(sig):
    # Calculate autocorrelation (same thing as convolution, but with
    # one input reversed in time), and throw away the negative lags
    corr = scipy.signal.fftconvolve(sig, sig[::-1], mode='full')
    corr = corr[len(corr)/2:]

    # Find the first low point
    diffs = numpy.diff(corr)

    # Find the next peak after the low point (other than 0 lag). This bit is
    # not reliable for long signals, due to the desired peak occurring between
    # samples, and other peaks appearing higher.
    # Should use a weighting function to de-emphasize the peaks at longer lags.
    start = first(diffs)
    peak = numpy.argmax(corr[start:]) + start
    return parabolic(corr, peak) * (FRAMERATE / len(sig))
# --------------------------------------------------------------------------

# These are the wrong keys (ie it is detecting middle C as an A), but I'm far too lazy to figure out why.
# Anyway, these are what are detected from the Wikipedia .ogg file:
notes = [73,          66,           64,       66,         68,       69,        71,          73,       66,     68,          69,         71,         66,        68,         69,        71      ] 
words = ["Twinkle, ", "twinkle, ", "little ", "star,\n",  "How I ", "wonder ", "what you ", "are.\n", "Up a", "bove the ", "world so ", "high,\n", "Like a ", "diamond ", "in the ", "sky.\n"]
notes += notes[:8]
words += words[:8]

pa = pyaudio.PyAudio()
stream = pa.open(format=pyaudio.paInt16, channels = 1, rate = FRAMERATE, input = True, frames_per_buffer = 4096)
idx = 0
while(idx < len(notes)):
    # Read signal
    sig = array('h', stream.read(4096))
    if(getAmplitude(sig) > MIN_AMPLITUDE):
        note = getNote(getFrequency(sig))
        if(note == notes[idx]):
            sys.stdout.write(words[idx])
            sys.stdout.flush()
            idx += 1

Eu escrevi uma pequena ajuda de sintaxe para você. Verifique as linhas 14-29 e 80-88. pastebin.com/W9XSYwMJ
seequ

@ Sieg - impressionante; obrigado! É difícil quebrar velhos hábitos;
Robert Fraser
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.