parent
b9fde1b0c2
commit
d58966bfbd
@ -0,0 +1,33 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> |
||||||
|
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="21513" systemVersion="22A400" minimumToolsVersion="Automatic" sourceLanguage="Swift" usedWithCloudKit="YES" userDefinedModelVersionIdentifier=""> |
||||||
|
<entity name="AbstractSoundTimer" representedClassName="AbstractSoundTimer" isAbstract="YES" parentEntity="AbstractTimer" syncable="YES"> |
||||||
|
<attribute name="repeatCount" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/> |
||||||
|
<attribute name="sound" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/> |
||||||
|
</entity> |
||||||
|
<entity name="AbstractTimer" representedClassName="AbstractTimer" isAbstract="YES" syncable="YES"> |
||||||
|
<attribute name="image" optional="YES" attributeType="String"/> |
||||||
|
<attribute name="order" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/> |
||||||
|
<relationship name="activity" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Activity" inverseName="timers" inverseEntity="Activity"/> |
||||||
|
</entity> |
||||||
|
<entity name="Activity" representedClassName="Activity" syncable="YES"> |
||||||
|
<attribute name="name" attributeType="String" defaultValueString=""/> |
||||||
|
<relationship name="records" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Record" inverseName="activity" inverseEntity="Record"/> |
||||||
|
<relationship name="timers" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="AbstractTimer" inverseName="activity" inverseEntity="AbstractTimer"/> |
||||||
|
</entity> |
||||||
|
<entity name="Alarm" representedClassName="Alarm" parentEntity="AbstractSoundTimer" syncable="YES"> |
||||||
|
<attribute name="fireDate" optional="YES" attributeType="Date" usesScalarValueType="NO"/> |
||||||
|
</entity> |
||||||
|
<entity name="Countdown" representedClassName="Countdown" parentEntity="AbstractSoundTimer" syncable="YES"> |
||||||
|
<attribute name="duration" attributeType="Double" defaultValueString="0" usesScalarValueType="YES"/> |
||||||
|
</entity> |
||||||
|
<entity name="Record" representedClassName="Record" syncable="YES"> |
||||||
|
<attribute name="end" attributeType="Date" defaultDateTimeInterval="696425400" usesScalarValueType="NO"/> |
||||||
|
<attribute name="start" attributeType="Date" defaultDateTimeInterval="696425400" usesScalarValueType="NO"/> |
||||||
|
<relationship name="activity" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Activity" inverseName="records" inverseEntity="Activity"/> |
||||||
|
</entity> |
||||||
|
<entity name="Stopwatch" representedClassName="Stopwatch" parentEntity="AbstractTimer" syncable="YES"> |
||||||
|
<attribute name="end" optional="YES" attributeType="Date" usesScalarValueType="NO"/> |
||||||
|
<attribute name="sound" optional="YES" attributeType="Integer 16" usesScalarValueType="YES"/> |
||||||
|
<attribute name="start" optional="YES" attributeType="Date" usesScalarValueType="NO"/> |
||||||
|
</entity> |
||||||
|
</model> |
||||||
@ -0,0 +1,52 @@ |
|||||||
|
// |
||||||
|
// TextToSpeechRecorder.swift |
||||||
|
// LeCountdown |
||||||
|
// |
||||||
|
// Created by Laurent Morvillier on 07/02/2023. |
||||||
|
// |
||||||
|
|
||||||
|
import Foundation |
||||||
|
import AVFoundation |
||||||
|
|
||||||
|
struct TextToSpeechFile: Codable { |
||||||
|
var speech: String |
||||||
|
var fileName: String |
||||||
|
} |
||||||
|
|
||||||
|
class TextToSpeechRecorder { |
||||||
|
|
||||||
|
static func record(speech: String, handler: @escaping (Result<TextToSpeechFile, Error>) -> Void) { |
||||||
|
let synthesizer = AVSpeechSynthesizer() |
||||||
|
let utterance = AVSpeechUtterance(string: speech) |
||||||
|
|
||||||
|
utterance.voice = AVSpeechSynthesisVoice() |
||||||
|
var output: AVAudioFile? |
||||||
|
|
||||||
|
synthesizer.write(utterance) { buffer in |
||||||
|
guard let pcmBuffer = buffer as? AVAudioPCMBuffer else { |
||||||
|
fatalError("unknown buffer type: \(buffer)") |
||||||
|
} |
||||||
|
let fileName = "\(UUID().uuidString).caf" |
||||||
|
if pcmBuffer.frameLength == 0 { |
||||||
|
handler(.success(TextToSpeechFile(speech: speech, fileName: fileName))) |
||||||
|
// done |
||||||
|
} else { |
||||||
|
// append buffer to file |
||||||
|
do { |
||||||
|
if output == nil { |
||||||
|
|
||||||
|
output = try AVAudioFile( |
||||||
|
forWriting: URL(fileURLWithPath: fileName), |
||||||
|
settings: pcmBuffer.format.settings, |
||||||
|
commonFormat: .pcmFormatInt16, |
||||||
|
interleaved: false) |
||||||
|
} |
||||||
|
try output?.write(from: pcmBuffer) |
||||||
|
} catch { |
||||||
|
handler(.failure(error)) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
Loading…
Reference in new issue