algorithm guida - Algoritmo per mixare il suono





gratis musica (17)


// #include <algorithm>
// short ileft, nleft; ...
// short iright, nright; ...

// Mix
float hiL = ileft + nleft;
float hiR = iright + nright;

// Clipping
short left = std::max(-32768.0f, std::min(hiL, 32767.0f));
short right = std::max(-32768.0f, std::min(hiR, 32767.0f));

Ho due flussi audio grezzi che devo aggiungere insieme. Ai fini di questa domanda, possiamo presumere che abbiano lo stesso bitrate e la stessa profondità di bit (diciamo campione a 16 bit, frequenza di campionamento 44.1khz).

Ovviamente se li aggiungo insieme, trabocchetto e trabatterò il mio spazio a 16 bit. Se li aggiungo e li divido per due, il volume di ciascuno viene dimezzato, il che non è corretto dal punto di vista sonoro - se due persone parlano in una stanza, le loro voci non diventano più basse della metà e un microfono può selezionarle entrambi senza colpire il limitatore.

  • Quindi qual è il metodo corretto per aggiungere questi suoni insieme nel mio mixer software?
  • Ho sbagliato e il metodo corretto è di abbassare il volume di ciascuno della metà?
  • Devo aggiungere un compressore / limitatore o qualche altro stadio di elaborazione per ottenere il volume e l'effetto di missaggio che sto cercando?

-Adamo




Direi di aggiungerli insieme. Se stai sovraccaricando il tuo spazio PCM a 16 bit, i suoni che stai usando sono già incredibilmente forti per cominciare e dovresti attenuarli. Se ciò li inducesse a essere troppo morbidi da soli, cerca un altro modo per aumentare l'uscita del volume generale, ad esempio un'impostazione del sistema operativo o ruotando la manopola sugli altoparlanti.




Ho fatto la seguente cosa:

MAX_VAL = Full 8 or 16 or whatever value
dst_val = your base audio sample
src_val = sample to add to base

Res = (((MAX_VAL - dst_val) * src_val) / MAX_VAL) + dst_val

Moltiplicare il margine sinistro di src per il valore di destinazione normalizzato MAX_VAL e aggiungerlo. Non si fermerà mai, non sarà mai meno rumoroso e suonerà assolutamente naturale.

Esempio:

250.5882 = (((255 - 180) * 240) / 255) + 180

E questo suona bene :)




C'è un articolo sul mixaggio here . Sarei interessato a sapere cosa ne pensano gli altri.




Non posso credere che nessuno conosca la risposta corretta. Tutti sono abbastanza vicini ma pur sempre, una filosofia pura. Il più vicino, cioè il migliore era: (s1 + s2) - (s1 * s2). È un approccio eccellente, specialmente per gli MCU.

Quindi, l'algoritmo va:

  1. Scopri il volume in cui desideri che il suono in uscita sia. Può essere la media o il massimo di uno dei segnali.
    factor = average(s1) Si presuppone che entrambi i segnali siano già OK, non in eccesso rispetto al 32767.0
  2. Normalizza entrambi i segnali con questo fattore:
    s1 = (s1/max(s1))*factor
    s2 = (s2/max(s2))*factor
  3. Aggiungili insieme e normalizza il risultato con lo stesso fattore
    output = ((s1+s2)/max(s1+s2))*factor

Nota che dopo il passaggio 1. non hai davvero bisogno di tornare ai numeri interi, puoi lavorare con float nell'intervallo da -1.0 a 1.0 e applicare il ritorno ai numeri interi alla fine con il fattore di potenza scelto in precedenza. Spero di non aver sbagliato ora, perché sono di fretta.




Grazie a tutti per aver condiviso le vostre idee, di recente sto anche facendo del lavoro legato al missaggio del suono. Ho anche fatto qualcosa di sperimentale su questo tema, potrebbe aiutarti ragazzi :).

Notare che sto usando una frequenza di campionamento di 8 Khz e un suono di campionamento a 16 bit (SInt16) in ios RemoteIO AudioUnit.

Lungo i miei esperimenti il ​​risultato migliore che ho trovato era qualcosa di diverso da tutta questa risposta, ma la base è la stessa (come suggerisce Roddy )

" Dovresti aggiungerli insieme, ma ritagliare il risultato nell'intervallo consentito per prevenire l'over / underflow ".

Ma quale dovrebbe essere il modo migliore per aggiungere senza overflow / underflow?

