Défi musical Tweet


37

Ceci est la version audio du défi d’encodage d’images Twitter .

Concevez un format de compression audio pouvant représenter au moins une minute de musique sur 140 octets ou moins de texte imprimable encodé en UTF-8.

Implémentez-le en écrivant un programme en ligne de commande qui utilise les 3 arguments suivants (après le nom du programme lui-même):

  1. La ficelle encodeou decode.
  2. Le nom de fichier d'entrée.
  3. Le nom du fichier de sortie.

(Si votre langage de programmation préféré ne permet pas d'utiliser des arguments de ligne de commande, vous pouvez utiliser une approche alternative, mais vous devez l'expliquer dans votre réponse.)

L' encodeopération convertira le format audio choisi en votre format compressé «tweet» et l' decodeopération convertira votre format «tweet» au format audio d'origine. (Bien entendu, vous êtes censé implémenter une compression avec perte, le fichier de sortie ne doit pas nécessairement être identique à celui de l'entrée, mais dans le même format)

Inclure dans votre réponse:

  • Le code source de votre programme, en entier. (Si la page est trop longue, vous pouvez l'héberger ailleurs et y poster un lien.)
  • Une explication de la façon dont cela fonctionne.
  • Au moins un exemple, avec un lien vers le ou les fichiers audio d'origine, le texte «tweet» qu'il compresse et le fichier audio obtenu en décodant le tweet. (Le répondeur est responsable des assertions de “fair use” du copyright.)

Règles

  • Je me réserve le droit de supprimer les échappatoires du règlement du concours à tout moment.
  • [Édité le 24 avril] Pour l’entrée de votre encodefonction (et la sortie de votre decodefonction), vous pouvez utiliser n’importe quel format audio commun raisonnable, que ce soit:
    • Forme d'onde non compressée, comme WAV.
    • Forme d'onde compressée, comme MP3.
    • Le style “Partition”, comme le MIDI.
  • Votre format compressé «tweet» doit en fait coder les sons du fichier d’entrée. Ainsi, les types de sortie suivants ne comptent pas :
    • Un URI ou un chemin de fichier indiquant l'emplacement où la sortie réelle est stockée.
    • Une clé pour une table de base de données où la sortie réelle est stockée sous forme de blob.
    • Quelque chose de similaire.
  • Votre programme doit être conçu pour compresser des fichiers de musique génériques , évitez donc de faire des choses trop manifestement liées à votre exemple de chanson. Par exemple, si vous montrez «Twinkle, Twinkle, Little Star», votre programme de compression ne doit pas coder en dur un symbole spécifique pour la séquence do-do-so-so-la-la-so.
  • La sortie de votre programme devrait pouvoir passer par Twitter et en ressortir indemne. Je n'ai pas de liste des caractères exacts pris en charge, mais essayez de vous en tenir aux lettres, aux chiffres, aux symboles et à la ponctuation; et évitez les caractères de contrôle, en combinant des caractères, des marqueurs BIDI, ou d'autres choses étranges comme ça.
  • Vous pouvez soumettre plus d'une entrée.

Critère de jugement

Il s’agit d’un concours de popularité (c’est-à-dire que la plupart des votes positifs augmentent), mais les électeurs sont invités à prendre en compte les éléments suivants:

Précision

  • Pouvez-vous toujours reconnaître la chanson après l'avoir comprimée?
  • Ca sonne bien?
  • Pouvez-vous toujours reconnaître quels instruments sont joués?
  • Pouvez-vous encore reconnaître les paroles? (C'est probablement impossible, mais ce serait impressionnant si quelqu'un le faisait.)

Complexité

Le choix de la chanson exemple compte ici.

  • [Ajouté le 24 avril] Ce défi sera plus facile avec les formats MIDI ou similaires. Toutefois, si vous faites l'effort supplémentaire de le faire fonctionner avec des formats de type forme d'onde, cela mérite un crédit supplémentaire.
  • Quelle est la structure? Bien sûr, vous pouvez répondre à l'exigence d'une minute en répétant simplement les 4 mêmes mesures un nombre arbitraire de fois. Mais les structures de chansons plus complexes méritent plus de points.
  • Le format peut-il gérer plusieurs notes jouées en même temps?

Le code

  • Gardez-le aussi court et simple que possible. Cependant, ce n'est pas un code de golf, la lisibilité est donc plus importante que le nombre de caractères.
  • Des algorithmes intelligents et compliqués sont également acceptables, dans la mesure où ils sont justifiés par une amélioration de la qualité des résultats.

9
Utiliser MIDI contre WAV est un défi radicalement différent. Je pense que vous devriez limiter les formats au format WAV uniquement.
GrovesNL

10
Je suis impatient de trouver des solutions, mais pour être honnête: Composer 60 octets de son sur 140 octets signifie que vous disposez de moins de 19 bits par seconde. Il existe quelques codeurs de parole ultra efficaces, qui fonctionnent à 300 bps, mais ils ne peuvent décoder que des phonèmes synthétisés dans le but de produire une parole compréhensible et ne peuvent en aucune manière encoder de la musique.
jarnbjo

2
Vous demandez un logiciel avec des facteurs de compression de plusieurs ordres de grandeur supérieurs à l'état actuel de la technique. Si vous souhaitez des réponses judicieuses (n'impliquant pas de compositions comme 4'33 " ou Marche funèbre pour les obsèques d'un sourd ), je vous encourage à laisser tomber la contrainte de temps à 1 seconde.
ossifrage délirant

