A note about input channel state and the communication of which between worker and mixer threads. This logic should also apply to output states, but I'm focussing on the former here.

The state encompasses information about currently loaded resource, like type and file name, but also more general things like the position counter. The position counter is one thing where the difference between mixer and input device is apparent: One counts the recently buffered samples as aleady gone, the other regards them as the future.

The mixer basically wants to know some meta data about the audio it handles; which is what has been delivered by the input worker. While working with this data, the input worker could change just about everything about its instance of the meta data: It could seek around, load a different track, modify eq parameters, etc.

There are separate instances to allow the worker to mess around in the background while the mixer works with its copy of the data (perhaps simply for echoing it to clients).

The method inchannel::sync_state() does update the channel state from the input file (device) state, incorporating the buffer fill for sample offset. Now, where is it called?

1. inchannel::handle_action(): when some notification arrives (big change, like loading new input resource) (mixer thread)
2. inchannel::work_play(): at the very end, after copying the input data. (worker thread)
3. inchannel::statline(): before printing out status info to clients (mixer thread)

Now, I had my doubts about point 2 in this list. Are those justified? First, let's check who's got a mutex here. The real deal in locking is optional in the input_state class.

* inchannel::workstate: has got the inchannel::worklist as mutex (lazy, I know)
* inchannel::state: is clear of any protection

Now, does that seem right? The play action of the worker should not collide with any usage of the inchannel state from the mixer, but that is no law, and that one could be broken in future anyway. In fact, in this instance, using the lock on the workstate from the worker thread is silly, as the worker thread is the only one changing this instance.

So, either I arrange for the sync_state() call being done from the mixer to make sense, or I give locks to both states. The latter seems to be called for, as the worker thread not just updates the state, but also tells clients about position advancement, to off-load that from the mixer.

Heck! I just notice that indeed, this is very wrong: The worker sends the signal that it's done putting data into the buffer and then goes on messing with the channel state and telling clients about positions. At that time, the mixer could alrady be in the game again. The worker is not allowed to dare looking at any general channel state, then. So ... what to do ... I do want to spare the mixer the telltime() mess. That telling could operate on the workstate ... but what about the buffer offset? The buffer belongs to the mixer after posting the semaphore, so one has to store the fill beforehand. No biggie. Then do a telltime, but using the internal state with explicit offset. That should work.

But wait, I realize something else: Do the states properly communicate accelerated playback (speed setting), or generally, resampling business? Input positions before and after funny processing ... well, at least the channel speed should be between them, eh?

I need another look at the different accounts on position. OK, done with that. Continue here.

I realized that I do not need the buffered sample count in telltime(). Now, I also realize that with the general-purpose worker thread, I do not need to call telltime() in the mixer at all. The worker should do the communication! I want to eliminate all non-essential work from the mixer bottleneck. The mixer shall only synchronize commands to the input/output worker threads.

There is a lot of work to remove, still, but pushing the telltime() out is a start. This that one gone from mixer's scope, I do wonder if I can get away with removing that second instance of input_state, or at least the permanent second instance. There is still the nasty fullstat action, which would be nice to offload to the workers ...

One caveat for telltime in worker: That method utlilizes the position info from the state and also the channel state variable (playing/paused/...). The channel state variable belongs to the mixer thread. It's kindof essential ... being used to determine from whom to request data for mixing.

But then, since the mixing itself already happens in the input worker threads ... does the mixer really need to know who's playing? It can just always ask everyone. Could even just be a global semaphore (hm, but that would be tricky for ensuring that each input really does only once cycle). The only thing the mixer needs to know is when everyone's finished. Currently it uses the channel status for that, counting the active ones.

The mixer could just always ask everyone to play and wait for everyone to finish. Those channels that have nothing to do would do nothing, only waking up their worker threads and sending the notification. The downside would be that threads of inactive channels would still be active all the time, but that might also be an upside in giving you a hint that, if that really influences performance, you will for sure experience a breakdown once those channels are active.

Somehow, despite the needless thread waking, I like that approach ... further reducing what the mixer thread does (it didn't do any mixing for quite a long time now;-). Heck, does the mixer actually need to be a middle man for passing actions to the channels?

Damn, it's really a tough question about who should set and watch the channel status (mainly, if it's available for playback). The worker thread might set it, also could check in work_play(), but there is a catch: What if the worker is busy? It's the whole point of this status variable (I'm grouping inchannel::status and inchannel::working together here, basically, it's about inchannel::on_air()) to make the mixer aware of the worker thread not being responsive right now.

So, I don't really see how I could lift the burden from the mixer thread to set and watch over that channel status. This is more far-reaching: This means that I have to route actions that change the channel status through the main mixer thread, always. Unless, of course, there's yet another set of threads for each channel, solely for responding to playback requests (guarantee return signal, but only deliver data depending on the worker thread being idle or not). That seems massively wasteful. I'm not yet in a mindset that considers threads and associated scheduling vanishingly cheap.

