Imprime la letra de "Twinkle Twinkle Little Star"

24

Su objetivo es imprimir la letra de la canción "Twinkle Twinkle Little Star" a medida que se toca cada nota.

El micrófono de la computadora escuchará notas. Si el tono (pero no necesariamente la duración) de la nota es correcto, imprima la sílaba apropiada. De lo contrario, no hagas nada. Cada nota tendrá una duración de al menos medio segundo, y habrá un descanso de al menos un cuarto de segundo entre las notas.

Use las notas musicales proporcionadas aquí y las siguientes letras: (Las líneas verticales representan saltos de sílabas).

Twin | kle, twin | kle, lit | tle estrella,

Cómo gané lo que eres.

Arriba un mundo tan alto

Como un dia | mond en el cielo.

Twin | kle, twin | kle, lit | tle estrella,

Cómo gané lo que eres.

Una grabación de la música se puede encontrar aquí .

Ejemplo

La computadora oye una C media e imprime "Twin"

Oye otra C media e imprime "kle"

Luego escucha otra C media (nota incorrecta) y no hace nada.

Luego oye la G arriba de la C central e imprime "gemelo" y así sucesivamente.

Reglas

  • La puntuación debe ser como se muestra.
  • El espaciado debe ser como se muestra (con espacios y líneas nuevas).
  • El espacio en blanco se puede imprimir junto con la sílaba anterior o siguiente.
Ypnypn
fuente
2
¿Hay alguna forma de relajarse "debe imprimirse antes de que finalice la nota?" Con notas de 1/16 de segundo, incluso si dedica 3/4 de ese tiempo al muestreo, solo tiene ~ 47 ms de sonido para trabajar. Eso proporciona una resolución de frecuencia bastante turbia para notas de rango medio.
Geobits
@Geobits Buen punto; Quité esa regla.
Ypnypn
1
¡Este es el primer rompecabezas con entrada de audio que pude encontrar! Felicidades!
No es que Charles
1
¿El título está mal escrito a propósito para diferenciar los dos centelleos?
Rainbolt
1
¿Podríamos tener un enlace a un archivo de audio para probar?
Aficiones de Calvin

Respuestas:

7

Python 3 - solución parcial ( 760 742 734 710 705 657 caracteres)

(Última edición; lo prometo)

Esto parece un problema muy, muy, muy difícil (especialmente reconocer dónde comienzan o terminan las notas). La transcripción automática de música parece un tema de investigación abierto (no es que yo sepa nada al respecto). Así que aquí hay una solución parcial que no hace ninguna segmentación de notas (por ejemplo, imprime "Twinkle" de una vez cuando escucha la frecuencia) y probablemente solo funciona para ese archivo 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

Esto requiere...

Cambie la A = -52 (amplitud mínima) en la línea superior dependiendo de su micrófono, cantidad de sonido ambiental, qué tan fuerte suena la canción, etc. En mi micrófono, menos de -57 parece captar mucho ruido extraño y más de -49 requiere que lo juegues muy alto.

Esto podría jugar mucho más golf; Estoy seguro de que hay formas de guardar un montón de caracteres en la matriz de palabras en particular. Este es mi primer programa no trivial en python, por lo que todavía no estoy muy familiarizado con el lenguaje.

Robé el código para la detección de frecuencia mediante autocorrelación de https://gist.github.com/endolith/255291

Sin golf:

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
Robert Fraser
fuente
Escribí un poco de ayuda de sintaxis para ti. Verifique las líneas 14-29 y 80-88. pastebin.com/W9XSYwMJ
seequ
@Sieg - Impresionante; ¡Gracias! Los viejos hábitos son difíciles de romper;
Robert Fraser