3
@squeamishossifrage il n'a pas dit que cela devait sembler reconnaissable, cependant.
cjfaure

5
Il y a une discussion sur le chat (et le lendemain) pour savoir si vous voulez réellement dire 140 octets ou 140 caractères de la taille d'un tweet .
Peter Taylor

Réponses:


26

Scala

Bien sûr, il serait plus facile de coder des fichiers MIDI, mais qui a un tas de fichiers MIDI qui traînent? Ce n'est pas 1997!

Tout d’abord: j’ai décidé d’interpréter un «octet Unicode» comme un «caractère Unicode» et d’utiliser des caractères CJK, car:

  • Cela correspond au défi de l'image
  • Twitter est cool avec ça
  • J'ai vraiment besoin de ces bits

Il y a quelques astuces que j'utilise pour extraire chaque source d'entropie des sources:

Tout d'abord, la musique est faite avec des notes. De plus, nous considérons généralement la même note dans une octave différente comme la même note (c'est pourquoi une guitare à 12 cordes sonne bien), de sorte que nous n'avons que 12 possibilités pour encoder. (Quand je produis B, par exemple, je produis un accord composé uniquement de B dans toutes les octaves, un peu comme une guitare à 12 cordes).

Ensuite, je me souviens du cours de musique au lycée que la plupart des transitions de notes sont petites (une note vers le haut ou le bas). Les sauts sont moins fréquents. Cela nous indique qu'il y a probablement moins d'entropie dans les tailles de saut que dans les notes elles-mêmes.

