Archief - Muzieknoten modelleren om snel noten te vinden per tijdstip

Het archief is een bevroren moment uit een vorige versie van dit forum, met andere regels en andere bazen. Deze posts weerspiegelen op geen enkele manier onze huidige ideeën, waarden of wereldbeelden en zijn op sommige plaatsen gecensureerd wegens ontoelaatbaar. Veel zijn in een andere tijdsgeest gemaakt, al dan niet ironisch - zoals in het ironische subforum Off-Topic - en zouden op dit moment niet meer gepost (mogen) worden. Toch bieden we dit archief nog graag aan als informatiedatabank en naslagwerk. Lees er hier meer over of start een gesprek met anderen.

sanzo

Legacy Member
Lange tittel :)

Ik zit vast met iets en hopelijk heeft iemand hier suggesties over hoe ik dit beter zou kunnen aanpakken. Momenteel ben ik aan een sequencer aan het werken voor de ipad. Om het simpel te houden komt alle geluid nu van een zelfgemaakte 'synth' (niet veel meer dan een sinusfunctie).

De gebruiker vult allemaal noten in die hij wilt laten afspelen door de sequencer. De iOS API om geluid af te spelen vraagt herhaaldelijk naar x aantal samples om af te spelen.

Mijn model is in grote lijnen: een Song die meerdere Tracks heeft, waarvan elk meerdere Patterns kan hebben (soort lange maten) en elke Pattern heeft meerdere Noten.

Elke noot heeft een starttime. Ik werk met 44100 samples p/s dus als de starttime van een noot 88200 is bijvoorbeeld weet ik het moet afspelen vanaf de tweede seconde. Ik hou ook bij hoeveel samples ik al naar de buffer gestuurd heb in totaal zodat ik dus kan weten op elk moment waar ik moet hervatten als er nieuwe samples worden gevraagd.

Ik ben dit in C++ aan het doen en hou de noten binnen een Pattern in vectoren. Mijn probleem: stel dat een nummer 10 minuten lang is, en ik heb 4 tracks (gitaar, bass, synth, drum). Dat zijn in totaal een hele boel noten. Als ik ze elke keer allemaal moet afgaan om te zien welk om tijdstip x beginnen krijg ik de buffer volgens mij nooit op tijd gevuld. Daarbij moet ik dan ook nog eens de samples zelf berekenen aan de hand van de noten. Gedoemd om te falen.

Heeft er iemand suggesties hoe ik dit efficient zou kunnen aanpakken?

Gurdt

Legacy Member
Ik doe maar een wilde gooi he: ge zou een database kunnen gebruiken en dan query'en op de tijdstippen (eventueel in range, dus bv tijdstip tussen x en y) van uw noten. Afhankelijk van de door u gebruikte database kan dit efficiënt adhv een index op die tijdstippen.

Ge moet nu gene Oracle of SQL Server op gaan zetten, er bestaan light-weight alternatieven, al dan niet relationeel :) Ik weet dat bv SQL Server een Express variant heeft voor mobile devices, maar ik weet niet of dit ook voor Apple kan.

sanzo

Legacy Member
Ik kan me moeilijk voorstellen dat een database gebruiken efficiënter zal zijn dan alles in geheugen bijhouden. Daarbij ga ik die keuze moeilijk kunnen verantwoorden voor een muziekapplicatie. (eindwerk dit hier)

Er moeten betere manieren zijn, dit is zeker geen uniek concept, er bestaan sequencers zat voor elk platform.

Gurdt

Legacy Member
En waarom kan je je dat moeilijk voorstellen? :) Je neemt wellicht aan dat een database een gigantisch dik zwaar programma is, maar een vector is ook maar een database.

Wat gij wilt doen is een hoop data bijhouden, en gemakkelijk kunnen zoeken op bepaalde eigenschappen van die data (een index op tijdstip). Dit kan je zelf schrijven, of je gebruikt een database! Er bestaan genoeg in-memory databases ook hee.

sanzo

Legacy Member
Misschien heb je gelijk, SQLite zal wel ondersteund worden en misschien dat die ook snel genoeg zal zijn.

