Sfida Tweet musicale


37

Questa è la versione audio della sfida di codifica delle immagini di Twitter .

Progettare un formato di compressione audio in grado di rappresentare almeno un minuto di musica in 140 byte o meno di testo codificato UTF-8 stampabile.

Implementalo scrivendo un programma da riga di comando che accetta i seguenti 3 argomenti (dopo il nome del programma stesso):

  1. La stringa encodeo decode.
  2. Il nome file di input.
  3. Il nome del file di output.

(Se il tuo linguaggio di programmazione preferito non ha la capacità di usare argomenti della riga di comando, puoi usare un approccio alternativo, ma devi spiegarlo nella tua risposta.)

L' encodeoperazione verrà convertita dal formato audio prescelto nel formato compresso "tweet" e l' decodeoperazione verrà convertita dal formato "tweet" nel formato audio originale. (Naturalmente, ci si aspetta che implementi la compressione con perdita, quindi il file di output non deve essere identico all'input, solo nello stesso formato.)

Includi nella tua risposta:

  • Il codice sorgente del tuo programma, per intero. (Se è troppo lungo per questa pagina, potresti ospitarla altrove e pubblicare un link ad essa.)
  • Una spiegazione di come funziona.
  • Almeno un esempio, con un collegamento ai file audio originali, al testo "tweet" che comprime e al file audio ottenuto decodificando il tweet. (Il risponditore è responsabile delle affermazioni sul "fair use" del copyright.)

Regole

  • Mi riservo il diritto di colmare eventuali lacune nelle regole del concorso in qualsiasi momento.
  • [Modificato il 24 aprile] Per l'input della tua encodefunzione (e l'output della tua decodefunzione), puoi utilizzare qualsiasi formato audio comune ragionevole, sia esso:
    • Forma d'onda non compressa, come WAV.
    • Forma d'onda compressa, come MP3.
    • Stile "spartito", come il MIDI.
  • Il formato "tweet" compresso deve effettivamente codificare i suoni nel file di input. Pertanto, i seguenti tipi di output non contano:
    • Un URI o un percorso di file che fornisce la posizione in cui è memorizzato l'output effettivo.
    • Una chiave per una tabella di database in cui l'output effettivo viene archiviato come BLOB.
    • Qualcosa di simile.
  • Il tuo programma deve essere progettato per comprimere file musicali generici , quindi non fare cose che sono ovviamente troppo legate al tuo esempio specifico. Ad esempio, se stai dimostrando "Twinkle, Twinkle, Little Star", la tua routine di compressione non dovrebbe codificare un simbolo specifico per la sequenza do-do-so-so-la-la-so.
  • L'output del tuo programma dovrebbe effettivamente essere in grado di passare attraverso Twitter e di uscire incolume. Non ho un elenco dei caratteri esatti supportati, ma cerco di attenermi a lettere, cifre, simboli e punteggiatura; ed evita i personaggi di controllo, combinando personaggi, marcatori BIDI o altre cose strane come quella.
  • È possibile inviare più di una voce.

Criteri di valutazione

Questo è un concorso di popolarità (vale a dire, la maggior parte dei voti netti vince), ma gli elettori sono invitati a considerare quanto segue:

Precisione

  • Riesci ancora a riconoscere la canzone dopo che è stata compressa?
  • Suona bene?
  • Riesci ancora a riconoscere quali strumenti vengono suonati?
  • Riesci ancora a riconoscere i testi? (Questo è probabilmente impossibile, ma sarebbe impressionante se qualcuno lo realizzasse.)

Complessità

La scelta della canzone di esempio è importante qui.

  • [Aggiunto il 24 aprile] Questa sfida sarà più semplice con MIDI o formati simili. Tuttavia, se si prende lo sforzo supplementare per farlo funzionare con formati di forme d'onda, ciò merita ulteriore credito.
  • Qual è la struttura? Certo, puoi soddisfare il requisito di un minuto semplicemente ripetendo le stesse 4 misure un numero arbitrario di volte. Ma strutture di canzoni più complesse meritano più punti.
  • Il formato può gestire molte note suonate contemporaneamente?

Il codice

  • Mantenerlo il più breve e semplice possibile. Tuttavia, questo non è un codice golf, quindi la leggibilità conta più del conteggio dei caratteri.
  • Anche algoritmi intelligenti e complicati vanno bene, purché giustificati da una migliore qualità dei risultati.

