Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Core/Resgrid.Config/TtsConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public static class TtsConfig
public static string PiperModelDirectory = "/usr/local/share/piper-voices";
public static string FfmpegExecutable = "ffmpeg";
public static string TempDirectory = "";
public static string CachePrefix = "tts";
public static string CachePrefix = "tts2";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kody code-review Bug high

WHAT: The k8s deployment.yaml (line 28) sets RESGRID__TtsConfig__CachePrefix: tts, which overrides the new code default tts2 via the env-var → TtsConfig → TtsOptions chain in ServiceCollectionExtensions.cs:60, defeating the cache-busting intent in production. WHY: Non-English voice model names (Spanish, German, French, etc.) did not change in this PR, so their SHA256 cache hashes are identical to before; the prefix change from ttstts2 was the only mechanism to invalidate their stale cache entries, but the env var override keeps it at tts, causing non-English voices to serve old audio generated with sentence-silence 0.0, lowpass 3000, and no loudnorm/noise parameters indefinitely. HOW: Update the k8s deployment.yaml RESGRID__TtsConfig__CachePrefix value from tts to tts2 to match the code default, or remove the env var override so the code default takes effect.

public static string CachePrefix = "tts2";
// Also update Web/Resgrid.Web.Tts/k8s/deployment.yaml line 28:
//   RESGRID__TtsConfig__CachePrefix: tts2
Prompt for LLM

File Core/Resgrid.Config/TtsConfig.cs:

Line 35:

WHAT: The k8s deployment.yaml (line 28) sets `RESGRID__TtsConfig__CachePrefix: tts`, which overrides the new code default `tts2` via the env-var → TtsConfig → TtsOptions chain in ServiceCollectionExtensions.cs:60, defeating the cache-busting intent in production. WHY: Non-English voice model names (Spanish, German, French, etc.) did not change in this PR, so their SHA256 cache hashes are identical to before; the prefix change from `tts` → `tts2` was the only mechanism to invalidate their stale cache entries, but the env var override keeps it at `tts`, causing non-English voices to serve old audio generated with sentence-silence 0.0, lowpass 3000, and no loudnorm/noise parameters indefinitely. HOW: Update the k8s deployment.yaml `RESGRID__TtsConfig__CachePrefix` value from `tts` to `tts2` to match the code default, or remove the env var override so the code default takes effect.

Suggested Code:

public static string CachePrefix = "tts2";
// Also update Web/Resgrid.Web.Tts/k8s/deployment.yaml line 28:
//   RESGRID__TtsConfig__CachePrefix: tts2

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

public static int NormalizedSampleRate = 8000;
public static int NormalizedChannels = 1;
public static bool WarmupEnabled = true;
Expand Down Expand Up @@ -73,8 +73,8 @@ public static class TtsConfig
"Thank you. Your response has been recorded."
});

public static int RateLimitPermitLimit = 60;
public static int RateLimitQueueLimit = 10;
public static int RateLimitPermitLimit = 600;
public static int RateLimitQueueLimit = 60;
Comment on lines +76 to +77

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kody code-review Bug high

Environment variable overrides in Web/Resgrid.Web.Tts/k8s/deployment.yaml silently negate the rate-limit increase, as ConfigProcessor.LoadAndProcessEnvVariables (Program.cs:17) loads these values into static TtsConfig fields that ApplyRateLimitOptions (ServiceCollectionExtensions.cs:77-78) passes to FixedWindowRateLimiter at Program.cs:86-87, causing futile retry-on-429 attempts. Update the RESGRID__TtsConfig__RateLimitPermitLimit and RESGRID__TtsConfig__RateLimitQueueLimit overrides at lines 35-36 to match the intended 600/60 code defaults.

// Code defaults are correct; but deployment.yaml must be updated to match,
// otherwise the env override keeps production at 60/10:
//   RESGRID__TtsConfig__RateLimitPermitLimit: "600"
//   RESGRID__TtsConfig__RateLimitQueueLimit: "60"
Prompt for LLM

File Core/Resgrid.Config/TtsConfig.cs:

Line 76 to 77:

WHAT: The rate-limit raise to 600 permits / 60 queue is silently overridden in the production k8s deployment, so it never takes effect. WHY: deployment.yaml still sets RESGRID__TtsConfig__RateLimitPermitLimit:"60" and RateLimitQueueLimit:"10"; ConfigProcessor.LoadAndProcessEnvVariables (Program.cs:17) loads those into the static TtsConfig fields, which ApplyRateLimitOptions (ServiceCollectionExtensions.cs:77-78) copies into RateLimitOptions, which Program.cs:86-87 feeds to the FixedWindowRateLimiter — env vars take precedence over the code defaults, so production still throttles at 60/10. Combined with the new retry-on-429 logic, retries within the same 60s fixed window are largely futile (permits don't free until window reset), so the dispatch fan-out failures the PR targets can still occur. HOW: update Web/Resgrid.Web.Tts/k8s/deployment.yaml lines 35-36 to RESGRID__TtsConfig__RateLimitPermitLimit:"600" and RESGRID__TtsConfig__RateLimitQueueLimit:"60" (or remove those overrides so the code defaults apply).

Suggested Code:

// Code defaults are correct; but deployment.yaml must be updated to match,
// otherwise the env override keeps production at 60/10:
//   RESGRID__TtsConfig__RateLimitPermitLimit: "600"
//   RESGRID__TtsConfig__RateLimitQueueLimit: "60"

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

public static int RateLimitWindowSeconds = 60;
}
}
3 changes: 3 additions & 0 deletions Core/Resgrid.Services/DepartmentSettingsService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,9 @@ public async Task<bool> IsTestingEnabledForDepartmentAsync(int departmentId)

public async Task<Coordinates> GetMapCenterCoordinatesAsync(Department department)
{
if (department == null)
return new Coordinates() { Latitude = 39.14086268299356, Longitude = -119.7583809782715 };
Comment on lines +307 to +308

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kody code-review Kody Rules low

Magic number violation identified at line 308 and lines 371-372, where the default map center coordinates 39.14086268299356 and -119.7583809782715 are hardcoded as raw literals. Extract these fallback values into static readonly fields, following the existing cache key pattern at lines 17-25 to eliminate duplication across PersonnelController, UnitsController, MappingController v4, and LeafletMapView.tsx.

Kody rule violation: Replace magic numbers with named constants

private static readonly double DefaultLatitude = 39.14086268299356;
private static readonly double DefaultLongitude = -119.7583809782715;

// ...

if (department == null)
    return new Coordinates() { Latitude = DefaultLatitude, Longitude = DefaultLongitude };
Prompt for LLM

File Core/Resgrid.Services/DepartmentSettingsService.cs:

Line 307 to 308:

Violates rule 'Replace magic numbers with named constants': the default map center coordinates 39.14086268299356 and -119.7583809782715 are hardcoded as raw literals at line 308 and again at lines 371-372 within the same method. These are meaningful fallback values (default location when no department coordinates exist) that also appear identically in PersonnelController, UnitsController, and MappingController v4. They should be extracted to named static readonly constants, following the existing pattern in this class (e.g., lines 17-25 define static string constants for cache keys).

Suggested Code:

private static readonly double DefaultLatitude = 39.14086268299356;
private static readonly double DefaultLongitude = -119.7583809782715;

// ...

if (department == null)
    return new Coordinates() { Latitude = DefaultLatitude, Longitude = DefaultLongitude };

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.


var address = await GetBigBoardCenterAddressDepartmentAsync(department.DepartmentId);
var gpsCoordinates = await GetBigBoardCenterGpsCoordinatesDepartmentAsync(department.DepartmentId);

Expand Down
52 changes: 50 additions & 2 deletions Core/Resgrid.Services/TtsAudioService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ namespace Resgrid.Services
public class TtsAudioService : ITtsAudioService
{
private const string AdminKeyHeaderName = "X-Resgrid-Admin-Key";
// Retries are bounded (worst case ~1.2s of delay) so Twilio voice webhooks that
// generate audio inline still respond well within Twilio's timeout.
private const int MaxTransientRetries = 2;
private const int BaseRetryDelayMilliseconds = 300;
private readonly Func<RestClient> _restClientFactory;

public TtsAudioService(Func<RestClient> restClientFactory)
Expand All @@ -39,7 +43,9 @@ public async Task<Uri> GenerateSpeechUrlAsync(string text, string voice = null,
Speed = speed ?? TtsConfig.DefaultSpeed
});

var response = await restClient.ExecuteAsync<GenerateSpeechResponse>(request, cancellationToken);
var response = await ExecuteWithTransientRetryAsync(
() => restClient.ExecuteAsync<GenerateSpeechResponse>(request, cancellationToken),
cancellationToken);

if (!response.IsSuccessful || response.Data == null || string.IsNullOrWhiteSpace(response.Data.Url))
throw CreateRequestFailure("generate speech audio", response);
Expand Down Expand Up @@ -74,12 +80,54 @@ public async Task RegenerateStaticPromptsAsync(IEnumerable<string> prompts, Canc
Prompts = promptRequests
});