Een probleem waar ik aan denk bij het gebruiken van een database (maar bij nader inzien zou dat probleem er ook zijn bij bv. vectoren) is dat ik geen absolute starttijd meer kan gebruiken omdat ik dan bij het veranderen van de BPM, dez voor elke noot zou moeten aanpassen.. daar moet ik ook weer iets anders op vinden.

Lethall

Legacy Member
Ook eens een wilde gok doen.

Steek uw noten per track in iets dat sorteert op grote zoals bijv een BinaryHeap (en dan gesorteerd op hun startTime).

Hou van iedere track de eerstvolgende noot bij, iedere frame (of wanneer ge het nodig hebt) kijkt ge naar de startTime van die noot, als die op dat moment valt start die met afspelen (en steekt ge die in een vector van huidige noten die afgespeeld worden of whatever).

En dan iedere keer als een noot start popt ge een nieuwe uit uw BinaryHeap.

Bij 4 tracks zijn dat 4 noten dat ge moet overlopen, en een Binary heap is normaal gezien heel snel voor zo'n dingen (lijst die gesorteerd is op een bepaald iets de laagste er uit halen).

Eventueel eerst hetzelfde met uw patterns, en dan iedere pattern heeft een binaryHeap van noten, als die leeg is start ge de volgende pattern (want ik gok dat die altijd aansluiten aan elkaar).


Geen idee hoe dit zou gaan, het is maar een idee : p

sanzo

Legacy Member
Waarom zou ik de noten per grote moeten opslaan? En ik snap niet goed wat je bedoelt met de eerstvolgende noot bijhouden. Gezien ik dat toch moet berekenen aan de hand van de starttijd maakt het toch weinig uit of ik dat een noot op voorhand doe, denk ik? Ik snap niet zo goed wat je bedoelt vrees ik :p

Een track is trouwens gewoon een "lijn" die instrument info bevat, de noten zijn alleen aan patterns rechtstreeks gebonden (zodat een pattern bv. 5x achter elkaar kan worden gespeeld als het een repetitief drum beat is of zo)

Lethall

Legacy Member
als ik het goed begrijp weet je op voorhand de start tijd van iedere noot?

Dus je steekt alle noten voor die track in een Binary heap gesorteerd op hun starttijd.

Dus 4 tracks, heb je 4 gesorteerde lijsten. (of iets in die zin, ik ga er van uit dat ieder instrument op een of andere manier zijn eigen "lijst" van noten heeft dat hij moet afspelen)

Iedere frame kijk je naar de eerste noot in iedere van die 4 binary heaps, als de starttijd gelijk is aan de huidige tijd, dan haal je die er uit en laat je die noot afspelen.

(eventueel steek je die in een vector met alle noten die op dat moment aan het afspelen zijn).

Dus in principe exact hetzelfde als iedere noot overlopen, alleen met het grote verschil dat je enkel naar de eerstvolgende noot kijkt (+een binaryheap is normaal gezien snel voor zo'n dingen).

Hopelijk komt dit iets duidelijker over, ik ben soms niet zo goed in dingen uitleggen.
Allé dit is als ik het correct zelf begrijp wat ge probeert te doen : p

Moto

Legacy Member
Een goed idee om te beginnen bij zo'n dingen is altijd om te gaan zien hoe "gevestigde waarden" die problemen oplossen, bijvoorbeeld hier is de beschrijving van het Guitar Pro 4 file format

Guitar Pro 4.06 File Format Description

Anyway uw "noten" zult ge toch direkt allemaal kunnen inlezen, die dingen gewoon via id laten verwijzen naar settings of samples, zodat ge bij vanaf bepaalde minuut, dat via streamen + bufferen makkelijk uitleest liefst nog in een aparte thread die de volgende maten uitleest terwijl een andere thread de huidige maat afspeelt, als ge multithreaded kunt gaan in een iOS app

sanzo

Legacy Member
Lethall zei:
Dus je steekt alle noten voor die track in een Binary heap gesorteerd op hun starttijd.
Binary Heaps kende ik niet maar zal ik eens in kijken. Als die effectief snel genoeg zijn in het sorteren zou dat de oplossing kunnen zijn.
Er kunnen bij het maken van een pattern natuurlijk noten tussen komen dus op zo'n moment zouden die heaps opnieuw moeten worden gesorteerd.


Moto zei:
Een goed idee om te beginnen bij zo'n dingen is altijd om te gaan zien hoe "gevestigde waarden"
Ik wist niet dat het GP file format open source was, zal er eens in kijken. Op het eerste zicht ziet het er wel veel uitgebreider uit dan ik nodig heb.


Moto zei:
Anyway uw "noten" zult ge toch direkt allemaal kunnen inlezen
Dat snap ik niet zo goed. Het niet (op tijd) kunnen inlezen is juist mijn probleem. Dat van die multithreads is wel een goed idee en ik veronderstel dat het wel ondersteund wordt, net zoals op Android.



Bedankt voor jullie hulp allemaal :)