9
L'uso del MIDI contro il WAV è una sfida drasticamente diversa. Penso che dovresti limitare i formati solo a WAV.
GroveNL

10
Sono ansioso di vedere qualsiasi soluzione, ma ad essere sincero: impacchettare 60s di suono in 140 byte significa che hai meno di 19 bit al secondo disponibili. Esistono alcuni codificatori vocali ultra efficienti, che funzionano a 300 bps, ma questi sono in grado di decodificare solo in fonemi sintetizzati con l'obiettivo di produrre un linguaggio comprensibile e non sono in alcun modo in grado di codificare la musica.
jarnbjo,

2
Stai chiedendo un software con fattori di compressione molti ordini di grandezza maggiori dell'attuale stato dell'arte. Se vuoi risposte sensate (vale a dire, senza coinvolgere composizioni come 4'33 " o Funeral March for the Obsequies of a Deaf Man ), ti incoraggio a ridurre il
ossifrage schizzinoso

3
@squeamishossifrage non ha detto che doveva sembrare riconoscibile, comunque.
cjfaure,

5
C'è un argomento in chat (e il giorno seguente) sul fatto che tu intenda effettivamente 140 byte o 140 caratteri tweet .
Peter Taylor,

Risposte:


26

Scala

Certo, sarebbe più facile codificare i file MIDI, ma chi ha un mucchio di file MIDI in giro? Non è il 1997!

Per prima cosa: ho deciso di interpretare un "byte Unicode" come un "carattere Unicode" e di usare caratteri CJK, perché:

  • Corrisponde alla sfida dell'immagine
  • Twitter è forte con questo
  • Ho davvero bisogno di quei pezzi

Ci sono alcuni trucchi che uso per spremere ogni ultima goccia di entropia dalle fonti:

In primo luogo, la musica è fatta con note. Inoltre, generalmente consideriamo la stessa nota in un'ottava diversa come la stessa nota (motivo per cui una chitarra a 12 corde suona bene), quindi abbiamo solo 12 possibilità di codifica. (quando suono B, ad esempio, in realtà emetto un accordo, costituito esclusivamente da B in tutte le ottave, un po 'come una chitarra a 12 corde).

Successivamente, ricordo dalle lezioni di musica del liceo che la maggior parte delle transizioni delle note sono piccole (su o giù di una nota). I salti sono meno comuni. Questo ci dice che probabilmente c'è meno entropia nelle dimensioni del salto che nelle note stesse.

Quindi, il nostro approccio è quello di suddividere la nostra sorgente in un numero di blocchi: ho trovato che 14 blocchi al secondo funzionavano bene (nota a margine, mi ero sempre chiesto perché l'audio fosse codificato a 44100 Hz. Si scopre che 44100 ha molti fattori, quindi avrei potuto scegliere 1, 2, 3, 4, 5, 6, 7, 9, 10, 12, 14, 15, 18, 20, 21, 25, 28 o 30 blocchi al secondo, e si sarebbe diviso in modo pulito ). Abbiamo quindi FFT questi blocchi (beh, tecnicamente non veloce, poiché la libreria che ho usato non è veloce per i blocchi non-power-of. 2 E tecnicamente ho usato una trasformata di Hartley , non di Fourier).

Troviamo quindi la nota che suona più forte (ho usato la ponderazione A , con cut-off alti e bassi, principalmente perché è più facile da implementare) e codificare questa nota o codificare il silenzio (il rilevamento del silenzio si basa su SNR - SNR basso è silenzio).

Traduciamo quindi le nostre note codificate in salti e le alimentiamo a un codificatore aritmetico adattivo. Il processo per la traduzione in testo è simile alla domanda sulla compressione delle immagini (ma comporta un uso abusivo di BigInteger).

Fin qui tutto bene, ma se il campione avesse troppa entropia? Usiamo un modello psicoacustico grezzo per rimuoverne alcuni. Il salto di entropia più basso è "nessun cambiamento", quindi guardiamo i nostri dati FFT per cercare di trovare blocchi in cui l'ascoltatore probabilmente non noterà se continuiamo a suonare la nota precedente, cercando blocchi in cui la nota del blocco precedente è quasi forte come la nota più forte (dove "quasi" è controllato dal parametro quality).

Quindi, abbiamo un obiettivo di 140 caratteri. Iniziamo con la codifica con qualità 1.0 (qualità massima) e vediamo quanti caratteri sono. Se sono troppi, scendiamo a 0,95 e ripetiamo, fino a quando non arriviamo a 140 caratteri (o rinunciamo dopo la qualità 0,05). Questo rende l'encoder un encoder n-pass, per n <= 20 (anche se è enormemente inefficiente anche in altre aree, quindi m'eh).