var response = await restClient.ExecuteAsync(request, cancellationToken);
var response = await ExecuteWithTransientRetryAsync(
() => restClient.ExecuteAsync(request, cancellationToken),
cancellationToken);

if (!response.IsSuccessful)
throw CreateRequestFailure("regenerate static prompts", response);
}

private static async Task<TResponse> ExecuteWithTransientRetryAsync<TResponse>(
Func<Task<TResponse>> sendAsync,
CancellationToken cancellationToken) where TResponse : RestResponse
{
TResponse response = null;

for (var attempt = 0; attempt <= MaxTransientRetries; attempt++)
{
if (attempt > 0)
{
// 300ms then 900ms, with ±25% jitter so retries from a dispatch
// fan-out don't land on the rate-limit window edge in lockstep.
var delayMilliseconds = BaseRetryDelayMilliseconds * Math.Pow(3, attempt - 1)
* (0.75 + (Random.Shared.NextDouble() * 0.5));
await Task.Delay(TimeSpan.FromMilliseconds(delayMilliseconds), cancellationToken);
}

response = await sendAsync();

if (!IsTransientFailure(response))
return response;
}

return response;
}

private static bool IsTransientFailure(RestResponse response)
{
if (response.IsSuccessful)
return false;

if (response.ErrorException is OperationCanceledException or TaskCanceledException)
return false;

// 429 (rate limited), any 5xx, or no response at all (network failure).
return response.StatusCode == HttpStatusCode.TooManyRequests
|| (int)response.StatusCode >= 500
|| response.StatusCode == 0;
}

private static Exception CreateRequestFailure(string operation, RestResponse response)
{
if (response.ErrorException is OperationCanceledException or TaskCanceledException)
Expand Down
42 changes: 27 additions & 15 deletions Tests/Resgrid.Tests/Web/Tts/TtsServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@ public async Task generate_async_should_return_cached_response_without_generatin

_audioProcessingService
.Setup(x => x.GetEffectiveSynthesisProfile("en-us+klatt4", 165))
.Returns(("en_US-norman-medium.onnx", 165));
.Returns(("en_US-ryan-high.onnx", 165));
_cacheService
.Setup(x => x.CreateCacheKey("Press 1 for yes", "en_US-norman-medium.onnx", 165))
.Setup(x => x.CreateCacheKey("Press 1 for yes", "en_US-ryan-high.onnx", 165))
.Returns(CacheKey);
_cacheService
.Setup(x => x.TryGetCachedUrlAsync(CacheKey, It.IsAny<CancellationToken>()))
Expand Down Expand Up @@ -84,9 +84,9 @@ public async Task generate_async_should_generate_and_store_audio_when_cache_miss

_audioProcessingService
.Setup(x => x.GetEffectiveSynthesisProfile("en-us+klatt4", 165))
.Returns(("en_US-norman-medium.onnx", 165));
.Returns(("en_US-ryan-high.onnx", 165));
_cacheService
.Setup(x => x.CreateCacheKey("Press 1 for yes", "en_US-norman-medium.onnx", 165))
.Setup(x => x.CreateCacheKey("Press 1 for yes", "en_US-ryan-high.onnx", 165))
.Returns(CacheKey);
_cacheService
.SetupSequence(x => x.TryGetCachedUrlAsync(CacheKey, It.IsAny<CancellationToken>()))
Expand Down Expand Up @@ -150,9 +150,9 @@ public async Task generate_async_should_replace_legacy_default_voices_with_confi

_audioProcessingService
.Setup(x => x.GetEffectiveSynthesisProfile("en-us+klatt4", 165))
.Returns(("en_US-norman-medium.onnx", 165));
.Returns(("en_US-ryan-high.onnx", 165));
_cacheService
.Setup(x => x.CreateCacheKey("Press 1 for yes", "en_US-norman-medium.onnx", 165))
.Setup(x => x.CreateCacheKey("Press 1 for yes", "en_US-ryan-high.onnx", 165))
.Returns(cacheKey);
_cacheService
.Setup(x => x.TryGetCachedUrlAsync(cacheKey, It.IsAny<CancellationToken>()))
Expand Down Expand Up @@ -189,9 +189,9 @@ public async Task generate_async_should_deduplicate_concurrent_generation_for_th

_audioProcessingService
.Setup(x => x.GetEffectiveSynthesisProfile("en-us+klatt4", 165))
.Returns(("en_US-norman-medium.onnx", 165));
.Returns(("en_US-ryan-high.onnx", 165));
_cacheService
.Setup(x => x.CreateCacheKey("Press 1 for yes", "en_US-norman-medium.onnx", 165))
.Setup(x => x.CreateCacheKey("Press 1 for yes", "en_US-ryan-high.onnx", 165))
.Returns(CacheKey);
_cacheService
.Setup(x => x.TryGetCachedUrlAsync(CacheKey, It.IsAny<CancellationToken>()))
Expand Down Expand Up @@ -249,13 +249,17 @@ public void create_piper_start_info_should_use_english_model_for_english_voices(
startInfo.FileName.Should().Be("piper");
startInfo.ArgumentList.Should().Equal(
"--model",
Path.Combine("/usr/local/share/piper-voices", "en_US-norman-medium.onnx"),
Path.Combine("/usr/local/share/piper-voices", "en_US-ryan-high.onnx"),
"--output_file",
"/tmp/raw.wav",
"--length-scale",
"1.06",
"--sentence-silence",
"0.0");
"0.35",
"--noise-scale",
"0.333",
"--noise-w",
"0.4");
}

