Plugins en C++

@3ddie dan is mijn declaratie uitleg niet korrekt.

of half korrekt...

ik snap dat de syntax priegelen om de vierkante millimeter soms is, in C en C++.....

( er is ook nog iets zoals int* p;
maar dat ben ik weer vergeten, het verschil)
Nou, ik denk waar ProgHead een beetje mee zat is dat een 'adres' ook gewoon een integer is. Afhankelijk van je processor (of 'modus' van de processor) is dat 32-bits of 64 bits maar in principe is een geheugen adres iets dat opsombaar is. Het begint bij adres 0 en gaat tot je fysieke geheugenomvang.

Dus theoretisch zou je kunnen afspreken dat je zoiets zou kunnen doen:
Code:
char c = 'a';
int adres = &a;

cout << *adres << '\n';

Maar nu moet of de compiler, of de runtime (dynamic typing) weten naar wat voor ding dat 'adres' nu wijst. Is het een char? Is het een int? Is het een struct?

En daarom wordt een pointer gedeclareerd als 'een pointer naar een bepaald type'. En we hebben notatie voor het declareren van een getypeerde variabele:
Code:
int a;

en dus moeten we ook syntax hebben voor het declareren van een pointer naar een getypeerd stuk geheugen:

Code:
int *p;   //  het * hier geeft aan dat we een pointer naar een int type willen hebben
 
Goed - ik moet *p dus lezen als "let op: p is een pointer". Uitspraken Q waarin *p voorkomt kun je dan schrijven als de gecombineerde uitspraak R & S met R = "let op: p is een pointer" en S is Q waaruit het sterretje is weggelaten.
 
@3ddie dank voor je uitleg! het is mij duidelijk.

Goed - ik moet *p dus lezen als "let op: p is een pointer". Uitspraken Q waarin *p voorkomt kun je dan schrijven als de gecombineerde uitspraak R & S met R = "let op: p is een pointer" en S is Q waaruit het sterretje is weggelaten.

en dit is voor mij weer onduidelijk.

het begint me enigszins te dagen....



OEPS: 2 berichten tegelijkertijd gemaakt, multi-threading.....


EDIT:

ik heb nog

int* p;

opgezocht wat toch identiek blijkt te zijn aan

int *p;

maar verwarring kan brengen, b.v.

int* p, x;

maakt van x géen pointer... want de asterix hoort bij p, en niet bij int. of zoiets. ahum.

kuch.
 
( er is ook nog iets zoals int* p;
maar dat ben ik weer vergeten, het verschil)

Goed dat je dat aanhaalt. Het is hetzelfde maar ik zal je laten zien waarom ik liever int *a; schrijf dan int* a;

Code:
int* a;
int *b;
int* c, d, e;

De eerste twee zijn gewoon echt synoniem. Dit maakt geen verschil. En als je naar de eerste twee kijkt dan zou je zelfs kunnen zeggen dat 'int* a' beter is dan 'int *a' omdat het type 'int pointer' is dus het sterretje hoort dan bij 'int' en niet bij 'a'.

Maar!!! (en nu komt het):

Als je naar de derde regel kijkt dan zou je zeggen dat het drie integer-pointers tegelijk declareert. Maar wat er daadwerkelijk gebeurt is dat we maar één int pointer krijgen (c) en zowel d als e zijn gewone integers. En daarom schuif ik zelf liever het sterretje naar de variabele omdat het explicieter is:

Code:
int *c, d, e;

Het is nog steeds niet fraai maar je kunt in ieder geval afleiden dat c een pointer is en d en e waarschijnlijk niet. Hoe ik dit in echte software zou doen is zo:
Code:
int *c;
int d, e;

Uiteraard zouden programmeurs moeten weten dat alleen de eerste variabele een pointer is en de twee andere niet maar waarom aan dit toeval overlaten? Ik schrijf het liever expres. Iedere C/C++ programmeur die dit leest snapt precies dat ik 1 int* c heb en 2 integers d en e.

Het declareren van drie integer pointers gaat zo:
Code:
int* c, *d, *e;

int *c, *d, *e;

En ik vind de tweede variant gewoon netter en leesbaarder. Vandaar dat ik altijd het * naar de variabele naam "schuif".
 