Idea chiave :: Hai due onde sonore che dicono A e B, e l'onda risultante C sarà la superposition di due onde A e B. Il campione con raggio di bit limitato potrebbe farla traboccare. Quindi ora possiamo calcolare la croce del limite massimo al punto di incrocio del limite superiore e del limite minimo sul lato inferiore della forma d'onda di sovrapposizione. Ora sottrarremo il limite massimo del limite al rialzo alla porzione superiore della forma d'onda di sovrapposizione e aggiungiamo il limite minimo del limite inferiore alla porzione inferiore della forma d'onda di sovrapposizione. VOILA ... hai finito.

passi:

  1. Per prima cosa attraversare il circuito dati una volta per il valore massimo della croce del limite superiore e il valore minimo del limite del limite inferiore.
  2. Trasforma un altro traverso ai dati audio, sottrai il valore massimo dalla porzione di dati audio positiva e aggiungi il valore minimo alla parte negativa dei dati audio.

il seguente codice mostrerebbe l'implementazione.

static unsigned long upSideDownValue = 0;
static unsigned long downSideUpValue = 0;
#define SINT16_MIN -32768
#define SINT16_MAX 32767
SInt16* mixTwoVoice (SInt16* RecordedVoiceData, SInt16* RealTimeData, SInt16 *OutputData, unsigned int dataLength){

unsigned long tempDownUpSideValue = 0;
unsigned long tempUpSideDownValue = 0;
//calibrate maker loop
for(unsigned int i=0;i<dataLength ; i++)
{
    SInt32 summedValue = RecordedVoiceData[i] + RealTimeData[i];

    if(SINT16_MIN < summedValue && summedValue < SINT16_MAX)
    {
        //the value is within range -- good boy
    }
    else
    {
       //nasty calibration needed
        unsigned long tempCalibrateValue;
        tempCalibrateValue = ABS(summedValue) - SINT16_MIN; // here an optimization comes ;)

        if(summedValue < 0)
        {
            //check the downside -- to calibrate
            if(tempDownUpSideValue < tempCalibrateValue)
                tempDownUpSideValue = tempCalibrateValue;
        }
        else
        {
            //check the upside ---- to calibrate
            if(tempUpSideDownValue < tempCalibrateValue)
                tempUpSideDownValue = tempCalibrateValue;
        }
    }
}

//here we need some function which will gradually set the value
downSideUpValue = tempUpSideDownValue;
upSideDownValue = tempUpSideDownValue;

//real mixer loop
for(unsigned int i=0;i<dataLength;i++)
{
    SInt32 summedValue = RecordedVoiceData[i] + RealTimeData[i];

    if(summedValue < 0)
    {
        OutputData[i] = summedValue + downSideUpValue;
    }
    else if(summedValue > 0)
    {
        OutputData[i] = summedValue - upSideDownValue;
    }
    else
    {
        OutputData[i] = summedValue;
    }
}

return OutputData;
}

funziona bene per me, ho intenzione più tardi di modificare gradualmente il valore di upSideDownValue e downSideUpValue per ottenere un risultato più fluido.




convertire i campioni in valori in virgola mobile compresi tra -1.0 e +1.0, quindi:

out = (s1 + s2) - (s1 * s2);



Hai ragione a aggiungerli insieme. Puoi sempre scansionare la somma dei due file per i punti di picco e ridimensionare l'intero file se colpiscono una specie di soglia (o se la media di esso e i suoi punti circostanti colpiscono una soglia)




La maggior parte delle applicazioni di missaggio audio eseguiranno il loro mixaggio con numeri in virgola mobile (32 bit è abbastanza buono per mixare un piccolo numero di stream). Converti i campioni a 16 bit in numeri in virgola mobile con l'intervallo da -1,0 a 1,0 che rappresenta il fondo scala nel mondo a 16 bit. Quindi somma i campioni insieme: ora hai un sacco di spazio per la testa. Infine, se si finisce con qualsiasi campione il cui valore vada oltre il fondo scala, è possibile attenuare l'intero segnale o utilizzare limiti duri (valori di ritaglio a 1.0).

Ciò fornirà risultati sonori molto migliori rispetto all'aggiunta di campioni a 16 bit e lasciandoli traboccare. Ecco un esempio di codice molto semplice che mostra come potresti sommare due campioni a 16 bit:

short sample1 = ...;
short sample2 = ...;
float samplef1 = sample1 / 32768.0f;
float samplef2 = sample2 / 32768.0f;
float mixed = samplef1 + sample2f;
// reduce the volume a bit:
mixed *= 0.8;
// hard clipping
if (mixed > 1.0f) mixed = 1.0f;
if (mixed < -1.0f) mixed = -1.0f;
short outputSample = (short)(mixed * 32768.0f)



