Musikalische Tweet Challenge

37

Dies ist die Audioversion der Twitter Image Encoding Challenge .

Entwerfen Sie ein Audiokomprimierungsformat, das mindestens eine Minute Musik in 140 Byte oder weniger druckbarem UTF-8-codiertem Text darstellen kann.

Implementieren Sie es, indem Sie ein Befehlszeilenprogramm schreiben, das die folgenden 3 Argumente (nach dem Namen des Programms selbst) enthält:

  1. Die Zeichenfolge encodeoder decode.
  2. Der eingegebene Dateiname.
  3. Der Ausgabedateiname.

(Wenn Ihre bevorzugte Programmiersprache keine Befehlszeilenargumente verwenden kann, können Sie einen alternativen Ansatz verwenden, der jedoch in Ihrer Antwort erläutert werden muss.)

Die encodeOperation konvertiert das von Ihnen gewählte Audioformat in das komprimierte Tweet-Format und die decodeOperation konvertiert das Tweet-Format in das ursprüngliche Audioformat. (Natürlich wird von Ihnen erwartet, dass Sie eine verlustbehaftete Komprimierung implementieren, sodass die Ausgabedatei nicht mit der Eingabe identisch sein muss, sondern nur im selben Format.)

Nehmen Sie in Ihre Antwort auf:

  • Der vollständige Quellcode Ihres Programms. (Wenn diese Seite zu lang ist, können Sie sie an einer anderen Stelle hosten und einen Link dazu posten.)
  • Eine Erklärung, wie es funktioniert.
  • Zumindest ein Beispiel mit einem Link zu den ursprünglichen Audiodateien, dem „Tweet“ -Text, auf den es komprimiert wurde, und der Audiodatei, die durch Dekodieren des Tweets erhalten wurde. (Der Beantworter ist für die Behauptungen des Urheberrechts im Hinblick auf eine „faire Verwendung“ verantwortlich.)

Regeln

  • Ich behalte mir das Recht vor, Lücken in den Wettbewerbsregeln jederzeit zu schließen.
  • [Bearbeitet am 24. April] Für die Eingabe Ihrer encodeFunktion (und die Ausgabe Ihrer decodeFunktion) können Sie jedes vernünftige, übliche Audioformat verwenden, sei es:
    • Nicht komprimierte Wellenform, wie WAV.
    • Komprimierte Wellenform, wie MP3.
    • Notenstil wie MIDI.
  • Ihr komprimiertes "Tweet" -Format muss die Sounds in der Eingabedatei tatsächlich codieren. Die folgenden Ausgabetypen zählen also nicht :
    • Ein URI oder Dateipfad, der den Speicherort angibt, an dem die tatsächliche Ausgabe gespeichert ist.
    • Ein Schlüssel zu einer Datenbanktabelle, in der die tatsächliche Ausgabe als Blob gespeichert ist.
    • Sowas ähnliches.
  • Ihr Programm muss so konzipiert sein, dass es generische Musikdateien komprimiert. Tun Sie also nichts, was zu offensichtlich mit Ihrem speziellen Beispielsong zusammenhängt. Wenn Sie beispielsweise "Twinkle, Twinkle, Little Star" demonstrieren, sollte Ihre Komprimierungsroutine kein bestimmtes Symbol für die Sequenz "do-do-so-so-la-la-so" fest codieren.
  • Die Ausgabe Ihres Programms sollte tatsächlich in der Lage sein, Twitter zu durchlaufen und unbeschadet herauszukommen. Ich habe keine genaue Liste der unterstützten Zeichen, versuche jedoch, mich an Buchstaben, Ziffern, Symbole und Satzzeichen zu halten. Vermeiden Sie Steuerzeichen, kombinieren Sie Zeichen, BIDI-Marker oder ähnliches.
  • Sie können mehr als einen Beitrag einreichen.

Beurteilungskriterien

Dies ist ein Beliebtheitswettbewerb (dh die meisten Netto-Upvotes gewinnen), aber die Wähler werden aufgefordert, Folgendes zu berücksichtigen:

Richtigkeit

  • Kannst du das Lied noch erkennen, nachdem es komprimiert wurde?
  • Hört es sich gut an?
  • Kannst du noch erkennen, welche Instrumente gespielt werden?
  • Kannst du die Texte noch erkennen? (Dies ist wahrscheinlich unmöglich, aber es wäre beeindruckend, wenn es jemandem gelingen würde.)

Komplexität

