Sida 1 av 1

Bolla med matriser, hur?

Postat: 17 apr 2012, 22:11
av Johnny Rosenberg
Jag känner att jag har lite svårt att förklara vad jag egentligen är ute efter, så jag tar några korta exempel på småsaker jag vill veta hur man gör (om möjligt).

Kod: Markera allt

int c[]={1,2,3,4,5,6,7,8,9,10};
Jag vill nu deklarera a[] och b[], där båda ska vara fem element stor. Problemet är att jag vill att a ska peka på c[0] och b på c[5]. Alltså kommer a i detta fall att vara {1,2,3,4,5} och b att vara {6,7,8,9,10}.
Något förslag på hur det enklast görs? Först måste ju antagligen rätt mängd minne allokeras till a respektive b, men hur får man dem att peka rätt?

Om vi lämnar a och b därhän, skulle det vara intressant att kika lite mer på c-matrisen.
Vårt program som hanterar c[] kanske har kört några varv och nu behöver vi inte längre de fem första värdena, så vi vill frigöra det minnet och låta c peka på sjätte elementet istället (gamla c[5]), så att c istället innehåller {6,7,8,9,10}. För att bibehålla samma storlek på c tar vi nu in fem nya värden som vi lägger på slutet, så att c nu innehåller {6,7,8,9,10,11,12,13,14,15}.

Givetvis vet jag att jag kan kopiera de fem sista värdena till de fem första i en enkel loop och sedan fylla på de fem sista värdena hur jag vill, men det är inte det jag är ute efter i detta fall (särskilt inte när antalet värden i mitt ”riktiga” projekt är väldigt många fler). Jag vill hellre göra typ följande, som jag tycker borde går snabbare:
  • Låt c peka på c[5].
  • Frigör gamla c[0] till och med gamla c[4] från minnet.
  • Allokera fem nya element till c.
  • Fyll på med fem nya värden i den nyallokerade området.

Re: Bolla med matriser, hur?

Postat: 17 apr 2012, 22:55
av uppsalanet
Hej,
Vilket språk ämnar du använda?
//Fredrik

Re: Bolla med matriser, hur?

Postat: 18 apr 2012, 01:20
av David Andersson
Antar programspråk C.
Johnny Rosenberg skrev: Jag vill nu deklarera a[] och b[], där båda ska vara fem element stor. Problemet är att jag vill att a ska peka på c[0] och b på c[5]. Alltså kommer a i detta fall att vara {1,2,3,4,5} och b att vara {6,7,8,9,10}.
Ibland kan man betrakta pekare (*a) och arrayer (a[]) som samma sak i C. Här betraktar vi a[] och b[] som pekare in i arrayen c[]. En pekare är minnesadressen till minnescellen där värdet ligger. När en pekare betraktas som en array antar man att det finns fler värden av samma typ i efterföljande minnesceller, men pekaren själv vet inte hur många. Med *a och *b så har vi större flexibilitet att deklarera dem var vi vill. Vi kan bara deklarera a[] och b[] utan initiering på ställen där de automatiskt initieras, t.ex. som argument till en funktion.

Ett sätt att få pekaren till c[0] är adress-operatorn &

Kod: Markera allt

int *a;
a = &c[0]:
eller inse att adressen till första elementet i c är adressen till hela c

Kod: Markera allt

int *a;
a = c;
Ett sätt att få adressen till c[5] är åter adress-operatorn &

Kod: Markera allt

int *b;
b = &c[5];
eller använda adress-aritmetik

Kod: Markera allt

int *b;
b = c+5;
(Adress-aritmetik är lite olik vanlig aritmetik. Eftersom b och c är pekare till int som är 4 byte lång så kommer c+5 att addera det numeriska värdet 20 till minnesadressen.)

I inget av ovanstående fall så "vet" a eller b hur många återstående värden det finns i arrayen. De har inte nån inbyggd längd 5. Pekare a kan flytta sig framåt och se alla 10 elementen i c. Pekare b kan flytta sig framåt 4 steg innan den kommer till 10. Ingen pekare bör försöka peka efter 10, men språket C hindrar inte att programmet försöker. Där kan finnas nåt slumpvis värde från nån annan array eller variabel eller det kan bli adresseringsfel.
Johnny Rosenberg skrev: Vårt program som hanterar c[] kanske har kört några varv och nu behöver vi inte längre de fem första värdena, så vi vill frigöra det minnet och låta c peka på sjätte elementet istället (gamla c[5]), så att c istället innehåller {6,7,8,9,10}. För att bibehålla samma storlek på c tar vi nu in fem nya värden som vi lägger på slutet, så att c nu innehåller {6,7,8,9,10,11,12,13,14,15}.
Om arrayen är allokerad statiskt eller på stacken (int c[10]; eller int c[]={...};) så kan man inte göra den längre i efterhand. Om den är allokerad dynamisk så kan man göra den längre i efterhand (re-allokering) men det är inte säkert, kanske inte ens troligt, att de fanns ledigt minne direkt efter, så det troliga är att den längre arrayen allokeras på ett nytt ställe och det gamla innehållet kopieras dit. Eftersom din fråga verkar handla om effektivitet är det nog inte det du vill.