Donc, notre approche est de diviser notre source en plusieurs blocs - j'ai trouvé que 14 blocs par seconde fonctionnaient bien (note de côté, je me suis toujours demandé pourquoi l'audio était codé à 44100 Hz. Il s'avère que 44100 a beaucoup de facteurs, donc j'aurais pu choisir 1, 2, 3, 4, 5, 6, 7, 9, 10, 12, 14, 15, 18, 20, 21, 25, 28 ou 30 blocs par seconde, et cela aurait été divisé proprement ). Nous avons ensuite FFT ces blocs (enfin, techniquement, pas rapidement, car la bibliothèque que j'ai utilisée n'est pas rapide pour les blocs non-power-of-2. Et techniquement, j'ai utilisé une transformation de Hartley , pas de Fourier).

Nous trouvons ensuite la note qui sonne le plus fort (j’ai utilisé la pondération A , avec des seuils haut et bas, principalement parce que c’est plus facile à mettre en œuvre), et soit coder cette note, soit coder le silence (la détection de silence est basée sur SNR - low SNR est le silence).

Nous traduisons ensuite nos notes codées en sauts et les transmettons à un codeur arithmétique adaptatif. Le processus de traduction en texte est similaire à la question de compression d'image (mais implique une utilisation abusive de BigInteger).

Jusqu'ici, tout va bien, mais que se passe-t-il si l'échantillon a trop d'entropie? Nous utilisons un modèle psychoacoustique brut pour en supprimer. Le saut d'entropie le plus bas est "aucun changement", nous examinons donc nos données FFT pour essayer de trouver des blocs où l'auditeur ne remarquera probablement pas si nous continuons à jouer la note précédente, en recherchant des blocs contenant la note du bloc précédent. presque aussi fort que la note la plus forte (où "presque" est contrôlé par le paramètre de qualité).

Nous avons donc une cible de 140 caractères. Nous commençons par encoder à la qualité 1.0 (qualité maximale), et voyons combien de caractères il s’agit. Si c'est trop, on tombe à 0,95 et on répète, jusqu'à atteindre 140 caractères (ou on abandonne après la qualité 0.05). Cela fait du codeur un codeur n-pass, pour n <= 20 (bien que ce soit massivement inefficace dans d’autres domaines aussi, alors ça me fait mal).

L'encodeur / décodeur attend de l'audio au format mono s16be. Ceci peut être réalisé en utilisant avconv comme:

#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

Pour exécuter l'encodeur:

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"

Code complet à l' adresse https://github.com/jamespic/twelvestring .

Piège à noter: vous aurez besoin de la bibliothèque de codage arithmétique de nayuki, qui ne dispose pas actuellement d'artefacts Maven. Au lieu de cela, vous devrez construire et installer localement la fourchette de développement .

Et voici quelques exemples. Ils paraissent affreux, mais presque reconnaissables:

  • Beethoven's 5th: original , codé - Utiliser le code de la souris pour afficher le code de la chanson, afficher le texte dans l'image, afficher le code du texte ou de la modifier.趕姆
  • Fourrure Elise: originale , codée - Image de Fourrure, Fourre-tout, Mannequin, Fourre-tout, Fourrure誺俰 駟 髉
  • Twinkle Twinkle Little Star: original , codé - Cliquez ici pour en savoir plus sur le style
  • Une chiptune amusante: originale , codée - Connectez-vous pour la voir si vous voulez en savoir plus sur ce que vous avez besoin de savoir ce qu'il se passe陇醶

Mise à jour

J'ai peaufiné le seuil de silence dans le code et l'ai ré-encodé. Les encodages ont été mis à jour en conséquence. De plus, j'ai ajouté une autre chanson (techniquement, pas en open source, mais je doute que le détenteur du copyright original sentira que sa propriété intellectuelle est menacée), juste pour le plaisir:

  • Marche impériale: originale , codée - Liste des produits - Version imprimable - Liste des éléments génériques銯隆

Plus de mises à jour

J'ai légèrement modifié le codeur, ce qui a eu un impact surprenant sur la qualité (j'avais oublié qu'en DHT, les signaux déphasés sont effectivement négatifs, donc je les ignorais).

Une version antérieure du code prenait simplement le plus gros de ces signaux déphasés, mais nous prenons maintenant le RMS. De plus, j'ai ajouté une fonction de fenêtre assez conservatrice à l'encodeur (Tukey, alpha 0.3), pour tenter de lutter contre les artefacts.

Tout est mis à jour en conséquence.


1
Je ne peux pas jouer au Twinkle Twinkle et au chiptune. Fur Elise est assez proche, tandis que Beethoven est à peine reconnaissable, haha.
demi

Voulez-vous essayer à nouveau Twinkle Twinkle et le Chiptune? Je pense avoir corrigé les URL.
James_pic

1
Ça fonctionne maintenant. Le scintillement scintille est assez descendant. Mais que se passe-t-il à la fin?
moitié

Oui, je ne suis pas totalement sûr de ce qui se passe à la fin. Je suppose que cela se passe quelque part dans le codage arithmétique. Dans une version antérieure du code, le flux était terminé par un symbole EOF, mais dans certains cas, le décodeur ne lisait pas le symbole EOF. Je soupçonne que je n’ai pas fermé correctement le BitOutputStream, mais je vais vérifier.
James_pic

1
Oui, en fait c'était exactement ça. Il y avait une BitOutputStream::closeméthode que j'avais oublié d'appeler. Je vais corriger le code et mettre à jour les sorties.
James_pic

11

Python

Je ne fais aucune distinction particulière en ce qui concerne UTF-8, ma soumission satisfait donc à l'exigence de 140 octets. Je ne fais aucune déclaration sur l'utilité, la précision ou l'efficacité de ma solution.

J'ai utilisé une fréquence d'échantillonnage de 44100 Hz pour l'entrée et la sortie. SAMPLES_PER_BYTE contrôle la qualité de la conversion. Plus le chiffre est bas, meilleure est la qualité du son. Les valeurs que j'ai utilisées sont données dans la section résultats.

Usage

Encoder

Le fichier d'entrée doit être un wav. Il n'encode que le premier canal.

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

Décoder

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

Jouer de la musique décodée

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

Le 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))

Les entrées

Ma soumission officielle est Impromptu pour Pianoforte et Beatbox de Kevin MacLeod . Pour ce fichier, j'ai utilisé un SAMPLES_PER_BYTE de 25450.

J'ai aussi pris la liberté de coder Twinkle, Twinkle, Little Star avec un SAMPLES_PER_BYTE de 10200. Cela sonne beaucoup mieux.

Le résultat

Impromptu pour Pianoforte et Beatbox

aWnxQDg4mWqZWVl6W+LyOThfHOPyQThAe4x5XCqJK1EJ8Rh6jXt5XEMpk1Epe5JqTJJDSisrkkNCSqnSkkJDkiorCZHhCxsq8nlakfEp8vNb8iqLysp6MpJ7s4x7XlxdW4qKMinJKho

Lien

Scintille, scintille petite étoile

HBobGlJSUlJSY2FlYVNRUVFCQkJCQjs5PDksKisqGxoZGVFTUVNRREFDQjs6OjoqKykpKVRRVFJDQkJCOjs6OzksKikpGxobG1JSUlNRZWFlYVNSUVFCQkJDQTw5PDorKisqGhsZGRk

Lien

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.