Die Wahl des Beispielsongs spielt hier eine Rolle.

  • [Hinzugefügt am 24. April] Diese Herausforderung ist mit MIDI oder ähnlichen Formaten am einfachsten. Wenn Sie sich jedoch mehr Mühe geben, damit es mit wellenformartigen Formaten funktioniert, verdient dies zusätzliche Anerkennung.
  • Wie ist die Struktur? Sicher, Sie können die Ein-Minuten-Anforderung erfüllen, indem Sie dieselben 4 Takte beliebig oft wiederholen. Komplexere Songstrukturen verdienen jedoch mehr Punkte.
  • Kann das Format mehrere gleichzeitig gespielte Noten verarbeiten?

Der Code

  • Halte es so kurz und einfach wie möglich. Dies ist jedoch kein Codegolf, daher ist die Lesbarkeit wichtiger als die Anzahl der Zeichen.
  • Clevere, komplizierte Algorithmen sind ebenfalls in Ordnung, sofern sie durch eine verbesserte Ergebnisqualität gerechtfertigt sind.
dan04
quelle
9
Die Verwendung von MIDI im Vergleich zu WAV ist eine völlig andere Herausforderung. Ich denke, Sie sollten die Formate nur auf WAV beschränken.
HainenNL
10
Ich bin gespannt auf Lösungen, aber um ehrlich zu sein: Wenn Sie 60s Sound in 140 Bytes packen, haben Sie weniger als 19 Bits pro Sekunde zur Verfügung. Es gibt einige hocheffiziente Sprachcodierer, die mit 300 Bit / s arbeiten. Diese können jedoch nur zu synthetisierten Phonemen decodieren, um verständliche Sprache zu erzeugen, und sie können in keiner Weise Musik codieren.
Jarnbjo
2
Sie fragen nach Software mit Komprimierungsfaktoren, die um viele Größenordnungen über dem aktuellen Stand der Technik liegen. Wenn Sie vernünftige Antworten (dh nicht - Zusammensetzungen wie denen 4'33" oder Trauermarsch für die Obsequies einer Taube ), würde ich Sie ermutigen , die Zeitbeschränkung auf 1 Sekunde fallen zu lassen.
zimperlich ossifrage
3
@squeamishossifrage hat er aber nicht gesagt, dass es erkennbar klingen muss.
cjfaure
5
Im Chat (und am folgenden Tag) wird darüber gestritten, ob Sie tatsächlich 140 Bytes oder einen Tweet mit 140 Zeichen bedeuten .
Peter Taylor

Antworten:

26

Scala

Sicher, es wäre einfacher, MIDI-Dateien zu kodieren, aber wer hat eine Menge MIDI-Dateien, die herumliegen? Es ist nicht 1997!

Das Wichtigste zuerst: Ich habe beschlossen, ein "Unicode-Byte" als "Unicode-Zeichen" zu interpretieren und CJK-Zeichen zu verwenden, weil:

  • Es passt zur Image-Herausforderung
  • Twitter ist cool damit
  • Ich brauche diese Teile wirklich

Es gibt ein paar Tricks, mit denen ich jeden letzten Tropfen Entropie aus den Quellen herausdrücke:

Erstens wird Musik mit Noten gemacht. Darüber hinaus betrachten wir dieselbe Note in einer anderen Oktave im Allgemeinen als dieselbe Note (weshalb eine 12-saitige Gitarre richtig klingt), sodass wir nur 12 Codierungsmöglichkeiten haben. (Wenn ich zum Beispiel B ausgebe, gebe ich tatsächlich einen Akkord aus, der in allen Oktaven nur aus B besteht, ein bisschen wie bei einer 12-saitigen Gitarre).

Als nächstes erinnere ich mich aus der Musikklasse, dass die meisten Notenübergänge klein sind (eine Note nach oben oder unten). Sprünge sind seltener. Dies sagt uns, dass es wahrscheinlich weniger Entropie in den Sprunggrößen als in den Noten selbst gibt.

Daher besteht unser Ansatz darin, unsere Quelle in eine Reihe von Blöcken zu unterteilen. Ich fand, dass 14 Blöcke pro Sekunde gut funktionieren (Anmerkung: Ich habe mich immer gefragt, warum Audio mit 44100 Hz codiert wurde. Es stellt sich heraus, dass 44100 viele Faktoren hat. Ich hätte also 1, 2, 3, 4, 5, 6, 7, 9, 10, 12, 14, 15, 18, 20, 21, 25, 28 oder 30 Blöcke pro Sekunde wählen können und es hätte sauber geteilt ). Wir haben dann diese Blöcke FFT (naja, technisch nicht schnell, da die Bibliothek, die ich verwendet habe, nicht schnell für Nicht-Potenz-2-Blöcke ist. Und technisch habe ich eine Hartley-Transformation verwendet , nicht Fourier).

Wir finden dann die Note, die am lautesten klingt (ich habe die A-Gewichtung mit hohen und niedrigen Cut-Offs verwendet, hauptsächlich, weil sie am einfachsten zu implementieren ist), und codieren diese Note oder codieren die Stille (die Stilleerkennung basiert auf SNR - niedrigem SNR) ist Stille).