Laatst gewijzigd:
Goed - ik moet *p dus lezen als "let op: p is een pointer". Uitspraken Q waarin *p voorkomt kun je dan schrijven als de gecombineerde uitspraak R & S met R = "let op: p is een pointer" en S is Q waaruit het sterretje is weggelaten.
Ja echt heel helder is dit niet maar ik zeg nee. Dat is niet hetzelfde. *p is niet "pas op dit is een pointer". We willen graag weten waar de pointer naar wijst dus leer jezelf aan om het een 'integer pointer' te noemen of een 'char pointer' of een 'pointer naar een struct'. Het voorkomen van een pointer zonder * of zonder dereference (->) is in principe het adres waar de pointer naar wijst. Als de pointer naar een atomair type wijst (int, char, etc.) dan is *pointer de waarde. Wijst de pointer naar een samengesteld type (struct) wijst pointer->member naar het lid in de struct. C++ werkt met objecten en daar kun je ook referenties naar hebben en dan kun je een pointer ook gebruiken om een functie in dat object aan te roepen pointer->functie(1,2,3).

Om de zaak nog verwarrender te maken hebben we ook nog zogenaamde void pointers. Dit is inderdaad puur een adres en je moet casting gebruiken om er iets mee te kunnen. Het voordeel van void pointers is dat je ze overal naar kunt laten wijzen... integers, chars, structs, objecten... anything... maar je moet een cast doen om iets betekenisvols te kunnen doen. Die cast doet niet iets magisch... het kan niet een integer naar een struct converteren. Het verandert alleen de manier waarop de pointer geïnterpreteerd wordt. Laat je het naar een integer wijzen maar cast je het naar een struct dan is het dereferencen van waarden in de struct een grote gok met waarschijnlijk onwenselijke uitkomsten. Dus void pointers zijn cool maar zijn ook een middel om jezelf gigantisch in de voet te schieten. Met andere woorden: mijd het tenzij het niet anders kan.
 
Omdat ik geen geschikt open source progje van een sample player kon vinden heb ik er met behulp van AI maar een gegenereerd. Zie bijlage. Dat progje ga ik nu verder bestuderen en naar mijn eigen wensen aanpassen. Vervolgens zal ik er mijn eigen eerdere progje als geluidsbron in opnemen. Als dat allemaal lukt...

Voor een beter begrip van C (en C++) ga ik dit boek lezen: Expert C Programming
 

Attachments

  • simpel.zip
    3,3 MB · Bekeken: 8
Het is overigens geen foute werkwijze als je synth een tweetrapsraket zou zijn: in fase 1 maakt je algoritme offline een sample. In fase 2 kan je met je sample player die sample, naar de juiste noot getransponeerd, live spelen. Je algoritme hoéft niet live tijdens het spelen de klank te genereren. Dat biedt natuurlijk wel meer mogelijkheden: live modulatie, parameters die anders zijn in functie van toonhoogte etc. Dus het is zeker wel een meerwaarde als je algoritme live de klank kan genereren. Maar als dit niet zou gaan bv omwille van latency, is een tweetrapsraket een mogelijke "nood"oplossing om toch nog eten op tafel te krijgen.
 
Ik streef naar een tussenoplossing waarbij de gebruiker de synth na het inschakelen op de gewenste klank moet inregelen. En pas daarna kan er dan bij gelijkblijvende instelling op de synth worden gespeeld.
 
Uiteindelijk is het verschil tussen een 'oscillator' die een waveform genereert of een 'sample player' niet zo groot. Ik heb ooit een synthje gemaakt die samples gebruikte voor zijn waveforms. Deze waren pre-anti-aliased zodat je daar minder last van hebt zonder zware oversampling toe te hoeven passen. Dus eigenlijk waren de waveforms al single-cycle samples. Dat concept is heel makkelijk uit te breiden naar een 'algemene' sample. Je verlegt de pointer van je Waveform tabel naar de sample en klaar is Kees.

