This page is for programmers who want to read, maintain or extend the code that computes the physical dates of sound-objects, notes and time patterns once an item has been produced. This is done by TimeSet() and SetTimeObjects(), in the console's source file TimeSet.c. The page describes their algorithm step by step.
The theory behind this code is explained in the paper Time-setting of sound-objects: a constraint-satisfaction approach (Bel 1992). This page uses its vocabulary: phase diagram, structure of time, time streaks, pivot, local drift, global drift, correction 1 and correction 2. References such as “paper §5.2” point at its sections. Where the code has gone further than the paper, or differs from it, this page says so.
The code lives in four C files of the console (source/BP3/):
TimeSet.ccontainsTimeSet(),SetTimeObjects()andGetCap();FillPhaseDiagram.ccontainsFillPhaseDiagram(),MakeEmptyTokensSilent()andClass(), which build the phase diagram;SetObjectFeatures.ccontainsCalculate_alpha(),FixDilationRatioInCyclicObject(),SetLimits()andFixObjectTimings(), which compute the dilation ratios, the limits of each object and its default dates;TimeSetFunctions.ccontainsLocate(), the constraint solver, and its subroutinesSituation_ok(),Possible_choices(),Next_choice(),Alternate_correction1()andSolution_is_accepted().
Comments of the form // note 12 have been placed in TimeSet.c. Each one points to the numbered note of this page that explains the code below it. Searching for // note 12 finds every place that note 12 describes. Line numbers are not given because they change with every revision.
Where TimeSet() stands in the production of music
A grammar produces an item: a string of terminal symbols, notes, performance controls and polymetric structures such as {C4 D4 E4, F3 G3}. Playing this item takes three steps, called from PlayBuffer1() in PlayThings.c:
PolyMake()(Polymetric.c) normalises the polymetric structure. It computesRatio, the number of grid units per beat that represents every duration of the item exactly, andKpress, a compression rate used when the grid would be too large (note 8).TimeSet()places every object on the phase diagram (FillPhaseDiagram()), then computes the date at which each one starts and ends (SetTimeObjects()). Constraints such as overlapping, continuity, truncation, relocation and tempo breaks are resolved here.MakeSound()(MakeSound.c) reads the timed objects and sends their events to the outputs. Its algorithm is described on the page Playing an item: how MakeSound() works.
PlayBuffer1() interprets the result of TimeSet(): OK lets MakeSound() play the item, MISSED displays error 37 (no solution could be found, note 17), ABORT and EXIT stop, and AGAIN is noted in the variable again, although none of the functions called by TimeSet() returns it at present.
The data used by SetTimeObjects()
The phase diagram
The phase diagram (paper §1 and §3) is the table p_Seq. Each line nseq, from 0 to *p_nmax, is a sequence: a voice of the polymetric structure. Each column i, from 0 to maxseq, is a position in symbolic time. The cell (*((*p_Seq)[nseq]))[i] contains an instance index k:
k ≥ 2: an instance(*p_Instance)[k]that starts in this column;k = 1: a silence-;k = 0: an empty cell. The object or silence before it continues. This is the prolongation_of the paper (its null object E0);k = -1: the end of the sequence, called NIL in the paper (its virtual object E-1).(*p_imaxseq)[nseq]is its column.
The paper numbers columns from 1, and gives all real objects an index k ≥ 1. The code numbers columns from 0, reserves k = 0 and k = 1 for _ and -, and gives real instances k ≥ 2. The scans of a sequence rely on finding the -1 marker. The idiom inext = i; while(seq[++inext] == 0);, found in every function below, finds the next column that is not empty, i.e. the end of the object starting in column i.
The instances
Each instance (*p_Instance)[k] has a field object, called j in the code, which tells what it is: a sound-object if 1 < j < Jbol, a time pattern if Jbol ≤ j < 16384, a simple note of key j - 16384 if j ≥ 16384, and an out-time object (written <<…>>, with no duration) if j < 0. SetTimeObjects() computes the following fields:
alpha: the dilation ratio, the α(k) of the paper. It is the ratio between the duration of the instance and that of its prototype. If α > 1 the object is played slower than its prototype.betaandncycles: the ratio applied to the dates of the events, and the number of repetitions of the periodical part of a cyclic object (note 12).starttimeandendtime: the dates in milliseconds, the t1(k) and t2(k) of the paper.truncbegandtruncend: the durations cut at the beginning and at the end, TrBeg(k) and TrEnd(k).
The prototypes
The properties of a sound-object prototype j come from the -so file. They are global tables indexed on j. Those used here are:
p_Dur: the duration of the prototype in milliseconds, andp_Tref: the reference period. A prototype withTref > 0is striated: its duration is defined relative to a beat. A simple note is treated as an object of 1000 ms.p_PivMode,p_PivPos: the pivot, the time point placed on the streak (paper §2).p_PreRoll,p_PostRoll: durations played before the start or after the end.p_OkExpand,p_OkCompress,p_FixScale,p_RescaleMode,p_AlphaMin,p_AlphaMax,p_CyclicMode: what dilation ratios are allowed (note 12).p_OkRelocate, or elsep_MaxDelayandp_MaxForward: whether the object can be moved (Reloc in the paper), and by how much.p_CoverBeg,p_CoverEnd,p_MaxCoverBeg,p_MaxCoverEnd: whether its beginning or end may be overlapped by other objects of the same sequence (OverBeg and OverEnd), and by how much.p_ContBeg,p_ContEnd,p_MaxBegGap,p_MaxEndGap: whether its beginning or end must join a neighbour (ContBeg and ContEnd), and the largest gap allowed.p_TruncBeg,p_TruncEnd,p_MaxTruncBeg,p_MaxTruncEnd: whether its beginning or end may be truncated, and by how much.p_BreakTempo: whether a global drift may start after it (BrkTempo).
In the paper, these properties are TRUE or FALSE. In BP3, most of them come with a limit, whose mode (…Mode tables) is FIXVALUE (in milliseconds), PERCENT (a percentage of the duration of the instance) or IRRELEVANT (no limit). SetLimits() converts them to milliseconds (note 13).
Striated and smooth time
The setting Nature_of_time is STRIATED or SMOOTH (paper §7.2):
- In striated time, the structure of time is given in advance by the metronome. Column
ihas a date(*p_T)[i], its time streak, and each object is placed with its pivot on the streak of its column. The constraint solver may then move objects, truncate them, or delay all following streaks (a global drift, or tempo break). - In smooth time, there is no structure of time in advance. The objects of the first line are played one after the other, each one with the duration of its prototype. Their dates become the streaks, and the objects of the other lines are fitted between them.
Both modes use the same functions, with branches on nature_time. It is copied from Nature_of_time at the start because it must not change during the computation.
Overview of the algorithm
TimeSet() prepares the tables, calls FillPhaseDiagram(), then SetTimeObjects(), and reports the ties that could not be resolved (notes 1–6).
SetTimeObjects() works as follows:
- It computes the default structure of time: the dates
(*p_T)[i]of all columns, from the metronome in striated time, or zero in smooth time (notes 7–10). - It processes the sequences in order, from line 0 to line
*p_nmax(note 11). For each sequence:- it computes the dilation ratio of every object (note 12) and its limits of overlapping, gap and truncation (note 13), once only;
- it computes the default dates of every object, with its pivot on its streak (note 15);
- it calls
Locate(), which scans the sequence from left to right and changes these dates until all constraints are satisfied (note 16); - if
Locate()fails, it releases the constraints step by step and tries again (note 17); - it adds the global drift found by
Locate()to the structure of time (notes 19–20). In striated time, a global drift means that the sequences already placed may no longer be correct, so the whole loop starts again from line 0, as the paper explains at the end of §2; - it updates the limits
tminandtmaxof the item (note 21).
- Finally, it applies legato and staccato to all objects (notes 22–24).
Step 1 — TimeSet()
Note 1 — Starting
The function returns ABORT if an emergency stop was requested. *p_tmin and *p_tmax, the limits of the item in time, start at Infpos and Infneg so that the first object found replaces them (note 21).
Chunk_number counts the calls when the item is played in chunks (PlayChunks). It is only used in messages. It is not increased when improvising.
p_Articul is allocated with one value per instance (MaxObjects). FillPhaseDiagram() stores in it the articulation of each instance, i.e. the value of _legato() or _staccato() in force, as a percentage. It is used at the end (note 23) and freed when TimeSet() returns.
Note 2 — Tie tables
Ties (C4& … &C4, and the same for sound-objects) are resolved by FillPhaseDiagram(), which needs four tables allocated here:
p_Tie_note[channel][key]counts the ties opened and not yet closed for each MIDI channel and key (128 keys). Since 2026-11-13 it is a counter, not a flag, so that several ties of the same note can be open at the same time;p_Tie_event[instrument][j]does the same for sound-objects, for each Csound instrument and each prototypej. Its size,maxties = Jbol + Jpatt, covers sound-objects and time patterns;p_Missed_tie_noteandp_Missed_tie_eventcount the ties that could not be closed. They are reported at the end (note 6).
Note 3 — Making empty objects silent
MakeEmptyTokensSilent() (FillPhaseDiagram.c) scans the item before the phase diagram is built:
- A variable left in the item (token
T4, when the grammar did not rewrite it) becomes a new silent sound-object with the same name (tokenT47). This creates a new prototype, soJbolincreases, and a new instance, soMaxObjectsincreases andp_Instance,p_ObjectSpecsandp_Articulare enlarged. Its duration is 1000 ms. - A sound-object with no MIDI codes and no Csound score becomes a silent object. If it is striated, its duration is its reference period
Tref; otherwise it is 1000 ms.
Silent objects still take their place in the phase diagram and in the constraints. Because new prototypes may have been created, the tie tables of note 2 are enlarged to the new value of Jbol + Jpatt (fixed on 2026-11-06).
Note 4 — Filling the phase diagram
FillPhaseDiagram() reads the item, creates an instance for each sound-object, note and time pattern, with the values of the performance controls in force (channel, velocity, articulation, transposition, continuous parameters…), and places it on the phase diagram. It returns the number of instances (*p_kmx), the number of columns (*p_maxseq), the index of the last line (*p_nmax) and the last column of each line (p_imaxseq). It also adds an out-time silence at the end of the item, carrying the last values of the performance parameters.
It computes CorrectionFactor, the ratio between the expected duration of the item and the duration given by the phase diagram. It differs from 1 when Prod and Ratio overflowed in PolyMake(). The message Possible error of duration is displayed when it is outside 0.9–1.1.
Note 5 — Measuring time
The time spent in the production, in FillPhaseDiagram() and in SetTimeObjects() is added to ProductionTime, PhaseDiagramTime and TimeSettingTime. The console displays them at the end of the run (Phase-diagram filling time, Time-setting time) when they are not zero. They are measured with time(), so in whole seconds.
Note 6 — Missed ties and cleaning up
After time-setting, or after an error, every note and sound-object with ties that could not be closed is reported, with the number of occurrences and the channel or Csound instrument, followed by the total number of notes or events concerned. These are warnings (messages starting with =>): the item is still played. Then the tie tables and p_Articul are freed, and the result of SetTimeObjects() is returned.
Step 2 — The structure of time
Note 7 — Enlarging the sequences
Each line of p_Seq is enlarged to maxseq + 50 cells. The working tables (note 10) have the same size. The margin is needed because some scans read a few cells past the end marker of a line. Locate() allocates its own tables with 20 extra cells for the same reason.
Note 8 — Time streaks
In striated time, the date of each column is computed from the metronome:
period = Pclock × 1000 × CorrectionFactor / Qclockis the duration of a beat in milliseconds. The metronome isQclock / Pclockbeats per second.- A beat contains
Ratiogrid units. When the item would need too many columns,PolyMake()chose a compression rateKpress: one column of the phase diagram then holdsKpressgrid units. This is the quantization of paper §7.1. - The loop moves a position
inalong the grid, in steps ofKpressunits.Class(in)gives the column of this position. The first time a column is reached, its date isperiod × (in - 1) / Ratio. - When
Kpress > 2, the dates of the neighbouring columns are set to the same value. Because of rounding, a column could otherwise be skipped and keep the date 0.Calculate_alpha()reports this case as a Probable rounding error.
In smooth time, all dates are set to zero: they will be computed from the objects of the first line (note 20).
Note 9 — Displaying the phase diagram
This block is only compiled if DISPLAY_PHASE_DIAGRAM is set to 1 at the top of TimeSet.c. It prints the list of instances, then the phase diagram itself if it has fewer than 1000 objects, then checks that every instance points at a valid object. It is a debugging aid, and it prints its results even when trace_timeset is 0.
Note 10 — Working tables
Tables with one value per column (size maxseq + 50):
p_DELTA: the global drift of each streak computed byLocate(), the Δ(i) of the paper (notes 19–20);p_marked: in smooth time, TRUE for columns whose date is known (note 20);p_time1,p_time2: the default start and end dates of the object starting in each column (note 15);p_maxcoverbeg,p_maxcoverend,p_maxgapbeg,p_maxgapend,p_maxtruncbeg,p_maxtruncend: the limits of the object starting in each column (note 13).
p_alphadone has one value per line: TRUE once its dilation ratios and limits are computed (note 11).
max_end_time is the date of the last column. It is used in note 24.
Step 3 — Placing the sequences
Note 11 — The loop on sequences
The sequences are processed in order, from line 0. The label STARTAGAIN marks the start of this loop: after a global drift in striated time, the program comes back here and processes all lines again with the new structure of time (note 19). The dilation ratios and limits of a line are computed only once, during the first pass (p_alphadone). During the next passes, the objects keep their durations and only their dates change: this is the meaning of a tempo break, which adds time between objects rather than stretching them.
The order of the lines matters in smooth time: the dilation ratios of line nseq depend on the dates found for the lines before it (note 12).
With trace_timeset, a message gives the number of objects, the compression rate and the line being processed.
Note 12 — Dilation ratios: Calculate_alpha()
Calculate_alpha() (SetObjectFeatures.c) computes alpha for every object of the line. As the paper says (§3), all ratios are computed before the constraints are solved. The symbolic duration of an object, in beats, is d = (inext - i) × Kpress / Ratio, where inext is the next column that is not empty.
Striated time, or the first line in smooth time. The ratio is the duration available for the object, divided by the duration of reference of its prototype:
- in striated time, the available duration is
T[inext] - T[i]; the reference is 1000 ms for a simple note,Treffor a striated object (or a silent one), andDurfor a smooth object. Time patterns get α = 0 in striated time, because they carry no events; - in smooth time with a metronome (
Pclock > 0), the available duration isdbeats at the metronome period; without a metronome,alpha = d.
Other lines in smooth time. The dates of the first line are known only on its marked columns (note 20). Between two marked columns iprev and inextm, the duration To = T[inextm] - T[iprev] is shared among the objects of the line in proportion to r × d, where r = Dur / Tref for a striated object and 1 otherwise. The sum of r × d is sigmaridi, so alpha = To × r × d / dur / sigmaridi. The last object of a section gets exactly the remaining time, which compensates the rounding errors. A silence that starts and ends in the same section gets a date, and its column is marked, so that the next lines can use it. Beyond the end of a short line, the cells are treated as silences up to the end of the phase diagram, if more lines follow.
Limits. The ratio found is then restricted by the properties of the prototype:
- time patterns keep it;
- with
RescaleMode == OK_RESCALE, the ratio is accepted, within the limits below; - a prototype with fixed scale (
FixScale) that is not cyclic gets α = 1; - a cyclic object (one with a periodical part) that must be expanded is handled by
FixDilationRatioInCyclicObject(). It computesncycles, the number of times the periodical part is repeated, andbeta, the ratio applied to the dates of its events. If the object cannot be stretched,beta = 1and the periodical part is repeated as many times as needed (rounded to an integer ifForceIntegerCycles). Otherwise, it is stretched, up toAlphaMax, and repeated.alphais then recomputed from the actual duration; - with
RescaleMode == DILATION_RATIO, the ratio is kept betweenAlphaMinandAlphaMax; - otherwise, a ratio above 1 is only accepted if
OkExpand, and a ratio below 1 only ifOkCompress. Otherwise α = 1.
If ForceRatio is not negative, it replaces alpha and beta. For an object that is not cyclic, beta is not final: it is set equal to alpha after articulation (note 23) and again by MakeSound().
Note 13 — Limits: SetLimits()
SetLimits() (SetObjectFeatures.c) converts the properties listed in “The prototypes” above into milliseconds for the object starting in each column, from its duration dur = beta × Dur (1000 ms for a note, 0 for an out-time object):
maxcoverbeg,maxcoverend: how much of its beginning or end may be overlapped. Zero if the property is not set,Infposif there is no limit. If both together cover the whole object, both becomeInfpos.maxgapbeg,maxgapend: the largest gap allowed before or after it.Infposif continuity is not required.maxtruncbeg,maxtruncend: how much may be truncated. Zero if truncation is not allowed, and never more thandur.
Simple notes and silences have no limit of overlapping, no continuity requirement, and may be truncated entirely.
Note 14 — Relaxing constraints
Two tables, with one value per column, release part of the constraints for the object starting in that column:
p_coverok[i]: any overlapping between this object and the preceding one(s) is accepted;p_discontinuityok[i]: any gap between them is accepted.
They are set to FALSE at the start of each line, and set to TRUE by note 17 when no solution is found. The setting Ignore constraints in time setting (NoConstraint) sets them all to TRUE from the start. SetConstraintRelease(), at the end of TimeSet.c, sets a whole table.
Until 2026-11-14, these were two global flags, CoverOK and DiscontinuityOK, which applied to the whole line: a single object that could not be placed released the constraints of all objects of its line. Other objects could then overlap their neighbours, although they could have been placed correctly.
Note 15 — Default dates: FixObjectTimings()
FixObjectTimings() (SetObjectFeatures.c) computes the default position of each object, “with its pivot located exactly on its reference streak” (paper §5). For the object starting in column i:
- Sound-object:
t1 = T[i] - pivot, where the pivot isalpha × Dur × PivPos / 100in modePERCENT, orPivPosmilliseconds otherwise. A random shift may then be added (_rndtime(),RandomTime()). WithPlayFromInsertionPoint,t1 = T[i]. Thent2 = t1 + alpha × Dur. A negative pre-roll movest1earlier and a positive post-roll movest2later. - Time pattern:
t1 = T[i],t2 = t1 + alpha × Dur. - Simple note:
t1 = T[i], possibly with a random shift,t2 = t1 + alpha × 1000. The pivot of a note is its beginning. - Silence:
t2 = t1 = T[i], except on the first line in smooth time, where a silence has a durationalpha × Dur. - Out-time object or empty cell:
t1 = t2 = T[i].
These dates go into p_time1[i] and p_time2[i]. They are recomputed each time a line is tried again (label TRY), because T may have changed. With trace_fix, T and time2 are printed before and after.
Note 16 — The constraint solver: Locate()
Locate() (TimeSetFunctions.c) implements the flowchart of paper §5.1 (Fig.5). It scans the sequence from left to right and changes the dates of objects until all constraints are satisfied. The code follows the flowchart closely, with goto labels named after its boxes: START, INCREMENT, CHECK, NEWCHOICE1, NEWCHOICE2, DECREMENT.
Dates. For the object in column i:
tp1[i],tp2[i]: its proposed dates, andts1[i],ts2[i]: its dates in the solution being built. They start attime1andtime2plusddelta0[i].Ts[i]: the largest end date of the objects before columni, as in paper §5.2 (Fig.6).BreakTempoPrev[i]tells whether the object that definesTs[i]allows a tempo break.tscover[i]: the earliest start allowed by the overlapping limits of the objects before it (their end minusmaxcoverend).tsgap[i]: the latest start allowed by the continuity limit of the object before it (its end plusmaxgapend).delta1[i],delta2[i]: the local drift δ(k) of the object, from correction 1 and correction 2.ddelta1[i],ddelta2[i]: the global drift started in this column, andddelta0[i]: the global drift accumulated from the columns before (in striated time only).
Situations. The scan starts at i0, the first column that is not a silence. For each object, shift1 = Ts[i] - tp1[i] (paper §5.2):
shift1 < 0: there is a gap after the previous object (situation 1). The trace says the object “should save” this time.shift1 = 0: it starts exactly when the previous one ends (situation 2). Always acceptable.shift1 > 0: it overlaps the previous object (situations 3 and 4). The trace says it “should spend” this time.
Situation_ok() decides whether the situation is acceptable. In BP3 it uses the limits of note 13 rather than the yes/no conditions of the paper's Fig.7:
- a gap is accepted if continuity is released for this object (
p_discontinuityok[i], note 14), or for a simple note, or if the object starts beforetsgapand the gap does not exceed itsmaxgapbeg; - an overlap is accepted if overlapping is released for this object (
p_coverok[i]), or if the object starts aftertscoverand the overlap does not exceed itsmaxcoverbeg; - the first object of the line is always accepted, and so is a silence in striated time;
- on the first line in smooth time nothing is accepted: every object is moved to the end of the previous one, which is how the first line defines the dates (note 20). On the other lines in smooth time, an overlap is not accepted for an object whose column has no known date, or for a note or a silence.
When the situation is not acceptable, its column and its shift are stored in *p_iconflict and *p_shiftconflict. If Locate() fails, they tell which object could not be placed, and whether it overlapped or was separated from what precedes it (note 17).
An accepted situation leads to INCREMENT: Ts, tscover and tsgap are updated for the next object, and the scan moves on.
Correction 1 (paper §5.3) changes the start of the current object by shift1. Possible_choices() builds the set of solutions sol_set1[i], in this order:
- Shift the object (local drift): if it is relocatable, or if the shift does not exceed
MaxDelay(to the right) orMaxForward(to the left). Notes can always be shifted. An object is never shifted before date 0. - Break tempo (global drift): only to delay (
shift1 > 0), if the object that definesTsallows it (BreakTempoPrev), or always in smooth time. On the first line in smooth time, it is the only solution offered. - Truncate the beginning: only to delay, if the overlap does not exceed
maxtruncbeg. - (Truncate the end, for correction 2 only.)
- Revise the previous objects: always possible after the first object.
Next_choice() takes the first solution of the set and removes it from the set. Solution 0 moves both dates (delta1), solution 1 also moves the streak and all those after it (ddelta1), solution 2 moves only the start date and sets truncbeg. The scan then continues to the next object (INCREMENT).
Correction 2. If the set contains only “revise”, or if “revise” is chosen, the program goes back (DECREMENT) to the previous object and tries to change its end by shift2 = -shift1. Objects whose end does not define Ts are skipped: they are “not the one concerned”. A new set sol_set2 is built for this object: shift it, truncate its end (if -shift2 does not exceed maxtruncend), or revise the objects before it. Revising goes further back only if the object can move by that amount, or if an earlier column allows a tempo break (ibreak); otherwise Locate() gives up.
Alternate correction 1 (paper §5.4). When correction 2 has moved the start of a previous object, its situation with its own predecessor may have become wrong. Alternate_correction1() checks it, taking into account the constraints released for this object. If it overlaps too much, it first tries to truncate its beginning. If the situation is acceptable, it returns 0 and the scan continues forward from this object, re-examining all objects after it. Otherwise it returns the remaining shift: another solution of sol_set2 is tried, or the program goes back one more object.
End of the sequence. When the scan reaches the end marker, Solution_is_accepted() is called. It prints the solution if Time setting display (DisplayTimeSet) is on, and always accepts it. Then the dates ts1 and ts2 are written into starttime and endtime of each instance, and the global drift of each column, ddelta0 + ddelta1 + ddelta2, into p_DELTA. Columns past the end of the line keep the last value. On the first line in smooth time, the end date of the last object is also stored as a global drift, so that the end marker gets a date.
Return values. OK when a solution is found; MISSED when the scan went back to the first object without finding one; ABORT, EXIT, ENDREPEAT or QUICK when InterruptTimeSet() reports an event. InterruptTimeSet() is called every ten steps when there are more than 100 objects. It only does something when improvising with real-time MIDI: it listens to incoming MIDI events, which may ask to stop, to repeat, or to play at once (QUICK).
One solution only. The paper (§5.1) stores sol_set1 and sol_set2 in arrays indexed on i to enable backtracking, and BP2 could search for all solutions and let the user choose one. In the code, this search (FINDMORE, Get_choice(), the stack built by Store()) only runs when AllSolTimeSet is TRUE, and FillPhaseDiagram() always sets it to FALSE. Locate() therefore returns the first solution found, after going back as far as needed.
Note 17 — Releasing constraints
When Locate() returns MISSED, the message We must release time constraint(s) is displayed and the line is tried again (label TRY) with fewer constraints. The constraints are first released for the object on which Locate() failed, the one in column iconflict (note 16):
- overlapping, if it overlapped the preceding object(s) (
shiftconflict > 0), or continuity, if there was a gap (shiftconflict < 0); - if
Locate()fails again on the same object, both.
Each release displays a message such as • Releasing overlapping before “f” in sequence #1, naming the object with NameOfInstance() (at the end of TimeSet.c). The next try may fail on another object of the line, whose constraints are then released in the same way. All other objects keep their constraints. Since each try releases at least one more constraint, the number of tries is limited.
If constraints are already released for this object, or if no object was identified, the constraints are released for the whole line, as was done before 2026-11-14: first overlapping, then both overlapping and continuity. This is needed in smooth time, where some situations are refused whatever the constraints (note 16). If this fails too, SetTimeObjects() returns MISSED and the item is not played (error 37). The paper (§5.4) describes this as an approximate solution obtained by ignoring part or all of the topological properties.
For example, in C4 oscar2-trunc f - oscar2 C4 (with the files of -da.tryMiscObjects), f cannot be placed without overlapping. Releasing overlapping for f only lets oscar2-trunc and oscar2 start after the objects that precede them. When overlapping was released for the whole line, they started at their default positions, overlapping C4 and the silence.
QUICK means that the musician asked, during an improvisation, to play the item at once. Both constraints are released for the whole line, and the line is tried again.
Note 18 — Interactive choices
BP2 asked the user what to do when no solution was found: release continuity, overlapping, or both, decide step by step, or quit. It could also show a solution and ask whether to accept it. This code remains, but it is not active in the console:
- the block after the label
QUEST1is never reached, because it follows agoto EXIT1; - the dialogue after
BACKTRACKcallsGetCap(), which always answers'Q'(quit), so it returnsABORT.Locate()only returnsBACKTRACKduring the search for all solutions (note 16).
For the same reason, the setting Time setting step (StepTimeSet) aborts the time-setting as soon as Next_choice() needs a decision. The interface marks it as “not implemented”.
Note 19 — Global drift in striated time
In striated time, the global drift found by Locate() is added to the structure of time: T[i] += DELTA[i] for every column. After the first column with a drift, a column with no drift takes the value of the previous one, so that all later streaks are delayed together. This is the dotted line of the paper's Fig.4. DELTA is then set back to zero.
If there was any drift (BTflag), the sequences already placed may no longer satisfy their constraints, so the program goes back to STARTAGAIN and processes all lines again from line 0, with the new streaks (note 11). The loop ends when a whole pass creates no global drift. The paper (§6) gives the cost: without global drift, the algorithm takes at most a time proportional to nmax × imax³; with global drifts, nmax² × imax³. In most cases, according to the paper's abstract, it is proportional to nmax × imax.
Note 20 — Interpolating streaks in smooth time
In smooth time, there is no restart. The columns of line nseq that hold an object, a silence or the end marker are marked in p_marked, and their date becomes T[i] + DELTA[i]. Line 0 thus builds the structure of time from the durations of its objects, and the next lines can add dates where they have objects of their own. Dates must not decrease: a date smaller than the previous one, which may happen with small rounding errors, is raised to it. A column of the line that is not marked keeps the date 0; a non-zero date there is an error, reported with trace_timeset and set back to 0.
Columns without a date are never used directly: Calculate_alpha() only uses marked columns to share time among the objects of the next lines (note 12).
Note 21 — Limits of the item
For each line, the last instance (index ≥ 2) gives a candidate for tmax: its end date plus its truncated end, and the first instance a candidate for tmin: its start date minus its truncated beginning. Truncated parts are counted so that the limits include the whole objects. MakeSound() uses tmin and tmax to place the item and to draw the pianoroll.
Note 22 — No legato at the end of a chunk
When the item is a chunk other than the last one (PlayChunks && !LastChunk), when all items are produced (AllItems), or when improvising, the last object of each line must not be played legato: it would extend into the next chunk or item. Its articulation is set to 0 if it is positive. Articulations are stored as signed bytes, so a value above 127 is a negative one.
Step 4 — Articulation
Note 23 — Legato and staccato
Once all lines are placed, the articulation a of each instance (_legato(a) gives a positive percentage, _staccato(a) a negative one, at least -99) changes its duration: endtime = starttime + (endtime - starttime) × (1 + a/100), and alpha is multiplied by the same factor. For an object that is not cyclic, beta becomes equal to alpha.
This is done after the constraints are solved, so that legato and staccato change how long objects sound, not where they are placed. Legato is ignored for an object whose end was truncated, since it was truncated to avoid an overlap.
Note 24 — Out-time objects beyond the end
In striated time, an object starting after max_end_time, the date of the last column, is skipped. This happens with an out-time object at the beginning of an item. If no object with a duration was found before it in the list of instances (outtimeevents), its dates are set to 0, so that it is played at the start. A diagnostic message is kept in the code, disabled, to check this case with -gr.koto3.
Note 25 — Cleaning up
All working tables are freed, whatever the result. They are set to NULL before being allocated (note 10), so that a failed allocation can also come here and free only those that exist. TimeSet() does the same with its own tables, which are freed after the label SORTIR (note 6). If an emergency stop was requested in the meantime, the result is ABORT.
Limitations and oddities
- The tie tables
p_Tie_noteandp_Tie_eventholdcharvalues but are allocated withsizeof(short). This is harmless. - The search for all solutions, the step-by-step mode and the dialogues of note 18 are leftovers of BP2. They could be removed, or made available again through the interface.
Debugging time-setting
Variables at the top of the source files switch on detailed traces in the console's messages:
trace_timeset(TimeSet.c) traces the main steps ofSetTimeObjects(), the datesT[i]and the limits of the item;trace_fix(TimeSet.c) prints the datesT[i]andtime2[i]before and afterFixObjectTimings();trace_object_featuresandcheck_limits(SetObjectFeatures.c) trace the computation of dilation ratios and limits;trace_diagramandtrace_make_silent(FillPhaseDiagram.c) trace the phase diagram and the creation of silent objects;DISPLAY_PHASE_DIAGRAM(TimeSet.c) prints the phase diagram (note 9).
In the settings, Time setting display (DisplayTimeSet) prints every solution found, with the dates, truncations, local and global drifts of each object. Time setting trace (TraceTimeSet) follows the decisions of Locate(): what each object should spend or save, the solution chosen, and the backtracking. Ignore constraints in time setting (NoConstraint) shows the default placement of objects, which helps to tell whether a timing comes from the constraints.
The regression test run-ctests.sh plays every grammar and data project of ctests_git/ and compares the MIDI files and Csound scores produced by two builds of the console. Since every date passes through SetTimeObjects(), it should be run after every change of the code described here.
Note: This page was entirely composed by Claude Opus 5.5 on 13 November 2026.
