I run my own online audio mixing and mastering business, X3 Audio. I publish YouTube shorts on a variety of mixing/mastering topics, and they can be found here.
https://www.youtube.com/playlist?list=PLtqYAHDq4IsKTYDtsfFi3mq2fn1-TVboJ
Meanderings on (but mostly off) topic
I run my own online audio mixing and mastering business, X3 Audio. I publish YouTube shorts on a variety of mixing/mastering topics, and they can be found here.
https://www.youtube.com/playlist?list=PLtqYAHDq4IsKTYDtsfFi3mq2fn1-TVboJ
I upgraded the firmware on an h200 hub, and suddenly both my c420's no longer worked, and the hub did not recognise a known good sd card.
Reset, removed, added, rebooted many times but the cameras remained unusable.
A C425 worked via WiFi and was fine. It is normally a user of the hubs sd storage.
Firmware version is 1.6.1 Build 20251230 Rel 64168
The initial solution was to take the sd card out of the hub, after which the C420 cameras went back online.
After that, I formatted the sd card in the C425, put back in the hub and everything started working again. I can't find any release notes that indicate this might be an issue with the upgrade.
H200 firmware update release notes here: https://www.tp-link.com/us/support/download/tapo-h200/#Firmware-Release-Notes
Another Sepultus Deus release. This album signals a slight shift in intent and styling, with a mellower sound (for the most part).
Fittingly called Nolumus. All tracks remixed, remastered, some extended, some cut and a few additional parts recorded. Released on 20th January 2025.
It can be called a 'compilation' I suppose. I like that a possible translation of Nolumus is "we do not wish it" or "we do not want it". Twelve tracks selected from two albums and an EP.
As the liner notes portend, the first and final EP release by Sepultus Deus (IOW, me for the most part).
I mastered all tracks with Ozone 11 advanced, and reckon I have done quite a reasonable job of it. Most of the drum tracks were programmed with a step sequencer, which was a good and mostly positive experience.
All in all, a release I am happy with.
I recently read an excellently written article in Psyche, How not to fear your death, by Sam Dresser, an editor at Aeon/Psyche. It summarises Epicurean views on death, which are thought provoking if not always agreeable.
Additionally, the article includes some excellent links on alternate/competing philosophies, and a link to Shelly Kagan's (Yale) free philosophy course, succintly entitled "Death" 😄
Well worth a look if you wish to contemplate death with some useful (if somewhat abstract) references to guide your thought.
We had some 'classic' wallpaper in our lounge, which we could not wait to remove. The lounge itself is quite large, with high ceilings, so a block colour would be quite dull.
We decided to limewash instead, going for that rustic, time worn look. This post describes how we achieved the affect. I'm documenting it here in case someone may find it useful -- we found contradictory or confusing advice on the Resene site, so I've tried to keep this very plain.
Initial state
We had already plastered walls, and had done some light repairs and skimming. This was then sanded and washed down with sugar soap.
Materials
I estimate that we covered about 45 square metres with the measurements we give here. Also note that the colours noted are our choices 😀
I have a business site (https://x3audio.com) that features a contact form.
I'm using AWS Cloudfront to deliver the site from an S3 bucket and wanted to include a contact form. To help me do this, I decided to use a lambda function, exposed as a function URL.
This function URL can be called by a Javascript integration in the web page when a user completes and submits a very simple contact form. Once the recaptcha token has been 'scored', the lambda uses SES to send me an email.
In the era of bots, spam engines and the like, I can't just naively expose the URL and 'hope' everything will be alright. Two security measures have been employed:
<button class="w-100 btn btn-lg btn-primary g-recaptcha"
data-sitekey="YOUR SITE KEY"
data-callback='onSubmit'
data-action='submit'
type="submit"
id="contactformbutton">
Send
</button>
async function onSubmit(token) {
const cfr = new Request("https://x3audio.com/contact?mx=" + mx
+ "&ma=" + ma + "&rem=" + rem +
"&e=" + email);
cfr.method = "GET";
cfr.headers.append('x-v3token', token);
cfr.headers.append('x-v3token-length', token.length);
try {
const response = await fetch(cfr);
/*global fetch*/
export const handler = async (event) => {
const obj = await assess(event);
const response = {
statusCode: 200
};
return response;
};
async function assess(event) {
let obj = {
recaptcha_score: -1,
recaptcha_error_codes: [],
is_bot: true,
party: event["rawQueryString"],
source_ip: event.headers["x-forwarded-for"],
rc_v3_token: event.headers["x-v3token"],
};
try {
const rc_result = await checkToken(obj.rc_v3_token, obj.source_ip);
obj.recaptcha_score = rc_result.score;
obj.recaptcha_error_codes = rc_result.error_codes;
obj.is_bot = obj.recaptcha_score < 0.7;
}
catch (ex) {
console.log('Late exception: ' + ex, ex.stack);
}
return obj;
}
async function checkToken(token, ip) {
let score = -1;
let error_codes = [];
try {
const url = 'https://www.google.com/recaptcha/api/siteverify?secret=YOUR-SECRET-KEY&response=' + token;
let response = await fetch(url, { method: 'POST' });
const json = await response.json();
score= json.success ? json.score : -1;
error_codes = json.success ? [] : json["error-codes"];
}
catch (ex) {
console.log('Failed to check token: ' + ex, ex.stack);
error_codes = [ ex.toString() ];
}
return { score: score, error_codes: error_codes };
}
{
success: true,
challenge_ts: '2024-09-17T20:22:45Z',
hostname: 'x3audio.com',
score: 0.9,
action: 'submit'
}
I use the Cakewalk DAW (Digital Audio Workstation) for my musical "experiments". For one composition recently, I wanted to have a "cheesy" DJ intro and a "You've been listening to..." outro. Both of these voice parts would be over the playing song, so I needed to the track itself to "quiet down" when a voice part was active.
I thought about using volume automation, but that's clumsy and the song itself has quite a few tracks. So it became obvious that using side chain compression was going to be the best approach.
So I've got n tracks that should lower in volume (get compressed) when a voice track is active. It's actually easy to do in Cakewalk once you know how!
First, create a new stereo bus with a compressor in the fx bin (I used the standard Sonitus compressor), and send it out to the master bus. I called it sidechain (perhaps because I lack imagination 😄).
Next, ensure that all tracks and other buses that you want to be compressed are routed through the new sidechain bus. Here's an image of bus to bus, the "Rhythm" and "Bass" buses routed to "sidechain":I have been using JUCE for a while now, and have built a few plugins, including a "multi faceted" one (this post) and a DSP impulse response processing one, which is not yet released.
Rock Dog I did have as the subject of a Kickstarter campaign, which unfortunately failed to raise the funds I wanted to help improve it further -- I needed some financial injection to allow me to start modelling physical (non linear response) hardware (still open to funding of course!).
The original Kickstarter campaign video is here.
With Rock Dog I tried to make it a more interesting plugin, by including features I didn't see often discussed in JUCE forums, including:
The only thing I have not managed to do yet is build a MacOS version - when I get around to uploading the project into my public github repo then I'll have a crack at a github action for that. There is a Windows version available.
And to finish, some screenshots.
Standard theme plugin:
And with a different theme activated:
void AtanMiniProcessor::processBlock(juce::AudioBuffer<float>& buffer, int inputChannels, int outputChannels, ProcessParameters& p) {
ScopedNoDenormals noDenormals;
for (auto i = inputChannels; i < outputChannels; ++i)
buffer.clear(i, 0, buffer.getNumSamples());
for (int channel = 0; channel < inputChannels; ++channel) {
auto* channelData = buffer.getWritePointer(channel);
for (int sample = 0; sample < buffer.getNumSamples(); sample++) {
float cleanSig = *channelData;
*channelData *= p.drive * p.range;
*channelData = (((((2.0f / float_Pi) * atan(*channelData)) * p.blend) + (cleanSig * (1.0f - p.blend))) / 2) * p.volume;
channelData++;
}
}
}
I suppose it's not even random, just contextual.
We have some raised garden beds, notionally described as chattels GB-1 and GB-2. GB-2 has been built to a less than desirable standard, with the original builder not bothering to line the interior with polythene or other suitable barrier material to avoid internal "bed rot".
As some of the panels were rotted through and become friable, I had occasion to break out some tools, old fence palings and patchwork fix the bed, as shown in the picture. It's not a stunningly professional job, but it does the trick and in reality the vegetables aren't going to care.
And, as I sawed and hammered away, the philosophical condundrum that is The Ship Of Theseus sprang to mind. Has GB-2 retained its identity?
In terms of chattels, utility, occupied dimensions, GB-2 is in all respects unchanged at the macro level. However, it looks different, weighs slightly more, has more nails, screws, so therefore is technically a different object.
Has GB-2 retained its identity? Yes, with four dimensional theory applied. But in all senses practical, not really. I find much of the issue to be confounded by the application of frames of reference that are by their very nature incomplete, unsuitable or so broadly swept as to obscure rather than illuminate. Once we lapse into the truly metaphysical, I fear poor GB-2 may be consigned almost to a non existence.
Even every day chores may bring philosophical wonder. If Bertrand Russell could obsess over the place of a table in philosophy, nature, mind and others, then GB-2 has as much right to be considered.
Having a look at the Blazorise github source, I encountered this method:
protected override string FormatValueAsString( IReadOnlyList<TValue> value )
{
if ( value == null || value.Count == 0 )
return string.Empty;
if ( Multiple )
{
return string.Empty;
}
else
{
if ( value[0] == null )
return string.Empty;
return value[0].ToString();
}
}
Doesn't that seem like a lot of code for some simple behaviour ? I've seen development styles like this before, often with the argument that it's easy to read. I suppose that is true. protected override string FormatValueAsString(IReadOnlyList<TValue> values) {
var result = values?.FirstOrDefault()?.ToString();
return result == null || Multiple ? string.Empty : result;
}
It's shorter, loses no meaning, uses LINQ acceptably and has one return statement instead of four. I also took the liberty of removing the strange spacing around the method argument and naming it more appropriately. And using the K&R statement styling. protected override string FormatValueAsString(IReadOnlyList<TValue> values)
=> Multiple ? string.Empty : values?.FirstOrDefault()?.ToString() ?? string.Empty;
I've got a well set up Fender Jazz Bass (US); nice low action, totally true neck - in short, a veritable slap and pop monster. But I always thought the stock Fender bridges were a bit cheap and flimsy looking, and not fit for the high quality instruments that we know Fenders are.
So I recently had a Badass Bass II bridge fitted, by Weta Guitars. And what did I expect? Many pundits suggest longer sustain, "efficient sound coupling" (whatever that is supposed to be) and improved balance. It's a drop in replacement, so I suppose you could fit one yourself, but I didn't want to take the risk.
About a year in, I must admit I'm not noticing a huge difference. You feel the additional heft at the bridge end of the bass of course, but only for a bit as you acclimatise. Where it does work for me is appearance - it just looks the part. Perhaps this is a case of form over function.
<div class="row">
<div class="col-12">
<textarea cols="80" rows="10"
[(ngModel)]="sourceCode" style="min-width: 100%;"
name="sourceCode" required [disabled]="running">
</textarea>
</div>
</div>
<p></p>
<div class="row">
<div class="col-12">
<button type="submit" class="btn btn-primary"
[disabled]="!executionForm.form.valid || running">
Run
</button>
<button type="button" class="btn btn-danger"
(click)="cancel()" [disabled]="!running">
Cancel
</button>
</div>
</div>
<div class="row">
<div class="col-12">
<textarea cols="80" rows="10" bind="@SourceCode" style="min-width: 100%;"
name="sourceCode" required
onkeyup="this.dispatchEvent(new Event('change', { 'bubbles': true }));">
</textarea>
</div>
</div>
<p></p>
<div class="row">
<div class="col-12">
<button type="submit" class="btn btn-primary" onclick="@Run"
disabled='@(NotRunnable || Running)'>
Run
</button>
<button type="button" class="btn btn-danger"
disabled="@(Running == false)" onclick="@StopExecution">
Cancel
</button>
</div>
</div>
supportedLanguages() {
return this
._http
.get(this.formUrl(false))
.pipe(map((data: any[]) => {
return <LanguageDescription[]>data
}));
}
protected override async Task OnInitAsync() {
LanguageMetadata.All =
await httpClient.GetJsonAsync<List<LanguageMetadata>>
("http://localhost:55444/api/EsotericLanguage/SupportedLanguages");
}
window.websocketInterop = {
socket: null,
connect: function (url, helper, msg) {
console.log("Connecting");
socket = new WebSocket(url);
socket.onopen = function (evt) {
msg && socket.send(msg);
}
socket.onmessage = function (event) {
console.debug("WebSocket message received:", event);
helper.invokeMethod("OnMessage", event.data);
};
socket.onclose = function (evt) {
console.log("Socket closed. Notify this..");
helper.invokeMethod("OnChannelClose");
}
console.log("Connected and ready....");
},
send: function (msg) {
console.log("Sending:" + msg);
socket.send(msg);
},
close: function () {
console.log("Closing socket on demand");
socket && socket.close();
}
};
async Task Run() {
Output = string.Empty;
Running = true;
await JSRuntime.Current.InvokeAsync<object>
("websocketInterop.connect",
InterpreterServiceUrl,
new DotNetObjectRef(this),
$"|{Language}|{SourceCode}");
StateHasChanged();
}
helper.invokeMethod("OnChannelClose");
which is defined in the cshtml file as:
[JSInvokable]
public void OnChannelClose() {
Running = false;
StateHasChanged();
}
| Attribute | Semantics |
|---|---|
| ApiExclusion | Exclude a DbSet<> from the API |
| ApiResourceCollection | Supply a specific name to a ResourceCollection |
| ApiExposedResourceProperty | Expose a specific entity property as a resource property |
| ApiNullifyOnCreate | Request that property be nullified when the enclosing object is being created |
public class CompanyContext : DbContext, ICompanyContext {
public CompanyContext(DbContextOptions<CompanyContext> options) : base(options) {
}
public DbSet<Product> Products { get; set; }
[ApiExclusion]
public DbSet<Campaign> Campaigns { get; set; }
public DbSet<Supplier> Suppliers { get; set; }
[ApiResourceCollection(Name = "Clients")]
public DbSet<Customer> Customers { get; set; }
public DbSet<Order> Orders { get; set; }
[ApiExclusion]
public DbSet<OrderLine> OrderLines { get; set; }
}
public class Customer {
public Customer() {
Orders = new List<Order>();
}
public int CustomerId { get; set; }
[ApiExposedResourceProperty]
[MaxLength(128)]
public string Name { get; set; }
[ApiNullifyOnCreate]
[ApiExposedResourceProperty]
public virtual ICollection<Order> Orders { get; set; }
}
[EnableQuery(Order = (int)AllowedQueryOptions.All)]
[ODataRoute("Clients")]
public class ClientsController : BaseController<ICompanyContext, EF.Example.Customer, System.Int32, IBaseRepository<ICompanyContext, EF.Example.Customer, System.Int32>> {
public ClientsController(IBaseRepository<ICompanyContext, EF.Example.Customer, System.Int32> repo) : base(repo) {
}
[HttpGet("({key})/Name")]
public async Task<IActionResult> GetName(System.Int32 key) {
var entity = await Repository.FindAsync(key);
return entity == null ? (IActionResult)NotFound() : new ObjectResult(entity.Name);
}
[HttpGet("({key})/Orders")]
public async Task<IActionResult> GetOrders(System.Int32 key) {
var entity = await Repository.FindAsync(key, "Orders");
return entity == null ? (IActionResult)NotFound() : new ObjectResult(entity.Orders);
}
}
public partial class ClientsRepository : BaseRepository<ICompanyContext, EF.Example.Customer, System.Int32>, IBaseRepository<ICompanyContext, EF.Example.Customer, System.Int32> {
public ClientsRepository(ICompanyContext ctx, IProxy<ICompanyContext, EF.Example.Customer> proxy = null) : base(ctx, proxy) {
}
protected override async Task<EF.Example.Customer> GetAsync(IQueryable<EF.Example.Customer> query, System.Int32 key) {
return await query.FirstOrDefaultAsync(obj => obj.CustomerId == key);
}
protected override DbSet<EF.Example.Customer> Set { get { return Context.Customers; } }
public override System.Int32 GetKeyFromEntity(EF.Example.Customer e) {
return e.CustomerId;
}
}
private string GenerateMessage<TObject>(string doc, TObject ctx) {
ParseResult res = Parser.Parse(doc);
EvaluationContext ec = EvaluationContext.From(ctx);
var ctx = res.Execute(ExecutionContext.Build(ec));
return ctx.context.ToString();
}
1: tag=Blogs
2: singular-tag=Blog
3: model=Blog
4: # API usage
5: restResourceIdProperty=BlogId
6: restResourceIdPropertyType=int
7: #
8: baseQuery=
9: (await ctx.Blogs
10: .AsNoTracking()
11: .Where(expr)
12: .Select(b => new { Blog = b })
13: {joins}
14: {extraWhere}
15: .OrderBy(a => a.Blog.BlogId)
16: .Skip(skip)
17: .Take(top)
18: .ToListAsync())
19: #
20: expansion=Posts
21: IEnumerable<Post>
22: .GroupJoin(ctx.Posts, a => a.Blog.NonKeyField, post => post.NonKeyField, {selector})
23: #
24: expansion=Readers
25: IEnumerable<Party>
26: .GroupJoin(ctx.Parties.Where(r => r.Disposition == "reader"),
27: a => a.Blog.NonKeyField, party => party.NonKeyField, {selector})
28: #
29: expansion=Author
30: Party
31: .Join(ctx.Parties, a => a.Blog.NonKeyField, party => party.NonKeyField, {selector})
32: .Where(p => p.Author.Disposition == "author")
1: public partial class BlogsQueryHandler : BaseQueryHandling {
2:
3: protected override string TagName { get; } = "Blogs";
4:
5: public const string ExpandPosts = "Posts";
6: public const string ExpandAuthor = "Author";
7: public const string ExpandReaders = "Readers";
8:
9: public override IEnumerable<string> SupportedExpansions
10: { get; } = new [] { "Posts", "Author", "Readers"};
11:
1: private async Task<IEnumerable<CompositeBlog>>
2: Get_Blogs_Author(
3: BloggingContext ctx,
4: Expression<Func<Blog, bool>> expr,
5: int top,
6: int skip) {
7: return
8: (await ctx.Blogs
9: .AsNoTracking()
10: .Where(expr)
11: .Select(obj => new { Blog = obj })
12: .Join(ctx.Parties,
13: a => a.Blog.NonKeyField,
14: party => party.NonKeyField,
15: (a, author) => new { a.Blog, Author = author})
16: .Where(p => p.Author.Disposition == "author")
17: .OrderBy(a => a.Blog.BlogId)
18: .Skip(skip)
19: .Take(top)
20: .ToListAsync())
21: .Select(a => CompositeBlog.Accept(a.Blog, author: a.Author));
22: }
1: public async Task<IEnumerable<CompositeBlog>>
2: GetBlogsWithExpansion(
3: BloggingContext ctx,
4: Expression<Func<Blog, bool>> expr = null,
5: int top = 10,
6: int skip = 0,
7: IEnumerable<string> expansions = null) {
8: return await GetMultipleObjectsWithExpansion<CompositeBlog, Blog>
9: (ctx, expr, expansions, top, skip);
10: }
11:
12:
1: using (BloggingContext ctx = new BloggingContext()) {
2: var handler = new BlogsQueryHandler();
3: var result = await handler.GetBlogsWithExpansion(
4: ctx,
5: b => b.BlogId > 100,
6: 10,
7: 10,
8: BlogsQueryHandler.ExpandAuthor,
9: BlogsQueryHandler.ExpandReaders);
10: // .. Do something with the result
11: }
// ****** Options for generation ******
// Namespaces to include (for EF model and so on)
var includeNamespaces = new List<string> { "EF.Model" };
// The type of the EF context
var contextType = "BloggingContext";
// Base namespace for all generated objects
var baseNamespace = "Complex.Omnibus.Autogenerated.ExpansionHandling";
// The DSL instance file extension of interest (txt or json)
var srcFormat = "json";
// True i the advanced form of a DSL instance template should be used
var useAdvancedFormDSL = true;
// Form the dsl instance file name to use
var dslFile = "dsl-instance" + (useAdvancedFormDSL ? "-advanced" : string.Empty) + ".";
// Default top if none supplied
var defaultTop = 10;
// Default skip if none supplied
var defaultSkip = 0;
// True if the expansions passed in shold be checked
var checkExpansions = true;
// If true, then expansions should be title cased e.g. posts should be Posts, readers should be Readers and so on
var expansionsAreTitleCased = true;
// ****** Options for generation ******