L'encoder / decodificatore prevede l'audio in formato mono s16be. Ciò può essere ottenuto utilizzando avconv come:

#decoding ogg to s16be, and keeping only the first 60s
avconv -i input.ogg -ac 1 -ar 44100 -f s16be -t 60s input.raw
#encoding s16be to mp3
avconv -f s16be -ac 1 -ar 44100 -i output.raw output.mp3

Per eseguire l'encoder:

sbt "run-main com.stackexchange.codegolf.twelvestring.TwelveString encode input.raw encoded.txt"
sbt "run-main com.stackexchange.codegolf.twelvestring.TwelveString decode encoded.txt output.raw"

Codice completo su https://github.com/jamespic/twelvestring .

Trabocchetto da notare: avrai bisogno della libreria di codifica aritmetica di nayuki, che al momento non ha artefatti Maven disponibili. Invece, dovrai costruire e installare localmente il fork di Developster .

Ed ecco alcuni esempi. Sembrano orribili, ma quasi riconoscibili:

  • Quinto di Beethoven: originale , codificato - 刲 檁 囉 罓 佖 镱 賑 皌 蝩 蔲 恁 峕 逊 躹 呯 兲 搆 摼 蝺 筶 槛 庬 一 掛 獴 趤 笲 銗 娵 纜 喫 覤 粠 僭 鱨 蠰 咍 咍姑椻趕挍呪白鸞盙宠埘謭擆闯脲誜忘椐笸囃庣稨俖咛脔湙弻籚砌鍖裏橈镙訁鹻塿骱踠筟七趇杅峇敖窈裞瘫峦咰呹櫬茏蛏姆臸胝婁遼憀麁黦掏毈喙眝綄鴀耢椚筤菮蟞斗俼湛营筬禴籙嬧窻丄
  • Fur Elise: originale , codificata - 訖 忢 擫 鏝 拪 纒 铇 鯪 薯 鉰 孝 暱 埛 痏 絘 僌 莻 暆 鴈 屖 鳮 絒 婘 譮 蠣 託 騶 腀 饚 緂 柤 碠 瞢 碩 脅 歙 鄔 敗 辦 冏 酾 酾苯劺誺軩忇穤锳婁伉巠桭晘酉筟緵俅怚尵鎽蜓崁飿嘔但鈐謽酝屮呤誥俊覊鶮龔癸埙煂臙牎繬肜摲炚雗頨款驦燈菩凧咁楾媡夥俰欓焵韀冊嗥燠鱧駟髉
  • Twinkle Twinkle Little Star: originale , codificato - 欠 悺 矜 莳 錥 鷗 谴 裴 皽 鳺 憝 漿 箔 皇 殤 鸧 蜻 猻 丱
  • Un divertente chiptune: originale , codificato - 简 詐 諥 尘 牿 扫 鲑 龮 箫 颫 齰 蠏 騁 煟 靸 阒 萎 囦 鮇 雝 愯 訖 芉 馄 鈿 鬦 嶏 觲 沠 丆 贀 蛑 蛀 漥 荤 鲠 僵 麑 僵 僵 僵灼攲陇亄鹘琾業纟鵼牤棌匩碆踬葫鶙懲欃铳樯柇鋡疡衻澯伅墇搢囻荸香貱夹咽蟽籐屈锂蛉袒貊屨鈦夜镤沄鍡唦魮骬乔蚖醶矕咻喸碋利褼裊匎嶮窢幘六沧鼝瞮坡葍帷锆邵旻符琨鱴郤栱烇仾椃騋荄嘵統篏珆罽

Aggiornare

