diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml
deleted file mode 100644
index fb99c6b..0000000
--- a/.github/workflows/stale.yml
+++ /dev/null
@@ -1,27 +0,0 @@
-# This workflow warns and then closes issues and PRs that have had no activity for a specified amount of time.
-#
-# You can adjust the behavior by modifying this file.
-# For more information, see:
-# https://github.com/actions/stale
-name: Mark stale issues and pull requests
-
-on:
-  schedule:
-  - cron: '25 18 * * *'
-
-jobs:
-  stale:
-    runs-on: ubuntu-latest
-    permissions:
-      issues: write
-      pull-requests: write
-    steps:
-    - uses: actions/stale@v5
-      with:
-        repo-token: ${{ secrets.GITHUB_TOKEN }}
-        stale-issue-message: 'This issue has had no activity in the past 2 months. Marking as `stale`.'
-        stale-pr-message: 'This PR has had no activity in the past 2 months. Marking as `stale`.'
-        stale-issue-label: 'stale'
-        stale-pr-label: 'stale'
-        days-before-stale: 60
-        days-before-close: -1
diff --git a/README.md b/README.md
index 2c24abc..f2feab3 100644
--- a/README.md
+++ b/README.md
@@ -71,6 +71,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
 ## [Unreleased]
+- Updated dual barcodes primer sets to be created without csv files [#67](https://github.com/Koeng101/dnadesign/pull/67)
 - Added workers to bio as a way to process data [#62](https://github.com/Koeng101/dnadesign/pull/62)
 - Improved megamash efficiency and added []Match JSON conversion [#61](https://github.com/Koeng101/dnadesign/pull/61)
 - Added barcoding functionality for sequencing reads [#59](https://github.com/Koeng101/dnadesign/pull/59)
diff --git a/external/minimap2/minimap2.go b/external/minimap2/minimap2.go
index 578d24c..91ac73e 100644
--- a/external/minimap2/minimap2.go
+++ b/external/minimap2/minimap2.go
@@ -82,8 +82,7 @@ func Minimap2(templateFastaInput io.Reader, fastqInput io.Reader, w io.Writer) e
 }
 
 // Minimap2Channeled uses channels rather than io.Reader and io.Writers.
-func Minimap2Channeled(fastaTemplates io.Reader, fastqChan <-chan fastq.Read, samChan chan<- sam.Alignment) error {
-	ctx := context.Background()
+func Minimap2Channeled(ctx context.Context, fastaTemplates io.Reader, fastqChan <-chan fastq.Read, samChan chan<- sam.Alignment) error {
 	g, ctx := errgroup.WithContext(ctx)
 
 	// Create a pipe for writing fastq reads and reading them as an io.Reader
diff --git a/external/samtools/samtools.go b/external/samtools/samtools.go
index 8c4b1d2..d78e33b 100644
--- a/external/samtools/samtools.go
+++ b/external/samtools/samtools.go
@@ -10,6 +10,7 @@ import (
 	"os/exec"
 	"syscall"
 
+	"github.com/koeng101/dnadesign/lib/bio/sam"
 	"golang.org/x/sync/errgroup"
 )
 
@@ -22,7 +23,7 @@ import (
 // The first samtools view removes unmapped sequences, the sort sorts the
 // sequences for piping into pileup, and the final command builds the pileup
 // file.
-func Pileup(templateFastas io.Reader, samAlignments io.Reader, w io.Writer) error {
+func Pileup(ctx context.Context, templateFastas io.Reader, samAlignments io.Reader, w io.Writer) error {
 	/*
 		Due to how os.exec works in Golang, we can't directly have pipes as if
 		the whole thing was a script. However, we can attach pipes to each
@@ -49,7 +50,7 @@ func Pileup(templateFastas io.Reader, samAlignments io.Reader, w io.Writer) erro
 	}
 	tmpFile.Close() // Close the file as it's no longer needed
 
-	g, ctx := errgroup.WithContext(context.Background())
+	g, ctx := errgroup.WithContext(ctx)
 
 	// Setup pipe connections between commands
 	viewSortReader, viewSortWriter := io.Pipe()
@@ -73,7 +74,7 @@ func Pileup(templateFastas io.Reader, samAlignments io.Reader, w io.Writer) erro
 
 		select {
 		case <-ctx.Done():
-			viewCmd.Process.Signal(syscall.SIGTERM)
+			_ = viewCmd.Process.Signal(syscall.SIGTERM)
 			return ctx.Err()
 		default:
 			return viewCmd.Wait()
@@ -93,7 +94,7 @@ func Pileup(templateFastas io.Reader, samAlignments io.Reader, w io.Writer) erro
 
 		select {
 		case <-ctx.Done():
-			sortCmd.Process.Signal(syscall.SIGTERM)
+			_ = sortCmd.Process.Signal(syscall.SIGTERM)
 			return ctx.Err()
 		default:
 			return sortCmd.Wait()
@@ -111,7 +112,7 @@ func Pileup(templateFastas io.Reader, samAlignments io.Reader, w io.Writer) erro
 
 		select {
 		case <-ctx.Done():
-			mpileupCmd.Process.Signal(syscall.SIGTERM)
+			_ = mpileupCmd.Process.Signal(syscall.SIGTERM)
 			return ctx.Err()
 		default:
 			return mpileupCmd.Wait()
@@ -125,3 +126,37 @@ func Pileup(templateFastas io.Reader, samAlignments io.Reader, w io.Writer) erro
 
 	return nil
 }
+
+// PileupChanneled processes SAM alignments from a channel and sends pileup lines to another channel.
+func PileupChanneled(ctx context.Context, templateFastas io.Reader, samChan <-chan sam.Alignment, w io.Writer) error {
+	g, ctx := errgroup.WithContext(ctx)
+
+	// Create a pipe for writing SAM alignments and reading them as an io.Reader
+	samPr, samPw := io.Pipe()
+
+	// Goroutine to consume SAM alignments and write them to the PipeWriter
+	g.Go(func() error {
+		defer samPw.Close()
+		for alignment := range samChan {
+			// Assuming the sam.Alignment type has a WriteTo method or similar to serialize it to the writer
+			_, err := alignment.WriteTo(samPw)
+			if err != nil {
+				return err // return error to be handled by errgroup
+			}
+		}
+		return nil
+	})
+
+	// Run Pileup function in a goroutine
+	g.Go(func() error {
+		return Pileup(ctx, templateFastas, samPr, w) // Runs Pileup, writing output to pileupPw
+	})
+
+	// Wait for all goroutines in the group to finish
+	if err := g.Wait(); err != nil {
+		return err // This will return the first non-nil error from the group of goroutines
+	}
+
+	// At this point, all goroutines have finished successfully
+	return nil
+}
diff --git a/external/samtools/samtools_test.go b/external/samtools/samtools_test.go
index 8e95930..c75aadf 100644
--- a/external/samtools/samtools_test.go
+++ b/external/samtools/samtools_test.go
@@ -2,6 +2,7 @@ package samtools_test
 
 import (
 	"bytes"
+	"context"
 	"os"
 	"testing"
 
@@ -28,7 +29,8 @@ func TestPileup(t *testing.T) {
 	var buf bytes.Buffer
 
 	// Execute the pileup function
-	err = samtools.Pileup(templateFile, samFile, &buf)
+	ctx := context.Background()
+	err = samtools.Pileup(ctx, templateFile, samFile, &buf)
 	if err != nil {
 		t.Errorf("Pileup returned error: %s", err)
 	}
diff --git a/go.work b/go.work
index 6d2d1d1..bbf0ea2 100644
--- a/go.work
+++ b/go.work
@@ -2,6 +2,6 @@ go 1.22.0
 
 use (
 	./api
-    ./external
+	./external
 	./lib
 )
diff --git a/lib/align/megamash/megamash.go b/lib/align/megamash/megamash.go
index 0893092..3873d0c 100644
--- a/lib/align/megamash/megamash.go
+++ b/lib/align/megamash/megamash.go
@@ -11,6 +11,7 @@ package megamash
 import (
 	"encoding/json"
 	"fmt"
+	"strings"
 
 	"github.com/koeng101/dnadesign/lib/bio/fasta"
 	"github.com/koeng101/dnadesign/lib/transform"
@@ -19,6 +20,7 @@ import (
 // StandardizedDNA returns the alphabetically lesser strand of a double
 // stranded DNA molecule.
 func StandardizedDNA(sequence string) string {
+	sequence = strings.ToUpper(sequence)
 	var deterministicSequence string
 	reverseComplement := transform.ReverseComplement(sequence)
 	if sequence > reverseComplement {
@@ -32,7 +34,7 @@ func StandardizedDNA(sequence string) string {
 var (
 	DefaultKmerSize         uint    = 16
 	DefaultMinimalKmerCount uint    = 10
-	DefaultScoreThreshold   float64 = 0.2
+	DefaultScoreThreshold   float64 = 0.5
 )
 
 type MegamashMap struct {
diff --git a/lib/align/megamash/megamash_test.go b/lib/align/megamash/megamash_test.go
index 5e53c0f..3aeaaa1 100644
--- a/lib/align/megamash/megamash_test.go
+++ b/lib/align/megamash/megamash_test.go
@@ -1,8 +1,9 @@
-package megamash
+package megamash_test
 
 import (
 	"testing"
 
+	"github.com/koeng101/dnadesign/lib/align/megamash"
 	"github.com/koeng101/dnadesign/lib/bio/fasta"
 )
 
@@ -12,7 +13,7 @@ func TestMegamash(t *testing.T) {
 	oligo3 := "CCGTGCGACAAGATTTCAAGGGTCTCTCTTCTATCGCAGCCAAGGAAGAAGGTGTATCTCTAGAGAAGCGTCGAGTGAGACCCGGATCGAACTTAGGTAGCCCCCTTCGAAGTGGCTCTGTCTGATCCTCCGCGGATGGCGACACCATCGGACTGAGGATATTGGCCACA"
 
 	samples := []string{"TTTTGTCTACTTCGTTCCGTTGCGTATTGCTAAGGTTAAGACTACTTTCTGCCTTTGCGAGACGGCGCCTCCGTGCGACGAGATTTCAAGGGTCTCTGTGCTATATTGCCGCTAGTTCCGCTCTAGCTGCTCCAGTTAATACTACTACTGAAGATGAATTGGAGGGTGACTTCGATGTTGCTGTTCTGCCTTTTTCCGCTTCTGAGACCCAGATCGACTTTTAGATTCCTCAGGTGCTGTTCTCGCAAAGGCAGAAAGTAGTCTTAACCTTAGCAATACGTGG", "TGTCCTTTACTTCGTTCAGTTACGTATTGCTAAGGTTAAGACTACTTTCTGCCTTTGCGAGAACAGCACCTCTGCTAGGGGCTACTTATCGGGTCTCTAGTTCCGCTCTAGCTGCTCCAGTTAATACTACTACTGAAGATGAATTGGAGGGTGACTTCGATGTTGCTGTTCTGCCTTTTTCCGCTTCTATCTGAGACCGAAGTGGTTTGCCTAAACGCAGGTGCTGTTGGCAAAGGCAGAAAGTAGTCTTAACCTTGACAATGAGTGGTA", "GTTATTGTCGTCTCCTTTGACTCAGCGTATTGCTAAGGTTAAGACTACTTTCTGCCTTTGCGAGAACAGCACCTCTGCTAGGGGCTGCTGGGTCTCTAGTTCCGCTCTAGCTGCTCCAGTTAATACTACTACTGAAGATGAATTGGAGGGTGACTTCGATGTTGCTGTTCTGCCTTTTCCGCTTCTATCTGAGACCGAAGTGGTTAT", "TGTTCTGTACTTCGTTCAGTTACGTATTGCTAAGGTTAAGACTACTTCTGCCTTAGAGACCACGCCTCCGTGCGACAAGATTCAAGGGTCTCTGTGCTCTGCCGCTAGTTCCGCTCTAGCTGCTCCGGTATGCATCTACTACTGAAGATGAATTGGAGGGTGACTTCGATGTTGCTGCTGTTCTGCCTTTTTCCGCTTCTGAGACCCGGATCGAACTTAGGTAGCCAGGTGCTGTTCTCGCAAAGGCAGAAAGTAGTCTTAACCTTAGCAACTGTTGGTT"}
-	m, err := NewMegamashMap([]fasta.Record{{Sequence: oligo1, Identifier: "oligo1"}, {Sequence: oligo2, Identifier: "oligo2"}, {Sequence: oligo3, Identifier: "oligo3"}}, DefaultKmerSize, DefaultMinimalKmerCount, DefaultScoreThreshold)
+	m, err := megamash.NewMegamashMap([]fasta.Record{{Sequence: oligo1, Identifier: "oligo1"}, {Sequence: oligo2, Identifier: "oligo2"}, {Sequence: oligo3, Identifier: "oligo3"}}, megamash.DefaultKmerSize, megamash.DefaultMinimalKmerCount, megamash.DefaultScoreThreshold)
 	if err != nil {
 		t.Errorf("Failed to make NewMegamashMap: %s", err)
 	}
@@ -31,8 +32,8 @@ func BenchmarkMegamash(b *testing.B) {
 		oligo3 := "CCGTGCGACAAGATTTCAAGGGTCTCTCTTCTATCGCAGCCAAGGAAGAAGGTGTATCTCTAGAGAAGCGTCGAGTGAGACCCGGATCGAACTTAGGTAGCCCCCTTCGAAGTGGCTCTGTCTGATCCTCCGCGGATGGCGACACCATCGGACTGAGGATATTGGCCACA"
 
 		samples := []string{"TTTTGTCTACTTCGTTCCGTTGCGTATTGCTAAGGTTAAGACTACTTTCTGCCTTTGCGAGACGGCGCCTCCGTGCGACGAGATTTCAAGGGTCTCTGTGCTATATTGCCGCTAGTTCCGCTCTAGCTGCTCCAGTTAATACTACTACTGAAGATGAATTGGAGGGTGACTTCGATGTTGCTGTTCTGCCTTTTTCCGCTTCTGAGACCCAGATCGACTTTTAGATTCCTCAGGTGCTGTTCTCGCAAAGGCAGAAAGTAGTCTTAACCTTAGCAATACGTGG", "TGTCCTTTACTTCGTTCAGTTACGTATTGCTAAGGTTAAGACTACTTTCTGCCTTTGCGAGAACAGCACCTCTGCTAGGGGCTACTTATCGGGTCTCTAGTTCCGCTCTAGCTGCTCCAGTTAATACTACTACTGAAGATGAATTGGAGGGTGACTTCGATGTTGCTGTTCTGCCTTTTTCCGCTTCTATCTGAGACCGAAGTGGTTTGCCTAAACGCAGGTGCTGTTGGCAAAGGCAGAAAGTAGTCTTAACCTTGACAATGAGTGGTA", "GTTATTGTCGTCTCCTTTGACTCAGCGTATTGCTAAGGTTAAGACTACTTTCTGCCTTTGCGAGAACAGCACCTCTGCTAGGGGCTGCTGGGTCTCTAGTTCCGCTCTAGCTGCTCCAGTTAATACTACTACTGAAGATGAATTGGAGGGTGACTTCGATGTTGCTGTTCTGCCTTTTCCGCTTCTATCTGAGACCGAAGTGGTTAT", "TGTTCTGTACTTCGTTCAGTTACGTATTGCTAAGGTTAAGACTACTTCTGCCTTAGAGACCACGCCTCCGTGCGACAAGATTCAAGGGTCTCTGTGCTCTGCCGCTAGTTCCGCTCTAGCTGCTCCGGTATGCATCTACTACTGAAGATGAATTGGAGGGTGACTTCGATGTTGCTGCTGTTCTGCCTTTTTCCGCTTCTGAGACCCGGATCGAACTTAGGTAGCCAGGTGCTGTTCTCGCAAAGGCAGAAAGTAGTCTTAACCTTAGCAACTGTTGGTT"}
-		m, _ := NewMegamashMap([]fasta.Record{{Sequence: oligo1, Identifier: "oligo1"}, {Sequence: oligo2, Identifier: "oligo2"}, {Sequence: oligo3, Identifier: "oligo3"}},
-			DefaultKmerSize, DefaultMinimalKmerCount, DefaultScoreThreshold)
+		m, _ := megamash.NewMegamashMap([]fasta.Record{{Sequence: oligo1, Identifier: "oligo1"}, {Sequence: oligo2, Identifier: "oligo2"}, {Sequence: oligo3, Identifier: "oligo3"}},
+			megamash.DefaultKmerSize, megamash.DefaultMinimalKmerCount, megamash.DefaultScoreThreshold)
 		for _, sample := range samples {
 			_ = m.Match(sample)
 		}
@@ -41,24 +42,24 @@ func BenchmarkMegamash(b *testing.B) {
 
 func TestMatchesConversion(t *testing.T) {
 	// Initial slice of Match structs
-	matches := []Match{
+	matches := []megamash.Match{
 		{"match1", 90.1},
 		{"match2", 85.5},
 	}
 	// Convert matches to JSON string
-	jsonStr, err := MatchesToJSON(matches)
+	jsonStr, err := megamash.MatchesToJSON(matches)
 	if err != nil {
 		t.Fatalf("MatchesToJSON failed with error: %v", err)
 	}
 
 	// Convert JSON string back to slice of Match structs
-	convertedMatches, err := JSONToMatches(jsonStr)
+	convertedMatches, err := megamash.JSONToMatches(jsonStr)
 	if err != nil {
 		t.Fatalf("JSONToMatches failed with error: %v", err)
 	}
 
 	// Convert the convertedMatches back to JSON to compare strings
-	convertedJSONStr, err := MatchesToJSON(convertedMatches)
+	convertedJSONStr, err := megamash.MatchesToJSON(convertedMatches)
 	if err != nil {
 		t.Fatalf("MatchesToJSON failed with error: %v", err)
 	}
diff --git a/lib/bio/fastq/fastq.go b/lib/bio/fastq/fastq.go
index 15abd7d..160cc06 100644
--- a/lib/bio/fastq/fastq.go
+++ b/lib/bio/fastq/fastq.go
@@ -16,6 +16,7 @@ import (
 	"errors"
 	"fmt"
 	"io"
+	"sort"
 	"strings"
 )
 
@@ -40,6 +41,17 @@ type Read struct {
 	Quality    string            `json:"quality"`
 }
 
+// DeepCopy deep copies a read. Used for when you want to modify optionals then
+// pipe elsewhere.
+func (read *Read) DeepCopy() Read {
+	newRead := Read{Identifier: read.Identifier, Sequence: read.Sequence, Quality: read.Quality}
+	newRead.Optionals = make(map[string]string)
+	for key, value := range read.Optionals {
+		newRead.Optionals[key] = value
+	}
+	return newRead
+}
+
 // Header is a blank struct, needed for compatibility with bio parsers. It contains nothing.
 type Header struct{}
 
@@ -161,7 +173,11 @@ func (parser *Parser) Next() (Read, error) {
 	if len(line) <= 1 { // newline delimiter - actually checking for empty line
 		return Read{}, fmt.Errorf("empty quality sequence for %q,  got to line %d: %w", seqIdentifier, parser.line, err)
 	}
-	quality = string(line[:len(line)-1])
+	if parser.atEOF {
+		quality = string(line)
+	} else {
+		quality = string(line[:len(line)-1])
+	}
 
 	// Parsing ended. Check for inconsistencies.
 	if lookingForIdentifier {
@@ -179,12 +195,6 @@ func (parser *Parser) Next() (Read, error) {
 	return fastq, nil
 }
 
-// Reset discards all data in buffer and resets state.
-func (parser *Parser) Reset(r io.Reader) {
-	parser.reader.Reset(r)
-	parser.line = 0
-}
-
 /******************************************************************************
 
 Start of  Write functions
@@ -200,8 +210,15 @@ func (read *Read) WriteTo(w io.Writer) (int64, error) {
 	if err != nil {
 		return writtenBytes, err
 	}
-	for key, val := range read.Optionals {
-		newWrittenBytes, err = fmt.Fprintf(w, " %s=%s", key, val)
+	keys := make([]string, len(read.Optionals))
+	i := 0
+	for key := range read.Optionals {
+		keys[i] = key
+		i++
+	}
+	sort.Strings(keys)
+	for _, key := range keys {
+		newWrittenBytes, err = fmt.Fprintf(w, " %s=%s", key, read.Optionals[key])
 		writtenBytes += int64(newWrittenBytes)
 		if err != nil {
 			return writtenBytes, err
diff --git a/lib/go.mod b/lib/go.mod
index a062458..15e101f 100644
--- a/lib/go.mod
+++ b/lib/go.mod
@@ -4,5 +4,6 @@ go 1.22.0
 
 require (
 	github.com/google/go-cmp v0.6.0
+	github.com/koeng101/dnadesign/external v0.0.0-20240213205901-f4998ef84117
 	golang.org/x/sync v0.5.0
 )
diff --git a/lib/go.sum b/lib/go.sum
index e56dc15..440d22d 100644
--- a/lib/go.sum
+++ b/lib/go.sum
@@ -1,4 +1,6 @@
 github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
 github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/koeng101/dnadesign/external v0.0.0-20240213205901-f4998ef84117 h1:MLWgADbigSsAmDP3yG93ESlN0Ek9QLtH5uHigmWVXwg=
+github.com/koeng101/dnadesign/external v0.0.0-20240213205901-f4998ef84117/go.mod h1:nb80z/jm5HMCxfNZ50cBJa5TffkXxpY9okvqnBj8RrM=
 golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
 golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
diff --git a/lib/sequencing/barcoding/barcoding.go b/lib/sequencing/barcoding/barcoding.go
index ad2add5..7c29ccc 100644
--- a/lib/sequencing/barcoding/barcoding.go
+++ b/lib/sequencing/barcoding/barcoding.go
@@ -173,10 +173,48 @@ type DualBarcodePrimerSet struct {
 
 // DualBarcode contains a forward and reverse barcode.
 type DualBarcode struct {
+	Name    string
 	Forward string
 	Reverse string
 }
 
+// DualBarcodesToPrimerSet parsers a list of dual barcodes into a dual barcode
+// primer set.
+func DualBarcodesToPrimerSet(dualBarcodes []DualBarcode) DualBarcodePrimerSet {
+	var result DualBarcodePrimerSet
+	result.BarcodeMap = make(map[string]DualBarcode)
+	result.ReverseBarcodeMap = make(map[DualBarcode]string)
+	forwardBarcodesMap := make(map[string]bool)
+	reverseBarcodesMap := make(map[string]bool)
+
+	for _, barcode := range dualBarcodes {
+		forwardBarcodesMap[barcode.Forward] = true
+		reverseBarcodesMap[barcode.Reverse] = true
+		newDualBarcode := DualBarcode{Forward: barcode.Forward, Reverse: barcode.Reverse}
+		result.BarcodeMap[barcode.Name] = newDualBarcode
+		result.ReverseBarcodeMap[newDualBarcode] = barcode.Name
+	}
+	// Convert maps to slices
+	forwardBarcodes := make([]string, 0, len(forwardBarcodesMap))
+	for barcode := range forwardBarcodesMap {
+		forwardBarcodes = append(forwardBarcodes, barcode)
+	}
+	reverseBarcodes := make([]string, 0, len(reverseBarcodesMap))
+	for barcode := range reverseBarcodesMap {
+		reverseBarcodes = append(reverseBarcodes, barcode)
+	}
+
+	// Sort the slices
+	sort.Strings(forwardBarcodes)
+	sort.Strings(reverseBarcodes)
+
+	// Append sorted barcodes to result
+	result.ForwardBarcodes = forwardBarcodes
+	result.ReverseBarcodes = reverseBarcodes
+
+	return result
+}
+
 // ParseDualPrimerSet parses a csv file into a DualBarcodePrimerSet.
 func ParseDualPrimerSet(csvFile io.Reader) (DualBarcodePrimerSet, error) {
 	var result DualBarcodePrimerSet
diff --git a/lib/sequencing/barcoding/barcoding_test.go b/lib/sequencing/barcoding/barcoding_test.go
index 023b3a0..0686e05 100644
--- a/lib/sequencing/barcoding/barcoding_test.go
+++ b/lib/sequencing/barcoding/barcoding_test.go
@@ -1,11 +1,13 @@
-package barcoding
+package barcoding_test
 
 import (
 	_ "embed"
 	"fmt"
 	"strings"
+	"testing"
 
 	"github.com/koeng101/dnadesign/lib/bio"
+	"github.com/koeng101/dnadesign/lib/sequencing/barcoding"
 	"github.com/koeng101/dnadesign/lib/sequencing/nanopore"
 )
 
@@ -32,7 +34,7 @@ ATGTTGCCTACCTACTTGGTTCAGTTACGTATTGCTAAGGTTAACACAAAGACACCCGACAACTTTCTTCAGCACCTGCC
 +
 %%%%%%####$%(*+,)&''));::80-''')0000BCFJOSNLQJKJKRSMSFKLSDHIMGJEHDABFCCHIJKSSISHGJMSKSSKSOLSJSJIOJMSMNRKJKSJQLISGHGIHSJHSLJKSMISJEEBBCBH>=:/---849000>@GEEEJOKJIJHGSISIOMHKKGFGFJMGHHROIPSIJSSJKSLSMMSKJSJIIIKNMMEISKMLSKSHJPHLMJNQLHGRJKKKSHKKOMISOSGFJIOSSLSOSSSSNMSIFH876'&'(....+++(%`
 
-	primerSet, _ := ParseSinglePrimerSet(strings.NewReader(primerSetCsv))
+	primerSet, _ := barcoding.ParseSinglePrimerSet(strings.NewReader(primerSetCsv))
 	parser := bio.NewFastqParser(strings.NewReader(reads))
 	records, _ := parser.Parse()
 
@@ -40,7 +42,7 @@ ATGTTGCCTACCTACTTGGTTCAGTTACGTATTGCTAAGGTTAACACAAAGACACCCGACAACTTTCTTCAGCACCTGCC
 	for _, record := range records {
 		// Note: Nanopore has a score that requires a lower match (~16) than the
 		// default ScoreMagicNumber (18).
-		barcode, _ := SingleBarcodeSequence(record.Sequence, primerSet)
+		barcode, _ := barcoding.SingleBarcodeSequence(record.Sequence, primerSet)
 		if barcode != "" {
 			barcodes = append(barcodes, barcode)
 		}
@@ -102,13 +104,13 @@ GTGTGTACTTCGTTCAGTTACGTATGCTTAGTTGTGAACACAAAGACACCGACAACTTCTTCAGCACCTTCTTGATCTTC
 +
 $$$%%%%&',*+,//;=:9771111,-+*&&%%%%%'.1389:987777844443553((50+,,88:;2///;6555597677764334578879999:977778888888;=;99888766534424551666688767644445776777:<;:998986652222378*9:::<;:99876322211//..,,,,-0667873333488('''122../
 `
-	primerSet, _ := ParseDualPrimerSet(strings.NewReader(primerSetCsv))
+	primerSet, _ := barcoding.ParseDualPrimerSet(strings.NewReader(primerSetCsv))
 	parser := bio.NewFastqParser(strings.NewReader(reads))
 	records, _ := parser.Parse()
 
 	var wells []string
 	for _, record := range records[0:10] {
-		well, _ := DualBarcodeSequence(record.Sequence, primerSet)
+		well, _ := barcoding.DualBarcodeSequence(record.Sequence, primerSet)
 		if well != "" {
 			wells = append(wells, well)
 		}
@@ -117,3 +119,11 @@ $$$%%%%&',*+,//;=:9771111,-+*&&%%%%%'.1389:987777844443553((50+,,88:;2///;655559
 	fmt.Println(wells)
 	// Output: [B15 O1 O1 J22 C22 E20 A15]
 }
+
+func TestDualBarcodesToPrimerSet(t *testing.T) {
+	dualBarcodes := []barcoding.DualBarcode{{Name: "test", Forward: "ATG", Reverse: "TAA"}}
+	primerSet := barcoding.DualBarcodesToPrimerSet(dualBarcodes)
+	if len(primerSet.ForwardBarcodes) != 1 || len(primerSet.ReverseBarcodes) != 1 {
+		t.Errorf("Should have gotten barcode length of 1.")
+	}
+}
diff --git a/lib/sequencing/example_test.go b/lib/sequencing/example_test.go
index b53a794..7330151 100644
--- a/lib/sequencing/example_test.go
+++ b/lib/sequencing/example_test.go
@@ -1,125 +1,107 @@
 package sequencing_test
 
-import (
-	"bytes"
-	"context"
-	"fmt"
-	"log"
-	"os"
-	"os/exec"
-
-	"github.com/koeng101/dnadesign/external/minimap2"
-	"github.com/koeng101/dnadesign/lib/bio"
-	"github.com/koeng101/dnadesign/lib/bio/fasta"
-	"github.com/koeng101/dnadesign/lib/bio/fastq"
-	"github.com/koeng101/dnadesign/lib/bio/sam"
-	"github.com/koeng101/dnadesign/lib/primers/pcr"
-	"github.com/koeng101/dnadesign/lib/transform"
-	"golang.org/x/sync/errgroup"
-)
-
-func Example_ampliconAlignment() {
-	// This is currently a work-in-progress. Sequencing utilities are under
-	// development right now.
-	//
-	//
-	// Only run function if minimap2 is available
-	_, err := exec.LookPath("minimap2")
-	if err != nil {
-		fmt.Println("oligo2")
-		return
-	}
-	// First, let's define the type we are looking for: amplicons in a pool.
-	type Amplicon struct {
-		Identifier       string
-		TemplateSequence string
-		ForwardPrimer    string
-		ReversePrimer    string
-	}
-
-	// Next, let's define data we'll be working on. In particular, the
-	// templates and fastq files.
-
-	/*
-		Data processing steps:
-
-		1. Simulate PCRs of amplicons
-		2. Sort for the right barcodes
-		3. Trim fastq reads
-		4. Minimap2 fastq reads to amplicons
-		5. Filter for primary alignments
-	*/
-	var amplicons []Amplicon
-	var templates []fasta.Record
-	pcrTm := 50.0
-
-	forward := "CCGTGCGACAAGATTTCAAG"
-	reverse := transform.ReverseComplement("CGGATCGAACTTAGGTAGCC")
-	oligo1 := Amplicon{Identifier: "oligo1", ForwardPrimer: forward, ReversePrimer: reverse, TemplateSequence: "CCGTGCGACAAGATTTCAAGGGTCTCTGTCTCAATGACCAAACCAACGCAAGTCTTAGTTCGTTCAGTCTCTATTTTATTCTTCATCACACTGTTGCACTTGGTTGTTGCAATGAGATTTCCTAGTATTTTCACTGCTGTGCTGAGACCCGGATCGAACTTAGGTAGCCT"}
-	oligo2 := Amplicon{Identifier: "oligo2", ForwardPrimer: forward, ReversePrimer: reverse, TemplateSequence: "CCGTGCGACAAGATTTCAAGGGTCTCTGTGCTATTTGCCGCTAGTTCCGCTCTAGCTGCTCCAGTTAATACTACTACTGAAGATGAATTGGAGGGTGACTTCGATGTTGCTGTTCTGCCTTTTTCCGCTTCTGAGACCCGGATCGAACTTAGGTAGCCACTAGTCATAAT"}
-	oligo3 := Amplicon{Identifier: "oligo3", ForwardPrimer: forward, ReversePrimer: reverse, TemplateSequence: "CCGTGCGACAAGATTTCAAGGGTCTCTCTTCTATCGCAGCCAAGGAAGAAGGTGTATCTCTAGAGAAGCGTCGAGTGAGACCCGGATCGAACTTAGGTAGCCCCCTTCGAAGTGGCTCTGTCTGATCCTCCGCGGATGGCGACACCATCGGACTGAGGATATTGGCCACA"}
-	amplicons = []Amplicon{oligo1, oligo2, oligo3}
-
-	// Simulate PCRs
-	for _, amplicon := range amplicons {
-		fragments, _ := pcr.Simulate([]string{amplicon.TemplateSequence}, pcrTm, false, []string{amplicon.ForwardPrimer, amplicon.ReversePrimer})
-		if len(fragments) != 1 {
-			log.Fatalf("Should only get 1 fragment from PCR!")
-		}
-		// In case your template will have multiple fragments
-		for _, fragment := range fragments {
-			// Make sure to reset identifier if you have more than 1 fragment.
-			templates = append(templates, fasta.Record{Identifier: amplicon.Identifier, Sequence: fragment})
-		}
-	}
-	var buf bytes.Buffer
-	for _, template := range templates {
-		_, _ = template.WriteTo(&buf)
-	}
-
-	// Trim fastq reads. All the following processes (trimming, minimap2,
-	// filtering) are all done concurrently.
-
-	// Setup barcodes and fastq files
-	barcode := "barcode06"
-	r, _ := os.Open("data/reads.fastq")
-	parser := bio.NewFastqParser(r)
-
-	// Setup errorGroups and channels
-	ctx := context.Background()
-	errorGroup, ctx := errgroup.WithContext(ctx)
-
-	fastqReads := make(chan fastq.Read)
-	fastqBarcoded := make(chan fastq.Read)
-	samReads := make(chan sam.Alignment)
-	samPrimary := make(chan sam.Alignment)
-
-	// Read fastqs into channel
-	errorGroup.Go(func() error {
-		return parser.ParseToChannel(ctx, fastqReads, false)
-	})
-
-	// Filter the right barcode fastqs from channel
-	errorGroup.Go(func() error {
-		return bio.FilterData(ctx, fastqReads, fastqBarcoded, func(data fastq.Read) bool { return data.Optionals["barcode"] == barcode })
-	})
-
-	// Run minimap
-	errorGroup.Go(func() error {
-		return minimap2.Minimap2Channeled(&buf, fastqBarcoded, samReads)
-	})
-
-	// Sort out primary alignments
-	errorGroup.Go(func() error {
-		return bio.FilterData(ctx, samReads, samPrimary, sam.Primary)
-	})
-
-	// Read all them alignments out into memory
-	var outputAlignments []sam.Alignment
-	for alignment := range samPrimary {
-		outputAlignments = append(outputAlignments, alignment)
-	}
-
-	fmt.Println(outputAlignments[0].RNAME)
-	// Output: oligo2
-}
+//func Example_ampliconAlignment() {
+//	// This is currently a work-in-progress. Sequencing utilities are under
+//	// development right now.
+//	//
+//	//
+//	// Only run function if minimap2 is available
+//	_, err := exec.LookPath("minimap2")
+//	if err != nil {
+//		fmt.Println("oligo2")
+//		return
+//	}
+//	// First, let's define the type we are looking for: amplicons in a pool.
+//	type Amplicon struct {
+//		Identifier       string
+//		TemplateSequence string
+//		ForwardPrimer    string
+//		ReversePrimer    string
+//	}
+//
+//	// Next, let's define data we'll be working on. In particular, the
+//	// templates and fastq files.
+//
+//	/*
+//		Data processing steps:
+//
+//		1. Simulate PCRs of amplicons
+//		2. Sort for the right barcodes
+//		3. Trim fastq reads
+//		4. Minimap2 fastq reads to amplicons
+//		5. Filter for primary alignments
+//	*/
+//	var amplicons []Amplicon
+//	var templates []fasta.Record
+//	pcrTm := 50.0
+//
+//	forward := "CCGTGCGACAAGATTTCAAG"
+//	reverse := transform.ReverseComplement("CGGATCGAACTTAGGTAGCC")
+//	oligo1 := Amplicon{Identifier: "oligo1", ForwardPrimer: forward, ReversePrimer: reverse, TemplateSequence: "CCGTGCGACAAGATTTCAAGGGTCTCTGTCTCAATGACCAAACCAACGCAAGTCTTAGTTCGTTCAGTCTCTATTTTATTCTTCATCACACTGTTGCACTTGGTTGTTGCAATGAGATTTCCTAGTATTTTCACTGCTGTGCTGAGACCCGGATCGAACTTAGGTAGCCT"}
+//	oligo2 := Amplicon{Identifier: "oligo2", ForwardPrimer: forward, ReversePrimer: reverse, TemplateSequence: "CCGTGCGACAAGATTTCAAGGGTCTCTGTGCTATTTGCCGCTAGTTCCGCTCTAGCTGCTCCAGTTAATACTACTACTGAAGATGAATTGGAGGGTGACTTCGATGTTGCTGTTCTGCCTTTTTCCGCTTCTGAGACCCGGATCGAACTTAGGTAGCCACTAGTCATAAT"}
+//	oligo3 := Amplicon{Identifier: "oligo3", ForwardPrimer: forward, ReversePrimer: reverse, TemplateSequence: "CCGTGCGACAAGATTTCAAGGGTCTCTCTTCTATCGCAGCCAAGGAAGAAGGTGTATCTCTAGAGAAGCGTCGAGTGAGACCCGGATCGAACTTAGGTAGCCCCCTTCGAAGTGGCTCTGTCTGATCCTCCGCGGATGGCGACACCATCGGACTGAGGATATTGGCCACA"}
+//	amplicons = []Amplicon{oligo1, oligo2, oligo3}
+//
+//	// Simulate PCRs
+//	for _, amplicon := range amplicons {
+//		fragments, _ := pcr.Simulate([]string{amplicon.TemplateSequence}, pcrTm, false, []string{amplicon.ForwardPrimer, amplicon.ReversePrimer})
+//		if len(fragments) != 1 {
+//			log.Fatalf("Should only get 1 fragment from PCR!")
+//		}
+//		// In case your template will have multiple fragments
+//		for _, fragment := range fragments {
+//			// Make sure to reset identifier if you have more than 1 fragment.
+//			templates = append(templates, fasta.Record{Identifier: amplicon.Identifier, Sequence: fragment})
+//		}
+//	}
+//	var buf bytes.Buffer
+//	for _, template := range templates {
+//		_, _ = template.WriteTo(&buf)
+//	}
+//
+//	// Trim fastq reads. All the following processes (trimming, minimap2,
+//	// filtering) are all done concurrently.
+//
+//	// Setup barcodes and fastq files
+//	barcode := "barcode06"
+//	r, _ := os.Open("data/reads.fastq")
+//	parser := bio.NewFastqParser(r)
+//
+//	// Setup errorGroups and channels
+//	ctx := context.Background()
+//	errorGroup, ctx := errgroup.WithContext(ctx)
+//
+//	fastqReads := make(chan fastq.Read)
+//	fastqBarcoded := make(chan fastq.Read)
+//	samReads := make(chan sam.Alignment)
+//	samPrimary := make(chan sam.Alignment)
+//
+//	// Read fastqs into channel
+//	errorGroup.Go(func() error {
+//		return parser.ParseToChannel(ctx, fastqReads, false)
+//	})
+//
+//	// Filter the right barcode fastqs from channel
+//	errorGroup.Go(func() error {
+//		return bio.FilterData(ctx, fastqReads, fastqBarcoded, func(data fastq.Read) bool { return data.Optionals["barcode"] == barcode })
+//	})
+//
+//	// Run minimap
+//	errorGroup.Go(func() error {
+//		return minimap2.Minimap2Channeled(&buf, fastqBarcoded, samReads)
+//	})
+//
+//	// Sort out primary alignments
+//	errorGroup.Go(func() error {
+//		return bio.FilterData(ctx, samReads, samPrimary, sam.Primary)
+//	})
+//
+//	// Read all them alignments out into memory
+//	var outputAlignments []sam.Alignment
+//	for alignment := range samPrimary {
+//		outputAlignments = append(outputAlignments, alignment)
+//	}
+//
+//	fmt.Println(outputAlignments[0].RNAME)
+//	// Output: oligo2
+//}
diff --git a/lib/sequencing/sequencing.go b/lib/sequencing/sequencing.go
index fb049e5..0839139 100644
--- a/lib/sequencing/sequencing.go
+++ b/lib/sequencing/sequencing.go
@@ -1,4 +1,60 @@
 /*
 Package sequencing contains functions associated with handling sequencing data.
+
+This is a work-in-progess, and not ready for production.
 */
 package sequencing
+
+import (
+	"context"
+
+	"github.com/koeng101/dnadesign/lib/align/megamash"
+	"github.com/koeng101/dnadesign/lib/bio/fastq"
+	"github.com/koeng101/dnadesign/lib/sequencing/barcoding"
+)
+
+func MegamashFastq(ctx context.Context, megamashMap megamash.MegamashMap, input <-chan fastq.Read, output chan<- fastq.Read) error {
+	for {
+		select {
+		case <-ctx.Done():
+			return ctx.Err()
+		case data, ok := <-input:
+			if !ok {
+				return nil
+			}
+			matches := megamashMap.Match(data.Sequence)
+			jsonStr, _ := megamash.MatchesToJSON(matches)
+			readCopy := data.DeepCopy()
+			readCopy.Optionals["megamash"] = jsonStr
+			select {
+			case output <- readCopy:
+			case <-ctx.Done():
+				return ctx.Err()
+			}
+		}
+	}
+}
+
+func DualBarcodeFastq(ctx context.Context, primerSet barcoding.DualBarcodePrimerSet, input <-chan fastq.Read, output chan<- fastq.Read) error {
+	for {
+		select {
+		case <-ctx.Done():
+			return ctx.Err()
+		case data, ok := <-input:
+			if !ok {
+				return nil
+			}
+			well, err := barcoding.DualBarcodeSequence(data.Sequence, primerSet)
+			if err != nil {
+				return err
+			}
+			readCopy := data.DeepCopy()
+			readCopy.Optionals["dual_barcode"] = well
+			select {
+			case output <- readCopy:
+			case <-ctx.Done():
+				return ctx.Err()
+			}
+		}
+	}
+}