L'ho fatto in questo modo una volta: ho usato i float (campioni tra -1 e 1) e ho inizializzato una variabile "autoGain" con un valore di 1. Quindi aggiungerei tutti i campioni insieme (potrebbe anche essere più di 2). Quindi vorrei moltiplicare il segnale in uscita con autoGain. Se il valore assoluto della somma dei segnali prima della moltiplicazione fosse superiore a 1, vorrei assegnare 1 / questo valore di somma. Ciò renderebbe l'autogain più piccolo di 1 diciamo 0.7 e sarebbe equivalente a un operatore che abbassa rapidamente il volume principale non appena vede che il suono generale sta diventando troppo alto. Poi, dopo un periodo di tempo regolabile, aggiungo all'autogain fino a quando non tornerà finalmente su "1" (il nostro operatore si è ripreso dallo shock e sta lentamente aumentando il volume :-)).




Penso che, fintanto che i flussi non sono correlati, non dovresti preoccuparti troppo, dovresti essere in grado di cavartela con il clipping. Se sei davvero preoccupato per la distorsione nei punti di clip, un limitatore potrebbe funzionare correttamente.




Se hai bisogno di farlo bene, suggerirei di guardare alle implementazioni del software open source, almeno per la teoria.

Alcuni link:

Audacity

GStreamer

In realtà dovresti probabilmente usare una libreria.




Preferirei commentare una delle due risposte altamente classificate, ma a causa della mia scarsa reputazione (presumo) non posso.

La risposta "barrata": aggiunta e clip è corretta, ma non se si vuole evitare il clipping.

La risposta con il collegamento inizia con un algoritmo voodoo funzionante per due segnali positivi in ​​[0,1], ma poi applica un'algebra molto difettosa per derivare un algoritmo completamente errato per valori firmati e valori a 8 bit. L'algoritmo inoltre non scala su tre o più input (il prodotto dei segnali andrà giù mentre la somma aumenta).

Quindi, converti i segnali di input in virgola mobile, ridimensionandoli a [0,1] (ad esempio, un valore a 16 bit con segno sarebbe diventato
float v = ( s + 32767.0 ) / 65536.0 (close enough...))
e poi sommarli.

Per ridimensionare i segnali in ingresso, dovresti probabilmente fare un po 'di lavoro reale piuttosto che moltiplicare per o sottrarre un valore voodoo. Suggerirei di mantenere un volume medio corrente e quindi se inizia a deviare in alto (sopra 0,25 dire) o basso (sotto 0,01 dire) iniziare ad applicare un valore di ridimensionamento basato sul volume. Questo diventa essenzialmente un'implementazione a livello automatico e si adatta a qualsiasi numero di input. Meglio di tutti, nella maggior parte dei casi non si scherza con il segnale.




"Tranquillo a metà" non è del tutto corretto. A causa della risposta logaritmica dell'orecchio, la divisione dei campioni a metà lo renderà più silenzioso 6-db - certamente notevole, ma non disastroso.

Potresti voler scendere a compromessi moltiplicando per 0,75. Ciò renderà 3-db più silenzioso, ma ridurrà la possibilità di overflow e diminuirà anche la distorsione quando accadrà.




Dovresti aggiungerli insieme, ma tagliare il risultato nell'intervallo consentito per evitare un over / underflow.

Nel caso in cui si verifichi il clipping, introdurrà una distorsione nell'audio, ma ciò è inevitabile. Puoi usare il tuo codice di clipping per "rilevare" questa condizione e segnalarla all'utente / operatore (equivalente alla luce rossa "clip" su un mixer ...)

Potresti implementare un compressore / limitatore più "corretto", ma senza conoscere la tua esatta applicazione, è difficile dire se ne varrà la pena.

Se stai facendo molta elaborazione audio, potresti voler rappresentare i tuoi livelli audio come valori a virgola mobile e tornare allo spazio a 16 bit solo alla fine del processo. I sistemi audio digitali di fascia alta spesso funzionano in questo modo.




Dal momento che il tuo profilo dice che lavori nei sistemi embedded, supporrò che le operazioni in virgola mobile non siano sempre un'opzione.

> So what's the correct method to add these sounds together in my software mixer?

Come hai intuito, aggiungere e ritagliare è il modo corretto di andare se non vuoi perdere volume sui sorgenti. Con gli esempi int16_t , è necessario che la somma sia int32_t , quindi limitare e riconvertire in int16_t .

> Am I wrong and the correct method is to lower the volume of each by half?

Sì. Il dimezzamento del volume è in qualche modo soggettivo, ma quello che puoi vedere qua e là è che dimezzare il volume (volume) è una diminuzione di circa 10 dB (dividendo la potenza di 10, o i valori del campione di 3.16). Ma intendi ovviamente abbassare i valori del campione della metà. Si tratta di una riduzione di 6 dB, una riduzione notevole, ma non del tutto pari alla metà del volume (la tabella di loudness è molto utile).