Ho modificato la soglia del silenzio nel codice e ricodificato. Le codifiche sono state aggiornate di conseguenza. Inoltre, ho aggiunto un altro brano (tecnicamente non open source, ma dubito che il detentore del copyright originale sentirà il loro IP minacciato), solo per divertimento:

  • Marcia Imperiale: originale , codificata - 岼 讶 湠 衢 嫵 焅 喋 藭 霔 憣 嗗 颟 橳 蕖 匵 腾 嗅 鈪 芔 区 顕 樭 眀 冂 常 僠 寝 萉 乹 俚 戂 闤 灈 蟑 拷 邢 音 褈 霈 媬 盒 萳 璥唂焰銯艉鶱縩巻痭虊窻熲紆耺哅淙苉嘏庸锺禒旇蘄籷遪刨繱蕖嬯摺祑仰軈牰杊瘷棏郖弘卄浕眮騜阖鏴鶺艂税寛柭菸採偋隆兎豅蚦紛襈洋折踜跅軩树爺奘庄玫亳攩獼匑仡葾昐炡瞱咏斎煟价藭恐鷖璌榍脅樐嬨勀茌愋

Altri aggiornamenti

Ho modificato un po 'l'encoder e ha avuto un impatto sorprendente sulla qualità (avevo dimenticato che in DHT i segnali sfasati sono effettivamente negativi, quindi ignoravo i segnali sfasati).

Una versione precedente del codice ha appena preso il più grande di questi segnali sfasati, ma ora prendiamo l'RMS. Inoltre, ho aggiunto una funzione di finestra abbastanza conservativa all'encoder (Tukey, alpha 0.3), per provare a combattere l'artefatto.

Tutto è aggiornato di conseguenza.


1
Non riesco a suonare il Twinkle Twinkle e il chiptune. Fur Elise è piuttosto vicina, mentre Beethoven è appena riconoscibile, ahah.
solo

Vuoi provare di nuovo Twinkle Twinkle e Chiptune? Penso di aver corretto gli URL.
James_pic,

1
Ora funziona. Il Twinkle Twinkle è abbastanza in discesa. Ma cosa sta succedendo alla fine?
metà

Sì, non sono del tutto sicuro di cosa stia succedendo alla fine. Sospetto che stia accadendo da qualche parte nella codifica aritmetica. In una versione precedente del codice, il flusso era terminato da un simbolo EOF, ma in alcuni casi il decodificatore non riusciva a leggere il simbolo EOF. Sospetto di non aver chiuso BitOutputStream correttamente, ma lo esaminerò.
James_pic,

1
Sì, in effetti era esattamente così. C'era un BitOutputStream::closemetodo che avevo dimenticato di chiamare. Riparerò il codice e aggiornerò gli output.
James_pic

11

Pitone

Non mi occupo in modo particolare di UTF-8, quindi la mia richiesta supera i 140 byte richiesti. Non faccio affermazioni sull'utilità, l'accuratezza o l'efficienza della mia soluzione.

Ho usato una frequenza di campionamento di 44100 Hz per l'ingresso e l'uscita. SAMPLES_PER_BYTE controlla la qualità della conversione. Più basso è il numero, migliore è la qualità del suono. I valori che ho usato sono riportati nella sezione dei risultati.

uso

Codificare

Il file di input dovrebbe essere un wav. Codifica solo il primo canale.

twusic.py -e [input file] > output.b64

Decodificare

twusic.py -d [input file] > output.raw

Riproduzione della musica decodificata

aplay -f U8 --rate=[rate of input file] output.raw

Il codice

#!/usr/bin/env python
SAMPLES_PER_BYTE = 25450

from math import sin, pi, log
from decimal import Decimal

PI_2 = Decimal(2) * Decimal(pi)

FIXED_NOTE = Decimal('220') # A
A = Decimal('2') ** (Decimal('1') / Decimal('12'))
A_LN = A.ln()

def freq(note):
    return FIXED_NOTE * (A ** Decimal(note))

def note(freq):
    return (Decimal(freq) / FIXED_NOTE).ln() / A_LN

VOLUME_MAX = Decimal('8')
def volume(level):
    return Decimal('127') * (Decimal(level+1).ln() / VOLUME_MAX.ln())

def antivolume(level):
    x = Decimal(level) / Decimal('127')
    y = VOLUME_MAX ** x
    return y - 1

NOTES = [freq(step) for step in xrange(-16, 16)]
VOLUMES = [volume(level) for level in xrange(0, VOLUME_MAX)]


