Skip to content
Open
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
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,9 @@ fastlane/test_output
iOSInjectionProject/

# Mac OS
.DS_Store
.DS_Store

# Archive outputs
Archives/
*.xcarchive
Recap.app
45 changes: 36 additions & 9 deletions Recap.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@
Services/Summarization/Models/SummarizationRequest.swift,
Services/Summarization/Models/SummarizationResult.swift,
Services/Summarization/SummarizationServiceType.swift,
Services/Transcription/Models/TranscriptionSegment.swift,
Services/Transcription/TranscriptionServiceType.swift,
Services/Utilities/Warnings/ProviderWarningCoordinator.swift,
Services/Utilities/Warnings/WarningManager.swift,
Expand All @@ -112,12 +113,20 @@
);
target = A721065F2E30165B0073C515 /* RecapTests */;
};
E7A63B8F2E84794D00192B23 /* Exceptions for "Recap" folder in "Recap" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
Info.plist,
);
target = A72106512E3016590073C515 /* Recap */;
};
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */

/* Begin PBXFileSystemSynchronizedRootGroup section */
A72106542E3016590073C515 /* Recap */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
E7A63B8F2E84794D00192B23 /* Exceptions for "Recap" folder in "Recap" target */,
A7C35B1B2E3DFE1D00F9261F /* Exceptions for "Recap" folder in "RecapTests" target */,
);
path = Recap;
Expand Down Expand Up @@ -234,7 +243,7 @@
attributes = {
BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 1640;
LastUpgradeCheck = 1640;
LastUpgradeCheck = 2600;
TargetAttributes = {
A72106512E3016590073C515 = {
CreatedOnToolsVersion = 16.4;
Expand Down Expand Up @@ -348,6 +357,7 @@
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = EY7EQX6JC5;
ENABLE_STRICT_OBJC_MSGSEND = YES;
Expand All @@ -373,6 +383,7 @@
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited) MOCKING";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
Expand Down Expand Up @@ -412,6 +423,7 @@
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = EY7EQX6JC5;
ENABLE_NS_ASSERTIONS = NO;
Expand All @@ -430,6 +442,7 @@
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = macosx;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_COMPILATION_MODE = wholemodule;
};
name = Release;
Expand All @@ -443,9 +456,15 @@
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = EY7EQX6JC5;
DEAD_CODE_STRIPPING = YES;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = "";
ENABLE_APP_SANDBOX = YES;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;
ENABLE_PREVIEWS = YES;
ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = YES;
ENABLE_USER_SELECTED_FILES = readonly;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = Recap/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Recap;
Expand All @@ -457,7 +476,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 0.0.2;
MARKETING_VERSION = 0.1.0;
PRODUCT_BUNDLE_IDENTIFIER = dev.rawa.Recap;
PRODUCT_NAME = "$(TARGET_NAME)";
REGISTER_APP_GROUPS = YES;
Expand All @@ -476,9 +495,15 @@
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = EY7EQX6JC5;
DEAD_CODE_STRIPPING = YES;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = "";
ENABLE_APP_SANDBOX = YES;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;
ENABLE_PREVIEWS = YES;
ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = YES;
ENABLE_USER_SELECTED_FILES = readonly;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = Recap/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Recap;
Expand All @@ -490,7 +515,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 0.0.2;
MARKETING_VERSION = 0.1.0;
PRODUCT_BUNDLE_IDENTIFIER = dev.rawa.Recap;
PRODUCT_NAME = "$(TARGET_NAME)";
REGISTER_APP_GROUPS = YES;
Expand All @@ -505,7 +530,8 @@
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = EY7EQX6JC5;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = "";
GENERATE_INFOPLIST_FILE = YES;
MACOSX_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 1.0;
Expand All @@ -524,7 +550,8 @@
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = EY7EQX6JC5;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = "";
GENERATE_INFOPLIST_FILE = YES;
MACOSX_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 1.0;
Expand Down Expand Up @@ -573,8 +600,8 @@
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/argmaxinc/WhisperKit.git";
requirement = {
branch = main;
kind = branch;
kind = upToNextMajorVersion;
minimumVersion = 0.9.0;
};
};
A743B0892E3D479600785BFF /* XCRemoteSwiftPackageReference "swift-markdown-ui" */ = {
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Recap.xcodeproj/xcshareddata/xcschemes/Recap.xcscheme
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1640"
LastUpgradeVersion = "2600"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
25 changes: 25 additions & 0 deletions Recap/Assets.xcassets/barIcon-dark.imageset/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"images" : [
{
"filename" : "Icon-dark.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "Icon-dark@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "Icon-dark@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}


Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
33 changes: 33 additions & 0 deletions Recap/Assets.xcassets/barIcon.imageset/Contents.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,39 @@
"filename" : "Icon@3x.png",
"idiom" : "universal",
"scale" : "3x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "Icon-dark.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "Icon-dark@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "Icon-dark@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
Expand Down
Binary file added Recap/Assets.xcassets/barIcon.imageset/Icon-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
31 changes: 29 additions & 2 deletions Recap/Audio/Capture/MicrophoneCapture+AudioEngine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,39 @@ extension MicrophoneCapture {
throw AudioCaptureError.coreAudioError("No output URL specified")
}

// Verify input node is available and has audio input
guard let inputNode = inputNode else {
throw AudioCaptureError.coreAudioError("Input node not available")
}

let inputFormat = inputNode.inputFormat(forBus: 0)
logger.info("Starting audio engine with input format: \(inputFormat.sampleRate)Hz, \(inputFormat.channelCount)ch")

// Check if input node has audio input available
if inputFormat.channelCount == 0 {
logger.warning("Input node has no audio channels available - microphone may not be connected or permission denied")
throw AudioCaptureError.coreAudioError("No audio input channels available - check microphone connection and permissions")
}

// Verify microphone permission before starting
let permissionStatus = AVCaptureDevice.authorizationStatus(for: .audio)
if permissionStatus != .authorized {
logger.error("Microphone permission not authorized: \(permissionStatus.rawValue)")
throw AudioCaptureError.microphonePermissionDenied
}

try createAudioFile(at: outputURL)
try installAudioTap()
try audioEngine.start()

do {
try audioEngine.start()
logger.info("AVAudioEngine started successfully")
} catch {
logger.error("Failed to start AVAudioEngine: \(error)")
throw AudioCaptureError.coreAudioError("Failed to start audio engine: \(error.localizedDescription)")
}

isRecording = true
logger.info("AVAudioEngine started successfully")
}

func installAudioTap() throws {
Expand Down
9 changes: 9 additions & 0 deletions Recap/Audio/Capture/MicrophoneCapture+AudioProcessing.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ extension MicrophoneCapture {
func processAudioBuffer(_ buffer: AVAudioPCMBuffer, at time: AVAudioTime) {
guard isRecording else { return }

// Log audio data reception for debugging
if buffer.frameLength > 0 {
logger.debug("Microphone received audio data: \(buffer.frameLength) frames, \(buffer.format.sampleRate)Hz, \(buffer.format.channelCount)ch")
}

calculateAndUpdateAudioLevel(from: buffer)

if let audioFile = audioFile {
Expand All @@ -16,16 +21,20 @@ extension MicrophoneCapture {

if let convertedBuffer = convertBuffer(buffer, to: targetFormat) {
try audioFile.write(from: convertedBuffer)
logger.debug("Wrote converted audio buffer: \(convertedBuffer.frameLength) frames")
} else {
logger.warning("Failed to convert buffer, writing original")
try audioFile.write(from: buffer)
}
} else {
try audioFile.write(from: buffer)
logger.debug("Wrote audio buffer: \(buffer.frameLength) frames")
}
} catch {
logger.error("Failed to write audio buffer: \(error)")
}
} else {
logger.warning("No audio file available for writing")
}
}

Expand Down
23 changes: 23 additions & 0 deletions Recap/Audio/Capture/Tap/AudioTapType.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import Foundation
import AudioToolbox
import AVFoundation

protocol AudioTapType: ObservableObject {
var activated: Bool { get }
var audioLevel: Float { get }
var errorMessage: String? { get }
var tapStreamDescription: AudioStreamBasicDescription? { get }

@MainActor func activate()
func invalidate()
func run(on queue: DispatchQueue, ioBlock: @escaping AudioDeviceIOBlock,
invalidationHandler: @escaping (Self) -> Void) throws
}

protocol AudioTapRecorderType: ObservableObject {
var fileURL: URL { get }
var isRecording: Bool { get }

@MainActor func start() throws
func stop()
}
Loading