Wir übersetzen dann unsere codierten Noten in Sprünge und geben sie an einen adaptiven arithmetischen Codierer weiter. Der Prozess für die Übersetzung in Text ähnelt der Frage der Bildkomprimierung (beinhaltet jedoch eine missbräuchliche Verwendung von BigInteger).

So weit, so gut, aber was ist, wenn die Probe zu viel Entropie hat? Wir verwenden ein grobes psychoakustisches Modell, um einige zu entfernen. Der niedrigste Entropiesprung ist "keine Änderung", daher untersuchen wir unsere FFT-Daten, um zu versuchen, Blöcke zu finden, bei denen der Hörer es wahrscheinlich nicht bemerkt, wenn wir die vorherige Note weiter spielen, indem wir nach Blöcken suchen, bei denen sich die Note aus dem vorherigen Block befindet fast so laut wie die lauteste Note (wobei "fast" durch den Qualitätsparameter gesteuert wird).

Wir haben also ein Ziel von 140 Zeichen. Wir beginnen mit der Kodierung bei Qualität 1.0 (maximale Qualität) und sehen, wie viele Zeichen das sind. Wenn es zu viele sind, fallen wir auf 0,95 und wiederholen, bis wir 140 Zeichen haben (oder wir geben nach Qualität 0,05 auf). Dies macht den Encoder zu einem n-Pass-Encoder für n <= 20 (obwohl er auch in anderen Bereichen massiv ineffizient ist, also m'eh).

Der Encoder / Decoder erwartet Audio im s16be-Monoformat. Dies kann mit avconv erreicht werden als:

#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

So führen Sie den Encoder aus:

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"

Vollständiger Code unter https://github.com/jamespic/twelvestring .

Fallen zu beachten: Sie benötigen die Arithmetic Coding-Bibliothek von Nayuki, für die derzeit keine Maven-Artefakte verfügbar sind. Stattdessen müssen Sie die Entwicklergabel lokal erstellen und installieren .

Und hier sind einige Beispiele. Sie klingen schrecklich, sind aber fast wiederzuerkennen:

  • Beethovens 5.: original , kodiert - - 刲 檁 罓 佖 浝 浝 浝 浝 浝 浝 浝姑 姑 椻 趕 櫬 櫬 櫬 櫬 櫬 櫬 櫬 櫬 櫬 櫬 櫬 櫬 櫬 櫬 櫬 櫬 櫬 櫬 櫬丄 茏 蛏 姆 丄 丄 丄 丄 丄 丄 丄 丄 丄 丄 丄 丄 丄 丄 丄 丄 丄 丄
  • Fur Elise: original , verschlüsselt - 訖 訖 擫 鏝 萖 纒 铇 萖 萖 萖苯 劺 楾 楾 楾 楾 楾 楾 楾 楾 楾 楾 楾 楾 楾 楾 楾 楾 楾 楾 楾媡 媡 夥 俰 欓 焵 冊 嗥 燠 鱧 鱧 駟
  • Twinkle Twinkle Little Star: original , codiert -欠悺矜莳錥鷗谴裴皽鳺憝漿箔皇殤鸧蜻猻丱
  • A fun chiptune: original , encoded - 简 简 詐 諥 擕 擕 擕 擕 擕 擕灼 灼 攲 陇 骬 骬 骬 骬 骬 骬 骬 骬 骬 骬 骬 骬 骬 骬 骬 骬 骬 骬 骬罽 乔 蚖 醶 罽 罽 罽 罽 罽 罽 罽 罽 罽 罽 罽 罽 罽 罽 罽 罽 罽 罽

Aktualisieren

Ich habe die Stille-Schwelle im Code angepasst und neu codiert. Die Kodierungen wurden entsprechend aktualisiert. Außerdem habe ich aus Spaß einen weiteren Song hinzugefügt (technisch gesehen kein Open Source-Titel, aber ich bezweifle, dass der ursprüngliche Inhaber des Urheberrechts der Meinung ist, dass seine IP-Adresse in Gefahr ist):

  • Imperial March: original , verschlüsselt - 岼 岼 讶 衢 璥 璥 璥 璥 璥 璥 璥 璥 璥 璥 璥 璥 璥 璥唂 唂 焰 銯 菸 菸 菸 菸 菸 菸 菸 菸 菸 菸 菸 菸 菸 菸 菸 菸 菸 菸 菸愋 採 偋 隆 愋 愋 愋 愋 愋 愋 愋 愋 愋 愋 愋 愋 愋 愋 愋 愋 愋 愋 愋

Weitere Updates

Ich habe den Encoder ein wenig überarbeitet, was sich überraschend auf die Qualität ausgewirkt hat (ich hatte vergessen, dass bei DHT die Signale außerhalb der Phase im Grunde genommen negativ sind, also habe ich die Signale außerhalb der Phase ignoriert).

In einer früheren Version des Codes wurde nur das größere dieser phasenverschobenen Signale verwendet, aber jetzt wird der Effektivwert verwendet. Außerdem habe ich dem Encoder (Tukey, alpha 0.3) eine recht konservative Fensterfunktion hinzugefügt, um Artefakte zu bekämpfen.

Alles ist entsprechend aktualisiert.

James_pic
quelle
1
Ich kann nicht Twinkle Twinkle und Chiptune spielen. Fur Elise ist ziemlich nah, während Beethoven kaum wiederzuerkennen ist, haha.
Nur die Hälfte des
Möchten Sie Twinkle Twinkle and the Chiptune erneut versuchen? Ich denke, ich habe die URLs behoben.
James_pic
1
Es funktioniert jetzt. Das Funkeln Funkeln ist ziemlich absteigend. Aber was passiert am Ende?
Nur die Hälfte des
Ja, ich bin mir nicht ganz sicher, was am Ende passiert. Ich vermute, dass es irgendwo in der arithmetischen Codierung passiert. In einer früheren Version des Codes wurde der Stream durch ein EOF-Symbol beendet, aber in einigen Fällen konnte der Decoder das EOF-Symbol nicht lesen. Ich vermute, ich habe den BitOutputStream nicht richtig geschlossen, aber ich werde mich darum kümmern.
James_pic
1
Ja, genau das war es. Es gab eine BitOutputStream::closeMethode, die ich vergessen hatte anzurufen. Ich werde den Code korrigieren und die Ausgaben aktualisieren.
James_pic
11

Python

In Bezug auf UTF-8 mache ich kein spezielles Mangling, daher besteht meine Einreichung die 140-Byte-Anforderung. Ich mache keine Ansprüche auf die Nützlichkeit, Genauigkeit oder Effizienz meiner Lösung.

Ich habe eine Samplerate von 44100 Hz für den Ein- und Ausgang verwendet. SAMPLES_PER_BYTE steuert die Qualität der Konvertierung. Je niedriger die Zahl, desto besser ist die Klangqualität. Die von mir verwendeten Werte sind im Ergebnisbereich angegeben.

Verwendung

Kodieren

Die Eingabedatei sollte eine WAV sein. Es wird nur der erste Kanal codiert.

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

Dekodieren

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

Abspielen der dekodierten Musik

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

Der Code

#!/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))

Die Eingänge

Mein offizieller Beitrag ist Impromptu für Pianoforte und Beatbox von Kevin MacLeod . Für diese Datei habe ich einen SAMPLES_PER_BYTE von 25450 verwendet.

Ich habe mir auch erlaubt , Twinkle, Twinkle, Little Star mit einem SAMPLES_PER_BYTE von 10200 zu codieren. Das klingt viel besser.

Die Ausgabe

Impromptu für Pianoforte und Beatbox

aWnxQDg4mWqZWVl6W+LyOThfHOPyQThAe4x5XCqJK1EJ8Rh6jXt5XEMpk1Epe5JqTJJDSisrkkNCSqnSkkJDkiorCZHhCxsq8nlakfEp8vNb8iqLysp6MpJ7s4x7XlxdW4qKMinJKho

Verknüpfung

Funkel, funkel kleiner Stern

HBobGlJSUlJSY2FlYVNRUVFCQkJCQjs5PDksKisqGxoZGVFTUVNRREFDQjs6OjoqKykpKVRRVFJDQkJCOjs6OzksKikpGxobG1JSUlNRZWFlYVNSUVFCQkJDQTw5PDorKisqGhsZGRk

Verknüpfung

tecywiz121
quelle