[Test]
Expand All @@ -270,13 +274,17 @@ public void create_piper_start_info_should_fallback_to_default_model_for_unmappe
startInfo.FileName.Should().Be("piper");
startInfo.ArgumentList.Should().Equal(
"--model",
Path.Combine("/usr/local/share/piper-voices", "en_US-norman-medium.onnx"),
Path.Combine("/usr/local/share/piper-voices", "en_US-ryan-high.onnx"),
"--output_file",
"/tmp/raw.wav",
"--length-scale",
"1.06",
"--sentence-silence",
"0.0");
"0.35",
"--noise-scale",
"0.333",
"--noise-w",
"0.4");
}

[Test]
Expand All @@ -290,13 +298,17 @@ public void create_piper_start_info_should_adjust_length_scale_for_speed()
startInfo.FileName.Should().Be("piper");
startInfo.ArgumentList.Should().Equal(
"--model",
Path.Combine("/usr/local/share/piper-voices", "en_US-norman-medium.onnx"),
Path.Combine("/usr/local/share/piper-voices", "en_US-ryan-high.onnx"),
"--output_file",
"/tmp/raw.wav",
"--length-scale",
"0.50",
"--sentence-silence",
"0.0");
"0.35",
"--noise-scale",
"0.333",
"--noise-w",
"0.4");
}

[Test]
Expand All @@ -321,7 +333,7 @@ public void create_ffmpeg_start_info_should_apply_the_requested_telephone_filter
"-acodec",
"pcm_mulaw",
"-af",
"highpass=f=200, lowpass=f=3000, anequalizer=c0 f=2500 w=1000 g=3 t=1",
"highpass=f=200, lowpass=f=3400, anequalizer=c0 f=2500 w=1000 g=3 t=1, loudnorm=I=-16:TP=-1.5:LRA=11",
"/tmp/normalized.wav");
}