Det går inte heller att av-allokera/befria bara en del av tidigare allokerat minne.

Om det är en rullande buffer du behöver så får du nog finna dig i att det blir en mer komplicerad datastruktur där alla celler inte kommer att ligga i konsekutiva minnesceller. (Kolla om det finns färdigt bibliotek för det). Eller strunta i minneseffektivitet och allokera en jättearray för så många värden som kan tänkas behövas. Eller strunta i CPUeffektivitet och kopiera ner värden i bufferten allteftersom de förbrukas.

Om du berättar vad den egentliga problemet är kanske vi kan ge ett bättre svar.

Re: Bolla med matriser, hur?

Postat: 18 apr 2012, 17:13
av Johnny Rosenberg
uppsalanet skrev:Hej,
Vilket språk ämnar du använda?
//Fredrik
Jisses, glömde jag nämna det? Det var klantigt av mig. Tänkte använda C.

Re: Bolla med matriser, hur?

Postat: 18 apr 2012, 17:47
av Johnny Rosenberg
David Andersson skrev:Om du berättar vad den egentliga problemet är kanske vi kan ge ett bättre svar.
Jag brukar undvika att göra det, eftersom en del tolkar det som att man vill att andra ska skriva ens program åt en… Därför brukar jag bara försöka skapa något enkelt exempel som belyser just den del av det hela där jag för tillfället har svårt att hitta en optimal lösning.

Men eftersom du frågar tänker jag skriva ett litet program som läser in en ljudfil (valfritt format så länge det stöds av libsndfile, men i mitt fall kommer det till nästan 100% att röra sig om FLAC-filer) och agerar brickwall-limiter på den (bland annat).

Programmet ska leta efter toppar som överskrider en viss nivå. När en sådan hittas, ska en ”envelope” påföras signalen så att den högsta nivån istället landar PÅ den givna nivån. Så när jag hittar en topp kommer ett antal värden före toppen att påverkas, likaså ett antal värden efter toppen.
Oftast skulle det funka bra att bara läsa in hela filen i bufferten, men det KAN ju hända att man någon gång har spelat in en hel konsert som en enda FLAC-fil eller av någon annan anledning har att göra med riktigt långa filer.

I och för sig spelar det kanske inte någon större roll, mer än att datorn börjar skriva på hårddisken när internminnet inte räcker till. Hade kanske för höga ambitioner, men buffertlösningen känns ändå som att man gör rätt från början, även om det kan vara lite klurigt att få till det…

Jag skulle kanske i teorin kunna använda en länkad lista, men de funktioner som står till förfogande i libsndfile går ut på att man anger en pekare till en buffert som parameter samt buffertens storlek med mera, och funktionen fyller då på bufferten. Tänkte att det var lämpligt att försöka utnyttja detta…

Jag kan inte i dagsläget se att det finns något sätt att i förväg ta reda på hur stor bufferten behöver vara för att hela filens ljuddata ska få plats, med mindre än att läsa igenom hela filen i förväg, och det är ju kanske inte det första man kommer att tänka på när man är ute efter en snabb och effektiv lösning. Jag har studerat WAV-filer lite, även om det var ett tag sedan, och i dessa framgår hur många bytes ljuddata som finns sparade, men det är möjligt att motsvarande information inte finns tillgängliga i filer av övriga format som stöds av libsndfile.

Man kan ju förstås ha en viss buffertstorlek till att börja med som man sedan utökar i steg tills man nått filslutet. Då har man hela låten i minnet och kan göra vad tusan man vill fram och tillbaka hur som helst, men man måste ju helst då ha tillräckligt mycket minne i burken så att den inte börjar larva sig med hårddisken.