Vin

Legacy Member
Dit ruikt sterk naar een binaire zoekboom. Vergeet die binaire heap.
De binaire zoekboom is in de STL (je programmeert in C++ las ik?) geimplementeerd in de klasse std::set.

Voordelen van een binaire zoekboom:
- Element met gegeven sleutel (hier: tijdstip) opzoeken in lg(n) tijd in plaats van n in het geval van een vector.
- Eens het element gevonden is kan je in O(1) zijn voorloper en opvolger vinden, een "range" zoals je omschrijft kan je dus zeer efficient hieruit halen (de STL implementatie voorziet hier zelfs operaties voor als ik me niet vergis).

Verder: vermits je in C++ programmeert en met geluid bezig bent: bekijk zeker JUCE eens, dat is een klassenbibliotheek die voorziet in (onder meer) vanalles omtrent audio & midi, en daarnaast ook een grafische toolkit aanbiedt. Eenvoudig te compileren naar iOS ook.

PS: moest je om een of andere reden toch willen prutsen met een heap, die zit in de STL als std::priority_queue

Gurdt

Legacy Member
Ik zou ook voor die zoekboom gaan :)

(en dus standaard B-tree index in db :p)

sanzo

Legacy Member
b0red zei:
Dit ruikt sterk naar een binaire zoekboom. Vergeet die binaire heap.
De binaire zoekboom is in de STL (je programmeert in C++ las ik?) geimplementeerd in de klasse std::set.

Voordelen van een binaire zoekboom:
- Element met gegeven sleutel (hier: tijdstip) opzoeken in lg(n) tijd in plaats van n in het geval van een vector.
- Eens het element gevonden is kan je in O(1) zijn voorloper en opvolger vinden, een "range" zoals je omschrijft kan je dus zeer efficient hieruit halen (de STL implementatie voorziet hier zelfs operaties voor als ik me niet vergis).

Verder: vermits je in C++ programmeert en met geluid bezig bent: bekijk zeker JUCE eens, dat is een klassenbibliotheek die voorziet in (onder meer) vanalles omtrent audio & midi, en daarnaast ook een grafische toolkit aanbiedt. Eenvoudig te compileren naar iOS ook.

PS: moest je om een of andere reden toch willen prutsen met een heap, die zit in de STL als std::priority_queue
Ik had ondertussen voor een multiset geopteerd. Ga als key de maatnummers gebruiken en dan kan ik alle noten van een maat in een keer ophalen.

Bedankt ook voor de JUCE suggestie, zal er eens naar kijken.

Vin

Legacy Member
Gurdt zei:
Ik zou ook voor die zoekboom gaan :)

(en dus standaard B-tree index in db :p)

Met als verschil dat een B-tree niet bepaald een binaire zoekboom is dan. Vooral nuttig indien de data niet allemaal in het geheugen passen, maar dat is hier nog wel het geval denk ik.

Gurdt

Legacy Member
b0red zei:
Met als verschil dat een B-tree niet bepaald een binaire zoekboom is dan. Vooral nuttig indien de data niet allemaal in het geheugen passen, maar dat is hier nog wel het geval denk ik.

Ja, ma binair heeft nie echt veel nut als het nie moet door constraints.

Vin