Expand Down
4 changes: 2 additions & 2 deletions Web/Resgrid.Web.Tts/Configuration/RateLimitOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ namespace Resgrid.Web.Tts.Configuration
public sealed class RateLimitOptions
{
[Range(1, 10000)]
public int PermitLimit { get; set; } = 60;
public int PermitLimit { get; set; } = 600;

[Range(0, 1000)]
public int QueueLimit { get; set; } = 10;
public int QueueLimit { get; set; } = 60;

[Range(1, 3600)]
public int WindowSeconds { get; set; } = 60;
Expand Down
2 changes: 1 addition & 1 deletion Web/Resgrid.Web.Tts/Configuration/TtsOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public sealed class TtsOptions
public string TempDirectory { get; set; } = Path.Combine(Path.GetTempPath(), "resgrid-tts");

[Required]
public string CachePrefix { get; set; } = "tts";
public string CachePrefix { get; set; } = "tts2";

[Range(8000, 8000)]
public int NormalizedSampleRate { get; set; } = 8000;
Expand Down
2 changes: 1 addition & 1 deletion Web/Resgrid.Web.Tts/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ RUN set -eu; \
RUN set -eu; \
mkdir -p /usr/local/share/piper-voices; \
for f in \
"en/en_US/norman/medium/en_US-norman-medium" \
"en/en_US/ryan/high/en_US-ryan-high" \
"es/es_MX/claude/high/es_MX-claude-high" \
"sv/sv_SE/nst/medium/sv_SE-nst-medium" \
"de/de_DE/thorsten/medium/de_DE-thorsten-medium" \
Expand Down
25 changes: 18 additions & 7 deletions Web/Resgrid.Web.Tts/Services/AudioProcessingService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@ public sealed class AudioProcessingService : IAudioProcessingService
private const float SpeedReferenceWpm = 175f;
private const float MinLengthScale = 0.25f;
private const float MaxLengthScale = 3.0f;
private const string DefaultEnglishModel = "en_US-norman-medium.onnx";
private const string TelephoneAudioFilter = "highpass=f=200, lowpass=f=3000, anequalizer=c0 f=2500 w=1000 g=3 t=1";
private const string DefaultEnglishModel = "en_US-ryan-high.onnx";
// Lowpass sits at 3400 Hz (the telephony band edge) rather than 3000 so
// sibilants survive the mulaw encode; loudnorm keeps clips at a consistent
// perceived level over the phone.
private const string TelephoneAudioFilter = "highpass=f=200, lowpass=f=3400, anequalizer=c0 f=2500 w=1000 g=3 t=1, loudnorm=I=-16:TP=-1.5:LRA=11";

/// <summary>
/// Maps eSpeak voice identifiers to Piper model filenames provisioned in the
Expand All @@ -24,10 +27,10 @@ public sealed class AudioProcessingService : IAudioProcessingService
private static readonly Dictionary<string, string> VoiceModelMap = new(StringComparer.OrdinalIgnoreCase)
{
// English variants
{ "en", "en_US-norman-medium.onnx" },
{ "en-us", "en_US-norman-medium.onnx" },
{ "en-029", "en_US-norman-medium.onnx" },
{ "mb-us1", "en_US-norman-medium.onnx" },
{ "en", DefaultEnglishModel },
{ "en-us", DefaultEnglishModel },
{ "en-029", DefaultEnglishModel },
{ "mb-us1", DefaultEnglishModel },

// Spanish
{ "es", "es_MX-claude-high.onnx" },
Expand Down Expand Up @@ -202,8 +205,16 @@ private ProcessStartInfo CreatePiperStartInfo(string voice, int speed, string ou
startInfo.ArgumentList.Add(outputFilePath);
startInfo.ArgumentList.Add("--length-scale");
startInfo.ArgumentList.Add(invocation.LengthScale.ToString("0.00", CultureInfo.InvariantCulture));
// 0.35s of silence between sentences — dispatch messages are strings of
// short sentences and need audible boundaries to stay intelligible.
startInfo.ArgumentList.Add("--sentence-silence");
startInfo.ArgumentList.Add("0.0");
startInfo.ArgumentList.Add("0.35");
// Lower generation noise than the Piper defaults (0.667/0.8): reduces
// prosody jitter that makes digits and short words sound mumbled.
startInfo.ArgumentList.Add("--noise-scale");
startInfo.ArgumentList.Add("0.333");
startInfo.ArgumentList.Add("--noise-w");
startInfo.ArgumentList.Add("0.4");
Comment on lines +208 to +217

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Download Piper v1.2.0 binary and check its help output for argument names.

curl -fsSL "https://github.com/rhasspy/piper/releases/download/v1.2.0/piper_amd64.tar.gz" -o piper.tar.gz
tar -xzf piper.tar.gz
./piper/piper --help | grep -E 'sentence|noise'

Repository: Resgrid/Core

Length of output: 1266


Use Piper’s underscore flags here.
--sentence-silence, --noise-scale, and --noise-w are not recognized by Piper v1.2.0; use --sentence_silence, --noise_scale, and --noise_w instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Web/Resgrid.Web.Tts/Services/AudioProcessingService.cs` around lines 208 -
217, The Piper command arguments in AudioProcessingService must use
underscore-form flags for v1.2.0. Update the argument names in the affected
startInfo configuration from --sentence-silence, --noise-scale, and --noise-w to
--sentence_silence, --noise_scale, and --noise_w while preserving their existing
values.

Comment on lines +208 to +217

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kody code-review Kody Rules low

Violates rule 'Replace magic numbers with named constants': the new piper synthesis parameters 0.35 (sentence silence), 0.333 (noise scale), and 0.4 (noise width) on lines 211, 215, 217 are meaningful numeric literals that should be extracted into named constants. The same class already extracts similar tuning values (SpeedReferenceWpm=175f, MinLengthScale=0.25f, MaxLengthScale=3.0f), establishing the expected pattern.

// Named constants at the top of the class:
		private const float SentenceSilence = 0.35f;
		private const float NoiseScale = 0.333f;
		private const float NoiseWidth = 0.4f;

// In CreatePiperStartInfo:
			startInfo.ArgumentList.Add("--sentence-silence");
			startInfo.ArgumentList.Add(SentenceSilence.ToString("0.00", CultureInfo.InvariantCulture));
			startInfo.ArgumentList.Add("--noise-scale");
			startInfo.ArgumentList.Add(NoiseScale.ToString("0.000", CultureInfo.InvariantCulture));
			startInfo.ArgumentList.Add("--noise-w");
			startInfo.ArgumentList.Add(NoiseWidth.ToString("0.0", CultureInfo.InvariantCulture));
Prompt for LLM

File Web/Resgrid.Web.Tts/Services/AudioProcessingService.cs:

Line 208 to 217:

Violates rule 'Replace magic numbers with named constants': the new piper synthesis parameters 0.35 (sentence silence), 0.333 (noise scale), and 0.4 (noise width) on lines 211, 215, 217 are meaningful numeric literals that should be extracted into named constants. The same class already extracts similar tuning values (SpeedReferenceWpm=175f, MinLengthScale=0.25f, MaxLengthScale=3.0f), establishing the expected pattern.

Suggested Code:

// Named constants at the top of the class:
		private const float SentenceSilence = 0.35f;
		private const float NoiseScale = 0.333f;
		private const float NoiseWidth = 0.4f;

// In CreatePiperStartInfo:
			startInfo.ArgumentList.Add("--sentence-silence");
			startInfo.ArgumentList.Add(SentenceSilence.ToString("0.00", CultureInfo.InvariantCulture));
			startInfo.ArgumentList.Add("--noise-scale");
			startInfo.ArgumentList.Add(NoiseScale.ToString("0.000", CultureInfo.InvariantCulture));
			startInfo.ArgumentList.Add("--noise-w");
			startInfo.ArgumentList.Add(NoiseWidth.ToString("0.0", CultureInfo.InvariantCulture));

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.


return startInfo;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,10 @@ export default function LeafletMapView({
zoomControl: true,
});

// Leaflet throws "Set map center and zoom first." if the user interacts before
// the first setView/fitBounds; give the map a fallback view until mapData loads.
map.setView([39.14086268299356, -119.7583809782715], 9);

L.tileLayer(resolvedMapConfig.tileUrl, {
maxZoom: 19,
attribution: resolvedMapConfig.attribution,
Expand Down
8 changes: 8 additions & 0 deletions Web/Resgrid.Web/Areas/User/Controllers/MappingController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,10 @@ public async Task<IActionResult> NewLayer()
{
var model = new NewLayerView();
model.Department = await _departmentsService.GetDepartmentByIdAsync(DepartmentId);

if (model.Department == null)
return Unauthorized();

model.CenterCoordinates = await _departmentSettingsService.GetMapCenterCoordinatesAsync(model.Department);

return View(model);
Expand All @@ -170,6 +174,10 @@ public async Task<IActionResult> NewLayer()
public async Task<IActionResult> NewLayer(NewLayerView model)
{
model.Department = await _departmentsService.GetDepartmentByIdAsync(DepartmentId);

if (model.Department == null)
return Unauthorized();

model.CenterCoordinates = await _departmentSettingsService.GetMapCenterCoordinatesAsync(model.Department);

if (ModelState.IsValid)
Expand Down
27 changes: 0 additions & 27 deletions Web/Resgrid.Web/Areas/User/Views/Mapping/AddPOI.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -127,30 +127,3 @@
</div>
</div>
</div>


@section Scripts
{
<script>
$(document).ready(function () {
$('input[type=checkbox],input[type=radio]').iCheck({
checkboxClass: 'icheckbox_square-blue',
radioClass: 'iradio_square-blue'
});

$("#Type_Color").minicolors({
animationSpeed: 50,
animationEasing: 'swing',
changeDelay: 0,
control: 'hue',
defaultValue: '#0080ff',
format: 'hex',
showSpeed: 100,
hideSpeed: 100,
inline: false,
theme: 'bootstrap'
});
});
</script>

}
Loading