@ProgHead: denk goed na welke kant je op wilt gaan. Standalone programma bouwen lijkt makkelijk maar je bent dan ook verantwoordelijk voor de audioengine, midi interfacing e.d. Als je een plugin bouwt dan moet je aan een aantal voorwaarden voldoen maar de DAW doet (een deel van) het moeilijke werk voor je. Zo kun je beginnen met een plugin zonder UI. De DAW levert dan "standaard" bedieningselementen in een lelijke lijst onder elkaar... maar het werkt. Airwindows levert zijn plugins zonder UI maar die hebben vaak maar een paar parameters. Een synth, zelfs een eenvoudige met maar 1 oscillator heeft veel meer parameters. Als je voor plugin kiest zou ik voor een modern interface gaan zoals CLAP. Ondersteunt MIDI/2.0, MPE, en sample accurate automation. Er zijn wrappers die een CLAP ombuigen naar een VST3, AU (voor Mac) maar zelfs naar standalone etc. Voordeel is ook CLAP is een open standaard en goed gedocumenteerd.

Als ik heel eerlijk ben en gezien je huidige kennis van C/C++ is misschien een standalone programma het beste om mee te beginnen. Vergeet de UI, doe alles als Command line programma en bouw het langzaam op. Eerst een geluidje produceren, dan een sample afspelen, dan proberen een waveform te genereren on the fly (ipv een sample) etc.
 
Als ik waar ik nu mee bezig ben met Faust had gebouwd, was ik allang klaar geweest. Faust regelt de MIDI en audio interface al voor je, en dat is precies waar ik nu mee aan het worstelen ben. Dat ik nu nog door ploeter is vooral omdat ik beter in C en C++ thuis wil raken, dus als oefening. Maar het zullen zeker niet mijn favoriete talen worden.
 
@ProgHead "Voor een beter begrip van C (en C++) ga ik dit boek lezen: Expert C Programming" Dat is een heel leuk boek om te lezen.
Ik vind trouwens C een heel ander beest dan C++ (en veel prettiger). Ik kan mij niet herinneren dat er iets over C++ in dat boek werd gezegd.
 
  • Duimpje
Duimpjes: HJB
Omdat C++ op C voortbouwt zal ik uit het boek vanzelf ook wel wat over C++ leren. Vooral de syntax van C en C++ staat mij gigantisch tegen, maar ik hoop door wat meer over de achtergronden van C te lezen ook wat meer begrip voor die syntax te krijgen.
 
Wat syntax betreft heb je vaak ook nog keuzes. Veel programmeurs houden van "hoe korter, hoe beter". En ik hou van een code die leest als een stationsrommanetje. Dus mijn Arduino C++ code ziet er vaak een beetje als VB6 code uit. Natuurlijk ga ik hier ook niet té ver in, de leesbaarheid kan ook ten koste van de verstaanbaarheid gaan. Een enkel IF statement schrijf ik graag groot uit, maar als je 30 cases te checken hebt dan gebruik ik ook gewoon case en geen 30 IF statements hoor.
 
Wat syntax betreft heb je vaak ook nog keuzes. Veel programmeurs houden van "hoe korter, hoe beter". En ik hou van een code die leest als een stationsrommanetje. Dus mijn Arduino C++ code ziet er vaak een beetje als VB6 code uit. Natuurlijk ga ik hier ook niet té ver in, de leesbaarheid kan ook ten koste van de verstaanbaarheid gaan. Een enkel IF statement schrijf ik graag groot uit, maar als je 30 cases te checken hebt dan gebruik ik ook gewoon case en geen 30 IF statements hoor.

Dat zijn ook niet de dingen waarover ik struikel. Het gaat mij meer om de notatie van pointers, e.d. En de onduidelijkheid over wat dat sterretje nou precies betekent of doet. Geeft je met *p enkel aan dat p een pointer is? Kennelijk niet, want dan zou je na te hebben aangegeven dat p een pointer is ook p (zonder sterretje) als pointer kunnen gebruiken. Of is het sterretje meer een operator die werkt op wat eronder staat, zodat *b (meestal) iets anders is dan b? En verder heb je dan ook nog dat je het sterretje van *p van p kunt losmaken en bijvoorbeeld aan een daarvoor staande int kunt plakken zonder dat dit de betekenis van de code verandert. Kortom: hoe creëer ik een maximale verwarring...
 
Dat ik nu nog door ploeter is vooral omdat ik beter in C en C++ thuis wil raken, dus als oefening. Maar het zullen zeker niet mijn favoriete talen worden.