OK, then. The mixer controls things and it's going to stay that way for a while. How do I integrate the status variable into the client info the worker thread puts out? It could be communicated explicitly on each work request. How would safe access look? If I put a lock around the variable (re-using the workqueue comes to mind), would that be fine? If I establish that the mixer thread is the only one allowed to change the variable, I can avoid having to lock on that side solely for reading. One would have to lock on changes. The worker thread needs to lock for reading.

Since I want to keep the critical path of normal mixing clean, this does not look so bad. No lock in the way ... the worker accesses the status after mixing its data, so it doesn't matter that much if it has to meddle with the scheduler there.

Oh, I should not forget one tricky issue: Channels get activated by mixer worker threads, via the follower mechanism. I don't find setting of the status while activating ... eh ... how does following work, currenly? Damn, I got a hack there that the worker sets the state to PLAYING once it's playing. This is _wrong_. After all, there's enough locking going on generally to make this write safe by accident ... but I don't want to rely on such.

Sheesh, got to fix that, too. I could use set_status() in the worker, but if I do that, I'd have to use the mutex to access it from the mixer, too. Damn, damn. Heck, come to think of it, I'm accessing other stuff that is no strictly mutex-protected, too. What about the follower itself? Well, as long as changes to this happen before the worker fetches its action, there is a mutex/semaphore in between that protects this access. And, come to think of it, the work_play() method is indeed special in that it can assume to be left alone in accessing the channel object. Mixer does not interfere there, it just waits. So, it's actually safe to access all sorts of things without additional locking! Even setting stuff is fine as the mixer has to pass a semaphore before doing anything drastic (or anything at all).

From beginning to posting the playback semaphore, the worker is free to mess around in the playback routine. I'll make use of that freedom. And document it.

Oh, I just realize that there's also the channel ID ... that one can change, too. It has to be grouped with the status variable. Damn, and when it's about status info, there's also the fullstat stuff, managed from the mixer. There is the channel nickname. Frick. Well, don't make it harder than it is: This is about getting the usage of input_state settled. This includes regular updates to watchers as things happen, most notably, and perhaps for now limited to, the telltime() during playback.

Other status stuff is constructed by the mixer, so changes to various channel meta data are not needed across threads, only in the mixer thread.

Wait a moment: When I establish that no additional locking is necessary between mixer and the worker threads during playback stage ... what about the necessity for a input_state for the worker at all? Could it be that changes to this one are synchronized anyway, de facto? I have to check this. Perhaps it's all fuss about nothing.

The state contains things like the currently loaded file name. This is set by the worker thread at some point in time, then read by the mixer for fullstat. Seems like synchronization is due here. Scanning of length can update state, too. Those actions are why there is asynchronous business at all. And that's why status string production on the mixer side needs locking to ensure to get somewhat consistent info, excluding conflicting writes from worker while reading. Channel work status does not fall under that regime, that's only touched by the worker during playback, while the mixer waits.

So:

1. During playback stage, worker can mess around with itself (and a follower) as it pleases, without locks, until it posts the playback semaphore.
2. Outside inchannel::work_play(), worker is allowed to update the associated input_state, but needs to use locks. Read access is unproblematic. Though, it has to use locks for reading mixer-side channel state like the status variable (as strange as this sounds) or simple stuff like channel ID.
3. Mixer has to grab a lock to read input_state (make itself a temporary copy or whatnot).
4. Mixer can access channel status, but needs to use locking when changing.

Or, in other therms:

Worker uses locks to a) write the input_state b) read channel status, unless in work_play, where everything is allowed (even changing channel state).

Mixer uses locks to a) write channel status b) read input_state.

Worker never writes to channel state unless in work_play.

Mixer never writes to the input_state.

Now, that's dead simple, isn't it?

One periodic check of input_state gives me a sour taste: inchannel::now_and_then. The mixer checks for script actions that are due (via channel::getactions()). Wouldn't it be smart if the channel workers would do that? In parallel? Order of the actions isn't well defined anyway, as long as they're in the same block (new thought alert: What about preserving order of actions from different channels with some finer granularity? perhaps just document differences only making a difference when bigger than mixer buffer).

Well, channel workers pushing their script ... for followers, it's already how it happens, sort of: The leader starts the follower and adds script actions from that one to the mixer action queue. There is an inconsistency: Normally, the script action would be executed before the block in which it is due is decoded. With the follower, the actions are executed after that, on the next round.

If I'd be fine with executing all scripted actions after the block that contains their due time, I could move script extraction into the worker. I really would like that. The mixer would have nothing to do with script execution management. I could define the timing of scripts that way.

This only makes a distinct difference when setting some action right at the beginning of a track ... where you could just execute the actions before starting it.