Con questa riduzione di 6 dB eviterai il clipping. Ma cosa succede quando vuoi più canali di input? Per quattro canali, è necessario dividere i valori di input di 4, che si riduce di 12 dB, andando così a meno della metà del volume per ciascun canale.

> Do I need to add a compressor/limiter or some other processing stage to 
get the volume and mixing effect I'm trying for?

Vuoi mescolare, non tagliare e non perdere il volume sui segnali di ingresso. Questo non è possibile, non senza un qualche tipo di distorsione.

Come suggerito da Mark Ransom, una soluzione per evitare il clipping senza perdere fino a 6 dB per canale è quella di colpire da qualche parte tra "aggiungere e ritagliare" e "fare la media".

Questo vale per due fonti: aggiungendo, dividendo da qualche parte tra 1 e 2 (riduci l'intervallo da [-65536, 65534] a qualcosa di più piccolo), quindi limitando.

Se ti agganci spesso con questa soluzione e sembra troppo dura, allora potresti voler ammorbidire il ginocchio limite con un compressore. Questo è un po 'più complesso, dal momento che è necessario rendere il fattore di divisione dipendente dalla potenza in ingresso. Provare prima il limitatore e considerare il compressore solo se non si è soddisfatti del risultato.




La complessità del tempo con esempi

1 - Operazioni di base (aritmetica, confronti, accesso agli elementi dell'array, assegnazione): il tempo di esecuzione è sempre costante O (1)

Esempio :

read(x)                               // O(1)
a = 10;                               // O(1)
a = 1.000.000.000.000.000.000         // O(1)

2 - Se poi altra affermazione: Solo prendendo il tempo massimo di esecuzione da due o più affermazioni possibili.

Esempio:

age = read(x)                               // (1+1) = 2
if age < 17 then begin                      // 1
      status = "Not allowed!";              // 1
end else begin
      status = "Welcome! Please come in";   // 1
      visitors = visitors + 1;              // 1+1 = 2
end;

Quindi, la complessità dello pseudo codice sopra è T (n) = 2 + 1 + max (1, 1 + 2) = 6. Quindi, il suo grande oh è ancora costante T (n) = O (1).

3 - Ciclo continuo (per, mentre, ripetizione): il tempo di esecuzione di questa istruzione è il numero di cicli moltiplicato per il numero di operazioni all'interno di quel ciclo.

Esempio:

total = 0;                                  // 1
for i = 1 to n do begin                     // (1+1)*n = 2n
      total = total + i;                    // (1+1)*n = 2n
end;
writeln(total);                             // 1

Quindi, la sua complessità è T (n) = 1 + 4n + 1 = 4n + 2. Quindi, T (n) = O (n).

4 - Nested Loop (looping all'interno di looping): poiché c'è almeno un loop all'interno del loop principale, il tempo di esecuzione di questa istruzione è usato O (n ^ 2) o O (n ^ 3).

Esempio:

for i = 1 to n do begin                     // (1+1)*n  = 2n
   for j = 1 to n do begin                  // (1+1)n*n = 2n^2
       x = x + 1;                           // (1+1)n*n = 2n^2
       print(x);                            // (n*n)    = n^2
   end;
end;

Tempo di esecuzione comune

Ci sono alcuni tempi di esecuzione comuni durante l'analisi di un algoritmo:

  1. O (1) - Tempo costante Il tempo costante indica che il tempo di esecuzione è costante, non è influenzato dalla dimensione di input .

  2. O (n) - Tempo lineare Quando un algoritmo accetta n dimensioni di input, eseguirà anche n operazioni.

  3. O (log n) - Logarithmic Time Algorithm con tempo di esecuzione O (log n) è leggermente più veloce di O (n). Comunemente, l'algoritmo divide il problema in sotto-problemi con le stesse dimensioni. Esempio: algoritmo di ricerca binaria, algoritmo di conversione binaria.

  4. O (n log n) - Linearithmic Time Questo tempo di esecuzione si trova spesso negli "algoritmi divide & conquer" che dividono il problema in sotto-problemi in modo ricorsivo e quindi li uniscono in n time. Esempio: algoritmo Merge Sort.

  5. O (n 2 ) - algoritmo Quadratic Time Look Bubble Sort!

  6. O (n 3 ) - Cubic Time Ha lo stesso principio con O (n 2 ).

  7. O (2 n ) - Tempo esponenziale È molto lento quando l'input diventa più grande, se n = 1000.000, T (n) sarebbe 21000.000. L'algoritmo Brute Force ha questo tempo di esecuzione.

  8. O (n!) - Factorial Time THE SLOWEST !!! Esempio: Travel Salesman Problem (TSP)

Tratto da questo articolo . Molto ben spiegato dovrebbe dare una lettura.





algorithm audio