:halleluja ... ik zou dat niet kunnen. Tenminste niet als het voor de hobby is en je echt substantieel moet programmeren. Voor iets "kleins" wat regels code schrijven in een taal die je niet ligt is nog tot daaraan toe, maar een wat grotere applicatie bouwen, zou dan snel op zoek gaan naar iets anders en als dat niet kan, het idee in een andere vorm gieten (ander platform, andere hardware, etc.) om het op die manier te realiseren.

Academische overwegingen voor mij op dit moment, voor de komende herfst- en winteravonden lonkt de soldeerbout toch wat meer dan de kompjoetur.
 
Ik weet ook niet hoe lang ik dit nog volhoud. De talen die mij beter liggen ken ik al, en daarin zou ik dit project nu allang klaar hebben. Maar tot nog toe ligt de bevrediging van het oefenen met C++ er voor mij vooral in dat dit (op computergebied) de hoogste berg is de ik nog wil beklimmen.

Daarna is het klaar met het (zelf opgelegde) ploeteren en zwoegen, en ga ik in mijn hobby vooral voor het gemak en de lol. Dat wil zeggen: voor uitdagingen die beter te behappen zijn, en waar ik niet dag en nacht mee in weer hoef te zijn om de klus te klaren.
 
Ik heb nu een werkend progje. Had er helaas voor de MIDI en Audio interface veel hulp van AI voor nodig. Zelfs de door AI voorgestelde code zat vol fouten, maar als je die foutmeldingen steeds weer invoert kom je uiteindelijk (na zo'n 10 pogingen) tot een werkend code-fragment. Hier wat gepingen van mij op mijn soft synthje:

 
Het gaat mij meer om de notatie van pointers, e.d. En de onduidelijkheid over wat dat sterretje nou precies betekent of doet. Geeft je met *p enkel aan dat p een pointer is? Kennelijk niet, want dan zou je na te hebben aangegeven dat p een pointer is ook p (zonder sterretje) als pointer kunnen gebruiken. Of is het sterretje meer een operator die werkt op wat eronder staat, zodat *b (meestal) iets anders is dan b? En verder heb je dan ook nog dat je het sterretje van *p van p kunt losmaken en bijvoorbeeld aan een daarvoor staande int kunt plakken zonder dat dit de betekenis van de code verandert. Kortom: hoe creëer ik een maximale verwarring...

Het beste kun je de declaratie van een pointer variabele los zien van het gebruik van de variabele. Zoals eerder al gezegd we moeten duidelijk maken waar de pointer naar wijst. Is het een int? een char? een float? Daarom specificeren we bij declaratie wat het type is en dat het een pointer is:
Code:
float f = 0.0f;     // een normale float, om een float-pointer te definiëren gebruiken we '*':
float *fp = NULL;   // de pointer *fp wijst naar niets.

Voor het gebruik van pointers heeft '*' de betekenis dat we refereren aan waar de pointer naar wijst. We "volgen" dus de pointer. fp zelf bevat een adres. fp claimt dat daar een float staat. Als we willen weten wat die float dan echt is moeten we de pointer volgen. We moeten in het geheugen gaan kijken wat daar staat. Die operatie is 'dereferencing'.
Code:
fp = &f;     // hier krijgt fp 'het adres' van de float variabele f. De pointer wijst hierna naar het geheugen waar f is opgeslagen (dit noemen we referencing)
             // omdat we hier de pointer zelf aan een adres koppelen gebruiken we geen '*' want we kennen een waarde toe aan de variabele fp zelf.

*fp = 2.0f;  // hier willen we niet 'fp' de waarde 2.0f geven (fp = 2.0f; //kan niet) maar de float waar fp naar wijst -> daarom *
             // Het '*' staat dus voor de-referencing. We willen niet toekennen aan de variabele fp maar we willen toekennen aan waar fp aan refereert
             // (de float in het geheugen). Dit noemen we de-referencing.

Je zag in het eerder voorbeeld dat ik post dat als je een pointer zelf toont (cout << fp; ) dat je een hexadecimaal getal krijgt en dat is het geheugenadres dat is opgeslagen in de float* variabele fp zelf. Als je wilt weten wat die float is dan moet je dus de-referencen: cout << *fp;
 
Back
Top