En låt på 5 minuter, två kanaler, tre bytes per sampel (24 bitar) och 44,1 kHz samplingsfrekvens tar ju upp cirka 79 MB (riktiga MB alltså, det vill säga 79·10⁶ bytes) så det borde ju inte vara några problem så länge det gäller vanliga låtar och inte exempelvis inspelning av Beethovens nia eller liknande.

Det är ju rena ljuddata man hanterar, det vill säga sampel för sampel, så det spelar ingen roll vilket format filen har, Ogg Vorbis, mp3, FLAC, vad som helst, allt ”extraheras” automatiskt av libsndfile till rena ljuddata. Så klart. Annars var det ju inte mycket bevänt med det hela…

Och apropå Beethovens nia, om någon inte redan visste, så var den den som utgjorde ett av kraven när de olika parametrarna för CD skulle fastställas en gång i tiden. Det var inte ljudkvaliteten som kom först, utan det var tydligen väldigt viktigt att Beethovens nia skulle få plats på en CD. Beroende på hur fort man spelar brukar den ligga på runt 70 minuter, så jag antar att man ville ta till några minuter extra för säkerhets skull, när de bestämde sig för 74 minuter (något som sedan dess utökats efter hand). Sedan var det bara att ta hänsyn till vad dåtidens teknik tillät, därav att skivan blev 120 mm i diameter (Sonys variant – Philips variant var faktiskt bara 115 mm), 16 bitars upplösning (Philips: 14 bitar) och 44,1 kHz samplingsfrekvens.

Re: Bolla med matriser, hur?

Postat: 18 apr 2012, 23:00
av Johnny Rosenberg
Ju mer jag tänker på det, desto mer inser jag att det bästa trots allt nog är att läsa in hela låten i en buffert. Det blir så oändligt mycket enklare, kanske till och med så enkelt att det hela blir av… ;D

Re: Bolla med matriser, hur?

Postat: 19 apr 2012, 19:45
av Johnny Rosenberg
Har kikat lite på libsndfile och funnit att det är väldigt enkelt att ta redan på längden av ljuddata i en ljudfil räknat i sampel.
File Open Function
SNDFILE* sf_open (const char *path, int mode, SF_INFO *sfinfo) ;

The SF_INFO structure is for passing data between the calling function and the library when opening a file for reading or writing. It is defined in sndfile.h as follows:

Kod: Markera allt

typedef struct{
	sf_count_t  frames; // Used to be called samples.
	int samplerate;
	int channels;
	int format;
	int sections;
	int seekable;
} SF_INFO;
Enkelt exempel:

Kod: Markera allt

/*
libsndfile1-dev måste vara installerad för att exemplet ska fungera:
sudo apt-get install libsndfile1-dev
*/
include <stdio.h>
include <stdlib.h>
include <sndfile.h>

int main (int argc, char *argv[])
{
	SNDFILE *Infile, *Outfile;
	SF_INFO FileInfo;

	if ((Infile=sf_open(FileName, SFM_READ, &FileInfo))==NULL){
		printf ("Kan inte öppna denna fil:\n%s\n", FileName);
		sf_close (Infile);
		exit(1);
	}

	double PlayingTime;
	PlayingTime=(double)FileInfo.frames/FileInfo.samplerate;
	printf("Frames: %lld\nPlaying time: %f s.\n", FileInfo.frames, PlayingTime);

	sf_close (Infile);
	exit(0);
}
Resultat:

Kod: Markera allt

$ ./a.out ~/Eget/Media/Ljud\ och\ bild/Audio/Musiksamling/Albert\ Lee/09.\ Payola\ Blues.flac
Frames: 19248768
Playing time: 436.480000 s.
$
436 s=7:16, vilket stämmer med låtens verkliga längd.
En ”frame” är en sampel för samtliga inblandade kanaler, så om vi har en stereo-fil är en frame två värden: Vänster och höger. Första värdet är vänster och andra höger, så ska man bara analysera en kanal struntar man i vartannat värde för en stereo-fil. Normalt brukar man nog köra igenom alla kanalerna i en inre loop för varje frame.
Man pratar också om ”items” när man läser en ljudfil, och då läser man ett sampel åt gången istället för en hel frame.

Funderar på vilket svenskt ord som bäst stämmer med ”frame” i detta fall, men kommer inte på något bra.

Hittade en del ytterligare finesser i libsndfile som jag inte kände till tidigare. En del grejer som jag själv tänkte skriva funktioner för, visade sig redan finnas inbyggt i libsndfile. Där kommer jag förhoppningsvis att spara lite tid.