Legacy Member
Uitgezonderd zijn eenvoud natuurlijk (groter? rechts! kleiner? links!), daar waar de operaties op een B-tree al heel wat complexer worden indien knopen gesplitst of samengevoegd moeten worden.
Goed, ik denk dat we gewoon over andere zaken bezig zijn, ik heb het meer over de inwendige datastructuur, jij eerder over een database systeem.

Gurdt

Legacy Member
b0red zei:
Uitgezonderd zijn eenvoud natuurlijk (groter? rechts! kleiner? links!), daar waar de operaties op een B-tree al heel wat complexer worden indien knopen gesplitst of samengevoegd moeten worden.
Goed, ik denk dat we gewoon over andere zaken bezig zijn, ik heb het meer over de inwendige datastructuur, jij eerder over een database systeem.

Gade gij nooit zo een structuur zelf programmeren he :D Anders hebt ge gelijk ja.

Cycloon

Legacy Member
Muziek speelt toch gewoon sequentieel af? Als je een simpele queue gebruikt en steeds kijkt of de eerst volgende noot binnen het te komen tijdsinterval valt, heb je toch de oplossing? Het is toch niet zo dat je voor elke nieuwe sample heel je vector opnieuw gaat aflopen?

Vin

Legacy Member
Hoe dan ook is het gebruik van een aparte databank in dit geval af te raden. Dit omwille van het feit dat real time audio nergens op kan wachten, of je zit met glitches. Complexe berekeningen in de callbackfunctie zijn meestal geen probleem. Anders is het wanneer je locks op andere threads moet aanvragen, hetgeen bij een databank normaalgezien gebeurt wanneer je een tabel wil raadplegen om inconsistenties te voorkomen.

In het algemeen moet je bij het werken met audio opletten op eender welke call die kan blokkeren, waar je zelf de controle niet hebt over hoe lang hij zal blokkeren. Zo ook bijvoorbeeld het alloceren van geheugen is gevaarlijk, aangezien dit een blokkerende call naar het besturingssysteem aanroept.
Vandaar:
-opgelet met het gebruik van de "new" operator in de callback
-geen nieuwe elementen toevoegen aan de set tijdens callback
-nooit zaken in vectoren pushen in callback. Algemener: let in het geval van werken met real time audio altijd op de worst case uitvoeringstijd, nooit op de gemiddelde. Wanneer bijvoorbeeld de achterliggende tabel van je vector net vol zit en wordt vervangen door een tabel met dubbele grootte -> geheugen alloceren -> blokkerende call -> grote kans op glitches.

Een héél interessante blogpost over deze materie:
Real-time audio programming 101: time waits for nothing | Ross Bencina

sanzo

Legacy Member
Ziet er interessant uit, zal het eens doornemen.
Wel goed om weten dat ik op geen enkel moment met nieuwe objcten mag afkomen tijdens het afspelen. Ik ga nog eens goed moeten nadenken hoe ik ga bijhouden hoeveel samples al gestuurd zijn en waar ik moet hervatten want door dat artikel weet ik het weer even niet :p

Cycloon zei:
Muziek speelt toch gewoon sequentieel af? Als je een simpele queue gebruikt en steeds kijkt of de eerst volgende noot binnen het te komen tijdsinterval valt, heb je toch de oplossing? Het is toch niet zo dat je voor elke nieuwe sample heel je vector opnieuw gaat aflopen?
Het is niet gezegd dat alle noten in volgorde worden ingevoerd en ik ze zo kan afspelen achter elkaar en er kunnen ook meerdere noten tegelijk vallen.
Daarbij vraagt de buffer om de zoveel tijd x aantal samples dus moet er worden bijgehouden hoeveel samples van een noot al gestuurd zijn.
Het archief is een bevroren moment uit een vorige versie van dit forum, met andere regels en andere bazen. Deze posts weerspiegelen op geen enkele manier onze huidige ideeën, waarden of wereldbeelden en zijn op sommige plaatsen gecensureerd wegens ontoelaatbaar. Veel zijn in een andere tijdsgeest gemaakt, al dan niet ironisch - zoals in het ironische subforum Off-Topic - en zouden op dit moment niet meer gepost (mogen) worden. Toch bieden we dit archief nog graag aan als informatiedatabank en naslagwerk. Lees er hier meer over of start een gesprek met anderen.
Terug
Bovenaan