def play(stream, data):
    t = 0
    for x in data:
        x = ord(x)
        w = PI_2 * NOTES[(x&0xf8) >> 3] / Decimal(16000)
        a = float(VOLUMES[x&0x07])
        for _ in xrange(0, SAMPLES_PER_BYTE):
            stream.write(chr(int(128+(a*sin(w*t)))))
            t += 1

NOTE_MAP = {'A': 0b00000000,
    'g': 0b00001000,
    'G': 0b00010000,
    'f': 0b00011000,
    'F': 0b00100000,
    'E': 0b00101000,
    'd': 0b00110000,
    'D': 0b00111000,
    'c': 0b01000000,
    'C': 0b01001000,
    'B': 0b01010000,
    'a': 0b01011000}

def convert(notes, volume):
    result = []
    for n in notes:
        if n == ' ':
            result += '\00'
        else:
            result += chr(NOTE_MAP[n] | (volume & 0x07)) * 2
    return ''.join(result)

TWINKLE = convert('C C G G A A GG' +
                    'F F E E D D CC' +
                    'G G F F E E DD' +
                    'G G F F E E DD' +
                    'C C G G A A GG' +
                    'F F E E D D CC', 0x7)

if __name__ == '__main__':
    from base64 import b64encode, b64decode
    import numpy as np
    from numpy.fft import fft, fftfreq
    import wave
    import sys

    if len(sys.argv) != 3:
        print 'must specify -e or -d plus a filename'
        sys.exit(1)

    if sys.argv[1] == '-e':
        w = wave.open(sys.argv[2], 'rb')

        try:
            output = []
            (n_channels, sampwidth, framerate, n_frames, comptype, compname) = w.getparams()
            dtype = '<i' + str(sampwidth)

            # Find max amplitude
            frames = np.abs(np.frombuffer(w.readframes(n_frames), dtype=dtype)[::n_channels])
            max_amp = np.percentile(frames, 85)

            w.rewind()

            read = 0
            while read < n_frames:
                to_read = min(n_frames-read, SAMPLES_PER_BYTE)
                raw_frames = w.readframes(to_read)
                read += to_read

                frames = np.frombuffer(raw_frames, dtype=dtype)[::n_channels]
                absolute = np.abs(frames)
                amp = np.mean(absolute)

                amp = int(round(antivolume(min((amp / max_amp) * 127, 127))))

                result = fft(frames)
                freqs = fftfreq(len(frames))

                while True:
                    idx = np.argmax(np.abs(result)**2)
                    freq = freqs[idx]
                    hz = abs(freq * framerate)
                    if hz > 0:
                        break
                    result = np.delete(result, idx)
                    if len(result) <= 0:
                        hz = 220
                        amp = 0
                        break

                n = int(round(note(hz)))
                n &= 0x1F
                n <<= 3
                n |= amp & 0x07
                output.append(chr(n))
        finally:
            w.close()
        print b64encode(''.join(output)).rstrip('=')
    else:
        with open(sys.argv[2], 'rb') as f:
            data = f.read()
        data = data + '=' * (4-len(data)%4)
        play(sys.stdout, b64decode(data))

Gli input

La mia presentazione ufficiale è Impromptu per Pianoforte e Beatbox di Kevin MacLeod . Per questo file ho usato un SAMPLES_PER_BYTE di 25450.

Mi sono anche preso la libertà di codificare Twinkle, Twinkle, Little Star con un SAMPLES_PER_BYTE di 10200. Sembra molto meglio.

L'output

Improvvisato per Pianoforte e Beatbox

aWnxQDg4mWqZWVl6W+LyOThfHOPyQThAe4x5XCqJK1EJ8Rh6jXt5XEMpk1Epe5JqTJJDSisrkkNCSqnSkkJDkiorCZHhCxsq8nlakfEp8vNb8iqLysp6MpJ7s4x7XlxdW4qKMinJKho

collegamento

Twinkle, Twinkle Little Star

HBobGlJSUlJSY2FlYVNRUVFCQkJCQjs5PDksKisqGxoZGVFTUVNRREFDQjs6OjoqKykpKVRRVFJDQkJCOjs6OzksKikpGxobG1JSUlNRZWFlYVNSUVFCQkJDQTw5PDorKisqGhsZGRk

collegamento

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.